Commit 6048a3dd2371c58611ea0ab8b306f8f1469399ae
Committed by
Samuel Ortiz
1 parent
08ff6f2a99
mfd: Add HTCPLD driver
This change introduces a driver for the HTC PLD chip found on some smartphones, such as the HTC Wizard and HTC Herald. It works through the I2C bus and acts as a GPIO extender. Specifically: * it can have several sub-devices, each with its own I2C address * Each sub-device provides 8 output and 8 input pins * The chip attaches to one GPIO to signal when any of the input GPIOs change -- at which point all chips must be scanned for changes This driver implements the GPIOs throught the kernel's GPIO and IRQ framework. This allows any GPIO-servicing drivers to operate on htcpld pins, such as the gpio-keys and gpio-leds drivers. Signed-off-by: Cory Maccarrone <darkstar6262@gmail.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Showing 4 changed files with 744 additions and 0 deletions Side-by-side Diff
drivers/mfd/Kconfig
... | ... | @@ -78,6 +78,15 @@ |
78 | 78 | HTC Magician devices, respectively. Actual functionality is |
79 | 79 | handled by the leds-pasic3 and ds1wm drivers. |
80 | 80 | |
81 | +config HTC_I2CPLD | |
82 | + bool "HTC I2C PLD chip support" | |
83 | + depends on I2C=y | |
84 | + help | |
85 | + If you say yes here you get support for the supposed CPLD | |
86 | + found on omap850 HTC devices like the HTC Wizard and HTC Herald. | |
87 | + This device provides input and output GPIOs through an I2C | |
88 | + interface to one or more sub-chips. | |
89 | + | |
81 | 90 | config UCB1400_CORE |
82 | 91 | tristate "Philips UCB1400 Core driver" |
83 | 92 | depends on AC97_BUS |
drivers/mfd/Makefile
drivers/mfd/htc-i2cpld.c
1 | +/* | |
2 | + * htc-i2cpld.c | |
3 | + * Chip driver for an unknown CPLD chip found on omap850 HTC devices like | |
4 | + * the HTC Wizard and HTC Herald. | |
5 | + * The cpld is located on the i2c bus and acts as an input/output GPIO | |
6 | + * extender. | |
7 | + * | |
8 | + * Copyright (C) 2009 Cory Maccarrone <darkstar6262@gmail.com> | |
9 | + * | |
10 | + * Based on work done in the linwizard project | |
11 | + * Copyright (C) 2008-2009 Angelo Arrifano <miknix@gmail.com> | |
12 | + * | |
13 | + * This program is free software; you can redistribute it and/or modify | |
14 | + * it under the terms of the GNU General Public License as published by | |
15 | + * the Free Software Foundation; either version 2 of the License, or | |
16 | + * (at your option) any later version. | |
17 | + * | |
18 | + * This program is distributed in the hope that it will be useful, | |
19 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | + * GNU General Public License for more details. | |
22 | + * | |
23 | + * You should have received a copy of the GNU General Public License | |
24 | + * along with this program; if not, write to the Free Software | |
25 | + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
26 | + */ | |
27 | + | |
28 | +#include <linux/kernel.h> | |
29 | +#include <linux/init.h> | |
30 | +#include <linux/module.h> | |
31 | +#include <linux/interrupt.h> | |
32 | +#include <linux/platform_device.h> | |
33 | +#include <linux/i2c.h> | |
34 | +#include <linux/irq.h> | |
35 | +#include <linux/spinlock.h> | |
36 | +#include <linux/htcpld.h> | |
37 | +#include <linux/gpio.h> | |
38 | + | |
39 | +struct htcpld_chip { | |
40 | + spinlock_t lock; | |
41 | + | |
42 | + /* chip info */ | |
43 | + u8 reset; | |
44 | + u8 addr; | |
45 | + struct device *dev; | |
46 | + struct i2c_client *client; | |
47 | + | |
48 | + /* Output details */ | |
49 | + u8 cache_out; | |
50 | + struct gpio_chip chip_out; | |
51 | + | |
52 | + /* Input details */ | |
53 | + u8 cache_in; | |
54 | + struct gpio_chip chip_in; | |
55 | + | |
56 | + u16 irqs_enabled; | |
57 | + uint irq_start; | |
58 | + int nirqs; | |
59 | + | |
60 | + /* | |
61 | + * Work structure to allow for setting values outside of any | |
62 | + * possible interrupt context | |
63 | + */ | |
64 | + struct work_struct set_val_work; | |
65 | +}; | |
66 | + | |
67 | +struct htcpld_data { | |
68 | + /* irq info */ | |
69 | + u16 irqs_enabled; | |
70 | + uint irq_start; | |
71 | + int nirqs; | |
72 | + uint chained_irq; | |
73 | + unsigned int int_reset_gpio_hi; | |
74 | + unsigned int int_reset_gpio_lo; | |
75 | + | |
76 | + /* htcpld info */ | |
77 | + struct htcpld_chip *chip; | |
78 | + unsigned int nchips; | |
79 | +}; | |
80 | + | |
81 | +/* There does not appear to be a way to proactively mask interrupts | |
82 | + * on the htcpld chip itself. So, we simply ignore interrupts that | |
83 | + * aren't desired. */ | |
84 | +static void htcpld_mask(unsigned int irq) | |
85 | +{ | |
86 | + struct htcpld_chip *chip = get_irq_chip_data(irq); | |
87 | + chip->irqs_enabled &= ~(1 << (irq - chip->irq_start)); | |
88 | + pr_debug("HTCPLD mask %d %04x\n", irq, chip->irqs_enabled); | |
89 | +} | |
90 | +static void htcpld_unmask(unsigned int irq) | |
91 | +{ | |
92 | + struct htcpld_chip *chip = get_irq_chip_data(irq); | |
93 | + chip->irqs_enabled |= 1 << (irq - chip->irq_start); | |
94 | + pr_debug("HTCPLD unmask %d %04x\n", irq, chip->irqs_enabled); | |
95 | +} | |
96 | + | |
97 | +static int htcpld_set_type(unsigned int irq, unsigned int flags) | |
98 | +{ | |
99 | + struct irq_desc *d = irq_to_desc(irq); | |
100 | + | |
101 | + if (!d) { | |
102 | + pr_err("HTCPLD invalid IRQ: %d\n", irq); | |
103 | + return -EINVAL; | |
104 | + } | |
105 | + | |
106 | + if (flags & ~IRQ_TYPE_SENSE_MASK) | |
107 | + return -EINVAL; | |
108 | + | |
109 | + /* We only allow edge triggering */ | |
110 | + if (flags & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH)) | |
111 | + return -EINVAL; | |
112 | + | |
113 | + d->status &= ~IRQ_TYPE_SENSE_MASK; | |
114 | + d->status |= flags; | |
115 | + | |
116 | + return 0; | |
117 | +} | |
118 | + | |
119 | +static struct irq_chip htcpld_muxed_chip = { | |
120 | + .name = "htcpld", | |
121 | + .mask = htcpld_mask, | |
122 | + .unmask = htcpld_unmask, | |
123 | + .set_type = htcpld_set_type, | |
124 | +}; | |
125 | + | |
126 | +/* To properly dispatch IRQ events, we need to read from the | |
127 | + * chip. This is an I2C action that could possibly sleep | |
128 | + * (which is bad in interrupt context) -- so we use a threaded | |
129 | + * interrupt handler to get around that. | |
130 | + */ | |
131 | +static irqreturn_t htcpld_handler(int irq, void *dev) | |
132 | +{ | |
133 | + struct htcpld_data *htcpld = dev; | |
134 | + unsigned int i; | |
135 | + unsigned long flags; | |
136 | + int irqpin; | |
137 | + struct irq_desc *desc; | |
138 | + | |
139 | + if (!htcpld) { | |
140 | + pr_debug("htcpld is null in ISR\n"); | |
141 | + return IRQ_HANDLED; | |
142 | + } | |
143 | + | |
144 | + /* | |
145 | + * For each chip, do a read of the chip and trigger any interrupts | |
146 | + * desired. The interrupts will be triggered from LSB to MSB (i.e. | |
147 | + * bit 0 first, then bit 1, etc.) | |
148 | + * | |
149 | + * For chips that have no interrupt range specified, just skip 'em. | |
150 | + */ | |
151 | + for (i = 0; i < htcpld->nchips; i++) { | |
152 | + struct htcpld_chip *chip = &htcpld->chip[i]; | |
153 | + struct i2c_client *client; | |
154 | + int val; | |
155 | + unsigned long uval, old_val; | |
156 | + | |
157 | + if (!chip) { | |
158 | + pr_debug("chip %d is null in ISR\n", i); | |
159 | + continue; | |
160 | + } | |
161 | + | |
162 | + if (chip->nirqs == 0) | |
163 | + continue; | |
164 | + | |
165 | + client = chip->client; | |
166 | + if (!client) { | |
167 | + pr_debug("client %d is null in ISR\n", i); | |
168 | + continue; | |
169 | + } | |
170 | + | |
171 | + /* Scan the chip */ | |
172 | + val = i2c_smbus_read_byte_data(client, chip->cache_out); | |
173 | + if (val < 0) { | |
174 | + /* Throw a warning and skip this chip */ | |
175 | + dev_warn(chip->dev, "Unable to read from chip: %d\n", | |
176 | + val); | |
177 | + continue; | |
178 | + } | |
179 | + | |
180 | + uval = (unsigned long)val; | |
181 | + | |
182 | + spin_lock_irqsave(&chip->lock, flags); | |
183 | + | |
184 | + /* Save away the old value so we can compare it */ | |
185 | + old_val = chip->cache_in; | |
186 | + | |
187 | + /* Write the new value */ | |
188 | + chip->cache_in = uval; | |
189 | + | |
190 | + spin_unlock_irqrestore(&chip->lock, flags); | |
191 | + | |
192 | + /* | |
193 | + * For each bit in the data (starting at bit 0), trigger | |
194 | + * associated interrupts. | |
195 | + */ | |
196 | + for (irqpin = 0; irqpin < chip->nirqs; irqpin++) { | |
197 | + unsigned oldb, newb; | |
198 | + int flags; | |
199 | + | |
200 | + irq = chip->irq_start + irqpin; | |
201 | + desc = irq_to_desc(irq); | |
202 | + flags = desc->status; | |
203 | + | |
204 | + /* Run the IRQ handler, but only if the bit value | |
205 | + * changed, and the proper flags are set */ | |
206 | + oldb = (old_val >> irqpin) & 1; | |
207 | + newb = (uval >> irqpin) & 1; | |
208 | + | |
209 | + if ((!oldb && newb && (flags & IRQ_TYPE_EDGE_RISING)) || | |
210 | + (oldb && !newb && | |
211 | + (flags & IRQ_TYPE_EDGE_FALLING))) { | |
212 | + pr_debug("fire IRQ %d\n", irqpin); | |
213 | + desc->handle_irq(irq, desc); | |
214 | + } | |
215 | + } | |
216 | + } | |
217 | + | |
218 | + /* | |
219 | + * In order to continue receiving interrupts, the int_reset_gpio must | |
220 | + * be asserted. | |
221 | + */ | |
222 | + if (htcpld->int_reset_gpio_hi) | |
223 | + gpio_set_value(htcpld->int_reset_gpio_hi, 1); | |
224 | + if (htcpld->int_reset_gpio_lo) | |
225 | + gpio_set_value(htcpld->int_reset_gpio_lo, 0); | |
226 | + | |
227 | + return IRQ_HANDLED; | |
228 | +} | |
229 | + | |
230 | +/* | |
231 | + * The GPIO set routines can be called from interrupt context, especially if, | |
232 | + * for example they're attached to the led-gpio framework and a trigger is | |
233 | + * enabled. As such, we declared work above in the htcpld_chip structure, | |
234 | + * and that work is scheduled in the set routine. The kernel can then run | |
235 | + * the I2C functions, which will sleep, in process context. | |
236 | + */ | |
237 | +void htcpld_chip_set(struct gpio_chip *chip, unsigned offset, int val) | |
238 | +{ | |
239 | + struct i2c_client *client; | |
240 | + struct htcpld_chip *chip_data; | |
241 | + unsigned long flags; | |
242 | + | |
243 | + chip_data = container_of(chip, struct htcpld_chip, chip_out); | |
244 | + if (!chip_data) | |
245 | + return; | |
246 | + | |
247 | + client = chip_data->client; | |
248 | + if (client == NULL) | |
249 | + return; | |
250 | + | |
251 | + spin_lock_irqsave(&chip_data->lock, flags); | |
252 | + if (val) | |
253 | + chip_data->cache_out |= (1 << offset); | |
254 | + else | |
255 | + chip_data->cache_out &= ~(1 << offset); | |
256 | + spin_unlock_irqrestore(&chip_data->lock, flags); | |
257 | + | |
258 | + schedule_work(&(chip_data->set_val_work)); | |
259 | +} | |
260 | + | |
261 | +void htcpld_chip_set_ni(struct work_struct *work) | |
262 | +{ | |
263 | + struct htcpld_chip *chip_data; | |
264 | + struct i2c_client *client; | |
265 | + | |
266 | + chip_data = container_of(work, struct htcpld_chip, set_val_work); | |
267 | + client = chip_data->client; | |
268 | + i2c_smbus_read_byte_data(client, chip_data->cache_out); | |
269 | +} | |
270 | + | |
271 | +int htcpld_chip_get(struct gpio_chip *chip, unsigned offset) | |
272 | +{ | |
273 | + struct htcpld_chip *chip_data; | |
274 | + int val = 0; | |
275 | + int is_input = 0; | |
276 | + | |
277 | + /* Try out first */ | |
278 | + chip_data = container_of(chip, struct htcpld_chip, chip_out); | |
279 | + if (!chip_data) { | |
280 | + /* Try in */ | |
281 | + is_input = 1; | |
282 | + chip_data = container_of(chip, struct htcpld_chip, chip_in); | |
283 | + if (!chip_data) | |
284 | + return -EINVAL; | |
285 | + } | |
286 | + | |
287 | + /* Determine if this is an input or output GPIO */ | |
288 | + if (!is_input) | |
289 | + /* Use the output cache */ | |
290 | + val = (chip_data->cache_out >> offset) & 1; | |
291 | + else | |
292 | + /* Use the input cache */ | |
293 | + val = (chip_data->cache_in >> offset) & 1; | |
294 | + | |
295 | + if (val) | |
296 | + return 1; | |
297 | + else | |
298 | + return 0; | |
299 | +} | |
300 | + | |
301 | +static int htcpld_direction_output(struct gpio_chip *chip, | |
302 | + unsigned offset, int value) | |
303 | +{ | |
304 | + htcpld_chip_set(chip, offset, value); | |
305 | + return 0; | |
306 | +} | |
307 | + | |
308 | +static int htcpld_direction_input(struct gpio_chip *chip, | |
309 | + unsigned offset) | |
310 | +{ | |
311 | + /* | |
312 | + * No-op: this function can only be called on the input chip. | |
313 | + * We do however make sure the offset is within range. | |
314 | + */ | |
315 | + return (offset < chip->ngpio) ? 0 : -EINVAL; | |
316 | +} | |
317 | + | |
318 | +int htcpld_chip_to_irq(struct gpio_chip *chip, unsigned offset) | |
319 | +{ | |
320 | + struct htcpld_chip *chip_data; | |
321 | + | |
322 | + chip_data = container_of(chip, struct htcpld_chip, chip_in); | |
323 | + | |
324 | + if (offset < chip_data->nirqs) | |
325 | + return chip_data->irq_start + offset; | |
326 | + else | |
327 | + return -EINVAL; | |
328 | +} | |
329 | + | |
330 | +void htcpld_chip_reset(struct i2c_client *client) | |
331 | +{ | |
332 | + struct htcpld_chip *chip_data = i2c_get_clientdata(client); | |
333 | + if (!chip_data) | |
334 | + return; | |
335 | + | |
336 | + i2c_smbus_read_byte_data( | |
337 | + client, (chip_data->cache_out = chip_data->reset)); | |
338 | +} | |
339 | + | |
340 | +static int __devinit htcpld_setup_chip_irq( | |
341 | + struct platform_device *pdev, | |
342 | + int chip_index) | |
343 | +{ | |
344 | + struct htcpld_data *htcpld; | |
345 | + struct device *dev = &pdev->dev; | |
346 | + struct htcpld_core_platform_data *pdata; | |
347 | + struct htcpld_chip *chip; | |
348 | + struct htcpld_chip_platform_data *plat_chip_data; | |
349 | + unsigned int irq, irq_end; | |
350 | + int ret = 0; | |
351 | + | |
352 | + /* Get the platform and driver data */ | |
353 | + pdata = dev->platform_data; | |
354 | + htcpld = platform_get_drvdata(pdev); | |
355 | + chip = &htcpld->chip[chip_index]; | |
356 | + plat_chip_data = &pdata->chip[chip_index]; | |
357 | + | |
358 | + /* Setup irq handlers */ | |
359 | + irq_end = chip->irq_start + chip->nirqs; | |
360 | + for (irq = chip->irq_start; irq < irq_end; irq++) { | |
361 | + set_irq_chip(irq, &htcpld_muxed_chip); | |
362 | + set_irq_chip_data(irq, chip); | |
363 | + set_irq_handler(irq, handle_simple_irq); | |
364 | +#ifdef CONFIG_ARM | |
365 | + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); | |
366 | +#else | |
367 | + set_irq_probe(irq); | |
368 | +#endif | |
369 | + } | |
370 | + | |
371 | + return ret; | |
372 | +} | |
373 | + | |
374 | +static int __devinit htcpld_register_chip_i2c( | |
375 | + struct platform_device *pdev, | |
376 | + int chip_index) | |
377 | +{ | |
378 | + struct htcpld_data *htcpld; | |
379 | + struct device *dev = &pdev->dev; | |
380 | + struct htcpld_core_platform_data *pdata; | |
381 | + struct htcpld_chip *chip; | |
382 | + struct htcpld_chip_platform_data *plat_chip_data; | |
383 | + struct i2c_adapter *adapter; | |
384 | + struct i2c_client *client; | |
385 | + struct i2c_board_info info; | |
386 | + | |
387 | + /* Get the platform and driver data */ | |
388 | + pdata = dev->platform_data; | |
389 | + htcpld = platform_get_drvdata(pdev); | |
390 | + chip = &htcpld->chip[chip_index]; | |
391 | + plat_chip_data = &pdata->chip[chip_index]; | |
392 | + | |
393 | + adapter = i2c_get_adapter(pdata->i2c_adapter_id); | |
394 | + if (adapter == NULL) { | |
395 | + /* Eek, no such I2C adapter! Bail out. */ | |
396 | + dev_warn(dev, "Chip at i2c address 0x%x: Invalid i2c adapter %d\n", | |
397 | + plat_chip_data->addr, pdata->i2c_adapter_id); | |
398 | + return -ENODEV; | |
399 | + } | |
400 | + | |
401 | + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) { | |
402 | + dev_warn(dev, "i2c adapter %d non-functional\n", | |
403 | + pdata->i2c_adapter_id); | |
404 | + return -EINVAL; | |
405 | + } | |
406 | + | |
407 | + memset(&info, 0, sizeof(struct i2c_board_info)); | |
408 | + info.addr = plat_chip_data->addr; | |
409 | + strlcpy(info.type, "htcpld-chip", I2C_NAME_SIZE); | |
410 | + info.platform_data = chip; | |
411 | + | |
412 | + /* Add the I2C device. This calls the probe() function. */ | |
413 | + client = i2c_new_device(adapter, &info); | |
414 | + if (!client) { | |
415 | + /* I2C device registration failed, contineu with the next */ | |
416 | + dev_warn(dev, "Unable to add I2C device for 0x%x\n", | |
417 | + plat_chip_data->addr); | |
418 | + return -ENODEV; | |
419 | + } | |
420 | + | |
421 | + i2c_set_clientdata(client, chip); | |
422 | + snprintf(client->name, I2C_NAME_SIZE, "Chip_0x%d", client->addr); | |
423 | + chip->client = client; | |
424 | + | |
425 | + /* Reset the chip */ | |
426 | + htcpld_chip_reset(client); | |
427 | + chip->cache_in = i2c_smbus_read_byte_data(client, chip->cache_out); | |
428 | + | |
429 | + return 0; | |
430 | +} | |
431 | + | |
432 | +static void __devinit htcpld_unregister_chip_i2c( | |
433 | + struct platform_device *pdev, | |
434 | + int chip_index) | |
435 | +{ | |
436 | + struct htcpld_data *htcpld; | |
437 | + struct htcpld_chip *chip; | |
438 | + | |
439 | + /* Get the platform and driver data */ | |
440 | + htcpld = platform_get_drvdata(pdev); | |
441 | + chip = &htcpld->chip[chip_index]; | |
442 | + | |
443 | + if (chip->client) | |
444 | + i2c_unregister_device(chip->client); | |
445 | +} | |
446 | + | |
447 | +static int __devinit htcpld_register_chip_gpio( | |
448 | + struct platform_device *pdev, | |
449 | + int chip_index) | |
450 | +{ | |
451 | + struct htcpld_data *htcpld; | |
452 | + struct device *dev = &pdev->dev; | |
453 | + struct htcpld_core_platform_data *pdata; | |
454 | + struct htcpld_chip *chip; | |
455 | + struct htcpld_chip_platform_data *plat_chip_data; | |
456 | + struct gpio_chip *gpio_chip; | |
457 | + int ret = 0; | |
458 | + | |
459 | + /* Get the platform and driver data */ | |
460 | + pdata = dev->platform_data; | |
461 | + htcpld = platform_get_drvdata(pdev); | |
462 | + chip = &htcpld->chip[chip_index]; | |
463 | + plat_chip_data = &pdata->chip[chip_index]; | |
464 | + | |
465 | + /* Setup the GPIO chips */ | |
466 | + gpio_chip = &(chip->chip_out); | |
467 | + gpio_chip->label = "htcpld-out"; | |
468 | + gpio_chip->dev = dev; | |
469 | + gpio_chip->owner = THIS_MODULE; | |
470 | + gpio_chip->get = htcpld_chip_get; | |
471 | + gpio_chip->set = htcpld_chip_set; | |
472 | + gpio_chip->direction_input = NULL; | |
473 | + gpio_chip->direction_output = htcpld_direction_output; | |
474 | + gpio_chip->base = plat_chip_data->gpio_out_base; | |
475 | + gpio_chip->ngpio = plat_chip_data->num_gpios; | |
476 | + | |
477 | + gpio_chip = &(chip->chip_in); | |
478 | + gpio_chip->label = "htcpld-in"; | |
479 | + gpio_chip->dev = dev; | |
480 | + gpio_chip->owner = THIS_MODULE; | |
481 | + gpio_chip->get = htcpld_chip_get; | |
482 | + gpio_chip->set = NULL; | |
483 | + gpio_chip->direction_input = htcpld_direction_input; | |
484 | + gpio_chip->direction_output = NULL; | |
485 | + gpio_chip->to_irq = htcpld_chip_to_irq; | |
486 | + gpio_chip->base = plat_chip_data->gpio_in_base; | |
487 | + gpio_chip->ngpio = plat_chip_data->num_gpios; | |
488 | + | |
489 | + /* Add the GPIO chips */ | |
490 | + ret = gpiochip_add(&(chip->chip_out)); | |
491 | + if (ret) { | |
492 | + dev_warn(dev, "Unable to register output GPIOs for 0x%x: %d\n", | |
493 | + plat_chip_data->addr, ret); | |
494 | + return ret; | |
495 | + } | |
496 | + | |
497 | + ret = gpiochip_add(&(chip->chip_in)); | |
498 | + if (ret) { | |
499 | + int error; | |
500 | + | |
501 | + dev_warn(dev, "Unable to register input GPIOs for 0x%x: %d\n", | |
502 | + plat_chip_data->addr, ret); | |
503 | + | |
504 | + error = gpiochip_remove(&(chip->chip_out)); | |
505 | + if (error) | |
506 | + dev_warn(dev, "Error while trying to unregister gpio chip: %d\n", error); | |
507 | + | |
508 | + return ret; | |
509 | + } | |
510 | + | |
511 | + return 0; | |
512 | +} | |
513 | + | |
514 | +static int __devinit htcpld_setup_chips(struct platform_device *pdev) | |
515 | +{ | |
516 | + struct htcpld_data *htcpld; | |
517 | + struct device *dev = &pdev->dev; | |
518 | + struct htcpld_core_platform_data *pdata; | |
519 | + int i; | |
520 | + | |
521 | + /* Get the platform and driver data */ | |
522 | + pdata = dev->platform_data; | |
523 | + htcpld = platform_get_drvdata(pdev); | |
524 | + | |
525 | + /* Setup each chip's output GPIOs */ | |
526 | + htcpld->nchips = pdata->num_chip; | |
527 | + htcpld->chip = kzalloc(sizeof(struct htcpld_chip) * htcpld->nchips, | |
528 | + GFP_KERNEL); | |
529 | + if (!htcpld->chip) { | |
530 | + dev_warn(dev, "Unable to allocate memory for chips\n"); | |
531 | + return -ENOMEM; | |
532 | + } | |
533 | + | |
534 | + /* Add the chips as best we can */ | |
535 | + for (i = 0; i < htcpld->nchips; i++) { | |
536 | + int ret; | |
537 | + | |
538 | + /* Setup the HTCPLD chips */ | |
539 | + htcpld->chip[i].reset = pdata->chip[i].reset; | |
540 | + htcpld->chip[i].cache_out = pdata->chip[i].reset; | |
541 | + htcpld->chip[i].cache_in = 0; | |
542 | + htcpld->chip[i].dev = dev; | |
543 | + htcpld->chip[i].irq_start = pdata->chip[i].irq_base; | |
544 | + htcpld->chip[i].nirqs = pdata->chip[i].num_irqs; | |
545 | + | |
546 | + INIT_WORK(&(htcpld->chip[i].set_val_work), &htcpld_chip_set_ni); | |
547 | + spin_lock_init(&(htcpld->chip[i].lock)); | |
548 | + | |
549 | + /* Setup the interrupts for the chip */ | |
550 | + if (htcpld->chained_irq) { | |
551 | + ret = htcpld_setup_chip_irq(pdev, i); | |
552 | + if (ret) | |
553 | + continue; | |
554 | + } | |
555 | + | |
556 | + /* Register the chip with I2C */ | |
557 | + ret = htcpld_register_chip_i2c(pdev, i); | |
558 | + if (ret) | |
559 | + continue; | |
560 | + | |
561 | + | |
562 | + /* Register the chips with the GPIO subsystem */ | |
563 | + ret = htcpld_register_chip_gpio(pdev, i); | |
564 | + if (ret) { | |
565 | + /* Unregister the chip from i2c and continue */ | |
566 | + htcpld_unregister_chip_i2c(pdev, i); | |
567 | + continue; | |
568 | + } | |
569 | + | |
570 | + dev_info(dev, "Registered chip at 0x%x\n", pdata->chip[i].addr); | |
571 | + } | |
572 | + | |
573 | + return 0; | |
574 | +} | |
575 | + | |
576 | +static int __devinit htcpld_core_probe(struct platform_device *pdev) | |
577 | +{ | |
578 | + struct htcpld_data *htcpld; | |
579 | + struct device *dev = &pdev->dev; | |
580 | + struct htcpld_core_platform_data *pdata; | |
581 | + struct resource *res; | |
582 | + int ret = 0; | |
583 | + | |
584 | + if (!dev) | |
585 | + return -ENODEV; | |
586 | + | |
587 | + pdata = dev->platform_data; | |
588 | + if (!pdata) { | |
589 | + dev_warn(dev, "Platform data not found for htcpld core!\n"); | |
590 | + return -ENXIO; | |
591 | + } | |
592 | + | |
593 | + htcpld = kzalloc(sizeof(struct htcpld_data), GFP_KERNEL); | |
594 | + if (!htcpld) | |
595 | + return -ENOMEM; | |
596 | + | |
597 | + /* Find chained irq */ | |
598 | + ret = -EINVAL; | |
599 | + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | |
600 | + if (res) { | |
601 | + int flags; | |
602 | + htcpld->chained_irq = res->start; | |
603 | + | |
604 | + /* Setup the chained interrupt handler */ | |
605 | + flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; | |
606 | + ret = request_threaded_irq(htcpld->chained_irq, | |
607 | + NULL, htcpld_handler, | |
608 | + flags, pdev->name, htcpld); | |
609 | + if (ret) { | |
610 | + dev_warn(dev, "Unable to setup chained irq handler: %d\n", ret); | |
611 | + goto fail; | |
612 | + } else | |
613 | + device_init_wakeup(dev, 0); | |
614 | + } | |
615 | + | |
616 | + /* Set the driver data */ | |
617 | + platform_set_drvdata(pdev, htcpld); | |
618 | + | |
619 | + /* Setup the htcpld chips */ | |
620 | + ret = htcpld_setup_chips(pdev); | |
621 | + if (ret) | |
622 | + goto fail; | |
623 | + | |
624 | + /* Request the GPIO(s) for the int reset and set them up */ | |
625 | + if (pdata->int_reset_gpio_hi) { | |
626 | + ret = gpio_request(pdata->int_reset_gpio_hi, "htcpld-core"); | |
627 | + if (ret) { | |
628 | + /* | |
629 | + * If it failed, that sucks, but we can probably | |
630 | + * continue on without it. | |
631 | + */ | |
632 | + dev_warn(dev, "Unable to request int_reset_gpio_hi -- interrupts may not work\n"); | |
633 | + htcpld->int_reset_gpio_hi = 0; | |
634 | + } else { | |
635 | + htcpld->int_reset_gpio_hi = pdata->int_reset_gpio_hi; | |
636 | + gpio_set_value(htcpld->int_reset_gpio_hi, 1); | |
637 | + } | |
638 | + } | |
639 | + | |
640 | + if (pdata->int_reset_gpio_lo) { | |
641 | + ret = gpio_request(pdata->int_reset_gpio_lo, "htcpld-core"); | |
642 | + if (ret) { | |
643 | + /* | |
644 | + * If it failed, that sucks, but we can probably | |
645 | + * continue on without it. | |
646 | + */ | |
647 | + dev_warn(dev, "Unable to request int_reset_gpio_lo -- interrupts may not work\n"); | |
648 | + htcpld->int_reset_gpio_lo = 0; | |
649 | + } else { | |
650 | + htcpld->int_reset_gpio_lo = pdata->int_reset_gpio_lo; | |
651 | + gpio_set_value(htcpld->int_reset_gpio_lo, 0); | |
652 | + } | |
653 | + } | |
654 | + | |
655 | + dev_info(dev, "Initialized successfully\n"); | |
656 | + return 0; | |
657 | + | |
658 | +fail: | |
659 | + kfree(htcpld); | |
660 | + return ret; | |
661 | +} | |
662 | + | |
663 | +/* The I2C Driver -- used internally */ | |
664 | +static const struct i2c_device_id htcpld_chip_id[] = { | |
665 | + { "htcpld-chip", 0 }, | |
666 | + { } | |
667 | +}; | |
668 | +MODULE_DEVICE_TABLE(i2c, htcpld_chip_id); | |
669 | + | |
670 | + | |
671 | +static struct i2c_driver htcpld_chip_driver = { | |
672 | + .driver = { | |
673 | + .name = "htcpld-chip", | |
674 | + }, | |
675 | + .id_table = htcpld_chip_id, | |
676 | +}; | |
677 | + | |
678 | +/* The Core Driver */ | |
679 | +static struct platform_driver htcpld_core_driver = { | |
680 | + .driver = { | |
681 | + .name = "i2c-htcpld", | |
682 | + }, | |
683 | +}; | |
684 | + | |
685 | +static int __init htcpld_core_init(void) | |
686 | +{ | |
687 | + int ret; | |
688 | + | |
689 | + /* Register the I2C Chip driver */ | |
690 | + ret = i2c_add_driver(&htcpld_chip_driver); | |
691 | + if (ret) | |
692 | + return ret; | |
693 | + | |
694 | + /* Probe for our chips */ | |
695 | + return platform_driver_probe(&htcpld_core_driver, htcpld_core_probe); | |
696 | +} | |
697 | + | |
698 | +static void __exit htcpld_core_exit(void) | |
699 | +{ | |
700 | + i2c_del_driver(&htcpld_chip_driver); | |
701 | + platform_driver_unregister(&htcpld_core_driver); | |
702 | +} | |
703 | + | |
704 | +module_init(htcpld_core_init); | |
705 | +module_exit(htcpld_core_exit); | |
706 | + | |
707 | +MODULE_AUTHOR("Cory Maccarrone <darkstar6262@gmail.com>"); | |
708 | +MODULE_DESCRIPTION("I2C HTC PLD Driver"); | |
709 | +MODULE_LICENSE("GPL"); |
include/linux/htcpld.h
1 | +#ifndef __LINUX_HTCPLD_H | |
2 | +#define __LINUX_HTCPLD_H | |
3 | + | |
4 | +struct htcpld_chip_platform_data { | |
5 | + unsigned int addr; | |
6 | + unsigned int reset; | |
7 | + unsigned int num_gpios; | |
8 | + unsigned int gpio_out_base; | |
9 | + unsigned int gpio_in_base; | |
10 | + unsigned int irq_base; | |
11 | + unsigned int num_irqs; | |
12 | +}; | |
13 | + | |
14 | +struct htcpld_core_platform_data { | |
15 | + unsigned int int_reset_gpio_hi; | |
16 | + unsigned int int_reset_gpio_lo; | |
17 | + unsigned int i2c_adapter_id; | |
18 | + | |
19 | + struct htcpld_chip_platform_data *chip; | |
20 | + unsigned int num_chip; | |
21 | +}; | |
22 | + | |
23 | +#endif /* __LINUX_HTCPLD_H */ |