840 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			840 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
| // SPDX-License-Identifier:    GPL-2.0
 | |
| /*
 | |
|  * Copyright (C) 2018 Marvell International Ltd.
 | |
|  */
 | |
| 
 | |
| #include <clk.h>
 | |
| #include <dm.h>
 | |
| #include <i2c.h>
 | |
| #include <time.h>
 | |
| #include <asm/io.h>
 | |
| #include <linux/bitfield.h>
 | |
| #include <linux/compat.h>
 | |
| #include <linux/delay.h>
 | |
| 
 | |
| #define TWSI_SW_TWSI		0x00
 | |
| #define TWSI_TWSI_SW		0x08
 | |
| #define TWSI_INT		0x10
 | |
| #define TWSI_SW_TWSI_EXT	0x18
 | |
| 
 | |
| #define TWSI_SW_DATA_MASK	GENMASK_ULL(31, 0)
 | |
| #define TWSI_SW_EOP_IA_MASK	GENMASK_ULL(34, 32)
 | |
| #define TWSI_SW_IA_MASK		GENMASK_ULL(39, 35)
 | |
| #define TWSI_SW_ADDR_MASK	GENMASK_ULL(49, 40)
 | |
| #define TWSI_SW_SCR_MASK	GENMASK_ULL(51, 50)
 | |
| #define TWSI_SW_SIZE_MASK	GENMASK_ULL(54, 52)
 | |
| #define TWSI_SW_SOVR		BIT_ULL(55)
 | |
| #define TWSI_SW_R		BIT_ULL(56)
 | |
| #define TWSI_SW_OP_MASK		GENMASK_ULL(60, 57)
 | |
| #define TWSI_SW_EIA		GENMASK_ULL(61)
 | |
| #define TWSI_SW_SLONLY		BIT_ULL(62)
 | |
| #define TWSI_SW_V		BIT_ULL(63)
 | |
| 
 | |
| #define TWSI_INT_SDA_OVR	BIT_ULL(8)
 | |
| #define TWSI_INT_SCL_OVR	BIT_ULL(9)
 | |
| #define TWSI_INT_SDA		BIT_ULL(10)
 | |
| #define TWSI_INT_SCL		BIT_ULL(11)
 | |
| 
 | |
| 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	= BIT(2),
 | |
| 	TWSI_CTL_IFLG	= BIT(3),
 | |
| 	TWSI_CTL_STP	= BIT(4),
 | |
| 	TWSI_CTL_STA	= BIT(5),
 | |
| 	TWSI_CTL_ENAB	= BIT(6),
 | |
| 	TWSI_CTL_CE	= BIT(7),
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Internal errors. When debugging is enabled, the driver will report the
 | |
|  * error number and the user / developer can check the table below for the
 | |
|  * detailed error description.
 | |
|  */
 | |
| 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
 | |
| };
 | |
| 
 | |
| #define CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR	0x77
 | |
| 
 | |
| enum {
 | |
| 	PROBE_PCI = 0,		/* PCI based probing */
 | |
| 	PROBE_DT,		/* DT based probing */
 | |
| };
 | |
| 
 | |
| enum {
 | |
| 	CLK_METHOD_OCTEON = 0,
 | |
| 	CLK_METHOD_OCTEONTX2,
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * struct octeon_i2c_data - SoC specific data of this driver
 | |
|  *
 | |
|  * @probe:	Probing of this SoC (DT vs PCI)
 | |
|  * @reg_offs:	Register offset
 | |
|  * @thp:	THP define for divider calculation
 | |
|  * @clk_method:	Clock calculation method
 | |
|  */
 | |
| struct octeon_i2c_data {
 | |
| 	int probe;
 | |
| 	u32 reg_offs;
 | |
| 	int thp;
 | |
| 	int clk_method;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * struct octeon_twsi - Private data of this driver
 | |
|  *
 | |
|  * @base:	Base address of i2c registers
 | |
|  * @data:	Pointer to SoC specific data struct
 | |
|  */
 | |
| struct octeon_twsi {
 | |
| 	void __iomem *base;
 | |
| 	const struct octeon_i2c_data *data;
 | |
| 	struct clk clk;
 | |
| };
 | |
| 
 | |
| static void twsi_unblock(void *base);
 | |
| static int twsi_stop(void *base);
 | |
| 
 | |
| /**
 | |
|  * Returns true if we lost arbitration
 | |
|  *
 | |
|  * @code	status code
 | |
|  * @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) {
 | |
| 	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:
 | |
| 		/* Arbitration lost */
 | |
| 		return -EAGAIN;
 | |
| 
 | |
| 	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:
 | |
| 		/* Being addressed as slave, should back off and listen */
 | |
| 		return -EIO;
 | |
| 
 | |
| 	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:
 | |
| 		/* Core busy as slave */
 | |
| 		return  -EIO;
 | |
| 
 | |
| 	case TWSI_STAT_RXDATA_ACK_SENT:
 | |
| 		/* Ack allowed on pre-terminal bytes only */
 | |
| 		if (!final_read)
 | |
| 			return 0;
 | |
| 		return -EAGAIN;
 | |
| 
 | |
| 	case TWSI_STAT_RXDATA_NAK_SENT:
 | |
| 		/* NAK allowed on terminal byte only */
 | |
| 		if (!final_read)
 | |
| 			return 0;
 | |
| 		return -EAGAIN;
 | |
| 
 | |
| 	case TWSI_STAT_TXDATA_NAK:
 | |
| 	case TWSI_STAT_TXADDR_NAK:
 | |
| 	case TWSI_STAT_RXADDR_NAK:
 | |
| 	case TWSI_STAT_TXADDR2DATA_NAK:
 | |
| 		return -EAGAIN;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Writes to the MIO_TWS(0..5)_SW_TWSI register
 | |
|  *
 | |
|  * @base	Base address of i2c registers
 | |
|  * @val		value to write
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static u64 twsi_write_sw(void __iomem *base, u64 val)
 | |
| {
 | |
| 	unsigned long start = get_timer(0);
 | |
| 
 | |
| 	val &= ~TWSI_SW_R;
 | |
| 	val |= TWSI_SW_V;
 | |
| 
 | |
| 	debug("%s(%p, 0x%llx)\n", __func__, base, val);
 | |
| 	writeq(val, base + TWSI_SW_TWSI);
 | |
| 	do {
 | |
| 		val = readq(base + TWSI_SW_TWSI);
 | |
| 	} while ((val & TWSI_SW_V) && (get_timer(start) < 50));
 | |
| 
 | |
| 	if (val & TWSI_SW_V)
 | |
| 		debug("%s: timed out\n", __func__);
 | |
| 	return val;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Reads the MIO_TWS(0..5)_SW_TWSI register
 | |
|  *
 | |
|  * @base	Base address of i2c registers
 | |
|  * @val		value for eia and op, etc. to read
 | |
|  * @return	value of the register
 | |
|  */
 | |
| static u64 twsi_read_sw(void __iomem *base, u64 val)
 | |
| {
 | |
| 	unsigned long start = get_timer(0);
 | |
| 
 | |
| 	val |= TWSI_SW_R | TWSI_SW_V;
 | |
| 
 | |
| 	debug("%s(%p, 0x%llx)\n", __func__, base, val);
 | |
| 	writeq(val, base + TWSI_SW_TWSI);
 | |
| 
 | |
| 	do {
 | |
| 		val = readq(base + TWSI_SW_TWSI);
 | |
| 	} while ((val & TWSI_SW_V) && (get_timer(start) < 50));
 | |
| 
 | |
| 	if (val & TWSI_SW_V)
 | |
| 		debug("%s: Error writing 0x%llx\n", __func__, val);
 | |
| 
 | |
| 	debug("%s: Returning 0x%llx\n", __func__, val);
 | |
| 	return val;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Write control register
 | |
|  *
 | |
|  * @base	Base address for i2c registers
 | |
|  * @data	data to write
 | |
|  */
 | |
| static void twsi_write_ctl(void __iomem *base, u8 data)
 | |
| {
 | |
| 	u64 val;
 | |
| 
 | |
| 	debug("%s(%p, 0x%x)\n", __func__, base, data);
 | |
| 	val = data | FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
 | |
| 	twsi_write_sw(base, val);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Reads the TWSI Control Register
 | |
|  *
 | |
|  * @base	Base address for i2c
 | |
|  * @return	8-bit TWSI control register
 | |
|  */
 | |
| static u8 twsi_read_ctl(void __iomem *base)
 | |
| {
 | |
| 	u64 val;
 | |
| 
 | |
| 	val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CTL) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
 | |
| 	val = twsi_read_sw(base, val);
 | |
| 
 | |
| 	debug("%s(%p): 0x%x\n", __func__, base, (u8)val);
 | |
| 	return (u8)val;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Read i2c status register
 | |
|  *
 | |
|  * @base	Base address of i2c registers
 | |
|  * @return	value of status register
 | |
|  */
 | |
| static u8 twsi_read_status(void __iomem *base)
 | |
| {
 | |
| 	u64 val;
 | |
| 
 | |
| 	val = FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_STAT) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
 | |
| 
 | |
| 	return twsi_read_sw(base, val);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Waits for an i2c operation to complete
 | |
|  *
 | |
|  * @param	base	Base address of registers
 | |
|  * @return	0 for success, 1 if timeout
 | |
|  */
 | |
| static int twsi_wait(void __iomem *base)
 | |
| {
 | |
| 	unsigned long start = get_timer(0);
 | |
| 	u8 twsi_ctl;
 | |
| 
 | |
| 	debug("%s(%p)\n", __func__, base);
 | |
| 	do {
 | |
| 		twsi_ctl = twsi_read_ctl(base);
 | |
| 		twsi_ctl &= TWSI_CTL_IFLG;
 | |
| 	} while (!twsi_ctl && get_timer(start) < 50);
 | |
| 
 | |
| 	debug("  return: %u\n", !twsi_ctl);
 | |
| 	return !twsi_ctl;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Unsticks the i2c bus
 | |
|  *
 | |
|  * @base	base address of registers
 | |
|  */
 | |
| static int twsi_start_unstick(void __iomem *base)
 | |
| {
 | |
| 	twsi_stop(base);
 | |
| 	twsi_unblock(base);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sends an i2c start condition
 | |
|  *
 | |
|  * @base	base address of registers
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static int twsi_start(void __iomem *base)
 | |
| {
 | |
| 	int ret;
 | |
| 	u8 stat;
 | |
| 
 | |
| 	debug("%s(%p)\n", __func__, base);
 | |
| 	twsi_write_ctl(base, TWSI_CTL_STA | TWSI_CTL_ENAB);
 | |
| 	ret = twsi_wait(base);
 | |
| 	if (ret) {
 | |
| 		stat = twsi_read_status(base);
 | |
| 		debug("%s: ret: 0x%x, status: 0x%x\n", __func__, ret, stat);
 | |
| 		switch (stat) {
 | |
| 		case TWSI_STAT_START:
 | |
| 		case TWSI_STAT_RSTART:
 | |
| 			return 0;
 | |
| 		case TWSI_STAT_RXADDR_ACK:
 | |
| 		default:
 | |
| 			return twsi_start_unstick(base);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	debug("%s: success\n", __func__);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sends an i2c stop condition
 | |
|  *
 | |
|  * @base	register base address
 | |
|  * @return	0 for success, -1 if error
 | |
|  */
 | |
| static int twsi_stop(void __iomem *base)
 | |
| {
 | |
| 	u8 stat;
 | |
| 
 | |
| 	twsi_write_ctl(base, TWSI_CTL_STP | TWSI_CTL_ENAB);
 | |
| 
 | |
| 	stat = twsi_read_status(base);
 | |
| 	if (stat != TWSI_STAT_IDLE) {
 | |
| 		debug("%s: Bad status on bus@%p\n", __func__, base);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Writes data to the i2c bus
 | |
|  *
 | |
|  * @base	register base address
 | |
|  * @slave_addr	address of slave to write to
 | |
|  * @buffer	Pointer to buffer to write
 | |
|  * @length	Number of bytes in buffer to write
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static int twsi_write_data(void __iomem *base, u8  slave_addr,
 | |
| 			   u8 *buffer, unsigned int length)
 | |
| {
 | |
| 	unsigned int curr = 0;
 | |
| 	u64 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	debug("%s(%p, 0x%x, %p, 0x%x)\n", __func__, base, slave_addr,
 | |
| 	      buffer, length);
 | |
| 	ret = twsi_start(base);
 | |
| 	if (ret) {
 | |
| 		debug("%s: Could not start BUS transaction\n", __func__);
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	ret = twsi_wait(base);
 | |
| 	if (ret) {
 | |
| 		debug("%s: wait failed\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	val = (u32)(slave_addr << 1) | TWSI_OP_WRITE |
 | |
| 		FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
 | |
| 	twsi_write_sw(base, val);
 | |
| 	twsi_write_ctl(base, TWSI_CTL_ENAB);
 | |
| 
 | |
| 	debug("%s: Waiting\n", __func__);
 | |
| 	ret = twsi_wait(base);
 | |
| 	if (ret) {
 | |
| 		debug("%s: Timed out writing slave address 0x%x to target\n",
 | |
| 		      __func__, slave_addr);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = twsi_read_status(base);
 | |
| 	debug("%s: status: 0x%x\n", __func__, ret);
 | |
| 	if (ret != TWSI_STAT_TXADDR_ACK) {
 | |
| 		debug("%s: status: 0x%x\n", __func__, ret);
 | |
| 		twsi_stop(base);
 | |
| 		return twsi_i2c_lost_arb(ret, 0);
 | |
| 	}
 | |
| 
 | |
| 	while (curr < length) {
 | |
| 		val = buffer[curr++] |
 | |
| 			FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) |
 | |
| 			FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
 | |
| 		twsi_write_sw(base, val);
 | |
| 		twsi_write_ctl(base, TWSI_CTL_ENAB);
 | |
| 
 | |
| 		debug("%s: Writing 0x%llx\n", __func__, val);
 | |
| 
 | |
| 		ret = twsi_wait(base);
 | |
| 		if (ret) {
 | |
| 			debug("%s: Timed out writing data to 0x%x\n",
 | |
| 			      __func__, slave_addr);
 | |
| 			return ret;
 | |
| 		}
 | |
| 		ret = twsi_read_status(base);
 | |
| 		debug("%s: status: 0x%x\n", __func__, ret);
 | |
| 	}
 | |
| 
 | |
| 	debug("%s: Stopping\n", __func__);
 | |
| 	return twsi_stop(base);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Manually clear the I2C bus and send a stop
 | |
|  *
 | |
|  * @base	register base address
 | |
|  */
 | |
| static void twsi_unblock(void __iomem *base)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < 9; i++) {
 | |
| 		writeq(0, base + TWSI_INT);
 | |
| 		udelay(5);
 | |
| 		writeq(TWSI_INT_SCL_OVR, base + TWSI_INT);
 | |
| 		udelay(5);
 | |
| 	}
 | |
| 	writeq(TWSI_INT_SCL_OVR | TWSI_INT_SDA_OVR, base + TWSI_INT);
 | |
| 	udelay(5);
 | |
| 	writeq(TWSI_INT_SDA_OVR, base + TWSI_INT);
 | |
| 	udelay(5);
 | |
| 	writeq(0, base + TWSI_INT);
 | |
| 	udelay(5);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Performs a read transaction on the i2c bus
 | |
|  *
 | |
|  * @base	Base address of twsi registers
 | |
|  * @slave_addr	i2c bus address to read from
 | |
|  * @buffer	buffer to read into
 | |
|  * @length	number of bytes to read
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static int twsi_read_data(void __iomem *base, u8 slave_addr,
 | |
| 			  u8 *buffer, unsigned int length)
 | |
| {
 | |
| 	unsigned int curr = 0;
 | |
| 	u64 val;
 | |
| 	int ret;
 | |
| 
 | |
| 	debug("%s(%p, 0x%x, %p, %u)\n", __func__, base, slave_addr,
 | |
| 	      buffer, length);
 | |
| 	ret = twsi_start(base);
 | |
| 	if (ret) {
 | |
| 		debug("%s: start failed\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = twsi_wait(base);
 | |
| 	if (ret) {
 | |
| 		debug("%s: wait failed\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	val = (u32)(slave_addr << 1) | TWSI_OP_READ |
 | |
| 		FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_DATA) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA);
 | |
| 	twsi_write_sw(base, val);
 | |
| 	twsi_write_ctl(base, TWSI_CTL_ENAB);
 | |
| 
 | |
| 	ret = twsi_wait(base);
 | |
| 	if (ret) {
 | |
| 		debug("%s: waiting for sending addr failed\n", __func__);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = twsi_read_status(base);
 | |
| 	debug("%s: status: 0x%x\n", __func__, ret);
 | |
| 	if (ret != TWSI_STAT_RXADDR_ACK) {
 | |
| 		debug("%s: status: 0x%x\n", __func__, ret);
 | |
| 		twsi_stop(base);
 | |
| 		return twsi_i2c_lost_arb(ret, 0);
 | |
| 	}
 | |
| 
 | |
| 	while (curr < length) {
 | |
| 		twsi_write_ctl(base, TWSI_CTL_ENAB |
 | |
| 			       ((curr < length - 1) ? TWSI_CTL_AAK : 0));
 | |
| 
 | |
| 		ret = twsi_wait(base);
 | |
| 		if (ret) {
 | |
| 			debug("%s: waiting for data failed\n", __func__);
 | |
| 			return ret;
 | |
| 		}
 | |
| 
 | |
| 		val = twsi_read_sw(base, val);
 | |
| 		buffer[curr++] = (u8)val;
 | |
| 	}
 | |
| 
 | |
| 	twsi_stop(base);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Calculate the divisor values
 | |
|  *
 | |
|  * @speed	Speed to set
 | |
|  * @m_div	Pointer to M divisor
 | |
|  * @n_div	Pointer to N divisor
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static void twsi_calc_div(struct udevice *bus, ulong sclk, unsigned int speed,
 | |
| 			  int *m_div, int *n_div)
 | |
| {
 | |
| 	struct octeon_twsi *twsi = dev_get_priv(bus);
 | |
| 	int thp = twsi->data->thp;
 | |
| 	int tclk, fsamp;
 | |
| 	int ndiv, mdiv;
 | |
| 
 | |
| 	if (twsi->data->clk_method == CLK_METHOD_OCTEON) {
 | |
| 		tclk = sclk / (2 * (thp + 1));
 | |
| 	} else {
 | |
| 		/* Refclk src in mode register defaults to 100MHz clock */
 | |
| 		sclk = 100000000; /* 100 Mhz */
 | |
| 		tclk = sclk / (thp + 2);
 | |
| 	}
 | |
| 	debug("%s( io_clock %lu tclk %u)\n", __func__, sclk, tclk);
 | |
| 
 | |
| 	/*
 | |
| 	 * Compute the clocks M divider:
 | |
| 	 *
 | |
| 	 * TWSI freq = (core freq) / (10 x (M+1) x 2 * (thp+1) x 2^N)
 | |
| 	 * M = ((core freq) / (10 x (TWSI freq) x 2 * (thp+1) x 2^N)) - 1
 | |
| 	 *
 | |
| 	 * For OcteonTX2 -
 | |
| 	 * TWSI freq = (core freq) / (10 x (M+1) x (thp+2) x 2^N)
 | |
| 	 * M = ((core freq) / (10 x (TWSI freq) x (thp+2) x 2^N)) - 1
 | |
| 	 */
 | |
| 	for (ndiv = 0; ndiv < 8; ndiv++) {
 | |
| 		fsamp = tclk / (1 << ndiv);
 | |
| 		mdiv = fsamp / speed / 10;
 | |
| 		mdiv -= 1;
 | |
| 		if (mdiv < 16)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	*m_div = mdiv;
 | |
| 	*n_div = ndiv;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Init I2C controller
 | |
|  *
 | |
|  * @base	Base address of twsi registers
 | |
|  * @slave_addr	I2C slave address to configure this controller to
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static int twsi_init(void __iomem *base, int slaveaddr)
 | |
| {
 | |
| 	u64 val;
 | |
| 
 | |
| 	debug("%s (%p, 0x%x)\n", __func__, base, slaveaddr);
 | |
| 
 | |
| 	val = slaveaddr << 1 |
 | |
| 		FIELD_PREP(TWSI_SW_EOP_IA_MASK, 0) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) |
 | |
| 		TWSI_SW_V;
 | |
| 	twsi_write_sw(base, val);
 | |
| 
 | |
| 	/* Set slave address */
 | |
| 	val = slaveaddr |
 | |
| 		FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_EOP_SLAVE_ADDR) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) |
 | |
| 		TWSI_SW_V;
 | |
| 	twsi_write_sw(base, val);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Transfers data over the i2c bus
 | |
|  *
 | |
|  * @bus		i2c bus to transfer data over
 | |
|  * @msg		Array of i2c messages
 | |
|  * @nmsgs	Number of messages to send/receive
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static int octeon_i2c_xfer(struct udevice *bus, struct i2c_msg *msg,
 | |
| 			   int nmsgs)
 | |
| {
 | |
| 	struct octeon_twsi *twsi = dev_get_priv(bus);
 | |
| 	int ret;
 | |
| 	int i;
 | |
| 
 | |
| 	debug("%s: %d messages\n", __func__, nmsgs);
 | |
| 	for (i = 0; i < nmsgs; i++, msg++) {
 | |
| 		debug("%s: chip=0x%x, len=0x%x\n", __func__, msg->addr,
 | |
| 		      msg->len);
 | |
| 
 | |
| 		if (msg->flags & I2C_M_RD) {
 | |
| 			debug("%s: Reading data\n", __func__);
 | |
| 			ret = twsi_read_data(twsi->base, msg->addr,
 | |
| 					     msg->buf, msg->len);
 | |
| 		} else {
 | |
| 			debug("%s: Writing data\n", __func__);
 | |
| 			ret = twsi_write_data(twsi->base, msg->addr,
 | |
| 					      msg->buf, msg->len);
 | |
| 		}
 | |
| 		if (ret) {
 | |
| 			debug("%s: error sending\n", __func__);
 | |
| 			return -EREMOTEIO;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Set I2C bus speed
 | |
|  *
 | |
|  * @bus		i2c bus to transfer data over
 | |
|  * @speed	Speed in Hz to set
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static int octeon_i2c_set_bus_speed(struct udevice *bus, unsigned int speed)
 | |
| {
 | |
| 	struct octeon_twsi *twsi = dev_get_priv(bus);
 | |
| 	int m_div, n_div;
 | |
| 	ulong clk_rate;
 | |
| 	u64 val;
 | |
| 
 | |
| 	debug("%s(%p, %u)\n", __func__, bus, speed);
 | |
| 
 | |
| 	clk_rate = clk_get_rate(&twsi->clk);
 | |
| 	if (IS_ERR_VALUE(clk_rate))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	twsi_calc_div(bus, clk_rate, speed, &m_div, &n_div);
 | |
| 	if (m_div >= 16)
 | |
| 		return -1;
 | |
| 
 | |
| 	val = (u32)(((m_div & 0xf) << 3) | ((n_div & 0x7) << 0)) |
 | |
| 		FIELD_PREP(TWSI_SW_EOP_IA_MASK, TWSI_CLKCTL) |
 | |
| 		FIELD_PREP(TWSI_SW_OP_MASK, TWSI_SW_EOP_IA) |
 | |
| 		TWSI_SW_V;
 | |
| 	/* Only init non-slave ports */
 | |
| 	writeq(val, twsi->base + TWSI_SW_TWSI);
 | |
| 
 | |
| 	debug("%s: Wrote 0x%llx to sw_twsi\n", __func__, val);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct octeon_i2c_data i2c_octeon_data = {
 | |
| 	.probe = PROBE_DT,
 | |
| 	.reg_offs = 0x0000,
 | |
| 	.thp = 3,
 | |
| 	.clk_method = CLK_METHOD_OCTEON,
 | |
| };
 | |
| 
 | |
| static const struct octeon_i2c_data i2c_octeontx_data = {
 | |
| 	.probe = PROBE_PCI,
 | |
| 	.reg_offs = 0x1000,
 | |
| 	.thp = 24,
 | |
| 	.clk_method = CLK_METHOD_OCTEON,
 | |
| };
 | |
| 
 | |
| static const struct octeon_i2c_data i2c_octeontx2_data = {
 | |
| 	.probe = PROBE_PCI,
 | |
| 	.reg_offs = 0x1000,
 | |
| 	.thp = 3,
 | |
| 	.clk_method = CLK_METHOD_OCTEONTX2,
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Driver probe function
 | |
|  *
 | |
|  * @dev		I2C device to probe
 | |
|  * @return	0 for success, otherwise error
 | |
|  */
 | |
| static int octeon_i2c_probe(struct udevice *dev)
 | |
| {
 | |
| 	struct octeon_twsi *twsi = dev_get_priv(dev);
 | |
| 	u32 i2c_slave_addr;
 | |
| 	int ret;
 | |
| 
 | |
| 	/* Octeon TX2 needs a different data struct */
 | |
| 	if (device_is_compatible(dev, "cavium,thunderx-i2c"))
 | |
| 		dev->driver_data = (long)&i2c_octeontx2_data;
 | |
| 
 | |
| 	twsi->data = (const struct octeon_i2c_data *)dev_get_driver_data(dev);
 | |
| 
 | |
| 	if (twsi->data->probe == PROBE_PCI) {
 | |
| 		pci_dev_t bdf = dm_pci_get_bdf(dev);
 | |
| 
 | |
| 		debug("TWSI PCI device: %x\n", bdf);
 | |
| 
 | |
| 		twsi->base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0,
 | |
| 					    PCI_REGION_MEM);
 | |
| 	} else {
 | |
| 		twsi->base = dev_remap_addr(dev);
 | |
| 	}
 | |
| 	twsi->base += twsi->data->reg_offs;
 | |
| 
 | |
| 	i2c_slave_addr = dev_read_u32_default(dev, "i2c-sda-hold-time-ns",
 | |
| 					      CONFIG_SYS_I2C_OCTEON_SLAVE_ADDR);
 | |
| 
 | |
| 	ret = clk_get_by_index(dev, 0, &twsi->clk);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = clk_enable(&twsi->clk);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	debug("TWSI bus %d at %p\n", dev_seq(dev), twsi->base);
 | |
| 
 | |
| 	/* Start with standard speed, real speed set via DT or cmd */
 | |
| 	return twsi_init(twsi->base, i2c_slave_addr);
 | |
| }
 | |
| 
 | |
| static const struct dm_i2c_ops octeon_i2c_ops = {
 | |
| 	.xfer		= octeon_i2c_xfer,
 | |
| 	.set_bus_speed	= octeon_i2c_set_bus_speed,
 | |
| };
 | |
| 
 | |
| static const struct udevice_id octeon_i2c_ids[] = {
 | |
| 	{ .compatible = "cavium,octeon-7890-twsi",
 | |
| 	  .data = (ulong)&i2c_octeon_data },
 | |
| 	{ .compatible = "cavium,thunder-8890-twsi",
 | |
| 	  .data = (ulong)&i2c_octeontx_data },
 | |
| 	{ }
 | |
| };
 | |
| 
 | |
| U_BOOT_DRIVER(octeon_pci_twsi) = {
 | |
| 	.name	= "i2c_octeon",
 | |
| 	.id	= UCLASS_I2C,
 | |
| 	.of_match = octeon_i2c_ids,
 | |
| 	.probe	= octeon_i2c_probe,
 | |
| 	.priv_auto	= sizeof(struct octeon_twsi),
 | |
| 	.ops	= &octeon_i2c_ops,
 | |
| };
 |