aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd
diff options
context:
space:
mode:
authorMika Westerberg <mika.westerberg@linux.intel.com>2011-09-21 07:03:07 -0400
committerSamuel Ortiz <sameo@linux.intel.com>2011-10-24 08:09:15 -0400
commit1f5a371c075a7101fe75a75cde5aad928460a42e (patch)
tree3eea7a24339011a0cc75011f60352cf067dc23ce /drivers/mfd
parent429c9ecc76c096cab836060cd3219620437c3221 (diff)
mfd: Add Intel MSIC driver
Add support for Intel MSIC chip found on Intel Medfield platforms. This chip embeds several subdevices: audio, ADC, GPIO, power button, etc. The driver creates platform device for each subdevice. We also provide an MSIC register access API which should replace the more generic SCU IPC interface currently used. Existing drivers can choose whether they convert to this new API or stick with the SCU IPC interface. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig9
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/intel_msic.c501
3 files changed, 511 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index ac8bd4feb047..b01fbe27822d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -757,6 +757,15 @@ config MFD_AAT2870_CORE
757 additional drivers must be enabled in order to use the 757 additional drivers must be enabled in order to use the
758 functionality of the device. 758 functionality of the device.
759 759
760config MFD_INTEL_MSIC
761 bool "Support for Intel MSIC"
762 depends on INTEL_SCU_IPC
763 select MFD_CORE
764 help
765 Select this option to enable access to Intel MSIC (Avatele
766 Passage) chip. This chip embeds audio, battery, GPIO, etc.
767 devices used in Intel Medfield platforms.
768
760endmenu 769endmenu
761endif 770endif
762 771
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c58020303d18..7d53a7c530c8 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -102,3 +102,4 @@ obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o
102obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o 102obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o
103obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o 103obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o
104obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o 104obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o
105obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o
diff --git a/drivers/mfd/intel_msic.c b/drivers/mfd/intel_msic.c
new file mode 100644
index 000000000000..bd086b9e852e
--- /dev/null
+++ b/drivers/mfd/intel_msic.c
@@ -0,0 +1,501 @@
1/*
2 * Driver for Intel MSIC
3 *
4 * Copyright (C) 2011, Intel Corporation
5 * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#include <linux/gpio.h>
13#include <linux/module.h>
14#include <linux/mfd/core.h>
15#include <linux/mfd/intel_msic.h>
16#include <linux/platform_device.h>
17#include <linux/slab.h>
18
19#include <asm/intel_scu_ipc.h>
20
21#define MSIC_VENDOR(id) ((id >> 6) & 3)
22#define MSIC_VERSION(id) (id & 0x3f)
23#define MSIC_MAJOR(id) ('A' + ((id >> 3) & 7))
24#define MSIC_MINOR(id) (id & 7)
25
26/*
27 * MSIC interrupt tree is readable from SRAM at INTEL_MSIC_IRQ_PHYS_BASE.
28 * Since IRQ block starts from address 0x002 we need to substract that from
29 * the actual IRQ status register address.
30 */
31#define MSIC_IRQ_STATUS(x) (INTEL_MSIC_IRQ_PHYS_BASE + ((x) - 2))
32#define MSIC_IRQ_STATUS_ACCDET MSIC_IRQ_STATUS(INTEL_MSIC_ACCDET)
33
34/*
35 * The SCU hardware has limitation of 16 bytes per read/write buffer on
36 * Medfield.
37 */
38#define SCU_IPC_RWBUF_LIMIT 16
39
40/**
41 * struct intel_msic - an MSIC MFD instance
42 * @pdev: pointer to the platform device
43 * @vendor: vendor ID
44 * @version: chip version
45 * @irq_base: base address of the mapped MSIC SRAM interrupt tree
46 */
47struct intel_msic {
48 struct platform_device *pdev;
49 unsigned vendor;
50 unsigned version;
51 void __iomem *irq_base;
52};
53
54static struct resource msic_touch_resources[] = {
55 {
56 .flags = IORESOURCE_IRQ,
57 },
58};
59
60static struct resource msic_adc_resources[] = {
61 {
62 .flags = IORESOURCE_IRQ,
63 },
64};
65
66static struct resource msic_battery_resources[] = {
67 {
68 .flags = IORESOURCE_IRQ,
69 },
70};
71
72static struct resource msic_gpio_resources[] = {
73 {
74 .flags = IORESOURCE_IRQ,
75 },
76};
77
78static struct resource msic_audio_resources[] = {
79 {
80 .name = "IRQ",
81 .flags = IORESOURCE_IRQ,
82 },
83 /*
84 * We will pass IRQ_BASE to the driver now but this can be removed
85 * when/if the driver starts to use intel_msic_irq_read().
86 */
87 {
88 .name = "IRQ_BASE",
89 .flags = IORESOURCE_MEM,
90 .start = MSIC_IRQ_STATUS_ACCDET,
91 .end = MSIC_IRQ_STATUS_ACCDET,
92 },
93};
94
95static struct resource msic_hdmi_resources[] = {
96 {
97 .flags = IORESOURCE_IRQ,
98 },
99};
100
101static struct resource msic_thermal_resources[] = {
102 {
103 .flags = IORESOURCE_IRQ,
104 },
105};
106
107static struct resource msic_power_btn_resources[] = {
108 {
109 .flags = IORESOURCE_IRQ,
110 },
111};
112
113static struct resource msic_ocd_resources[] = {
114 {
115 .flags = IORESOURCE_IRQ,
116 },
117};
118
119/*
120 * Devices that are part of the MSIC and are available via firmware
121 * populated SFI DEVS table.
122 */
123static struct mfd_cell msic_devs[] = {
124 [INTEL_MSIC_BLOCK_TOUCH] = {
125 .name = "msic_touch",
126 .num_resources = ARRAY_SIZE(msic_touch_resources),
127 .resources = msic_touch_resources,
128 },
129 [INTEL_MSIC_BLOCK_ADC] = {
130 .name = "msic_adc",
131 .num_resources = ARRAY_SIZE(msic_adc_resources),
132 .resources = msic_adc_resources,
133 },
134 [INTEL_MSIC_BLOCK_BATTERY] = {
135 .name = "msic_battery",
136 .num_resources = ARRAY_SIZE(msic_battery_resources),
137 .resources = msic_battery_resources,
138 },
139 [INTEL_MSIC_BLOCK_GPIO] = {
140 .name = "msic_gpio",
141 .num_resources = ARRAY_SIZE(msic_gpio_resources),
142 .resources = msic_gpio_resources,
143 },
144 [INTEL_MSIC_BLOCK_AUDIO] = {
145 .name = "msic_audio",
146 .num_resources = ARRAY_SIZE(msic_audio_resources),
147 .resources = msic_audio_resources,
148 },
149 [INTEL_MSIC_BLOCK_HDMI] = {
150 .name = "msic_hdmi",
151 .num_resources = ARRAY_SIZE(msic_hdmi_resources),
152 .resources = msic_hdmi_resources,
153 },
154 [INTEL_MSIC_BLOCK_THERMAL] = {
155 .name = "msic_thermal",
156 .num_resources = ARRAY_SIZE(msic_thermal_resources),
157 .resources = msic_thermal_resources,
158 },
159 [INTEL_MSIC_BLOCK_POWER_BTN] = {
160 .name = "msic_power_btn",
161 .num_resources = ARRAY_SIZE(msic_power_btn_resources),
162 .resources = msic_power_btn_resources,
163 },
164 [INTEL_MSIC_BLOCK_OCD] = {
165 .name = "msic_ocd",
166 .num_resources = ARRAY_SIZE(msic_ocd_resources),
167 .resources = msic_ocd_resources,
168 },
169};
170
171/*
172 * Other MSIC related devices which are not directly available via SFI DEVS
173 * table. These can be pseudo devices, regulators etc. which are needed for
174 * different purposes.
175 *
176 * These devices appear only after the MSIC driver itself is initialized so
177 * we can guarantee that the SCU IPC interface is ready.
178 */
179static struct mfd_cell msic_other_devs[] = {
180 /* Audio codec in the MSIC */
181 {
182 .id = -1,
183 .name = "sn95031",
184 },
185};
186
187/**
188 * intel_msic_reg_read - read a single MSIC register
189 * @reg: register to read
190 * @val: register value is placed here
191 *
192 * Read a single register from MSIC. Returns %0 on success and negative
193 * errno in case of failure.
194 *
195 * Function may sleep.
196 */
197int intel_msic_reg_read(unsigned short reg, u8 *val)
198{
199 return intel_scu_ipc_ioread8(reg, val);
200}
201EXPORT_SYMBOL_GPL(intel_msic_reg_read);
202
203/**
204 * intel_msic_reg_write - write a single MSIC register
205 * @reg: register to write
206 * @val: value to write to that register
207 *
208 * Write a single MSIC register. Returns 0 on success and negative
209 * errno in case of failure.
210 *
211 * Function may sleep.
212 */
213int intel_msic_reg_write(unsigned short reg, u8 val)
214{
215 return intel_scu_ipc_iowrite8(reg, val);
216}
217EXPORT_SYMBOL_GPL(intel_msic_reg_write);
218
219/**
220 * intel_msic_reg_update - update a single MSIC register
221 * @reg: register to update
222 * @val: value to write to the register
223 * @mask: specifies which of the bits are updated (%0 = don't update,
224 * %1 = update)
225 *
226 * Perform an update to a register @reg. @mask is used to specify which
227 * bits are updated. Returns %0 in case of success and negative errno in
228 * case of failure.
229 *
230 * Function may sleep.
231 */
232int intel_msic_reg_update(unsigned short reg, u8 val, u8 mask)
233{
234 return intel_scu_ipc_update_register(reg, val, mask);
235}
236EXPORT_SYMBOL_GPL(intel_msic_reg_update);
237
238/**
239 * intel_msic_bulk_read - read an array of registers
240 * @reg: array of register addresses to read
241 * @buf: array where the read values are placed
242 * @count: number of registers to read
243 *
244 * Function reads @count registers from the MSIC using addresses passed in
245 * @reg. Read values are placed in @buf. Reads are performed atomically
246 * wrt. MSIC.
247 *
248 * Returns %0 in case of success and negative errno in case of failure.
249 *
250 * Function may sleep.
251 */
252int intel_msic_bulk_read(unsigned short *reg, u8 *buf, size_t count)
253{
254 if (WARN_ON(count > SCU_IPC_RWBUF_LIMIT))
255 return -EINVAL;
256
257 return intel_scu_ipc_readv(reg, buf, count);
258}
259EXPORT_SYMBOL_GPL(intel_msic_bulk_read);
260
261/**
262 * intel_msic_bulk_write - write an array of values to the MSIC registers
263 * @reg: array of registers to write
264 * @buf: values to write to each register
265 * @count: number of registers to write
266 *
267 * Function writes @count registers in @buf to MSIC. Writes are performed
268 * atomically wrt MSIC. Returns %0 in case of success and negative errno in
269 * case of failure.
270 *
271 * Function may sleep.
272 */
273int intel_msic_bulk_write(unsigned short *reg, u8 *buf, size_t count)
274{
275 if (WARN_ON(count > SCU_IPC_RWBUF_LIMIT))
276 return -EINVAL;
277
278 return intel_scu_ipc_writev(reg, buf, count);
279}
280EXPORT_SYMBOL_GPL(intel_msic_bulk_write);
281
282/**
283 * intel_msic_irq_read - read a register from an MSIC interrupt tree
284 * @msic: MSIC instance
285 * @reg: interrupt register (between %INTEL_MSIC_IRQLVL1 and
286 * %INTEL_MSIC_RESETIRQ2)
287 * @val: value of the register is placed here
288 *
289 * This function can be used by an MSIC subdevice interrupt handler to read
290 * a register value from the MSIC interrupt tree. In this way subdevice
291 * drivers don't have to map in the interrupt tree themselves but can just
292 * call this function instead.
293 *
294 * Function doesn't sleep and is callable from interrupt context.
295 *
296 * Returns %-EINVAL if @reg is outside of the allowed register region.
297 */
298int intel_msic_irq_read(struct intel_msic *msic, unsigned short reg, u8 *val)
299{
300 if (WARN_ON(reg < INTEL_MSIC_IRQLVL1 || reg > INTEL_MSIC_RESETIRQ2))
301 return -EINVAL;
302
303 *val = readb(msic->irq_base + (reg - INTEL_MSIC_IRQLVL1));
304 return 0;
305}
306EXPORT_SYMBOL_GPL(intel_msic_irq_read);
307
308static int __devinit intel_msic_init_devices(struct intel_msic *msic)
309{
310 struct platform_device *pdev = msic->pdev;
311 struct intel_msic_platform_data *pdata = pdev->dev.platform_data;
312 int ret, i;
313
314 if (pdata->gpio) {
315 struct mfd_cell *cell = &msic_devs[INTEL_MSIC_BLOCK_GPIO];
316
317 cell->platform_data = pdata->gpio;
318 cell->pdata_size = sizeof(*pdata->gpio);
319 }
320
321 if (pdata->ocd) {
322 unsigned gpio = pdata->ocd->gpio;
323
324 ret = gpio_request_one(gpio, GPIOF_IN, "ocd_gpio");
325 if (ret) {
326 dev_err(&pdev->dev, "failed to register OCD GPIO\n");
327 return ret;
328 }
329
330 ret = gpio_to_irq(gpio);
331 if (ret < 0) {
332 dev_err(&pdev->dev, "no IRQ number for OCD GPIO\n");
333 gpio_free(gpio);
334 return ret;
335 }
336
337 /* Update the IRQ number for the OCD */
338 pdata->irq[INTEL_MSIC_BLOCK_OCD] = ret;
339 }
340
341 for (i = 0; i < ARRAY_SIZE(msic_devs); i++) {
342 if (!pdata->irq[i])
343 continue;
344
345 ret = mfd_add_devices(&pdev->dev, -1, &msic_devs[i], 1, NULL,
346 pdata->irq[i]);
347 if (ret)
348 goto fail;
349 }
350
351 ret = mfd_add_devices(&pdev->dev, 0, msic_other_devs,
352 ARRAY_SIZE(msic_other_devs), NULL, 0);
353 if (ret)
354 goto fail;
355
356 return 0;
357
358fail:
359 mfd_remove_devices(&pdev->dev);
360 if (pdata->ocd)
361 gpio_free(pdata->ocd->gpio);
362
363 return ret;
364}
365
366static void __devexit intel_msic_remove_devices(struct intel_msic *msic)
367{
368 struct platform_device *pdev = msic->pdev;
369 struct intel_msic_platform_data *pdata = pdev->dev.platform_data;
370
371 mfd_remove_devices(&pdev->dev);
372
373 if (pdata->ocd)
374 gpio_free(pdata->ocd->gpio);
375}
376
377static int __devinit intel_msic_probe(struct platform_device *pdev)
378{
379 struct intel_msic_platform_data *pdata = pdev->dev.platform_data;
380 struct intel_msic *msic;
381 struct resource *res;
382 u8 id0, id1;
383 int ret;
384
385 if (!pdata) {
386 dev_err(&pdev->dev, "no platform data passed\n");
387 return -EINVAL;
388 }
389
390 /* First validate that we have an MSIC in place */
391 ret = intel_scu_ipc_ioread8(INTEL_MSIC_ID0, &id0);
392 if (ret) {
393 dev_err(&pdev->dev, "failed to identify the MSIC chip (ID0)\n");
394 return -ENXIO;
395 }
396
397 ret = intel_scu_ipc_ioread8(INTEL_MSIC_ID1, &id1);
398 if (ret) {
399 dev_err(&pdev->dev, "failed to identify the MSIC chip (ID1)\n");
400 return -ENXIO;
401 }
402
403 if (MSIC_VENDOR(id0) != MSIC_VENDOR(id1)) {
404 dev_err(&pdev->dev, "invalid vendor ID: %x, %x\n", id0, id1);
405 return -ENXIO;
406 }
407
408 msic = kzalloc(sizeof(*msic), GFP_KERNEL);
409 if (!msic)
410 return -ENOMEM;
411
412 msic->vendor = MSIC_VENDOR(id0);
413 msic->version = MSIC_VERSION(id0);
414 msic->pdev = pdev;
415
416 /*
417 * Map in the MSIC interrupt tree area in SRAM. This is exposed to
418 * the clients via intel_msic_irq_read().
419 */
420 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
421 if (!res) {
422 dev_err(&pdev->dev, "failed to get SRAM iomem resource\n");
423 ret = -ENODEV;
424 goto fail_free_msic;
425 }
426
427 res = request_mem_region(res->start, resource_size(res), pdev->name);
428 if (!res) {
429 ret = -EBUSY;
430 goto fail_free_msic;
431 }
432
433 msic->irq_base = ioremap_nocache(res->start, resource_size(res));
434 if (!msic->irq_base) {
435 dev_err(&pdev->dev, "failed to map SRAM memory\n");
436 ret = -ENOMEM;
437 goto fail_release_region;
438 }
439
440 platform_set_drvdata(pdev, msic);
441
442 ret = intel_msic_init_devices(msic);
443 if (ret) {
444 dev_err(&pdev->dev, "failed to initialize MSIC devices\n");
445 goto fail_unmap_mem;
446 }
447
448 dev_info(&pdev->dev, "Intel MSIC version %c%d (vendor %#x)\n",
449 MSIC_MAJOR(msic->version), MSIC_MINOR(msic->version),
450 msic->vendor);
451
452 return 0;
453
454fail_unmap_mem:
455 iounmap(msic->irq_base);
456fail_release_region:
457 release_mem_region(res->start, resource_size(res));
458fail_free_msic:
459 kfree(msic);
460
461 return ret;
462}
463
464static int __devexit intel_msic_remove(struct platform_device *pdev)
465{
466 struct intel_msic *msic = platform_get_drvdata(pdev);
467 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
468
469 intel_msic_remove_devices(msic);
470 platform_set_drvdata(pdev, NULL);
471 iounmap(msic->irq_base);
472 release_mem_region(res->start, resource_size(res));
473 kfree(msic);
474
475 return 0;
476}
477
478static struct platform_driver intel_msic_driver = {
479 .probe = intel_msic_probe,
480 .remove = __devexit_p(intel_msic_remove),
481 .driver = {
482 .name = "intel_msic",
483 .owner = THIS_MODULE,
484 },
485};
486
487static int __init intel_msic_init(void)
488{
489 return platform_driver_register(&intel_msic_driver);
490}
491module_init(intel_msic_init);
492
493static void __exit intel_msic_exit(void)
494{
495 platform_driver_unregister(&intel_msic_driver);
496}
497module_exit(intel_msic_exit);
498
499MODULE_DESCRIPTION("Driver for Intel MSIC");
500MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
501MODULE_LICENSE("GPL");