From 8e579254f12b19f33d28fa233312d3b24dae889c Mon Sep 17 00:00:00 2001 From: kfx Date: Tue, 7 Feb 2012 18:06:57 +0800 Subject: [PATCH] add rk30 i2c drvier --- arch/arm/mach-rk30/board-rk30-sdk.c | 44 +- arch/arm/mach-rk30/devices.c | 195 ++++++++ arch/arm/plat-rk/include/plat/board.h | 13 + drivers/i2c/busses/Kconfig | 81 ++++ drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-rk29-adapter.c | 619 ++++++++++++++++++++++++++ drivers/i2c/busses/i2c-rk30-adapter.c | 69 +++ drivers/i2c/busses/i2c-rk30.c | 296 ++++++++++++ drivers/i2c/busses/i2c-rk30.h | 79 ++++ 9 files changed, 1396 insertions(+), 1 deletion(-) create mode 100755 drivers/i2c/busses/i2c-rk29-adapter.c create mode 100755 drivers/i2c/busses/i2c-rk30-adapter.c create mode 100755 drivers/i2c/busses/i2c-rk30.c create mode 100755 drivers/i2c/busses/i2c-rk30.h diff --git a/arch/arm/mach-rk30/board-rk30-sdk.c b/arch/arm/mach-rk30/board-rk30-sdk.c index d31641290823..73ee2a23da4f 100755 --- a/arch/arm/mach-rk30/board-rk30-sdk.c +++ b/arch/arm/mach-rk30/board-rk30-sdk.c @@ -39,9 +39,51 @@ static struct platform_device *devices[] __initdata = { }; +// i2c +#ifdef CONFIG_I2C0_RK30 +static struct i2c_board_info __initdata i2c0_info[] = { +}; +#endif +#ifdef CONFIG_I2C1_RK30 +static struct i2c_board_info __initdata i2c1_info[] = { +}; +#endif +#ifdef CONFIG_I2C2_RK30 +static struct i2c_board_info __initdata i2c2_info[] = { +}; +#endif +#ifdef CONFIG_I2C3_RK30 +static struct i2c_board_info __initdata i2c3_info[] = { +}; +#endif +#ifdef CONFIG_I2C4_RK30 +static struct i2c_board_info __initdata i2c4_info[] = { +}; +#endif +static void __init rk30_i2c_register_board_info(void) +{ +#ifdef CONFIG_I2C0_RK30 + i2c_register_board_info(0, i2c0_info, ARRAY_SIZE(i2c0_info)); +#endif +#ifdef CONFIG_I2C1_RK30 + i2c_register_board_info(1, i2c1_info, ARRAY_SIZE(i2c1_info)); +#endif +#ifdef CONFIG_I2C2_RK30 + i2c_register_board_info(2, i2c2_info, ARRAY_SIZE(i2c2_info)); +#endif +#ifdef CONFIG_I2C3_RK30 + i2c_register_board_info(3, i2c3_info, ARRAY_SIZE(i2c3_info)); +#endif +#ifdef CONFIG_I2C4_RK30 + i2c_register_board_info(4, i2c4_info, ARRAY_SIZE(i2c4_info)); +#endif +} +//end of i2c + static void __init machine_rk30_board_init(void) { - platform_add_devices(devices, ARRAY_SIZE(devices)); + rk30_i2c_register_board_info(); + platform_add_devices(devices, ARRAY_SIZE(devices)); } static void __init rk30_reserve(void) diff --git a/arch/arm/mach-rk30/devices.c b/arch/arm/mach-rk30/devices.c index 20c0b7294eb9..d3af4dead458 100644 --- a/arch/arm/mach-rk30/devices.c +++ b/arch/arm/mach-rk30/devices.c @@ -126,6 +126,200 @@ static void __init rk30_init_uart(void) #endif } +// i2c +#ifdef CONFIG_I2C0_RK30 +static struct rk30_i2c_platform_data default_i2c0_data = { + .bus_num = 0, + .is_div_from_arm = 1, + #ifdef CONFIG_I2C0_CONTROLLER_RK29 + .adap_type = I2C_RK29_ADAP, + #endif + #ifdef CONFIG_I2C0_CONTROLLER_RK30 + .adap_type = I2C_RK30_ADAP, + #endif +}; +static struct resource resources_i2c0[] = { + { + .start = IRQ_I2C0, + .end = IRQ_I2C0, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK30_I2C0_PHYS, + .end = RK30_I2C0_PHYS + RK30_I2C0_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device device_i2c0 = { + .name = "i2c-rk30", + .id = 0, + .num_resources = ARRAY_SIZE(resources_i2c0), + .resource = resources_i2c0, + .dev = { + .platform_data = &default_i2c0_data, + }, +}; +#endif +#ifdef CONFIG_I2C1_RK30 +static struct rk30_i2c_platform_data default_i2c1_data = { + .bus_num = 1, + .is_div_from_arm = 1, + #ifdef CONFIG_I2C1_CONTROLLER_RK29 + .adap_type = I2C_RK29_ADAP, + #endif + #ifdef CONFIG_I2C1_CONTROLLER_RK30 + .adap_type = I2C_RK30_ADAP, + #endif +}; +static struct resource resources_i2c1[] = { + { + .start = IRQ_I2C1, + .end = IRQ_I2C1, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK30_I2C1_PHYS, + .end = RK30_I2C1_PHYS + RK30_I2C1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device device_i2c1 = { + .name = "i2c-rk30", + .id = 1, + .num_resources = ARRAY_SIZE(resources_i2c1), + .resource = resources_i2c1, + .dev = { + .platform_data = &default_i2c1_data, + }, +}; +#endif +#ifdef CONFIG_I2C2_RK30 +static struct rk30_i2c_platform_data default_i2c2_data = { + .bus_num = 2, + .is_div_from_arm = 0, + #ifdef CONFIG_I2C2_CONTROLLER_RK29 + .adap_type = I2C_RK29_ADAP, + #endif + #ifdef CONFIG_I2C2_CONTROLLER_RK30 + .adap_type = I2C_RK30_ADAP, + #endif +}; +static struct resource resources_i2c2[] = { + { + .start = IRQ_I2C2, + .end = IRQ_I2C2, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK30_I2C2_PHYS, + .end = RK30_I2C2_PHYS + RK30_I2C2_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device device_i2c2 = { + .name = "i2c-rk30", + .id = 2, + .num_resources = ARRAY_SIZE(resources_i2c2), + .resource = resources_i2c2, + .dev = { + .platform_data = &default_i2c2_data, + }, +}; +#endif + +#ifdef CONFIG_I2C3_RK30 +static struct rk30_i2c_platform_data default_i2c3_data = { + .bus_num = 3, + .is_div_from_arm = 0, + #ifdef CONFIG_I2C3_CONTROLLER_RK29 + .adap_type = I2C_RK29_ADAP, + #endif + #ifdef CONFIG_I2C3_CONTROLLER_RK30 + .adap_type = I2C_RK30_ADAP, + #endif +}; +static struct resource resources_i2c3[] = { + { + .start = IRQ_I2C3, + .end = IRQ_I2C3, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK30_I2C3_PHYS, + .end = RK30_I2C3_PHYS + RK30_I2C3_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device device_i2c3 = { + .name = "i2c-rk30", + .id = 3, + .num_resources = ARRAY_SIZE(resources_i2c3), + .resource = resources_i2c3, + .dev = { + .platform_data = &default_i2c3_data, + }, +}; +#endif +#ifdef CONFIG_I2C4_RK30 +static struct rk30_i2c_platform_data default_i2c4_data = { + .bus_num = 4, + .is_div_from_arm = 0, + #ifdef CONFIG_I2C4_CONTROLLER_RK29 + .adap_type = I2C_RK29_ADAP, + #endif + #ifdef CONFIG_I2C4_CONTROLLER_RK30 + .adap_type = I2C_RK30_ADAP, + #endif +}; +static struct resource resources_i2c4[] = { + { + .start = IRQ_I2C4, + .end = IRQ_I2C4, + .flags = IORESOURCE_IRQ, + }, + { + .start = RK30_I2C4_PHYS, + .end = RK30_I2C4_PHYS + RK30_I2C4_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device device_i2c4 = { + .name = "i2c-rk30", + .id = 4, + .num_resources = ARRAY_SIZE(resources_i2c4), + .resource = resources_i2c4, + .dev = { + .platform_data = &default_i2c4_data, + }, +}; +#endif + +static void __init rk30_init_i2c(void) +{ +#ifdef CONFIG_I2C0_RK30 + platform_device_register(&device_i2c0); +#endif +#ifdef CONFIG_I2C1_RK30 + platform_device_register(&device_i2c1); +#endif +#ifdef CONFIG_I2C2_RK30 + platform_device_register(&device_i2c2); +#endif +#ifdef CONFIG_I2C3_RK30 + platform_device_register(&device_i2c3); +#endif +#ifdef CONFIG_I2C4_RK30 + platform_device_register(&device_i2c4); +#endif +} +//end of i2c + + #ifdef CONFIG_MTD_NAND_RK29XX static struct resource resources_nand[] = { { @@ -146,6 +340,7 @@ static struct platform_device device_nand = { static int __init rk30_init_devices(void) { rk30_init_uart(); + rk30_init_i2c(); #ifdef CONFIG_MTD_NAND_RK29XX platform_device_register(&device_nand); #endif diff --git a/arch/arm/plat-rk/include/plat/board.h b/arch/arm/plat-rk/include/plat/board.h index 8729fde11879..5761c138d628 100644 --- a/arch/arm/plat-rk/include/plat/board.h +++ b/arch/arm/plat-rk/include/plat/board.h @@ -11,6 +11,19 @@ #define BOOT_MODE_OFFMODE_CHARGING 5 #define BOOT_MODE_REBOOT 6 #define BOOT_MODE_PANIC 7 + +struct rk30_i2c_platform_data { + char *name; + int bus_num; +#define I2C_RK29_ADAP 0 +#define I2C_RK30_ADAP 1 + int adap_type:1; + int is_div_from_arm:1; + u32 flags; + int (*io_init)(void); + int (*io_deinit)(void); +}; + int board_boot_mode(void); /* for USB detection */ diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 01e5f49a0c83..2d7e414c4070 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -839,6 +839,87 @@ config SCx200_I2C depends on SCx200_GPIO select I2C_ALGOBIT +config I2C_RK30 + tristate "RK I2C Adapter" + depends on PLAT_RK && !ARCH_RK29 + default y + help + This supports I2C Adapter on RK Soc. + +if I2C_RK30 + comment "Now, there are five selectable I2C channels." + + config I2C0_RK30 + bool "I2C0 Channel Support" + default y + help + This supports the use of the I2C0 channel on RK Soc. + if I2C0_RK30 + choice + prompt "I2C Controller Select" + config I2C0_CONTROLLER_RK29 + bool "With RK29 I2C Controller" + config I2C0_CONTROLLER_RK30 + bool "With RK30 I2C Controller" + endchoice + endif + config I2C1_RK30 + bool "I2C1 Channel Support" + default y + help + This supports the use of the I2C1 channel on RK Soc. + if I2C1_RK30 + choice + prompt "I2C Controller Select" + config I2C1_CONTROLLER_RK29 + bool "With RK29 I2C Controller" + config I2C1_CONTROLLER_RK30 + bool "With RK30 I2C Controller" + endchoice + endif + config I2C2_RK30 + bool "I2C2 Channel Support" + default y + help + This supports the use of the I2C2 channel on RK Soc. + if I2C2_RK30 + choice + prompt "I2C Controller Select" + config I2C2_CONTROLLER_RK29 + bool "With RK29 I2C Controller" + config I2C2_CONTROLLER_RK30 + bool "With RK30 I2C Controller" + endchoice + endif + config I2C3_RK30 + bool "I2C3 Channel Support" + default y + help + This supports the use of the I2C3 channel on RK Soc. + if I2C3_RK30 + choice + prompt "I2C Controller Select" + config I2C3_CONTROLLER_RK29 + bool "With RK29 I2C Controller" + config I2C3_CONTROLLER_RK30 + bool "With RK30 I2C Controller" + endchoice + endif + config I2C4_RK30 + bool "I2C4 Channel Support" + default y + help + This supports the use of the I2C4 channel on RK Soc. + if I2C4_RK30 + choice + prompt "I2C Controller Select" + config I2C4_CONTROLLER_RK29 + bool "With RK29 I2C Controller" + config I2C4_CONTROLLER_RK30 + bool "With RK30 I2C Controller" + endchoice + endif +endif config I2C_RK29 tristate "RK29 i2c interface (I2C)" depends on ARCH_RK29 diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 7c2fd1ae13df..bb71ae214ec3 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_I2C_RK29) += i2c-rk29.o obj-$(CONFIG_I2C_DEV_RK29) += i2c-dev-rk29.o +obj-$(CONFIG_I2C_RK30) += i2c-rk30.o i2c-rk29-adapter.o i2c-rk30-adapter.o obj-y += i2c-gpio.o # ACPI drivers diff --git a/drivers/i2c/busses/i2c-rk29-adapter.c b/drivers/i2c/busses/i2c-rk29-adapter.c new file mode 100755 index 000000000000..5edf143f69a2 --- /dev/null +++ b/drivers/i2c/busses/i2c-rk29-adapter.c @@ -0,0 +1,619 @@ +/* drivers/i2c/busses/i2c-rk29-adapter.c + * + * Copyright (C) 2012 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include "i2c-rk30.h" + +/* master transmit */ +#define I2C_MTXR (0x0000) +/* master receive */ +#define I2C_MRXR (0x0004) +/* slave address */ +#define I2C_SADDR (0x0010) +/* interrupt enable control */ +#define I2C_IER (0x0014) +#define I2C_IER_ARBITR_LOSE (1<<7) +#define I2C_IER_MRX_NEED_ACK (1<<1) +#define I2C_IER_MTX_RCVD_ACK (1<<0) + +#define IRQ_MST_ENABLE (I2C_IER_ARBITR_LOSE | \ + I2C_IER_MRX_NEED_ACK | \ + I2C_IER_MTX_RCVD_ACK) +#define IRQ_ALL_DISABLE (0x00) + +/* interrupt status, write 0 to clear */ +#define I2C_ISR (0x0018) +#define I2C_ISR_ARBITR_LOSE (1<<7) +#define I2C_ISR_MRX_NEED_ACK (1<<1) +#define I2C_ISR_MTX_RCVD_ACK (1<<0) + +/* stop/start/resume command, write 1 to set */ +#define I2C_LCMR (0x001c) +#define I2C_LCMR_RESUME (1<<2) +#define I2C_LCMR_STOP (1<<1) +#define I2C_LCMR_START (1<<0) + +/* i2c core status */ +#define I2C_LSR (0x0020) +#define I2C_LSR_RCV_NAK (1<<1) +#define I2C_LSR_RCV_ACK (~(1<<1)) +#define I2C_LSR_BUSY (1<<0) + +/* i2c config */ +#define I2C_CONR (0x0024) +#define I2C_CONR_NAK (1<<4) +#define I2C_CONR_ACK (~(1<<4)) +#define I2C_CONR_MTX_MODE (1<<3) +#define I2C_CONR_MRX_MODE (~(1<<3)) +#define I2C_CONR_MPORT_ENABLE (1<<2) +#define I2C_CONR_MPORT_DISABLE (~(1<<2)) + +/* i2c core config */ +#define I2C_OPR (0x0028) +#define I2C_OPR_RESET_STATUS (1<<7) +#define I2C_OPR_CORE_ENABLE (1<<6) + +#define I2CCDVR_REM_BITS (0x03) +#define I2CCDVR_REM_MAX (1<<(I2CCDVR_REM_BITS)) +#define I2CCDVR_EXP_BITS (0x03) +#define I2CCDVR_EXP_MAX (1<<(I2CCDVR_EXP_BITS)) + + +#define RK29_I2C_START_TMO_COUNT 100 // msleep 1 * 100 +#define ABNORMAL_STOP_DELAY 200 //unit us +static unsigned int abnormal_stop_addr[] = { + 0x2d, +}; + +int i2c_suspended(struct i2c_adapter *adap) +{ + return 1; +} +EXPORT_SYMBOL(i2c_suspended); + +static inline void rk29_i2c_disable_ack(void __iomem *regs) +{ + unsigned long conr = readl(regs + I2C_CONR); + + conr |= I2C_CONR_NAK; + writel(conr, regs + I2C_CONR); +} + +static inline void rk29_i2c_enable_ack(void __iomem *regs) +{ + unsigned long conr = readl(regs + I2C_CONR); + + conr &= I2C_CONR_ACK; + writel(conr, regs + I2C_CONR); +} +static inline void rk29_i2c_disable_mport(void __iomem *regs) +{ + unsigned long conr = readl(regs + I2C_CONR); + + conr &= I2C_CONR_MPORT_DISABLE; + writel(conr, regs + I2C_CONR); +} + +static inline void rk29_i2c_enable_mport(void __iomem *regs) +{ + unsigned long conr = readl(regs + I2C_CONR); + + conr |= I2C_CONR_MPORT_ENABLE; + writel(conr, regs + I2C_CONR); +} + +static inline void rk29_i2c_disable_irq(void __iomem *regs) +{ + writel(IRQ_ALL_DISABLE, regs + I2C_IER); +} + +static inline void rk29_i2c_enable_irq(void __iomem *regs) +{ + writel(IRQ_MST_ENABLE, regs + I2C_IER); +} + +/* scl = pclk/(5 *(rem+1) * 2^(exp+1)) */ +static void rk29_i2c_calcdivisor(unsigned long pclk, + unsigned long scl_rate, + unsigned long *real_rate, + unsigned int *rem, unsigned int *exp) +{ + unsigned int calc_rem = 0; + unsigned int calc_exp = 0; + + for(calc_exp = 0; calc_exp < I2CCDVR_EXP_MAX; calc_exp++) + { + calc_rem = pclk / (5 * scl_rate * (1 <<(calc_exp +1))); + if(calc_rem < I2CCDVR_REM_MAX) + break; + } + if(calc_rem >= I2CCDVR_REM_MAX || calc_exp >= I2CCDVR_EXP_MAX) + { + calc_rem = I2CCDVR_REM_MAX - 1; + calc_exp = I2CCDVR_EXP_MAX - 1; + } + *rem = calc_rem; + *exp = calc_exp; + *real_rate = pclk/(5 * (calc_rem + 1) * (1 <<(calc_exp +1))); + return; +} +static void rk29_i2c_set_clk(struct rk30_i2c *i2c, unsigned long scl_rate) +{ + + unsigned int rem = 0, exp = 0; + unsigned long real_rate = 0, tmp; + + unsigned long i2c_rate = 24000000;//clk_get_rate(i2c->clk); + + if((scl_rate == i2c->scl_rate) && (i2c_rate == i2c->i2c_rate)) + return; + + i2c->i2c_rate = i2c_rate; + i2c->scl_rate = scl_rate; + + rk29_i2c_calcdivisor(i2c->i2c_rate, i2c->scl_rate, &real_rate, &rem, &exp); + + tmp = readl(i2c->regs + I2C_OPR); + tmp &= ~0x3f; + tmp |= exp; + tmp |= rem<regs + I2C_OPR); + return; +} + +static void rk29_i2c_init_hw(struct rk30_i2c *i2c) +{ + unsigned long opr = readl(i2c->regs + I2C_OPR); + + opr |= I2C_OPR_RESET_STATUS; + writel(opr, i2c->regs + I2C_OPR); + + udelay(10); + opr = readl(i2c->regs + I2C_OPR); + opr &= ~I2C_OPR_RESET_STATUS; + writel(opr, i2c->regs + I2C_OPR); + + rk29_i2c_set_clk(i2c, 100000); + + rk29_i2c_disable_irq(i2c); + writel(0, i2c->regs + I2C_LCMR); + writel(0, i2c->regs + I2C_LCMR); + + opr = readl(i2c->regs + I2C_OPR); + opr |= I2C_OPR_CORE_ENABLE; + writel(opr, i2c->regs + I2C_OPR); + udelay(i2c->tx_setup); + + return; +} + +static inline void rk29_i2c_master_complete(struct rk30_i2c *i2c, int ret) +{ + i2c_dbg(i2c->dev, "master_complete %d\n", ret); + + i2c->msg_ptr = 0; + i2c->msg = NULL; + i2c->msg_idx++; + i2c->msg_num = 0; + if (ret) + i2c->msg_idx = ret; + + wake_up(&i2c->wait); +} + +static void rk29_i2c_message_start(struct rk30_i2c *i2c, + struct i2c_msg *msg) +{ + unsigned int addr = (msg->addr & 0x7f) << 1; + unsigned long stat, conr; + + stat = 0; + + i2c->addr = msg->addr & 0x7f; + + if (msg->flags & I2C_M_RD) + addr |= 1; + + if (msg->flags & I2C_M_REV_DIR_ADDR) + addr ^= 1; + + rk29_i2c_enable_ack(i2c->regs); + i2c_dbg(i2c->dev, "START: set addr 0x%02x to DS\n", addr); + + conr = readl(i2c->regs + I2C_CONR); + conr |= I2C_CONR_MTX_MODE; + writel(conr, i2c->regs + I2C_CONR); + writel(addr, i2c->regs + I2C_MTXR); + + udelay(i2c->tx_setup); + writel(I2C_LCMR_START|I2C_LCMR_RESUME, i2c->regs + I2C_LCMR); + +} + +static inline void rk29_i2c_stop(struct rk30_i2c *i2c, int ret) +{ + unsigned int n, i; + + i2c_dbg(i2c->dev, "STOP\n"); + udelay(i2c->tx_setup); + + n = sizeof(abnormal_stop_addr)/sizeof(abnormal_stop_addr[0]); + for(i = 0; i < n; i++){ + if(i2c->addr == abnormal_stop_addr[i]) + udelay(ABNORMAL_STOP_DELAY); + } + + + + writel(I2C_LCMR_STOP|I2C_LCMR_RESUME, i2c->regs + I2C_LCMR); + + i2c->state = STATE_STOP; + + rk29_i2c_master_complete(i2c, ret); + rk29_i2c_disable_irq(i2c->regs); +} + +/* returns TRUE if the current message is the last in the set */ +static inline int is_lastmsg(struct rk30_i2c *i2c) +{ + return i2c->msg_idx >= (i2c->msg_num - 1); +} + +/* returns TRUE if we this is the last byte in the current message */ +static inline int is_msglast(struct rk30_i2c *i2c) +{ + return i2c->msg_ptr == i2c->msg->len-1; +} + +/* returns TRUE if we reached the end of the current message */ +static inline int is_msgend(struct rk30_i2c *i2c) +{ + return i2c->msg_ptr >= i2c->msg->len; +} + +static int rk29_i2c_irq_nextbyte(struct rk30_i2c *i2c, unsigned long isr) +{ + unsigned long lsr, conr; + unsigned char byte; + int ret = 0; + + lsr = readl(i2c->regs + I2C_LSR); + + switch (i2c->state) { + case STATE_IDLE: + dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__); + goto out; + + case STATE_STOP: + dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__); + rk29_i2c_disable_irq(i2c->regs); + goto out; + + case STATE_START: + if(!(isr & I2C_ISR_MTX_RCVD_ACK)){ + writel(isr & ~I2C_ISR_MTX_RCVD_ACK, i2c->regs + I2C_ISR); + rk29_i2c_stop(i2c, -ENXIO); + dev_err(i2c->dev, "START: addr[0x%02x] ack was not received(isr)\n", i2c->addr); + goto out; + } + + if ((lsr & I2C_LSR_RCV_NAK) && + !(i2c->msg->flags & I2C_M_IGNORE_NAK)) { + writel(isr & ~I2C_ISR_MTX_RCVD_ACK, i2c->regs + I2C_ISR); + + rk29_i2c_stop(i2c, -ENXIO); + dev_err(i2c->dev, "START: addr[0x%02x] ack was not received(lsr)\n", i2c->addr); + goto out; + } + + if (i2c->msg->flags & I2C_M_RD) + i2c->state = STATE_READ; + else + i2c->state = STATE_WRITE; + + /* terminate the transfer if there is nothing to do + * as this is used by the i2c probe to find devices. */ + if (is_lastmsg(i2c) && i2c->msg->len == 0) { + writel(isr & ~I2C_ISR_MTX_RCVD_ACK, i2c->regs + I2C_ISR); + rk29_i2c_stop(i2c, 0); + goto out; + } + + if (i2c->state == STATE_READ){ + writel(isr & ~I2C_ISR_MTX_RCVD_ACK, i2c->regs + I2C_ISR); + goto prepare_read; + } + + case STATE_WRITE: + if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) { + if (!(isr & I2C_ISR_MTX_RCVD_ACK)){ + + rk29_i2c_stop(i2c, -ECONNREFUSED); + dev_err(i2c->dev, "WRITE: addr[0x%02x] No Ack\n", i2c->addr); + goto out; + } + } + writel(isr & ~I2C_ISR_MTX_RCVD_ACK, i2c->regs + I2C_ISR); + +retry_write: + + if (!is_msgend(i2c)) { + byte = i2c->msg->buf[i2c->msg_ptr++]; + conr = readl(i2c->regs + I2C_CONR); + conr |= I2C_CONR_MTX_MODE; + writel(conr, i2c->regs + I2C_CONR); + writel(byte, i2c->regs + I2C_MTXR); + + writel(I2C_LCMR_RESUME, i2c->regs + I2C_LCMR); + + } else if (!is_lastmsg(i2c)) { + /* we need to go to the next i2c message */ + + i2c_dbg(i2c->dev, "WRITE: Next Message\n"); + + i2c->msg_ptr = 0; + i2c->msg_idx++; + i2c->msg++; + + /* check to see if we need to do another message */ + if (i2c->msg->flags & I2C_M_NOSTART) { + if (i2c->msg->flags & I2C_M_RD) { + /* cannot do this, the controller + * forces us to send a new START + * when we change direction */ + + rk29_i2c_stop(i2c, -EINVAL); + } + + goto retry_write; + } else { + /* send the new start */ + rk29_i2c_message_start(i2c, i2c->msg); + i2c->state = STATE_START; + } + + } else { + /* send stop */ + + rk29_i2c_stop(i2c, 0); + } + break; + + case STATE_READ: + if(!(isr & I2C_ISR_MRX_NEED_ACK)){ + rk29_i2c_stop(i2c, -ENXIO); + dev_err(i2c->dev, "READ: addr[0x%02x] not recv need ack interrupt\n", i2c->addr); + goto out; + } + writel(isr & ~I2C_ISR_MRX_NEED_ACK, i2c->regs + I2C_ISR); + byte = readl(i2c->regs + I2C_MRXR); + i2c_dbg(i2c->dev, "READ: byte = %d\n", byte); + i2c->msg->buf[i2c->msg_ptr++] = byte; + prepare_read: + if (is_msgend(i2c)) { + if (is_lastmsg(i2c)) { + /* last message, send stop and complete */ + rk29_i2c_disable_ack(i2c); + i2c_dbg(i2c->dev, "READ: Send Stop\n"); + + rk29_i2c_stop(i2c, 0); + } else { + /* go to the next transfer */ + i2c_dbg(i2c->dev, "READ: Next Transfer\n"); + + i2c->msg_ptr = 0; + i2c->msg_idx++; + i2c->msg++; + rk29_i2c_message_start(i2c, i2c->msg); + i2c->state = STATE_START; + } + }else{ + conr = readl(i2c->regs + I2C_CONR); + conr &= I2C_CONR_MRX_MODE; + writel(conr, i2c->regs + I2C_CONR); + + writel(I2C_LCMR_RESUME, i2c->regs + I2C_LCMR); + } + break; + } + + out: + return ret; +} + +static irqreturn_t rk29_i2c_irq(int irqno, void *dev_id) +{ + struct rk30_i2c *i2c = dev_id; + unsigned long isr; + + spin_lock(&i2c->lock); + udelay(i2c->tx_setup); + + isr = readl(i2c->regs + I2C_ISR); + + if (isr & I2C_ISR_ARBITR_LOSE) { + writel(isr & ~I2C_ISR_ARBITR_LOSE, i2c->regs + I2C_ISR); + dev_err(i2c->dev, "deal with arbitration loss\n"); + } + + if (i2c->state == STATE_IDLE) { + i2c_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n"); + goto out; + } + + rk29_i2c_irq_nextbyte(i2c, isr); + + out: + spin_unlock(&i2c->lock); + + return IRQ_HANDLED; +} + + +/* rk29_i2c_set_master + * + * get the i2c bus for a master transaction +*/ + +static int rk29_i2c_set_master(struct rk30_i2c *i2c) +{ + int tmo = RK29_I2C_START_TMO_COUNT; + unsigned long lsr; + + while (tmo-- > 0) { + lsr = readl(i2c->regs + I2C_LSR); + if (!(lsr & I2C_LSR_BUSY)) + return 0; + msleep(1); + } + return -ETIMEDOUT; +} + +/* rk29_i2c_doxfer + * + * this starts an i2c transfer +*/ + +static int rk29_i2c_doxfer(struct rk30_i2c *i2c, + struct i2c_msg *msgs, int num) +{ + unsigned long timeout; + int ret; + + if (i2c->suspended) + return -EIO; + + ret = rk29_i2c_set_master(i2c); + if (ret != 0) { + dev_err(i2c->dev, "addr[0x%02x] cannot get bus (error %d)\n", msgs[0].addr, ret); + ret = -EAGAIN; + goto out; + } + + spin_lock_irq(&i2c->lock); + + i2c->msg = msgs; + i2c->msg_num = num; + i2c->msg_ptr = 0; + i2c->msg_idx = 0; + i2c->state = STATE_START; + + rk29_i2c_enable_irq(i2c); + rk29_i2c_message_start(i2c, msgs); + spin_unlock_irq(&i2c->lock); + + timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); + + ret = i2c->msg_idx; + + if (timeout == 0) + i2c_dbg(i2c->dev, "addr[0x%02x] wait event timeout\n", msgs[0].addr); + else if (ret != num) + i2c_dbg(i2c->dev, "addr[0x%02x ]incomplete xfer (%d)\n", msgs[0].addr, ret); + msleep(1); + if((readl(i2c->regs + I2C_LSR) & I2C_LSR_BUSY) || + readl(i2c->regs + I2C_LCMR) & I2C_LCMR_STOP ){ + dev_warn(i2c->dev, "WARNING: STOP abnormal, addr[0x%02x] isr = 0x%x, lsr = 0x%x, lcmr = 0x%x\n", + msgs[0].addr, + readl(i2c->regs + I2C_ISR), + readl(i2c->regs + I2C_LSR), + readl(i2c->regs + I2C_LCMR) + ); + } + out: + return ret; +} + +/* rk29_i2c_xfer + * + * first port of call from the i2c bus code when an message needs + * transferring across the i2c bus. +*/ + +static int rk29_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct rk30_i2c *i2c = (struct rk30_i2c *)adap->algo_data; + int retry; + int ret; + unsigned long scl_rate; + + if(msgs[0].scl_rate <= 400000 && msgs[0].scl_rate >= 10000) + scl_rate = msgs[0].scl_rate; + else if(msgs[0].scl_rate > 400000){ + dev_warn(i2c->dev, "Warning: addr[0x%x] msg[0].scl_rate( = %dKhz) is too high!", + msgs[0].addr, msgs[0].scl_rate/1000); + scl_rate = 400000; + } + else{ + dev_warn(i2c->dev, "Warning: addr[0x%x] msg[0].scl_rate( = %dKhz) is too low!", + msgs[0].addr, msgs[0].scl_rate/1000); + scl_rate = 10000; + } + if(i2c->is_div_from_arm[i2c->adap.nr]) + wake_lock(&i2c->idlelock[i2c->adap.nr]); + + rk29_i2c_set_clk(i2c, scl_rate); + rk29_i2c_enable_mport(i2c->regs); + udelay(i2c->tx_setup); + + for (retry = 0; retry < adap->retries; retry++) { + + ret = rk29_i2c_doxfer(i2c, msgs, num); + + if (ret != -EAGAIN) { + return ret; + } + + i2c_dbg(i2c->dev, "Retrying transmission (%d)\n", retry); + + msleep(1); + } + + rk29_i2c_disable_mport(i2c->regs); + if(i2c->is_div_from_arm[i2c->adap.nr]) + wake_unlock(&i2c->idlelock[i2c->adap.nr]); + return -EREMOTEIO; +} + +/* declare our i2c functionality */ +static u32 rk29_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING; +} + +/* i2c bus registration info */ + +static const struct i2c_algorithm rk29_i2c_algorithm = { + .master_xfer = rk29_i2c_xfer, + .functionality = rk29_i2c_func, +}; + +int i2c_add_rk29_adapter(struct i2c_adapter *adap) +{ + int ret = 0; + struct rk30_i2c *i2c = (struct rk30_i2c *)adap->algo_data; + + adap->algo = &rk29_i2c_algorithm; + adap->retries = 3; + + i2c->i2c_init_hw = &rk29_i2c_init_hw; + i2c->i2c_set_clk = &rk29_i2c_set_clk; + i2c->i2c_irq = &rk29_i2c_irq; + + ret = i2c_add_numbered_adapter(adap); + + return ret; +} + + diff --git a/drivers/i2c/busses/i2c-rk30-adapter.c b/drivers/i2c/busses/i2c-rk30-adapter.c new file mode 100755 index 000000000000..ea1e350c84f3 --- /dev/null +++ b/drivers/i2c/busses/i2c-rk30-adapter.c @@ -0,0 +1,69 @@ +/* drivers/i2c/busses/i2c-rk30-adapter.c + * + * Copyright (C) 2012 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include "i2c-rk30.h" + +static void rk30_i2c_set_clk(struct rk30_i2c *i2c, unsigned long scl_rate) +{ + return; +} +static void rk30_i2c_init_hw(struct rk30_i2c *i2c) +{ + return; +} + + +static irqreturn_t rk30_i2c_irq(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + + +static int rk30_i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + return 0; +} + +/* declare our i2c functionality */ +static u32 rk30_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING; +} + +/* i2c bus registration info */ + +static const struct i2c_algorithm rk30_i2c_algorithm = { + .master_xfer = rk30_i2c_xfer, + .functionality = rk30_i2c_func, +}; + +int i2c_add_rk30_adapter(struct i2c_adapter *adap) +{ + int ret = 0; + struct rk30_i2c *i2c = (struct rk30_i2c *)adap->algo_data; + + adap->algo = &rk30_i2c_algorithm; + adap->retries = 3; + + i2c->i2c_init_hw = &rk30_i2c_init_hw; + i2c->i2c_set_clk = &rk30_i2c_set_clk; + i2c->i2c_irq = &rk30_i2c_irq; + + ret = i2c_add_numbered_adapter(adap); + + return ret; +} + + diff --git a/drivers/i2c/busses/i2c-rk30.c b/drivers/i2c/busses/i2c-rk30.c new file mode 100755 index 000000000000..e738fbaabc22 --- /dev/null +++ b/drivers/i2c/busses/i2c-rk30.c @@ -0,0 +1,296 @@ +/* drivers/i2c/busses/i2c-rk30.c + * + * Copyright (C) 2012 ROCKCHIP, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include "i2c-rk30.h" + +#define TX_SETUP 1 //unit us + +#ifdef CONFIG_CPU_FREQ + +#define freq_to_i2c(_n) container_of(_n, struct rk30_i2c, freq_transition) + +static int rk30_i2c_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct rk30_i2c *i2c = freq_to_i2c(nb); + unsigned long flags; + int delta_f; + + delta_f = clk_get_rate(i2c->clk) - i2c->i2c_rate; + + if ((val == CPUFREQ_POSTCHANGE && delta_f < 0) || + (val == CPUFREQ_PRECHANGE && delta_f > 0)) + { + spin_lock_irqsave(&i2c->lock, flags); + i2c->i2c_set_clk(i2c, i2c->scl_rate); + spin_unlock_irqrestore(&i2c->lock, flags); + } + return 0; +} + +static inline int rk30_i2c_register_cpufreq(struct rk30_i2c *i2c) +{ + if (i2c->adap.nr != 0) + return 0; + i2c->freq_transition.notifier_call = rk30_i2c_cpufreq_transition; + + return cpufreq_register_notifier(&i2c->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +static inline void rk30_i2c_deregister_cpufreq(struct rk30_i2c *i2c) +{ + if (i2c->adap.nr != 0) + return; + cpufreq_unregister_notifier(&i2c->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} + +#else +static inline int rk30_i2c_register_cpufreq(struct rk30_i2c *i2c) +{ + return 0; +} + +static inline void rk30_i2c_deregister_cpufreq(struct rk30_i2c *i2c) +{ +} +#endif + +/* rk30_i2c_probe + * + * called by the bus driver when a suitable device is found +*/ + +static int rk30_i2c_probe(struct platform_device *pdev) +{ + struct rk30_i2c *i2c = NULL; + struct rk30_i2c_platform_data *pdata = NULL; + struct resource *res; + char name[5]; + int ret; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + if(pdata->io_init) + pdata->io_init(); + + i2c = kzalloc(sizeof(struct rk30_i2c), GFP_KERNEL); + if (!i2c) { + dev_err(&pdev->dev, "no memory for state\n"); + return -ENOMEM; + } + + strlcpy(i2c->adap.name, "rk30_i2c", sizeof(i2c->adap.name)); + i2c->adap.owner = THIS_MODULE; + i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + i2c->tx_setup = TX_SETUP; + + spin_lock_init(&i2c->lock); + init_waitqueue_head(&i2c->wait); + + /* find the clock and enable it */ + + i2c->dev = &pdev->dev; + i2c->clk = clk_get(&pdev->dev, "i2c"); + if (IS_ERR(i2c->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + ret = -ENOENT; + goto err_noclk; + } + + i2c_dbg(&pdev->dev, "clock source %p\n", i2c->clk); + + clk_enable(i2c->clk); + + /* map the registers */ + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "cannot find IO resource\n"); + ret = -ENOENT; + goto err_get_resource; + } + + i2c->ioarea = request_mem_region(res->start, resource_size(res), + pdev->name); + + if (i2c->ioarea == NULL) { + dev_err(&pdev->dev, "cannot request IO\n"); + ret = -ENXIO; + goto err_ioarea; + } + + i2c->regs = ioremap(res->start, resource_size(res)); + + if (i2c->regs == NULL) { + dev_err(&pdev->dev, "cannot map IO\n"); + ret = -ENXIO; + goto err_ioremap; + } + + i2c_dbg(&pdev->dev, "registers %p (%p, %p)\n", + i2c->regs, i2c->ioarea, res); + + /* setup info block for the i2c core */ + + i2c->adap.algo_data = i2c; + i2c->adap.dev.parent = &pdev->dev; + i2c->adap.nr = pdata->bus_num; + if(pdata->adap_type == I2C_RK29_ADAP) + ret = i2c_add_rk29_adapter(&i2c->adap); + else // I2C_RK30_ADAP + ret = i2c_add_rk30_adapter(&i2c->adap); + + if (ret < 0) { + dev_err(&pdev->dev, "failed to add adapter\n"); + goto err_add_adapter; + } + + /* find the IRQ for this unit (note, this relies on the init call to + * ensure no current IRQs pending + */ + + i2c->irq = ret = platform_get_irq(pdev, 0); + if (ret <= 0) { + dev_err(&pdev->dev, "cannot find IRQ\n"); + goto err_get_irq; + } + + ret = request_irq(i2c->irq, i2c->i2c_irq, IRQF_DISABLED, + dev_name(&pdev->dev), i2c); + + if (ret != 0) { + dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq); + goto err_request_irq; + } + + ret = rk30_i2c_register_cpufreq(i2c); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register cpufreq notifier\n"); + goto err_register_cpufreq; + } + + platform_set_drvdata(pdev, i2c); + + sprintf(name, "%s%d", "i2c", i2c->adap.nr); + i2c->is_div_from_arm[i2c->adap.nr] = pdata->is_div_from_arm; + if(i2c->is_div_from_arm[i2c->adap.nr]) + wake_lock_init(&i2c->idlelock[i2c->adap.nr], WAKE_LOCK_IDLE, name); + + i2c->i2c_init_hw(i2c); + dev_info(&pdev->dev, "%s: RK30 I2C adapter\n", dev_name(&i2c->adap.dev)); + return 0; +//err_none: +// rk30_i2c_deregister_cpufreq(i2c); +err_register_cpufreq: + free_irq(i2c->irq, i2c); +err_request_irq: +err_get_irq: + i2c_del_adapter(&i2c->adap); +err_add_adapter: + iounmap(i2c->regs); +err_ioremap: + kfree(i2c->ioarea); +err_ioarea: + release_resource(i2c->ioarea); +err_get_resource: + clk_put(i2c->clk); +err_noclk: + kfree(i2c); + return ret; +} + +/* rk30_i2c_remove + * + * called when device is removed from the bus +*/ + +static int rk30_i2c_remove(struct platform_device *pdev) +{ + struct rk30_i2c *i2c = platform_get_drvdata(pdev); + + rk30_i2c_deregister_cpufreq(i2c); + free_irq(i2c->irq, i2c); + i2c_del_adapter(&i2c->adap); + iounmap(i2c->regs); + kfree(i2c->ioarea); + release_resource(i2c->ioarea); + clk_put(i2c->clk); + kfree(i2c); + return 0; +} + +#ifdef CONFIG_PM +static int rk30_i2c_suspend_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rk30_i2c *i2c = platform_get_drvdata(pdev); + + i2c->suspended = 1; + + return 0; +} + +static int rk30_i2c_resume_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rk30_i2c *i2c = platform_get_drvdata(pdev); + + i2c->suspended = 0; + i2c->i2c_init_hw(i2c); + + return 0; +} + +static const struct dev_pm_ops rk30_i2c_dev_pm_ops = { + .suspend_noirq = rk30_i2c_suspend_noirq, + .resume_noirq = rk30_i2c_resume_noirq, +}; + +#define rk30_DEV_PM_OPS (&rk30_i2c_dev_pm_ops) +#else +#define rk30_DEV_PM_OPS NULL +#endif + +MODULE_DEVICE_TABLE(platform, rk30_driver_ids); + +static struct platform_driver rk30_i2c_driver = { + .probe = rk30_i2c_probe, + .remove = rk30_i2c_remove, + .driver = { + .owner = THIS_MODULE, + .name = "rk30_i2c", + .pm = rk30_DEV_PM_OPS, + }, +}; + +static int __init i2c_adap_init(void) +{ + return platform_driver_register(&rk30_i2c_driver); +} +subsys_initcall(i2c_adap_init); + +static void __exit i2c_adap_exit(void) +{ + platform_driver_unregister(&rk30_i2c_driver); +} +module_exit(i2c_adap_exit); + +MODULE_DESCRIPTION("Driver for RK30 I2C Bus"); +MODULE_AUTHOR("kfx, kfx@rock-chips.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-rk30.h b/drivers/i2c/busses/i2c-rk30.h new file mode 100755 index 000000000000..23f0751bac61 --- /dev/null +++ b/drivers/i2c/busses/i2c-rk30.h @@ -0,0 +1,79 @@ +#ifndef __RK30_I2C_H__ +#define __RK30_I2C_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if 0 +#define i2c_dbg(dev, format, arg...) \ + dev_printk(KERN_INFO , dev , format , ## arg) +#else +#define i2c_dbg(dev, format, arg...) +#endif + + +enum rk30_i2c_state { + STATE_IDLE, + STATE_START, + STATE_READ, + STATE_WRITE, + STATE_STOP +}; + +struct rk30_i2c { + spinlock_t lock; + wait_queue_head_t wait; + unsigned int suspended:1; + + struct i2c_msg *msg; + unsigned int msg_num; + unsigned int msg_idx; + unsigned int msg_ptr; + + unsigned int tx_setup; + unsigned int irq; + + enum rk30_i2c_state state; + unsigned long clkrate; + + void __iomem *regs; + struct clk *clk; + struct device *dev; + struct resource *ioarea; + struct i2c_adapter adap; + + unsigned long scl_rate; + unsigned long i2c_rate; + unsigned int addr; + + struct wake_lock idlelock[5]; + int is_div_from_arm[5]; + +#ifdef CONFIG_CPU_FREQ + struct notifier_block freq_transition; +#endif + + void (*i2c_init_hw)(struct rk30_i2c *); + void (*i2c_set_clk)(struct rk30_i2c *, unsigned long); + irqreturn_t (*i2c_irq)(int, void *); +}; + +int i2c_add_rk29_adapter(struct i2c_adapter *); +int i2c_add_rk30_adapter(struct i2c_adapter *); +#endif -- 2.34.1