1c7b76dceSAaro Koskinen /*
295e50f6aSAaro Koskinen * Retu/Tahvo MFD driver
3c7b76dceSAaro Koskinen *
4c7b76dceSAaro Koskinen * Copyright (C) 2004, 2005 Nokia Corporation
5c7b76dceSAaro Koskinen *
6c7b76dceSAaro Koskinen * Based on code written by Juha Yrjölä, David Weinehall and Mikko Ylinen.
7c7b76dceSAaro Koskinen * Rewritten by Aaro Koskinen.
8c7b76dceSAaro Koskinen *
9c7b76dceSAaro Koskinen * This file is subject to the terms and conditions of the GNU General
10c7b76dceSAaro Koskinen * Public License. See the file "COPYING" in the main directory of this
11c7b76dceSAaro Koskinen * archive for more details.
12c7b76dceSAaro Koskinen *
13c7b76dceSAaro Koskinen * This program is distributed in the hope that it will be useful,
14c7b76dceSAaro Koskinen * but WITHOUT ANY WARRANTY; without even the implied warranty of
15c7b76dceSAaro Koskinen * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16c7b76dceSAaro Koskinen * GNU General Public License for more details.
17c7b76dceSAaro Koskinen */
18c7b76dceSAaro Koskinen
19c7b76dceSAaro Koskinen #include <linux/err.h>
20c7b76dceSAaro Koskinen #include <linux/i2c.h>
21c7b76dceSAaro Koskinen #include <linux/irq.h>
22c7b76dceSAaro Koskinen #include <linux/slab.h>
23c7b76dceSAaro Koskinen #include <linux/mutex.h>
24c7b76dceSAaro Koskinen #include <linux/module.h>
25c7b76dceSAaro Koskinen #include <linux/regmap.h>
26c7b76dceSAaro Koskinen #include <linux/mfd/core.h>
27c7b76dceSAaro Koskinen #include <linux/mfd/retu.h>
28c7b76dceSAaro Koskinen #include <linux/interrupt.h>
29c7b76dceSAaro Koskinen #include <linux/moduleparam.h>
30c7b76dceSAaro Koskinen
31c7b76dceSAaro Koskinen /* Registers */
32c7b76dceSAaro Koskinen #define RETU_REG_ASICR 0x00 /* ASIC ID and revision */
33c7b76dceSAaro Koskinen #define RETU_REG_ASICR_VILMA (1 << 7) /* Bit indicating Vilma */
34c7b76dceSAaro Koskinen #define RETU_REG_IDR 0x01 /* Interrupt ID */
3595e50f6aSAaro Koskinen #define RETU_REG_IMR 0x02 /* Interrupt mask (Retu) */
3695e50f6aSAaro Koskinen #define TAHVO_REG_IMR 0x03 /* Interrupt mask (Tahvo) */
37c7b76dceSAaro Koskinen
38c7b76dceSAaro Koskinen /* Interrupt sources */
39c7b76dceSAaro Koskinen #define RETU_INT_PWR 0 /* Power button */
40c7b76dceSAaro Koskinen
41c7b76dceSAaro Koskinen struct retu_dev {
42c7b76dceSAaro Koskinen struct regmap *regmap;
43c7b76dceSAaro Koskinen struct device *dev;
44c7b76dceSAaro Koskinen struct mutex mutex;
45c7b76dceSAaro Koskinen struct regmap_irq_chip_data *irq_data;
46c7b76dceSAaro Koskinen };
47c7b76dceSAaro Koskinen
48c4a164f4SRikard Falkeborn static const struct resource retu_pwrbutton_res[] = {
49c7b76dceSAaro Koskinen {
50c7b76dceSAaro Koskinen .name = "retu-pwrbutton",
51c7b76dceSAaro Koskinen .start = RETU_INT_PWR,
52c7b76dceSAaro Koskinen .end = RETU_INT_PWR,
53c7b76dceSAaro Koskinen .flags = IORESOURCE_IRQ,
54c7b76dceSAaro Koskinen },
55c7b76dceSAaro Koskinen };
56c7b76dceSAaro Koskinen
575ac98553SGeert Uytterhoeven static const struct mfd_cell retu_devs[] = {
58c7b76dceSAaro Koskinen {
59c7b76dceSAaro Koskinen .name = "retu-wdt"
60c7b76dceSAaro Koskinen },
61c7b76dceSAaro Koskinen {
62c7b76dceSAaro Koskinen .name = "retu-pwrbutton",
63c7b76dceSAaro Koskinen .resources = retu_pwrbutton_res,
64c7b76dceSAaro Koskinen .num_resources = ARRAY_SIZE(retu_pwrbutton_res),
65c7b76dceSAaro Koskinen }
66c7b76dceSAaro Koskinen };
67c7b76dceSAaro Koskinen
68*5af1a4caSJavier Carrasco static const struct regmap_irq retu_irqs[] = {
69c7b76dceSAaro Koskinen [RETU_INT_PWR] = {
70c7b76dceSAaro Koskinen .mask = 1 << RETU_INT_PWR,
71c7b76dceSAaro Koskinen }
72c7b76dceSAaro Koskinen };
73c7b76dceSAaro Koskinen
74*5af1a4caSJavier Carrasco static const struct regmap_irq_chip retu_irq_chip = {
75c7b76dceSAaro Koskinen .name = "RETU",
76c7b76dceSAaro Koskinen .irqs = retu_irqs,
77c7b76dceSAaro Koskinen .num_irqs = ARRAY_SIZE(retu_irqs),
78c7b76dceSAaro Koskinen .num_regs = 1,
79c7b76dceSAaro Koskinen .status_base = RETU_REG_IDR,
80c7b76dceSAaro Koskinen .mask_base = RETU_REG_IMR,
81c7b76dceSAaro Koskinen .ack_base = RETU_REG_IDR,
82c7b76dceSAaro Koskinen };
83c7b76dceSAaro Koskinen
84c7b76dceSAaro Koskinen /* Retu device registered for the power off. */
85c7b76dceSAaro Koskinen static struct retu_dev *retu_pm_power_off;
86c7b76dceSAaro Koskinen
87c4a164f4SRikard Falkeborn static const struct resource tahvo_usb_res[] = {
8895e50f6aSAaro Koskinen {
8995e50f6aSAaro Koskinen .name = "tahvo-usb",
9095e50f6aSAaro Koskinen .start = TAHVO_INT_VBUS,
9195e50f6aSAaro Koskinen .end = TAHVO_INT_VBUS,
9295e50f6aSAaro Koskinen .flags = IORESOURCE_IRQ,
9395e50f6aSAaro Koskinen },
9495e50f6aSAaro Koskinen };
9595e50f6aSAaro Koskinen
965ac98553SGeert Uytterhoeven static const struct mfd_cell tahvo_devs[] = {
9795e50f6aSAaro Koskinen {
9895e50f6aSAaro Koskinen .name = "tahvo-usb",
9995e50f6aSAaro Koskinen .resources = tahvo_usb_res,
10095e50f6aSAaro Koskinen .num_resources = ARRAY_SIZE(tahvo_usb_res),
10195e50f6aSAaro Koskinen },
10295e50f6aSAaro Koskinen };
10395e50f6aSAaro Koskinen
104*5af1a4caSJavier Carrasco static const struct regmap_irq tahvo_irqs[] = {
10595e50f6aSAaro Koskinen [TAHVO_INT_VBUS] = {
10695e50f6aSAaro Koskinen .mask = 1 << TAHVO_INT_VBUS,
10795e50f6aSAaro Koskinen }
10895e50f6aSAaro Koskinen };
10995e50f6aSAaro Koskinen
110*5af1a4caSJavier Carrasco static const struct regmap_irq_chip tahvo_irq_chip = {
11195e50f6aSAaro Koskinen .name = "TAHVO",
11295e50f6aSAaro Koskinen .irqs = tahvo_irqs,
11395e50f6aSAaro Koskinen .num_irqs = ARRAY_SIZE(tahvo_irqs),
11495e50f6aSAaro Koskinen .num_regs = 1,
11595e50f6aSAaro Koskinen .status_base = RETU_REG_IDR,
11695e50f6aSAaro Koskinen .mask_base = TAHVO_REG_IMR,
11795e50f6aSAaro Koskinen .ack_base = RETU_REG_IDR,
11895e50f6aSAaro Koskinen };
11995e50f6aSAaro Koskinen
12095e50f6aSAaro Koskinen static const struct retu_data {
12195e50f6aSAaro Koskinen char *chip_name;
12295e50f6aSAaro Koskinen char *companion_name;
123*5af1a4caSJavier Carrasco const struct regmap_irq_chip *irq_chip;
1245ac98553SGeert Uytterhoeven const struct mfd_cell *children;
12595e50f6aSAaro Koskinen int nchildren;
12695e50f6aSAaro Koskinen } retu_data[] = {
12795e50f6aSAaro Koskinen [0] = {
12895e50f6aSAaro Koskinen .chip_name = "Retu",
12995e50f6aSAaro Koskinen .companion_name = "Vilma",
13095e50f6aSAaro Koskinen .irq_chip = &retu_irq_chip,
13195e50f6aSAaro Koskinen .children = retu_devs,
13295e50f6aSAaro Koskinen .nchildren = ARRAY_SIZE(retu_devs),
13395e50f6aSAaro Koskinen },
13495e50f6aSAaro Koskinen [1] = {
13595e50f6aSAaro Koskinen .chip_name = "Tahvo",
13695e50f6aSAaro Koskinen .companion_name = "Betty",
13795e50f6aSAaro Koskinen .irq_chip = &tahvo_irq_chip,
13895e50f6aSAaro Koskinen .children = tahvo_devs,
13995e50f6aSAaro Koskinen .nchildren = ARRAY_SIZE(tahvo_devs),
14095e50f6aSAaro Koskinen }
14195e50f6aSAaro Koskinen };
14295e50f6aSAaro Koskinen
retu_read(struct retu_dev * rdev,u8 reg)143c7b76dceSAaro Koskinen int retu_read(struct retu_dev *rdev, u8 reg)
144c7b76dceSAaro Koskinen {
145c7b76dceSAaro Koskinen int ret;
146c7b76dceSAaro Koskinen int value;
147c7b76dceSAaro Koskinen
148c7b76dceSAaro Koskinen mutex_lock(&rdev->mutex);
149c7b76dceSAaro Koskinen ret = regmap_read(rdev->regmap, reg, &value);
150c7b76dceSAaro Koskinen mutex_unlock(&rdev->mutex);
151c7b76dceSAaro Koskinen
152c7b76dceSAaro Koskinen return ret ? ret : value;
153c7b76dceSAaro Koskinen }
154c7b76dceSAaro Koskinen EXPORT_SYMBOL_GPL(retu_read);
155c7b76dceSAaro Koskinen
retu_write(struct retu_dev * rdev,u8 reg,u16 data)156c7b76dceSAaro Koskinen int retu_write(struct retu_dev *rdev, u8 reg, u16 data)
157c7b76dceSAaro Koskinen {
158c7b76dceSAaro Koskinen int ret;
159c7b76dceSAaro Koskinen
160c7b76dceSAaro Koskinen mutex_lock(&rdev->mutex);
161c7b76dceSAaro Koskinen ret = regmap_write(rdev->regmap, reg, data);
162c7b76dceSAaro Koskinen mutex_unlock(&rdev->mutex);
163c7b76dceSAaro Koskinen
164c7b76dceSAaro Koskinen return ret;
165c7b76dceSAaro Koskinen }
166c7b76dceSAaro Koskinen EXPORT_SYMBOL_GPL(retu_write);
167c7b76dceSAaro Koskinen
retu_power_off(void)168c7b76dceSAaro Koskinen static void retu_power_off(void)
169c7b76dceSAaro Koskinen {
170c7b76dceSAaro Koskinen struct retu_dev *rdev = retu_pm_power_off;
171c7b76dceSAaro Koskinen int reg;
172c7b76dceSAaro Koskinen
173c7b76dceSAaro Koskinen mutex_lock(&retu_pm_power_off->mutex);
174c7b76dceSAaro Koskinen
175c7b76dceSAaro Koskinen /* Ignore power button state */
176c7b76dceSAaro Koskinen regmap_read(rdev->regmap, RETU_REG_CC1, ®);
177c7b76dceSAaro Koskinen regmap_write(rdev->regmap, RETU_REG_CC1, reg | 2);
178c7b76dceSAaro Koskinen
179c7b76dceSAaro Koskinen /* Expire watchdog immediately */
180c7b76dceSAaro Koskinen regmap_write(rdev->regmap, RETU_REG_WATCHDOG, 0);
181c7b76dceSAaro Koskinen
182c7b76dceSAaro Koskinen /* Wait for poweroff */
183c7b76dceSAaro Koskinen for (;;)
184c7b76dceSAaro Koskinen cpu_relax();
185c7b76dceSAaro Koskinen
186c7b76dceSAaro Koskinen mutex_unlock(&retu_pm_power_off->mutex);
187c7b76dceSAaro Koskinen }
188c7b76dceSAaro Koskinen
retu_regmap_read(void * context,const void * reg,size_t reg_size,void * val,size_t val_size)189c7b76dceSAaro Koskinen static int retu_regmap_read(void *context, const void *reg, size_t reg_size,
190c7b76dceSAaro Koskinen void *val, size_t val_size)
191c7b76dceSAaro Koskinen {
192c7b76dceSAaro Koskinen int ret;
193c7b76dceSAaro Koskinen struct device *dev = context;
194c7b76dceSAaro Koskinen struct i2c_client *i2c = to_i2c_client(dev);
195c7b76dceSAaro Koskinen
196c7b76dceSAaro Koskinen BUG_ON(reg_size != 1 || val_size != 2);
197c7b76dceSAaro Koskinen
198c7b76dceSAaro Koskinen ret = i2c_smbus_read_word_data(i2c, *(u8 const *)reg);
199c7b76dceSAaro Koskinen if (ret < 0)
200c7b76dceSAaro Koskinen return ret;
201c7b76dceSAaro Koskinen
202c7b76dceSAaro Koskinen *(u16 *)val = ret;
203c7b76dceSAaro Koskinen return 0;
204c7b76dceSAaro Koskinen }
205c7b76dceSAaro Koskinen
retu_regmap_write(void * context,const void * data,size_t count)206c7b76dceSAaro Koskinen static int retu_regmap_write(void *context, const void *data, size_t count)
207c7b76dceSAaro Koskinen {
208c7b76dceSAaro Koskinen u8 reg;
209c7b76dceSAaro Koskinen u16 val;
210c7b76dceSAaro Koskinen struct device *dev = context;
211c7b76dceSAaro Koskinen struct i2c_client *i2c = to_i2c_client(dev);
212c7b76dceSAaro Koskinen
213c7b76dceSAaro Koskinen BUG_ON(count != sizeof(reg) + sizeof(val));
214c7b76dceSAaro Koskinen memcpy(®, data, sizeof(reg));
215c7b76dceSAaro Koskinen memcpy(&val, data + sizeof(reg), sizeof(val));
216c7b76dceSAaro Koskinen return i2c_smbus_write_word_data(i2c, reg, val);
217c7b76dceSAaro Koskinen }
218c7b76dceSAaro Koskinen
219*5af1a4caSJavier Carrasco static const struct regmap_bus retu_bus = {
220c7b76dceSAaro Koskinen .read = retu_regmap_read,
221c7b76dceSAaro Koskinen .write = retu_regmap_write,
222c7b76dceSAaro Koskinen .val_format_endian_default = REGMAP_ENDIAN_NATIVE,
223c7b76dceSAaro Koskinen };
224c7b76dceSAaro Koskinen
2251b33d5e2SKrzysztof Kozlowski static const struct regmap_config retu_config = {
226c7b76dceSAaro Koskinen .reg_bits = 8,
227c7b76dceSAaro Koskinen .val_bits = 16,
228c7b76dceSAaro Koskinen };
229c7b76dceSAaro Koskinen
retu_probe(struct i2c_client * i2c)230faa424fcSUwe Kleine-König static int retu_probe(struct i2c_client *i2c)
231c7b76dceSAaro Koskinen {
23295e50f6aSAaro Koskinen struct retu_data const *rdat;
233c7b76dceSAaro Koskinen struct retu_dev *rdev;
234c7b76dceSAaro Koskinen int ret;
235c7b76dceSAaro Koskinen
23695e50f6aSAaro Koskinen if (i2c->addr > ARRAY_SIZE(retu_data))
23795e50f6aSAaro Koskinen return -ENODEV;
23895e50f6aSAaro Koskinen rdat = &retu_data[i2c->addr - 1];
23995e50f6aSAaro Koskinen
240c7b76dceSAaro Koskinen rdev = devm_kzalloc(&i2c->dev, sizeof(*rdev), GFP_KERNEL);
241c7b76dceSAaro Koskinen if (rdev == NULL)
242c7b76dceSAaro Koskinen return -ENOMEM;
243c7b76dceSAaro Koskinen
244c7b76dceSAaro Koskinen i2c_set_clientdata(i2c, rdev);
245c7b76dceSAaro Koskinen rdev->dev = &i2c->dev;
246c7b76dceSAaro Koskinen mutex_init(&rdev->mutex);
247c7b76dceSAaro Koskinen rdev->regmap = devm_regmap_init(&i2c->dev, &retu_bus, &i2c->dev,
248c7b76dceSAaro Koskinen &retu_config);
249c7b76dceSAaro Koskinen if (IS_ERR(rdev->regmap))
250c7b76dceSAaro Koskinen return PTR_ERR(rdev->regmap);
251c7b76dceSAaro Koskinen
252c7b76dceSAaro Koskinen ret = retu_read(rdev, RETU_REG_ASICR);
253c7b76dceSAaro Koskinen if (ret < 0) {
25495e50f6aSAaro Koskinen dev_err(rdev->dev, "could not read %s revision: %d\n",
25595e50f6aSAaro Koskinen rdat->chip_name, ret);
256c7b76dceSAaro Koskinen return ret;
257c7b76dceSAaro Koskinen }
258c7b76dceSAaro Koskinen
25995e50f6aSAaro Koskinen dev_info(rdev->dev, "%s%s%s v%d.%d found\n", rdat->chip_name,
26095e50f6aSAaro Koskinen (ret & RETU_REG_ASICR_VILMA) ? " & " : "",
26195e50f6aSAaro Koskinen (ret & RETU_REG_ASICR_VILMA) ? rdat->companion_name : "",
262c7b76dceSAaro Koskinen (ret >> 4) & 0x7, ret & 0xf);
263c7b76dceSAaro Koskinen
26495e50f6aSAaro Koskinen /* Mask all interrupts. */
26595e50f6aSAaro Koskinen ret = retu_write(rdev, rdat->irq_chip->mask_base, 0xffff);
266c7b76dceSAaro Koskinen if (ret < 0)
267c7b76dceSAaro Koskinen return ret;
268c7b76dceSAaro Koskinen
269c7b76dceSAaro Koskinen ret = regmap_add_irq_chip(rdev->regmap, i2c->irq, IRQF_ONESHOT, -1,
27095e50f6aSAaro Koskinen rdat->irq_chip, &rdev->irq_data);
271c7b76dceSAaro Koskinen if (ret < 0)
272c7b76dceSAaro Koskinen return ret;
273c7b76dceSAaro Koskinen
27495e50f6aSAaro Koskinen ret = mfd_add_devices(rdev->dev, -1, rdat->children, rdat->nchildren,
275c7b76dceSAaro Koskinen NULL, regmap_irq_chip_get_base(rdev->irq_data),
276c7b76dceSAaro Koskinen NULL);
277c7b76dceSAaro Koskinen if (ret < 0) {
278c7b76dceSAaro Koskinen regmap_del_irq_chip(i2c->irq, rdev->irq_data);
279c7b76dceSAaro Koskinen return ret;
280c7b76dceSAaro Koskinen }
281c7b76dceSAaro Koskinen
28295e50f6aSAaro Koskinen if (i2c->addr == 1 && !pm_power_off) {
283c7b76dceSAaro Koskinen retu_pm_power_off = rdev;
284c7b76dceSAaro Koskinen pm_power_off = retu_power_off;
285c7b76dceSAaro Koskinen }
286c7b76dceSAaro Koskinen
287c7b76dceSAaro Koskinen return 0;
288c7b76dceSAaro Koskinen }
289c7b76dceSAaro Koskinen
retu_remove(struct i2c_client * i2c)290ed5c2f5fSUwe Kleine-König static void retu_remove(struct i2c_client *i2c)
291c7b76dceSAaro Koskinen {
292c7b76dceSAaro Koskinen struct retu_dev *rdev = i2c_get_clientdata(i2c);
293c7b76dceSAaro Koskinen
294c7b76dceSAaro Koskinen if (retu_pm_power_off == rdev) {
295c7b76dceSAaro Koskinen pm_power_off = NULL;
296c7b76dceSAaro Koskinen retu_pm_power_off = NULL;
297c7b76dceSAaro Koskinen }
298c7b76dceSAaro Koskinen mfd_remove_devices(rdev->dev);
299c7b76dceSAaro Koskinen regmap_del_irq_chip(i2c->irq, rdev->irq_data);
300c7b76dceSAaro Koskinen }
301c7b76dceSAaro Koskinen
302c7b76dceSAaro Koskinen static const struct i2c_device_id retu_id[] = {
3035e9ea43cSUwe Kleine-König { "retu" },
3045e9ea43cSUwe Kleine-König { "tahvo" },
305c7b76dceSAaro Koskinen { }
306c7b76dceSAaro Koskinen };
307c7b76dceSAaro Koskinen MODULE_DEVICE_TABLE(i2c, retu_id);
308c7b76dceSAaro Koskinen
30946c20bdfSJavier Martinez Canillas static const struct of_device_id retu_of_match[] = {
31046c20bdfSJavier Martinez Canillas { .compatible = "nokia,retu" },
31146c20bdfSJavier Martinez Canillas { .compatible = "nokia,tahvo" },
31246c20bdfSJavier Martinez Canillas { }
31346c20bdfSJavier Martinez Canillas };
31446c20bdfSJavier Martinez Canillas MODULE_DEVICE_TABLE(of, retu_of_match);
31546c20bdfSJavier Martinez Canillas
316c7b76dceSAaro Koskinen static struct i2c_driver retu_driver = {
317c7b76dceSAaro Koskinen .driver = {
318c7b76dceSAaro Koskinen .name = "retu-mfd",
31946c20bdfSJavier Martinez Canillas .of_match_table = retu_of_match,
320c7b76dceSAaro Koskinen },
3219816d859SUwe Kleine-König .probe = retu_probe,
322c7b76dceSAaro Koskinen .remove = retu_remove,
323c7b76dceSAaro Koskinen .id_table = retu_id,
324c7b76dceSAaro Koskinen };
325c7b76dceSAaro Koskinen module_i2c_driver(retu_driver);
326c7b76dceSAaro Koskinen
327c7b76dceSAaro Koskinen MODULE_DESCRIPTION("Retu MFD driver");
328c7b76dceSAaro Koskinen MODULE_AUTHOR("Juha Yrjölä");
329c7b76dceSAaro Koskinen MODULE_AUTHOR("David Weinehall");
330c7b76dceSAaro Koskinen MODULE_AUTHOR("Mikko Ylinen");
331c7b76dceSAaro Koskinen MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
332c7b76dceSAaro Koskinen MODULE_LICENSE("GPL");
333