/* * 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);