xref: /linux/drivers/mfd/retu-mfd.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
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, &reg);
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(&reg, 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