Patrick Rudolph has uploaded this change for review. ( https://review.coreboot.org/23751
Change subject: soc/cavium: Add twsi ......................................................................
soc/cavium: Add twsi
Add TWSI. Ported from BDK uboot tree.
Change-Id: I9f85d024b3ffd0a0c4376a8880d14db9e3570d79 Signed-off-by: Patrick Rudolph patrick.rudolph@9elements.com --- M src/soc/cavium/cn81xx/Makefile.inc A src/soc/cavium/cn81xx/twsi.c M src/soc/cavium/common/Makefile.inc D src/soc/cavium/common/i2c.c A src/soc/cavium/common/include/soc/twsi.h A src/soc/cavium/common/twsi.c 6 files changed, 764 insertions(+), 331 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/51/23751/1
diff --git a/src/soc/cavium/cn81xx/Makefile.inc b/src/soc/cavium/cn81xx/Makefile.inc index d2bd94b..fbba930 100644 --- a/src/soc/cavium/cn81xx/Makefile.inc +++ b/src/soc/cavium/cn81xx/Makefile.inc @@ -28,12 +28,15 @@ bootblock-y += bootblock.c bootblock-y += clock.c #bootblock-y += gpio.c +bootblock-y += twsi.c bootblock-y += l2c.c bootblock-y += mmu_operations.c #bootblock-y += sdram.c bootblock-y += timer.c bootblock-y += ../common/wdt.c
+romstage-y += twsi.c + romstage-y += sdram.c romstage-y += ../common/bdk/libdram/libdram.c romstage-y += ../common/bdk/libbdk-arch/bdk-csr.c @@ -98,6 +101,7 @@ ramstage-y += ../common/cbmem.c ramstage-y += sdram.c ramstage-y += spi.c +ramstage-y += twsi.c ramstage-$(CONFIG_DRIVERS_UART) += uart.c ##ramstage-y += clock.c #ramstage-y += ../common/gpio.c diff --git a/src/soc/cavium/cn81xx/twsi.c b/src/soc/cavium/cn81xx/twsi.c new file mode 100644 index 0000000..a87de79 --- /dev/null +++ b/src/soc/cavium/cn81xx/twsi.c @@ -0,0 +1,20 @@ +/* + * Copyright 2018-present Facebook, Inc. + * SPDX-License-Identifier: GPL-2.0+ + */ +#include <stddef.h> +#include <assert.h> +#include <soc/twsi.h> + +static void *const twsi_bus[] = { + (void *const)0x87E0D0000000ULL, + (void *const)0x87E0D1000000ULL, +}; + +void *twsi_get_baseaddr(const size_t bus) +{ + assert (bus < ARRAY_SIZE(twsi_bus)); + if (bus >= ARRAY_SIZE(twsi_bus)) + return NULL; + return twsi_bus[bus]; +} diff --git a/src/soc/cavium/common/Makefile.inc b/src/soc/cavium/common/Makefile.inc index 2ed9a5e..6777dfc 100644 --- a/src/soc/cavium/common/Makefile.inc +++ b/src/soc/cavium/common/Makefile.inc @@ -16,5 +16,15 @@ ifeq ($(CONFIG_SOC_CAVIUM_COMMON),y)
bootblock-$(CONFIG_BOOTBLOCK_CUSTOM) += bootblock.c +bootblock-y += twsi.c + + + +romstage-y += twsi.c + + +ramstage-y += twsi.c + +CPPFLAGS_common += -Isrc/soc/cavium/common/include
endif diff --git a/src/soc/cavium/common/i2c.c b/src/soc/cavium/common/i2c.c deleted file mode 100644 index 6c1bdfa..0000000 --- a/src/soc/cavium/common/i2c.c +++ /dev/null @@ -1,331 +0,0 @@ -/* - * This file is part of the coreboot project. - * - * Copyright 2017-present Facebook, 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; version 2 of the License. - * - * 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. - */ - -/* FIXME: scaffolding based on rk3399 */ - -#include <arch/io.h> -#include <assert.h> -#include <cbfs.h> -#include <console/console.h> -#include <delay.h> -//#include <device/i2c_simple.h> -#include <soc/addressmap.h> -//#include <soc/grf.h> -#include <soc/soc.h> -#include <soc/i2c.h> -//#include <soc/clock.h> -#include <stdlib.h> -#include <string.h> - -#define RETRY_COUNT 3 -/* 100000us = 100ms */ -#define I2C_TIMEOUT_US 100000 -#define I2C_BUS_MAX 6 -#define I2C_NOACK 2 -#define I2C_TIMEOUT 3 - -#define i2c_info(x...) do {if (0) printk(BIOS_DEBUG, x); } while (0) - -#if 0 -struct rk_i2c_regs { - u32 i2c_con; - u32 i2c_clkdiv; - u32 i2c_mrxaddr; - u32 i2c_mrxraddr; - u32 i2c_mtxcnt; - u32 i2c_mrxcnt; - u32 i2c_ien; - u32 i2c_ipd; - u32 i2c_fcnt; - u32 reserved0[(0x100 - 0x24) / 4]; - u32 txdata[8]; - u32 reserved1[(0x200 - 0x120) / 4]; - u32 rxdata[8]; -}; - -static const uintptr_t i2c_bus[] = IC_BASES; - -/* Con register bits. */ -#define I2C_ACT2NAK (1<<6) -#define I2C_NAK (1<<5) -#define I2C_STOP (1<<4) -#define I2C_START (1<<3) -#define I2C_MODE_TX (0<<1) -#define I2C_MODE_TRX (1<<1) -#define I2C_MODE_RX (2<<1) -#define I2C_EN (1<<0) - -#define I2C_8BIT (1<<24) -#define I2C_16BIT (3<<24) -#define I2C_24BIT (7<<24) - -/* Mtxcnt register bits. */ -#define I2C_CNT(cnt) ((cnt) & 0x3F) - -#define I2C_NAKRCVI (1<<6) -#define I2C_STOPI (1<<5) -#define I2C_STARTI (1<<4) -#define I2C_MBRFI (1<<3) -#define I2C_MBTFI (1<<2) -#define I2C_BRFI (1<<1) -#define I2C_BTFI (1<<0) -#define I2C_CLEANI 0x7F -#endif - -static int i2c_send_start(struct cavium_i2c_regs *reg_addr) -{ - /* FIXME: stub */ - printk(BIOS_ERR, "%s: implement this.\n", __func__); - return -1; -#if 0 - int res = 0; - int timeout = I2C_TIMEOUT_US; - - i2c_info("I2c Start::Send Start bit\n"); - write32(®_addr->i2c_ipd, I2C_CLEANI); - write32(®_addr->i2c_con, I2C_EN | I2C_START); - while (timeout--) { - if (read32(®_addr->i2c_ipd) & I2C_STARTI) - break; - udelay(1); - } - - if (timeout <= 0) { - printk(BIOS_ERR, "I2C Start::Send Start Bit Timeout\n"); - res = I2C_TIMEOUT; - } - - return res; -#endif -} - -static int i2c_send_stop(struct cavium_i2c_regs *reg_addr) -{ - /* FIXME: stub */ - printk(BIOS_ERR, "%s: implement this.\n", __func__); - return -1; -#if 0 - int res = 0; - int timeout = I2C_TIMEOUT_US; - - i2c_info("I2c Stop::Send Stop bit\n"); - write32(®_addr->i2c_ipd, I2C_CLEANI); - write32(®_addr->i2c_con, I2C_EN | I2C_STOP); - while (timeout--) { - if (read32(®_addr->i2c_ipd) & I2C_STOPI) - break; - udelay(1); - } - write32(®_addr->i2c_con, 0); - if (timeout <= 0) { - printk(BIOS_ERR, "I2C Stop::Send Stop Bit Timeout\n"); - res = I2C_TIMEOUT; - } - - return res; -#endif -} - -static int i2c_read(struct cavium_i2c_regs *reg_addr, struct i2c_msg segment) -{ - /* FIXME: stub */ - printk(BIOS_ERR, "%s: implement this.\n", __func__); - return -1; -#if 0 - int res = 0; - uint8_t *data = segment.buf; - int timeout = I2C_TIMEOUT_US; - unsigned int bytes_remaining = segment.len; - unsigned int bytes_transferred = 0; - unsigned int words_transferred = 0; - unsigned int rxdata = 0; - unsigned int con = 0; - unsigned int i, j; - - write32(®_addr->i2c_mrxaddr, I2C_8BIT | segment.slave << 1 | 1); - write32(®_addr->i2c_mrxraddr, 0); - con = I2C_MODE_TRX | I2C_EN | I2C_ACT2NAK; - while (bytes_remaining) { - bytes_transferred = MIN(bytes_remaining, 32); - bytes_remaining -= bytes_transferred; - if (!bytes_remaining) - con |= I2C_EN | I2C_NAK; - words_transferred = ALIGN_UP(bytes_transferred, 4) / 4; - - write32(®_addr->i2c_ipd, I2C_CLEANI); - write32(®_addr->i2c_con, con); - write32(®_addr->i2c_mrxcnt, bytes_transferred); - - timeout = I2C_TIMEOUT_US; - while (timeout--) { - if (read32(®_addr->i2c_ipd) & I2C_NAKRCVI) { - write32(®_addr->i2c_mrxcnt, 0); - write32(®_addr->i2c_con, 0); - return I2C_NOACK; - } - if (read32(®_addr->i2c_ipd) & I2C_MBRFI) - break; - udelay(1); - } - if (timeout <= 0) { - printk(BIOS_ERR, "I2C Read::Recv Data Timeout\n"); - write32(®_addr->i2c_mrxcnt, 0); - write32(®_addr->i2c_con, 0); - return I2C_TIMEOUT; - } - - for (i = 0; i < words_transferred; i++) { - rxdata = read32(®_addr->rxdata[i]); - i2c_info("I2c Read::RXDATA[%d] = 0x%x\n", i, rxdata); - for (j = 0; j < 4; j++) { - if ((i * 4 + j) == bytes_transferred) - break; - *data++ = (rxdata >> (j * 8)) & 0xff; - } - } - con = I2C_MODE_RX | I2C_EN | I2C_ACT2NAK; - } - return res; -#endif -} - -static int i2c_write(struct rk_i2c_regs *reg_addr, struct i2c_msg segment) -{ - /* FIXME: stub */ - printk(BIOS_ERR, "%s: implement this.\n", __func__); - return -1; -#if 0 - int res = 0; - uint8_t *data = segment.buf; - int timeout = I2C_TIMEOUT_US; - int bytes_remaining = segment.len + 1; - int bytes_transferred = 0; - int words_transferred = 0; - unsigned int i; - unsigned int j = 1; - u32 txdata = 0; - - txdata |= (segment.slave << 1); - while (bytes_remaining) { - bytes_transferred = MIN(bytes_remaining, 32); - words_transferred = ALIGN_UP(bytes_transferred, 4) / 4; - for (i = 0; i < words_transferred; i++) { - do { - if ((i * 4 + j) == bytes_transferred) - break; - txdata |= (*data++) << (j * 8); - } while (++j < 4); - write32(®_addr->txdata[i], txdata); - j = 0; - i2c_info("I2c Write::TXDATA[%d] = 0x%x\n", i, txdata); - txdata = 0; - } - - write32(®_addr->i2c_ipd, I2C_CLEANI); - write32(®_addr->i2c_con, - I2C_EN | I2C_MODE_TX | I2C_ACT2NAK); - write32(®_addr->i2c_mtxcnt, bytes_transferred); - - timeout = I2C_TIMEOUT_US; - while (timeout--) { - if (read32(®_addr->i2c_ipd) & I2C_NAKRCVI) { - write32(®_addr->i2c_mtxcnt, 0); - write32(®_addr->i2c_con, 0); - return I2C_NOACK; - } - if (read32(®_addr->i2c_ipd) & I2C_MBTFI) - break; - udelay(1); - } - - if (timeout <= 0) { - printk(BIOS_ERR, "I2C Write::Send Data Timeout\n"); - write32(®_addr->i2c_mtxcnt, 0); - write32(®_addr->i2c_con, 0); - return I2C_TIMEOUT; - } - - bytes_remaining -= bytes_transferred; - } - return res; -#endif -} - -static int i2c_do_xfer(void *reg_addr, struct i2c_msg segment) -{ - /* FIXME: stub */ - printk(BIOS_ERR, "%s: implement this.\n", __func__); - return -1; -#if 0 - int res = 0; - - if (i2c_send_start(reg_addr)) - return I2C_TIMEOUT; - if (segment.flags & I2C_M_RD) - res = i2c_read(reg_addr, segment); - else - res = i2c_write(reg_addr, segment); - return i2c_send_stop(reg_addr) || res; -#endif -} - -int platform_i2c_transfer(unsigned bus, struct i2c_msg *segments, - int seg_count) -{ - /* FIXME: stub */ - printk(BIOS_ERR, "%s: implement this.\n", __func__); - return -1; -#if 0 - int i; - int res = 0; - struct rk_i2c_regs *regs = (struct rk_i2c_regs *)(i2c_bus[bus]); - struct i2c_msg *seg = segments; - - for (i = 0; i < seg_count; i++, seg++) { - res = i2c_do_xfer(regs, *seg); - if (res) - break; - } - return res; -#endif -} - -void i2c_init(unsigned int bus, unsigned int hz) -{ - /* FIXME: stub */ - printk(BIOS_ERR, "%s: implement this.\n", __func__); - return -1; -#if 0 - unsigned int clk_div; - unsigned int divl; - unsigned int divh; - unsigned int i2c_src_clk; - unsigned int i2c_clk; - struct rk_i2c_regs *regs = (struct rk_i2c_regs *)(i2c_bus[bus]); - - i2c_src_clk = rkclk_i2c_clock_for_bus(bus); - - /* SCL Divisor = 8*(CLKDIVL + 1 + CLKDIVH + 1) - SCL = PCLK / SCLK Divisor */ - clk_div = div_round_up(i2c_src_clk, hz * 8); - divh = clk_div * 3 / 7 - 1; - divl = clk_div - divh - 2; - i2c_clk = i2c_src_clk / (8 * (divl + 1 + divh + 1)); - printk(BIOS_DEBUG, "I2C bus %u: %uHz (divh = %u, divl = %u)\n", - bus, i2c_clk, divh, divl); - assert((divh < 65536) && (divl < 65536) && hz - i2c_clk < 15*KHz); - write32(®s->i2c_clkdiv, (divh << 16) | (divl << 0)); -#endif -} diff --git a/src/soc/cavium/common/include/soc/twsi.h b/src/soc/cavium/common/include/soc/twsi.h new file mode 100644 index 0000000..a9f23ad --- /dev/null +++ b/src/soc/cavium/common/include/soc/twsi.h @@ -0,0 +1,14 @@ +/* + * Copyright 2018-present Facebook, Inc. + * SPDX-License-Identifier: GPL-2.0+ + */ +#include <types.h> +#include <device/i2c.h> + +#ifndef __SOC_CAVIUM_CN81XX_INCLUDE_SOC_TWSI_H +#define __SOC_CAVIUM_CN81XX_INCLUDE_SOC_TWSI_H + +int twsi_init(unsigned int bus, enum i2c_speed hz); +void *twsi_get_baseaddr(const size_t bus); + +#endif diff --git a/src/soc/cavium/common/twsi.c b/src/soc/cavium/common/twsi.c new file mode 100644 index 0000000..e2ea838 --- /dev/null +++ b/src/soc/cavium/common/twsi.c @@ -0,0 +1,716 @@ +/* + * Copyright 2016 Cavium, Inc. support@cavium.com + * Copyright 2018-present Facebook, Inc. + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <console/console.h> +#include <soc/twsi.h> +#include <device/i2c.h> +#include <device/i2c_simple.h> +#include <assert.h> +#include <delay.h> +#include <arch/io.h> + + +#define RST_BOOT ((void *const)0x87e006001600ll) +#define PLL_REF_CLK 50000000 /* 50 MHz */ + +#define TWSI_THP 24 + +#define TWSI_SW_TWSI 0x1000 +#define TWSI_TWSI_SW 0x1008 +#define TWSI_INT 0x1010 +#define TWSI_SW_TWSI_EXT 0x1018 + +union rst_boot { + u64 u; + struct { + u64 rboot_pin:1; + u64 rboot:1; + u64 lboot:10; + u64 lboot_ext23:6; + u64 lboot_ext45:6; + u64 reserved_24_29:6; + u64 lboot_oci:3; + u64 pnr_mul:6; + u64 reserved_39_39:1; + u64 c_mul:7; + u64 reserved_47_54:8; + u64 dis_scan:1; + u64 dis_huk:1; + u64 vrm_err:1; + u64 jt_tstmode:1; + u64 ckill_ppdis:1; + u64 trusted_mode:1; + u64 ejtagdis:1; + u64 jtcsrdis:1; + u64 chipkill:1; + } s; +}; + +union twsx_sw_twsi { + u64 u; + struct { + u64 data:32; + u64 eop_ia:3; + u64 ia:5; + u64 addr:10; + u64 scr:2; + u64 size:3; + u64 sovr:1; + u64 r:1; + u64 op:4; + u64 eia:1; + u64 slonly:1; + u64 v:1; + } s; +}; + +union twsx_sw_twsi_ext { + u64 u; + struct { + u64 data:32; + u64 ia:8; + u64 :24; + } s; +}; + +union twsx_int { + u64 u; + struct { + u64 st_int:1; /** TWSX_SW_TWSI register update int */ + u64 ts_int:1; /** TWSX_TWSI_SW register update int */ + u64 core_int:1; /** TWSI core interrupt, ignored for HLC */ + u64 :5; /** Reserved */ + u64 sda_ovr:1; /** SDA testing override */ + u64 scl_ovr:1; /** SCL testing override */ + u64 sda:1; /** SDA signal */ + u64 scl:1; /** SCL signal */ + u64 :52; /** Reserved */ + } s; +}; + +enum { + TWSI_OP_WRITE = 0, + TWSI_OP_READ = 1, +}; + +enum { + TWSI_EOP_SLAVE_ADDR = 0, + TWSI_EOP_CLK_CTL = 3, + TWSI_SW_EOP_IA = 6, +}; + +enum { + TWSI_SLAVEADD = 0, + TWSI_DATA = 1, + TWSI_CTL = 2, + TWSI_CLKCTL = 3, + TWSI_STAT = 3, + TWSI_SLAVEADD_EXT = 4, + TWSI_RST = 7, +}; + +enum { + TWSI_CTL_AAK = (1 << 2), + TWSI_CTL_IFLG = (1 << 3), + TWSI_CTL_STP = (1 << 4), + TWSI_CTL_STA = (1 << 5), + TWSI_CTL_ENAB = (1 << 6), + TWSI_CTL_CE = (1 << 7), +}; + +enum { + /** Bus error */ + TWSI_STAT_BUS_ERROR = 0x00, + /** Start condition transmitted */ + TWSI_STAT_START = 0x08, + /** Repeat start condition transmitted */ + TWSI_STAT_RSTART = 0x10, + /** Address + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR_ACK = 0x18, + /** Address + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR_NAK = 0x20, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_ACK = 0x28, + /** Data byte transmitted in master mode, ACK received */ + TWSI_STAT_TXDATA_NAK = 0x30, + /** Arbitration lost in address or data byte */ + TWSI_STAT_TX_ARB_LOST = 0x38, + /** Address + read bit transmitted, ACK received */ + TWSI_STAT_RXADDR_ACK = 0x40, + /** Address + read bit transmitted, /ACK received */ + TWSI_STAT_RXADDR_NAK = 0x48, + /** Data byte received in master mode, ACK transmitted */ + TWSI_STAT_RXDATA_ACK_SENT = 0x50, + /** Data byte received, NACK transmitted */ + TWSI_STAT_RXDATA_NAK_SENT = 0x58, + /** Slave address received, sent ACK */ + TWSI_STAT_SLAVE_RXADDR_ACK = 0x60, + /** + * Arbitration lost in address as master, slave address + write bit + * received, ACK transmitted + */ + TWSI_STAT_TX_ACK_ARB_LOST = 0x68, + /** General call address received, ACK transmitted */ + TWSI_STAT_RX_GEN_ADDR_ACK = 0x70, + /** + * Arbitration lost in address as master, general call address + * received, ACK transmitted + */ + TWSI_STAT_RX_GEN_ADDR_ARB_LOST = 0x78, + /** Data byte received after slave address received, ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_ACK = 0x80, + /** Data byte received after slave address received, /ACK transmitted */ + TWSI_STAT_SLAVE_RXDATA_NAK = 0x88, + /** + * Data byte received after general call address received, ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_ACK = 0x90, + /** + * Data byte received after general call address received, /ACK + * transmitted + */ + TWSI_STAT_GEN_RXADDR_NAK = 0x98, + /** STOP or repeated START condition received in slave mode */ + TWSI_STAT_STOP_MULTI_START = 0xA0, + /** Slave address + read bit received, ACK transmitted */ + TWSI_STAT_SLAVE_RXADDR2_ACK = 0xA8, + /** + * Arbitration lost in address as master, slave address + read bit + * received, ACK transmitted + */ + TWSI_STAT_RXDATA_ACK_ARB_LOST = 0xB0, + /** Data byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_ACK = 0xB8, + /** Data byte transmitted in slave mode, /ACK received */ + TWSI_STAT_SLAVE_TXDATA_NAK = 0xC0, + /** Last byte transmitted in slave mode, ACK received */ + TWSI_STAT_SLAVE_TXDATA_END_ACK = 0xC8, + /** Second address byte + write bit transmitted, ACK received */ + TWSI_STAT_TXADDR2DATA_ACK = 0xD0, + /** Second address byte + write bit transmitted, /ACK received */ + TWSI_STAT_TXADDR2DATA_NAK = 0xD8, + /** No relevant status information */ + TWSI_STAT_IDLE = 0xF8 +}; + +/** + * Returns true if we lost arbitration + * + * @param code status code + * @param final_read true if this is the final read operation + * + * @return true if arbitration has been lost, false if it hasn't been lost. + */ +static int twsi_i2c_lost_arb(u8 code, int final_read) +{ + switch (code) { + /* Arbitration lost */ + case TWSI_STAT_TX_ARB_LOST: + case TWSI_STAT_TX_ACK_ARB_LOST: + case TWSI_STAT_RX_GEN_ADDR_ARB_LOST: + case TWSI_STAT_RXDATA_ACK_ARB_LOST: + return -1; + + /* Being addressed as slave, should back off and listen */ + case TWSI_STAT_SLAVE_RXADDR_ACK: + case TWSI_STAT_RX_GEN_ADDR_ACK: + case TWSI_STAT_GEN_RXADDR_ACK: + case TWSI_STAT_GEN_RXADDR_NAK: + return -1; + + /* Core busy as slave */ + case TWSI_STAT_SLAVE_RXDATA_ACK: + case TWSI_STAT_SLAVE_RXDATA_NAK: + case TWSI_STAT_STOP_MULTI_START: + case TWSI_STAT_SLAVE_RXADDR2_ACK: + case TWSI_STAT_SLAVE_TXDATA_ACK: + case TWSI_STAT_SLAVE_TXDATA_NAK: + case TWSI_STAT_SLAVE_TXDATA_END_ACK: + return -1; + + /* Ack allowed on pre-terminal bytes only */ + case TWSI_STAT_RXDATA_ACK_SENT: + if (!final_read) + return 0; + return -1; + + /* NAK allowed on terminal byte only */ + case TWSI_STAT_RXDATA_NAK_SENT: + if (!final_read) + return 0; + return -1; + + case TWSI_STAT_TXDATA_NAK: + case TWSI_STAT_TXADDR_NAK: + case TWSI_STAT_RXADDR_NAK: + case TWSI_STAT_TXADDR2DATA_NAK: + return -1; + } + return 0; +} + +#define RST_BOOT_PNR_MUL(Val) ((Val >> 33) & 0x1F) + +/** + * Writes to the MIO_TWS(0..5)_SW_TWSI register + * + * @param baseaddr Base address of i2c registers + * @param sw_twsi value to write + * + * @return 0 for success, otherwise error + */ +static u64 twsi_write_sw(void *baseaddr, union twsx_sw_twsi sw_twsi) +{ + unsigned long timeout = 500000; + + sw_twsi.s.r = 0; + sw_twsi.s.v = 1; + + printk(BIOS_SPEW, "%s(%p, 0x%llx)\n", __func__, baseaddr, sw_twsi.u); + write64(baseaddr + TWSI_SW_TWSI, sw_twsi.u); + do { + sw_twsi.u = read64(baseaddr + TWSI_SW_TWSI); + timeout--; + } while (sw_twsi.s.v != 0 && timeout > 0); + + if (sw_twsi.s.v) + printk(BIOS_ERR, "%s: timed out\n", __func__); + return sw_twsi.u; +} + +/** + * Reads the MIO_TWS(0..5)_SW_TWSI register + * + * @param baseaddr Base address of i2c registers + * @param sw_twsi value for eia and op, etc. to read + * + * @return value of the register + */ +static u64 twsi_read_sw(void *baseaddr, union twsx_sw_twsi sw_twsi) +{ + unsigned long timeout = 500000; + sw_twsi.s.r = 1; + sw_twsi.s.v = 1; + + printk(BIOS_SPEW, "%s(%p, 0x%llx)\n", __func__, baseaddr, sw_twsi.u); + write64(baseaddr + TWSI_SW_TWSI, sw_twsi.u); + + do { + sw_twsi.u = read64(baseaddr + TWSI_SW_TWSI); + timeout--; + } while (sw_twsi.s.v != 0 && timeout > 0); + + if (sw_twsi.s.v) + printk(BIOS_ERR, "%s: Error writing 0x%llx\n", __func__, + sw_twsi.u); + + printk(BIOS_SPEW, "%s: Returning 0x%llx\n", __func__, sw_twsi.u); + return sw_twsi.u; +} + +/** + * Write control register + * + * @param baseaddr Base address for i2c registers + * @param data data to write + */ +static void twsi_write_ctl(void *baseaddr, const u8 data) +{ + union twsx_sw_twsi twsi_sw; + + printk(BIOS_SPEW, "%s(%p, 0x%x)\n", __func__, baseaddr, data); + twsi_sw.u = 0; + + twsi_sw.s.op = TWSI_SW_EOP_IA; + twsi_sw.s.eop_ia = TWSI_CTL; + twsi_sw.s.data = data; + + twsi_write_sw(baseaddr, twsi_sw); +} + +/** + * Reads the TWSI Control Register + * + * @param[in] baseaddr Base address for i2c + * + * @return 8-bit TWSI control register + */ +static u32 twsi_read_ctl(void *baseaddr) +{ + union twsx_sw_twsi sw_twsi; + + sw_twsi.u = 0; + sw_twsi.s.op = TWSI_SW_EOP_IA; + sw_twsi.s.eop_ia = TWSI_CTL; + + sw_twsi.u = twsi_read_sw(baseaddr, sw_twsi); + printk(BIOS_SPEW, "%s(%p): 0x%x\n", __func__, baseaddr, sw_twsi.s.data); + return sw_twsi.s.data; +} + +/** + * Read i2c status register + * + * @param baseaddr Base address of i2c registers + * + * @return value of status register + */ +static u8 twsi_read_status(void *baseaddr) +{ + union twsx_sw_twsi twsi_sw; + + twsi_sw.u = 0; + twsi_sw.s.op = TWSI_SW_EOP_IA; + twsi_sw.s.eop_ia = TWSI_STAT; + + return twsi_read_sw(baseaddr, twsi_sw); +} + +/** + * Waits for an i2c operation to complete + * + * @param baseaddr Base address of registers + * + * @return 0 for success, 1 if timeout + */ +static int twsi_wait(void *baseaddr) +{ + unsigned long timeout = 500000; + u8 twsi_ctl; + + printk(BIOS_SPEW, "%s(%p)\n", __func__, baseaddr); + do { + twsi_ctl = twsi_read_ctl(baseaddr); + twsi_ctl &= TWSI_CTL_IFLG; + timeout--; + } while (!twsi_ctl && timeout > 0); + + printk(BIOS_SPEW, " return: %u\n", !twsi_ctl); + return !twsi_ctl; +} + +/** + * Sends an i2c stop condition + * + * @param baseaddr register base address + * + * @return 0 for success, -1 if error + */ +static int twsi_stop(void *baseaddr) +{ + u8 stat; + twsi_write_ctl(baseaddr, TWSI_CTL_STP | TWSI_CTL_ENAB); + + stat = twsi_read_status(baseaddr); + if (stat != TWSI_STAT_IDLE) { + printk(BIOS_ERR, "%s: Bad status on bus@%p\n", __func__, + baseaddr); + return -1; + } + return 0; +} + +/** + * Manually clear the I2C bus and send a stop + */ +static void twsi_unblock(void *baseaddr) +{ + int i; + union twsx_int int_reg; + + int_reg.u = 0; + for (i = 0; i < 9; i++) { + int_reg.s.scl_ovr = 0; + write64(baseaddr + TWSI_INT, int_reg.u); + udelay(5); + int_reg.s.scl_ovr = 1; + write64(baseaddr + TWSI_INT, int_reg.u); + udelay(5); + } + int_reg.s.sda_ovr = 1; + write64(baseaddr + TWSI_INT, int_reg.u); + udelay(5); + int_reg.s.scl_ovr = 0; + write64(baseaddr + TWSI_INT, int_reg.u); + udelay(5); + int_reg.u = 0; + write64(baseaddr + TWSI_INT, int_reg.u); + udelay(5); +} + +/** + * Unsticks the i2c bus + * + * @param baseaddr base address of registers + */ +static int twsi_start_unstick(void *baseaddr) +{ + twsi_stop(baseaddr); + + twsi_unblock(baseaddr); + + return 0; +} + +/** + * Sends an i2c start condition + * + * @param baseaddr base address of registers + * + * @return 0 for success, otherwise error + */ +static int twsi_start(void *baseaddr) +{ + int result; + u8 stat; + + printk(BIOS_SPEW, "%s(%p)\n", __func__, baseaddr); + twsi_write_ctl(baseaddr, TWSI_CTL_STA | TWSI_CTL_ENAB); + result = twsi_wait(baseaddr); + if (result) { + stat = twsi_read_status(baseaddr); + printk(BIOS_SPEW, "%s: result: 0x%x, status: 0x%x\n", __func__, + result, stat); + switch (stat) { + case TWSI_STAT_START: + case TWSI_STAT_RSTART: + return 0; + case TWSI_STAT_RXADDR_ACK: + default: + return twsi_start_unstick(baseaddr); + } + } + printk(BIOS_SPEW, "%s: success\n", __func__); + return 0; +} + +/** + * Writes data to the i2c bus + * + * @param baseraddr register base address + * @param slave_addr address of slave to write to + * @param buffer Pointer to buffer to write + * @param length Number of bytes in buffer to write + * + * @return 0 for success, otherwise error + */ +static int twsi_write_data(void *baseaddr, const u8 slave_addr, + const u8 *buffer, const unsigned int length) +{ + union twsx_sw_twsi twsi_sw; + unsigned int curr = 0; + int result; + + printk(BIOS_DEBUG, "%s(%p, 0x%x, %p, 0x%x)\n", __func__, baseaddr, + slave_addr, buffer, length); + result = twsi_start(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: Could not start BUS transaction\n", + __func__); + return -1; + } + + result = twsi_wait(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: wait failed\n", __func__); + return result; + } + + twsi_sw.u = 0; + twsi_sw.s.op = TWSI_SW_EOP_IA; + twsi_sw.s.eop_ia = TWSI_DATA; + twsi_sw.s.data = (u32) (slave_addr << 1) | TWSI_OP_WRITE; + + twsi_write_sw(baseaddr, twsi_sw); + twsi_write_ctl(baseaddr, TWSI_CTL_ENAB); + + printk(BIOS_DEBUG, "%s: Waiting\n", __func__); + result = twsi_wait(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: Timed out writing slave address 0x%x\n", + __func__, slave_addr); + return result; + } + result = twsi_read_status(baseaddr); + if ((result = twsi_read_status(baseaddr)) != TWSI_STAT_TXADDR_ACK) { + twsi_stop(baseaddr); + return twsi_i2c_lost_arb(result, 0); + } + + while (curr < length) { + twsi_sw.u = 0; + twsi_sw.s.op = TWSI_SW_EOP_IA; + twsi_sw.s.eop_ia = TWSI_DATA; + twsi_sw.s.data = buffer[curr++]; + + twsi_write_sw(baseaddr, twsi_sw); + twsi_write_ctl(baseaddr, TWSI_CTL_ENAB); + + result = twsi_wait(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: Timed out writing data to 0x%x\n", + __func__, slave_addr); + return result; + } + } + + printk(BIOS_DEBUG, "%s: Stopping\n", __func__); + return twsi_stop(baseaddr); +} + +/** + * Performs a read transaction on the i2c bus + * + * @param baseaddr Base address of twsi registers + * @param slave_addr i2c bus address to read from + * @param buffer buffer to read into + * @param length number of bytes to read + * + * @return 0 for success, otherwise error + */ +static int twsi_read_data(void *baseaddr, const u8 slave_addr, + u8 *buffer, const unsigned int length) +{ + union twsx_sw_twsi twsi_sw; + unsigned int curr = 0; + int result; + + printk(BIOS_DEBUG, "%s(%p, 0x%x, %p, %u)\n", __func__, baseaddr, + slave_addr, buffer, length); + result = twsi_start(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: start failed\n", __func__); + return result; + } + + result = twsi_wait(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: wait failed\n", __func__); + return result; + } + + twsi_sw.u = 0; + twsi_sw.s.op = TWSI_SW_EOP_IA; + twsi_sw.s.eop_ia = TWSI_DATA; + + twsi_sw.s.data = (u32) (slave_addr << 1) | TWSI_OP_READ; + + twsi_write_sw(baseaddr, twsi_sw); + twsi_write_ctl(baseaddr, TWSI_CTL_ENAB); + + result = twsi_wait(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: waiting for sending addr failed\n", __func__); + return result; + } + + result = twsi_read_status(baseaddr); + if (result != TWSI_STAT_RXADDR_ACK) { + twsi_stop(baseaddr); + return twsi_i2c_lost_arb(result, 0); + } + + while (curr < length) { + twsi_write_ctl(baseaddr, TWSI_CTL_ENAB | + ((curr < length - 1) ? TWSI_CTL_AAK : 0)); + + result = twsi_wait(baseaddr); + if (result) { + printk(BIOS_ERR, "%s: waiting for data failed\n", + __func__); + return result; + } + + twsi_sw.u = twsi_read_sw(baseaddr, twsi_sw); + buffer[curr++] = twsi_sw.s.data; + } + + twsi_stop(baseaddr); + + return 0; +} + +static int twsi_set_speed(void *baseaddr, const unsigned int speed) +{ + int io_clock_hz; + int n_div; + int m_div; + union twsx_sw_twsi sw_twsi; + union rst_boot rst_boot; + + printk(BIOS_DEBUG, "%s(%p, %u)\n", __func__, baseaddr, speed); + rst_boot.u = read64(RST_BOOT); + + io_clock_hz = rst_boot.s.pnr_mul * PLL_REF_CLK; + + /* Set the TWSI clock to a conservative TWSI_BUS_FREQ. Compute the + * clocks M divider based on the SCLK. + * TWSI freq = (core freq) / (20 x (M+1) x (thp+1) x 2^N) + * M = ((core freq) / (20 x (TWSI freq) x (thp+1) x 2^N)) - 1 + */ + for (n_div = 0; n_div < 8; n_div++) { + m_div = io_clock_hz / (20 * speed * (TWSI_THP + 1)); + m_div /= 1 << n_div; + m_div -= 1; + if (m_div < 16) + break; + } + if (m_div >= 16) + return -1; + + sw_twsi.u = 0; + sw_twsi.s.v = 1; + sw_twsi.s.op = 0x6; /* See EOP field */ + sw_twsi.s.r = 0; /* Select CLKCTL when R = 0 */ + sw_twsi.s.eop_ia = 3; /* R=0 selects CLKCTL, R=1 selects STAT */ + sw_twsi.s.data = ((m_div & 0xf) << 3) | ((n_div & 0x7) << 0); + + twsi_write_sw(baseaddr, sw_twsi); + return 0; +} + +int twsi_init(unsigned int bus, enum i2c_speed hz) +{ + void *baseaddr = twsi_get_baseaddr(bus); + if (!baseaddr) + return -1; + + if (twsi_set_speed(baseaddr, hz) < 0) + return -1; + + /* Enable TWSI, HLC disable, STOP, NAK */ + twsi_write_ctl(baseaddr, TWSI_CTL_ENAB); + + return 0; +} + +int platform_i2c_transfer(unsigned bus, struct i2c_msg *segments, + int seg_count) +{ + int result; + void *baseaddr = twsi_get_baseaddr(bus); + if (!baseaddr) + return -1; + + printk(BIOS_SPEW, "%s: %d messages\n", __func__, seg_count); + for (; seg_count > 0; seg_count--, segments++) { + if (segments->flags & I2C_M_RD) { + result = twsi_read_data(baseaddr, segments->slave, + segments->buf, segments->len); + } else { + result = twsi_write_data(baseaddr, segments->slave, + segments->buf, segments->len); + } + if (result) { + printk(BIOS_ERR, "%s: error transmitting data\n", + __func__); + return -1; + } + } + + return 0; +}