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++) { | |
f451e76f UKK |
115 | modem->gpios[i].gpio = devm_gpiod_get_index(dev, NULL, i, |
116 | GPIOD_OUT_LOW); | |
eafaebd9 SR |
117 | if (IS_ERR(modem->gpios[i].gpio)) { |
118 | dev_err(dev, "Could not get gpio %d\n", i); | |
119 | return PTR_ERR(modem->gpios[i].gpio); | |
120 | } | |
121 | ||
122 | err = of_property_read_string_index(np, "gpio-names", i, | |
123 | &(modem->gpios[i].name)); | |
124 | if (err) { | |
125 | dev_err(dev, "Could not get gpio name %d\n", i); | |
126 | return err; | |
127 | } | |
128 | ||
eafaebd9 SR |
129 | err = gpiod_export(modem->gpios[i].gpio, 0); |
130 | if (err) | |
131 | return err; | |
132 | ||
133 | err = gpiod_export_link(dev, modem->gpios[i].name, | |
134 | modem->gpios[i].gpio); | |
135 | if (err) | |
136 | return err; | |
137 | } | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
142 | static int nokia_modem_probe(struct device *dev) | |
143 | { | |
144 | struct device_node *np; | |
145 | struct nokia_modem_device *modem; | |
146 | struct hsi_client *cl = to_hsi_client(dev); | |
147 | struct hsi_port *port = hsi_get_port(cl); | |
148 | int irq, pflags, err; | |
149 | struct hsi_board_info ssip; | |
f9c0d76e | 150 | struct hsi_board_info cmtspeech; |
eafaebd9 SR |
151 | |
152 | np = dev->of_node; | |
153 | if (!np) { | |
154 | dev_err(dev, "device tree node not found\n"); | |
155 | return -ENXIO; | |
156 | } | |
157 | ||
158 | modem = devm_kzalloc(dev, sizeof(*modem), GFP_KERNEL); | |
159 | if (!modem) { | |
160 | dev_err(dev, "Could not allocate memory for nokia_modem_device\n"); | |
161 | return -ENOMEM; | |
162 | } | |
163 | dev_set_drvdata(dev, modem); | |
67e9a2ce | 164 | modem->device = dev; |
eafaebd9 SR |
165 | |
166 | irq = irq_of_parse_and_map(np, 0); | |
d95dc9e3 | 167 | if (!irq) { |
eafaebd9 | 168 | dev_err(dev, "Invalid rst_ind interrupt (%d)\n", irq); |
d95dc9e3 | 169 | return -EINVAL; |
eafaebd9 SR |
170 | } |
171 | modem->nokia_modem_rst_ind_irq = irq; | |
172 | pflags = irq_get_trigger_type(irq); | |
173 | ||
174 | tasklet_init(&modem->nokia_modem_rst_ind_tasklet, | |
175 | do_nokia_modem_rst_ind_tasklet, (unsigned long)modem); | |
176 | err = devm_request_irq(dev, irq, nokia_modem_rst_ind_isr, | |
a26a4250 | 177 | pflags, "modem_rst_ind", modem); |
eafaebd9 SR |
178 | if (err < 0) { |
179 | dev_err(dev, "Request rst_ind irq(%d) failed (flags %d)\n", | |
180 | irq, pflags); | |
181 | return err; | |
182 | } | |
183 | enable_irq_wake(irq); | |
184 | ||
185 | if(pm) { | |
186 | err = nokia_modem_gpio_probe(dev); | |
187 | if (err < 0) { | |
188 | dev_err(dev, "Could not probe GPIOs\n"); | |
189 | goto error1; | |
190 | } | |
191 | } | |
192 | ||
193 | ssip.name = "ssi-protocol"; | |
194 | ssip.tx_cfg = cl->tx_cfg; | |
195 | ssip.rx_cfg = cl->rx_cfg; | |
196 | ssip.platform_data = NULL; | |
197 | ssip.archdata = NULL; | |
198 | ||
199 | modem->ssi_protocol = hsi_new_client(port, &ssip); | |
200 | if (!modem->ssi_protocol) { | |
201 | dev_err(dev, "Could not register ssi-protocol device\n"); | |
b2249129 | 202 | err = -ENOMEM; |
eafaebd9 SR |
203 | goto error2; |
204 | } | |
205 | ||
206 | err = device_attach(&modem->ssi_protocol->device); | |
207 | if (err == 0) { | |
505875e1 | 208 | dev_dbg(dev, "Missing ssi-protocol driver\n"); |
eafaebd9 SR |
209 | err = -EPROBE_DEFER; |
210 | goto error3; | |
211 | } else if (err < 0) { | |
212 | dev_err(dev, "Could not load ssi-protocol driver (%d)\n", err); | |
213 | goto error3; | |
214 | } | |
215 | ||
f9c0d76e SR |
216 | cmtspeech.name = "cmt-speech"; |
217 | cmtspeech.tx_cfg = cl->tx_cfg; | |
218 | cmtspeech.rx_cfg = cl->rx_cfg; | |
219 | cmtspeech.platform_data = NULL; | |
220 | cmtspeech.archdata = NULL; | |
221 | ||
222 | modem->cmt_speech = hsi_new_client(port, &cmtspeech); | |
223 | if (!modem->cmt_speech) { | |
224 | dev_err(dev, "Could not register cmt-speech device\n"); | |
225 | err = -ENOMEM; | |
226 | goto error3; | |
227 | } | |
228 | ||
229 | err = device_attach(&modem->cmt_speech->device); | |
230 | if (err == 0) { | |
505875e1 | 231 | dev_dbg(dev, "Missing cmt-speech driver\n"); |
f9c0d76e SR |
232 | err = -EPROBE_DEFER; |
233 | goto error4; | |
234 | } else if (err < 0) { | |
235 | dev_err(dev, "Could not load cmt-speech driver (%d)\n", err); | |
236 | goto error4; | |
237 | } | |
eafaebd9 SR |
238 | |
239 | dev_info(dev, "Registered Nokia HSI modem\n"); | |
240 | ||
241 | return 0; | |
242 | ||
f9c0d76e SR |
243 | error4: |
244 | hsi_remove_client(&modem->cmt_speech->device, NULL); | |
eafaebd9 SR |
245 | error3: |
246 | hsi_remove_client(&modem->ssi_protocol->device, NULL); | |
247 | error2: | |
248 | nokia_modem_gpio_unexport(dev); | |
249 | error1: | |
250 | disable_irq_wake(modem->nokia_modem_rst_ind_irq); | |
251 | tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); | |
252 | ||
253 | return err; | |
254 | } | |
255 | ||
256 | static int nokia_modem_remove(struct device *dev) | |
257 | { | |
258 | struct nokia_modem_device *modem = dev_get_drvdata(dev); | |
259 | ||
260 | if (!modem) | |
261 | return 0; | |
262 | ||
f9c0d76e SR |
263 | if (modem->cmt_speech) { |
264 | hsi_remove_client(&modem->cmt_speech->device, NULL); | |
265 | modem->cmt_speech = NULL; | |
266 | } | |
267 | ||
eafaebd9 SR |
268 | if (modem->ssi_protocol) { |
269 | hsi_remove_client(&modem->ssi_protocol->device, NULL); | |
270 | modem->ssi_protocol = NULL; | |
271 | } | |
272 | ||
273 | nokia_modem_gpio_unexport(dev); | |
274 | dev_set_drvdata(dev, NULL); | |
275 | disable_irq_wake(modem->nokia_modem_rst_ind_irq); | |
276 | tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); | |
277 | ||
278 | return 0; | |
279 | } | |
280 | ||
281 | #ifdef CONFIG_OF | |
282 | static const struct of_device_id nokia_modem_of_match[] = { | |
283 | { .compatible = "nokia,n900-modem", }, | |
284 | {}, | |
285 | }; | |
286 | MODULE_DEVICE_TABLE(of, nokia_modem_of_match); | |
287 | #endif | |
288 | ||
289 | static struct hsi_client_driver nokia_modem_driver = { | |
290 | .driver = { | |
291 | .name = "nokia-modem", | |
292 | .owner = THIS_MODULE, | |
293 | .probe = nokia_modem_probe, | |
294 | .remove = nokia_modem_remove, | |
295 | .of_match_table = of_match_ptr(nokia_modem_of_match), | |
296 | }, | |
297 | }; | |
298 | ||
299 | static int __init nokia_modem_init(void) | |
300 | { | |
301 | return hsi_register_client_driver(&nokia_modem_driver); | |
302 | } | |
303 | module_init(nokia_modem_init); | |
304 | ||
305 | static void __exit nokia_modem_exit(void) | |
306 | { | |
307 | hsi_unregister_client_driver(&nokia_modem_driver); | |
308 | } | |
309 | module_exit(nokia_modem_exit); | |
310 | ||
311 | MODULE_ALIAS("hsi:nokia-modem"); | |
312 | MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); | |
313 | MODULE_DESCRIPTION("HSI driver module for Nokia N900 Modem"); | |
314 | MODULE_LICENSE("GPL"); |