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