Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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 | |
Sebastian Reichel | cdb8394 | 2014-11-14 23:33:08 +0100 | [diff] [blame] | 32 | static unsigned int pm = 1; |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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; |
Sebastian Reichel | f9c0d76 | 2015-03-03 01:14:41 +0100 | [diff] [blame] | 49 | struct hsi_client *cmt_speech; |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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++) { |
Uwe Kleine-König | f451e76 | 2015-06-08 11:53:45 +0200 | [diff] [blame] | 115 | modem->gpios[i].gpio = devm_gpiod_get_index(dev, NULL, i, |
| 116 | GPIOD_OUT_LOW); |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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 | |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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; |
Sebastian Reichel | f9c0d76 | 2015-03-03 01:14:41 +0100 | [diff] [blame] | 150 | struct hsi_board_info cmtspeech; |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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); |
Aaro Koskinen | 67e9a2c | 2015-01-04 19:34:13 +0200 | [diff] [blame] | 164 | modem->device = dev; |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 165 | |
| 166 | irq = irq_of_parse_and_map(np, 0); |
Dmitry Torokhov | d95dc9e | 2014-11-14 14:06:37 -0800 | [diff] [blame] | 167 | if (!irq) { |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 168 | dev_err(dev, "Invalid rst_ind interrupt (%d)\n", irq); |
Dmitry Torokhov | d95dc9e | 2014-11-14 14:06:37 -0800 | [diff] [blame] | 169 | return -EINVAL; |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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, |
Michael Opdenacker | a26a425 | 2014-10-01 22:28:55 +0200 | [diff] [blame] | 177 | pflags, "modem_rst_ind", modem); |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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"); |
Julia Lawall | b224912 | 2014-11-22 15:39:16 +0100 | [diff] [blame] | 202 | err = -ENOMEM; |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 203 | goto error2; |
| 204 | } |
| 205 | |
| 206 | err = device_attach(&modem->ssi_protocol->device); |
| 207 | if (err == 0) { |
Sebastian Reichel | 505875e | 2015-05-04 15:51:36 +0200 | [diff] [blame] | 208 | dev_dbg(dev, "Missing ssi-protocol driver\n"); |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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 | |
Sebastian Reichel | f9c0d76 | 2015-03-03 01:14:41 +0100 | [diff] [blame] | 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) { |
Sebastian Reichel | 505875e | 2015-05-04 15:51:36 +0200 | [diff] [blame] | 231 | dev_dbg(dev, "Missing cmt-speech driver\n"); |
Sebastian Reichel | f9c0d76 | 2015-03-03 01:14:41 +0100 | [diff] [blame] | 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 | } |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 238 | |
| 239 | dev_info(dev, "Registered Nokia HSI modem\n"); |
| 240 | |
| 241 | return 0; |
| 242 | |
Sebastian Reichel | f9c0d76 | 2015-03-03 01:14:41 +0100 | [diff] [blame] | 243 | error4: |
| 244 | hsi_remove_client(&modem->cmt_speech->device, NULL); |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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 | |
Sebastian Reichel | f9c0d76 | 2015-03-03 01:14:41 +0100 | [diff] [blame] | 263 | if (modem->cmt_speech) { |
| 264 | hsi_remove_client(&modem->cmt_speech->device, NULL); |
| 265 | modem->cmt_speech = NULL; |
| 266 | } |
| 267 | |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 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", }, |
Sebastian Reichel | 633f67a | 2016-01-17 16:49:07 +0100 | [diff] [blame] | 284 | { .compatible = "nokia,n950-modem", }, |
| 285 | { .compatible = "nokia,n9-modem", }, |
Sebastian Reichel | eafaebd | 2014-03-28 20:19:44 +0100 | [diff] [blame] | 286 | {}, |
| 287 | }; |
| 288 | MODULE_DEVICE_TABLE(of, nokia_modem_of_match); |
| 289 | #endif |
| 290 | |
| 291 | static struct hsi_client_driver nokia_modem_driver = { |
| 292 | .driver = { |
| 293 | .name = "nokia-modem", |
| 294 | .owner = THIS_MODULE, |
| 295 | .probe = nokia_modem_probe, |
| 296 | .remove = nokia_modem_remove, |
| 297 | .of_match_table = of_match_ptr(nokia_modem_of_match), |
| 298 | }, |
| 299 | }; |
| 300 | |
| 301 | static int __init nokia_modem_init(void) |
| 302 | { |
| 303 | return hsi_register_client_driver(&nokia_modem_driver); |
| 304 | } |
| 305 | module_init(nokia_modem_init); |
| 306 | |
| 307 | static void __exit nokia_modem_exit(void) |
| 308 | { |
| 309 | hsi_unregister_client_driver(&nokia_modem_driver); |
| 310 | } |
| 311 | module_exit(nokia_modem_exit); |
| 312 | |
| 313 | MODULE_ALIAS("hsi:nokia-modem"); |
| 314 | MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>"); |
| 315 | MODULE_DESCRIPTION("HSI driver module for Nokia N900 Modem"); |
| 316 | MODULE_LICENSE("GPL"); |