diff options
Diffstat (limited to 'drivers/mfd/88pm860x-i2c.c')
-rw-r--r-- | drivers/mfd/88pm860x-i2c.c | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c new file mode 100644 index 000000000000..c37e12bf3004 --- /dev/null +++ b/drivers/mfd/88pm860x-i2c.c @@ -0,0 +1,236 @@ +/* + * I2C driver for Marvell 88PM860x + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/mfd/88pm860x.h> + +static inline int pm860x_read_device(struct i2c_client *i2c, + int reg, int bytes, void *dest) +{ + unsigned char data; + int ret; + + data = (unsigned char)reg; + ret = i2c_master_send(i2c, &data, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(i2c, dest, bytes); + if (ret < 0) + return ret; + return 0; +} + +static inline int pm860x_write_device(struct i2c_client *i2c, + int reg, int bytes, void *src) +{ + unsigned char buf[bytes + 1]; + int ret; + + buf[0] = (unsigned char)reg; + memcpy(&buf[1], src, bytes); + + ret = i2c_master_send(i2c, buf, bytes + 1); + if (ret < 0) + return ret; + return 0; +} + +int pm860x_reg_read(struct i2c_client *i2c, int reg) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + unsigned char data; + int ret; + + mutex_lock(&chip->io_lock); + ret = pm860x_read_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + if (ret < 0) + return ret; + else + return (int)data; +} +EXPORT_SYMBOL(pm860x_reg_read); + +int pm860x_reg_write(struct i2c_client *i2c, int reg, + unsigned char data) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = pm860x_write_device(i2c, reg, 1, &data); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm860x_reg_write); + +int pm860x_bulk_read(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = pm860x_read_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm860x_bulk_read); + +int pm860x_bulk_write(struct i2c_client *i2c, int reg, + int count, unsigned char *buf) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&chip->io_lock); + ret = pm860x_write_device(i2c, reg, count, buf); + mutex_unlock(&chip->io_lock); + + return ret; +} +EXPORT_SYMBOL(pm860x_bulk_write); + +int pm860x_set_bits(struct i2c_client *i2c, int reg, + unsigned char mask, unsigned char data) +{ + struct pm860x_chip *chip = i2c_get_clientdata(i2c); + unsigned char value; + int ret; + + mutex_lock(&chip->io_lock); + ret = pm860x_read_device(i2c, reg, 1, &value); + if (ret < 0) + goto out; + value &= ~mask; + value |= data; + ret = pm860x_write_device(i2c, reg, 1, &value); +out: + mutex_unlock(&chip->io_lock); + return ret; +} +EXPORT_SYMBOL(pm860x_set_bits); + + +static const struct i2c_device_id pm860x_id_table[] = { + { "88PM860x", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pm860x_id_table); + +static int verify_addr(struct i2c_client *i2c) +{ + unsigned short addr_8607[] = {0x30, 0x34}; + unsigned short addr_8606[] = {0x10, 0x11}; + int size, i; + + if (i2c == NULL) + return 0; + size = ARRAY_SIZE(addr_8606); + for (i = 0; i < size; i++) { + if (i2c->addr == *(addr_8606 + i)) + return CHIP_PM8606; + } + size = ARRAY_SIZE(addr_8607); + for (i = 0; i < size; i++) { + if (i2c->addr == *(addr_8607 + i)) + return CHIP_PM8607; + } + return 0; +} + +static int __devinit pm860x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pm860x_platform_data *pdata = client->dev.platform_data; + struct pm860x_chip *chip; + + if (!pdata) { + pr_info("No platform data in %s!\n", __func__); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + + chip->id = verify_addr(client); + chip->client = client; + i2c_set_clientdata(client, chip); + chip->dev = &client->dev; + mutex_init(&chip->io_lock); + dev_set_drvdata(chip->dev, chip); + + /* + * Both client and companion client shares same platform driver. + * Driver distinguishes them by pdata->companion_addr. + * pdata->companion_addr is only assigned if companion chip exists. + * At the same time, the companion_addr shouldn't equal to client + * address. + */ + if (pdata->companion_addr && (pdata->companion_addr != client->addr)) { + chip->companion_addr = pdata->companion_addr; + chip->companion = i2c_new_dummy(chip->client->adapter, + chip->companion_addr); + i2c_set_clientdata(chip->companion, chip); + } + + pm860x_device_init(chip, pdata); + return 0; +} + +static int __devexit pm860x_remove(struct i2c_client *client) +{ + struct pm860x_chip *chip = i2c_get_clientdata(client); + + pm860x_device_exit(chip); + i2c_unregister_device(chip->companion); + i2c_set_clientdata(chip->companion, NULL); + i2c_set_clientdata(chip->client, NULL); + kfree(chip); + return 0; +} + +static struct i2c_driver pm860x_driver = { + .driver = { + .name = "88PM860x", + .owner = THIS_MODULE, + }, + .probe = pm860x_probe, + .remove = __devexit_p(pm860x_remove), + .id_table = pm860x_id_table, +}; + +static int __init pm860x_i2c_init(void) +{ + int ret; + ret = i2c_add_driver(&pm860x_driver); + if (ret != 0) + pr_err("Failed to register 88PM860x I2C driver: %d\n", ret); + return ret; +} +subsys_initcall(pm860x_i2c_init); + +static void __exit pm860x_i2c_exit(void) +{ + i2c_del_driver(&pm860x_driver); +} +module_exit(pm860x_i2c_exit); + +MODULE_DESCRIPTION("I2C Driver for Marvell 88PM860x"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); |