88pm860x-i2c.c revision 28ade0f217a3a3ff992b01e06e6e425c250a8406
1/*
2 * I2C driver for Marvell 88PM860x
3 *
4 * Copyright (C) 2009 Marvell International Ltd.
5 * 	Haojian Zhuang <haojian.zhuang@marvell.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11#include <linux/kernel.h>
12#include <linux/module.h>
13#include <linux/platform_device.h>
14#include <linux/i2c.h>
15#include <linux/mfd/88pm860x.h>
16#include <linux/slab.h>
17
18static inline int pm860x_read_device(struct i2c_client *i2c,
19				     int reg, int bytes, void *dest)
20{
21	unsigned char data;
22	int ret;
23
24	data = (unsigned char)reg;
25	ret = i2c_master_send(i2c, &data, 1);
26	if (ret < 0)
27		return ret;
28
29	ret = i2c_master_recv(i2c, dest, bytes);
30	if (ret < 0)
31		return ret;
32	return 0;
33}
34
35static inline int pm860x_write_device(struct i2c_client *i2c,
36				      int reg, int bytes, void *src)
37{
38	unsigned char buf[bytes + 1];
39	int ret;
40
41	buf[0] = (unsigned char)reg;
42	memcpy(&buf[1], src, bytes);
43
44	ret = i2c_master_send(i2c, buf, bytes + 1);
45	if (ret < 0)
46		return ret;
47	return 0;
48}
49
50int pm860x_reg_read(struct i2c_client *i2c, int reg)
51{
52	struct pm860x_chip *chip = i2c_get_clientdata(i2c);
53	unsigned char data;
54	int ret;
55
56	mutex_lock(&chip->io_lock);
57	ret = pm860x_read_device(i2c, reg, 1, &data);
58	mutex_unlock(&chip->io_lock);
59
60	if (ret < 0)
61		return ret;
62	else
63		return (int)data;
64}
65EXPORT_SYMBOL(pm860x_reg_read);
66
67int pm860x_reg_write(struct i2c_client *i2c, int reg,
68		     unsigned char data)
69{
70	struct pm860x_chip *chip = i2c_get_clientdata(i2c);
71	int ret;
72
73	mutex_lock(&chip->io_lock);
74	ret = pm860x_write_device(i2c, reg, 1, &data);
75	mutex_unlock(&chip->io_lock);
76
77	return ret;
78}
79EXPORT_SYMBOL(pm860x_reg_write);
80
81int pm860x_bulk_read(struct i2c_client *i2c, int reg,
82		     int count, unsigned char *buf)
83{
84	struct pm860x_chip *chip = i2c_get_clientdata(i2c);
85	int ret;
86
87	mutex_lock(&chip->io_lock);
88	ret = pm860x_read_device(i2c, reg, count, buf);
89	mutex_unlock(&chip->io_lock);
90
91	return ret;
92}
93EXPORT_SYMBOL(pm860x_bulk_read);
94
95int pm860x_bulk_write(struct i2c_client *i2c, int reg,
96		      int count, unsigned char *buf)
97{
98	struct pm860x_chip *chip = i2c_get_clientdata(i2c);
99	int ret;
100
101	mutex_lock(&chip->io_lock);
102	ret = pm860x_write_device(i2c, reg, count, buf);
103	mutex_unlock(&chip->io_lock);
104
105	return ret;
106}
107EXPORT_SYMBOL(pm860x_bulk_write);
108
109int pm860x_set_bits(struct i2c_client *i2c, int reg,
110		    unsigned char mask, unsigned char data)
111{
112	struct pm860x_chip *chip = i2c_get_clientdata(i2c);
113	unsigned char value;
114	int ret;
115
116	mutex_lock(&chip->io_lock);
117	ret = pm860x_read_device(i2c, reg, 1, &value);
118	if (ret < 0)
119		goto out;
120	value &= ~mask;
121	value |= data;
122	ret = pm860x_write_device(i2c, reg, 1, &value);
123out:
124	mutex_unlock(&chip->io_lock);
125	return ret;
126}
127EXPORT_SYMBOL(pm860x_set_bits);
128
129
130static const struct i2c_device_id pm860x_id_table[] = {
131	{ "88PM860x", 0 },
132	{}
133};
134MODULE_DEVICE_TABLE(i2c, pm860x_id_table);
135
136static int verify_addr(struct i2c_client *i2c)
137{
138	unsigned short addr_8607[] = {0x30, 0x34};
139	unsigned short addr_8606[] = {0x10, 0x11};
140	int size, i;
141
142	if (i2c == NULL)
143		return 0;
144	size = ARRAY_SIZE(addr_8606);
145	for (i = 0; i < size; i++) {
146		if (i2c->addr == *(addr_8606 + i))
147			return CHIP_PM8606;
148	}
149	size = ARRAY_SIZE(addr_8607);
150	for (i = 0; i < size; i++) {
151		if (i2c->addr == *(addr_8607 + i))
152			return CHIP_PM8607;
153	}
154	return 0;
155}
156
157static int __devinit pm860x_probe(struct i2c_client *client,
158				  const struct i2c_device_id *id)
159{
160	struct pm860x_platform_data *pdata = client->dev.platform_data;
161	struct pm860x_chip *chip;
162
163	if (!pdata) {
164		pr_info("No platform data in %s!\n", __func__);
165		return -EINVAL;
166	}
167
168	chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL);
169	if (chip == NULL)
170		return -ENOMEM;
171
172	chip->id = verify_addr(client);
173	chip->client = client;
174	i2c_set_clientdata(client, chip);
175	chip->dev = &client->dev;
176	mutex_init(&chip->io_lock);
177	dev_set_drvdata(chip->dev, chip);
178
179	/*
180	 * Both client and companion client shares same platform driver.
181	 * Driver distinguishes them by pdata->companion_addr.
182	 * pdata->companion_addr is only assigned if companion chip exists.
183	 * At the same time, the companion_addr shouldn't equal to client
184	 * address.
185	 */
186	if (pdata->companion_addr && (pdata->companion_addr != client->addr)) {
187		chip->companion_addr = pdata->companion_addr;
188		chip->companion = i2c_new_dummy(chip->client->adapter,
189						chip->companion_addr);
190		i2c_set_clientdata(chip->companion, chip);
191	}
192
193	pm860x_device_init(chip, pdata);
194	return 0;
195}
196
197static int __devexit pm860x_remove(struct i2c_client *client)
198{
199	struct pm860x_chip *chip = i2c_get_clientdata(client);
200
201	pm860x_device_exit(chip);
202	i2c_unregister_device(chip->companion);
203	i2c_set_clientdata(chip->client, NULL);
204	i2c_set_clientdata(client, NULL);
205	kfree(chip);
206	return 0;
207}
208
209static struct i2c_driver pm860x_driver = {
210	.driver	= {
211		.name	= "88PM860x",
212		.owner	= THIS_MODULE,
213	},
214	.probe		= pm860x_probe,
215	.remove		= __devexit_p(pm860x_remove),
216	.id_table	= pm860x_id_table,
217};
218
219static int __init pm860x_i2c_init(void)
220{
221	int ret;
222	ret = i2c_add_driver(&pm860x_driver);
223	if (ret != 0)
224		pr_err("Failed to register 88PM860x I2C driver: %d\n", ret);
225	return ret;
226}
227subsys_initcall(pm860x_i2c_init);
228
229static void __exit pm860x_i2c_exit(void)
230{
231	i2c_del_driver(&pm860x_driver);
232}
233module_exit(pm860x_i2c_exit);
234
235MODULE_DESCRIPTION("I2C Driver for Marvell 88PM860x");
236MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
237MODULE_LICENSE("GPL");
238