|
@@ -0,0 +1,626 @@
|
|
|
+#include <common.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <dm.h>
|
|
|
+#include <i2c.h>
|
|
|
+#include <log.h>
|
|
|
+#include <asm/arch/nexell.h>
|
|
|
+#include <asm/arch/reset.h>
|
|
|
+#include <asm/arch/clk.h>
|
|
|
+#include <asm/arch/nx_gpio.h>
|
|
|
+#include <linux/delay.h>
|
|
|
+
|
|
|
+#define I2C_WRITE 0
|
|
|
+#define I2C_READ 1
|
|
|
+
|
|
|
+#define I2CSTAT_MTM 0xC0 /* Master Transmit Mode */
|
|
|
+#define I2CSTAT_MRM 0x80 /* Master Receive Mode */
|
|
|
+#define I2CSTAT_BSY 0x20 /* Read: Bus Busy */
|
|
|
+#define I2CSTAT_SS 0x20 /* Write: START (1) / STOP (0) */
|
|
|
+#define I2CSTAT_RXTXEN 0x10 /* Rx/Tx enable */
|
|
|
+#define I2CSTAT_ABT 0x08 /* Arbitration bit */
|
|
|
+#define I2CSTAT_NACK 0x01 /* Nack bit */
|
|
|
+#define I2CCON_IRCLR 0x100 /* Interrupt Clear bit */
|
|
|
+#define I2CCON_ACKGEN 0x80 /* Acknowledge generation */
|
|
|
+#define I2CCON_TCP256 0x40 /* Tx-clock prescaler: 16 (0) / 256 (1) */
|
|
|
+#define I2CCON_IRENB 0x20 /* Interrupt Enable bit */
|
|
|
+#define I2CCON_IRPND 0x10 /* Interrupt pending bit */
|
|
|
+#define I2CCON_TCDMSK 0x0F /* I2C-bus transmit clock divider bit mask */
|
|
|
+
|
|
|
+#ifdef CONFIG_ARCH_S5P6818
|
|
|
+#define SDADLY_CLKSTEP 5 /* SDA delay: Reg. val. is multiple of 5 clks */
|
|
|
+#define SDADLY_MAX 3 /* SDA delay: Max. reg. value is 3 */
|
|
|
+#define I2CLC_FILTER 0x04 /* SDA filter on */
|
|
|
+#else
|
|
|
+#define STOPCON_CLR 0x01 /* Clock Line Release */
|
|
|
+#define STOPCON_DLR 0x02 /* Data Line Release */
|
|
|
+#define STOPCON_NAG 0x04 /* not-ackn. generation and data shift cont. */
|
|
|
+#endif
|
|
|
+
|
|
|
+#define I2C_TIMEOUT_MS 10 /* 10 ms */
|
|
|
+
|
|
|
+#define I2C_M_NOSTOP 0x100
|
|
|
+
|
|
|
+#define MAX_I2C_NUM 3
|
|
|
+
|
|
|
+#define DEFAULT_SPEED 100000 /* default I2C speed [Hz] */
|
|
|
+
|
|
|
+DECLARE_GLOBAL_DATA_PTR;
|
|
|
+
|
|
|
+struct nx_i2c_regs {
|
|
|
+ uint iiccon;
|
|
|
+ uint iicstat;
|
|
|
+ uint iicadd;
|
|
|
+ uint iicds;
|
|
|
+#ifdef CONFIG_ARCH_S5P6818
|
|
|
+ /* S5P6818: Offset 0x10 is Line Control Register (SDA-delay, Filter) */
|
|
|
+ uint iiclc;
|
|
|
+#else
|
|
|
+ /* S5P4418: Offset 0x10 is Stop Control Register */
|
|
|
+ uint iicstopcon;
|
|
|
+#endif
|
|
|
+};
|
|
|
+
|
|
|
+struct nx_i2c_bus {
|
|
|
+ uint bus_num;
|
|
|
+ struct nx_i2c_regs *regs;
|
|
|
+ uint speed;
|
|
|
+ uint target_speed;
|
|
|
+#ifdef CONFIG_ARCH_S5P6818
|
|
|
+ uint sda_delay;
|
|
|
+#else
|
|
|
+ /* setup time for Stop condition [us] */
|
|
|
+ uint tsu_stop;
|
|
|
+#endif
|
|
|
+};
|
|
|
+
|
|
|
+/* s5pxx18 i2c must be reset before enabled */
|
|
|
+static void i2c_reset(int ch)
|
|
|
+{
|
|
|
+ int rst_id = RESET_ID_I2C0 + ch;
|
|
|
+
|
|
|
+ nx_rstcon_setrst(rst_id, 0);
|
|
|
+ nx_rstcon_setrst(rst_id, 1);
|
|
|
+}
|
|
|
+
|
|
|
+static uint i2c_get_clkrate(struct nx_i2c_bus *bus)
|
|
|
+{
|
|
|
+ struct clk *clk;
|
|
|
+ int index = bus->bus_num;
|
|
|
+ char name[50] = {0, };
|
|
|
+
|
|
|
+ sprintf(name, "%s.%d", DEV_NAME_I2C, index);
|
|
|
+ clk = clk_get((const char *)name);
|
|
|
+ if (!clk)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ return clk_get_rate(clk);
|
|
|
+}
|
|
|
+
|
|
|
+static uint i2c_set_clk(struct nx_i2c_bus *bus, uint enb)
|
|
|
+{
|
|
|
+ struct clk *clk;
|
|
|
+ char name[50];
|
|
|
+
|
|
|
+ sprintf(name, "%s.%d", DEV_NAME_I2C, bus->bus_num);
|
|
|
+ clk = clk_get((const char *)name);
|
|
|
+ if (!clk) {
|
|
|
+ debug("%s(): clk_get(%s) error!\n",
|
|
|
+ __func__, (const char *)name);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ clk_disable(clk);
|
|
|
+ if (enb)
|
|
|
+ clk_enable(clk);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef CONFIG_ARCH_S5P6818
|
|
|
+/* Set SDA line delay, not available at S5P4418 */
|
|
|
+static int nx_i2c_set_sda_delay(struct nx_i2c_bus *bus)
|
|
|
+{
|
|
|
+ struct nx_i2c_regs *i2c = bus->regs;
|
|
|
+ uint pclk = 0;
|
|
|
+ uint t_pclk = 0;
|
|
|
+ uint delay = 0;
|
|
|
+
|
|
|
+ /* get input clock of the I2C-controller */
|
|
|
+ pclk = i2c_get_clkrate(bus);
|
|
|
+
|
|
|
+ if (bus->sda_delay) {
|
|
|
+ /* t_pclk = period time of one pclk [ns] */
|
|
|
+ t_pclk = DIV_ROUND_UP(1000, pclk / 1000000);
|
|
|
+ /* delay = number of pclks required for sda_delay [ns] */
|
|
|
+ delay = DIV_ROUND_UP(bus->sda_delay, t_pclk);
|
|
|
+ /* delay = register value (step of 5 clocks) */
|
|
|
+ delay = DIV_ROUND_UP(delay, SDADLY_CLKSTEP);
|
|
|
+ /* max. possible register value = 3 */
|
|
|
+ if (delay > SDADLY_MAX) {
|
|
|
+ delay = SDADLY_MAX;
|
|
|
+ debug("%s(): sda-delay des.: %dns, sat. to max.: %dns (granularity: %dns)\n",
|
|
|
+ __func__, bus->sda_delay, t_pclk * delay * SDADLY_CLKSTEP,
|
|
|
+ t_pclk * SDADLY_CLKSTEP);
|
|
|
+ } else {
|
|
|
+ debug("%s(): sda-delay des.: %dns, act.: %dns (granularity: %dns)\n",
|
|
|
+ __func__, bus->sda_delay, t_pclk * delay * SDADLY_CLKSTEP,
|
|
|
+ t_pclk * SDADLY_CLKSTEP);
|
|
|
+ }
|
|
|
+
|
|
|
+ delay |= I2CLC_FILTER;
|
|
|
+ } else {
|
|
|
+ delay = 0;
|
|
|
+ debug("%s(): sda-delay = 0\n", __func__);
|
|
|
+ }
|
|
|
+
|
|
|
+ delay &= 0x7;
|
|
|
+ writel(delay, &i2c->iiclc);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+static int nx_i2c_set_bus_speed(struct udevice *dev, uint speed)
|
|
|
+{
|
|
|
+ struct nx_i2c_bus *bus = dev_get_priv(dev);
|
|
|
+ struct nx_i2c_regs *i2c = bus->regs;
|
|
|
+ unsigned long pclk, pres = 16, div;
|
|
|
+
|
|
|
+ if (i2c_set_clk(bus, 1))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ /* get input clock of the I2C-controller */
|
|
|
+ pclk = i2c_get_clkrate(bus);
|
|
|
+
|
|
|
+ /* calculate prescaler and divisor values */
|
|
|
+ if ((pclk / pres / (16 + 1)) > speed)
|
|
|
+ /* prescaler value 16 is too less --> set to 256 */
|
|
|
+ pres = 256;
|
|
|
+
|
|
|
+ div = 0;
|
|
|
+ /* actual divider = div + 1 */
|
|
|
+ while ((pclk / pres / (div + 1)) > speed)
|
|
|
+ div++;
|
|
|
+
|
|
|
+ if (div > 0xF) {
|
|
|
+ debug("%s(): pres==%ld, div==0x%lx is saturated to 0xF !)\n",
|
|
|
+ __func__, pres, div);
|
|
|
+ div = 0xF;
|
|
|
+ } else {
|
|
|
+ debug("%s(): pres==%ld, div==0x%lx)\n", __func__, pres, div);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* set Tx-clock divisor and prescaler values */
|
|
|
+ writel((div & I2CCON_TCDMSK) | ((pres == 256) ? I2CCON_TCP256 : 0),
|
|
|
+ &i2c->iiccon);
|
|
|
+
|
|
|
+ /* init to SLAVE REVEIVE and set slaveaddr */
|
|
|
+ writel(0, &i2c->iicstat);
|
|
|
+ writel(0x00, &i2c->iicadd);
|
|
|
+
|
|
|
+ /* program Master Transmit (and implicit STOP) */
|
|
|
+ writel(I2CSTAT_MTM | I2CSTAT_RXTXEN, &i2c->iicstat);
|
|
|
+
|
|
|
+ /* calculate actual I2C speed [Hz] */
|
|
|
+ bus->speed = pclk / ((div + 1) * pres);
|
|
|
+ debug("%s(): speed des.: %dHz, act.: %dHz\n",
|
|
|
+ __func__, speed, bus->speed);
|
|
|
+
|
|
|
+#ifdef CONFIG_ARCH_S5P6818
|
|
|
+ nx_i2c_set_sda_delay(bus);
|
|
|
+#else
|
|
|
+ /* setup time for Stop condition [us], min. 4us @ 100kHz I2C-clock */
|
|
|
+ bus->tsu_stop = DIV_ROUND_UP(400, bus->speed / 1000);
|
|
|
+#endif
|
|
|
+
|
|
|
+ if (i2c_set_clk(bus, 0))
|
|
|
+ return -EINVAL;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void i2c_process_node(struct udevice *dev)
|
|
|
+{
|
|
|
+ struct nx_i2c_bus *bus = dev_get_priv(dev);
|
|
|
+
|
|
|
+ bus->target_speed = dev_read_s32_default(dev, "clock-frequency",
|
|
|
+ DEFAULT_SPEED);
|
|
|
+#ifdef CONFIG_ARCH_S5P6818
|
|
|
+ bus->sda_delay = dev_read_s32_default(dev, "i2c-sda-delay-ns", 0);
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+static int nx_i2c_probe(struct udevice *dev)
|
|
|
+{
|
|
|
+ struct nx_i2c_bus *bus = dev_get_priv(dev);
|
|
|
+ fdt_addr_t addr;
|
|
|
+
|
|
|
+ /* get regs = i2c base address */
|
|
|
+ addr = devfdt_get_addr(dev);
|
|
|
+ if (addr == FDT_ADDR_T_NONE)
|
|
|
+ return -EINVAL;
|
|
|
+ bus->regs = (struct nx_i2c_regs *)addr;
|
|
|
+
|
|
|
+ bus->bus_num = dev->seq;
|
|
|
+
|
|
|
+ /* i2c node parsing */
|
|
|
+ i2c_process_node(dev);
|
|
|
+ if (!bus->target_speed)
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ /* reset */
|
|
|
+ i2c_reset(bus->bus_num);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* i2c bus busy check */
|
|
|
+static int i2c_is_busy(struct nx_i2c_regs *i2c)
|
|
|
+{
|
|
|
+ ulong start_time;
|
|
|
+
|
|
|
+ start_time = get_timer(0);
|
|
|
+ while (readl(&i2c->iicstat) & I2CSTAT_BSY) {
|
|
|
+ if (get_timer(start_time) > I2C_TIMEOUT_MS) {
|
|
|
+ debug("Timeout\n");
|
|
|
+ return -EBUSY;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* irq enable/disable functions */
|
|
|
+static void i2c_enable_irq(struct nx_i2c_regs *i2c)
|
|
|
+{
|
|
|
+ unsigned int reg;
|
|
|
+
|
|
|
+ reg = readl(&i2c->iiccon);
|
|
|
+ reg |= I2CCON_IRENB;
|
|
|
+ writel(reg, &i2c->iiccon);
|
|
|
+}
|
|
|
+
|
|
|
+/* irq clear function */
|
|
|
+static void i2c_clear_irq(struct nx_i2c_regs *i2c)
|
|
|
+{
|
|
|
+ unsigned int reg;
|
|
|
+
|
|
|
+ reg = readl(&i2c->iiccon);
|
|
|
+ /* reset interrupt pending flag */
|
|
|
+ reg &= ~(I2CCON_IRPND);
|
|
|
+ /*
|
|
|
+ * Interrupt must also be cleared!
|
|
|
+ * Otherwise linux boot may hang after:
|
|
|
+ * [ 0.436000] NetLabel: unlabeled traffic allowed by default
|
|
|
+ * Next would be:
|
|
|
+ * [ 0.442000] clocksource: Switched to clocksource source timer
|
|
|
+ */
|
|
|
+ reg |= I2CCON_IRCLR;
|
|
|
+ writel(reg, &i2c->iiccon);
|
|
|
+}
|
|
|
+
|
|
|
+/* ack enable functions */
|
|
|
+static void i2c_enable_ack(struct nx_i2c_regs *i2c)
|
|
|
+{
|
|
|
+ unsigned int reg;
|
|
|
+
|
|
|
+ reg = readl(&i2c->iiccon);
|
|
|
+ reg |= I2CCON_ACKGEN;
|
|
|
+ writel(reg, &i2c->iiccon);
|
|
|
+}
|
|
|
+
|
|
|
+static void i2c_send_stop(struct nx_i2c_bus *bus)
|
|
|
+{
|
|
|
+ struct nx_i2c_regs *i2c = bus->regs;
|
|
|
+
|
|
|
+ if (IS_ENABLED(CONFIG_ARCH_S5P6818)) {
|
|
|
+ unsigned int reg;
|
|
|
+
|
|
|
+ reg = readl(&i2c->iicstat);
|
|
|
+ reg |= I2CSTAT_MRM | I2CSTAT_RXTXEN;
|
|
|
+ reg &= (~I2CSTAT_SS);
|
|
|
+
|
|
|
+ writel(reg, &i2c->iicstat);
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+ } else { /* S5P4418 */
|
|
|
+ writel(STOPCON_NAG, &i2c->iicstopcon);
|
|
|
+
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Clock Line Release --> SDC changes from Low to High and
|
|
|
+ * SDA from High to Low
|
|
|
+ */
|
|
|
+ writel(STOPCON_CLR, &i2c->iicstopcon);
|
|
|
+
|
|
|
+ /* Hold SDA Low (Setup Time for Stop condition) */
|
|
|
+ udelay(bus->tsu_stop);
|
|
|
+
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+
|
|
|
+ /* Master Receive Mode Stop --> SDA becomes High */
|
|
|
+ writel(I2CSTAT_MRM, &i2c->iicstat);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int wait_for_xfer(struct nx_i2c_regs *i2c)
|
|
|
+{
|
|
|
+ unsigned long start_time = get_timer(0);
|
|
|
+
|
|
|
+ do {
|
|
|
+ if (readl(&i2c->iiccon) & I2CCON_IRPND)
|
|
|
+ /* return -EREMOTEIO if not Acknowledged, otherwise 0 */
|
|
|
+ return (readl(&i2c->iicstat) & I2CSTAT_NACK) ?
|
|
|
+ -EREMOTEIO : 0;
|
|
|
+ } while (get_timer(start_time) < I2C_TIMEOUT_MS);
|
|
|
+
|
|
|
+ return -ETIMEDOUT;
|
|
|
+}
|
|
|
+
|
|
|
+static int i2c_transfer(struct nx_i2c_regs *i2c,
|
|
|
+ uchar cmd_type,
|
|
|
+ uchar chip_addr,
|
|
|
+ uchar addr[],
|
|
|
+ uchar addr_len,
|
|
|
+ uchar data[],
|
|
|
+ unsigned short data_len,
|
|
|
+ uint seq)
|
|
|
+{
|
|
|
+ uint status;
|
|
|
+ int i = 0, result;
|
|
|
+
|
|
|
+ /* Note: data_len = 0 is supported for "probe_chip" */
|
|
|
+
|
|
|
+ i2c_enable_irq(i2c);
|
|
|
+ i2c_enable_ack(i2c);
|
|
|
+
|
|
|
+ /* Get the slave chip address going */
|
|
|
+ /* Enable Rx/Tx */
|
|
|
+ writel(I2CSTAT_RXTXEN, &i2c->iicstat);
|
|
|
+
|
|
|
+ writel(chip_addr, &i2c->iicds);
|
|
|
+ status = I2CSTAT_RXTXEN | I2CSTAT_SS;
|
|
|
+ if (cmd_type == I2C_WRITE || (addr && addr_len))
|
|
|
+ status |= I2CSTAT_MTM;
|
|
|
+ else
|
|
|
+ status |= I2CSTAT_MRM;
|
|
|
+
|
|
|
+ writel(status, &i2c->iicstat);
|
|
|
+ if (seq)
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+
|
|
|
+ /* Wait for chip address to transmit. */
|
|
|
+ result = wait_for_xfer(i2c);
|
|
|
+ if (result) {
|
|
|
+ debug("%s: transmitting chip address failed\n", __func__);
|
|
|
+ goto bailout;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If register address needs to be transmitted - do it now. */
|
|
|
+ if (addr && addr_len) { /* register addr */
|
|
|
+ while ((i < addr_len) && !result) {
|
|
|
+ writel(addr[i++], &i2c->iicds);
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+ result = wait_for_xfer(i2c);
|
|
|
+ }
|
|
|
+
|
|
|
+ i = 0;
|
|
|
+ if (result) {
|
|
|
+ debug("%s: transmitting register address failed\n",
|
|
|
+ __func__);
|
|
|
+ goto bailout;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (cmd_type) {
|
|
|
+ case I2C_WRITE:
|
|
|
+ while ((i < data_len) && !result) {
|
|
|
+ writel(data[i++], &i2c->iicds);
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+ result = wait_for_xfer(i2c);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case I2C_READ:
|
|
|
+ if (addr && addr_len) {
|
|
|
+ /*
|
|
|
+ * Register address has been sent, now send slave chip
|
|
|
+ * address again to start the actual read transaction.
|
|
|
+ */
|
|
|
+ writel(chip_addr, &i2c->iicds);
|
|
|
+
|
|
|
+ /* Generate a re-START. */
|
|
|
+ writel(I2CSTAT_MRM | I2CSTAT_RXTXEN |
|
|
|
+ I2CSTAT_SS, &i2c->iicstat);
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+ result = wait_for_xfer(i2c);
|
|
|
+ if (result) {
|
|
|
+ debug("%s: I2C_READ: sending chip addr. failed\n",
|
|
|
+ __func__);
|
|
|
+ goto bailout;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ while ((i < data_len) && !result) {
|
|
|
+ /* disable ACK for final READ */
|
|
|
+ if (i == data_len - 1)
|
|
|
+ clrbits_le32(&i2c->iiccon, I2CCON_ACKGEN);
|
|
|
+
|
|
|
+ i2c_clear_irq(i2c);
|
|
|
+ result = wait_for_xfer(i2c);
|
|
|
+ data[i++] = readb(&i2c->iicds);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result == -EREMOTEIO)
|
|
|
+ /* Not Acknowledged --> normal terminated read. */
|
|
|
+ result = 0;
|
|
|
+ else if (result == -ETIMEDOUT)
|
|
|
+ debug("%s: I2C_READ: time out\n", __func__);
|
|
|
+ else
|
|
|
+ debug("%s: I2C_READ: read not terminated with NACK\n",
|
|
|
+ __func__);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ debug("%s: bad call\n", __func__);
|
|
|
+ result = -EINVAL;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+bailout:
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+static int nx_i2c_read(struct udevice *dev, uchar chip_addr, uint addr,
|
|
|
+ uint alen, uchar *buffer, uint len, uint seq)
|
|
|
+{
|
|
|
+ struct nx_i2c_bus *i2c;
|
|
|
+ uchar xaddr[4];
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ i2c = dev_get_priv(dev);
|
|
|
+ if (!i2c)
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ if (alen > 4) {
|
|
|
+ debug("I2C read: addr len %d not supported\n", alen);
|
|
|
+ return -EADDRNOTAVAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (alen > 0)
|
|
|
+ xaddr[0] = (addr >> 24) & 0xFF;
|
|
|
+
|
|
|
+ if (alen > 0) {
|
|
|
+ xaddr[0] = (addr >> 24) & 0xFF;
|
|
|
+ xaddr[1] = (addr >> 16) & 0xFF;
|
|
|
+ xaddr[2] = (addr >> 8) & 0xFF;
|
|
|
+ xaddr[3] = addr & 0xFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = i2c_transfer(i2c->regs, I2C_READ, chip_addr << 1,
|
|
|
+ &xaddr[4 - alen], alen, buffer, len, seq);
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ debug("I2C read failed %d\n", ret);
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int nx_i2c_write(struct udevice *dev, uchar chip_addr, uint addr,
|
|
|
+ uint alen, uchar *buffer, uint len, uint seq)
|
|
|
+{
|
|
|
+ struct nx_i2c_bus *i2c;
|
|
|
+ uchar xaddr[4];
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ i2c = dev_get_priv(dev);
|
|
|
+ if (!i2c)
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ if (alen > 4) {
|
|
|
+ debug("I2C write: addr len %d not supported\n", alen);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (alen > 0) {
|
|
|
+ xaddr[0] = (addr >> 24) & 0xFF;
|
|
|
+ xaddr[1] = (addr >> 16) & 0xFF;
|
|
|
+ xaddr[2] = (addr >> 8) & 0xFF;
|
|
|
+ xaddr[3] = addr & 0xFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = i2c_transfer(i2c->regs, I2C_WRITE, chip_addr << 1,
|
|
|
+ &xaddr[4 - alen], alen, buffer, len, seq);
|
|
|
+ if (ret) {
|
|
|
+ debug("I2C write failed %d\n", ret);
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int nx_i2c_xfer(struct udevice *dev, struct i2c_msg *msg, int nmsgs)
|
|
|
+{
|
|
|
+ struct nx_i2c_bus *bus = dev_get_priv(dev);
|
|
|
+ struct nx_i2c_regs *i2c = bus->regs;
|
|
|
+ int ret;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* The power loss by the clock, only during on/off. */
|
|
|
+ ret = i2c_set_clk(bus, 1);
|
|
|
+
|
|
|
+ if (!ret)
|
|
|
+ /* Bus State(Busy) check */
|
|
|
+ ret = i2c_is_busy(i2c);
|
|
|
+ if (!ret) {
|
|
|
+ for (i = 0; i < nmsgs; msg++, i++) {
|
|
|
+ if (msg->flags & I2C_M_RD) {
|
|
|
+ ret = nx_i2c_read(dev, msg->addr, 0, 0,
|
|
|
+ msg->buf, msg->len, i);
|
|
|
+ } else {
|
|
|
+ ret = nx_i2c_write(dev, msg->addr, 0, 0,
|
|
|
+ msg->buf, msg->len, i);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ret) {
|
|
|
+ debug("i2c_xfer: error sending\n");
|
|
|
+ ret = -EREMOTEIO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ i2c_send_stop(bus);
|
|
|
+ if (i2c_set_clk(bus, 0))
|
|
|
+ ret = -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+};
|
|
|
+
|
|
|
+static int nx_i2c_probe_chip(struct udevice *dev, u32 chip_addr,
|
|
|
+ u32 chip_flags)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ struct nx_i2c_bus *bus = dev_get_priv(dev);
|
|
|
+
|
|
|
+ ret = i2c_set_clk(bus, 1);
|
|
|
+
|
|
|
+ if (!ret) {
|
|
|
+ /*
|
|
|
+ * Send Chip Address only
|
|
|
+ * --> I2C transfer with data length and address length = 0.
|
|
|
+ * If there is a Slave, i2c_transfer() returns 0 (acknowledge
|
|
|
+ * transfer).
|
|
|
+ * I2C_WRITE must be used in order Master Transmit Mode is
|
|
|
+ * selected. Otherwise (in Master Receive Mode, I2C_READ)
|
|
|
+ * sending the stop condition below is not working (SDA does
|
|
|
+ * not transit to High).
|
|
|
+ */
|
|
|
+ ret = i2c_transfer(bus->regs, I2C_WRITE, (uchar)chip_addr << 1,
|
|
|
+ NULL, 0, NULL, 0, 0);
|
|
|
+
|
|
|
+ i2c_send_stop(bus);
|
|
|
+ if (i2c_set_clk(bus, 0))
|
|
|
+ ret = -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static const struct dm_i2c_ops nx_i2c_ops = {
|
|
|
+ .xfer = nx_i2c_xfer,
|
|
|
+ .probe_chip = nx_i2c_probe_chip,
|
|
|
+ .set_bus_speed = nx_i2c_set_bus_speed,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct udevice_id nx_i2c_ids[] = {
|
|
|
+ { .compatible = "nexell,s5pxx18-i2c" },
|
|
|
+ { }
|
|
|
+};
|
|
|
+
|
|
|
+U_BOOT_DRIVER(i2c_nexell) = {
|
|
|
+ .name = "i2c_nexell",
|
|
|
+ .id = UCLASS_I2C,
|
|
|
+ .of_match = nx_i2c_ids,
|
|
|
+ .probe = nx_i2c_probe,
|
|
|
+ .priv_auto_alloc_size = sizeof(struct nx_i2c_bus),
|
|
|
+ .ops = &nx_i2c_ops,
|
|
|
+};
|