/* * Copyright 2013 GE Intelligent Platforms, Inc * Copyright 2006,2009 Freescale Semiconductor, Inc. * * 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. * * Early I2C support functions to read SPD data or board * information. * Based on U-Boot drivers/i2c/fsl_i2c.c */ #include #include #include #include #include /* FSL I2C registers */ #define FSL_I2C_ADR 0x00 #define FSL_I2C_FDR 0x04 #define FSL_I2C_CR 0x08 #define FSL_I2C_SR 0x0C #define FSL_I2C_DR 0x10 #define FSL_I2C_DFSRR 0x14 /* Bits of FSL I2C registers */ #define I2C_SR_RXAK 0x01 #define I2C_SR_MIF 0x02 #define I2C_SR_MAL 0x10 #define I2C_SR_MBB 0x20 #define I2C_SR_MCF 0x80 #define I2C_CR_RSTA 0x04 #define I2C_CR_TXAK 0x08 #define I2C_CR_MTX 0x10 #define I2C_CR_MSTA 0x20 #define I2C_CR_MEN 0x80 /* * Set the I2C bus speed for a given I2C device * * parameters: * - i2c: the I2C base address. * - i2c_clk: I2C bus clock frequency. * - speed: the desired speed of the bus. * * The I2C device must be stopped before calling this function. * * The return value is the actual bus speed that is set. */ static uint32_t fsl_set_i2c_bus_speed(const void __iomem *i2c, uint32_t i2c_clk, uint32_t speed) { uint8_t dfsr, fdr = 0x31; /* Default if no FDR found */ uint16_t a, b, ga, gb, bin_gb, bin_ga, divider; uint32_t c_div, est_div; divider = min((uint16_t)(i2c_clk / speed), (uint16_t) -1); /* Condition 1: dfsr <= 50/T */ dfsr = (5 * (i2c_clk / 1000)) / 100000; if (!dfsr) dfsr = 1; est_div = ~0; /* * Bus speed is calculated as per Freescale AN2919. * a, b and dfsr matches identifiers A,B and C as in the * application note. */ for (ga = 0x4, a = 10; a <= 30; ga++, a += 2) { for (gb = 0; gb < 8; gb++) { b = 16 << gb; c_div = b * (a + (((3 * dfsr) / b) * 2)); if ((c_div > divider) && (c_div < est_div)) { est_div = c_div; bin_gb = gb << 2; bin_ga = (ga & 0x3) | ((ga & 0x4) << 3); fdr = bin_gb | bin_ga; speed = i2c_clk / est_div; } } if (a == 20) a += 2; if (a == 24) a += 4; } writeb(dfsr, i2c + FSL_I2C_DFSRR); /* set default filter */ writeb(fdr, i2c + FSL_I2C_FDR); /* set bus speed */ return speed; } void fsl_i2c_init(void __iomem *i2c, int speed, int slaveadd) { uint32_t i2c_clk; i2c_clk = fsl_get_i2c_freq(); writeb(0, i2c + FSL_I2C_CR); early_udelay(5); fsl_set_i2c_bus_speed(i2c, i2c_clk, speed); writeb(slaveadd << 1, i2c + FSL_I2C_ADR); writeb(0x0, i2c + FSL_I2C_SR); writeb(I2C_CR_MEN, i2c + FSL_I2C_CR); } static uint32_t fsl_usec2ticks(uint32_t usec) { ulong ticks; if (usec < 1000) { ticks = (usec * (fsl_get_timebase_clock() / 1000)); ticks = (ticks + 500) / 1000; } else { ticks = (usec / 10); ticks *= (fsl_get_timebase_clock() / 100000); } return ticks; } static int fsl_i2c_wait4bus(void __iomem *i2c) { uint64_t timeval = get_ticks(); const uint64_t timeout = fsl_usec2ticks(20000); while (readb(i2c + FSL_I2C_SR) & I2C_SR_MBB) if ((get_ticks() - timeval) > timeout) return -1; return 0; } void fsl_i2c_stop(void __iomem *i2c) { writeb(I2C_CR_MEN, i2c + FSL_I2C_CR); } static int fsl_i2c_wait(void __iomem *i2c, int write) { const uint64_t timeout = fsl_usec2ticks(100000); uint64_t timeval = get_ticks(); int csr; do { csr = readb(i2c + FSL_I2C_SR); if (csr & I2C_SR_MIF) break; } while ((get_ticks() - timeval) < timeout); if ((get_ticks() - timeval) > timeout) goto error; csr = readb(i2c + FSL_I2C_SR); writeb(0x0, i2c + FSL_I2C_SR); if (csr & I2C_SR_MAL) goto error; if (!(csr & I2C_SR_MCF)) goto error; if (write == 0 && (csr & I2C_SR_RXAK)) goto error; return 0; error: return -1; } static int __i2c_write(void __iomem *i2c, uint8_t *data, int length) { int i; for (i = 0; i < length; i++) { writeb(data[i], i2c + FSL_I2C_DR); if (fsl_i2c_wait(i2c, 0) < 0) break; } return i; } static int __i2c_read(void __iomem *i2c, uint8_t *data, int length) { int i; uint8_t val = I2C_CR_MEN | I2C_CR_MSTA; if (length == 1) writeb(val | I2C_CR_TXAK, i2c + FSL_I2C_CR); else writeb(val, i2c + FSL_I2C_CR); readb(i2c + FSL_I2C_DR); for (i = 0; i < length; i++) { if (fsl_i2c_wait(i2c, 1) < 0) break; /* Generate ack on last next to last byte */ if (i == length - 2) writeb(val | I2C_CR_TXAK, i2c + FSL_I2C_CR); /* Do not generate stop on last byte */ if (i == length - 1) writeb(val | I2C_CR_MTX, i2c + FSL_I2C_CR); data[i] = readb(i2c + FSL_I2C_DR); } return i; } static int fsl_i2c_write_addr(void __iomem *i2c, uint8_t dev, uint8_t dir, int rsta) { uint8_t val = I2C_CR_MEN | I2C_CR_MSTA | I2C_CR_MTX; if (rsta) val |= I2C_CR_RSTA; writeb(val, i2c + FSL_I2C_CR); writeb((dev << 1) | dir, i2c + FSL_I2C_DR); if (fsl_i2c_wait(i2c, 0) < 0) return 0; return 1; } int fsl_i2c_read(void __iomem *i2c, uint8_t dev, uint addr, int alen, uint8_t *data, int length) { int i = -1; uint8_t *a = (uint8_t *)&addr; if (alen && (fsl_i2c_wait4bus(i2c) >= 0) && (fsl_i2c_write_addr(i2c, dev, 0, 0) != 0) && (__i2c_write(i2c, &a[4 - alen], alen) == alen)) i = 0; if (length && fsl_i2c_write_addr(i2c, dev, 1, 1) != 0) i = __i2c_read(i2c, data, length); if (i == length) return 0; return -1; }