From 5c622a5e538221a3f9d72277a19724dfc644af85 Mon Sep 17 00:00:00 2001 From: Mika Laitio Date: Tue, 20 Jan 2009 01:09:50 +0200 Subject: [PATCH] h63xx: pca9535 support. --- arch/arm/mach-omap1/Kconfig | 1 + drivers/i2c/chips/Kconfig | 10 + drivers/i2c/chips/Makefile | 1 + drivers/i2c/chips/pca9535.c | 419 ++++++++++++++++++++++++++++ drivers/video/omap/lcd_h6300.c | 5 +- include/asm-arm/arch-omap/pca9535.h | 39 +++ 6 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 drivers/i2c/chips/pca9535.c create mode 100644 include/asm-arm/arch-omap/pca9535.h diff --git a/arch/arm/mach-omap1/Kconfig b/arch/arm/mach-omap1/Kconfig index 1aaac427c30..48643a001ec 100644 --- a/arch/arm/mach-omap1/Kconfig +++ b/arch/arm/mach-omap1/Kconfig @@ -98,6 +98,7 @@ config MACH_OMAP_H6300 bool "HP iPAQ h6300 series" depends on ARCH_OMAP1 && ARCH_OMAP15XX select I2C + select PCA9535 help HP iPAQ h6315, h6340 and h6365 devices that are based on the OMAP 1510. Say Y here if you have such a device. diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index 946cc210e80..f77027d9897 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -46,6 +46,16 @@ config SENSORS_PCF8574 This driver can also be built as a module. If so, the module will be called pcf8574. +config PCA9535 + tristate "Philips PCA9535 16-bit I/O port" + depends on I2C + help + If you say yes here you get support for the Philips PCA9535 + 16-bit I/O port. + + This driver can also be built as a module. If so, the module + will be called pca9535. + config SENSORS_PCA9539 tristate "Philips PCA9539 16-bit I/O port" depends on I2C && EXPERIMENTAL diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 316b67d9a73..0ccd0b48a31 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_SENSORS_DS1374) += ds1374.o obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o obj-$(CONFIG_SENSORS_MAX6875) += max6875.o obj-$(CONFIG_SENSORS_M41T00) += m41t00.o +obj-$(CONFIG_PCA9535) += pca9535.o obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o diff --git a/drivers/i2c/chips/pca9535.c b/drivers/i2c/chips/pca9535.c new file mode 100644 index 00000000000..26d2748d2b8 --- /dev/null +++ b/drivers/i2c/chips/pca9535.c @@ -0,0 +1,419 @@ +/* + * drivers/i2c/chips/pca9535.c + * + * Driver for Philips PCA9535 16-bit low power I/O port with interrupt. + * Tested with OMAP-1510 based iPAQ h63xx series of mobile phones. + * (h6315, h6340 and h6365) + * + * Copyright (C) 2009 Mika Laitio + * Copyright (C) 2009 Husam Senussi + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Copyright (C) 2005 Husam Senussi + * Framework based on Pawel Kolodziejski's pca9535 driver in + * handheld.org's 2.6.13 kernel. Driver updated by Mika Laitio. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +EXPORT_SYMBOL(pca9535_gpio_read); +EXPORT_SYMBOL(pca9535_gpio_write); +EXPORT_SYMBOL(pca9535_gpio_direction); + +static int pca9535_attach_adapter(struct i2c_adapter *adapter); +static int pca9535_detach_client(struct i2c_client *client); +static int pca9535_attach(struct i2c_adapter *adapter, int address, int zero_or_minus_one); +static u32 pca9535_read_reg(struct i2c_client *client, u8 regaddr); +static void pca9535_write_reg(struct i2c_client *client, u8 regaddr, u16 param); + +enum pca9535_cmd +{ + PCA9535_INPUT_0 = 0, + PCA9535_INPUT_1 = 1, + PCA9535_OUTPUT_0 = 2, + PCA9535_OUTPUT_1 = 3, + PCA9535_INVERT_0 = 4, + PCA9535_INVERT_1 = 5, + PCA9535_DIRECTION_0 = 6, + PCA9535_DIRECTION_1 = 7, +}; + +struct pca9535_data { + struct semaphore lock; + struct i2c_client client; +}; + +static struct i2c_driver pca9535_driver = { + .driver = { + .name = "pca9535", + }, + .attach_adapter = pca9535_attach_adapter, + .detach_client = pca9535_detach_client, +}; + +static struct i2c_client *pca9535_i2c_client = NULL; +static struct pca9535_data pca9535_inited; + +static unsigned short normal_i2c[] = { 0x20, I2C_CLIENT_END }; + +#define DRIVER_VERSION "20 OCT 2005" +#define DRIVER_NAME "PCA9535" + +/* + * sysfs callback function. + */ +static ssize_t pca9535_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *psa = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + return sprintf(buf, "%02X\n", (pca9535_read_reg(client, psa->index) >> 8)); +} + +/* + * sysfs callback function. + */ +static ssize_t pca9535_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *psa = to_sensor_dev_attr(attr); + struct i2c_client *client = to_i2c_client(dev); + unsigned long val = simple_strtoul(buf, NULL, 0); + unsigned long old = pca9535_read_reg(client, psa->index); + + if (val > 0xff) + return -EINVAL; + + val = (old & 0xff) | (val << 8); + pca9535_write_reg(client, psa->index, val); + return count; +} + +#define PCA9535_ENTRY_RO(name, cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, pca9535_show, NULL, cmd_idx) + +#define PCA9535_ENTRY_RW(name, cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO | S_IWUSR, pca9535_show, \ + pca9535_store, cmd_idx) + +PCA9535_ENTRY_RO(input0, PCA9535_INPUT_0); +PCA9535_ENTRY_RO(input1, PCA9535_INPUT_1); +PCA9535_ENTRY_RW(output0, PCA9535_OUTPUT_0); +PCA9535_ENTRY_RW(output1, PCA9535_OUTPUT_1); +PCA9535_ENTRY_RW(invert0, PCA9535_INVERT_0); +PCA9535_ENTRY_RW(invert1, PCA9535_INVERT_1); +PCA9535_ENTRY_RW(direction0, PCA9535_DIRECTION_0); +PCA9535_ENTRY_RW(direction1, PCA9535_DIRECTION_1); + +static struct attribute *pca9535_attributes[] = { + &sensor_dev_attr_input0.dev_attr.attr, + &sensor_dev_attr_input1.dev_attr.attr, + &sensor_dev_attr_output0.dev_attr.attr, + &sensor_dev_attr_output1.dev_attr.attr, + &sensor_dev_attr_invert0.dev_attr.attr, + &sensor_dev_attr_invert1.dev_attr.attr, + &sensor_dev_attr_direction0.dev_attr.attr, + &sensor_dev_attr_direction1.dev_attr.attr, + NULL +}; + +static struct attribute_group pca9535_defattr_group = { + .attrs = pca9535_attributes, +}; +//End of sysfs management code. + +I2C_CLIENT_INSMOD; + +u32 pca9535_read_input(void) +{ + return pca9535_read_reg(pca9535_i2c_client, 0); +} +EXPORT_SYMBOL(pca9535_read_input); + +void pca9535_write_output(u16 param) +{ + pca9535_write_reg(pca9535_i2c_client, 2, param); +} +EXPORT_SYMBOL(pca9535_write_output); + +void pca9535_set_dir(u16 param) +{ + pca9535_write_reg(pca9535_i2c_client, 6, param); +} +EXPORT_SYMBOL(pca9535_set_dir); + +static int pca9535_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, pca9535_attach); +} + +static int pca9535_attach(struct i2c_adapter *adapter, int address, int zero_or_minus_one) +{ + struct i2c_client *new_client; + int err = 0; + + new_client = &(pca9535_inited.client); + i2c_set_clientdata(new_client, 0); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &pca9535_driver; + strcpy(new_client->name, DRIVER_NAME); + + if ((err = i2c_attach_client(new_client))) + goto exit_free; + + pca9535_i2c_client = new_client; + + init_MUTEX(&pca9535_inited.lock); + i2c_set_clientdata(pca9535_i2c_client, &pca9535_inited); + + sysfs_create_group(&pca9535_i2c_client->dev.kobj, &pca9535_defattr_group); + + printk("pca9535_attach() ok, address = %d, zero_or_minus_one = %d\n", address, zero_or_minus_one); + return 0; + +exit_free: + printk("pca9535_attach() failed, error code = %d\n", err); + return err; +} + +static int pca9535_detach_client(struct i2c_client *client) +{ + int err; + + if ((err = i2c_detach_client(client))) { + dev_err(&client->dev, "Client deregistration failed, client not detached.\n"); + return err; + } + pca9535_i2c_client = NULL; + + return 0; +} + +static int __init pca9535_init(void) +{ + return i2c_add_driver(&pca9535_driver); +} + +static void __exit pca9535_exit(void) +{ + i2c_del_driver(&pca9535_driver); +} + +/* + * Reads the value of GPIO available via I2C. + */ +int pca9535_gpio_read(int gpio){ + unsigned char reg = 0; + unsigned long val = 0; + + printk("9535_gpio_read() called\n"); + if(!pca9535_i2c_client) + return -ENODEV; + + if(gpio < GPIO0 || gpio > GPIO17) + return -EINVAL; + + if(gpio >= GPIO0 && gpio <= GPIO7){ + reg = PCA9535_INPUT_0; + gpio -= GPIO0; + }else if(gpio >= GPIO8 && gpio <= GPIO17){ + reg = PCA9535_INPUT_1; + gpio -= GPIO8; + } + + down(&pca9535_inited.lock); + + // Read the existing values first + val = pca9535_read_reg(pca9535_i2c_client, reg) >> 8; + val = (val >> gpio) & 0x01; + + up(&pca9535_inited.lock); + + return val; +} + +/* + * Set the value of I2C GPIO. + */ +int pca9535_gpio_write(int gpio, unsigned char value){ + unsigned char in_reg = 0; + unsigned char out_reg = 0; + unsigned long val = 0; + unsigned long old = 0; + int ret = 0; + + if(!pca9535_i2c_client) + return -ENODEV; + + if(gpio < GPIO0 || gpio > GPIO17) + return -EINVAL; + + if(gpio >= GPIO0 && gpio <= GPIO7){ + in_reg = PCA9535_INPUT_0; + out_reg = PCA9535_OUTPUT_0; + gpio -= GPIO0; + }else if(gpio >= GPIO8 && gpio <= GPIO17){ + in_reg = PCA9535_INPUT_1; + out_reg = PCA9535_OUTPUT_1; + gpio -= GPIO8; + } + + down(&pca9535_inited.lock); + + // Read the existing values first + val = pca9535_read_reg(pca9535_i2c_client, in_reg); + old = val >> 8; + + switch(value){ + case LOW: + old |= (1 << gpio); + break; + case HI: + old &= ~(1 << gpio); + break; + default: + ret = -EINVAL; + goto error; + } + + val = (val & 0xff) | (old << 8); + + // write the values back to the register + pca9535_write_reg(pca9535_i2c_client, out_reg, val); +error: + + up(&pca9535_inited.lock); + return ret; +} + +/* + * Set the direction of I2C GPIO. + */ +int pca9535_gpio_direction(int gpio, unsigned char direction){ + unsigned char reg = 0; + unsigned long val = 0; + unsigned long old = 0; + int ret = 0; + + if(!pca9535_i2c_client) + return -ENODEV; + + if(gpio < GPIO0 || gpio > GPIO17) + return -EINVAL; + + if(gpio >= GPIO0 && gpio <= GPIO7){ + reg = PCA9535_DIRECTION_0; + gpio -= GPIO0; + }else if(gpio >= GPIO8 && gpio <= GPIO17){ + reg = PCA9535_DIRECTION_1; + gpio -= GPIO8; + } + + down(&pca9535_inited.lock); + + // Read the existing values first + old = pca9535_read_reg(pca9535_i2c_client, reg); + val = old >> 8; + + switch(direction){ + case GPIO_INPUT: + val |= (1 << gpio); + break; + case GPIO_OUTPUT: + val &= ~(1 << gpio); + break; + default: + ret = -EINVAL; + goto error; + } + + val = (old & 0xff) | (val << 8); + + // write the values back to the register + pca9535_write_reg(pca9535_i2c_client, reg, val); +error: + + up(&pca9535_inited.lock); + return ret; +} + +static u32 pca9535_read_reg(struct i2c_client *client, u8 regaddr) +{ + char buffer[3]; + int r; + u32 data; + + buffer[0] = regaddr; + buffer[1] = 0; + buffer[2] = 0; + + r = i2c_master_send(client, buffer, 1); + if (r != 1) { + printk(KERN_ERR "pca9535: read failed, status %d\n", r); + return 0xffffffff; + } + + r = i2c_master_recv(client, buffer, 3); + if (r != 3) { + printk(KERN_ERR "pca9535: read failed, status %d\n", r); + return 0xffffffff; + } + + data = buffer[1]; + data |= buffer[2] << 8; + //printk(KERN_ERR "%s: reading %x in %x\n", __FUNCTION__, data, regaddr); + + return data; +} + +static void pca9535_write_reg(struct i2c_client *client, u8 regaddr, u16 data) +{ + char buffer[3]; + int r; + + //printk(KERN_ERR "%s: writing %x in %x\n", __FUNCTION__, data, regaddr); + buffer[0] = regaddr; + buffer[1] = data >> 8; + buffer[2] = data & 0xff; + + r = i2c_master_send(client, buffer, 3); + if (r != 3) { + printk(KERN_ERR "pca9535: write failed, status %d\n", r); + } +} + +MODULE_AUTHOR("Husam Senussi "); +MODULE_DESCRIPTION("PCA9535 driver"); +MODULE_LICENSE("GPL"); + +module_init(pca9535_init); +module_exit(pca9535_exit); diff --git a/drivers/video/omap/lcd_h6300.c b/drivers/video/omap/lcd_h6300.c index d476fe69f4b..9511339d983 100644 --- a/drivers/video/omap/lcd_h6300.c +++ b/drivers/video/omap/lcd_h6300.c @@ -24,8 +24,9 @@ #include #include -#include +#include +#include #include /* #define OMAPFB_DBG 1 */ @@ -123,12 +124,14 @@ static int h6300_panel_remove(struct platform_device *pdev) static int h6300_panel_suspend(struct platform_device *pdev, pm_message_t mesg) { DBGENTER(1); + pca9535_gpio_write(GPIO3, HI); return 0; } static int h6300_panel_resume(struct platform_device *pdev) { DBGENTER(1); + pca9535_gpio_write(GPIO3, LOW); return 0; } diff --git a/include/asm-arm/arch-omap/pca9535.h b/include/asm-arm/arch-omap/pca9535.h new file mode 100644 index 00000000000..edf8f54882c --- /dev/null +++ b/include/asm-arm/arch-omap/pca9535.h @@ -0,0 +1,39 @@ +#ifndef _PCA9535_H +#define _PCA9535_H + +enum pca9535_gpios { + GPIO0 = 0, + GPIO1 = 1, + GPIO2 = 2, + GPIO3 = 3, + GPIO4 = 4, + GPIO5 = 5, + GPIO6 = 6, + GPIO7 = 7, + GPIO8 = 8, + GPIO9 = 9, + GPIO10 = 10, + GPIO11 = 11, + GPIO12 = 12, + GPIO13 = 13, + GPIO14 = 14, + GPIO15 = 15, + GPIO16 = 16, + GPIO17 = 17 +}; + +enum gpio_values { + HI = 0, + LOW = 1 +}; + +enum gpio_direction { + GPIO_INPUT = 0, + GPIO_OUTPUT = 1 +}; + +extern int pca9535_gpio_read(int gpio); +extern int pca9535_gpio_write(int gpio, unsigned char val); +extern int pca9535_gpio_direction(int gpio, unsigned char direction); + +#endif -- 2.41.1