[U-Boot] [PATCH 03/22] ARM sunxi: I2C driver

Luka Perkov luka at openwrt.org
Sun Nov 25 15:41:10 CET 2012


Hi Henrik,

On Sun, Nov 25, 2012 at 12:38:46PM +0100, Henrik Nordström wrote:
> A basic basic driver for the I2C controller found in Allwinner
> sunXi (A10 & A13) SoCs.
> 
> Signed-off-by: Henrik Nordstrom <henrik at henriknordstrom.net>
> Signed-off-by: Stefan Roese <sr at denx.de>
> ---
>  arch/arm/cpu/armv7/sunxi/clock.c      |   15 ++
>  arch/arm/include/asm/arch-sunxi/i2c.h |  185 ++++++++++++++++++++++
>  drivers/i2c/Makefile                  |    1 +
>  drivers/i2c/sunxi_i2c.c               |  278 +++++++++++++++++++++++++++++++++
>  include/configs/sunxi-common.h        |    8 +
>  5 files changed, 487 insertions(+), 0 deletions(-)
>  create mode 100644 arch/arm/include/asm/arch-sunxi/i2c.h
>  create mode 100644 drivers/i2c/sunxi_i2c.c
> 
> diff --git a/arch/arm/cpu/armv7/sunxi/clock.c b/arch/arm/cpu/armv7/sunxi/clock.c
> index 424acfc..b9bbb7d 100644
> --- a/arch/arm/cpu/armv7/sunxi/clock.c
> +++ b/arch/arm/cpu/armv7/sunxi/clock.c
> @@ -42,6 +42,7 @@ int clock_init(void)
>  	return 0;
>  }
>  
> +

No need to add extra newline here.

>  /* Return PLL5 frequency in Hz
>   * Note: Assumes PLL5 reference is 24MHz clock
>   */
> @@ -55,3 +56,17 @@ unsigned int clock_get_pll5(void)
>  	int p = 1 << ((rval >> 16) & 3);
>  	return 24000000 * n * k / p;
>  }
> +
> +int clock_twi_onoff(int port, int state)
> +{
> +	struct sunxi_ccm_reg *const ccm =
> +		(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
> +
> +	if (port > 2)
> +		return -1;
> +
> +	/* set the apb1 clock gate for twi */
> +	sr32(&ccm->apb1_gate, 0 + port, 1, state);
> +
> +	return 0;
> +}
> diff --git a/arch/arm/include/asm/arch-sunxi/i2c.h b/arch/arm/include/asm/arch-sunxi/i2c.h
> new file mode 100644
> index 0000000..9a6e168
> --- /dev/null
> +++ b/arch/arm/include/asm/arch-sunxi/i2c.h
> @@ -0,0 +1,185 @@
> +/*
> + * (C) Copyright 2012 Henrik Nordstrom <henrik at henriknordstrom.net>
> + *
> + * Based on sun4i linux kernle i2c.h
> + * (C) Copyright 2007-2012
> + * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
> + * Tom Cubie <tanglaing at allwinnertech.com>
> + * Victor Wei <weiziheng at allwinnertech.com>
> + *
> + * See file CREDITS for list of people who contributed to this
> + * project.
> + *
> + * 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.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
> + * MA 02111-1307 USA
> + */

Add one newline here like in all other .h files.

> +#ifndef _SUNXI_I2C_H_
> +#define _SUNXI_I2C_H_
> +
> +struct i2c {
> +	u32 saddr;	/*  31:8bit res,7-1bit for slave addr,0 bit for GCE */
> +	u32 xsaddr;	/*  31:8bit res,7-0bit for second addr in 10bit addr */
> +	u32 data;	/*  31:8bit res, 7-0bit send or receive data byte */
> +	u32 ctl;	/*  INT_EN,BUS_EN,M_STA,INT_FLAG,A_ACK */
> +	u32 status;	/*  28 interrupt types + 0xF8 normal type = 29  */
> +	u32 clkr;	/*  31:7bit res,6-3bit,CLK_M,2-0bit CLK_N */
> +	u32 reset;	/*  31:1bit res;0bit,write 1 to clear 0. */
> +	u32 efr;	/*  31:2bit res,1:0 bit data byte follow read comand */
> +	u32 lctl;	/*  31:6bits res 5:0bit for sda&scl control */
> +};
> +
> +/* TWI address register */
> +#define TWI_GCE_EN	(0x1 << 0)	/* gen call addr enable slave mode */
> +#define TWI_ADDR_MASK	(0x7f << 1)	/* 7:1bits */
> +#define TWI_XADDR_MASK	0xff		/* 7:0bits for extend slave address */
> +
> +#define TWI_DATA_MASK	0xff		/* 7:0bits for send or received */
> +
> +/* TWI Control Register Bit Fields */
> +/* 1:0 bits reserved */
> +/* set 1 to send A_ACK,then low level on SDA */
> +#define TWI_CTL_ACK	(0x1 << 2)
> +/* INT_FLAG,interrupt status flag: set '1' when interrupt coming */
> +#define TWI_CTL_INTFLG	(0x1 << 3)
> +#define TWI_CTL_STP	(0x1 << 4)	/* M_STP,Automatic clear 0 */
> +#define TWI_CTL_STA	(0x1 << 5)	/* M_STA,atutomatic clear 0 */
> +#define TWI_CTL_BUSEN	(0x1 << 6)	/* BUS_EN, mastr mode should be set 1 */
> +#define TWI_CTL_INTEN	(0x1 << 7)	/* INT_EN */
> +/* 31:8 bit reserved */
> +
> +/*
> + * TWI Clock Register Bit Fields & Masks,default value:0x0000_0000
> + * Fin is APB CLOCK INPUT;
> + * Fsample = F0 = Fin/2^CLK_N;
> + *           F1 = F0/(CLK_M+1);
> + *
> + * Foscl = F1/10 = Fin/(2^CLK_N * (CLK_M+1)*10);
> + * Foscl is clock SCL;standard mode:100KHz or fast mode:400KHz
> + */
> +
> +#define TWI_CLK_DIV_M		(0xF << 3)	/* 6:3bit  */
> +#define TWI_CLK_DIV_N		(0x7 << 0)	/* 2:0bit */
> +#define TWI_CLK_DIV(N, M)	((((N) & 0xF) << 3) | (((M) & 0x7) << 0))
> +
> +/* TWI Soft Reset Register Bit Fields & Masks  */
> +/* write 1 to clear 0, when complete soft reset clear 0 */
> +#define TWI_SRST_SRST		(0x1 << 0)
> +
> +/* TWI Enhance Feature Register Bit Fields & Masks  */
> +/* default -- 0x0 */
> +/* 00:no,01: 1byte, 10:2 bytes, 11: 3bytes */
> +#define TWI_EFR_MASK		(0x3 << 0)
> +#define TWI_EFR_WARC_0		(0x0 << 0)
> +#define TWI_EFR_WARC_1		(0x1 << 0)
> +#define TWI_EFR_WARC_2		(0x2 << 0)
> +#define TWI_EFR_WARC_3		(0x3 << 0)
> +
> +/* twi line control register -default value: 0x0000_003a */
> +/* SDA line state control enable ,1:enable;0:disable */
> +#define TWI_LCR_SDA_EN		(0x01 << 0)
> +/* SDA line state control bit, 1:high level;0:low level */
> +#define TWI_LCR_SDA_CTL		(0x01 << 1)
> +/* SCL line state control enable ,1:enable;0:disable */
> +#define TWI_LCR_SCL_EN		(0x01 << 2)
> +/* SCL line state control bit, 1:high level;0:low level */
> +#define TWI_LCR_SCL_CTL		(0x01 << 3)
> +/* current state of SDA,readonly bit */
> +#define TWI_LCR_SDA_STATE_MASK	(0x01 << 4)
> +/* current state of SCL,readonly bit */
> +#define TWI_LCR_SCL_STATE_MASK	(0x01 << 5)
> +/* 31:6bits reserved */
> +#define TWI_LCR_IDLE_STATUS	0x3a
> +
> +/* TWI Status Register Bit Fields & Masks  */
> +#define TWI_STAT_MASK		0xff
> +/* 7:0 bits use only,default is 0xF8 */
> +#define TWI_STAT_BUS_ERR	0x00	/* BUS ERROR */
> +
> +/* Master mode use only */
> +#define TWI_STAT_TX_STA		0x08	/* START condition transmitted */
> +/* Repeated START condition transmitted */
> +#define TWI_STAT_TX_RESTA	0x10
> +/* Address+Write bit transmitted, ACK received */
> +#define TWI_STAT_TX_AW_ACK	0x18
> +/* Address+Write bit transmitted, ACK not received */
> +#define TWI_STAT_TX_AW_NAK	0x20
> +/* data byte transmitted in master mode,ack received */
> +#define TWI_STAT_TXD_ACK	0x28
> +/* data byte transmitted in master mode ,ack not received */
> +#define TWI_STAT_TXD_NAK	0x30
> +/* arbitration lost in address or data byte */
> +#define TWI_STAT_ARBLOST	0x38
> +/* Address+Read bit transmitted, ACK received */
> +#define TWI_STAT_TX_AR_ACK	0x40
> +/* Address+Read bit transmitted, ACK not received */
> +#define TWI_STAT_TX_AR_NAK	0x48
> +/* Second Address byte + Write bit transmitted, ACK received */
> +#define TWI_STAT_TX_2AW_ACK	0xD0
> +/* Second Address byte + Write bit transmitted, ACK received */
> +#define TWI_STAT_TX_2AW_NAK	0xD8
> +/* data byte received in master mode ,ack transmitted */
> +#define TWI_STAT_RXD_ACK	0x50
> +/* date byte received in master mode,not ack transmitted */
> +#define TWI_STAT_RXD_NAK	0x58
> +
> +/* Slave mode use only */
> +/* Slave address+Write bit received, ACK transmitted */
> +#define TWI_STAT_RXWS_ACK	0x60
> +/*
> + * Arbitration lost in address as master, slave address + Write bit received,
> + * ACK transmitted
> + */
> +#define TWI_STAT_ARBLOST_RXWS_ACK 0x68
> +/* General Call address received, ACK transmitted */
> +#define TWI_STAT_RXGCAS_ACK	0x70
> +/*
> + * Arbitration lost in address as master, General Call address received,
> + * ACK transmitted
> + */
> +#define TWI_STAT_ARBLOST_RXGCAS_ACK 0x78
> +/* Data byte received after slave address received, ACK transmitted */
> +#define TWI_STAT_RXDS_ACK	0x80
> +/* Data byte received after slave address received, not ACK transmitted */
> +#define TWI_STAT_RXDS_NAK	0x88
> +/* Data byte received after General Call received, ACK transmitted */
> +#define TWI_STAT_RXDGCAS_ACK	0x90
> +/* Data byte received after General Call received, not ACK transmitted */
> +#define TWI_STAT_RXDGCAS_NAK	0x98
> +/* STOP or repeated START condition received in slave  */
> +#define TWI_STAT_RXSTPS_RXRESTAS 0xA0
> +/* Slave address + Read bit received, ACK transmitted */
> +#define TWI_STAT_RXRS_ACK	0xA8
> +/*
> + * Arbitration lost in address as master, slave address + Read bit received,
> + * ACK transmitted
> + */
> +#define TWI_STAT_ARBLOST_SLAR_ACK 0xB0
> +/* Data byte transmitted in slave mode, ACK received */
> +#define TWI_STAT_TXDS_ACK	0xB8
> +/* Data byte transmitted in slave mode, ACK not received */
> +#define TWI_STAT_TXDS_NAK	0xC0
> +/* Last byte transmitted in slave mode, ACK received */
> +#define TWI_STAT_TXDSL_ACK	0xC8
> +
> +/* 10bit Address, second part of address */
> +/* Second Address byte+Write bit transmitted,ACK received */
> +#define TWI_STAT_TX_SAW_ACK	0xD0
> +/* Second Address byte+Write bit transmitted,ACK not received */
> +#define TWI_STAT_TX_SAW_NAK	0xD8
> +
> +/* No relevant status infomation,INT_FLAG = 0 */
> +#define TWI_STAT_IDLE		0xF8
> +
> +#endif
> diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
> index 5dbdbe3..9f929e6 100644
> --- a/drivers/i2c/Makefile
> +++ b/drivers/i2c/Makefile
> @@ -46,6 +46,7 @@ COBJS-$(CONFIG_TSI108_I2C) += tsi108_i2c.o
>  COBJS-$(CONFIG_U8500_I2C) += u8500_i2c.o
>  COBJS-$(CONFIG_SH_I2C) += sh_i2c.o
>  COBJS-$(CONFIG_SH_SH7734_I2C) += sh_sh7734_i2c.o
> +COBJS-$(CONFIG_SUNXI_I2C) += sunxi_i2c.o
>  
>  COBJS	:= $(COBJS-y)
>  SRCS	:= $(COBJS:.o=.c)
> diff --git a/drivers/i2c/sunxi_i2c.c b/drivers/i2c/sunxi_i2c.c
> new file mode 100644
> index 0000000..6bf5309
> --- /dev/null
> +++ b/drivers/i2c/sunxi_i2c.c
> @@ -0,0 +1,278 @@
> +/*
> + * Copyright (c) 2012 Henrik Nordstrom <henrik at henriknordstrom.net>
> + *
> + * See file CREDITS for list of people who contributed to this
> + * project.
> + *
> + * 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.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
> + * MA 02111-1307 USA
> + */
> +
> +#include <common.h>
> +#include <i2c.h>
> +#include <asm/io.h>
> +#include <asm/arch/cpu.h>
> +#include <asm/arch/i2c.h>
> +#include <asm/arch/gpio.h>
> +#include <asm/arch/clock.h>
> +
> +static struct i2c __attribute__ ((section(".data"))) *i2c_base =
> +	(struct i2c *)0x1c2ac00;
> +
> +void i2c_init(int speed, int slaveaddr)
> +{
> +	sunxi_gpio_set_cfgpin(SUNXI_GPB(0), 2);
> +	sunxi_gpio_set_cfgpin(SUNXI_GPB(1), 2);
> +	clock_twi_onoff(0, 1);
> +
> +	/* Enable the i2c bus */
> +	writel(TWI_CTL_BUSEN, &i2c_base->ctl);
> +
> +	/* 400KHz operation M=2, N=1, 24MHz APB clock */
> +	writel(TWI_CLK_DIV(2, 1), &i2c_base->clkr);
> +	writel(TWI_SRST_SRST, &i2c_base->reset);
> +
> +	while ((readl(&i2c_base->reset) & TWI_SRST_SRST))
> +		;
> +}
> +
> +int i2c_probe(uchar chip)
> +{
> +	return -1;
> +}
> +
> +static int i2c_wait_ctl(int mask, int state)
> +{
> +	int timeout = 0x2ff;
> +	int value = state ? mask : 0;
> +
> +	debug("i2c_wait_ctl(%x == %x), ctl=%x, status=%x\n", mask, value,
> +	      i2c_base->ctl, i2c_base->status);
> +
> +	while (((readl(&i2c_base->ctl) & mask) != value) && timeout-- > 0)
> +		;
> +
> +	debug("i2c_wait_ctl(), timeout=%d, ctl=%x, status=%x\n", timeout,
> +	      i2c_base->ctl, i2c_base->status);
> +
> +	if (timeout != 0)
> +		return 0;
> +	else
> +		return -1;
> +}
> +
> +static void i2c_clear_irq(void)
> +{
> +	writel(readl(&i2c_base->ctl) & ~TWI_CTL_INTFLG, &i2c_base->ctl);
> +}
> +
> +static int i2c_wait_irq(void)
> +{
> +	return i2c_wait_ctl(TWI_CTL_INTFLG, 1);
> +}
> +
> +static int i2c_wait_status(int status)
> +{
> +	int timeout = 0x2ff;
> +
> +	while (readl(&i2c_base->status) != status && timeout-- > 0)
> +		;
> +
> +	if (timeout != 0)
> +		return 0;
> +	else
> +		return -1;
> +}
> +
> +static int i2c_wait_irq_status(int status)
> +{
> +	if (i2c_wait_irq() != 0)
> +		return -1;
> +
> +	if (readl(&i2c_base->status) != status)
> +		return -1;
> +
> +	return 0;
> +}
> +
> +static int i2c_wait_bus_idle(void)
> +{
> +	int timeout = 0x2ff;
> +
> +	while (readl(&i2c_base->lctl) != 0x3a && timeout-- > 0)
> +		;
> +
> +	if (timeout != 0)
> +		return 0;
> +	else
> +		return -1;
> +}
> +
> +static int i2c_stop(void)
> +{
> +	u32 ctl;
> +
> +	ctl = readl(&i2c_base->ctl) & 0xc0;
> +	ctl |= TWI_CTL_STP;
> +
> +	writel(ctl, &i2c_base->ctl);
> +
> +	/* dummy to delay one I/O operation to make sure it's started */
> +	(void)readl(&i2c_base->ctl);
> +
> +	if (i2c_wait_ctl(TWI_CTL_STP, 0) != 0)
> +		return -1;
> +	if (i2c_wait_status(TWI_STAT_IDLE))
> +		return -1;
> +	if (i2c_wait_bus_idle() != 0)
> +		return -1;
> +
> +	return 0;
> +}
> +
> +static int i2c_send_data(u8 data, u8 status)
> +{
> +	debug("i2c_write(%02x, %x), ctl=%x, status=%x\n", data, status,
> +	      i2c_base->ctl, i2c_base->status);
> +
> +	writel(data, &i2c_base->data);
> +	i2c_clear_irq();
> +
> +	if (i2c_wait_irq_status(status) != 0)
> +		return -1;
> +
> +	return 0;
> +}
> +
> +static int i2c_start(int status)
> +{
> +	u32 ctl;
> +
> +	debug("i2c_start(%x), ctl=%x, status=%x\n", status, i2c_base->ctl,
> +	      i2c_base->status);
> +	/* Check that the controller is idle */
> +	if (status == TWI_STAT_TX_STA
> +	    && readl(&i2c_base->status) != TWI_STAT_IDLE) {
> +		return -1;
> +	}
> +
> +	writel(0, &i2c_base->efr);
> +
> +	/* Send start */
> +	ctl = readl(&i2c_base->ctl);
> +	ctl |= TWI_CTL_STA;	/* Set start bit */
> +	ctl &= ~TWI_CTL_INTFLG;	/* Clear int flag */
> +	writel(ctl, &i2c_base->ctl);
> +
> +	if (i2c_wait_ctl(TWI_CTL_STA, 0) != 0)
> +		return -1;
> +	if (i2c_wait_irq_status(status) != 0)
> +		return -1;
> +
> +	return 0;
> +}
> +
> +int i2c_do_read(uchar chip, uint addr, int alen, uchar *buffer, int len)
> +{
> +	u32 status;
> +	u32 ctl;
> +
> +	if (i2c_start(TWI_STAT_TX_STA) != 0)
> +		return -1;
> +
> +	/* Send chip address */
> +	if (i2c_send_data(chip << 1 | 0, TWI_STAT_TX_AW_ACK) != 0)
> +		return -1;
> +
> +	/* Send data address */
> +	if (i2c_send_data(addr, TWI_STAT_TXD_ACK) != 0)
> +		return -1;
> +
> +	/* Send restart for read */
> +	if (i2c_start(TWI_STAT_TX_RESTA) != 0)
> +		return -1;
> +
> +	/* Send chip address */
> +	if (i2c_send_data(chip << 1 | 1, TWI_STAT_TX_AR_ACK) != 0)
> +		return -1;
> +
> +	/* Set ACK mode */
> +	ctl = readl(&i2c_base->ctl);
> +	ctl |= TWI_CTL_ACK;
> +	writel(ctl, &i2c_base->ctl);
> +	status = TWI_STAT_RXD_ACK;
> +
> +	/* Read data */
> +	while (len > 0) {
> +		if (len == 1) {
> +			/* Set NACK mode (last byte) */
> +			ctl = readl(&i2c_base->ctl);
> +			ctl &= ~TWI_CTL_ACK;
> +			writel(ctl, &i2c_base->ctl);
> +			status = TWI_STAT_RXD_NAK;
> +		}
> +
> +		i2c_clear_irq();
> +		if (i2c_wait_irq_status(status) != 0)
> +			return -1;
> +
> +		*buffer++ = readl(&i2c_base->data);
> +		len--;
> +	}
> +
> +	return 0;
> +}
> +
> +int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len)
> +{
> +	int rc = i2c_do_read(chip, addr, alen, buffer, len);
> +
> +	i2c_stop();
> +
> +	return rc;
> +}
> +
> +static int i2c_do_write(uchar chip, uint addr, int alen, uchar *buffer,
> +			int len)
> +{
> +	if (i2c_start(TWI_STAT_TX_STA) != 0)
> +		return -1;
> +
> +	/* Send chip address */
> +	if (i2c_send_data(chip << 1 | 0, TWI_STAT_TX_AW_ACK) != 0)
> +		return -1;
> +
> +	/* Send data address */
> +	if (i2c_send_data(addr, TWI_STAT_TXD_ACK) != 0)
> +		return -1;
> +
> +	/* Send data */
> +	while (len > 0) {
> +		if (i2c_send_data(*buffer++, TWI_STAT_TXD_ACK) != 0)
> +			return -1;
> +		len--;
> +	}
> +
> +	return 0;
> +}
> +
> +int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len)
> +{
> +	int rc = i2c_do_write(chip, addr, alen, buffer, len);
> +
> +	i2c_stop();
> +
> +	return rc;
> +}
> diff --git a/include/configs/sunxi-common.h b/include/configs/sunxi-common.h
> index 8a026e0..c2d16fb 100644
> --- a/include/configs/sunxi-common.h
> +++ b/include/configs/sunxi-common.h
> @@ -194,4 +194,12 @@
>  #undef CONFIG_CMD_NET
>  #undef CONFIG_CMD_NFS
>  
> +/* I2C */
> +#define CONFIG_SPL_I2C_SUPPORT
> +#define CONFIG_SYS_I2C_SPEED		400000
> +#define CONFIG_HARD_I2C
> +#define CONFIG_SUNXI_I2C
> +#define CONFIG_SYS_I2C_SLAVE		0x7f
> +#define CONFIG_CMD_I2C

Why don't you do it like this:

#ifdef CONFIG_CMD_I2C
#define CONFIG_SPL_I2C_SUPPORT
#define CONFIG_SYS_I2C_SPEED		400000
#define CONFIG_HARD_I2C
#define CONFIG_SUNXI_I2C
#define CONFIG_SYS_I2C_SLAVE		0x7f
#endif /* CONFIG_CMD_I2C */

That way you can simply turn on i2c support in board configuration file.
If users don't want/need i2c they don't need to use it.

> +
>  #endif /* __CONFIG_H */
> -- 
> 1.7.7.6

Luka


More information about the U-Boot mailing list