diff options
author | Philipp Zabel <philipp.zabel@gmail.com> | 2008-04-12 08:25:41 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2008-04-19 06:29:08 -0400 |
commit | 5dc3339aa5ba29593ea57814049ddca8c12831c8 (patch) | |
tree | 0481d4344d0cf4052effce8605706414ce63ce8a /drivers/mfd/htc-pasic3.c | |
parent | 481ea5a16aaabe2570d56b5a4d29fa80adc8008b (diff) |
[ARM] 4964/1: htc-pasic3: MFD driver for PASIC3 LED control + DS1WM chip
This driver will provide registers, clocks and GPIOs of
the HTC PASIC3 (AIC3) and PASIC2 (AIC2) chips to the
ds1wm and leds-pasic3 drivers.
Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'drivers/mfd/htc-pasic3.c')
-rw-r--r-- | drivers/mfd/htc-pasic3.c | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/drivers/mfd/htc-pasic3.c b/drivers/mfd/htc-pasic3.c new file mode 100644 index 000000000000..af66f4f28300 --- /dev/null +++ b/drivers/mfd/htc-pasic3.c | |||
@@ -0,0 +1,265 @@ | |||
1 | /* | ||
2 | * Core driver for HTC PASIC3 LED/DS1WM chip. | ||
3 | * | ||
4 | * Copyright (C) 2006 Philipp Zabel <philipp.zabel@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; version 2 of the License. | ||
9 | */ | ||
10 | |||
11 | #include <linux/init.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/platform_device.h> | ||
14 | |||
15 | #include <linux/ds1wm.h> | ||
16 | #include <linux/gpio.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/irq.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/mfd/htc-pasic3.h> | ||
21 | |||
22 | #include <asm/arch/pxa-regs.h> | ||
23 | |||
24 | struct pasic3_data { | ||
25 | void __iomem *mapping; | ||
26 | unsigned int bus_shift; | ||
27 | struct platform_device *ds1wm_pdev; | ||
28 | struct platform_device *led_pdev; | ||
29 | }; | ||
30 | |||
31 | #define REG_ADDR 5 | ||
32 | #define REG_DATA 6 | ||
33 | #define NUM_REGS 7 | ||
34 | |||
35 | #define READ_MODE 0x80 | ||
36 | |||
37 | /* | ||
38 | * write to a secondary register on the PASIC3 | ||
39 | */ | ||
40 | void pasic3_write_register(struct device *dev, u32 reg, u8 val) | ||
41 | { | ||
42 | struct pasic3_data *asic = dev->driver_data; | ||
43 | int bus_shift = asic->bus_shift; | ||
44 | void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); | ||
45 | void __iomem *data = asic->mapping + (REG_DATA << bus_shift); | ||
46 | |||
47 | __raw_writeb(~READ_MODE & reg, addr); | ||
48 | __raw_writeb(val, data); | ||
49 | } | ||
50 | EXPORT_SYMBOL(pasic3_write_register); /* for leds-pasic3 */ | ||
51 | |||
52 | /* | ||
53 | * read from a secondary register on the PASIC3 | ||
54 | */ | ||
55 | u8 pasic3_read_register(struct device *dev, u32 reg) | ||
56 | { | ||
57 | struct pasic3_data *asic = dev->driver_data; | ||
58 | int bus_shift = asic->bus_shift; | ||
59 | void __iomem *addr = asic->mapping + (REG_ADDR << bus_shift); | ||
60 | void __iomem *data = asic->mapping + (REG_DATA << bus_shift); | ||
61 | |||
62 | __raw_writeb(READ_MODE | reg, addr); | ||
63 | return __raw_readb(data); | ||
64 | } | ||
65 | EXPORT_SYMBOL(pasic3_read_register); /* for leds-pasic3 */ | ||
66 | |||
67 | /* | ||
68 | * LEDs | ||
69 | */ | ||
70 | |||
71 | static int led_device_add(struct device *pasic3_dev, | ||
72 | const struct pasic3_leds_machinfo *pdata) | ||
73 | { | ||
74 | struct pasic3_data *asic = pasic3_dev->driver_data; | ||
75 | struct platform_device *pdev; | ||
76 | int ret; | ||
77 | |||
78 | pdev = platform_device_alloc("pasic3-led", -1); | ||
79 | if (!pdev) { | ||
80 | dev_dbg(pasic3_dev, "failed to allocate LED platform device\n"); | ||
81 | return -ENOMEM; | ||
82 | } | ||
83 | |||
84 | ret = platform_device_add_data(pdev, pdata, | ||
85 | sizeof(struct pasic3_leds_machinfo)); | ||
86 | if (ret < 0) { | ||
87 | dev_dbg(pasic3_dev, "failed to add LED platform data\n"); | ||
88 | goto exit_pdev_put; | ||
89 | } | ||
90 | |||
91 | pdev->dev.parent = pasic3_dev; | ||
92 | ret = platform_device_add(pdev); | ||
93 | if (ret < 0) { | ||
94 | dev_dbg(pasic3_dev, "failed to add LED platform device\n"); | ||
95 | goto exit_pdev_put; | ||
96 | } | ||
97 | |||
98 | asic->led_pdev = pdev; | ||
99 | return 0; | ||
100 | |||
101 | exit_pdev_put: | ||
102 | platform_device_put(pdev); | ||
103 | return ret; | ||
104 | } | ||
105 | |||
106 | /* | ||
107 | * DS1WM | ||
108 | */ | ||
109 | |||
110 | static void ds1wm_enable(struct platform_device *pdev) | ||
111 | { | ||
112 | struct device *dev = pdev->dev.parent; | ||
113 | int c; | ||
114 | |||
115 | c = pasic3_read_register(dev, 0x28); | ||
116 | pasic3_write_register(dev, 0x28, c & 0x7f); | ||
117 | |||
118 | dev_dbg(dev, "DS1WM OWM_EN low (active) %02x\n", c & 0x7f); | ||
119 | } | ||
120 | |||
121 | static void ds1wm_disable(struct platform_device *pdev) | ||
122 | { | ||
123 | struct device *dev = pdev->dev.parent; | ||
124 | int c; | ||
125 | |||
126 | c = pasic3_read_register(dev, 0x28); | ||
127 | pasic3_write_register(dev, 0x28, c | 0x80); | ||
128 | |||
129 | dev_dbg(dev, "DS1WM OWM_EN high (inactive) %02x\n", c | 0x80); | ||
130 | } | ||
131 | |||
132 | static struct ds1wm_platform_data ds1wm_pdata = { | ||
133 | .bus_shift = 2, | ||
134 | .enable = ds1wm_enable, | ||
135 | .disable = ds1wm_disable, | ||
136 | }; | ||
137 | |||
138 | static int ds1wm_device_add(struct device *pasic3_dev, int bus_shift) | ||
139 | { | ||
140 | struct pasic3_data *asic = pasic3_dev->driver_data; | ||
141 | struct platform_device *pdev; | ||
142 | int ret; | ||
143 | |||
144 | pdev = platform_device_alloc("ds1wm", -1); | ||
145 | if (!pdev) { | ||
146 | dev_dbg(pasic3_dev, "failed to allocate DS1WM platform device\n"); | ||
147 | return -ENOMEM; | ||
148 | } | ||
149 | |||
150 | ret = platform_device_add_resources(pdev, pdev->resource, | ||
151 | pdev->num_resources); | ||
152 | if (ret < 0) { | ||
153 | dev_dbg(pasic3_dev, "failed to add DS1WM resources\n"); | ||
154 | goto exit_pdev_put; | ||
155 | } | ||
156 | |||
157 | ds1wm_pdata.bus_shift = asic->bus_shift; | ||
158 | ret = platform_device_add_data(pdev, &ds1wm_pdata, | ||
159 | sizeof(struct ds1wm_platform_data)); | ||
160 | if (ret < 0) { | ||
161 | dev_dbg(pasic3_dev, "failed to add DS1WM platform data\n"); | ||
162 | goto exit_pdev_put; | ||
163 | } | ||
164 | |||
165 | pdev->dev.parent = pasic3_dev; | ||
166 | ret = platform_device_add(pdev); | ||
167 | if (ret < 0) { | ||
168 | dev_dbg(pasic3_dev, "failed to add DS1WM platform device\n"); | ||
169 | goto exit_pdev_put; | ||
170 | } | ||
171 | |||
172 | asic->ds1wm_pdev = pdev; | ||
173 | return 0; | ||
174 | |||
175 | exit_pdev_put: | ||
176 | platform_device_put(pdev); | ||
177 | return ret; | ||
178 | } | ||
179 | |||
180 | static int __init pasic3_probe(struct platform_device *pdev) | ||
181 | { | ||
182 | struct pasic3_platform_data *pdata = pdev->dev.platform_data; | ||
183 | struct device *dev = &pdev->dev; | ||
184 | struct pasic3_data *asic; | ||
185 | struct resource *r; | ||
186 | int ret; | ||
187 | |||
188 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
189 | if (!r) | ||
190 | return -ENXIO; | ||
191 | |||
192 | if (!request_mem_region(r->start, r->end - r->start + 1, "pasic3")) | ||
193 | return -EBUSY; | ||
194 | |||
195 | asic = kzalloc(sizeof(struct pasic3_data), GFP_KERNEL); | ||
196 | if (!asic) | ||
197 | return -ENOMEM; | ||
198 | |||
199 | platform_set_drvdata(pdev, asic); | ||
200 | |||
201 | if (pdata && pdata->bus_shift) | ||
202 | asic->bus_shift = pdata->bus_shift; | ||
203 | else | ||
204 | asic->bus_shift = 2; | ||
205 | |||
206 | asic->mapping = ioremap(r->start, r->end - r->start + 1); | ||
207 | if (!asic->mapping) { | ||
208 | dev_err(dev, "couldn't ioremap PASIC3\n"); | ||
209 | kfree(asic); | ||
210 | return -ENOMEM; | ||
211 | } | ||
212 | |||
213 | ret = ds1wm_device_add(dev, asic->bus_shift); | ||
214 | if (ret < 0) | ||
215 | dev_warn(dev, "failed to register DS1WM\n"); | ||
216 | |||
217 | if (pdata->led_pdata) { | ||
218 | ret = led_device_add(dev, pdata->led_pdata); | ||
219 | if (ret < 0) | ||
220 | dev_warn(dev, "failed to register LED device\n"); | ||
221 | } | ||
222 | |||
223 | return 0; | ||
224 | } | ||
225 | |||
226 | static int pasic3_remove(struct platform_device *pdev) | ||
227 | { | ||
228 | struct pasic3_data *asic = platform_get_drvdata(pdev); | ||
229 | struct resource *r; | ||
230 | |||
231 | if (asic->led_pdev) | ||
232 | platform_device_unregister(asic->led_pdev); | ||
233 | if (asic->ds1wm_pdev) | ||
234 | platform_device_unregister(asic->ds1wm_pdev); | ||
235 | |||
236 | iounmap(asic->mapping); | ||
237 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
238 | release_mem_region(r->start, r->end - r->start + 1); | ||
239 | kfree(asic); | ||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | static struct platform_driver pasic3_driver = { | ||
244 | .driver = { | ||
245 | .name = "pasic3", | ||
246 | }, | ||
247 | .remove = pasic3_remove, | ||
248 | }; | ||
249 | |||
250 | static int __init pasic3_base_init(void) | ||
251 | { | ||
252 | return platform_driver_probe(&pasic3_driver, pasic3_probe); | ||
253 | } | ||
254 | |||
255 | static void __exit pasic3_base_exit(void) | ||
256 | { | ||
257 | platform_driver_unregister(&pasic3_driver); | ||
258 | } | ||
259 | |||
260 | module_init(pasic3_base_init); | ||
261 | module_exit(pasic3_base_exit); | ||
262 | |||
263 | MODULE_AUTHOR("Philipp Zabel <philipp.zabel@gmail.com>"); | ||
264 | MODULE_DESCRIPTION("Core driver for HTC PASIC3"); | ||
265 | MODULE_LICENSE("GPL"); | ||