diff options
author | Flora Fu <flora.fu@mediatek.com> | 2015-02-22 07:15:29 -0500 |
---|---|---|
committer | Lee Jones <lee.jones@linaro.org> | 2015-03-04 03:18:27 -0500 |
commit | 6df8dd5c185b212d3d7364402df58bff0e67ace4 (patch) | |
tree | 111743466d36b07117d87131645aaba303392e05 /drivers/mfd/mt6397-core.c | |
parent | 82a00c49ed97cf5b9aa96bd6cda2021720578ecc (diff) |
mfd: Add support for the MediaTek MT6397 PMIC
This adds support for the MediaTek MT6397 PMIC. This is a
multifunction device with the following sub modules:
- Regulator
- RTC
- Audio codec
- GPIO
- Clock
It is interfaced to the host controller using SPI interface by a proprietary
hardware called PMIC wrapper or pwrap. MT6397 MFD is a child device of the
pwrap.
Signed-off-by: Flora Fu, MediaTek
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
Diffstat (limited to 'drivers/mfd/mt6397-core.c')
-rw-r--r-- | drivers/mfd/mt6397-core.c | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c new file mode 100644 index 000000000000..09bc7804952a --- /dev/null +++ b/drivers/mfd/mt6397-core.c | |||
@@ -0,0 +1,227 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2014 MediaTek Inc. | ||
3 | * Author: Flora Fu, MediaTek | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License version 2 as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | */ | ||
14 | |||
15 | #include <linux/interrupt.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/of_device.h> | ||
18 | #include <linux/of_irq.h> | ||
19 | #include <linux/regmap.h> | ||
20 | #include <linux/mfd/core.h> | ||
21 | #include <linux/mfd/mt6397/core.h> | ||
22 | #include <linux/mfd/mt6397/registers.h> | ||
23 | |||
24 | static const struct mfd_cell mt6397_devs[] = { | ||
25 | { | ||
26 | .name = "mt6397-rtc", | ||
27 | .of_compatible = "mediatek,mt6397-rtc", | ||
28 | }, { | ||
29 | .name = "mt6397-regulator", | ||
30 | .of_compatible = "mediatek,mt6397-regulator", | ||
31 | }, { | ||
32 | .name = "mt6397-codec", | ||
33 | .of_compatible = "mediatek,mt6397-codec", | ||
34 | }, { | ||
35 | .name = "mt6397-clk", | ||
36 | .of_compatible = "mediatek,mt6397-clk", | ||
37 | }, | ||
38 | }; | ||
39 | |||
40 | static void mt6397_irq_lock(struct irq_data *data) | ||
41 | { | ||
42 | struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); | ||
43 | |||
44 | mutex_lock(&mt6397->irqlock); | ||
45 | } | ||
46 | |||
47 | static void mt6397_irq_sync_unlock(struct irq_data *data) | ||
48 | { | ||
49 | struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); | ||
50 | |||
51 | regmap_write(mt6397->regmap, MT6397_INT_CON0, mt6397->irq_masks_cur[0]); | ||
52 | regmap_write(mt6397->regmap, MT6397_INT_CON1, mt6397->irq_masks_cur[1]); | ||
53 | |||
54 | mutex_unlock(&mt6397->irqlock); | ||
55 | } | ||
56 | |||
57 | static void mt6397_irq_disable(struct irq_data *data) | ||
58 | { | ||
59 | struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); | ||
60 | int shift = data->hwirq & 0xf; | ||
61 | int reg = data->hwirq >> 4; | ||
62 | |||
63 | mt6397->irq_masks_cur[reg] &= ~BIT(shift); | ||
64 | } | ||
65 | |||
66 | static void mt6397_irq_enable(struct irq_data *data) | ||
67 | { | ||
68 | struct mt6397_chip *mt6397 = irq_get_chip_data(data->irq); | ||
69 | int shift = data->hwirq & 0xf; | ||
70 | int reg = data->hwirq >> 4; | ||
71 | |||
72 | mt6397->irq_masks_cur[reg] |= BIT(shift); | ||
73 | } | ||
74 | |||
75 | static struct irq_chip mt6397_irq_chip = { | ||
76 | .name = "mt6397-irq", | ||
77 | .irq_bus_lock = mt6397_irq_lock, | ||
78 | .irq_bus_sync_unlock = mt6397_irq_sync_unlock, | ||
79 | .irq_enable = mt6397_irq_enable, | ||
80 | .irq_disable = mt6397_irq_disable, | ||
81 | }; | ||
82 | |||
83 | static void mt6397_irq_handle_reg(struct mt6397_chip *mt6397, int reg, | ||
84 | int irqbase) | ||
85 | { | ||
86 | unsigned int status; | ||
87 | int i, irq, ret; | ||
88 | |||
89 | ret = regmap_read(mt6397->regmap, reg, &status); | ||
90 | if (ret) { | ||
91 | dev_err(mt6397->dev, "Failed to read irq status: %d\n", ret); | ||
92 | return; | ||
93 | } | ||
94 | |||
95 | for (i = 0; i < 16; i++) { | ||
96 | if (status & BIT(i)) { | ||
97 | irq = irq_find_mapping(mt6397->irq_domain, irqbase + i); | ||
98 | if (irq) | ||
99 | handle_nested_irq(irq); | ||
100 | } | ||
101 | } | ||
102 | |||
103 | regmap_write(mt6397->regmap, reg, status); | ||
104 | } | ||
105 | |||
106 | static irqreturn_t mt6397_irq_thread(int irq, void *data) | ||
107 | { | ||
108 | struct mt6397_chip *mt6397 = data; | ||
109 | |||
110 | mt6397_irq_handle_reg(mt6397, MT6397_INT_STATUS0, 0); | ||
111 | mt6397_irq_handle_reg(mt6397, MT6397_INT_STATUS1, 16); | ||
112 | |||
113 | return IRQ_HANDLED; | ||
114 | } | ||
115 | |||
116 | static int mt6397_irq_domain_map(struct irq_domain *d, unsigned int irq, | ||
117 | irq_hw_number_t hw) | ||
118 | { | ||
119 | struct mt6397_chip *mt6397 = d->host_data; | ||
120 | |||
121 | irq_set_chip_data(irq, mt6397); | ||
122 | irq_set_chip_and_handler(irq, &mt6397_irq_chip, handle_level_irq); | ||
123 | irq_set_nested_thread(irq, 1); | ||
124 | #ifdef CONFIG_ARM | ||
125 | set_irq_flags(irq, IRQF_VALID); | ||
126 | #else | ||
127 | irq_set_noprobe(irq); | ||
128 | #endif | ||
129 | |||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | static struct irq_domain_ops mt6397_irq_domain_ops = { | ||
134 | .map = mt6397_irq_domain_map, | ||
135 | }; | ||
136 | |||
137 | static int mt6397_irq_init(struct mt6397_chip *mt6397) | ||
138 | { | ||
139 | int ret; | ||
140 | |||
141 | mutex_init(&mt6397->irqlock); | ||
142 | |||
143 | /* Mask all interrupt sources */ | ||
144 | regmap_write(mt6397->regmap, MT6397_INT_CON0, 0x0); | ||
145 | regmap_write(mt6397->regmap, MT6397_INT_CON1, 0x0); | ||
146 | |||
147 | mt6397->irq_domain = irq_domain_add_linear(mt6397->dev->of_node, | ||
148 | MT6397_IRQ_NR, &mt6397_irq_domain_ops, mt6397); | ||
149 | if (!mt6397->irq_domain) { | ||
150 | dev_err(mt6397->dev, "could not create irq domain\n"); | ||
151 | return -ENOMEM; | ||
152 | } | ||
153 | |||
154 | ret = devm_request_threaded_irq(mt6397->dev, mt6397->irq, NULL, | ||
155 | mt6397_irq_thread, IRQF_ONESHOT, "mt6397-pmic", mt6397); | ||
156 | if (ret) { | ||
157 | dev_err(mt6397->dev, "failed to register irq=%d; err: %d\n", | ||
158 | mt6397->irq, ret); | ||
159 | return ret; | ||
160 | } | ||
161 | |||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | static int mt6397_probe(struct platform_device *pdev) | ||
166 | { | ||
167 | int ret; | ||
168 | struct mt6397_chip *mt6397; | ||
169 | |||
170 | mt6397 = devm_kzalloc(&pdev->dev, sizeof(*mt6397), GFP_KERNEL); | ||
171 | if (!mt6397) | ||
172 | return -ENOMEM; | ||
173 | |||
174 | mt6397->dev = &pdev->dev; | ||
175 | /* | ||
176 | * mt6397 MFD is child device of soc pmic wrapper. | ||
177 | * Regmap is set from its parent. | ||
178 | */ | ||
179 | mt6397->regmap = dev_get_regmap(pdev->dev.parent, NULL); | ||
180 | if (!mt6397->regmap) | ||
181 | return -ENODEV; | ||
182 | |||
183 | platform_set_drvdata(pdev, mt6397); | ||
184 | |||
185 | mt6397->irq = platform_get_irq(pdev, 0); | ||
186 | if (mt6397->irq > 0) { | ||
187 | ret = mt6397_irq_init(mt6397); | ||
188 | if (ret) | ||
189 | return ret; | ||
190 | } | ||
191 | |||
192 | ret = mfd_add_devices(&pdev->dev, -1, mt6397_devs, | ||
193 | ARRAY_SIZE(mt6397_devs), NULL, 0, NULL); | ||
194 | if (ret) | ||
195 | dev_err(&pdev->dev, "failed to add child devices: %d\n", ret); | ||
196 | |||
197 | return ret; | ||
198 | } | ||
199 | |||
200 | static int mt6397_remove(struct platform_device *pdev) | ||
201 | { | ||
202 | mfd_remove_devices(&pdev->dev); | ||
203 | |||
204 | return 0; | ||
205 | } | ||
206 | |||
207 | static const struct of_device_id mt6397_of_match[] = { | ||
208 | { .compatible = "mediatek,mt6397" }, | ||
209 | { } | ||
210 | }; | ||
211 | MODULE_DEVICE_TABLE(of, mt6397_of_match); | ||
212 | |||
213 | static struct platform_driver mt6397_driver = { | ||
214 | .probe = mt6397_probe, | ||
215 | .remove = mt6397_remove, | ||
216 | .driver = { | ||
217 | .name = "mt6397", | ||
218 | .of_match_table = of_match_ptr(mt6397_of_match), | ||
219 | }, | ||
220 | }; | ||
221 | |||
222 | module_platform_driver(mt6397_driver); | ||
223 | |||
224 | MODULE_AUTHOR("Flora Fu, MediaTek"); | ||
225 | MODULE_DESCRIPTION("Driver for MediaTek MT6397 PMIC"); | ||
226 | MODULE_LICENSE("GPL"); | ||
227 | MODULE_ALIAS("platform:mt6397"); | ||