/* * Faraday I2C Controller * * (C) Copyright 2010 Faraday Technology * Dante Su * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include "fti2c010.h" #ifndef CONFIG_HARD_I2C #error "fti2c010: CONFIG_HARD_I2C is not defined" #endif #ifndef CONFIG_SYS_I2C_SPEED #define CONFIG_SYS_I2C_SPEED 5000 #endif #ifndef CONFIG_FTI2C010_CLOCK #define CONFIG_FTI2C010_CLOCK clk_get_rate("I2C") #endif #ifndef CONFIG_FTI2C010_TIMEOUT #define CONFIG_FTI2C010_TIMEOUT 10 /* ms */ #endif /* 7-bit dev address + 1-bit read/write */ #define I2C_RD(dev) ((((dev) << 1) & 0xfe) | 1) #define I2C_WR(dev) (((dev) << 1) & 0xfe) struct fti2c010_chip { struct fti2c010_regs *regs; uint bus; uint speed; }; static struct fti2c010_chip chip_list[] = { { .bus = 0, .regs = (struct fti2c010_regs *)CONFIG_FTI2C010_BASE, }, #ifdef CONFIG_I2C_MULTI_BUS # ifdef CONFIG_FTI2C010_BASE1 { .bus = 1, .regs = (struct fti2c010_regs *)CONFIG_FTI2C010_BASE1, }, # endif # ifdef CONFIG_FTI2C010_BASE2 { .bus = 2, .regs = (struct fti2c010_regs *)CONFIG_FTI2C010_BASE2, }, # endif # ifdef CONFIG_FTI2C010_BASE3 { .bus = 3, .regs = (struct fti2c010_regs *)CONFIG_FTI2C010_BASE3, }, # endif #endif /* #ifdef CONFIG_I2C_MULTI_BUS */ }; static struct fti2c010_chip *curr = chip_list; static int fti2c010_wait(uint32_t mask) { int ret = -1; uint32_t stat, ts; struct fti2c010_regs *regs = curr->regs; for (ts = get_timer(0); get_timer(ts) < CONFIG_FTI2C010_TIMEOUT; ) { stat = readl(®s->sr); if ((stat & mask) == mask) { ret = 0; break; } } return ret; } /* * u-boot I2C API */ /* * Initialization, must be called once on start up, may be called * repeatedly to change the speed and slave addresses. */ void i2c_init(int speed, int slaveaddr) { if (speed || !curr->speed) i2c_set_bus_speed(speed); /* if slave mode disabled */ if (!slaveaddr) return; /* * TODO: * Implement slave mode, but is it really necessary? */ } /* * Probe the given I2C chip address. Returns 0 if a chip responded, * not 0 on failure. */ int i2c_probe(uchar chip) { int ret; struct fti2c010_regs *regs = curr->regs; i2c_init(0, 0); /* 1. Select slave device (7bits Address + 1bit R/W) */ writel(I2C_WR(chip), ®s->dr); writel(CR_ENABLE | CR_TBEN | CR_START, ®s->cr); ret = fti2c010_wait(SR_DT); if (ret) return ret; /* 2. Select device register */ writel(0, ®s->dr); writel(CR_ENABLE | CR_TBEN, ®s->cr); ret = fti2c010_wait(SR_DT); return ret; } /* * Read/Write interface: * chip: I2C chip address, range 0..127 * addr: Memory (register) address within the chip * alen: Number of bytes to use for addr (typically 1, 2 for larger * memories, 0 for register type devices with only one * register) * buffer: Where to read/write the data * len: How many bytes to read/write * * Returns: 0 on success, not 0 on failure */ int i2c_read(uchar chip, uint addr, int alen, uchar *buf, int len) { int ret, pos; uchar paddr[4]; struct fti2c010_regs *regs = curr->regs; i2c_init(0, 0); paddr[0] = (addr >> 0) & 0xFF; paddr[1] = (addr >> 8) & 0xFF; paddr[2] = (addr >> 16) & 0xFF; paddr[3] = (addr >> 24) & 0xFF; /* * Phase A. Set register address */ /* A.1 Select slave device (7bits Address + 1bit R/W) */ writel(I2C_WR(chip), ®s->dr); writel(CR_ENABLE | CR_TBEN | CR_START, ®s->cr); ret = fti2c010_wait(SR_DT); if (ret) return ret; /* A.2 Select device register */ for (pos = 0; pos < alen; ++pos) { uint32_t ctrl = CR_ENABLE | CR_TBEN; writel(paddr[pos], ®s->dr); writel(ctrl, ®s->cr); ret = fti2c010_wait(SR_DT); if (ret) return ret; } /* * Phase B. Get register data */ /* B.1 Select slave device (7bits Address + 1bit R/W) */ writel(I2C_RD(chip), ®s->dr); writel(CR_ENABLE | CR_TBEN | CR_START, ®s->cr); ret = fti2c010_wait(SR_DT); if (ret) return ret; /* B.2 Get register data */ for (pos = 0; pos < len; ++pos) { uint32_t ctrl = CR_ENABLE | CR_TBEN; uint32_t stat = SR_DR; if (pos == len - 1) { ctrl |= CR_NAK | CR_STOP; stat |= SR_ACK; } writel(ctrl, ®s->cr); ret = fti2c010_wait(stat); if (ret) break; buf[pos] = (uchar)(readl(®s->dr) & 0xFF); } return ret; } /* * Read/Write interface: * chip: I2C chip address, range 0..127 * addr: Memory (register) address within the chip * alen: Number of bytes to use for addr (typically 1, 2 for larger * memories, 0 for register type devices with only one * register) * buffer: Where to read/write the data * len: How many bytes to read/write * * Returns: 0 on success, not 0 on failure */ int i2c_write(uchar chip, uint addr, int alen, uchar *buf, int len) { int ret, pos; uchar paddr[4]; struct fti2c010_regs *regs = curr->regs; i2c_init(0, 0); paddr[0] = (addr >> 0) & 0xFF; paddr[1] = (addr >> 8) & 0xFF; paddr[2] = (addr >> 16) & 0xFF; paddr[3] = (addr >> 24) & 0xFF; /* * Phase A. Set register address * * A.1 Select slave device (7bits Address + 1bit R/W) */ writel(I2C_WR(chip), ®s->dr); writel(CR_ENABLE | CR_TBEN | CR_START, ®s->cr); ret = fti2c010_wait(SR_DT); if (ret) return ret; /* A.2 Select device register */ for (pos = 0; pos < alen; ++pos) { uint32_t ctrl = CR_ENABLE | CR_TBEN; writel(paddr[pos], ®s->dr); writel(ctrl, ®s->cr); ret = fti2c010_wait(SR_DT); if (ret) return ret; } /* * Phase B. Set register data */ for (pos = 0; pos < len; ++pos) { uint32_t ctrl = CR_ENABLE | CR_TBEN; if (pos == len - 1) ctrl |= CR_STOP; writel(buf[pos], ®s->dr); writel(ctrl, ®s->cr); ret = fti2c010_wait(SR_DT); if (ret) break; } return ret; } /* * Functions for setting the current I2C bus and its speed */ #ifdef CONFIG_I2C_MULTI_BUS /* * i2c_set_bus_num: * * Change the active I2C bus. Subsequent read/write calls will * go to this one. * * bus - bus index, zero based * * Returns: 0 on success, not 0 on failure */ int i2c_set_bus_num(uint bus) { if (bus >= ARRAY_SIZE(chip_list)) return -1; curr = chip_list + bus; i2c_init(0, 0); return 0; } /* * i2c_get_bus_num: * * Returns index of currently active I2C bus. Zero-based. */ uint i2c_get_bus_num(void) { return curr->bus; } #endif /* #ifdef CONFIG_I2C_MULTI_BUS */ /* * i2c_set_bus_speed: * * Change the speed of the active I2C bus * * speed - bus speed in Hz * * Returns: 0 on success, not 0 on failure */ int i2c_set_bus_speed(uint speed) { struct fti2c010_regs *regs = curr->regs; uint clk = CONFIG_FTI2C010_CLOCK; uint gsr = 0, tsr = 32; uint spd, div; if (!speed) speed = CONFIG_SYS_I2C_SPEED; for (div = 0; div < 0x3ffff; ++div) { /* SCLout = PCLK/(2*(COUNT + 2) + GSR) */ spd = clk / (2 * (div + 2) + gsr); if (spd <= speed) break; } if (curr->speed == spd) return 0; writel(CR_I2CRST, ®s->cr); mdelay(100); if (readl(®s->cr) & CR_I2CRST) { printf("fti2c010: reset timeout\n"); return -1; } curr->speed = spd; writel(TGSR_GSR(gsr) | TGSR_TSR(tsr), ®s->tgsr); writel(CDR_DIV(div), ®s->cdr); return 0; } /* * i2c_get_bus_speed: * * Returns speed of currently active I2C bus in Hz */ uint i2c_get_bus_speed(void) { return curr->speed; }