1bbd51b1fSHaojian Zhuang /* 253dbab7aSHaojian Zhuang * I2C driver for Marvell 88PM860x 3bbd51b1fSHaojian Zhuang * 4bbd51b1fSHaojian Zhuang * Copyright (C) 2009 Marvell International Ltd. 5bbd51b1fSHaojian Zhuang * Haojian Zhuang <haojian.zhuang@marvell.com> 6bbd51b1fSHaojian Zhuang * 7bbd51b1fSHaojian Zhuang * This program is free software; you can redistribute it and/or modify 8bbd51b1fSHaojian Zhuang * it under the terms of the GNU General Public License version 2 as 9bbd51b1fSHaojian Zhuang * published by the Free Software Foundation. 10bbd51b1fSHaojian Zhuang */ 11bbd51b1fSHaojian Zhuang #include <linux/kernel.h> 12bbd51b1fSHaojian Zhuang #include <linux/module.h> 13bbd51b1fSHaojian Zhuang #include <linux/platform_device.h> 14bbd51b1fSHaojian Zhuang #include <linux/i2c.h> 1553dbab7aSHaojian Zhuang #include <linux/mfd/88pm860x.h> 16bbd51b1fSHaojian Zhuang 1753dbab7aSHaojian Zhuang static inline int pm860x_read_device(struct i2c_client *i2c, 18bbd51b1fSHaojian Zhuang int reg, int bytes, void *dest) 19bbd51b1fSHaojian Zhuang { 20bbd51b1fSHaojian Zhuang unsigned char data; 21bbd51b1fSHaojian Zhuang int ret; 22bbd51b1fSHaojian Zhuang 23bbd51b1fSHaojian Zhuang data = (unsigned char)reg; 24bbd51b1fSHaojian Zhuang ret = i2c_master_send(i2c, &data, 1); 25bbd51b1fSHaojian Zhuang if (ret < 0) 26bbd51b1fSHaojian Zhuang return ret; 27bbd51b1fSHaojian Zhuang 28bbd51b1fSHaojian Zhuang ret = i2c_master_recv(i2c, dest, bytes); 29bbd51b1fSHaojian Zhuang if (ret < 0) 30bbd51b1fSHaojian Zhuang return ret; 31bbd51b1fSHaojian Zhuang return 0; 32bbd51b1fSHaojian Zhuang } 33bbd51b1fSHaojian Zhuang 3453dbab7aSHaojian Zhuang static inline int pm860x_write_device(struct i2c_client *i2c, 35bbd51b1fSHaojian Zhuang int reg, int bytes, void *src) 36bbd51b1fSHaojian Zhuang { 37bbd51b1fSHaojian Zhuang unsigned char buf[bytes + 1]; 38bbd51b1fSHaojian Zhuang int ret; 39bbd51b1fSHaojian Zhuang 40bbd51b1fSHaojian Zhuang buf[0] = (unsigned char)reg; 41bbd51b1fSHaojian Zhuang memcpy(&buf[1], src, bytes); 42bbd51b1fSHaojian Zhuang 43bbd51b1fSHaojian Zhuang ret = i2c_master_send(i2c, buf, bytes + 1); 44bbd51b1fSHaojian Zhuang if (ret < 0) 45bbd51b1fSHaojian Zhuang return ret; 46bbd51b1fSHaojian Zhuang return 0; 47bbd51b1fSHaojian Zhuang } 48bbd51b1fSHaojian Zhuang 4953dbab7aSHaojian Zhuang int pm860x_reg_read(struct i2c_client *i2c, int reg) 50bbd51b1fSHaojian Zhuang { 5153dbab7aSHaojian Zhuang struct pm860x_chip *chip = i2c_get_clientdata(i2c); 52bbd51b1fSHaojian Zhuang unsigned char data; 53bbd51b1fSHaojian Zhuang int ret; 54bbd51b1fSHaojian Zhuang 55bbd51b1fSHaojian Zhuang mutex_lock(&chip->io_lock); 5653dbab7aSHaojian Zhuang ret = pm860x_read_device(i2c, reg, 1, &data); 57bbd51b1fSHaojian Zhuang mutex_unlock(&chip->io_lock); 58bbd51b1fSHaojian Zhuang 59bbd51b1fSHaojian Zhuang if (ret < 0) 60bbd51b1fSHaojian Zhuang return ret; 61bbd51b1fSHaojian Zhuang else 62bbd51b1fSHaojian Zhuang return (int)data; 63bbd51b1fSHaojian Zhuang } 6453dbab7aSHaojian Zhuang EXPORT_SYMBOL(pm860x_reg_read); 65bbd51b1fSHaojian Zhuang 6653dbab7aSHaojian Zhuang int pm860x_reg_write(struct i2c_client *i2c, int reg, 67bbd51b1fSHaojian Zhuang unsigned char data) 68bbd51b1fSHaojian Zhuang { 6953dbab7aSHaojian Zhuang struct pm860x_chip *chip = i2c_get_clientdata(i2c); 70bbd51b1fSHaojian Zhuang int ret; 71bbd51b1fSHaojian Zhuang 72bbd51b1fSHaojian Zhuang mutex_lock(&chip->io_lock); 7353dbab7aSHaojian Zhuang ret = pm860x_write_device(i2c, reg, 1, &data); 74bbd51b1fSHaojian Zhuang mutex_unlock(&chip->io_lock); 75bbd51b1fSHaojian Zhuang 76bbd51b1fSHaojian Zhuang return ret; 77bbd51b1fSHaojian Zhuang } 7853dbab7aSHaojian Zhuang EXPORT_SYMBOL(pm860x_reg_write); 79bbd51b1fSHaojian Zhuang 8053dbab7aSHaojian Zhuang int pm860x_bulk_read(struct i2c_client *i2c, int reg, 81bbd51b1fSHaojian Zhuang int count, unsigned char *buf) 82bbd51b1fSHaojian Zhuang { 8353dbab7aSHaojian Zhuang struct pm860x_chip *chip = i2c_get_clientdata(i2c); 84bbd51b1fSHaojian Zhuang int ret; 85bbd51b1fSHaojian Zhuang 86bbd51b1fSHaojian Zhuang mutex_lock(&chip->io_lock); 8753dbab7aSHaojian Zhuang ret = pm860x_read_device(i2c, reg, count, buf); 88bbd51b1fSHaojian Zhuang mutex_unlock(&chip->io_lock); 89bbd51b1fSHaojian Zhuang 90bbd51b1fSHaojian Zhuang return ret; 91bbd51b1fSHaojian Zhuang } 9253dbab7aSHaojian Zhuang EXPORT_SYMBOL(pm860x_bulk_read); 93bbd51b1fSHaojian Zhuang 9453dbab7aSHaojian Zhuang int pm860x_bulk_write(struct i2c_client *i2c, int reg, 95bbd51b1fSHaojian Zhuang int count, unsigned char *buf) 96bbd51b1fSHaojian Zhuang { 9753dbab7aSHaojian Zhuang struct pm860x_chip *chip = i2c_get_clientdata(i2c); 98bbd51b1fSHaojian Zhuang int ret; 99bbd51b1fSHaojian Zhuang 100bbd51b1fSHaojian Zhuang mutex_lock(&chip->io_lock); 10153dbab7aSHaojian Zhuang ret = pm860x_write_device(i2c, reg, count, buf); 102bbd51b1fSHaojian Zhuang mutex_unlock(&chip->io_lock); 103bbd51b1fSHaojian Zhuang 104bbd51b1fSHaojian Zhuang return ret; 105bbd51b1fSHaojian Zhuang } 10653dbab7aSHaojian Zhuang EXPORT_SYMBOL(pm860x_bulk_write); 107bbd51b1fSHaojian Zhuang 10853dbab7aSHaojian Zhuang int pm860x_set_bits(struct i2c_client *i2c, int reg, 109bbd51b1fSHaojian Zhuang unsigned char mask, unsigned char data) 110bbd51b1fSHaojian Zhuang { 11153dbab7aSHaojian Zhuang struct pm860x_chip *chip = i2c_get_clientdata(i2c); 112bbd51b1fSHaojian Zhuang unsigned char value; 113bbd51b1fSHaojian Zhuang int ret; 114bbd51b1fSHaojian Zhuang 115bbd51b1fSHaojian Zhuang mutex_lock(&chip->io_lock); 11653dbab7aSHaojian Zhuang ret = pm860x_read_device(i2c, reg, 1, &value); 117bbd51b1fSHaojian Zhuang if (ret < 0) 118bbd51b1fSHaojian Zhuang goto out; 119bbd51b1fSHaojian Zhuang value &= ~mask; 120bbd51b1fSHaojian Zhuang value |= data; 12153dbab7aSHaojian Zhuang ret = pm860x_write_device(i2c, reg, 1, &value); 122bbd51b1fSHaojian Zhuang out: 123bbd51b1fSHaojian Zhuang mutex_unlock(&chip->io_lock); 124bbd51b1fSHaojian Zhuang return ret; 125bbd51b1fSHaojian Zhuang } 12653dbab7aSHaojian Zhuang EXPORT_SYMBOL(pm860x_set_bits); 127bbd51b1fSHaojian Zhuang 128bbd51b1fSHaojian Zhuang 129bbd51b1fSHaojian Zhuang static const struct i2c_device_id pm860x_id_table[] = { 13053dbab7aSHaojian Zhuang { "88PM860x", 0 }, 131bbd51b1fSHaojian Zhuang {} 132bbd51b1fSHaojian Zhuang }; 133bbd51b1fSHaojian Zhuang MODULE_DEVICE_TABLE(i2c, pm860x_id_table); 134bbd51b1fSHaojian Zhuang 13553dbab7aSHaojian Zhuang static int verify_addr(struct i2c_client *i2c) 13653dbab7aSHaojian Zhuang { 13753dbab7aSHaojian Zhuang unsigned short addr_8607[] = {0x30, 0x34}; 13853dbab7aSHaojian Zhuang unsigned short addr_8606[] = {0x10, 0x11}; 13953dbab7aSHaojian Zhuang int size, i; 14053dbab7aSHaojian Zhuang 14153dbab7aSHaojian Zhuang if (i2c == NULL) 14253dbab7aSHaojian Zhuang return 0; 14353dbab7aSHaojian Zhuang size = ARRAY_SIZE(addr_8606); 14453dbab7aSHaojian Zhuang for (i = 0; i < size; i++) { 14553dbab7aSHaojian Zhuang if (i2c->addr == *(addr_8606 + i)) 14653dbab7aSHaojian Zhuang return CHIP_PM8606; 14753dbab7aSHaojian Zhuang } 14853dbab7aSHaojian Zhuang size = ARRAY_SIZE(addr_8607); 14953dbab7aSHaojian Zhuang for (i = 0; i < size; i++) { 15053dbab7aSHaojian Zhuang if (i2c->addr == *(addr_8607 + i)) 15153dbab7aSHaojian Zhuang return CHIP_PM8607; 15253dbab7aSHaojian Zhuang } 15353dbab7aSHaojian Zhuang return 0; 15453dbab7aSHaojian Zhuang } 15553dbab7aSHaojian Zhuang 156bbd51b1fSHaojian Zhuang static int __devinit pm860x_probe(struct i2c_client *client, 157bbd51b1fSHaojian Zhuang const struct i2c_device_id *id) 158bbd51b1fSHaojian Zhuang { 15953dbab7aSHaojian Zhuang struct pm860x_platform_data *pdata = client->dev.platform_data; 16053dbab7aSHaojian Zhuang static struct pm860x_chip *chip; 16153dbab7aSHaojian Zhuang struct i2c_board_info i2c_info = { 16253dbab7aSHaojian Zhuang .type = "88PM860x", 16353dbab7aSHaojian Zhuang .platform_data = client->dev.platform_data, 16453dbab7aSHaojian Zhuang }; 16553dbab7aSHaojian Zhuang int addr_c, found_companion = 0; 166bbd51b1fSHaojian Zhuang 16753dbab7aSHaojian Zhuang if (pdata == NULL) { 16853dbab7aSHaojian Zhuang pr_info("No platform data in %s!\n", __func__); 16953dbab7aSHaojian Zhuang return -EINVAL; 17053dbab7aSHaojian Zhuang } 17153dbab7aSHaojian Zhuang 17253dbab7aSHaojian Zhuang /* 17353dbab7aSHaojian Zhuang * Both client and companion client shares same platform driver. 17453dbab7aSHaojian Zhuang * Driver distinguishes them by pdata->companion_addr. 17553dbab7aSHaojian Zhuang * pdata->companion_addr is only assigned if companion chip exists. 17653dbab7aSHaojian Zhuang * At the same time, the companion_addr shouldn't equal to client 17753dbab7aSHaojian Zhuang * address. 17853dbab7aSHaojian Zhuang */ 17953dbab7aSHaojian Zhuang addr_c = pdata->companion_addr; 18053dbab7aSHaojian Zhuang if (addr_c && (addr_c != client->addr)) { 18153dbab7aSHaojian Zhuang i2c_info.addr = addr_c; 18253dbab7aSHaojian Zhuang found_companion = 1; 18353dbab7aSHaojian Zhuang } 18453dbab7aSHaojian Zhuang 18553dbab7aSHaojian Zhuang if (found_companion || (addr_c == 0)) { 18653dbab7aSHaojian Zhuang chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL); 187bbd51b1fSHaojian Zhuang if (chip == NULL) 188bbd51b1fSHaojian Zhuang return -ENOMEM; 189bbd51b1fSHaojian Zhuang 19053dbab7aSHaojian Zhuang chip->id = verify_addr(client); 19153dbab7aSHaojian Zhuang chip->companion_addr = addr_c; 192bbd51b1fSHaojian Zhuang chip->client = client; 193bbd51b1fSHaojian Zhuang i2c_set_clientdata(client, chip); 19453dbab7aSHaojian Zhuang chip->dev = &client->dev; 195bbd51b1fSHaojian Zhuang mutex_init(&chip->io_lock); 196bbd51b1fSHaojian Zhuang dev_set_drvdata(chip->dev, chip); 197bbd51b1fSHaojian Zhuang 19853dbab7aSHaojian Zhuang if (found_companion) { 19953dbab7aSHaojian Zhuang /* 20053dbab7aSHaojian Zhuang * If this driver is built in, probe function is 20153dbab7aSHaojian Zhuang * recursive. 20253dbab7aSHaojian Zhuang * If this driver is built as module, the next probe 20353dbab7aSHaojian Zhuang * function is called after the first one finished. 20453dbab7aSHaojian Zhuang */ 20553dbab7aSHaojian Zhuang chip->companion = i2c_new_device(client->adapter, 20653dbab7aSHaojian Zhuang &i2c_info); 20753dbab7aSHaojian Zhuang } 20853dbab7aSHaojian Zhuang } 209bbd51b1fSHaojian Zhuang 21053dbab7aSHaojian Zhuang /* 21153dbab7aSHaojian Zhuang * If companion chip existes, it's called by companion probe. 21253dbab7aSHaojian Zhuang * If there's no companion chip, it's called by client probe. 21353dbab7aSHaojian Zhuang */ 21453dbab7aSHaojian Zhuang if ((addr_c == 0) || (addr_c == client->addr)) { 21553dbab7aSHaojian Zhuang chip->companion = client; 21653dbab7aSHaojian Zhuang i2c_set_clientdata(chip->companion, chip); 21753dbab7aSHaojian Zhuang pm860x_device_init(chip, pdata); 21853dbab7aSHaojian Zhuang } 219bbd51b1fSHaojian Zhuang return 0; 220bbd51b1fSHaojian Zhuang } 221bbd51b1fSHaojian Zhuang 222bbd51b1fSHaojian Zhuang static int __devexit pm860x_remove(struct i2c_client *client) 223bbd51b1fSHaojian Zhuang { 22453dbab7aSHaojian Zhuang struct pm860x_chip *chip = i2c_get_clientdata(client); 225bbd51b1fSHaojian Zhuang 22653dbab7aSHaojian Zhuang /* 22753dbab7aSHaojian Zhuang * If companion existes, companion client is removed first. 22853dbab7aSHaojian Zhuang * Because companion client is registered last and removed first. 22953dbab7aSHaojian Zhuang */ 23053dbab7aSHaojian Zhuang if (chip->companion_addr == client->addr) 23153dbab7aSHaojian Zhuang return 0; 23253dbab7aSHaojian Zhuang pm860x_device_exit(chip); 23353dbab7aSHaojian Zhuang i2c_unregister_device(chip->companion); 23453dbab7aSHaojian Zhuang i2c_set_clientdata(chip->companion, NULL); 23553dbab7aSHaojian Zhuang i2c_set_clientdata(chip->client, NULL); 236bbd51b1fSHaojian Zhuang kfree(chip); 237bbd51b1fSHaojian Zhuang return 0; 238bbd51b1fSHaojian Zhuang } 239bbd51b1fSHaojian Zhuang 240bbd51b1fSHaojian Zhuang static struct i2c_driver pm860x_driver = { 241bbd51b1fSHaojian Zhuang .driver = { 242bbd51b1fSHaojian Zhuang .name = "88PM860x", 243bbd51b1fSHaojian Zhuang .owner = THIS_MODULE, 244bbd51b1fSHaojian Zhuang }, 245bbd51b1fSHaojian Zhuang .probe = pm860x_probe, 246bbd51b1fSHaojian Zhuang .remove = __devexit_p(pm860x_remove), 247bbd51b1fSHaojian Zhuang .id_table = pm860x_id_table, 248bbd51b1fSHaojian Zhuang }; 249bbd51b1fSHaojian Zhuang 250bbd51b1fSHaojian Zhuang static int __init pm860x_i2c_init(void) 251bbd51b1fSHaojian Zhuang { 252bbd51b1fSHaojian Zhuang int ret; 253bbd51b1fSHaojian Zhuang ret = i2c_add_driver(&pm860x_driver); 254bbd51b1fSHaojian Zhuang if (ret != 0) 255bbd51b1fSHaojian Zhuang pr_err("Failed to register 88PM860x I2C driver: %d\n", ret); 256bbd51b1fSHaojian Zhuang return ret; 257bbd51b1fSHaojian Zhuang } 258bbd51b1fSHaojian Zhuang subsys_initcall(pm860x_i2c_init); 259bbd51b1fSHaojian Zhuang 260bbd51b1fSHaojian Zhuang static void __exit pm860x_i2c_exit(void) 261bbd51b1fSHaojian Zhuang { 262bbd51b1fSHaojian Zhuang i2c_del_driver(&pm860x_driver); 263bbd51b1fSHaojian Zhuang } 264bbd51b1fSHaojian Zhuang module_exit(pm860x_i2c_exit); 265bbd51b1fSHaojian Zhuang 266bbd51b1fSHaojian Zhuang MODULE_DESCRIPTION("I2C Driver for Marvell 88PM860x"); 267bbd51b1fSHaojian Zhuang MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); 268bbd51b1fSHaojian Zhuang MODULE_LICENSE("GPL"); 269