From c01b0831057381c7f6e0bfb3634bac8c5f7fb256 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 22 Apr 2008 22:16:46 +0200 Subject: i2c-algo-pca: Extend for future drivers The separation between algorithm and adapter was unsharp at places. This was partly hidden by the fact, that the ISA-driver allowed just one instance and had all private data in static variables. This patch makes neccessary preparations to add a platform driver on top of the algorithm, while still supporting ISA. Note: Due to lack of hardware, the ISA-driver could not be tested except that it builds. Concerning the core struct i2c_algo_pca_data: - A private data field was added, all hardware dependant data may go here. Similar to other algorithms, now a pointer to this data is passed to the adapter's functions. In order to make as less changes as possible to the ISA-driver, it leaves the private data empty and still only uses its static variables. - A "reset_chip" function pointer was added; such a functionality must come from the adapter, not the algorithm. - use a variable "i2c_clock" instead of a function pointer "get_clock", allowing for write access to a default in case a wrong value was supplied. In the algorithm-file: - move "i2c-pca-algo.h" into "linux/i2c-algo-pca.h" - now using per_instance timeout values (i2c_adap->timeout) - error messages specify the device, not only the driver name - restructure initialization to easily support "i2c_add_numbered_adapter" - drop "retries" and "own" (i2c address) as they were unused (The state-machine for I2C-communication was not touched.) In the ISA-driver: - adapt to new algorithm Signed-off-by: Wolfram Sang Signed-off-by: Jean Delvare --- include/linux/i2c-algo-pca.h | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/include/linux/i2c-algo-pca.h b/include/linux/i2c-algo-pca.h index fce47c051bb1..adcb3dc7ac26 100644 --- a/include/linux/i2c-algo-pca.h +++ b/include/linux/i2c-algo-pca.h @@ -1,14 +1,41 @@ #ifndef _LINUX_I2C_ALGO_PCA_H #define _LINUX_I2C_ALGO_PCA_H +/* Clock speeds for the bus */ +#define I2C_PCA_CON_330kHz 0x00 +#define I2C_PCA_CON_288kHz 0x01 +#define I2C_PCA_CON_217kHz 0x02 +#define I2C_PCA_CON_146kHz 0x03 +#define I2C_PCA_CON_88kHz 0x04 +#define I2C_PCA_CON_59kHz 0x05 +#define I2C_PCA_CON_44kHz 0x06 +#define I2C_PCA_CON_36kHz 0x07 + +/* PCA9564 registers */ +#define I2C_PCA_STA 0x00 /* STATUS Read Only */ +#define I2C_PCA_TO 0x00 /* TIMEOUT Write Only */ +#define I2C_PCA_DAT 0x01 /* DATA Read/Write */ +#define I2C_PCA_ADR 0x02 /* OWN ADR Read/Write */ +#define I2C_PCA_CON 0x03 /* CONTROL Read/Write */ + +#define I2C_PCA_CON_AA 0x80 /* Assert Acknowledge */ +#define I2C_PCA_CON_ENSIO 0x40 /* Enable */ +#define I2C_PCA_CON_STA 0x20 /* Start */ +#define I2C_PCA_CON_STO 0x10 /* Stop */ +#define I2C_PCA_CON_SI 0x08 /* Serial Interrupt */ +#define I2C_PCA_CON_CR 0x07 /* Clock Rate (MASK) */ + struct i2c_algo_pca_data { - int (*get_own) (struct i2c_algo_pca_data *adap); /* Obtain own address */ - int (*get_clock) (struct i2c_algo_pca_data *adap); - void (*write_byte) (struct i2c_algo_pca_data *adap, int reg, int val); - int (*read_byte) (struct i2c_algo_pca_data *adap, int reg); - int (*wait_for_interrupt) (struct i2c_algo_pca_data *adap); + void *data; /* private low level data */ + void (*write_byte) (void *data, int reg, int val); + int (*read_byte) (void *data, int reg); + int (*wait_for_completion) (void *data); + void (*reset_chip) (void *data); + /* i2c_clock values are defined in linux/i2c-algo-pca.h */ + unsigned int i2c_clock; }; int i2c_pca_add_bus(struct i2c_adapter *); +int i2c_pca_add_numbered_bus(struct i2c_adapter *); #endif /* _LINUX_I2C_ALGO_PCA_H */ -- cgit From 244fbbb81c46eefcc5f3a9cc37c4668f9d8ff801 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 22 Apr 2008 22:16:46 +0200 Subject: i2c: Add platform driver on top of the new pca-algorithm Tested on a blackfin. Signed-off-by: Wolfram Sang Signed-off-by: Jean Delvare --- drivers/i2c/busses/Kconfig | 15 +- drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-pca-platform.c | 298 ++++++++++++++++++++++++++++++++++ include/linux/i2c-pca-platform.h | 12 ++ 4 files changed, 324 insertions(+), 2 deletions(-) create mode 100644 drivers/i2c/busses/i2c-pca-platform.c create mode 100644 include/linux/i2c-pca-platform.h (limited to 'include/linux') diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index b04c99580d0d..51de2ccc1519 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -632,8 +632,8 @@ config I2C_PCA_ISA select I2C_ALGOPCA default n help - This driver supports ISA boards using the Philips PCA 9564 - Parallel bus to I2C bus controller + This driver supports ISA boards using the Philips PCA9564 + parallel bus to I2C bus controller. This driver can also be built as a module. If so, the module will be called i2c-pca-isa. @@ -643,6 +643,17 @@ config I2C_PCA_ISA delays when I2C/SMBus chip drivers are loaded (e.g. at boot time). If unsure, say N. +config I2C_PCA_PLATFORM + tristate "PCA9564 as platform device" + select I2C_ALGOPCA + default n + help + This driver supports a memory mapped Philips PCA9564 + parallel bus to I2C bus controller. + + This driver can also be built as a module. If so, the module + will be called i2c-pca-platform. + config I2C_MV64XXX tristate "Marvell mv64xxx I2C Controller" depends on (MV64X60 || PLAT_ORION) && EXPERIMENTAL diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index ea7068f1eb6b..378d5c07e048 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o +obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o obj-$(CONFIG_I2C_PNX) += i2c-pnx.o diff --git a/drivers/i2c/busses/i2c-pca-platform.c b/drivers/i2c/busses/i2c-pca-platform.c new file mode 100644 index 000000000000..9d75f51e8f0e --- /dev/null +++ b/drivers/i2c/busses/i2c-pca-platform.c @@ -0,0 +1,298 @@ +/* + * i2c_pca_platform.c + * + * Platform driver for the PCA9564 I2C controller. + * + * Copyright (C) 2008 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define res_len(r) ((r)->end - (r)->start + 1) + +struct i2c_pca_pf_data { + void __iomem *reg_base; + int irq; /* if 0, use polling */ + int gpio; + wait_queue_head_t wait; + struct i2c_adapter adap; + struct i2c_algo_pca_data algo_data; + unsigned long io_base; + unsigned long io_size; +}; + +/* Read/Write functions for different register alignments */ + +static int i2c_pca_pf_readbyte8(void *pd, int reg) +{ + struct i2c_pca_pf_data *i2c = pd; + return ioread8(i2c->reg_base + reg); +} + +static int i2c_pca_pf_readbyte16(void *pd, int reg) +{ + struct i2c_pca_pf_data *i2c = pd; + return ioread8(i2c->reg_base + reg * 2); +} + +static int i2c_pca_pf_readbyte32(void *pd, int reg) +{ + struct i2c_pca_pf_data *i2c = pd; + return ioread8(i2c->reg_base + reg * 4); +} + +static void i2c_pca_pf_writebyte8(void *pd, int reg, int val) +{ + struct i2c_pca_pf_data *i2c = pd; + iowrite8(val, i2c->reg_base + reg); +} + +static void i2c_pca_pf_writebyte16(void *pd, int reg, int val) +{ + struct i2c_pca_pf_data *i2c = pd; + iowrite8(val, i2c->reg_base + reg * 2); +} + +static void i2c_pca_pf_writebyte32(void *pd, int reg, int val) +{ + struct i2c_pca_pf_data *i2c = pd; + iowrite8(val, i2c->reg_base + reg * 4); +} + + +static int i2c_pca_pf_waitforcompletion(void *pd) +{ + struct i2c_pca_pf_data *i2c = pd; + int ret = 0; + + if (i2c->irq) { + ret = wait_event_interruptible(i2c->wait, + i2c->algo_data.read_byte(i2c, I2C_PCA_CON) + & I2C_PCA_CON_SI); + } else { + /* + * Do polling... + * XXX: Could get stuck in extreme cases! + * Maybe add timeout, but using irqs is preferred anyhow. + */ + while ((i2c->algo_data.read_byte(i2c, I2C_PCA_CON) + & I2C_PCA_CON_SI) == 0) + udelay(100); + } + + return ret; +} + +static void i2c_pca_pf_dummyreset(void *pd) +{ + struct i2c_pca_pf_data *i2c = pd; + printk(KERN_WARNING "%s: No reset-pin found. Chip may get stuck!\n", + i2c->adap.name); +} + +static void i2c_pca_pf_resetchip(void *pd) +{ + struct i2c_pca_pf_data *i2c = pd; + + gpio_set_value(i2c->gpio, 0); + ndelay(100); + gpio_set_value(i2c->gpio, 1); +} + +static irqreturn_t i2c_pca_pf_handler(int this_irq, void *dev_id) +{ + struct i2c_pca_pf_data *i2c = dev_id; + + if ((i2c->algo_data.read_byte(i2c, I2C_PCA_CON) & I2C_PCA_CON_SI) == 0) + return IRQ_NONE; + + wake_up_interruptible(&i2c->wait); + + return IRQ_HANDLED; +} + + +static int __devinit i2c_pca_pf_probe(struct platform_device *pdev) +{ + struct i2c_pca_pf_data *i2c; + struct resource *res; + struct i2c_pca9564_pf_platform_data *platform_data = + pdev->dev.platform_data; + int ret = 0; + int irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + /* If irq is 0, we do polling. */ + + if (res == NULL) { + ret = -ENODEV; + goto e_print; + } + + if (!request_mem_region(res->start, res_len(res), res->name)) { + ret = -ENOMEM; + goto e_print; + } + + i2c = kzalloc(sizeof(struct i2c_pca_pf_data), GFP_KERNEL); + if (!i2c) { + ret = -ENOMEM; + goto e_alloc; + } + + init_waitqueue_head(&i2c->wait); + + i2c->reg_base = ioremap(res->start, res_len(res)); + if (!i2c->reg_base) { + ret = -EIO; + goto e_remap; + } + i2c->io_base = res->start; + i2c->io_size = res_len(res); + i2c->irq = irq; + + i2c->adap.nr = pdev->id >= 0 ? pdev->id : 0; + i2c->adap.owner = THIS_MODULE; + snprintf(i2c->adap.name, sizeof(i2c->adap.name), "PCA9564 at 0x%08lx", + (unsigned long) res->start); + i2c->adap.algo_data = &i2c->algo_data; + i2c->adap.dev.parent = &pdev->dev; + i2c->adap.timeout = platform_data->timeout; + + i2c->algo_data.i2c_clock = platform_data->i2c_clock_speed; + i2c->algo_data.data = i2c; + + switch (res->flags & IORESOURCE_MEM_TYPE_MASK) { + case IORESOURCE_MEM_32BIT: + i2c->algo_data.write_byte = i2c_pca_pf_writebyte32; + i2c->algo_data.read_byte = i2c_pca_pf_readbyte32; + break; + case IORESOURCE_MEM_16BIT: + i2c->algo_data.write_byte = i2c_pca_pf_writebyte16; + i2c->algo_data.read_byte = i2c_pca_pf_readbyte16; + break; + case IORESOURCE_MEM_8BIT: + default: + i2c->algo_data.write_byte = i2c_pca_pf_writebyte8; + i2c->algo_data.read_byte = i2c_pca_pf_readbyte8; + break; + } + + i2c->algo_data.wait_for_completion = i2c_pca_pf_waitforcompletion; + + i2c->gpio = platform_data->gpio; + i2c->algo_data.reset_chip = i2c_pca_pf_dummyreset; + + /* Use gpio_is_valid() when in mainline */ + if (i2c->gpio > -1) { + ret = gpio_request(i2c->gpio, i2c->adap.name); + if (ret == 0) { + gpio_direction_output(i2c->gpio, 1); + i2c->algo_data.reset_chip = i2c_pca_pf_resetchip; + } else { + printk(KERN_WARNING "%s: Registering gpio failed!\n", + i2c->adap.name); + i2c->gpio = ret; + } + } + + if (irq) { + ret = request_irq(irq, i2c_pca_pf_handler, + IRQF_TRIGGER_FALLING, i2c->adap.name, i2c); + if (ret) + goto e_reqirq; + } + + if (i2c_pca_add_numbered_bus(&i2c->adap) < 0) { + ret = -ENODEV; + goto e_adapt; + } + + platform_set_drvdata(pdev, i2c); + + printk(KERN_INFO "%s registered.\n", i2c->adap.name); + + return 0; + +e_adapt: + if (irq) + free_irq(irq, i2c); +e_reqirq: + if (i2c->gpio > -1) + gpio_free(i2c->gpio); + + iounmap(i2c->reg_base); +e_remap: + kfree(i2c); +e_alloc: + release_mem_region(res->start, res_len(res)); +e_print: + printk(KERN_ERR "Registering PCA9564 FAILED! (%d)\n", ret); + return ret; +} + +static int __devexit i2c_pca_pf_remove(struct platform_device *pdev) +{ + struct i2c_pca_pf_data *i2c = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + i2c_del_adapter(&i2c->adap); + + if (i2c->irq) + free_irq(i2c->irq, i2c); + + if (i2c->gpio > -1) + gpio_free(i2c->gpio); + + iounmap(i2c->reg_base); + release_mem_region(i2c->io_base, i2c->io_size); + kfree(i2c); + + return 0; +} + +static struct platform_driver i2c_pca_pf_driver = { + .probe = i2c_pca_pf_probe, + .remove = __devexit_p(i2c_pca_pf_remove), + .driver = { + .name = "i2c-pca-platform", + .owner = THIS_MODULE, + }, +}; + +static int __init i2c_pca_pf_init(void) +{ + return platform_driver_register(&i2c_pca_pf_driver); +} + +static void __exit i2c_pca_pf_exit(void) +{ + platform_driver_unregister(&i2c_pca_pf_driver); +} + +MODULE_AUTHOR("Wolfram Sang "); +MODULE_DESCRIPTION("I2C-PCA9564 platform driver"); +MODULE_LICENSE("GPL"); + +module_init(i2c_pca_pf_init); +module_exit(i2c_pca_pf_exit); + diff --git a/include/linux/i2c-pca-platform.h b/include/linux/i2c-pca-platform.h new file mode 100644 index 000000000000..3d191873f2d1 --- /dev/null +++ b/include/linux/i2c-pca-platform.h @@ -0,0 +1,12 @@ +#ifndef I2C_PCA9564_PLATFORM_H +#define I2C_PCA9564_PLATFORM_H + +struct i2c_pca9564_pf_platform_data { + int gpio; /* pin to reset chip. driver will work when + * not supplied (negative value), but it + * cannot exit some error conditions then */ + int i2c_clock_speed; /* values are defined in linux/i2c-algo-pca.h */ + int timeout; /* timeout = this value * 10us */ +}; + +#endif /* I2C_PCA9564_PLATFORM_H */ -- cgit