Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

HSI: Introduce Nokia N900 modem driver

The Nokia N900's modem is connected via Synchronous Serial Interface (SSI),
which is a legacy version of MIPI's High-speed Synchronous Serial Interface
(HSI).

The handles the GPIOs for enabling and resetting the modem and instanciates
ssi-protocol for data exchange. It does not yet support exchanging voice data
with the modem.

Signed-off-by: Sebastian Reichel <sre@kernel.org>
Reviewed-by: Pavel Machek <pavel@ucw.cz>
Tested-By: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>

+352
+57
Documentation/devicetree/bindings/hsi/nokia-modem.txt
··· 1 + Nokia modem client bindings 2 + 3 + The Nokia modem HSI client follows the common HSI client binding 4 + and inherits all required properties. The following additional 5 + properties are needed by the Nokia modem HSI client: 6 + 7 + Required properties: 8 + - compatible: Should be one of 9 + "nokia,n900-modem" 10 + - hsi-channel-names: Should contain the following strings 11 + "mcsaab-control" 12 + "speech-control" 13 + "speech-data" 14 + "mcsaab-data" 15 + - gpios: Should provide a GPIO handler for each GPIO listed in 16 + gpio-names 17 + - gpio-names: Should contain the following strings 18 + "cmt_apeslpx" 19 + "cmt_rst_rq" 20 + "cmt_en" 21 + "cmt_rst" 22 + "cmt_bsi" 23 + - interrupts: Should be IRQ handle for modem's reset indication 24 + 25 + Example: 26 + 27 + &ssi_port { 28 + modem: hsi-client { 29 + compatible = "nokia,n900-modem"; 30 + 31 + pinctrl-names = "default"; 32 + pinctrl-0 = <&modem_pins>; 33 + 34 + hsi-channel-ids = <0>, <1>, <2>, <3>; 35 + hsi-channel-names = "mcsaab-control", 36 + "speech-control", 37 + "speech-data", 38 + "mcsaab-data"; 39 + hsi-speed-kbps = <55000>; 40 + hsi-mode = "frame"; 41 + hsi-flow = "synchronized"; 42 + hsi-arb-mode = "round-robin"; 43 + 44 + interrupts-extended = <&gpio3 8 IRQ_TYPE_EDGE_FALLING>; /* 72 */ 45 + 46 + gpios = <&gpio3 6 GPIO_ACTIVE_HIGH>, /* 70 */ 47 + <&gpio3 9 GPIO_ACTIVE_HIGH>, /* 73 */ 48 + <&gpio3 10 GPIO_ACTIVE_HIGH>, /* 74 */ 49 + <&gpio3 11 GPIO_ACTIVE_HIGH>, /* 75 */ 50 + <&gpio5 29 GPIO_ACTIVE_HIGH>; /* 157 */ 51 + gpio-names = "cmt_apeslpx", 52 + "cmt_rst_rq", 53 + "cmt_en", 54 + "cmt_rst", 55 + "cmt_bsi"; 56 + }; 57 + };
+9
drivers/hsi/clients/Kconfig
··· 4 4 5 5 comment "HSI clients" 6 6 7 + config NOKIA_MODEM 8 + tristate "Nokia Modem" 9 + depends on HSI && SSI_PROTOCOL 10 + help 11 + Say Y here if you want to add support for the modem on Nokia 12 + N900 (Nokia RX-51) hardware. 13 + 14 + If unsure, say N. 15 + 7 16 config SSI_PROTOCOL 8 17 tristate "SSI protocol" 9 18 depends on HSI && PHONET && (OMAP_SSI=y || OMAP_SSI=m)
+1
drivers/hsi/clients/Makefile
··· 2 2 # Makefile for HSI clients 3 3 # 4 4 5 + obj-$(CONFIG_NOKIA_MODEM) += nokia-modem.o 5 6 obj-$(CONFIG_SSI_PROTOCOL) += ssi_protocol.o 6 7 obj-$(CONFIG_HSI_CHAR) += hsi_char.o
+285
drivers/hsi/clients/nokia-modem.c
··· 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");