diff options
-rw-r--r-- | drivers/mxc/Kconfig | 3 | ||||
-rw-r--r-- | drivers/mxc/Makefile | 3 | ||||
-rw-r--r-- | drivers/mxc/mlb/Kconfig | 17 | ||||
-rw-r--r-- | drivers/mxc/mlb/Makefile | 5 | ||||
-rwxr-xr-x | drivers/mxc/mlb/mxc_mlb.c | 2810 | ||||
-rw-r--r-- | include/linux/mxc_mlb.h | 55 | ||||
-rw-r--r-- | include/uapi/linux/mxc_mlb.h | 55 |
7 files changed, 2946 insertions, 2 deletions
diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig index 6533d2edb5e5..d52baf16682b 100644 --- a/drivers/mxc/Kconfig +++ b/drivers/mxc/Kconfig @@ -4,8 +4,9 @@ if ARCH_MXC menu "MXC support drivers" -source "drivers/mxc/vpu/Kconfig" +source "drivers/mxc/mlb/Kconfig" source "drivers/mxc/sim/Kconfig" +source "drivers/mxc/vpu/Kconfig" endmenu diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile index ea4ee9d606c1..060dc06d5f7b 100644 --- a/drivers/mxc/Makefile +++ b/drivers/mxc/Makefile @@ -1,2 +1,3 @@ -obj-$(CONFIG_MXC_VPU) += vpu/ +obj-$(CONFIG_MXC_MLB) += mlb/ obj-$(CONFIG_MXC_SIM) += sim/ +obj-$(CONFIG_MXC_VPU) += vpu/ diff --git a/drivers/mxc/mlb/Kconfig b/drivers/mxc/mlb/Kconfig new file mode 100644 index 000000000000..667226e1492f --- /dev/null +++ b/drivers/mxc/mlb/Kconfig @@ -0,0 +1,17 @@ +# +# MLB150 configuration +# + +menu "MXC Media Local Bus Driver" + +config MXC_MLB + boolean + +config MXC_MLB150 + tristate "MLB150 support" + depends on SOC_IMX6Q + select MXC_MLB + ---help--- + Say Y to get the MLB150 support. + +endmenu diff --git a/drivers/mxc/mlb/Makefile b/drivers/mxc/mlb/Makefile new file mode 100644 index 000000000000..e71005a3788f --- /dev/null +++ b/drivers/mxc/mlb/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the i.MX6Q/DL MLB150 driver +# + +obj-$(CONFIG_MXC_MLB150) += mxc_mlb.o diff --git a/drivers/mxc/mlb/mxc_mlb.c b/drivers/mxc/mlb/mxc_mlb.c new file mode 100755 index 000000000000..fa6cff1d5b04 --- /dev/null +++ b/drivers/mxc/mlb/mxc_mlb.c @@ -0,0 +1,2810 @@ +/* + * 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/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 mlb_dev_info *devinfo; + struct clk *clk_mlb3p; + struct clk *clk_mlb6p; + 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; + u32 irq_ahb0; + u32 irq_ahb1; + u32 irq_mlb; + u32 quirk_flag; +}; + +/* + * 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); + } + + clk_prepare_enable(drvdata->clk_mlb6p); + + 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; + + clk_disable_unprepare(drvdata->clk_mlb6p); + + 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_ahb1); + enable_irq(drvdata->irq_mlb); + } else { + disable_irq(drvdata->irq_ahb0); + disable_irq(drvdata->irq_ahb1); + disable_irq(drvdata->irq_mlb); + } +} + +/* + * 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: + tx_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: + tx_cis = + (cdt_val[2] & ~CDT_CTRL_ASYNC_RSTS_MASK) + >> CDT_CTRL_ASYNC_RSTS_SHIFT; + tx_cis = (cdt_val[3] & CDT_CTRL_ASYNC_RSTS_1) ? + (tx_cis | (0x1 << 4)) : tx_cis; + cdt_val[3] &= ~CDT_CTRL_ASYNC_RSTS_1; + cdt_val[2] &= + ~(0x4 << CDT_CTRL_ASYNC_RSTS_SHIFT); + break; + case MLB_CTYPE_ISOC: + tx_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 __iomem *buf_addr; + ulong 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); + + 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; + } + + clk_prepare_enable(drvdata->clk_mlb3p); + + /* 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 = (void __iomem *)gen_pool_alloc(drvdata->iram_pool, buf_size); + if (buf_addr == NULL) { + ret = -ENOMEM; + pr_err("can not alloc rx/tx buffers: %d\n", buf_size); + return ret; + } + phy_addr = gen_pool_virt_to_phys(drvdata->iram_pool, (ulong)buf_addr); + 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 = buf_size; + + 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 + + gen_pool_free(drvdata->iram_pool, + (ulong)pdevinfo->rbuf_base_virt, drvdata->iram_size); + + mlb150_dev_exit(); + + if (pdevinfo && atomic_read(&pdevinfo->on) + && (pdevinfo->fps >= CLK_2048FS)) + clk_disable_unprepare(drvdata->clk_mlb6p); + + atomic_set(&pdevinfo->on, 0); + + clk_disable_unprepare(drvdata->clk_mlb3p); + /* 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_warning + ("mxc_mlb150: received data size is bigger than " + "size: %d, count: %d\n", size, 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; + } + + 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); + if (drvdata->irq_ahb1 < 0) { + dev_err(&pdev->dev, "No ahb1 irq line provided\n"); + goto err_dev; + } + dev_dbg(&pdev->dev, "ahb1_irq: %d\n", drvdata->irq_ahb1); + 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); + dev_dbg(&pdev->dev, "mapped base address: 0x%08x\n", (u32)mlb_base); + 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 + + /* enable clock */ + drvdata->clk_mlb3p = devm_clk_get(&pdev->dev, "mlb"); + if (IS_ERR(drvdata->clk_mlb3p)) { + dev_err(&pdev->dev, "unable to get mlb clock\n"); + ret = PTR_ERR(drvdata->clk_mlb3p); + goto err_dev; + } + + if (drvdata->quirk_flag & MLB_QUIRK_MLB150) { + drvdata->clk_mlb6p = devm_clk_get(&pdev->dev, "pll8_mlb"); + if (IS_ERR(drvdata->clk_mlb6p)) { + dev_err(&pdev->dev, "unable to get mlb pll clock\n"); + ret = PTR_ERR(drvdata->clk_mlb6p); + goto err_dev; + } + } + + drvdata->iram_pool = of_gen_pool_get(np, "iram", 0); + if (!drvdata->iram_pool) { + dev_err(&pdev->dev, "iram pool not available\n"); + ret = -ENOMEM; + goto err_dev; + } + + 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->on) + && (pdevinfo->fps >= CLK_2048FS)) + clk_disable_unprepare(drvdata->clk_mlb6p); + + if (pdevinfo && atomic_read(&pdevinfo->opencnt)) + clk_disable_unprepare(drvdata->clk_mlb3p); + + /* 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->on) + && (pdevinfo->fps >= CLK_2048FS)) + clk_disable_unprepare(drvdata->clk_mlb6p); + + if (pdevinfo && atomic_read(&pdevinfo->opencnt)) { + mlb150_dev_exit(); + clk_disable_unprepare(drvdata->clk_mlb3p); + } + + 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->clk_mlb3p); + mlb150_dev_init(); + } + + if (pdevinfo && atomic_read(&pdevinfo->on) && + (pdevinfo->fps >= CLK_2048FS)) + clk_prepare_enable(drvdata->clk_mlb6p); + + 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"); diff --git a/include/linux/mxc_mlb.h b/include/linux/mxc_mlb.h new file mode 100644 index 000000000000..d7c792a2bee4 --- /dev/null +++ b/include/linux/mxc_mlb.h @@ -0,0 +1,55 @@ +/* + * mxc_mlb.h + * + * Copyright 2008-2013 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _MXC_MLB_H +#define _MXC_MLB_H + +/* define IOCTL command */ +#define MLB_DBG_RUNTIME _IO('S', 0x09) +#define MLB_SET_FPS _IOW('S', 0x10, unsigned int) +#define MLB_GET_VER _IOR('S', 0x11, unsigned long) +#define MLB_SET_DEVADDR _IOR('S', 0x12, unsigned char) + +/*! + * set channel address for each logical channel + * the MSB 16bits is for tx channel, the left LSB is for rx channel + */ +#define MLB_CHAN_SETADDR _IOW('S', 0x13, unsigned int) +#define MLB_CHAN_STARTUP _IO('S', 0x14) +#define MLB_CHAN_SHUTDOWN _IO('S', 0x15) +#define MLB_CHAN_GETEVENT _IOR('S', 0x16, unsigned long) + +#define MLB_SET_ISOC_BLKSIZE_188 _IO('S', 0x17) +#define MLB_SET_ISOC_BLKSIZE_196 _IO('S', 0x18) +#define MLB_SET_SYNC_QUAD _IOW('S', 0x19, unsigned int) +#define MLB_IRQ_ENABLE _IO('S', 0x20) +#define MLB_IRQ_DISABLE _IO('S', 0x21) + +/*! + * MLB event define + */ +enum { + MLB_EVT_TX_PROTO_ERR_CUR = 1 << 0, + MLB_EVT_TX_BRK_DETECT_CUR = 1 << 1, + MLB_EVT_TX_PROTO_ERR_PREV = 1 << 8, + MLB_EVT_TX_BRK_DETECT_PREV = 1 << 9, + MLB_EVT_RX_PROTO_ERR_CUR = 1 << 16, + MLB_EVT_RX_BRK_DETECT_CUR = 1 << 17, + MLB_EVT_RX_PROTO_ERR_PREV = 1 << 24, + MLB_EVT_RX_BRK_DETECT_PREV = 1 << 25, +}; + + +#endif /* _MXC_MLB_H */ diff --git a/include/uapi/linux/mxc_mlb.h b/include/uapi/linux/mxc_mlb.h new file mode 100644 index 000000000000..20ba5240ea51 --- /dev/null +++ b/include/uapi/linux/mxc_mlb.h @@ -0,0 +1,55 @@ +/* + * mxc_mlb.h + * + * Copyright 2008-2013 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _MXC_MLB_UAPI_H +#define _MXC_MLB_UAPI_H + +/* define IOCTL command */ +#define MLB_DBG_RUNTIME _IO('S', 0x09) +#define MLB_SET_FPS _IOW('S', 0x10, unsigned int) +#define MLB_GET_VER _IOR('S', 0x11, unsigned long) +#define MLB_SET_DEVADDR _IOR('S', 0x12, unsigned char) + +/*! + * set channel address for each logical channel + * the MSB 16bits is for tx channel, the left LSB is for rx channel + */ +#define MLB_CHAN_SETADDR _IOW('S', 0x13, unsigned int) +#define MLB_CHAN_STARTUP _IO('S', 0x14) +#define MLB_CHAN_SHUTDOWN _IO('S', 0x15) +#define MLB_CHAN_GETEVENT _IOR('S', 0x16, unsigned long) + +#define MLB_SET_ISOC_BLKSIZE_188 _IO('S', 0x17) +#define MLB_SET_ISOC_BLKSIZE_196 _IO('S', 0x18) +#define MLB_SET_SYNC_QUAD _IOW('S', 0x19, unsigned int) +#define MLB_IRQ_ENABLE _IO('S', 0x20) +#define MLB_IRQ_DISABLE _IO('S', 0x21) + +/*! + * MLB event define + */ +enum { + MLB_EVT_TX_PROTO_ERR_CUR = 1 << 0, + MLB_EVT_TX_BRK_DETECT_CUR = 1 << 1, + MLB_EVT_TX_PROTO_ERR_PREV = 1 << 8, + MLB_EVT_TX_BRK_DETECT_PREV = 1 << 9, + MLB_EVT_RX_PROTO_ERR_CUR = 1 << 16, + MLB_EVT_RX_BRK_DETECT_CUR = 1 << 17, + MLB_EVT_RX_PROTO_ERR_PREV = 1 << 24, + MLB_EVT_RX_BRK_DETECT_PREV = 1 << 25, +}; + + +#endif /* _MXC_MLB_H */ |