forked from Mirrors/opensbi
		
	lib: utils/i2c: Add minimal SiFive I2C driver
Minimum SiFive I2C driver to read/send bytes over I2C bus. This allows querying information and perform operation of onboard PMIC, as well as power-off and reset. Tested-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com> Reviewed-by: Alexandre Ghiti <alexandre.ghiti@canonical.com> Tested-by: Alexandre Ghiti <alexandre.ghiti@canonical.com> Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
This commit is contained in:
		
				
					committed by
					
						
						Anup Patel
					
				
			
			
				
	
			
			
			
						parent
						
							6ca6bcafb7
						
					
				
				
					commit
					13a1158d24
				
			@@ -18,7 +18,10 @@
 | 
			
		||||
 | 
			
		||||
#include <sbi/sbi_console.h>
 | 
			
		||||
 | 
			
		||||
extern struct fdt_i2c_adapter fdt_i2c_adapter_sifive;
 | 
			
		||||
 | 
			
		||||
static struct fdt_i2c_adapter *i2c_adapter_drivers[] = {
 | 
			
		||||
	&fdt_i2c_adapter_sifive
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int fdt_i2c_adapter_init(void *fdt, int nodeoff)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										277
									
								
								lib/utils/i2c/fdt_i2c_sifive.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										277
									
								
								lib/utils/i2c/fdt_i2c_sifive.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,277 @@
 | 
			
		||||
/*
 | 
			
		||||
 * SPDX-License-Identifier: BSD-2-Clause
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2021 YADRO
 | 
			
		||||
 *
 | 
			
		||||
 * Authors:
 | 
			
		||||
 *   Nikita Shubin <n.shubin@yadro.com>
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <sbi/riscv_io.h>
 | 
			
		||||
#include <sbi/sbi_error.h>
 | 
			
		||||
#include <sbi/sbi_timer.h>
 | 
			
		||||
#include <sbi_utils/fdt/fdt_helper.h>
 | 
			
		||||
#include <sbi_utils/i2c/fdt_i2c.h>
 | 
			
		||||
 | 
			
		||||
#define SIFIVE_I2C_ADAPTER_MAX	2
 | 
			
		||||
 | 
			
		||||
#define SIFIVE_I2C_PRELO	0x00
 | 
			
		||||
#define SIFIVE_I2C_PREHI	0x04
 | 
			
		||||
#define SIFIVE_I2C_CTR		0x08
 | 
			
		||||
#define SIFIVE_I2C_TXR		0x00c
 | 
			
		||||
#define SIFIVE_I2C_RXR		SIFIVE_I2C_TXR
 | 
			
		||||
#define SIFIVE_I2C_CR		0x010
 | 
			
		||||
#define SIFIVE_I2C_SR		SIFIVE_I2C_CR
 | 
			
		||||
 | 
			
		||||
#define SIFIVE_I2C_CTR_IEN	(1 << 6)
 | 
			
		||||
#define SIFIVE_I2C_CTR_EN	(1 << 7)
 | 
			
		||||
 | 
			
		||||
#define SIFIVE_I2C_CMD_IACK	(1 << 0)
 | 
			
		||||
#define SIFIVE_I2C_CMD_ACK	(1 << 3)
 | 
			
		||||
#define SIFIVE_I2C_CMD_WR	(1 << 4)
 | 
			
		||||
#define SIFIVE_I2C_CMD_RD	(1 << 5)
 | 
			
		||||
#define SIFIVE_I2C_CMD_STO	(1 << 6)
 | 
			
		||||
#define SIFIVE_I2C_CMD_STA	(1 << 7)
 | 
			
		||||
 | 
			
		||||
#define SIFIVE_I2C_STATUS_IF	(1 << 0)
 | 
			
		||||
#define SIFIVE_I2C_STATUS_TIP	(1 << 1)
 | 
			
		||||
#define SIFIVE_I2C_STATUS_AL	(1 << 5)
 | 
			
		||||
#define SIFIVE_I2C_STATUS_BUSY	(1 << 6)
 | 
			
		||||
#define SIFIVE_I2C_STATUS_RXACK	(1 << 7)
 | 
			
		||||
 | 
			
		||||
#define SIFIVE_I2C_WRITE_BIT	(0 << 0)
 | 
			
		||||
#define SIFIVE_I2C_READ_BIT	(1 << 0)
 | 
			
		||||
 | 
			
		||||
struct sifive_i2c_adapter {
 | 
			
		||||
	unsigned long addr;
 | 
			
		||||
	struct i2c_adapter adapter;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static unsigned int sifive_i2c_adapter_count;
 | 
			
		||||
static struct sifive_i2c_adapter
 | 
			
		||||
	sifive_i2c_adapter_array[SIFIVE_I2C_ADAPTER_MAX];
 | 
			
		||||
 | 
			
		||||
extern struct fdt_i2c_adapter fdt_i2c_adapter_sifive;
 | 
			
		||||
 | 
			
		||||
static inline void sifive_i2c_setreg(struct sifive_i2c_adapter *adap,
 | 
			
		||||
				     uint8_t reg, uint8_t value)
 | 
			
		||||
{
 | 
			
		||||
	writel(value, (volatile void *)adap->addr + reg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline uint8_t sifive_i2c_getreg(struct sifive_i2c_adapter *adap,
 | 
			
		||||
					uint8_t reg)
 | 
			
		||||
{
 | 
			
		||||
	return readl((volatile void *)adap->addr + reg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int sifive_i2c_adapter_rxack(struct sifive_i2c_adapter *adap)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t val = sifive_i2c_getreg(adap, SIFIVE_I2C_SR);
 | 
			
		||||
 | 
			
		||||
	if (val & SIFIVE_I2C_STATUS_RXACK)
 | 
			
		||||
		return SBI_EIO;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int sifive_i2c_adapter_poll(struct sifive_i2c_adapter *adap,
 | 
			
		||||
				   uint32_t mask)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int timeout = 1; // [msec]
 | 
			
		||||
	int count = 0;
 | 
			
		||||
	uint8_t val;
 | 
			
		||||
 | 
			
		||||
	sbi_timer_udelay(80); // worst case if bus speed is 100 kHz
 | 
			
		||||
 | 
			
		||||
	do {
 | 
			
		||||
		val = sifive_i2c_getreg(adap, SIFIVE_I2C_SR);
 | 
			
		||||
		if (!(val & mask))
 | 
			
		||||
			return 0;
 | 
			
		||||
 | 
			
		||||
		sbi_timer_udelay(1);
 | 
			
		||||
		count += 1;
 | 
			
		||||
		if (count == (timeout * 1000))
 | 
			
		||||
			return SBI_ETIMEDOUT;
 | 
			
		||||
	} while (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define sifive_i2c_adapter_poll_tip(adap)	\
 | 
			
		||||
	sifive_i2c_adapter_poll(adap, SIFIVE_I2C_STATUS_TIP)
 | 
			
		||||
#define sifive_i2c_adapter_poll_busy(adap)	\
 | 
			
		||||
sifive_i2c_adapter_poll(adap, SIFIVE_I2C_STATUS_BUSY)
 | 
			
		||||
 | 
			
		||||
static int sifive_i2c_adapter_start(struct sifive_i2c_adapter *adap,
 | 
			
		||||
				    uint8_t addr, uint8_t bit)
 | 
			
		||||
{
 | 
			
		||||
	uint8_t val = (addr << 1) | bit;
 | 
			
		||||
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, val);
 | 
			
		||||
	val = SIFIVE_I2C_CMD_STA | SIFIVE_I2C_CMD_WR | SIFIVE_I2C_CMD_IACK;
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_CR, val);
 | 
			
		||||
 | 
			
		||||
	return sifive_i2c_adapter_poll_tip(adap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int sifive_i2c_adapter_write(struct i2c_adapter *ia, uint8_t addr,
 | 
			
		||||
				    uint8_t reg, uint8_t *buffer, int len)
 | 
			
		||||
{
 | 
			
		||||
	struct sifive_i2c_adapter *adap =
 | 
			
		||||
		container_of(ia, struct sifive_i2c_adapter, adapter);
 | 
			
		||||
	int rc = sifive_i2c_adapter_start(adap, addr, SIFIVE_I2C_WRITE_BIT);
 | 
			
		||||
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	rc = sifive_i2c_adapter_rxack(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* set register address */
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, reg);
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_WR |
 | 
			
		||||
				SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
	rc = sifive_i2c_adapter_poll_tip(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	rc = sifive_i2c_adapter_rxack(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* set value */
 | 
			
		||||
	while (len) {
 | 
			
		||||
		sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, *buffer);
 | 
			
		||||
		sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_WR |
 | 
			
		||||
						       SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
 | 
			
		||||
		rc = sifive_i2c_adapter_poll_tip(adap);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
 | 
			
		||||
		rc = sifive_i2c_adapter_rxack(adap);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
 | 
			
		||||
		buffer++;
 | 
			
		||||
		len--;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_STO |
 | 
			
		||||
					       SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
 | 
			
		||||
	/* poll BUSY instead of ACK*/
 | 
			
		||||
	rc = sifive_i2c_adapter_poll_busy(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int sifive_i2c_adapter_read(struct i2c_adapter *ia, uint8_t addr,
 | 
			
		||||
				   uint8_t reg, uint8_t *buffer, int len)
 | 
			
		||||
{
 | 
			
		||||
	struct sifive_i2c_adapter *adap =
 | 
			
		||||
		container_of(ia, struct sifive_i2c_adapter, adapter);
 | 
			
		||||
	int rc;
 | 
			
		||||
 | 
			
		||||
	rc = sifive_i2c_adapter_start(adap, addr, SIFIVE_I2C_WRITE_BIT);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	rc = sifive_i2c_adapter_rxack(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_TXR, reg);
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_WR |
 | 
			
		||||
					       SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
 | 
			
		||||
	rc = sifive_i2c_adapter_poll_tip(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	rc = sifive_i2c_adapter_rxack(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	/* setting addr with high 0 bit */
 | 
			
		||||
	rc = sifive_i2c_adapter_start(adap, addr, SIFIVE_I2C_READ_BIT);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	rc = sifive_i2c_adapter_rxack(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	while (len) {
 | 
			
		||||
		if (len == 1)
 | 
			
		||||
			sifive_i2c_setreg(adap, SIFIVE_I2C_CR,
 | 
			
		||||
					  SIFIVE_I2C_CMD_ACK |
 | 
			
		||||
					  SIFIVE_I2C_CMD_RD |
 | 
			
		||||
					  SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
		else
 | 
			
		||||
			sifive_i2c_setreg(adap, SIFIVE_I2C_CR,
 | 
			
		||||
					  SIFIVE_I2C_CMD_RD |
 | 
			
		||||
					  SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
 | 
			
		||||
		rc = sifive_i2c_adapter_poll_tip(adap);
 | 
			
		||||
		if (rc)
 | 
			
		||||
			return rc;
 | 
			
		||||
 | 
			
		||||
		*buffer = sifive_i2c_getreg(adap, SIFIVE_I2C_RXR);
 | 
			
		||||
		buffer++;
 | 
			
		||||
		len--;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_STO |
 | 
			
		||||
					       SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
	rc = sifive_i2c_adapter_poll_busy(adap);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	sifive_i2c_setreg(adap, SIFIVE_I2C_CR, SIFIVE_I2C_CMD_IACK);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int sifive_i2c_init(void *fdt, int nodeoff,
 | 
			
		||||
			    const struct fdt_match *match)
 | 
			
		||||
{
 | 
			
		||||
	int rc;
 | 
			
		||||
	struct sifive_i2c_adapter *adapter;
 | 
			
		||||
	uint64_t addr;
 | 
			
		||||
 | 
			
		||||
	if (sifive_i2c_adapter_count >= SIFIVE_I2C_ADAPTER_MAX)
 | 
			
		||||
		return SBI_ENOSPC;
 | 
			
		||||
 | 
			
		||||
	adapter = &sifive_i2c_adapter_array[sifive_i2c_adapter_count];
 | 
			
		||||
 | 
			
		||||
	rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &addr, NULL);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	adapter->addr = addr;
 | 
			
		||||
	adapter->adapter.driver = &fdt_i2c_adapter_sifive;
 | 
			
		||||
	adapter->adapter.id = nodeoff;
 | 
			
		||||
	adapter->adapter.write = sifive_i2c_adapter_write;
 | 
			
		||||
	adapter->adapter.read = sifive_i2c_adapter_read;
 | 
			
		||||
	rc = i2c_adapter_add(&adapter->adapter);
 | 
			
		||||
	if (rc)
 | 
			
		||||
		return rc;
 | 
			
		||||
 | 
			
		||||
	sifive_i2c_adapter_count++;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct fdt_match sifive_i2c_match[] = {
 | 
			
		||||
	{ .compatible = "sifive,i2c0" },
 | 
			
		||||
	{ },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct fdt_i2c_adapter fdt_i2c_adapter_sifive = {
 | 
			
		||||
	.match_table = sifive_i2c_match,
 | 
			
		||||
	.init = sifive_i2c_init,
 | 
			
		||||
};
 | 
			
		||||
@@ -9,3 +9,4 @@
 | 
			
		||||
 | 
			
		||||
libsbiutils-objs-y += i2c/i2c.o
 | 
			
		||||
libsbiutils-objs-y += i2c/fdt_i2c.o
 | 
			
		||||
libsbiutils-objs-y += i2c/fdt_i2c_sifive.o
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user