diff options
| -rw-r--r-- | Documentation/devicetree/bindings/hsi/nokia-modem.txt | 57 | ||||
| -rw-r--r-- | drivers/hsi/clients/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/hsi/clients/Makefile | 1 | ||||
| -rw-r--r-- | drivers/hsi/clients/nokia-modem.c | 285 |
4 files changed, 352 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/hsi/nokia-modem.txt b/Documentation/devicetree/bindings/hsi/nokia-modem.txt new file mode 100644 index 000000000000..8a979780452b --- /dev/null +++ b/Documentation/devicetree/bindings/hsi/nokia-modem.txt | |||
| @@ -0,0 +1,57 @@ | |||
| 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 | }; | ||
diff --git a/drivers/hsi/clients/Kconfig b/drivers/hsi/clients/Kconfig index 1457cfb5b453..71b9f9ab86e4 100644 --- a/drivers/hsi/clients/Kconfig +++ b/drivers/hsi/clients/Kconfig | |||
| @@ -4,6 +4,15 @@ | |||
| 4 | 4 | ||
| 5 | comment "HSI clients" | 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 | config SSI_PROTOCOL | 16 | config SSI_PROTOCOL |
| 8 | tristate "SSI protocol" | 17 | tristate "SSI protocol" |
| 9 | depends on HSI && PHONET && (OMAP_SSI=y || OMAP_SSI=m) | 18 | depends on HSI && PHONET && (OMAP_SSI=y || OMAP_SSI=m) |
diff --git a/drivers/hsi/clients/Makefile b/drivers/hsi/clients/Makefile index ccbf768ea42b..4d5bc0e0b27b 100644 --- a/drivers/hsi/clients/Makefile +++ b/drivers/hsi/clients/Makefile | |||
| @@ -2,5 +2,6 @@ | |||
| 2 | # Makefile for HSI clients | 2 | # Makefile for HSI clients |
| 3 | # | 3 | # |
| 4 | 4 | ||
| 5 | obj-$(CONFIG_NOKIA_MODEM) += nokia-modem.o | ||
| 5 | obj-$(CONFIG_SSI_PROTOCOL) += ssi_protocol.o | 6 | obj-$(CONFIG_SSI_PROTOCOL) += ssi_protocol.o |
| 6 | obj-$(CONFIG_HSI_CHAR) += hsi_char.o | 7 | obj-$(CONFIG_HSI_CHAR) += hsi_char.o |
diff --git a/drivers/hsi/clients/nokia-modem.c b/drivers/hsi/clients/nokia-modem.c new file mode 100644 index 000000000000..363b780dacea --- /dev/null +++ b/drivers/hsi/clients/nokia-modem.c | |||
| @@ -0,0 +1,285 @@ | |||
| 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"); | ||
