blob: cab46b9eed8fface953358ca779ddd26309329ab [file] [log] [blame]
/*
* Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/cdev.h>
#include <linux/circ_buf.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/genalloc.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mxc_mlb.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/regulator/consumer.h>
#include <linux/sched/signal.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#define DRIVER_NAME "mxc_mlb150"
/*
* MLB module memory map registers define
*/
#define REG_MLBC0 0x0
#define MLBC0_MLBEN (0x1)
#define MLBC0_MLBCLK_MASK (0x7 << 2)
#define MLBC0_MLBCLK_SHIFT (2)
#define MLBC0_MLBPEN (0x1 << 5)
#define MLBC0_MLBLK (0x1 << 7)
#define MLBC0_ASYRETRY (0x1 << 12)
#define MLBC0_CTLRETRY (0x1 << 12)
#define MLBC0_FCNT_MASK (0x7 << 15)
#define MLBC0_FCNT_SHIFT (15)
#define REG_MLBPC0 0x8
#define MLBPC0_MCLKHYS (0x1 << 11)
#define REG_MS0 0xC
#define REG_MS1 0x14
#define REG_MSS 0x20
#define MSS_RSTSYSCMD (0x1)
#define MSS_LKSYSCMD (0x1 << 1)
#define MSS_ULKSYSCMD (0x1 << 2)
#define MSS_CSSYSCMD (0x1 << 3)
#define MSS_SWSYSCMD (0x1 << 4)
#define MSS_SERVREQ (0x1 << 5)
#define REG_MSD 0x24
#define REG_MIEN 0x2C
#define MIEN_ISOC_PE (0x1)
#define MIEN_ISOC_BUFO (0x1 << 1)
#define MIEN_SYNC_PE (0x1 << 16)
#define MIEN_ARX_DONE (0x1 << 17)
#define MIEN_ARX_PE (0x1 << 18)
#define MIEN_ARX_BREAK (0x1 << 19)
#define MIEN_ATX_DONE (0x1 << 20)
#define MIEN_ATX_PE (0x1 << 21)
#define MIEN_ATX_BREAK (0x1 << 22)
#define MIEN_CRX_DONE (0x1 << 24)
#define MIEN_CRX_PE (0x1 << 25)
#define MIEN_CRX_BREAK (0x1 << 26)
#define MIEN_CTX_DONE (0x1 << 27)
#define MIEN_CTX_PE (0x1 << 28)
#define MIEN_CTX_BREAK (0x1 << 29)
#define REG_MLBPC2 0x34
#define REG_MLBPC1 0x38
#define MLBPC1_VAL (0x00000888)
#define REG_MLBC1 0x3C
#define MLBC1_LOCK (0x1 << 6)
#define MLBC1_CLKM (0x1 << 7)
#define MLBC1_NDA_MASK (0xFF << 8)
#define MLBC1_NDA_SHIFT (8)
#define REG_HCTL 0x80
#define HCTL_RST0 (0x1)
#define HCTL_RST1 (0x1 << 1)
#define HCTL_EN (0x1 << 15)
#define REG_HCMR0 0x88
#define REG_HCMR1 0x8C
#define REG_HCER0 0x90
#define REG_HCER1 0x94
#define REG_HCBR0 0x98
#define REG_HCBR1 0x9C
#define REG_MDAT0 0xC0
#define REG_MDAT1 0xC4
#define REG_MDAT2 0xC8
#define REG_MDAT3 0xCC
#define REG_MDWE0 0xD0
#define REG_MDWE1 0xD4
#define REG_MDWE2 0xD8
#define REG_MDWE3 0xDC
#define REG_MCTL 0xE0
#define MCTL_XCMP (0x1)
#define REG_MADR 0xE4
#define MADR_WNR (0x1 << 31)
#define MADR_TB (0x1 << 30)
#define MADR_ADDR_MASK (0x7f << 8)
#define MADR_ADDR_SHIFT (0)
#define REG_ACTL 0x3C0
#define ACTL_MPB (0x1 << 4)
#define ACTL_DMAMODE (0x1 << 2)
#define ACTL_SMX (0x1 << 1)
#define ACTL_SCE (0x1)
#define REG_ACSR0 0x3D0
#define REG_ACSR1 0x3D4
#define REG_ACMR0 0x3D8
#define REG_ACMR1 0x3DC
#define REG_CAT_MDATn(ch) (REG_MDAT0 + ((ch % 8) >> 1) * 4)
#define REG_CAT_MDWEn(ch) (REG_MDWE0 + ((ch % 8) >> 1) * 4)
#define INT_AHB0_CH_START (0)
#define INT_AHB1_CH_START (32)
#define LOGIC_CH_NUM (64)
#define BUF_CDT_OFFSET (0x0)
#define BUF_ADT_OFFSET (0x40)
#define BUF_CAT_MLB_OFFSET (0x80)
#define BUF_CAT_HBI_OFFSET (0x88)
#define BUF_CTR_END_OFFSET (0x8F)
#define CAT_MODE_RX (0x1 << 0)
#define CAT_MODE_TX (0x1 << 1)
#define CAT_MODE_INBOUND_DMA (0x1 << 8)
#define CAT_MODE_OUTBOUND_DMA (0x1 << 9)
#define CH_SYNC_DEFAULT_QUAD (1)
#define CH_SYNC_MAX_QUAD (15)
#define CH_SYNC_CDT_BUF_DEP (CH_SYNC_DEFAULT_QUAD * 4 * 4)
#define CH_SYNC_ADT_BUF_MULTI (4)
#define CH_SYNC_ADT_BUF_DEP (CH_SYNC_CDT_BUF_DEP * CH_SYNC_ADT_BUF_MULTI)
#define CH_SYNC_BUF_SZ (CH_SYNC_MAX_QUAD * 4 * 4 * \
CH_SYNC_ADT_BUF_MULTI)
#define CH_CTRL_CDT_BUF_DEP (64)
#define CH_CTRL_ADT_BUF_DEP (CH_CTRL_CDT_BUF_DEP)
#define CH_CTRL_BUF_SZ (CH_CTRL_ADT_BUF_DEP)
#define CH_ASYNC_MDP_PACKET_LEN (1024)
#define CH_ASYNC_MEP_PACKET_LEN (1536)
#define CH_ASYNC_CDT_BUF_DEP (CH_ASYNC_MEP_PACKET_LEN)
#define CH_ASYNC_ADT_BUF_DEP (CH_ASYNC_CDT_BUF_DEP)
#define CH_ASYNC_BUF_SZ (CH_ASYNC_ADT_BUF_DEP)
#define CH_ISOC_BLK_SIZE_188 (188)
#define CH_ISOC_BLK_SIZE_196 (196)
#define CH_ISOC_BLK_SIZE (CH_ISOC_BLK_SIZE_188)
#define CH_ISOC_BLK_NUM (1)
#define CH_ISOC_CDT_BUF_DEP (CH_ISOC_BLK_SIZE * CH_ISOC_BLK_NUM)
#define CH_ISOC_ADT_BUF_DEP (CH_ISOC_CDT_BUF_DEP)
#define CH_ISOC_BUF_SZ (1024)
#define CH_SYNC_DBR_BUF_OFFSET (0x0)
#define CH_CTRL_DBR_BUF_OFFSET (CH_SYNC_DBR_BUF_OFFSET + \
2 * (CH_SYNC_MAX_QUAD * 4 * 4))
#define CH_ASYNC_DBR_BUF_OFFSET (CH_CTRL_DBR_BUF_OFFSET + \
2 * CH_CTRL_CDT_BUF_DEP)
#define CH_ISOC_DBR_BUF_OFFSET (CH_ASYNC_DBR_BUF_OFFSET + \
2 * CH_ASYNC_CDT_BUF_DEP)
#define DBR_BUF_START 0x00000
#define CDT_LEN (16)
#define ADT_LEN (16)
#define CAT_LEN (2)
#define CDT_SZ (CDT_LEN * LOGIC_CH_NUM)
#define ADT_SZ (ADT_LEN * LOGIC_CH_NUM)
#define CAT_SZ (CAT_LEN * LOGIC_CH_NUM * 2)
#define CDT_BASE(base) (base + BUF_CDT_OFFSET)
#define ADT_BASE(base) (base + BUF_ADT_OFFSET)
#define CAT_MLB_BASE(base) (base + BUF_CAT_MLB_OFFSET)
#define CAT_HBI_BASE(base) (base + BUF_CAT_HBI_OFFSET)
#define CDTn_ADDR(base, n) (base + BUF_CDT_OFFSET + n * CDT_LEN)
#define ADTn_ADDR(base, n) (base + BUF_ADT_OFFSET + n * ADT_LEN)
#define CATn_MLB_ADDR(base, n) (base + BUF_CAT_MLB_OFFSET + n * CAT_LEN)
#define CATn_HBI_ADDR(base, n) (base + BUF_CAT_HBI_OFFSET + n * CAT_LEN)
#define CAT_CL_SHIFT (0x0)
#define CAT_CT_SHIFT (8)
#define CAT_CE (0x1 << 11)
#define CAT_RNW (0x1 << 12)
#define CAT_MT (0x1 << 13)
#define CAT_FCE (0x1 << 14)
#define CAT_MFE (0x1 << 14)
#define CDT_WSBC_SHIFT (14)
#define CDT_WPC_SHIFT (11)
#define CDT_RSBC_SHIFT (30)
#define CDT_RPC_SHIFT (27)
#define CDT_WPC_1_SHIFT (12)
#define CDT_RPC_1_SHIFT (28)
#define CDT_WPTR_SHIFT (0)
#define CDT_SYNC_WSTS_MASK (0x0000f000)
#define CDT_SYNC_WSTS_SHIFT (12)
#define CDT_CTRL_ASYNC_WSTS_MASK (0x0000f000)
#define CDT_CTRL_ASYNC_WSTS_SHIFT (12)
#define CDT_ISOC_WSTS_MASK (0x0000e000)
#define CDT_ISOC_WSTS_SHIFT (13)
#define CDT_RPTR_SHIFT (16)
#define CDT_SYNC_RSTS_MASK (0xf0000000)
#define CDT_SYNC_RSTS_SHIFT (28)
#define CDT_CTRL_ASYNC_RSTS_MASK (0xf0000000)
#define CDT_CTRL_ASYNC_RSTS_SHIFT (28)
#define CDT_ISOC_RSTS_MASK (0xe0000000)
#define CDT_ISOC_RSTS_SHIFT (29)
#define CDT_CTRL_ASYNC_WSTS_1 (0x1 << 14)
#define CDT_CTRL_ASYNC_RSTS_1 (0x1 << 15)
#define CDT_BD_SHIFT (0)
#define CDT_BA_SHIFT (16)
#define CDT_BS_SHIFT (0)
#define CDT_BF_SHIFT (31)
#define ADT_PG (0x1 << 13)
#define ADT_LE (0x1 << 14)
#define ADT_CE (0x1 << 15)
#define ADT_BD1_SHIFT (0)
#define ADT_ERR1 (0x1 << 13)
#define ADT_DNE1 (0x1 << 14)
#define ADT_RDY1 (0x1 << 15)
#define ADT_BD2_SHIFT (16)
#define ADT_ERR2 (0x1 << 29)
#define ADT_DNE2 (0x1 << 30)
#define ADT_RDY2 (0x1 << 31)
#define ADT_BA1_SHIFT (0x0)
#define ADT_BA2_SHIFT (0x0)
#define ADT_PS1 (0x1 << 12)
#define ADT_PS2 (0x1 << 28)
#define ADT_MEP1 (0x1 << 11)
#define ADT_MEP2 (0x1 << 27)
#define MLB_MINOR_DEVICES 4
#define MLB_CONTROL_DEV_NAME "ctrl"
#define MLB_ASYNC_DEV_NAME "async"
#define MLB_SYNC_DEV_NAME "sync"
#define MLB_ISOC_DEV_NAME "isoc"
#define TX_CHANNEL 0
#define RX_CHANNEL 1
#define TRANS_RING_NODES (1 << 3)
#define MLB_QUIRK_MLB150 (1 << 0)
enum MLB_CTYPE {
MLB_CTYPE_SYNC,
MLB_CTYPE_CTRL,
MLB_CTYPE_ASYNC,
MLB_CTYPE_ISOC,
};
enum CLK_SPEED {
CLK_256FS,
CLK_512FS,
CLK_1024FS,
CLK_2048FS,
CLK_3072FS,
CLK_4096FS,
CLK_6144FS,
CLK_8192FS,
};
enum MLB_INDEX {
IMX6Q_MLB = 0,
IMX6SX_MLB,
};
struct mlb_ringbuf {
s8 *virt_bufs[TRANS_RING_NODES];
u32 phy_addrs[TRANS_RING_NODES];
s32 head;
s32 tail;
s32 unit_size;
s32 total_size;
rwlock_t rb_lock ____cacheline_aligned; /* ring index lock */
};
struct mlb_channel_info {
/* Input MLB channel address */
u32 address;
/* Internal AHB channel label */
u32 cl;
/* DBR buf head */
u32 dbr_buf_head;
};
struct mlb_dev_info {
/* device node name */
const char dev_name[20];
/* channel type */
const unsigned int channel_type;
/* ch fps */
enum CLK_SPEED fps;
/* channel info for tx/rx */
struct mlb_channel_info channels[2];
/* ring buffer */
u8 *rbuf_base_virt;
u32 rbuf_base_phy;
struct mlb_ringbuf rx_rbuf;
struct mlb_ringbuf tx_rbuf;
/* exception event */
unsigned long ex_event;
/* tx busy indicator */
unsigned long tx_busy;
/* channel started up or not */
atomic_t on;
/* device open count */
atomic_t opencnt;
/* wait queue head for channel */
wait_queue_head_t rx_wq;
wait_queue_head_t tx_wq;
/* TX OK */
s32 tx_ok;
/* spinlock for event access */
spinlock_t event_lock;
/*
* Block size for isoc mode
* This variable can be configured in ioctl
*/
u32 isoc_blksz;
/*
* Quads number for sync mode
* This variable can be confifured in ioctl
*/
u32 sync_quad;
/* Buffer depth in cdt */
u32 cdt_buf_dep;
/* Buffer depth in adt */
u32 adt_buf_dep;
/* Buffer size to hold data */
u32 buf_size;
};
struct mlb_data {
struct device *dev;
struct mlb_dev_info *devinfo;
#ifdef CONFIG_ARCH_MXC_ARM64
struct clk *ipg;
struct clk *hclk;
#endif
struct clk *mlb;
struct cdev cdev;
struct class *class; /* device class */
dev_t firstdev;
#ifdef CONFIG_REGULATOR
struct regulator *nvcc;
#endif
void __iomem *membase; /* mlb module base address */
struct gen_pool *iram_pool;
u32 iram_size;
int irq_ahb0;
int irq_ahb1;
int irq_mlb;
u32 quirk_flag;
bool use_iram;
};
/*
* For optimization, we use fixed channel label for
* input channels of each mode
* SYNC: CL = 0 for RX, CL = 64 for TX
* CTRL: CL = 1 for RX, CL = 65 for TX
* ASYNC: CL = 2 for RX, CL = 66 for TX
* ISOC: CL = 3 for RX, CL = 67 for TX
*/
#define SYNC_RX_CL_AHB0 0
#define CTRL_RX_CL_AHB0 1
#define ASYNC_RX_CL_AHB0 2
#define ISOC_RX_CL_AHB0 3
#define SYNC_TX_CL_AHB0 4
#define CTRL_TX_CL_AHB0 5
#define ASYNC_TX_CL_AHB0 6
#define ISOC_TX_CL_AHB0 7
#define SYNC_RX_CL_AHB1 32
#define CTRL_RX_CL_AHB1 33
#define ASYNC_RX_CL_AHB1 34
#define ISOC_RX_CL_AHB1 35
#define SYNC_TX_CL_AHB1 36
#define CTRL_TX_CL_AHB1 37
#define ASYNC_TX_CL_AHB1 38
#define ISOC_TX_CL_AHB1 39
#define SYNC_RX_CL SYNC_RX_CL_AHB0
#define CTRL_RX_CL CTRL_RX_CL_AHB0
#define ASYNC_RX_CL ASYNC_RX_CL_AHB0
#define ISOC_RX_CL ISOC_RX_CL_AHB0
#define SYNC_TX_CL SYNC_TX_CL_AHB0
#define CTRL_TX_CL CTRL_TX_CL_AHB0
#define ASYNC_TX_CL ASYNC_TX_CL_AHB0
#define ISOC_TX_CL ISOC_TX_CL_AHB0
static struct mlb_dev_info mlb_devinfo[MLB_MINOR_DEVICES] = {
{
.dev_name = MLB_SYNC_DEV_NAME,
.channel_type = MLB_CTYPE_SYNC,
.channels = {
[0] = {
.cl = SYNC_TX_CL,
.dbr_buf_head = CH_SYNC_DBR_BUF_OFFSET,
},
[1] = {
.cl = SYNC_RX_CL,
.dbr_buf_head = CH_SYNC_DBR_BUF_OFFSET
+ CH_SYNC_BUF_SZ,
},
},
.rx_rbuf = {
.unit_size = CH_SYNC_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[0].rx_rbuf.rb_lock),
},
.tx_rbuf = {
.unit_size = CH_SYNC_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[0].tx_rbuf.rb_lock),
},
.cdt_buf_dep = CH_SYNC_CDT_BUF_DEP,
.adt_buf_dep = CH_SYNC_ADT_BUF_DEP,
.buf_size = CH_SYNC_BUF_SZ,
.on = ATOMIC_INIT(0),
.opencnt = ATOMIC_INIT(0),
.event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[0].event_lock),
},
{
.dev_name = MLB_CONTROL_DEV_NAME,
.channel_type = MLB_CTYPE_CTRL,
.channels = {
[0] = {
.cl = CTRL_TX_CL,
.dbr_buf_head = CH_CTRL_DBR_BUF_OFFSET,
},
[1] = {
.cl = CTRL_RX_CL,
.dbr_buf_head = CH_CTRL_DBR_BUF_OFFSET
+ CH_CTRL_BUF_SZ,
},
},
.rx_rbuf = {
.unit_size = CH_CTRL_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[1].rx_rbuf.rb_lock),
},
.tx_rbuf = {
.unit_size = CH_CTRL_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[1].tx_rbuf.rb_lock),
},
.cdt_buf_dep = CH_CTRL_CDT_BUF_DEP,
.adt_buf_dep = CH_CTRL_ADT_BUF_DEP,
.buf_size = CH_CTRL_BUF_SZ,
.on = ATOMIC_INIT(0),
.opencnt = ATOMIC_INIT(0),
.event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[1].event_lock),
},
{
.dev_name = MLB_ASYNC_DEV_NAME,
.channel_type = MLB_CTYPE_ASYNC,
.channels = {
[0] = {
.cl = ASYNC_TX_CL,
.dbr_buf_head = CH_ASYNC_DBR_BUF_OFFSET,
},
[1] = {
.cl = ASYNC_RX_CL,
.dbr_buf_head = CH_ASYNC_DBR_BUF_OFFSET
+ CH_ASYNC_BUF_SZ,
},
},
.rx_rbuf = {
.unit_size = CH_ASYNC_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[2].rx_rbuf.rb_lock),
},
.tx_rbuf = {
.unit_size = CH_ASYNC_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[2].tx_rbuf.rb_lock),
},
.cdt_buf_dep = CH_ASYNC_CDT_BUF_DEP,
.adt_buf_dep = CH_ASYNC_ADT_BUF_DEP,
.buf_size = CH_ASYNC_BUF_SZ,
.on = ATOMIC_INIT(0),
.opencnt = ATOMIC_INIT(0),
.event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[2].event_lock),
},
{
.dev_name = MLB_ISOC_DEV_NAME,
.channel_type = MLB_CTYPE_ISOC,
.channels = {
[0] = {
.cl = ISOC_TX_CL,
.dbr_buf_head = CH_ISOC_DBR_BUF_OFFSET,
},
[1] = {
.cl = ISOC_RX_CL,
.dbr_buf_head = CH_ISOC_DBR_BUF_OFFSET
+ CH_ISOC_BUF_SZ,
},
},
.rx_rbuf = {
.unit_size = CH_ISOC_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[3].rx_rbuf.rb_lock),
},
.tx_rbuf = {
.unit_size = CH_ISOC_BUF_SZ,
.rb_lock =
__RW_LOCK_UNLOCKED(mlb_devinfo[3].tx_rbuf.rb_lock),
},
.cdt_buf_dep = CH_ISOC_CDT_BUF_DEP,
.adt_buf_dep = CH_ISOC_ADT_BUF_DEP,
.buf_size = CH_ISOC_BUF_SZ,
.on = ATOMIC_INIT(0),
.opencnt = ATOMIC_INIT(0),
.event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[3].event_lock),
.isoc_blksz = CH_ISOC_BLK_SIZE_188,
},
};
static void __iomem *mlb_base;
DEFINE_SPINLOCK(ctr_lock);
#ifdef DEBUG
#define DUMP_REG(reg) pr_debug(#reg": 0x%08x\n", __raw_readl(mlb_base + reg))
static void mlb150_dev_dump_reg(void)
{
pr_debug("mxc_mlb150: Dump registers:\n");
DUMP_REG(REG_MLBC0);
DUMP_REG(REG_MLBPC0);
DUMP_REG(REG_MS0);
DUMP_REG(REG_MS1);
DUMP_REG(REG_MSS);
DUMP_REG(REG_MSD);
DUMP_REG(REG_MIEN);
DUMP_REG(REG_MLBPC2);
DUMP_REG(REG_MLBPC1);
DUMP_REG(REG_MLBC1);
DUMP_REG(REG_HCTL);
DUMP_REG(REG_HCMR0);
DUMP_REG(REG_HCMR1);
DUMP_REG(REG_HCER0);
DUMP_REG(REG_HCER1);
DUMP_REG(REG_HCBR0);
DUMP_REG(REG_HCBR1);
DUMP_REG(REG_MDAT0);
DUMP_REG(REG_MDAT1);
DUMP_REG(REG_MDAT2);
DUMP_REG(REG_MDAT3);
DUMP_REG(REG_MDWE0);
DUMP_REG(REG_MDWE1);
DUMP_REG(REG_MDWE2);
DUMP_REG(REG_MDWE3);
DUMP_REG(REG_MCTL);
DUMP_REG(REG_MADR);
DUMP_REG(REG_ACTL);
DUMP_REG(REG_ACSR0);
DUMP_REG(REG_ACSR1);
DUMP_REG(REG_ACMR0);
DUMP_REG(REG_ACMR1);
}
static void mlb150_dev_dump_hex(const u8 *buf, u32 len)
{
print_hex_dump(KERN_DEBUG, "CTR DUMP:",
DUMP_PREFIX_OFFSET, 8, 1, buf, len, 0);
}
#endif
static inline void mlb150_dev_enable_ctr_write(u32 mdat0_bits_en,
u32 mdat1_bits_en, u32 mdat2_bits_en, u32 mdat3_bits_en)
{
__raw_writel(mdat0_bits_en, mlb_base + REG_MDWE0);
__raw_writel(mdat1_bits_en, mlb_base + REG_MDWE1);
__raw_writel(mdat2_bits_en, mlb_base + REG_MDWE2);
__raw_writel(mdat3_bits_en, mlb_base + REG_MDWE3);
}
#ifdef DEBUG
static inline u8 mlb150_dev_dbr_read(u32 dbr_addr)
{
s32 timeout = 1000;
u8 dbr_val = 0;
unsigned long flags;
spin_lock_irqsave(&ctr_lock, flags);
__raw_writel(MADR_TB | dbr_addr,
mlb_base + REG_MADR);
while ((!(__raw_readl(mlb_base + REG_MCTL)
& MCTL_XCMP)) &&
timeout--)
;
if (0 == timeout) {
spin_unlock_irqrestore(&ctr_lock, flags);
return -ETIME;
}
dbr_val = __raw_readl(mlb_base + REG_MDAT0) & 0x000000ff;
__raw_writel(0, mlb_base + REG_MCTL);
spin_unlock_irqrestore(&ctr_lock, flags);
return dbr_val;
}
static inline s32 mlb150_dev_dbr_write(u32 dbr_addr, u8 dbr_val)
{
s32 timeout = 1000;
u32 mdat0 = dbr_val & 0x000000ff;
unsigned long flags;
spin_lock_irqsave(&ctr_lock, flags);
__raw_writel(mdat0, mlb_base + REG_MDAT0);
__raw_writel(MADR_WNR | MADR_TB | dbr_addr,
mlb_base + REG_MADR);
while ((!(__raw_readl(mlb_base + REG_MCTL)
& MCTL_XCMP)) &&
timeout--)
;
if (timeout <= 0) {
spin_unlock_irqrestore(&ctr_lock, flags);
return -ETIME;
}
__raw_writel(0, mlb_base + REG_MCTL);
spin_unlock_irqrestore(&ctr_lock, flags);
return 0;
}
static inline s32 mlb150_dev_dbr_dump(u32 addr, u32 size)
{
u8 *dump_buf = NULL;
u8 *buf_ptr = NULL;
s32 i;
dump_buf = kzalloc(size, GFP_KERNEL);
if (!dump_buf) {
pr_err("can't allocate enough memory\n");
return -ENOMEM;
}
for (i = 0, buf_ptr = dump_buf;
i < size; ++i, ++buf_ptr)
*buf_ptr = mlb150_dev_dbr_read(addr + i);
mlb150_dev_dump_hex(dump_buf, size);
kfree(dump_buf);
return 0;
}
#endif
static s32 mlb150_dev_ctr_read(u32 ctr_offset, u32 *ctr_val)
{
s32 timeout = 1000;
unsigned long flags;
spin_lock_irqsave(&ctr_lock, flags);
__raw_writel(ctr_offset, mlb_base + REG_MADR);
while ((!(__raw_readl(mlb_base + REG_MCTL)
& MCTL_XCMP)) &&
timeout--)
;
if (timeout <= 0) {
spin_unlock_irqrestore(&ctr_lock, flags);
pr_debug("mxc_mlb150: Read CTR timeout\n");
return -ETIME;
}
ctr_val[0] = __raw_readl(mlb_base + REG_MDAT0);
ctr_val[1] = __raw_readl(mlb_base + REG_MDAT1);
ctr_val[2] = __raw_readl(mlb_base + REG_MDAT2);
ctr_val[3] = __raw_readl(mlb_base + REG_MDAT3);
__raw_writel(0, mlb_base + REG_MCTL);
spin_unlock_irqrestore(&ctr_lock, flags);
return 0;
}
static s32 mlb150_dev_ctr_write(u32 ctr_offset, const u32 *ctr_val)
{
s32 timeout = 1000;
unsigned long flags;
spin_lock_irqsave(&ctr_lock, flags);
__raw_writel(ctr_val[0], mlb_base + REG_MDAT0);
__raw_writel(ctr_val[1], mlb_base + REG_MDAT1);
__raw_writel(ctr_val[2], mlb_base + REG_MDAT2);
__raw_writel(ctr_val[3], mlb_base + REG_MDAT3);
__raw_writel(MADR_WNR | ctr_offset,
mlb_base + REG_MADR);
while ((!(__raw_readl(mlb_base + REG_MCTL)
& MCTL_XCMP)) &&
timeout--)
;
if (timeout <= 0) {
spin_unlock_irqrestore(&ctr_lock, flags);
pr_debug("mxc_mlb150: Write CTR timeout\n");
return -ETIME;
}
__raw_writel(0, mlb_base + REG_MCTL);
spin_unlock_irqrestore(&ctr_lock, flags);
#ifdef DEBUG_CTR
{
u32 ctr_rd[4] = { 0 };
if (!mlb150_dev_ctr_read(ctr_offset, ctr_rd)) {
if (ctr_val[0] == ctr_rd[0] &&
ctr_val[1] == ctr_rd[1] &&
ctr_val[2] == ctr_rd[2] &&
ctr_val[3] == ctr_rd[3])
return 0;
else {
pr_debug("mxc_mlb150: ctr write failed\n");
pr_debug("offset: 0x%x\n", ctr_offset);
pr_debug("Write: 0x%x 0x%x 0x%x 0x%x\n",
ctr_val[3], ctr_val[2],
ctr_val[1], ctr_val[0]);
pr_debug("Read: 0x%x 0x%x 0x%x 0x%x\n",
ctr_rd[3], ctr_rd[2],
ctr_rd[1], ctr_rd[0]);
return -EBADE;
}
} else {
pr_debug("mxc_mlb150: ctr read failed\n");
return -EBADE;
}
}
#endif
return 0;
}
#ifdef DEBUG
static s32 mlb150_dev_cat_read(u32 ctr_offset, u32 ch, u16 *cat_val)
{
u16 ctr_val[8] = { 0 };
if (mlb150_dev_ctr_read(ctr_offset, (u32 *)ctr_val))
return -ETIME;
/*
* Use u16 array to get u32 array value,
* need to convert
*/
cat_val = ctr_val[ch % 8];
return 0;
}
#endif
static s32 mlb150_dev_cat_write(u32 ctr_offset, u32 ch, const u16 cat_val)
{
u16 ctr_val[8] = { 0 };
if (mlb150_dev_ctr_read(ctr_offset, (u32 *)ctr_val))
return -ETIME;
ctr_val[ch % 8] = cat_val;
if (mlb150_dev_ctr_write(ctr_offset, (u32 *)ctr_val))
return -ETIME;
return 0;
}
#define mlb150_dev_cat_mlb_read(ch, cat_val) \
mlb150_dev_cat_read(BUF_CAT_MLB_OFFSET + (ch >> 3), ch, cat_val)
#define mlb150_dev_cat_mlb_write(ch, cat_val) \
mlb150_dev_cat_write(BUF_CAT_MLB_OFFSET + (ch >> 3), ch, cat_val)
#define mlb150_dev_cat_hbi_read(ch, cat_val) \
mlb150_dev_cat_read(BUF_CAT_HBI_OFFSET + (ch >> 3), ch, cat_val)
#define mlb150_dev_cat_hbi_write(ch, cat_val) \
mlb150_dev_cat_write(BUF_CAT_HBI_OFFSET + (ch >> 3), ch, cat_val)
#define mlb150_dev_cdt_read(ch, cdt_val) \
mlb150_dev_ctr_read(BUF_CDT_OFFSET + ch, cdt_val)
#define mlb150_dev_cdt_write(ch, cdt_val) \
mlb150_dev_ctr_write(BUF_CDT_OFFSET + ch, cdt_val)
#define mlb150_dev_adt_read(ch, adt_val) \
mlb150_dev_ctr_read(BUF_ADT_OFFSET + ch, adt_val)
#define mlb150_dev_adt_write(ch, adt_val) \
mlb150_dev_ctr_write(BUF_ADT_OFFSET + ch, adt_val)
static s32 mlb150_dev_get_adt_sts(u32 ch)
{
s32 timeout = 1000;
unsigned long flags;
u32 reg;
spin_lock_irqsave(&ctr_lock, flags);
__raw_writel(BUF_ADT_OFFSET + ch,
mlb_base + REG_MADR);
while ((!(__raw_readl(mlb_base + REG_MCTL)
& MCTL_XCMP)) &&
timeout--)
;
if (timeout <= 0) {
spin_unlock_irqrestore(&ctr_lock, flags);
pr_debug("mxc_mlb150: Read CTR timeout\n");
return -ETIME;
}
reg = __raw_readl(mlb_base + REG_MDAT1);
__raw_writel(0, mlb_base + REG_MCTL);
spin_unlock_irqrestore(&ctr_lock, flags);
#ifdef DEBUG_ADT
pr_debug("mxc_mlb150: Get ch %d adt sts: 0x%08x\n", ch, reg);
#endif
return reg;
}
#ifdef DEBUG
static void mlb150_dev_dump_ctr_tbl(u32 ch_start, u32 ch_end)
{
u32 i = 0;
u32 ctr_val[4] = { 0 };
pr_debug("mxc_mlb150: CDT Table");
for (i = BUF_CDT_OFFSET + ch_start;
i < BUF_CDT_OFFSET + ch_end;
++i) {
mlb150_dev_ctr_read(i, ctr_val);
pr_debug("CTR 0x%02x: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
i, ctr_val[3], ctr_val[2], ctr_val[1], ctr_val[0]);
}
pr_debug("mxc_mlb150: ADT Table");
for (i = BUF_ADT_OFFSET + ch_start;
i < BUF_ADT_OFFSET + ch_end;
++i) {
mlb150_dev_ctr_read(i, ctr_val);
pr_debug("CTR 0x%02x: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
i, ctr_val[3], ctr_val[2], ctr_val[1], ctr_val[0]);
}
pr_debug("mxc_mlb150: CAT MLB Table");
for (i = BUF_CAT_MLB_OFFSET + (ch_start >> 3);
i <= BUF_CAT_MLB_OFFSET + ((ch_end + 8) >> 3);
++i) {
mlb150_dev_ctr_read(i, ctr_val);
pr_debug("CTR 0x%02x: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
i, ctr_val[3], ctr_val[2], ctr_val[1], ctr_val[0]);
}
pr_debug("mxc_mlb150: CAT HBI Table");
for (i = BUF_CAT_HBI_OFFSET + (ch_start >> 3);
i <= BUF_CAT_HBI_OFFSET + ((ch_end + 8) >> 3);
++i) {
mlb150_dev_ctr_read(i, ctr_val);
pr_debug("CTR 0x%02x: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
i, ctr_val[3], ctr_val[2], ctr_val[1], ctr_val[0]);
}
}
#endif
/*
* Initial the MLB module device
*/
static inline void mlb150_dev_enable_dma_irq(u32 enable)
{
u32 ch_rx_mask = (1 << SYNC_RX_CL_AHB0) | (1 << CTRL_RX_CL_AHB0)
| (1 << ASYNC_RX_CL_AHB0) | (1 << ISOC_RX_CL_AHB0)
| (1 << SYNC_TX_CL_AHB0) | (1 << CTRL_TX_CL_AHB0)
| (1 << ASYNC_TX_CL_AHB0) | (1 << ISOC_TX_CL_AHB0);
u32 ch_tx_mask = (1 << (SYNC_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (CTRL_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ASYNC_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ISOC_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (SYNC_TX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (CTRL_TX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ASYNC_TX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ISOC_TX_CL_AHB1 - INT_AHB1_CH_START));
if (enable) {
__raw_writel(ch_rx_mask, mlb_base + REG_ACMR0);
__raw_writel(ch_tx_mask, mlb_base + REG_ACMR1);
} else {
__raw_writel(0x0, mlb_base + REG_ACMR0);
__raw_writel(0x0, mlb_base + REG_ACMR1);
}
}
static void mlb150_dev_init_ir_amba_ahb(void)
{
u32 reg = 0;
/*
* Step 1. Program the ACMRn registers to enable interrupts from all
* active DMA channels
*/
mlb150_dev_enable_dma_irq(1);
/*
* Step 2. Select the status clear method:
* ACTL.SCE = 0, hardware clears on read
* ACTL.SCE = 1, software writes a '1' to clear
* We only support DMA MODE 1
*/
reg = __raw_readl(mlb_base + REG_ACTL);
reg |= ACTL_DMAMODE;
#ifdef MULTIPLE_PACKAGE_MODE
reg |= REG_ACTL_MPB;
#endif
/*
* Step 3. Select 1 or 2 interrupt signals:
* ACTL.SMX = 0: one interrupt for channels 0 - 31 on ahb_init[0]
* and another interrupt for channels 32 - 63 on ahb_init[1]
* ACTL.SMX = 1: singel interrupt all channels on ahb_init[0]
*/
reg &= ~ACTL_SMX;
__raw_writel(reg, mlb_base + REG_ACTL);
}
static inline void mlb150_dev_enable_ir_mlb(u32 enable)
{
/*
* Step 1, Select the MSn to be cleared by software,
* writing a '0' to the appropriate bits
*/
__raw_writel(0, mlb_base + REG_MS0);
__raw_writel(0, mlb_base + REG_MS1);
/*
* Step 1, Program MIEN to enable protocol error
* interrupts for all active MLB channels
*/
if (enable)
__raw_writel(MIEN_CTX_PE |
MIEN_CRX_PE | MIEN_ATX_PE |
MIEN_ARX_PE | MIEN_SYNC_PE |
MIEN_ISOC_PE,
mlb_base + REG_MIEN);
else
__raw_writel(0, mlb_base + REG_MIEN);
}
static inline void mlb150_enable_pll(struct mlb_data *drvdata)
{
u32 c0_val;
__raw_writel(MLBPC1_VAL,
drvdata->membase + REG_MLBPC1);
c0_val = __raw_readl(drvdata->membase + REG_MLBC0);
if (c0_val & MLBC0_MLBPEN) {
c0_val &= ~MLBC0_MLBPEN;
__raw_writel(c0_val,
drvdata->membase + REG_MLBC0);
}
c0_val |= (MLBC0_MLBPEN);
__raw_writel(c0_val, drvdata->membase + REG_MLBC0);
}
static inline void mlb150_disable_pll(struct mlb_data *drvdata)
{
u32 c0_val;
c0_val = __raw_readl(drvdata->membase + REG_MLBC0);
__raw_writel(0x0, drvdata->membase + REG_MLBPC1);
c0_val &= ~MLBC0_MLBPEN;
__raw_writel(c0_val, drvdata->membase + REG_MLBC0);
}
static void mlb150_dev_reset_cdt(void)
{
int i = 0;
u32 ctr_val[4] = { 0 };
mlb150_dev_enable_ctr_write(0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff);
for (i = 0; i < (LOGIC_CH_NUM); ++i)
mlb150_dev_ctr_write(BUF_CDT_OFFSET + i, ctr_val);
}
static s32 mlb150_dev_init_ch_cdt(struct mlb_dev_info *pdevinfo, u32 ch,
enum MLB_CTYPE ctype, u32 ch_func)
{
u32 cdt_val[4] = { 0 };
/* a. Set the 14-bit base address (BA) */
pr_debug("mxc_mlb150: ctype: %d, ch: %d, dbr_buf_head: 0x%08x",
ctype, ch, pdevinfo->channels[ch_func].dbr_buf_head);
cdt_val[3] = (pdevinfo->channels[ch_func].dbr_buf_head)
<< CDT_BA_SHIFT;
/*
* b. Set the 12-bit or 13-bit buffer depth (BD)
* BD = buffer depth in bytes - 1
* For synchronous channels: (BD + 1) = 4 * m * bpf
* For control channels: (BD + 1) >= max packet length (64)
* For asynchronous channels: (BD + 1) >= max packet length
* 1024 for a MOST Data packet (MDP);
* 1536 for a MOST Ethernet Packet (MEP)
* For isochronous channels: (BD + 1) mod (BS + 1) = 0
* BS
*/
if (MLB_CTYPE_ISOC == ctype)
cdt_val[1] |= (pdevinfo->isoc_blksz - 1);
/* BD */
cdt_val[3] |= (pdevinfo->cdt_buf_dep - 1) << CDT_BD_SHIFT;
pr_debug("mxc_mlb150: Set CDT val of channel %d, type: %d: "
"0x%08x 0x%08x 0x%08x 0x%08x\n",
ch, ctype, cdt_val[3], cdt_val[2], cdt_val[1], cdt_val[0]);
if (mlb150_dev_cdt_write(ch, cdt_val))
return -ETIME;
#ifdef DEBUG_CTR
{
u32 cdt_rd[4] = { 0 };
if (!mlb150_dev_cdt_read(ch, cdt_rd)) {
pr_debug("mxc_mlb150: CDT val of channel %d: "
"0x%08x 0x%08x 0x%08x 0x%08x\n",
ch, cdt_rd[3], cdt_rd[2], cdt_rd[1], cdt_rd[0]);
if (cdt_rd[3] == cdt_val[3] &&
cdt_rd[2] == cdt_val[2] &&
cdt_rd[1] == cdt_val[1] &&
cdt_rd[0] == cdt_val[0]) {
pr_debug("mxc_mlb150: set cdt succeed!\n");
return 0;
} else {
pr_debug("mxc_mlb150: set cdt failed!\n");
return -EBADE;
}
} else {
pr_debug("mxc_mlb150: Read CDT val of channel %d failed\n",
ch);
return -EBADE;
}
}
#endif
return 0;
}
static s32 mlb150_dev_init_ch_cat(u32 ch, u32 cl,
u32 cat_mode, enum MLB_CTYPE ctype)
{
u16 cat_val = 0;
#ifdef DEBUG_CTR
u16 cat_rd = 0;
#endif
cat_val = CAT_CE | (ctype << CAT_CT_SHIFT) | cl;
if (cat_mode & CAT_MODE_OUTBOUND_DMA)
cat_val |= CAT_RNW;
if (MLB_CTYPE_SYNC == ctype)
cat_val |= CAT_MT;
switch (cat_mode) {
case CAT_MODE_RX | CAT_MODE_INBOUND_DMA:
case CAT_MODE_TX | CAT_MODE_OUTBOUND_DMA:
pr_debug("mxc_mlb150: set CAT val of channel %d, type: %d: 0x%04x\n",
ch, ctype, cat_val);
if (mlb150_dev_cat_mlb_write(ch, cat_val))
return -ETIME;
#ifdef DEBUG_CTR
if (!mlb150_dev_cat_mlb_read(ch, &cat_rd))
pr_debug("mxc_mlb150: CAT val of mlb channel %d: 0x%04x",
ch, cat_rd);
else {
pr_debug("mxc_mlb150: Read CAT of mlb channel %d failed\n",
ch);
return -EBADE;
}
#endif
break;
case CAT_MODE_TX | CAT_MODE_INBOUND_DMA:
case CAT_MODE_RX | CAT_MODE_OUTBOUND_DMA:
pr_debug("mxc_mlb150: set CAT val of channel %d, type: %d: 0x%04x\n",
cl, ctype, cat_val);
if (mlb150_dev_cat_hbi_write(cl, cat_val))
return -ETIME;
#ifdef DEBUG_CTR
if (!mlb150_dev_cat_hbi_read(cl, &cat_rd))
pr_debug("mxc_mlb150: CAT val of hbi channel %d: 0x%04x",
cl, cat_rd);
else {
pr_debug("mxc_mlb150: Read CAT of hbi channel %d failed\n",
cl);
return -EBADE;
}
#endif
break;
default:
return EBADRQC;
}
#ifdef DEBUG_CTR
{
if (cat_val == cat_rd) {
pr_debug("mxc_mlb150: set cat succeed!\n");
return 0;
} else {
pr_debug("mxc_mlb150: set cat failed!\n");
return -EBADE;
}
}
#endif
return 0;
}
static void mlb150_dev_reset_cat(void)
{
int i = 0;
u32 ctr_val[4] = { 0 };
mlb150_dev_enable_ctr_write(0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff);
for (i = 0; i < (LOGIC_CH_NUM >> 3); ++i) {
mlb150_dev_ctr_write(BUF_CAT_MLB_OFFSET + i, ctr_val);
mlb150_dev_ctr_write(BUF_CAT_HBI_OFFSET + i, ctr_val);
}
}
static void mlb150_dev_init_rfb(struct mlb_dev_info *pdevinfo, u32 rx_ch,
u32 tx_ch, enum MLB_CTYPE ctype)
{
u32 rx_cl = pdevinfo->channels[RX_CHANNEL].cl;
u32 tx_cl = pdevinfo->channels[TX_CHANNEL].cl;
/* Step 1, Initialize all bits of CAT to '0' */
mlb150_dev_reset_cat();
mlb150_dev_reset_cdt();
/*
* Step 2, Initialize logical channel
* Step 3, Program the CDT for channel N
*/
mlb150_dev_init_ch_cdt(pdevinfo, rx_cl, ctype, RX_CHANNEL);
mlb150_dev_init_ch_cdt(pdevinfo, tx_cl, ctype, TX_CHANNEL);
/* Step 4&5, Program the CAT for the inbound and outbound DMA */
mlb150_dev_init_ch_cat(rx_ch, rx_cl,
CAT_MODE_RX | CAT_MODE_INBOUND_DMA,
ctype);
mlb150_dev_init_ch_cat(rx_ch, rx_cl,
CAT_MODE_RX | CAT_MODE_OUTBOUND_DMA,
ctype);
mlb150_dev_init_ch_cat(tx_ch, tx_cl,
CAT_MODE_TX | CAT_MODE_INBOUND_DMA,
ctype);
mlb150_dev_init_ch_cat(tx_ch, tx_cl,
CAT_MODE_TX | CAT_MODE_OUTBOUND_DMA,
ctype);
}
static void mlb150_dev_reset_adt(void)
{
int i = 0;
u32 ctr_val[4] = { 0 };
mlb150_dev_enable_ctr_write(0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff);
for (i = 0; i < (LOGIC_CH_NUM); ++i)
mlb150_dev_ctr_write(BUF_ADT_OFFSET + i, ctr_val);
}
static void mlb150_dev_reset_whole_ctr(void)
{
mlb150_dev_enable_ctr_write(0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff);
mlb150_dev_reset_cdt();
mlb150_dev_reset_adt();
mlb150_dev_reset_cat();
}
#define CLR_REG(reg) __raw_writel(0x0, mlb_base + reg)
static void mlb150_dev_reset_all_regs(void)
{
CLR_REG(REG_MLBC0);
CLR_REG(REG_MLBPC0);
CLR_REG(REG_MS0);
CLR_REG(REG_MS1);
CLR_REG(REG_MSS);
CLR_REG(REG_MSD);
CLR_REG(REG_MIEN);
CLR_REG(REG_MLBPC2);
CLR_REG(REG_MLBPC1);
CLR_REG(REG_MLBC1);
CLR_REG(REG_HCTL);
CLR_REG(REG_HCMR0);
CLR_REG(REG_HCMR1);
CLR_REG(REG_HCER0);
CLR_REG(REG_HCER1);
CLR_REG(REG_HCBR0);
CLR_REG(REG_HCBR1);
CLR_REG(REG_MDAT0);
CLR_REG(REG_MDAT1);
CLR_REG(REG_MDAT2);
CLR_REG(REG_MDAT3);
CLR_REG(REG_MDWE0);
CLR_REG(REG_MDWE1);
CLR_REG(REG_MDWE2);
CLR_REG(REG_MDWE3);
CLR_REG(REG_MCTL);
CLR_REG(REG_MADR);
CLR_REG(REG_ACTL);
CLR_REG(REG_ACSR0);
CLR_REG(REG_ACSR1);
CLR_REG(REG_ACMR0);
CLR_REG(REG_ACMR1);
}
static inline s32 mlb150_dev_pipo_start(struct mlb_ringbuf *rbuf,
u32 ahb_ch, u32 buf_addr)
{
u32 ctr_val[4] = { 0 };
ctr_val[1] |= ADT_RDY1;
ctr_val[2] = buf_addr;
if (mlb150_dev_adt_write(ahb_ch, ctr_val))
return -ETIME;
return 0;
}
static inline s32 mlb150_dev_pipo_next(u32 ahb_ch, enum MLB_CTYPE ctype,
u32 dne_sts, u32 buf_addr)
{
u32 ctr_val[4] = { 0 };
if (MLB_CTYPE_ASYNC == ctype ||
MLB_CTYPE_CTRL == ctype) {
ctr_val[1] |= ADT_PS1;
ctr_val[1] |= ADT_PS2;
}
/*
* Clear DNE1 and ERR1
* Set the page ready bit (RDY1)
*/
if (dne_sts & ADT_DNE1) {
ctr_val[1] |= ADT_RDY2;
ctr_val[3] = buf_addr;
} else {
ctr_val[1] |= ADT_RDY1;
ctr_val[2] = buf_addr;
}
if (mlb150_dev_adt_write(ahb_ch, ctr_val))
return -ETIME;
return 0;
}
static inline s32 mlb150_dev_pipo_stop(struct mlb_ringbuf *rbuf, u32 ahb_ch)
{
u32 ctr_val[4] = { 0 };
unsigned long flags;
write_lock_irqsave(&rbuf->rb_lock, flags);
rbuf->head = rbuf->tail = 0;
write_unlock_irqrestore(&rbuf->rb_lock, flags);
if (mlb150_dev_adt_write(ahb_ch, ctr_val))
return -ETIME;
return 0;
}
static s32 mlb150_dev_init_ch_amba_ahb(struct mlb_dev_info *pdevinfo,
struct mlb_channel_info *chinfo,
enum MLB_CTYPE ctype)
{
u32 ctr_val[4] = { 0 };
/* a. Set the 32-bit base address (BA1) */
ctr_val[3] = 0;
ctr_val[2] = 0;
ctr_val[1] = (pdevinfo->adt_buf_dep - 1) << ADT_BD1_SHIFT;
ctr_val[1] |= (pdevinfo->adt_buf_dep - 1) << ADT_BD2_SHIFT;
if (MLB_CTYPE_ASYNC == ctype ||
MLB_CTYPE_CTRL == ctype) {
ctr_val[1] |= ADT_PS1;
ctr_val[1] |= ADT_PS2;
}
ctr_val[0] |= (ADT_LE | ADT_CE);
pr_debug("mxc_mlb150: Set ADT val of channel %d, ctype: %d: "
"0x%08x 0x%08x 0x%08x 0x%08x\n",
chinfo->cl, ctype, ctr_val[3], ctr_val[2],
ctr_val[1], ctr_val[0]);
if (mlb150_dev_adt_write(chinfo->cl, ctr_val))
return -ETIME;
#ifdef DEBUG_CTR
{
u32 ctr_rd[4] = { 0 };
if (!mlb150_dev_adt_read(chinfo->cl, ctr_rd)) {
pr_debug("mxc_mlb150: ADT val of channel %d: "
"0x%08x 0x%08x 0x%08x 0x%08x\n",
chinfo->cl, ctr_rd[3], ctr_rd[2],
ctr_rd[1], ctr_rd[0]);
if (ctr_rd[3] == ctr_val[3] &&
ctr_rd[2] == ctr_val[2] &&
ctr_rd[1] == ctr_val[1] &&
ctr_rd[0] == ctr_val[0]) {
pr_debug("mxc_mlb150: set adt succeed!\n");
return 0;
} else {
pr_debug("mxc_mlb150: set adt failed!\n");
return -EBADE;
}
} else {
pr_debug("mxc_mlb150: Read ADT val of channel %d failed\n",
chinfo->cl);
return -EBADE;
}
}
#endif
return 0;
}
static void mlb150_dev_init_amba_ahb(struct mlb_dev_info *pdevinfo,
enum MLB_CTYPE ctype)
{
struct mlb_channel_info *tx_chinfo = &pdevinfo->channels[TX_CHANNEL];
struct mlb_channel_info *rx_chinfo = &pdevinfo->channels[RX_CHANNEL];
/* Step 1, Initialize all bits of the ADT to '0' */
mlb150_dev_reset_adt();
/*
* Step 2, Select a logic channel
* Step 3, Program the AMBA AHB block ping page for channel N
* Step 4, Program the AMBA AHB block pong page for channel N
*/
mlb150_dev_init_ch_amba_ahb(pdevinfo, rx_chinfo, ctype);
mlb150_dev_init_ch_amba_ahb(pdevinfo, tx_chinfo, ctype);
}
static void mlb150_dev_exit(void)
{
u32 c0_val, hctl_val;
/* Disable EN bits */
c0_val = __raw_readl(mlb_base + REG_MLBC0);
c0_val &= ~(MLBC0_MLBEN | MLBC0_MLBPEN);
__raw_writel(c0_val, mlb_base + REG_MLBC0);
hctl_val = __raw_readl(mlb_base + REG_HCTL);
hctl_val &= ~HCTL_EN;
__raw_writel(hctl_val, mlb_base + REG_HCTL);
__raw_writel(0x0, mlb_base + REG_HCMR0);
__raw_writel(0x0, mlb_base + REG_HCMR1);
mlb150_dev_enable_dma_irq(0);
mlb150_dev_enable_ir_mlb(0);
}
static void mlb150_dev_init(void)
{
u32 c0_val;
u32 ch_rx_mask = (1 << SYNC_RX_CL_AHB0) | (1 << CTRL_RX_CL_AHB0)
| (1 << ASYNC_RX_CL_AHB0) | (1 << ISOC_RX_CL_AHB0)
| (1 << SYNC_TX_CL_AHB0) | (1 << CTRL_TX_CL_AHB0)
| (1 << ASYNC_TX_CL_AHB0) | (1 << ISOC_TX_CL_AHB0);
u32 ch_tx_mask = (1 << (SYNC_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (CTRL_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ASYNC_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ISOC_RX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (SYNC_TX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (CTRL_TX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ASYNC_TX_CL_AHB1 - INT_AHB1_CH_START)) |
(1 << (ISOC_TX_CL_AHB1 - INT_AHB1_CH_START));
/* Disable EN bits */
mlb150_dev_exit();
/*
* Step 1. Initialize CTR and registers
* a. Set all bit of the CTR (CAT, CDT, and ADT) to 0.
*/
mlb150_dev_reset_whole_ctr();
/* a. Set all bit of the CTR (CAT, CDT, and ADT) to 0. */
mlb150_dev_reset_all_regs();
/*
* Step 2, Configure the MediaLB interface
* Select pin mode and clock, 3-pin and 256fs
*/
c0_val = __raw_readl(mlb_base + REG_MLBC0);
c0_val &= ~(MLBC0_MLBPEN | MLBC0_MLBCLK_MASK);
__raw_writel(c0_val, mlb_base + REG_MLBC0);
c0_val |= MLBC0_MLBEN;
__raw_writel(c0_val, mlb_base + REG_MLBC0);
/* Step 3, Configure the HBI interface */
__raw_writel(ch_rx_mask, mlb_base + REG_HCMR0);
__raw_writel(ch_tx_mask, mlb_base + REG_HCMR1);
__raw_writel(HCTL_EN, mlb_base + REG_HCTL);
mlb150_dev_init_ir_amba_ahb();
mlb150_dev_enable_ir_mlb(1);
}
static s32 mlb150_dev_unmute_syn_ch(u32 rx_ch, u32 rx_cl, u32 tx_ch, u32 tx_cl)
{
u32 timeout = 10000;
/*
* Check that MediaLB clock is running (MLBC1.CLKM = 0)
* If MLBC1.CLKM = 1, clear the register bit, wait one
* APB or I/O clock cycle and repeat the check
*/
while ((__raw_readl(mlb_base + REG_MLBC1) & MLBC1_CLKM)
&& --timeout)
__raw_writel(~MLBC1_CLKM, mlb_base + REG_MLBC1);
if (0 == timeout)
return -ETIME;
timeout = 10000;
/* Poll for MLB lock (MLBC0.MLBLK = 1) */
while (!(__raw_readl(mlb_base + REG_MLBC0) & MLBC0_MLBLK)
&& --timeout)
;
if (0 == timeout)
return -ETIME;
/* Unmute synchronous channel(s) */
mlb150_dev_cat_mlb_write(rx_ch, CAT_CE | rx_cl);
mlb150_dev_cat_mlb_write(tx_ch,
CAT_CE | tx_cl | CAT_RNW);
mlb150_dev_cat_hbi_write(rx_cl,
CAT_CE | rx_cl | CAT_RNW);
mlb150_dev_cat_hbi_write(tx_cl, CAT_CE | tx_cl);
return 0;
}
/* In case the user calls channel shutdown, but rx or tx is not completed yet */
static s32 mlb150_trans_complete_check(struct mlb_dev_info *pdevinfo)
{
struct mlb_ringbuf *rx_rbuf = &pdevinfo->rx_rbuf;
struct mlb_ringbuf *tx_rbuf = &pdevinfo->tx_rbuf;
s32 timeout = 1024;
while (timeout--) {
read_lock(&tx_rbuf->rb_lock);
if (!CIRC_CNT(tx_rbuf->head, tx_rbuf->tail, TRANS_RING_NODES)) {
read_unlock(&tx_rbuf->rb_lock);
break;
} else
read_unlock(&tx_rbuf->rb_lock);
}
if (timeout <= 0) {
pr_debug("TX complete check timeout!\n");
return -ETIME;
}
timeout = 1024;
while (timeout--) {
read_lock(&rx_rbuf->rb_lock);
if (!CIRC_CNT(rx_rbuf->head, rx_rbuf->tail, TRANS_RING_NODES)) {
read_unlock(&rx_rbuf->rb_lock);
break;
} else
read_unlock(&rx_rbuf->rb_lock);
}
if (timeout <= 0) {
pr_debug("RX complete check timeout!\n");
return -ETIME;
}
/*
* Interrupt from TX can only inform that the data is sent
* to AHB bus, not mean that it is sent to MITB. Thus we add
* a delay here for data to be completed sent.
*/
udelay(1000);
return 0;
}
/*
* Enable/Disable the MLB IRQ
*/
static void mxc_mlb150_irq_enable(struct mlb_data *drvdata, u8 enable)
{
if (enable) {
enable_irq(drvdata->irq_ahb0);
enable_irq(drvdata->irq_mlb);
if (drvdata->irq_ahb1 > 0)
enable_irq(drvdata->irq_ahb1);
} else {
disable_irq(drvdata->irq_ahb0);
disable_irq(drvdata->irq_mlb);
if (drvdata->irq_ahb1 > 0)
disable_irq(drvdata->irq_ahb1);
}
}
/*
* Enable the MLB channel
*/
static s32 mlb_channel_enable(struct mlb_data *drvdata,
int chan_dev_id, int on)
{
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
struct mlb_channel_info *tx_chinfo = &pdevinfo->channels[TX_CHANNEL];
struct mlb_channel_info *rx_chinfo = &pdevinfo->channels[RX_CHANNEL];
u32 tx_ch = tx_chinfo->address;
u32 rx_ch = rx_chinfo->address;
u32 tx_cl = tx_chinfo->cl;
u32 rx_cl = rx_chinfo->cl;
s32 ret = 0;
/*
* setup the direction, enable, channel type,
* mode select, channel address and mask buf start
*/
if (on) {
u32 ctype = pdevinfo->channel_type;
mlb150_dev_enable_ctr_write(0xffffffff, 0xffffffff,
0xffffffff, 0xffffffff);
mlb150_dev_init_rfb(pdevinfo, rx_ch, tx_ch, ctype);
mlb150_dev_init_amba_ahb(pdevinfo, ctype);
#ifdef DEBUG
mlb150_dev_dump_ctr_tbl(0, tx_chinfo->cl + 1);
#endif
/* Synchronize and unmute synchrouous channel */
if (MLB_CTYPE_SYNC == ctype) {
ret = mlb150_dev_unmute_syn_ch(rx_ch, rx_cl,
tx_ch, tx_cl);
if (ret)
return ret;
}
mlb150_dev_enable_ctr_write(0x0, ADT_RDY1 | ADT_DNE1 |
ADT_ERR1 | ADT_PS1 |
ADT_RDY2 | ADT_DNE2 | ADT_ERR2 | ADT_PS2,
0xffffffff, 0xffffffff);
if (pdevinfo->fps >= CLK_2048FS)
mlb150_enable_pll(drvdata);
atomic_set(&pdevinfo->on, 1);
#ifdef DEBUG
mlb150_dev_dump_reg();
mlb150_dev_dump_ctr_tbl(0, tx_chinfo->cl + 1);
#endif
/* Init RX ADT */
mlb150_dev_pipo_start(&pdevinfo->rx_rbuf, rx_cl,
pdevinfo->rx_rbuf.phy_addrs[0]);
} else {
mlb150_dev_pipo_stop(&pdevinfo->rx_rbuf, rx_cl);
mlb150_dev_enable_dma_irq(0);
mlb150_dev_enable_ir_mlb(0);
mlb150_dev_reset_cat();
atomic_set(&pdevinfo->on, 0);
if (pdevinfo->fps >= CLK_2048FS)
mlb150_disable_pll(drvdata);
}
return 0;
}
/*
* MLB interrupt handler
*/
static void mlb_rx_isr(s32 ctype, u32 ahb_ch, struct mlb_dev_info *pdevinfo)
{
struct mlb_ringbuf *rx_rbuf = &pdevinfo->rx_rbuf;
s32 head, tail, adt_sts;
u32 rx_buf_ptr;
#ifdef DEBUG_RX
pr_debug("mxc_mlb150: mlb_rx_isr\n");
#endif
read_lock(&rx_rbuf->rb_lock);
head = (rx_rbuf->head + 1) & (TRANS_RING_NODES - 1);
tail = ACCESS_ONCE(rx_rbuf->tail);
read_unlock(&rx_rbuf->rb_lock);
if (CIRC_SPACE(head, tail, TRANS_RING_NODES) >= 1) {
rx_buf_ptr = rx_rbuf->phy_addrs[head];
/* commit the item before incrementing the head */
smp_wmb();
write_lock(&rx_rbuf->rb_lock);
rx_rbuf->head = head;
write_unlock(&rx_rbuf->rb_lock);
/* wake up the reader */
wake_up_interruptible(&pdevinfo->rx_wq);
} else {
rx_buf_ptr = rx_rbuf->phy_addrs[head];
pr_debug("drop RX package, due to no space, (%d,%d)\n",
head, tail);
}
adt_sts = mlb150_dev_get_adt_sts(ahb_ch);
/* Set ADT for RX */
mlb150_dev_pipo_next(ahb_ch, ctype, adt_sts, rx_buf_ptr);
}
static void mlb_tx_isr(s32 ctype, u32 ahb_ch, struct mlb_dev_info *pdevinfo)
{
struct mlb_ringbuf *tx_rbuf = &pdevinfo->tx_rbuf;
s32 head, tail, adt_sts;
u32 tx_buf_ptr;
read_lock(&tx_rbuf->rb_lock);
head = ACCESS_ONCE(tx_rbuf->head);
tail = (tx_rbuf->tail + 1) & (TRANS_RING_NODES - 1);
read_unlock(&tx_rbuf->rb_lock);
smp_mb();
write_lock(&tx_rbuf->rb_lock);
tx_rbuf->tail = tail;
write_unlock(&tx_rbuf->rb_lock);
/* check the current tx buffer is available or not */
if (CIRC_CNT(head, tail, TRANS_RING_NODES) >= 1) {
/* read index before reading contents at that index */
smp_read_barrier_depends();
tx_buf_ptr = tx_rbuf->phy_addrs[tail];
wake_up_interruptible(&pdevinfo->tx_wq);
adt_sts = mlb150_dev_get_adt_sts(ahb_ch);
/* Set ADT for TX */
mlb150_dev_pipo_next(ahb_ch, ctype, adt_sts, tx_buf_ptr);
}
}
static irqreturn_t mlb_ahb_isr(int irq, void *dev_id)
{
u32 acsr0, hcer0;
u32 ch_mask = (1 << SYNC_RX_CL) | (1 << CTRL_RX_CL)
| (1 << ASYNC_RX_CL) | (1 << ISOC_RX_CL)
| (1 << SYNC_TX_CL) | (1 << CTRL_TX_CL)
| (1 << ASYNC_TX_CL) | (1 << ISOC_TX_CL);
/*
* Step 5, Read the ACSRn registers to determine which channel or
* channels are causing the interrupt
*/
acsr0 = __raw_readl(mlb_base + REG_ACSR0);
hcer0 = __raw_readl(mlb_base + REG_HCER0);
/*
* Step 6, If ACTL.SCE = 1, write the result of step 5 back to ACSR0
* and ACSR1 to clear the interrupt
* We'll not set ACTL_SCE
*/
if (ch_mask & hcer0)
pr_err("CH encounters an AHB error: 0x%x\n", hcer0);
if ((1 << SYNC_RX_CL) & acsr0)
mlb_rx_isr(MLB_CTYPE_SYNC, SYNC_RX_CL,
&mlb_devinfo[MLB_CTYPE_SYNC]);
if ((1 << CTRL_RX_CL) & acsr0)
mlb_rx_isr(MLB_CTYPE_CTRL, CTRL_RX_CL,
&mlb_devinfo[MLB_CTYPE_CTRL]);
if ((1 << ASYNC_RX_CL) & acsr0)
mlb_rx_isr(MLB_CTYPE_ASYNC, ASYNC_RX_CL,
&mlb_devinfo[MLB_CTYPE_ASYNC]);
if ((1 << ISOC_RX_CL) & acsr0)
mlb_rx_isr(MLB_CTYPE_ISOC, ISOC_RX_CL,
&mlb_devinfo[MLB_CTYPE_ISOC]);
if ((1 << SYNC_TX_CL) & acsr0)
mlb_tx_isr(MLB_CTYPE_SYNC, SYNC_TX_CL,
&mlb_devinfo[MLB_CTYPE_SYNC]);
if ((1 << CTRL_TX_CL) & acsr0)
mlb_tx_isr(MLB_CTYPE_CTRL, CTRL_TX_CL,
&mlb_devinfo[MLB_CTYPE_CTRL]);
if ((1 << ASYNC_TX_CL) & acsr0)
mlb_tx_isr(MLB_CTYPE_ASYNC, ASYNC_TX_CL,
&mlb_devinfo[MLB_CTYPE_ASYNC]);
if ((1 << ISOC_TX_CL) & acsr0)
mlb_tx_isr(MLB_CTYPE_ASYNC, ISOC_TX_CL,
&mlb_devinfo[MLB_CTYPE_ISOC]);
return IRQ_HANDLED;
}
static irqreturn_t mlb_isr(int irq, void *dev_id)
{
u32 rx_int_sts, tx_int_sts, ms0,
ms1, tx_cis, rx_cis, ctype;
int minor;
u32 cdt_val[4] = { 0 };
/*
* Step 4, Read the MSn register to determine which channel(s)
* are causing the interrupt
*/
ms0 = __raw_readl(mlb_base + REG_MS0);
ms1 = __raw_readl(mlb_base + REG_MS1);
/*
* The MLB150_MS0, MLB150_MS1 registers need to be cleared. In
* the spec description, the registers should be cleared when
* enabling interrupt. In fact, we also should clear it in ISR.
*/
__raw_writel(0, mlb_base + REG_MS0);
__raw_writel(0, mlb_base + REG_MS1);
pr_debug("mxc_mlb150: mlb interrupt:0x%08x 0x%08x\n",
(u32)ms0, (u32)ms1);
for (minor = 0; minor < MLB_MINOR_DEVICES; minor++) {
struct mlb_dev_info *pdevinfo = &mlb_devinfo[minor];
u32 rx_mlb_ch = pdevinfo->channels[RX_CHANNEL].address;
u32 tx_mlb_ch = pdevinfo->channels[TX_CHANNEL].address;
u32 rx_mlb_cl = pdevinfo->channels[RX_CHANNEL].cl;
u32 tx_mlb_cl = pdevinfo->channels[TX_CHANNEL].cl;
tx_cis = rx_cis = 0;
ctype = pdevinfo->channel_type;
rx_int_sts = (rx_mlb_ch < 31) ? ms0 : ms1;
tx_int_sts = (tx_mlb_ch < 31) ? ms0 : ms1;
pr_debug("mxc_mlb150: channel interrupt: "
"tx %d: 0x%08x, rx %d: 0x%08x\n",
tx_mlb_ch, (u32)tx_int_sts, rx_mlb_ch, (u32)rx_int_sts);
/* Get tx channel interrupt status */
if (tx_int_sts & (1 << (tx_mlb_ch % 32))) {
mlb150_dev_cdt_read(tx_mlb_cl, cdt_val);
pr_debug("mxc_mlb150: TX_CH: %d, cdt_val[3]: 0x%08x, "
"cdt_val[2]: 0x%08x, "
"cdt_val[1]: 0x%08x, "
"cdt_val[0]: 0x%08x\n",
tx_mlb_ch, cdt_val[3], cdt_val[2],
cdt_val[1], cdt_val[0]);
switch (ctype) {
case MLB_CTYPE_SYNC:
tx_cis = (cdt_val[2] & CDT_SYNC_WSTS_MASK)
>> CDT_SYNC_WSTS_SHIFT;
/*
* Clear RSTS/WSTS errors to resume
* channel operation
* a. For synchronous channels: WSTS[3] = 0
*/
cdt_val[2] &= ~(0x8 << CDT_SYNC_WSTS_SHIFT);
break;
case MLB_CTYPE_CTRL:
case MLB_CTYPE_ASYNC:
tx_cis = (cdt_val[2] &
CDT_CTRL_ASYNC_WSTS_MASK)
>> CDT_CTRL_ASYNC_WSTS_SHIFT;
tx_cis = (cdt_val[3] & CDT_CTRL_ASYNC_WSTS_1) ?
(tx_cis | (0x1 << 4)) : tx_cis;
/*
* b. For async and ctrl channels:
* RSTS[4]/WSTS[4] = 0
* and RSTS[2]/WSTS[2] = 0
*/
cdt_val[3] &= ~CDT_CTRL_ASYNC_WSTS_1;
cdt_val[2] &=
~(0x4 << CDT_CTRL_ASYNC_WSTS_SHIFT);
break;
case MLB_CTYPE_ISOC:
tx_cis = (cdt_val[2] & CDT_ISOC_WSTS_MASK)
>> CDT_ISOC_WSTS_SHIFT;
/* c. For isoc channels: WSTS[2:1] = 0x00 */
cdt_val[2] &= ~(0x6 << CDT_ISOC_WSTS_SHIFT);
break;
default:
break;
}
mlb150_dev_cdt_write(tx_mlb_ch, cdt_val);
}
/* Get rx channel interrupt status */
if (rx_int_sts & (1 << (rx_mlb_ch % 32))) {
mlb150_dev_cdt_read(rx_mlb_cl, cdt_val);
pr_debug("mxc_mlb150: RX_CH: %d, cdt_val[3]: 0x%08x, "
"cdt_val[2]: 0x%08x, "
"cdt_val[1]: 0x%08x, "
"cdt_val[0]: 0x%08x\n",
rx_mlb_ch, cdt_val[3], cdt_val[2],
cdt_val[1], cdt_val[0]);
switch (ctype) {
case MLB_CTYPE_SYNC:
rx_cis = (cdt_val[2] & CDT_SYNC_RSTS_MASK)
>> CDT_SYNC_RSTS_SHIFT;
cdt_val[2] &= ~(0x8 << CDT_SYNC_WSTS_SHIFT);
break;
case MLB_CTYPE_CTRL:
case MLB_CTYPE_ASYNC:
rx_cis =
(cdt_val[2] & CDT_CTRL_ASYNC_RSTS_MASK)
>> CDT_CTRL_ASYNC_RSTS_SHIFT;
rx_cis = (cdt_val[3] & CDT_CTRL_ASYNC_RSTS_1) ?
(rx_cis | (0x1 << 4)) : rx_cis;
cdt_val[3] &= ~CDT_CTRL_ASYNC_RSTS_1;
cdt_val[2] &=
~(0x4 << CDT_CTRL_ASYNC_RSTS_SHIFT);
break;
case MLB_CTYPE_ISOC:
rx_cis = (cdt_val[2] & CDT_ISOC_RSTS_MASK)
>> CDT_ISOC_RSTS_SHIFT;
cdt_val[2] &= ~(0x6 << CDT_ISOC_WSTS_SHIFT);
break;
default:
break;
}
mlb150_dev_cdt_write(rx_mlb_ch, cdt_val);
}
if (!tx_cis && !rx_cis)
continue;
/* fill exception event */
spin_lock(&pdevinfo->event_lock);
pdevinfo->ex_event |= (rx_cis << 16) | tx_cis;
spin_unlock(&pdevinfo->event_lock);
}
return IRQ_HANDLED;
}
static int mxc_mlb150_open(struct inode *inode, struct file *filp)
{
int minor, ring_buf_size, buf_size, j, ret;
void *buf_addr;
dma_addr_t phy_addr;
struct mlb_dev_info *pdevinfo = NULL;
struct mlb_channel_info *pchinfo = NULL;
struct mlb_data *drvdata;
minor = MINOR(inode->i_rdev);
drvdata = container_of(inode->i_cdev, struct mlb_data, cdev);
drvdata->use_iram = true;
if (minor < 0 || minor >= MLB_MINOR_DEVICES) {
pr_err("no device\n");
return -ENODEV;
}
/* open for each channel device */
if (atomic_cmpxchg(&mlb_devinfo[minor].opencnt, 0, 1) != 0) {
pr_err("busy\n");
return -EBUSY;
}
#ifdef CONFIG_ARCH_MXC_ARM64
clk_prepare_enable(drvdata->ipg);
clk_prepare_enable(drvdata->hclk);
#endif
clk_prepare_enable(drvdata->mlb);
/* initial MLB module */
mlb150_dev_init();
pdevinfo = &mlb_devinfo[minor];
pchinfo = &pdevinfo->channels[TX_CHANNEL];
ring_buf_size = pdevinfo->buf_size;
buf_size = ring_buf_size * (TRANS_RING_NODES * 2);
buf_addr = gen_pool_dma_alloc(drvdata->iram_pool, buf_size, &phy_addr);
if (!buf_addr) {
drvdata->use_iram = false;
buf_addr = dma_alloc_coherent(drvdata->dev, buf_size, &phy_addr, GFP_KERNEL);
if (!buf_addr) {
ret = -ENOMEM;
pr_err("can not alloc rx/tx buffers: %d\n", buf_size);
return ret;
}
}
pr_debug("IRAM Range: Virt 0x%p - 0x%p, Phys 0x%x - 0x%x, size: 0x%x\n",
buf_addr, (buf_addr + buf_size - 1), (u32)phy_addr,
(u32)(phy_addr + buf_size - 1), buf_size);
pdevinfo->rbuf_base_virt = buf_addr;
pdevinfo->rbuf_base_phy = phy_addr;
drvdata->iram_size = drvdata->use_iram ? buf_size : 0;
memset(buf_addr, 0, buf_size);
for (j = 0; j < (TRANS_RING_NODES);
++j, buf_addr += ring_buf_size, phy_addr += ring_buf_size) {
pdevinfo->rx_rbuf.virt_bufs[j] = buf_addr;
pdevinfo->rx_rbuf.phy_addrs[j] = phy_addr;
pr_debug("RX Ringbuf[%d]: 0x%p 0x%x\n",
j, buf_addr, (u32)phy_addr);
}
pdevinfo->rx_rbuf.unit_size = ring_buf_size;
pdevinfo->rx_rbuf.total_size = buf_size;
for (j = 0; j < (TRANS_RING_NODES);
++j, buf_addr += ring_buf_size, phy_addr += ring_buf_size) {
pdevinfo->tx_rbuf.virt_bufs[j] = buf_addr;
pdevinfo->tx_rbuf.phy_addrs[j] = phy_addr;
pr_debug("TX Ringbuf[%d]: 0x%p 0x%x\n",
j, buf_addr, (u32)phy_addr);
}
pdevinfo->tx_rbuf.unit_size = ring_buf_size;
pdevinfo->tx_rbuf.total_size = buf_size;
/* reset the buffer read/write ptr */
pdevinfo->rx_rbuf.head = pdevinfo->rx_rbuf.tail = 0;
pdevinfo->tx_rbuf.head = pdevinfo->tx_rbuf.tail = 0;
pdevinfo->ex_event = 0;
pdevinfo->tx_ok = 0;
init_waitqueue_head(&pdevinfo->rx_wq);
init_waitqueue_head(&pdevinfo->tx_wq);
drvdata = container_of(inode->i_cdev, struct mlb_data, cdev);
drvdata->devinfo = pdevinfo;
mxc_mlb150_irq_enable(drvdata, 1);
filp->private_data = drvdata;
return 0;
}
static int mxc_mlb150_release(struct inode *inode, struct file *filp)
{
int minor;
struct mlb_data *drvdata = filp->private_data;
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
minor = MINOR(inode->i_rdev);
mxc_mlb150_irq_enable(drvdata, 0);
#ifdef DEBUG
mlb150_dev_dump_reg();
mlb150_dev_dump_ctr_tbl(0, pdevinfo->channels[TX_CHANNEL].cl + 1);
#endif
if (drvdata->use_iram)
gen_pool_free(drvdata->iram_pool,
(ulong)pdevinfo->rbuf_base_virt, drvdata->iram_size);
mlb150_dev_exit();
atomic_set(&pdevinfo->on, 0);
clk_disable_unprepare(drvdata->mlb);
#ifdef CONFIG_ARCH_MXC_ARM64
clk_disable_unprepare(drvdata->hclk);
clk_disable_unprepare(drvdata->ipg);
#endif
/* decrease the open count */
atomic_set(&pdevinfo->opencnt, 0);
drvdata->devinfo = NULL;
return 0;
}
static long mxc_mlb150_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
{
//struct inode *inode = filp->f_dentry->d_inode;
struct inode *inode = file_inode(filp);
struct mlb_data *drvdata = filp->private_data;
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
void __user *argp = (void __user *)arg;
unsigned long flags, event;
int minor;
minor = MINOR(inode->i_rdev);
switch (cmd) {
case MLB_CHAN_SETADDR:
{
unsigned int caddr;
/* get channel address from user space */
if (copy_from_user(&caddr, argp, sizeof(caddr))) {
pr_err("mxc_mlb150: copy from user failed\n");
return -EFAULT;
}
pdevinfo->channels[TX_CHANNEL].address =
(caddr >> 16) & 0xFFFF;
pdevinfo->channels[RX_CHANNEL].address = caddr & 0xFFFF;
pr_debug("mxc_mlb150: set ch addr, tx: %d, rx: %d\n",
pdevinfo->channels[TX_CHANNEL].address,
pdevinfo->channels[RX_CHANNEL].address);
break;
}
case MLB_CHAN_STARTUP:
if (atomic_read(&pdevinfo->on)) {
pr_debug("mxc_mlb150: channel alreadly startup\n");
break;
}
if (mlb_channel_enable(drvdata, minor, 1))
return -EFAULT;
break;
case MLB_CHAN_SHUTDOWN:
if (atomic_read(&pdevinfo->on) == 0) {
pr_debug("mxc_mlb150: channel areadly shutdown\n");
break;
}
mlb150_trans_complete_check(pdevinfo);
mlb_channel_enable(drvdata, minor, 0);
break;
case MLB_CHAN_GETEVENT:
/* get and clear the ex_event */
spin_lock_irqsave(&pdevinfo->event_lock, flags);
event = pdevinfo->ex_event;
pdevinfo->ex_event = 0;
spin_unlock_irqrestore(&pdevinfo->event_lock, flags);
if (event) {
if (copy_to_user(argp, &event, sizeof(event))) {
pr_err("mxc_mlb150: copy to user failed\n");
return -EFAULT;
}
} else
return -EAGAIN;
break;
case MLB_SET_ISOC_BLKSIZE_188:
pdevinfo->isoc_blksz = 188;
pdevinfo->cdt_buf_dep = pdevinfo->adt_buf_dep =
pdevinfo->isoc_blksz * CH_ISOC_BLK_NUM;
break;
case MLB_SET_ISOC_BLKSIZE_196:
pdevinfo->isoc_blksz = 196;
pdevinfo->cdt_buf_dep = pdevinfo->adt_buf_dep =
pdevinfo->isoc_blksz * CH_ISOC_BLK_NUM;
break;
case MLB_SET_SYNC_QUAD:
{
u32 quad;
if (copy_from_user(&quad, argp, sizeof(quad))) {
pr_err("mxc_mlb150: get quad number "
"from user failed\n");
return -EFAULT;
}
if (quad <= 0 || quad > 3) {
pr_err("mxc_mlb150: Invalid Quadlets!"
"Quadlets in Sync mode can "
"only be 1, 2, 3\n");
return -EINVAL;
}
pdevinfo->sync_quad = quad;
/* Each quadlets is 4 bytes */
pdevinfo->cdt_buf_dep = quad * 4 * 4;
pdevinfo->adt_buf_dep =
pdevinfo->cdt_buf_dep * CH_SYNC_ADT_BUF_MULTI;
}
break;
case MLB_SET_FPS:
{
u32 fps, c0_val;
/* get fps from user space */
if (copy_from_user(&fps, argp, sizeof(fps))) {
pr_err("mxc_mlb150: copy from user failed\n");
return -EFAULT;
}
if ((fps > 1024) &&
!(drvdata->quirk_flag & MLB_QUIRK_MLB150)) {
pr_err("mxc_mlb150: not support fps %d\n", fps);
return -EINVAL;
}
c0_val = __raw_readl(mlb_base + REG_MLBC0);
c0_val &= ~MLBC0_MLBCLK_MASK;
/* check fps value */
switch (fps) {
case 256:
case 512:
case 1024:
pdevinfo->fps = fps >> 9;
c0_val &= ~MLBC0_MLBPEN;
c0_val |= (fps >> 9)
<< MLBC0_MLBCLK_SHIFT;
if (1024 == fps) {
/*
* Invert output clock phase
* in 1024 fps
*/
__raw_writel(0x1,
mlb_base + REG_MLBPC2);
}
break;
case 2048:
case 3072:
case 4096:
pdevinfo->fps = (fps >> 10) + 1;
c0_val |= ((fps >> 10) + 1)
<< MLBC0_MLBCLK_SHIFT;
break;
case 6144:
pdevinfo->fps = fps >> 10;
c0_val |= ((fps >> 10) + 1)
<< MLBC0_MLBCLK_SHIFT;
break;
case 8192:
pdevinfo->fps = (fps >> 10) - 1;
c0_val |= ((fps >> 10) - 1)
<< MLBC0_MLBCLK_SHIFT;
break;
default:
pr_debug("mxc_mlb150: invalid fps argument: %d\n",
fps);
return -EINVAL;
}
__raw_writel(c0_val, mlb_base + REG_MLBC0);
pr_debug("mxc_mlb150: set fps to %d, MLBC0: 0x%08x\n",
fps,
(u32)__raw_readl(mlb_base + REG_MLBC0));
break;
}
case MLB_GET_VER:
{
u32 version;
/* get MLB device module version */
version = 0x03030003;
pr_debug("mxc_mlb150: get version: 0x%08x\n",
version);
if (copy_to_user(argp, &version, sizeof(version))) {
pr_err("mxc_mlb150: copy to user failed\n");
return -EFAULT;
}
break;
}
case MLB_SET_DEVADDR:
{
u32 c1_val;
u8 devaddr;
/* get MLB device address from user space */
if (copy_from_user
(&devaddr, argp, sizeof(unsigned char))) {
pr_err("mxc_mlb150: copy from user failed\n");
return -EFAULT;
}
c1_val = __raw_readl(mlb_base + REG_MLBC1);
c1_val &= ~MLBC1_NDA_MASK;
c1_val |= devaddr << MLBC1_NDA_SHIFT;
__raw_writel(c1_val, mlb_base + REG_MLBC1);
pr_debug("mxc_mlb150: set dev addr, dev addr: %d, "
"MLBC1: 0x%08x\n", devaddr,
(u32)__raw_readl(mlb_base + REG_MLBC1));
break;
}
case MLB_IRQ_DISABLE:
{
disable_irq(drvdata->irq_mlb);
break;
}
case MLB_IRQ_ENABLE:
{
enable_irq(drvdata->irq_mlb);
break;
}
default:
pr_info("mxc_mlb150: Invalid ioctl command\n");
return -EINVAL;
}
return 0;
}
/*
* MLB read routine
* Read the current received data from queued buffer,
* and free this buffer for hw to fill ingress data.
*/
static ssize_t mxc_mlb150_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
int size;
struct mlb_data *drvdata = filp->private_data;
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
struct mlb_ringbuf *rx_rbuf = &pdevinfo->rx_rbuf;
int head, tail;
unsigned long flags;
read_lock_irqsave(&rx_rbuf->rb_lock, flags);
head = ACCESS_ONCE(rx_rbuf->head);
tail = rx_rbuf->tail;
read_unlock_irqrestore(&rx_rbuf->rb_lock, flags);
/* check the current rx buffer is available or not */
if (0 == CIRC_CNT(head, tail, TRANS_RING_NODES)) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&pdevinfo->rx_wq,
&__wait, TASK_INTERRUPTIBLE);
read_lock_irqsave(&rx_rbuf->rb_lock, flags);
if (CIRC_CNT(rx_rbuf->head, rx_rbuf->tail,
TRANS_RING_NODES) > 0) {
read_unlock_irqrestore(&rx_rbuf->rb_lock,
flags);
break;
}
read_unlock_irqrestore(&rx_rbuf->rb_lock,
flags);
if (!signal_pending(current)) {
schedule();
continue;
}
return -ERESTARTSYS;
}
finish_wait(&pdevinfo->rx_wq, &__wait);
} while (0);
}
/* read index before reading contents at that index */
smp_read_barrier_depends();
size = pdevinfo->adt_buf_dep;
if (size > count) {
/* the user buffer is too small */
pr_warn("mxc_mlb150: received data size is biggerthan size:"
"%d, count: %d\n", size, (int)count);
return -EINVAL;
}
/* extract one item from the buffer */
if (copy_to_user(buf, rx_rbuf->virt_bufs[tail], size)) {
pr_err("mxc_mlb150: copy from user failed\n");
return -EFAULT;
}
/* finish reading descriptor before incrementing tail */
smp_mb();
write_lock_irqsave(&rx_rbuf->rb_lock, flags);
rx_rbuf->tail = (tail + 1) & (TRANS_RING_NODES - 1);
write_unlock_irqrestore(&rx_rbuf->rb_lock, flags);
*f_pos = 0;
return size;
}
/*
* MLB write routine
* Copy the user data to tx channel buffer,
* and prepare the channel current/next buffer ptr.
*/
static ssize_t mxc_mlb150_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
s32 ret = 0;
struct mlb_channel_info *pchinfo = NULL;
struct mlb_data *drvdata = filp->private_data;
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
struct mlb_ringbuf *tx_rbuf = &pdevinfo->tx_rbuf;
int head, tail;
unsigned long flags;
/*
* minor = MINOR(filp->f_dentry->d_inode->i_rdev);
*/
pchinfo = &pdevinfo->channels[TX_CHANNEL];
if (count > pdevinfo->buf_size) {
/* too many data to write */
pr_warning("mxc_mlb150: overflow write data\n");
return -EFBIG;
}
*f_pos = 0;
read_lock_irqsave(&tx_rbuf->rb_lock, flags);
head = tx_rbuf->head;
tail = ACCESS_ONCE(tx_rbuf->tail);
read_unlock_irqrestore(&tx_rbuf->rb_lock, flags);
if (0 == CIRC_SPACE(head, tail, TRANS_RING_NODES)) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&pdevinfo->tx_wq,
&__wait, TASK_INTERRUPTIBLE);
read_lock_irqsave(&tx_rbuf->rb_lock, flags);
if (CIRC_SPACE(tx_rbuf->head, tx_rbuf->tail,
TRANS_RING_NODES) > 0) {
read_unlock_irqrestore(&tx_rbuf->rb_lock,
flags);
break;
}
read_unlock_irqrestore(&tx_rbuf->rb_lock,
flags);
if (!signal_pending(current)) {
schedule();
continue;
}
return -ERESTARTSYS;
}
finish_wait(&pdevinfo->tx_wq, &__wait);
} while (0);
}
if (copy_from_user((void *)tx_rbuf->virt_bufs[head], buf, count)) {
read_unlock_irqrestore(&tx_rbuf->rb_lock, flags);
pr_err("mxc_mlb: copy from user failed\n");
ret = -EFAULT;
goto out;
}
write_lock_irqsave(&tx_rbuf->rb_lock, flags);
smp_wmb();
tx_rbuf->head = (head + 1) & (TRANS_RING_NODES - 1);
write_unlock_irqrestore(&tx_rbuf->rb_lock, flags);
if (0 == CIRC_CNT(head, tail, TRANS_RING_NODES)) {
u32 tx_buf_ptr, ahb_ch;
s32 adt_sts;
u32 ctype = pdevinfo->channel_type;
/* read index before reading contents at that index */
smp_read_barrier_depends();
tx_buf_ptr = tx_rbuf->phy_addrs[tail];
ahb_ch = pdevinfo->channels[TX_CHANNEL].cl;
adt_sts = mlb150_dev_get_adt_sts(ahb_ch);
/* Set ADT for TX */
mlb150_dev_pipo_next(ahb_ch, ctype, adt_sts, tx_buf_ptr);
}
ret = count;
out:
return ret;
}
static unsigned int mxc_mlb150_poll(struct file *filp,
struct poll_table_struct *wait)
{
int minor;
unsigned int ret = 0;
struct mlb_data *drvdata = filp->private_data;
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
struct mlb_ringbuf *tx_rbuf = &pdevinfo->tx_rbuf;
struct mlb_ringbuf *rx_rbuf = &pdevinfo->rx_rbuf;
int head, tail;
unsigned long flags;
minor = MINOR(file_inode(filp)->i_rdev);
poll_wait(filp, &pdevinfo->rx_wq, wait);
poll_wait(filp, &pdevinfo->tx_wq, wait);
read_lock_irqsave(&tx_rbuf->rb_lock, flags);
head = tx_rbuf->head;
tail = tx_rbuf->tail;
read_unlock_irqrestore(&tx_rbuf->rb_lock, flags);
/* check the tx buffer is avaiable or not */
if (CIRC_SPACE(head, tail, TRANS_RING_NODES) >= 1)
ret |= POLLOUT | POLLWRNORM;
read_lock_irqsave(&rx_rbuf->rb_lock, flags);
head = rx_rbuf->head;
tail = rx_rbuf->tail;
read_unlock_irqrestore(&rx_rbuf->rb_lock, flags);
/* check the rx buffer filled or not */
if (CIRC_CNT(head, tail, TRANS_RING_NODES) >= 1)
ret |= POLLIN | POLLRDNORM;
/* check the exception event */
if (pdevinfo->ex_event)
ret |= POLLIN | POLLRDNORM;
return ret;
}
/*
* char dev file operations structure
*/
static const struct file_operations mxc_mlb150_fops = {
.owner = THIS_MODULE,
.open = mxc_mlb150_open,
.release = mxc_mlb150_release,
.unlocked_ioctl = mxc_mlb150_ioctl,
.poll = mxc_mlb150_poll,
.read = mxc_mlb150_read,
.write = mxc_mlb150_write,
};
static struct platform_device_id imx_mlb150_devtype[] = {
{
.name = "imx6q-mlb150",
.driver_data = MLB_QUIRK_MLB150,
}, {
.name = "imx6sx-mlb50",
.driver_data = 0,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(platform, imx_mlb150_devtype);
static const struct of_device_id mlb150_imx_dt_ids[] = {
{ .compatible = "fsl,imx6q-mlb150",
.data = &imx_mlb150_devtype[IMX6Q_MLB], },
{ .compatible = "fsl,imx6sx-mlb50",
.data = &imx_mlb150_devtype[IMX6SX_MLB], },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mlb150_imx_dt_ids);
/*
* This function is called whenever the MLB device is detected.
*/
static int mxc_mlb150_probe(struct platform_device *pdev)
{
int ret, mlb_major, i;
struct mlb_data *drvdata;
struct resource *res;
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *of_id;
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct mlb_data),
GFP_KERNEL);
if (!drvdata) {
dev_err(&pdev->dev, "can't allocate enough memory\n");
return -ENOMEM;
}
drvdata->dev = &pdev->dev;
of_id = of_match_device(mlb150_imx_dt_ids, &pdev->dev);
if (of_id)
pdev->id_entry = of_id->data;
else
return -EINVAL;
/*
* Register MLB lld as four character devices
*/
ret = alloc_chrdev_region(&drvdata->firstdev, 0,
MLB_MINOR_DEVICES, "mxc_mlb150");
if (ret < 0) {
dev_err(&pdev->dev, "alloc region error\n");
goto err_reg;
}
mlb_major = MAJOR(drvdata->firstdev);
dev_dbg(&pdev->dev, "MLB device major: %d\n", mlb_major);
cdev_init(&drvdata->cdev, &mxc_mlb150_fops);
drvdata->cdev.owner = THIS_MODULE;
ret = cdev_add(&drvdata->cdev, drvdata->firstdev, MLB_MINOR_DEVICES);
if (ret) {
dev_err(&pdev->dev, "can't add cdev\n");
goto err_reg;
}
/* create class and device for udev information */
drvdata->class = class_create(THIS_MODULE, "mlb150");
if (IS_ERR(drvdata->class)) {
dev_err(&pdev->dev, "failed to create device class\n");
ret = -ENOMEM;
goto err_class;
}
for (i = 0; i < MLB_MINOR_DEVICES; i++) {
struct device *class_dev;
class_dev = device_create(drvdata->class, NULL,
MKDEV(mlb_major, i),
NULL, mlb_devinfo[i].dev_name);
if (IS_ERR(class_dev)) {
dev_err(&pdev->dev, "failed to create mlb150 %s"
" class device\n", mlb_devinfo[i].dev_name);
ret = -ENOMEM;
goto err_dev;
}
}
drvdata->quirk_flag = pdev->id_entry->driver_data;
/* ahb0 irq */
drvdata->irq_ahb0 = platform_get_irq(pdev, 1);
if (drvdata->irq_ahb0 < 0) {
dev_err(&pdev->dev, "No ahb0 irq line provided\n");
goto err_dev;
}
dev_dbg(&pdev->dev, "ahb0_irq: %d\n", drvdata->irq_ahb0);
if (devm_request_irq(&pdev->dev, drvdata->irq_ahb0, mlb_ahb_isr,
0, "mlb_ahb0", NULL)) {
dev_err(&pdev->dev, "can't claim irq %d\n", drvdata->irq_ahb0);
goto err_dev;
}
/* ahb1 irq */
drvdata->irq_ahb1 = platform_get_irq(pdev, 2);
dev_dbg(&pdev->dev, "ahb1_irq: %d\n", drvdata->irq_ahb1);
if (drvdata->irq_ahb1 > 0) {
if (devm_request_irq(&pdev->dev, drvdata->irq_ahb1, mlb_ahb_isr,
0, "mlb_ahb1", NULL)) {
dev_err(&pdev->dev, "can't claim irq %d\n", drvdata->irq_ahb1);
goto err_dev;
}
}
/* mlb irq */
drvdata->irq_mlb = platform_get_irq(pdev, 0);
if (drvdata->irq_mlb < 0) {
dev_err(&pdev->dev, "No mlb irq line provided\n");
goto err_dev;
}
dev_dbg(&pdev->dev, "mlb_irq: %d\n", drvdata->irq_mlb);
if (devm_request_irq(&pdev->dev, drvdata->irq_mlb, mlb_isr,
0, "mlb", NULL)) {
dev_err(&pdev->dev, "can't claim irq %d\n", drvdata->irq_mlb);
goto err_dev;
}
/* ioremap from phy mlb to kernel space */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "can't get device resources\n");
ret = -ENOENT;
goto err_dev;
}
mlb_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(mlb_base)) {
dev_err(&pdev->dev,
"failed to get ioremap base\n");
ret = PTR_ERR(mlb_base);
goto err_dev;
}
drvdata->membase = mlb_base;
#ifdef CONFIG_REGULATOR
drvdata->nvcc = devm_regulator_get(&pdev->dev, "reg_nvcc");
if (!IS_ERR(drvdata->nvcc)) {
regulator_set_voltage(drvdata->nvcc, 2500000, 2500000);
dev_err(&pdev->dev, "enalbe regulator\n");
ret = regulator_enable(drvdata->nvcc);
if (ret) {
dev_err(&pdev->dev, "vdd set voltage error\n");
goto err_dev;
}
}
#endif
#ifdef CONFIG_ARCH_MXC_ARM64
drvdata->ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(drvdata->ipg)) {
dev_err(&pdev->dev, "unable to get mlb ipg clock\n");
ret = PTR_ERR(drvdata->ipg);
goto err_dev;
};
drvdata->hclk = devm_clk_get(&pdev->dev, "hclk");
if (IS_ERR(drvdata->hclk)) {
dev_err(&pdev->dev, "unable to get mlb hclk clock\n");
ret = PTR_ERR(drvdata->hclk);
goto err_dev;
};
#endif
drvdata->mlb = devm_clk_get(&pdev->dev, "mlb");
if (IS_ERR(drvdata->mlb)) {
dev_err(&pdev->dev, "unable to get mlb clock\n");
ret = PTR_ERR(drvdata->mlb);
goto err_dev;
}
drvdata->iram_pool = of_gen_pool_get(np, "iram", 0);
if (!drvdata->iram_pool)
dev_warn(&pdev->dev, "no iram assigned, using external mem\n");
drvdata->devinfo = NULL;
mxc_mlb150_irq_enable(drvdata, 0);
platform_set_drvdata(pdev, drvdata);
return 0;
err_dev:
for (--i; i >= 0; i--)
device_destroy(drvdata->class, MKDEV(mlb_major, i));
class_destroy(drvdata->class);
err_class:
cdev_del(&drvdata->cdev);
err_reg:
unregister_chrdev_region(drvdata->firstdev, MLB_MINOR_DEVICES);
return ret;
}
static int mxc_mlb150_remove(struct platform_device *pdev)
{
int i;
struct mlb_data *drvdata = platform_get_drvdata(pdev);
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
if (pdevinfo && atomic_read(&pdevinfo->opencnt))
clk_disable_unprepare(drvdata->mlb);
/* disable mlb power */
#ifdef CONFIG_REGULATOR
if (!IS_ERR(drvdata->nvcc))
regulator_disable(drvdata->nvcc);
#endif
/* destroy mlb device class */
for (i = MLB_MINOR_DEVICES - 1; i >= 0; i--)
device_destroy(drvdata->class,
MKDEV(MAJOR(drvdata->firstdev), i));
class_destroy(drvdata->class);
cdev_del(&drvdata->cdev);
/* Unregister the two MLB devices */
unregister_chrdev_region(drvdata->firstdev, MLB_MINOR_DEVICES);
return 0;
}
#ifdef CONFIG_PM
static int mxc_mlb150_suspend(struct platform_device *pdev, pm_message_t state)
{
struct mlb_data *drvdata = platform_get_drvdata(pdev);
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
if (pdevinfo && atomic_read(&pdevinfo->opencnt)) {
mlb150_dev_exit();
clk_disable_unprepare(drvdata->mlb);
}
return 0;
}
static int mxc_mlb150_resume(struct platform_device *pdev)
{
struct mlb_data *drvdata = platform_get_drvdata(pdev);
struct mlb_dev_info *pdevinfo = drvdata->devinfo;
if (pdevinfo && atomic_read(&pdevinfo->opencnt)) {
clk_prepare_enable(drvdata->mlb);
mlb150_dev_init();
}
return 0;
}
#else
#define mxc_mlb150_suspend NULL
#define mxc_mlb150_resume NULL
#endif
/*
* platform driver structure for MLB
*/
static struct platform_driver mxc_mlb150_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = mlb150_imx_dt_ids,
},
.probe = mxc_mlb150_probe,
.remove = mxc_mlb150_remove,
.suspend = mxc_mlb150_suspend,
.resume = mxc_mlb150_resume,
.id_table = imx_mlb150_devtype,
};
static int __init mxc_mlb150_init(void)
{
return platform_driver_register(&mxc_mlb150_driver);
}
static void __exit mxc_mlb150_exit(void)
{
platform_driver_unregister(&mxc_mlb150_driver);
}
module_init(mxc_mlb150_init);
module_exit(mxc_mlb150_exit);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MLB150 low level driver");
MODULE_LICENSE("GPL");