aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorAaro Koskinen <aaro.koskinen@iki.fi>2012-11-18 11:36:20 -0500
committerSamuel Ortiz <sameo@linux.intel.com>2012-11-21 10:07:57 -0500
commitc7b76dce8ac95fd464bfae741b830d407884c274 (patch)
treef8a3440cad5a64bebb17d3a8457f35c54e43f444 /drivers
parentdac98aef59eae72c74d9d2464f389f4def15a347 (diff)
mfd: Introduce retu-mfd driver
Retu is a multi-function device found on Nokia Internet Tablets implementing at least watchdog, RTC, headset detection and power button functionality. This patch implements minimum functionality providing register access, IRQ handling and power off functions. Acked-by: Felipe Balbi <balbi@ti.com> Acked-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Aaro Koskinen <aaro.koskinen@iki.fi> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mfd/Kconfig9
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/retu-mfd.c264
3 files changed, 274 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index ca633df7c330..f5b839b718aa 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1094,6 +1094,15 @@ config MFD_VIPERBOARD
1094 You need to select the mfd cell drivers separately. 1094 You need to select the mfd cell drivers separately.
1095 The drivers do not support all features the board exposes. 1095 The drivers do not support all features the board exposes.
1096 1096
1097config MFD_RETU
1098 tristate "Support for Retu multi-function device"
1099 select MFD_CORE
1100 depends on I2C
1101 select REGMAP_IRQ
1102 help
1103 Retu is a multi-function device found on Nokia Internet Tablets
1104 (770, N800 and N810).
1105
1097endmenu 1106endmenu
1098endif 1107endif
1099 1108
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 8072460e99d2..2689c8a0d781 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -145,3 +145,4 @@ obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o
145obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o 145obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o
146obj-$(CONFIG_MFD_SYSCON) += syscon.o 146obj-$(CONFIG_MFD_SYSCON) += syscon.o
147obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o 147obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o
148obj-$(CONFIG_MFD_RETU) += retu-mfd.o
diff --git a/drivers/mfd/retu-mfd.c b/drivers/mfd/retu-mfd.c
new file mode 100644
index 000000000000..7ff4a37ab0c0
--- /dev/null
+++ b/drivers/mfd/retu-mfd.c
@@ -0,0 +1,264 @@
1/*
2 * Retu MFD driver
3 *
4 * Copyright (C) 2004, 2005 Nokia Corporation
5 *
6 * Based on code written by Juha Yrjölä, David Weinehall and Mikko Ylinen.
7 * Rewritten by Aaro Koskinen.
8 *
9 * This file is subject to the terms and conditions of the GNU General
10 * Public License. See the file "COPYING" in the main directory of this
11 * archive for more details.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 */
18
19#include <linux/err.h>
20#include <linux/i2c.h>
21#include <linux/irq.h>
22#include <linux/init.h>
23#include <linux/slab.h>
24#include <linux/mutex.h>
25#include <linux/module.h>
26#include <linux/regmap.h>
27#include <linux/mfd/core.h>
28#include <linux/mfd/retu.h>
29#include <linux/interrupt.h>
30#include <linux/moduleparam.h>
31
32/* Registers */
33#define RETU_REG_ASICR 0x00 /* ASIC ID and revision */
34#define RETU_REG_ASICR_VILMA (1 << 7) /* Bit indicating Vilma */
35#define RETU_REG_IDR 0x01 /* Interrupt ID */
36#define RETU_REG_IMR 0x02 /* Interrupt mask */
37
38/* Interrupt sources */
39#define RETU_INT_PWR 0 /* Power button */
40
41struct retu_dev {
42 struct regmap *regmap;
43 struct device *dev;
44 struct mutex mutex;
45 struct regmap_irq_chip_data *irq_data;
46};
47
48static struct resource retu_pwrbutton_res[] = {
49 {
50 .name = "retu-pwrbutton",
51 .start = RETU_INT_PWR,
52 .end = RETU_INT_PWR,
53 .flags = IORESOURCE_IRQ,
54 },
55};
56
57static struct mfd_cell retu_devs[] = {
58 {
59 .name = "retu-wdt"
60 },
61 {
62 .name = "retu-pwrbutton",
63 .resources = retu_pwrbutton_res,
64 .num_resources = ARRAY_SIZE(retu_pwrbutton_res),
65 }
66};
67
68static struct regmap_irq retu_irqs[] = {
69 [RETU_INT_PWR] = {
70 .mask = 1 << RETU_INT_PWR,
71 }
72};
73
74static struct regmap_irq_chip retu_irq_chip = {
75 .name = "RETU",
76 .irqs = retu_irqs,
77 .num_irqs = ARRAY_SIZE(retu_irqs),
78 .num_regs = 1,
79 .status_base = RETU_REG_IDR,
80 .mask_base = RETU_REG_IMR,
81 .ack_base = RETU_REG_IDR,
82};
83
84/* Retu device registered for the power off. */
85static struct retu_dev *retu_pm_power_off;
86
87int retu_read(struct retu_dev *rdev, u8 reg)
88{
89 int ret;
90 int value;
91
92 mutex_lock(&rdev->mutex);
93 ret = regmap_read(rdev->regmap, reg, &value);
94 mutex_unlock(&rdev->mutex);
95
96 return ret ? ret : value;
97}
98EXPORT_SYMBOL_GPL(retu_read);
99
100int retu_write(struct retu_dev *rdev, u8 reg, u16 data)
101{
102 int ret;
103
104 mutex_lock(&rdev->mutex);
105 ret = regmap_write(rdev->regmap, reg, data);
106 mutex_unlock(&rdev->mutex);
107
108 return ret;
109}
110EXPORT_SYMBOL_GPL(retu_write);
111
112static void retu_power_off(void)
113{
114 struct retu_dev *rdev = retu_pm_power_off;
115 int reg;
116
117 mutex_lock(&retu_pm_power_off->mutex);
118
119 /* Ignore power button state */
120 regmap_read(rdev->regmap, RETU_REG_CC1, &reg);
121 regmap_write(rdev->regmap, RETU_REG_CC1, reg | 2);
122
123 /* Expire watchdog immediately */
124 regmap_write(rdev->regmap, RETU_REG_WATCHDOG, 0);
125
126 /* Wait for poweroff */
127 for (;;)
128 cpu_relax();
129
130 mutex_unlock(&retu_pm_power_off->mutex);
131}
132
133static int retu_regmap_read(void *context, const void *reg, size_t reg_size,
134 void *val, size_t val_size)
135{
136 int ret;
137 struct device *dev = context;
138 struct i2c_client *i2c = to_i2c_client(dev);
139
140 BUG_ON(reg_size != 1 || val_size != 2);
141
142 ret = i2c_smbus_read_word_data(i2c, *(u8 const *)reg);
143 if (ret < 0)
144 return ret;
145
146 *(u16 *)val = ret;
147 return 0;
148}
149
150static int retu_regmap_write(void *context, const void *data, size_t count)
151{
152 u8 reg;
153 u16 val;
154 struct device *dev = context;
155 struct i2c_client *i2c = to_i2c_client(dev);
156
157 BUG_ON(count != sizeof(reg) + sizeof(val));
158 memcpy(&reg, data, sizeof(reg));
159 memcpy(&val, data + sizeof(reg), sizeof(val));
160 return i2c_smbus_write_word_data(i2c, reg, val);
161}
162
163static struct regmap_bus retu_bus = {
164 .read = retu_regmap_read,
165 .write = retu_regmap_write,
166 .val_format_endian_default = REGMAP_ENDIAN_NATIVE,
167};
168
169static struct regmap_config retu_config = {
170 .reg_bits = 8,
171 .val_bits = 16,
172};
173
174static int __devinit retu_probe(struct i2c_client *i2c,
175 const struct i2c_device_id *id)
176{
177 struct retu_dev *rdev;
178 int ret;
179
180 rdev = devm_kzalloc(&i2c->dev, sizeof(*rdev), GFP_KERNEL);
181 if (rdev == NULL)
182 return -ENOMEM;
183
184 i2c_set_clientdata(i2c, rdev);
185 rdev->dev = &i2c->dev;
186 mutex_init(&rdev->mutex);
187 rdev->regmap = devm_regmap_init(&i2c->dev, &retu_bus, &i2c->dev,
188 &retu_config);
189 if (IS_ERR(rdev->regmap))
190 return PTR_ERR(rdev->regmap);
191
192 ret = retu_read(rdev, RETU_REG_ASICR);
193 if (ret < 0) {
194 dev_err(rdev->dev, "could not read Retu revision: %d\n", ret);
195 return ret;
196 }
197
198 dev_info(rdev->dev, "Retu%s v%d.%d found\n",
199 (ret & RETU_REG_ASICR_VILMA) ? " & Vilma" : "",
200 (ret >> 4) & 0x7, ret & 0xf);
201
202 /* Mask all RETU interrupts. */
203 ret = retu_write(rdev, RETU_REG_IMR, 0xffff);
204 if (ret < 0)
205 return ret;
206
207 ret = regmap_add_irq_chip(rdev->regmap, i2c->irq, IRQF_ONESHOT, -1,
208 &retu_irq_chip, &rdev->irq_data);
209 if (ret < 0)
210 return ret;
211
212 ret = mfd_add_devices(rdev->dev, -1, retu_devs, ARRAY_SIZE(retu_devs),
213 NULL, regmap_irq_chip_get_base(rdev->irq_data),
214 NULL);
215 if (ret < 0) {
216 regmap_del_irq_chip(i2c->irq, rdev->irq_data);
217 return ret;
218 }
219
220 if (!pm_power_off) {
221 retu_pm_power_off = rdev;
222 pm_power_off = retu_power_off;
223 }
224
225 return 0;
226}
227
228static int __devexit retu_remove(struct i2c_client *i2c)
229{
230 struct retu_dev *rdev = i2c_get_clientdata(i2c);
231
232 if (retu_pm_power_off == rdev) {
233 pm_power_off = NULL;
234 retu_pm_power_off = NULL;
235 }
236 mfd_remove_devices(rdev->dev);
237 regmap_del_irq_chip(i2c->irq, rdev->irq_data);
238
239 return 0;
240}
241
242static const struct i2c_device_id retu_id[] = {
243 { "retu-mfd", 0 },
244 { }
245};
246MODULE_DEVICE_TABLE(i2c, retu_id);
247
248static struct i2c_driver retu_driver = {
249 .driver = {
250 .name = "retu-mfd",
251 .owner = THIS_MODULE,
252 },
253 .probe = retu_probe,
254 .remove = retu_remove,
255 .id_table = retu_id,
256};
257module_i2c_driver(retu_driver);
258
259MODULE_DESCRIPTION("Retu MFD driver");
260MODULE_AUTHOR("Juha Yrjölä");
261MODULE_AUTHOR("David Weinehall");
262MODULE_AUTHOR("Mikko Ylinen");
263MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
264MODULE_LICENSE("GPL");