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 | ||
32 | static unsigned int pm; | |
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; | |
49 | }; | |
50 | ||
51 | static void do_nokia_modem_rst_ind_tasklet(unsigned long data) | |
52 | { | |
53 | struct nokia_modem_device *modem = (struct nokia_modem_device *)data; | |
54 | ||
55 | if (!modem) | |
56 | return; | |
57 | ||
58 | dev_info(modem->device, "CMT rst line change detected\n"); | |
59 | ||
60 | if (modem->ssi_protocol) | |
61 | ssip_reset_event(modem->ssi_protocol); | |
62 | } | |
63 | ||
64 | static irqreturn_t nokia_modem_rst_ind_isr(int irq, void *data) | |
65 | { | |
66 | struct nokia_modem_device *modem = (struct nokia_modem_device *)data; | |
67 | ||
68 | tasklet_schedule(&modem->nokia_modem_rst_ind_tasklet); | |
69 | ||
70 | return IRQ_HANDLED; | |
71 | } | |
72 | ||
73 | static void nokia_modem_gpio_unexport(struct device *dev) | |
74 | { | |
75 | struct nokia_modem_device *modem = dev_get_drvdata(dev); | |
76 | int i; | |
77 | ||
78 | for (i = 0; i < modem->gpio_amount; i++) { | |
79 | sysfs_remove_link(&dev->kobj, modem->gpios[i].name); | |
80 | gpiod_unexport(modem->gpios[i].gpio); | |
81 | } | |
82 | } | |
83 | ||
84 | static int nokia_modem_gpio_probe(struct device *dev) | |
85 | { | |
86 | struct device_node *np = dev->of_node; | |
87 | struct nokia_modem_device *modem = dev_get_drvdata(dev); | |
88 | int gpio_count, gpio_name_count, i, err; | |
89 | ||
90 | gpio_count = of_gpio_count(np); | |
91 | ||
92 | if (gpio_count < 0) { | |
93 | dev_err(dev, "missing gpios: %d\n", gpio_count); | |
94 | return gpio_count; | |
95 | } | |
96 | ||
97 | gpio_name_count = of_property_count_strings(np, "gpio-names"); | |
98 | ||
99 | if (gpio_count != gpio_name_count) { | |
100 | dev_err(dev, "number of gpios does not equal number of gpio names\n"); | |
101 | return -EINVAL; | |
102 | } | |
103 | ||
104 | modem->gpios = devm_kzalloc(dev, gpio_count * | |
105 | sizeof(struct nokia_modem_gpio), GFP_KERNEL); | |
106 | if (!modem->gpios) { | |
107 | dev_err(dev, "Could not allocate memory for gpios\n"); | |
108 | return -ENOMEM; | |
109 | } | |
110 | ||
111 | modem->gpio_amount = gpio_count; | |
112 | ||
113 | for (i = 0; i < gpio_count; i++) { | |
114 | modem->gpios[i].gpio = devm_gpiod_get_index(dev, NULL, i); | |
115 | if (IS_ERR(modem->gpios[i].gpio)) { | |
116 | dev_err(dev, "Could not get gpio %d\n", i); | |
117 | return PTR_ERR(modem->gpios[i].gpio); | |
118 | } | |
119 | ||
120 | err = of_property_read_string_index(np, "gpio-names", i, | |
121 | &(modem->gpios[i].name)); | |
122 | if (err) { | |
123 | dev_err(dev, "Could not get gpio name %d\n", i); | |
124 | return err; | |
125 | } | |
126 | ||
127 | err = gpiod_direction_output(modem->gpios[i].gpio, 0); | |
128 | if (err) | |
129 | return err; | |
130 | ||
131 | err = gpiod_export(modem->gpios[i].gpio, 0); | |
132 | if (err) | |
133 | return err; | |
134 | ||
135 | err = gpiod_export_link(dev, modem->gpios[i].name, | |
136 | modem->gpios[i].gpio); | |
137 | if (err) | |
138 | return err; | |
139 | } | |
140 | ||
141 | return 0; | |
142 | } | |
143 | ||
144 | static int nokia_modem_probe(struct device *dev) | |
145 | { | |
146 | struct device_node *np; | |
147 | struct nokia_modem_device *modem; | |
148 | struct hsi_client *cl = to_hsi_client(dev); | |
149 | struct hsi_port *port = hsi_get_port(cl); | |
150 | int irq, pflags, err; | |
151 | struct hsi_board_info ssip; | |
152 | ||
153 | np = dev->of_node; | |
154 | if (!np) { | |
155 | dev_err(dev, "device tree node not found\n"); | |
156 | return -ENXIO; | |
157 | } | |
158 | ||
159 | modem = devm_kzalloc(dev, sizeof(*modem), GFP_KERNEL); | |
160 | if (!modem) { | |
161 | dev_err(dev, "Could not allocate memory for nokia_modem_device\n"); | |
162 | return -ENOMEM; | |
163 | } | |
164 | dev_set_drvdata(dev, modem); | |
165 | ||
166 | irq = irq_of_parse_and_map(np, 0); | |
167 | if (irq < 0) { | |
168 | dev_err(dev, "Invalid rst_ind interrupt (%d)\n", irq); | |
169 | return irq; | |
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, | |
177 | IRQF_DISABLED | pflags, "modem_rst_ind", modem); | |
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"); | |
202 | goto error2; | |
203 | } | |
204 | ||
205 | err = device_attach(&modem->ssi_protocol->device); | |
206 | if (err == 0) { | |
207 | dev_err(dev, "Missing ssi-protocol driver\n"); | |
208 | err = -EPROBE_DEFER; | |
209 | goto error3; | |
210 | } else if (err < 0) { | |
211 | dev_err(dev, "Could not load ssi-protocol driver (%d)\n", err); | |
212 | goto error3; | |
213 | } | |
214 | ||
215 | /* TODO: register cmt-speech hsi client */ | |
216 | ||
217 | dev_info(dev, "Registered Nokia HSI modem\n"); | |
218 | ||
219 | return 0; | |
220 | ||
221 | error3: | |
222 | hsi_remove_client(&modem->ssi_protocol->device, NULL); | |
223 | error2: | |
224 | nokia_modem_gpio_unexport(dev); | |
225 | error1: | |
226 | disable_irq_wake(modem->nokia_modem_rst_ind_irq); | |
227 | tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); | |
228 | ||
229 | return err; | |
230 | } | |
231 | ||
232 | static int nokia_modem_remove(struct device *dev) | |
233 | { | |
234 | struct nokia_modem_device *modem = dev_get_drvdata(dev); | |
235 | ||
236 | if (!modem) | |
237 | return 0; | |
238 | ||
239 | if (modem->ssi_protocol) { | |
240 | hsi_remove_client(&modem->ssi_protocol->device, NULL); | |
241 | modem->ssi_protocol = NULL; | |
242 | } | |
243 | ||
244 | nokia_modem_gpio_unexport(dev); | |
245 | dev_set_drvdata(dev, NULL); | |
246 | disable_irq_wake(modem->nokia_modem_rst_ind_irq); | |
247 | tasklet_kill(&modem->nokia_modem_rst_ind_tasklet); | |
248 | ||
249 | return 0; | |
250 | } | |
251 | ||
252 | #ifdef CONFIG_OF | |
253 | static const struct of_device_id nokia_modem_of_match[] = { | |
254 | { .compatible = "nokia,n900-modem", }, | |
255 | {}, | |
256 | }; | |
257 | MODULE_DEVICE_TABLE(of, nokia_modem_of_match); | |
258 | #endif | |
259 | ||
260 | static struct hsi_client_driver nokia_modem_driver = { | |
261 | .driver = { | |
262 | .name = "nokia-modem", | |
263 | .owner = THIS_MODULE, | |
264 | .probe = nokia_modem_probe, | |
265 | .remove = nokia_modem_remove, | |
266 | .of_match_table = of_match_ptr(nokia_modem_of_match), | |
267 | }, | |
268 | }; | |
269 | ||
270 | static int __init nokia_modem_init(void) | |
271 | { | |
272 | return hsi_register_client_driver(&nokia_modem_driver); | |
273 | } | |
274 | module_init(nokia_modem_init); | |
275 | ||
276 | static void __exit nokia_modem_exit(void) | |
277 | { | |
278 | hsi_unregister_client_driver(&nokia_modem_driver); | |
279 | } | |
280 | module_exit(nokia_modem_exit); | |
281 | ||
282 | MODULE_ALIAS("hsi:nokia-modem"); | |
283 | MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); | |
284 | MODULE_DESCRIPTION("HSI driver module for Nokia N900 Modem"); | |
285 | MODULE_LICENSE("GPL"); |