| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * netup_unidvb_i2c.c |
| * |
| * Internal I2C bus driver for NetUP Universal Dual DVB-CI |
| * |
| * Copyright (C) 2014 NetUP Inc. |
| * Copyright (C) 2014 Sergey Kozlov <serjk@netup.ru> |
| * Copyright (C) 2014 Abylay Ospan <aospan@netup.ru> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include "netup_unidvb.h" |
| |
| #define NETUP_I2C_BUS0_ADDR 0x4800 |
| #define NETUP_I2C_BUS1_ADDR 0x4840 |
| #define NETUP_I2C_TIMEOUT 1000 |
| |
| /* twi_ctrl0_stat reg bits */ |
| #define TWI_IRQEN_COMPL 0x1 |
| #define TWI_IRQEN_ANACK 0x2 |
| #define TWI_IRQEN_DNACK 0x4 |
| #define TWI_IRQ_COMPL (TWI_IRQEN_COMPL << 8) |
| #define TWI_IRQ_ANACK (TWI_IRQEN_ANACK << 8) |
| #define TWI_IRQ_DNACK (TWI_IRQEN_DNACK << 8) |
| #define TWI_IRQ_TX 0x800 |
| #define TWI_IRQ_RX 0x1000 |
| #define TWI_IRQEN (TWI_IRQEN_COMPL | TWI_IRQEN_ANACK | TWI_IRQEN_DNACK) |
| /* twi_addr_ctrl1 reg bits*/ |
| #define TWI_TRANSFER 0x100 |
| #define TWI_NOSTOP 0x200 |
| #define TWI_SOFT_RESET 0x2000 |
| /* twi_clkdiv reg value */ |
| #define TWI_CLKDIV 156 |
| /* fifo_stat_ctrl reg bits */ |
| #define FIFO_IRQEN 0x8000 |
| #define FIFO_RESET 0x4000 |
| /* FIFO size */ |
| #define FIFO_SIZE 16 |
| |
| struct netup_i2c_fifo_regs { |
| union { |
| __u8 data8; |
| __le16 data16; |
| __le32 data32; |
| }; |
| __u8 padding[4]; |
| __le16 stat_ctrl; |
| } __packed __aligned(1); |
| |
| struct netup_i2c_regs { |
| __le16 clkdiv; |
| __le16 twi_ctrl0_stat; |
| __le16 twi_addr_ctrl1; |
| __le16 length; |
| __u8 padding1[8]; |
| struct netup_i2c_fifo_regs tx_fifo; |
| __u8 padding2[6]; |
| struct netup_i2c_fifo_regs rx_fifo; |
| } __packed __aligned(1); |
| |
| irqreturn_t netup_i2c_interrupt(struct netup_i2c *i2c) |
| { |
| u16 reg, tmp; |
| unsigned long flags; |
| irqreturn_t iret = IRQ_HANDLED; |
| |
| spin_lock_irqsave(&i2c->lock, flags); |
| reg = readw(&i2c->regs->twi_ctrl0_stat); |
| writew(reg & ~TWI_IRQEN, &i2c->regs->twi_ctrl0_stat); |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): twi_ctrl0_state 0x%x\n", __func__, reg); |
| if ((reg & TWI_IRQEN_COMPL) != 0 && (reg & TWI_IRQ_COMPL)) { |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): TWI_IRQEN_COMPL\n", __func__); |
| i2c->state = STATE_DONE; |
| goto irq_ok; |
| } |
| if ((reg & TWI_IRQEN_ANACK) != 0 && (reg & TWI_IRQ_ANACK)) { |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): TWI_IRQEN_ANACK\n", __func__); |
| i2c->state = STATE_ERROR; |
| goto irq_ok; |
| } |
| if ((reg & TWI_IRQEN_DNACK) != 0 && (reg & TWI_IRQ_DNACK)) { |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): TWI_IRQEN_DNACK\n", __func__); |
| i2c->state = STATE_ERROR; |
| goto irq_ok; |
| } |
| if ((reg & TWI_IRQ_RX) != 0) { |
| tmp = readw(&i2c->regs->rx_fifo.stat_ctrl); |
| writew(tmp & ~FIFO_IRQEN, &i2c->regs->rx_fifo.stat_ctrl); |
| i2c->state = STATE_WANT_READ; |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): want read\n", __func__); |
| goto irq_ok; |
| } |
| if ((reg & TWI_IRQ_TX) != 0) { |
| tmp = readw(&i2c->regs->tx_fifo.stat_ctrl); |
| writew(tmp & ~FIFO_IRQEN, &i2c->regs->tx_fifo.stat_ctrl); |
| i2c->state = STATE_WANT_WRITE; |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): want write\n", __func__); |
| goto irq_ok; |
| } |
| dev_warn(&i2c->adap.dev, "%s(): not mine interrupt\n", __func__); |
| iret = IRQ_NONE; |
| irq_ok: |
| spin_unlock_irqrestore(&i2c->lock, flags); |
| if (iret == IRQ_HANDLED) |
| wake_up(&i2c->wq); |
| return iret; |
| } |
| |
| static void netup_i2c_reset(struct netup_i2c *i2c) |
| { |
| dev_dbg(i2c->adap.dev.parent, "%s()\n", __func__); |
| i2c->state = STATE_DONE; |
| writew(TWI_SOFT_RESET, &i2c->regs->twi_addr_ctrl1); |
| writew(TWI_CLKDIV, &i2c->regs->clkdiv); |
| writew(FIFO_RESET, &i2c->regs->tx_fifo.stat_ctrl); |
| writew(FIFO_RESET, &i2c->regs->rx_fifo.stat_ctrl); |
| writew(0x800, &i2c->regs->tx_fifo.stat_ctrl); |
| writew(0x800, &i2c->regs->rx_fifo.stat_ctrl); |
| } |
| |
| static void netup_i2c_fifo_tx(struct netup_i2c *i2c) |
| { |
| u8 data; |
| u32 fifo_space = FIFO_SIZE - |
| (readw(&i2c->regs->tx_fifo.stat_ctrl) & 0x3f); |
| u32 msg_length = i2c->msg->len - i2c->xmit_size; |
| |
| msg_length = min(msg_length, fifo_space); |
| while (msg_length--) { |
| data = i2c->msg->buf[i2c->xmit_size++]; |
| writeb(data, &i2c->regs->tx_fifo.data8); |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): write 0x%02x\n", __func__, data); |
| } |
| if (i2c->xmit_size < i2c->msg->len) { |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): TX IRQ enabled\n", __func__); |
| writew(readw(&i2c->regs->tx_fifo.stat_ctrl) | FIFO_IRQEN, |
| &i2c->regs->tx_fifo.stat_ctrl); |
| } |
| } |
| |
| static void netup_i2c_fifo_rx(struct netup_i2c *i2c) |
| { |
| u8 data; |
| u32 fifo_size = readw(&i2c->regs->rx_fifo.stat_ctrl) & 0x3f; |
| |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): RX fifo size %d\n", __func__, fifo_size); |
| while (fifo_size--) { |
| data = readb(&i2c->regs->rx_fifo.data8); |
| if ((i2c->msg->flags & I2C_M_RD) != 0 && |
| i2c->xmit_size < i2c->msg->len) { |
| i2c->msg->buf[i2c->xmit_size++] = data; |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): read 0x%02x\n", __func__, data); |
| } |
| } |
| if (i2c->xmit_size < i2c->msg->len) { |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): RX IRQ enabled\n", __func__); |
| writew(readw(&i2c->regs->rx_fifo.stat_ctrl) | FIFO_IRQEN, |
| &i2c->regs->rx_fifo.stat_ctrl); |
| } |
| } |
| |
| static void netup_i2c_start_xfer(struct netup_i2c *i2c) |
| { |
| u16 rdflag = ((i2c->msg->flags & I2C_M_RD) ? 1 : 0); |
| u16 reg = readw(&i2c->regs->twi_ctrl0_stat); |
| |
| writew(TWI_IRQEN | reg, &i2c->regs->twi_ctrl0_stat); |
| writew(i2c->msg->len, &i2c->regs->length); |
| writew(TWI_TRANSFER | (i2c->msg->addr << 1) | rdflag, |
| &i2c->regs->twi_addr_ctrl1); |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): length %d twi_addr_ctrl1 0x%x twi_ctrl0_stat 0x%x\n", |
| __func__, readw(&i2c->regs->length), |
| readw(&i2c->regs->twi_addr_ctrl1), |
| readw(&i2c->regs->twi_ctrl0_stat)); |
| i2c->state = STATE_WAIT; |
| i2c->xmit_size = 0; |
| if (!rdflag) |
| netup_i2c_fifo_tx(i2c); |
| else |
| writew(FIFO_IRQEN | readw(&i2c->regs->rx_fifo.stat_ctrl), |
| &i2c->regs->rx_fifo.stat_ctrl); |
| } |
| |
| static int netup_i2c_xfer(struct i2c_adapter *adap, |
| struct i2c_msg *msgs, int num) |
| { |
| unsigned long flags; |
| int i, trans_done, res = num; |
| struct netup_i2c *i2c = i2c_get_adapdata(adap); |
| u16 reg; |
| |
| spin_lock_irqsave(&i2c->lock, flags); |
| if (i2c->state != STATE_DONE) { |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): i2c->state == %d, resetting I2C\n", |
| __func__, i2c->state); |
| netup_i2c_reset(i2c); |
| } |
| dev_dbg(i2c->adap.dev.parent, "%s() num %d\n", __func__, num); |
| for (i = 0; i < num; i++) { |
| i2c->msg = &msgs[i]; |
| netup_i2c_start_xfer(i2c); |
| trans_done = 0; |
| while (!trans_done) { |
| spin_unlock_irqrestore(&i2c->lock, flags); |
| if (wait_event_timeout(i2c->wq, |
| i2c->state != STATE_WAIT, |
| msecs_to_jiffies(NETUP_I2C_TIMEOUT))) { |
| spin_lock_irqsave(&i2c->lock, flags); |
| switch (i2c->state) { |
| case STATE_WANT_READ: |
| netup_i2c_fifo_rx(i2c); |
| break; |
| case STATE_WANT_WRITE: |
| netup_i2c_fifo_tx(i2c); |
| break; |
| case STATE_DONE: |
| if ((i2c->msg->flags & I2C_M_RD) != 0 && |
| i2c->xmit_size != i2c->msg->len) |
| netup_i2c_fifo_rx(i2c); |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): msg %d OK\n", |
| __func__, i); |
| trans_done = 1; |
| break; |
| case STATE_ERROR: |
| res = -EIO; |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): error state\n", |
| __func__); |
| goto done; |
| default: |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): invalid state %d\n", |
| __func__, i2c->state); |
| res = -EINVAL; |
| goto done; |
| } |
| if (!trans_done) { |
| i2c->state = STATE_WAIT; |
| reg = readw( |
| &i2c->regs->twi_ctrl0_stat); |
| writew(TWI_IRQEN | reg, |
| &i2c->regs->twi_ctrl0_stat); |
| } |
| spin_unlock_irqrestore(&i2c->lock, flags); |
| } else { |
| spin_lock_irqsave(&i2c->lock, flags); |
| dev_dbg(i2c->adap.dev.parent, |
| "%s(): wait timeout\n", __func__); |
| res = -ETIMEDOUT; |
| goto done; |
| } |
| spin_lock_irqsave(&i2c->lock, flags); |
| } |
| } |
| done: |
| spin_unlock_irqrestore(&i2c->lock, flags); |
| dev_dbg(i2c->adap.dev.parent, "%s(): result %d\n", __func__, res); |
| return res; |
| } |
| |
| static u32 netup_i2c_func(struct i2c_adapter *adap) |
| { |
| return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; |
| } |
| |
| static const struct i2c_algorithm netup_i2c_algorithm = { |
| .master_xfer = netup_i2c_xfer, |
| .functionality = netup_i2c_func, |
| }; |
| |
| static const struct i2c_adapter netup_i2c_adapter = { |
| .owner = THIS_MODULE, |
| .name = NETUP_UNIDVB_NAME, |
| .class = I2C_CLASS_HWMON, |
| .algo = &netup_i2c_algorithm, |
| }; |
| |
| static int netup_i2c_init(struct netup_unidvb_dev *ndev, int bus_num) |
| { |
| int ret; |
| struct netup_i2c *i2c; |
| |
| if (bus_num < 0 || bus_num > 1) { |
| dev_err(&ndev->pci_dev->dev, |
| "%s(): invalid bus_num %d\n", __func__, bus_num); |
| return -EINVAL; |
| } |
| i2c = &ndev->i2c[bus_num]; |
| spin_lock_init(&i2c->lock); |
| init_waitqueue_head(&i2c->wq); |
| i2c->regs = (struct netup_i2c_regs __iomem *)(ndev->bmmio0 + |
| (bus_num == 0 ? NETUP_I2C_BUS0_ADDR : NETUP_I2C_BUS1_ADDR)); |
| netup_i2c_reset(i2c); |
| i2c->adap = netup_i2c_adapter; |
| i2c->adap.dev.parent = &ndev->pci_dev->dev; |
| i2c_set_adapdata(&i2c->adap, i2c); |
| ret = i2c_add_adapter(&i2c->adap); |
| if (ret) |
| return ret; |
| dev_info(&ndev->pci_dev->dev, |
| "%s(): registered I2C bus %d at 0x%x\n", |
| __func__, |
| bus_num, (bus_num == 0 ? |
| NETUP_I2C_BUS0_ADDR : |
| NETUP_I2C_BUS1_ADDR)); |
| return 0; |
| } |
| |
| static void netup_i2c_remove(struct netup_unidvb_dev *ndev, int bus_num) |
| { |
| struct netup_i2c *i2c; |
| |
| if (bus_num < 0 || bus_num > 1) { |
| dev_err(&ndev->pci_dev->dev, |
| "%s(): invalid bus number %d\n", __func__, bus_num); |
| return; |
| } |
| i2c = &ndev->i2c[bus_num]; |
| netup_i2c_reset(i2c); |
| /* remove adapter */ |
| i2c_del_adapter(&i2c->adap); |
| dev_info(&ndev->pci_dev->dev, |
| "netup_i2c_remove: unregistered I2C bus %d\n", bus_num); |
| } |
| |
| int netup_i2c_register(struct netup_unidvb_dev *ndev) |
| { |
| int ret; |
| |
| ret = netup_i2c_init(ndev, 0); |
| if (ret) |
| return ret; |
| ret = netup_i2c_init(ndev, 1); |
| if (ret) { |
| netup_i2c_remove(ndev, 0); |
| return ret; |
| } |
| return 0; |
| } |
| |
| void netup_i2c_unregister(struct netup_unidvb_dev *ndev) |
| { |
| netup_i2c_remove(ndev, 0); |
| netup_i2c_remove(ndev, 1); |
| } |
| |