| #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 = sizeof(struct nx_i2c_bus), |
| .ops = &nx_i2c_ops, |
| }; |