From patchwork Sun Nov 25 11:38:46 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Henrik_Nordstr=C3=B6m?= X-Patchwork-Id: 201520 X-Patchwork-Delegate: hs@denx.de Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id D99832C0084 for ; Sun, 25 Nov 2012 22:39:15 +1100 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id DBA3B4A03F; Sun, 25 Nov 2012 12:39:13 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id DeEocU0nP7+9; Sun, 25 Nov 2012 12:39:13 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 1B1154A029; Sun, 25 Nov 2012 12:39:10 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 2EE624A039 for ; Sun, 25 Nov 2012 12:38:57 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 6qDMf7CX-iVS for ; Sun, 25 Nov 2012 12:38:55 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from vps1.hno.se (vps1.hno.se [31.192.227.87]) by theia.denx.de (Postfix) with ESMTPS id 8DE7C4A025 for ; Sun, 25 Nov 2012 12:38:53 +0100 (CET) Received: from home.hno.se (home.hno.se [IPv6:2001:470:df90::1]) (authenticated bits=128) by vps1.hno.se (8.14.4/8.14.4) with ESMTP id qAPBcm5Z014198 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Sun, 25 Nov 2012 12:38:50 +0100 Received: from [127.0.0.1] (localhost.localdomain [127.0.0.1]) by home.hno.se (8.14.5/8.14.5) with ESMTP id qAPBckLb015112 for ; Sun, 25 Nov 2012 12:38:46 +0100 Message-ID: <1353843526.17518.15.camel@home.hno.se> From: Henrik =?ISO-8859-1?Q?Nordstr=F6m?= To: U-Boot Mailing List Date: Sun, 25 Nov 2012 12:38:46 +0100 In-Reply-To: References: X-Mailer: Evolution 3.2.3 (3.2.3-3.fc16) Mime-Version: 1.0 Subject: [U-Boot] [PATCH 03/22] ARM sunxi: I2C driver X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.11 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de A basic basic driver for the I2C controller found in Allwinner sunXi (A10 & A13) SoCs. Signed-off-by: Henrik Nordstrom Signed-off-by: Stefan Roese --- 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; } + /* 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 + * + * Based on sun4i linux kernle i2c.h + * (C) Copyright 2007-2012 + * Allwinner Technology Co., Ltd. + * Tom Cubie + * Victor Wei + * + * 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 + */ +#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 + * + * 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 +#include +#include +#include +#include +#include +#include + +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 + #endif /* __CONFIG_H */