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

Luka Perkov
2012-11-25T14:41:10Z

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@henriknordstrom.net>
> Signed-off-by: Stefan Roese <sr@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@henriknordstrom.net>
> + *
> + * Based on sun4i linux kernle i2c.h
> + * (C) Copyright 2007-2012
> + * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
> + * Tom Cubie <tanglaing@allwinnertech.com>
> + * Victor Wei <weiziheng@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@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