Commit | Line | Data |
---|---|---|
eafaebd9 SR |
1 | /* |
2 | * nokia-modem.c | |
3 | * | |
4 | * HSI client driver for Nokia N900 modem. | |
5 | * | |
6 | * Copyright (C) 2014 Sebastian Reichel <sre@kernel.org> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License | |
10 | * version 2 as published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA | |
20 | * 02110-1301 USA | |
21 | */ | |
22 | ||
23 | #include <linux/gpio/consumer.h> | |
24 | #include <linux/hsi/hsi.h> | |
25 | #include <linux/init.h> | |
26 | #include <linux/interrupt.h> | |
27 | #include <linux/of.h> | |
28 | #include <linux/of_irq.h> | |
29 | #include <linux/of_gpio.h> | |
30 | #include <linux/hsi/ssi_protocol.h> | |
31 | ||
cdb83947 | 32 | static unsigned int pm = 1; |
eafaebd9 SR |
33 | module_param(pm, int, 0400); |
34 | MODULE_PARM_DESC(pm, | |
35 | "Enable power management (0=disabled, 1=userland based [default])"); | |
36 | ||
37 | struct nokia_modem_gpio { | |
38 | struct gpio_desc *gpio; | |
39 | const char *name; | |
40 | }; | |
41 | ||
42 | struct nokia_modem_device { | |
43 | struct tasklet_struct nokia_modem_rst_ind_tasklet; | |
44 | int nokia_modem_rst_ind_irq; | |
45 | struct device *device; | |
46 | struct nokia_modem_gpio *gpios; | |
47 | int gpio_amount; | |
48 | struct hsi_client *ssi_protocol; | |
f9c0d76e | 49 | struct hsi_client *cmt_speech; |
eafaebd9 SR |
50 | }; |
51 | ||
52 | static void do_nokia_modem_rst_ind_tasklet(unsigned long data) | |
53 | { | |
54 | struct nokia_modem_device *modem = (struct nokia_modem_device *)data; | |
55 | ||
56 | if (!modem) | |
57 | return; | |
58 | ||
59 | dev_info(modem->device, "CMT rst line change detected\n"); | |
60 | ||
61 | if (modem->ssi_protocol) | |
62 | ssip_reset_event(modem->ssi_protocol); | |
63 | } | |
64 | ||
65 | static irqreturn_t nokia_modem_rst_ind_isr(int irq, void *data) | |
66 | { | |
67 | struct nokia_modem_device *modem = (struct nokia_modem_device *)data; | |
68 | ||
69 | tasklet_schedule(&modem->nokia_modem_rst_ind_tasklet); | |
70 | ||
71 | return IRQ_HANDLED; | |
72 | } | |
73 | ||
74 | static void nokia_modem_gpio_unexport(struct device *dev) | |
75 | { | |
76 | struct nokia_modem_device *modem = dev_get_drvdata(dev); | |
77 | int i; | |
78 | ||
79 | for (i = 0; i < modem->gpio_amount; i++) { | |
80 | sysfs_remove_link(&dev->kobj, modem->gpios[i].name); | |
81 | gpiod_unexport(modem->gpios[i].gpio); | |
82 | } | |
83 | } | |
84 | ||
85 | static int nokia_modem_gpio_probe(struct device *dev) | |
86 | { | |
87 | struct device_node *np = dev->of_node; | |
88 | struct nokia_modem_device *modem = dev_get_drvdata(dev); | |
89 | int gpio_count, gpio_name_count, i, err; | |
90 | ||
91 | gpio_count = of_gpio_count(np); | |
92 | ||
93 | if (gpio_count < 0) { | |
94 | dev_err(dev, "missing gpios: %d\n", gpio_count); | |
95 | return gpio_count; | |
96 | } | |
97 | ||
98 | gpio_name_count = of_property_count_strings(np, "gpio-names"); | |
99 | ||
100 | if (gpio_count != gpio_name_count) { | |
101 | dev_err(dev, "number of gpios does not equal number of gpio names\n"); | |
102 | return -EINVAL; | |
103 | } | |
104 | ||
105 | modem->gpios = devm_kzalloc(dev, gpio_count * | |
106 | sizeof(struct nokia_modem_gpio), GFP_KERNEL); | |
107 | if (!modem->gpios) { | |
108 | dev_err(dev, "Could not allocate memory for gpios\n"); | |
109 | return -ENOMEM; | |
110 | } | |
111 | ||
112 | modem->gpio_amount = gpio_count; | |
113 | ||
114 | for (i = 0; i < gpio_count; i++) { | |
115 | modem->gpios[i].gpio = devm_gpiod_get_index(dev, NULL, i); | |
116 | if (IS_ERR(modem->gpios[i].gpio)) { | |
117 | dev_err(dev, "Could not get gpio %d\n", i); | |
118 | return PTR_ERR(modem->gpios[i].gpio); | |
119 | } | |
120 | ||
121 | err = of_property_read_string_index(np, "gpio-names", i, | |
122 | &(modem->gpios[i].name)); | |
123 | if (err) { | |
124 | dev_err(dev, "Could not get gpio name %d\n", i); | |
125 | return err; | |
126 | } | |
127 | ||
128 | err = gpiod_direction_output(modem->gpios[i].gpio, 0); | |
129 | if (err) | |
130 | return err; | |
131 | ||
132 | err = gpiod_export(modem->gpios[i].gpio, 0); | |
133 | if (err) | |
134 | return err; | |
135 | ||
136 | err = gpiod_export_link(dev, modem->gpios[i].name, | |
137 | modem->gpios[i].gpio); | |
138 | if (err) | |
139 | return err; | |
140 | } | |
141 | ||
142 | return 0; | |
143 | } | |
144 | ||
145 | static int nokia_modem_probe(struct device *dev) | |
146 | { | |
147 | struct device_node *np; | |
148 | struct nokia_modem_device *modem; | |
149 | struct hsi_client *cl = to_hsi_client(dev); | |
150 | struct hsi_port *port = hsi_get_port(cl); | |
151 | int irq, pflags, err; | |
152 | struct hsi_board_info ssip; | |
f9c0d76e | 153 | struct hsi_board_info cmtspeech; |
eafaebd9 SR |
154 | |
155 | np = dev->of_node; | |
156 | if (!np) { | |
157 | dev_err(dev, "device tree node not found\n"); | |
158 | return -ENXIO; | |
159 | } | |
160 | ||
161 | modem = devm_kzalloc(dev, sizeof(*modem), GFP_KERNEL); | |
162 | if (!modem) { | |
163 | dev_err(dev, "Could not allocate memory for nokia_modem_device\n"); | |
164 | return -ENOMEM; | |
165 | } | |
166 | dev_set_drvdata(dev, modem); | |
67e9a2ce | 167 | modem->device = dev; |
eafaebd9 SR |
168 | |
169 | irq = irq_of_parse_and_map(np, 0); | |
d95dc9e3 | 170 | if (!irq) { |
eafaebd9 | 171 | dev_err(dev, "Invalid rst_ind interrupt (%d)\n", irq); |
d95dc9e3 | 172 | return -EINVAL; |
eafaebd9 SR |
173 | } |
174 | modem->nokia_modem_rst_ind_irq = irq; | |
175 | pflags = irq_get_trigger_type(irq); | |
176 | ||
177 | tasklet_init(&modem->nokia_modem_rst_ind_tasklet, | |
178 | do_nokia_modem_rst_ind_tasklet, (unsigned long)modem); | |
179 | err = devm_request_irq(dev, irq, nokia_modem_rst_ind_isr, | |
a26a4250 | 180 | pflags, "modem_rst_ind", modem); |
eafaebd9 SR |
181 | if (err < 0) { |
182 | dev_err(dev, "Request rst_ind irq(%d) failed (flags %d)\n", | |
183 | irq, pflags); | |
184 | return err; | |
185 | } | |
186 | enable_irq_wake(irq); | |
187 | ||
188 | if(pm) { | |
189 | err = nokia_modem_gpio_probe(dev); | |
190 | if (err < 0) { | |
191 | dev_err(dev, "Could not probe GPIOs\n"); | |
192 | goto error1; | |
193 | } | |
194 | } | |
195 | ||
196 | ssip.name = "ssi-protocol"; | |
197 | ssip.tx_cfg = cl->tx_cfg; | |
198 | ssip.rx_cfg = cl->rx_cfg; | |
199 | ssip.platform_data = NULL; | |
200 | ssip.archdata = NULL; | |
201 | ||
202 | modem->ssi_protocol = hsi_new_client(port, &ssip); | |
203 | if (!modem->ssi_protocol) { | |
204 | dev_err(dev, "Could not register ssi-protocol device\n"); | |
b2249129 | 205 | err = -ENOMEM; |
eafaebd9 SR |
206 | goto error2; |
207 | } | |
208 | ||
209 | err = device_attach(&modem->ssi_protocol->device); | |
210 | if (err == 0) { | |
505875e1 | 211 | dev_dbg(dev, "Missing ssi-protocol driver\n"); |
eafaebd9 SR |
212 | err = -EPROBE_DEFER; |
213 | goto error3; | |
214 | } else if (err < 0) { | |
215 | dev_err(dev, "Could not load ssi-protocol driver (%d)\n", err); | |
216 | goto error3; | |
217 | } | |
218 | ||
f9c0d76e SR |
219 | cmtspeech.name = "cmt-speech"; |
220 | cmtspeech.tx_cfg = cl->tx_cfg; | |
221 | cmtspeech.rx_cfg = cl->rx_cfg; | |
222 | cmtspeech.platform_data = NULL; | |
223 | cmtspeech.archdata = NULL; | |
224 | ||
225 | modem->cmt_speech = hsi_new_client(port, &cmtspeech); | |
226 | if (!modem->cmt_speech) { | |
227 | dev_err(dev, "Could not register cmt-speech device\n"); | |
228 | err = -ENOMEM; | |
229 | goto error3; | |
230 | } | |
231 | ||
232 | err = device_attach(&modem->cmt_speech->device); | |
233 | if (err == 0) { | |
505875e1 | 234 | dev_dbg(dev, "Missing cmt-speech driver\n"); |
f9c0d76e SR |
235 | err = -EPROBE_DEFER; |
236 | goto error4; | |
237 | } else if (err < 0) { | |
238 | dev_err(dev, "Could not load cmt-speech driver (%d)\n", err); | |
239 | goto error4; | |
240 | } | |
eafaebd9 SR |
241 | |
242 | dev_info(dev, "Registered Nokia HSI modem\n"); | |
243 | ||
244 | return 0; | |
245 | ||
f9c0d76e SR |
246 | error4: |
247 | hsi_remove_client(&modem->cmt_speech->device, NULL); | |
eafaebd9 SR |
248 | error3: |
249 | hsi_remove_client(&modem->ssi_protocol->device, NULL); | |
250 | error2: | |
251 | nokia_modem_gpio_unexport(dev); | |
252 | error1: | |
253 | disable_irq_wake(modem->nokia_modem_rst_ind_irq); | |
254 | tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); | |
255 | ||
256 | return err; | |
257 | } | |
258 | ||
259 | static int nokia_modem_remove(struct device *dev) | |
260 | { | |
261 | struct nokia_modem_device *modem = dev_get_drvdata(dev); | |
262 | ||
263 | if (!modem) | |
264 | return 0; | |
265 | ||
f9c0d76e SR |
266 | if (modem->cmt_speech) { |
267 | hsi_remove_client(&modem->cmt_speech->device, NULL); | |
268 | modem->cmt_speech = NULL; | |
269 | } | |
270 | ||
eafaebd9 SR |
271 | if (modem->ssi_protocol) { |
272 | hsi_remove_client(&modem->ssi_protocol->device, NULL); | |
273 | modem->ssi_protocol = NULL; | |
274 | } | |
275 | ||
276 | nokia_modem_gpio_unexport(dev); | |
277 | dev_set_drvdata(dev, NULL); | |
278 | disable_irq_wake(modem->nokia_modem_rst_ind_irq); | |
279 | tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); | |
280 | ||
281 | return 0; | |
282 | } | |
283 | ||
284 | #ifdef CONFIG_OF | |
285 | static const struct of_device_id nokia_modem_of_match[] = { | |
286 | { .compatible = "nokia,n900-modem", }, | |
287 | {}, | |
288 | }; | |
289 | MODULE_DEVICE_TABLE(of, nokia_modem_of_match); | |
290 | #endif | |
291 | ||
292 | static struct hsi_client_driver nokia_modem_driver = { | |
293 | .driver = { | |
294 | .name = "nokia-modem", | |
295 | .owner = THIS_MODULE, | |
296 | .probe = nokia_modem_probe, | |
297 | .remove = nokia_modem_remove, | |
298 | .of_match_table = of_match_ptr(nokia_modem_of_match), | |
299 | }, | |
300 | }; | |
301 | ||
302 | static int __init nokia_modem_init(void) | |
303 | { | |
304 | return hsi_register_client_driver(&nokia_modem_driver); | |
305 | } | |
306 | module_init(nokia_modem_init); | |
307 | ||
308 | static void __exit nokia_modem_exit(void) | |
309 | { | |
310 | hsi_unregister_client_driver(&nokia_modem_driver); | |
311 | } | |
312 | module_exit(nokia_modem_exit); | |
313 | ||
314 | MODULE_ALIAS("hsi:nokia-modem"); | |
315 | MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); | |
316 | MODULE_DESCRIPTION("HSI driver module for Nokia N900 Modem"); | |
317 | MODULE_LICENSE("GPL"); |