forked from Minki/linux
c942fddf87
Based on 3 normalized pattern(s): 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 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 [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] 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 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 [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
365 lines
9.6 KiB
C
365 lines
9.6 KiB
C
// 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 = (msg_length < fifo_space ? 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 | I2C_CLASS_SPD,
|
|
.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);
|
|
}
|
|
|