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