18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * nokia-modem.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * HSI client driver for Nokia N900 modem.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (C) 2014 Sebastian Reichel <sre@kernel.org>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
118c2ecf20Sopenharmony_ci#include <linux/hsi/hsi.h>
128c2ecf20Sopenharmony_ci#include <linux/init.h>
138c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
148c2ecf20Sopenharmony_ci#include <linux/of.h>
158c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
168c2ecf20Sopenharmony_ci#include <linux/of_gpio.h>
178c2ecf20Sopenharmony_ci#include <linux/hsi/ssi_protocol.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic unsigned int pm = 1;
208c2ecf20Sopenharmony_cimodule_param(pm, int, 0400);
218c2ecf20Sopenharmony_ciMODULE_PARM_DESC(pm,
228c2ecf20Sopenharmony_ci	"Enable power management (0=disabled, 1=userland based [default])");
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistruct nokia_modem_gpio {
258c2ecf20Sopenharmony_ci	struct gpio_desc	*gpio;
268c2ecf20Sopenharmony_ci	const char		*name;
278c2ecf20Sopenharmony_ci};
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct nokia_modem_device {
308c2ecf20Sopenharmony_ci	struct tasklet_struct	nokia_modem_rst_ind_tasklet;
318c2ecf20Sopenharmony_ci	int			nokia_modem_rst_ind_irq;
328c2ecf20Sopenharmony_ci	struct device		*device;
338c2ecf20Sopenharmony_ci	struct nokia_modem_gpio	*gpios;
348c2ecf20Sopenharmony_ci	int			gpio_amount;
358c2ecf20Sopenharmony_ci	struct hsi_client	*ssi_protocol;
368c2ecf20Sopenharmony_ci	struct hsi_client	*cmt_speech;
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic void do_nokia_modem_rst_ind_tasklet(unsigned long data)
408c2ecf20Sopenharmony_ci{
418c2ecf20Sopenharmony_ci	struct nokia_modem_device *modem = (struct nokia_modem_device *)data;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	if (!modem)
448c2ecf20Sopenharmony_ci		return;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	dev_info(modem->device, "CMT rst line change detected\n");
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	if (modem->ssi_protocol)
498c2ecf20Sopenharmony_ci		ssip_reset_event(modem->ssi_protocol);
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic irqreturn_t nokia_modem_rst_ind_isr(int irq, void *data)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	struct nokia_modem_device *modem = (struct nokia_modem_device *)data;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	tasklet_schedule(&modem->nokia_modem_rst_ind_tasklet);
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic void nokia_modem_gpio_unexport(struct device *dev)
628c2ecf20Sopenharmony_ci{
638c2ecf20Sopenharmony_ci	struct nokia_modem_device *modem = dev_get_drvdata(dev);
648c2ecf20Sopenharmony_ci	int i;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	for (i = 0; i < modem->gpio_amount; i++) {
678c2ecf20Sopenharmony_ci		sysfs_remove_link(&dev->kobj, modem->gpios[i].name);
688c2ecf20Sopenharmony_ci		gpiod_unexport(modem->gpios[i].gpio);
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic int nokia_modem_gpio_probe(struct device *dev)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	struct device_node *np = dev->of_node;
758c2ecf20Sopenharmony_ci	struct nokia_modem_device *modem = dev_get_drvdata(dev);
768c2ecf20Sopenharmony_ci	int gpio_count, gpio_name_count, i, err;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	gpio_count = of_gpio_count(np);
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	if (gpio_count < 0) {
818c2ecf20Sopenharmony_ci		dev_err(dev, "missing gpios: %d\n", gpio_count);
828c2ecf20Sopenharmony_ci		return gpio_count;
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	gpio_name_count = of_property_count_strings(np, "gpio-names");
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	if (gpio_count != gpio_name_count) {
888c2ecf20Sopenharmony_ci		dev_err(dev, "number of gpios does not equal number of gpio names\n");
898c2ecf20Sopenharmony_ci		return -EINVAL;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	modem->gpios = devm_kcalloc(dev, gpio_count, sizeof(*modem->gpios),
938c2ecf20Sopenharmony_ci				    GFP_KERNEL);
948c2ecf20Sopenharmony_ci	if (!modem->gpios)
958c2ecf20Sopenharmony_ci		return -ENOMEM;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	modem->gpio_amount = gpio_count;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	for (i = 0; i < gpio_count; i++) {
1008c2ecf20Sopenharmony_ci		modem->gpios[i].gpio = devm_gpiod_get_index(dev, NULL, i,
1018c2ecf20Sopenharmony_ci							    GPIOD_OUT_LOW);
1028c2ecf20Sopenharmony_ci		if (IS_ERR(modem->gpios[i].gpio)) {
1038c2ecf20Sopenharmony_ci			dev_err(dev, "Could not get gpio %d\n", i);
1048c2ecf20Sopenharmony_ci			return PTR_ERR(modem->gpios[i].gpio);
1058c2ecf20Sopenharmony_ci		}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci		err = of_property_read_string_index(np, "gpio-names", i,
1088c2ecf20Sopenharmony_ci						&(modem->gpios[i].name));
1098c2ecf20Sopenharmony_ci		if (err) {
1108c2ecf20Sopenharmony_ci			dev_err(dev, "Could not get gpio name %d\n", i);
1118c2ecf20Sopenharmony_ci			return err;
1128c2ecf20Sopenharmony_ci		}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci		err = gpiod_export(modem->gpios[i].gpio, 0);
1158c2ecf20Sopenharmony_ci		if (err)
1168c2ecf20Sopenharmony_ci			return err;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci		err = gpiod_export_link(dev, modem->gpios[i].name,
1198c2ecf20Sopenharmony_ci							modem->gpios[i].gpio);
1208c2ecf20Sopenharmony_ci		if (err)
1218c2ecf20Sopenharmony_ci			return err;
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	return 0;
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistatic int nokia_modem_probe(struct device *dev)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	struct device_node *np;
1308c2ecf20Sopenharmony_ci	struct nokia_modem_device *modem;
1318c2ecf20Sopenharmony_ci	struct hsi_client *cl = to_hsi_client(dev);
1328c2ecf20Sopenharmony_ci	struct hsi_port *port = hsi_get_port(cl);
1338c2ecf20Sopenharmony_ci	int irq, pflags, err;
1348c2ecf20Sopenharmony_ci	struct hsi_board_info ssip;
1358c2ecf20Sopenharmony_ci	struct hsi_board_info cmtspeech;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	np = dev->of_node;
1388c2ecf20Sopenharmony_ci	if (!np) {
1398c2ecf20Sopenharmony_ci		dev_err(dev, "device tree node not found\n");
1408c2ecf20Sopenharmony_ci		return -ENXIO;
1418c2ecf20Sopenharmony_ci	}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	modem = devm_kzalloc(dev, sizeof(*modem), GFP_KERNEL);
1448c2ecf20Sopenharmony_ci	if (!modem)
1458c2ecf20Sopenharmony_ci		return -ENOMEM;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, modem);
1488c2ecf20Sopenharmony_ci	modem->device = dev;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	irq = irq_of_parse_and_map(np, 0);
1518c2ecf20Sopenharmony_ci	if (!irq) {
1528c2ecf20Sopenharmony_ci		dev_err(dev, "Invalid rst_ind interrupt (%d)\n", irq);
1538c2ecf20Sopenharmony_ci		return -EINVAL;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci	modem->nokia_modem_rst_ind_irq = irq;
1568c2ecf20Sopenharmony_ci	pflags = irq_get_trigger_type(irq);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	tasklet_init(&modem->nokia_modem_rst_ind_tasklet,
1598c2ecf20Sopenharmony_ci			do_nokia_modem_rst_ind_tasklet, (unsigned long)modem);
1608c2ecf20Sopenharmony_ci	err = devm_request_irq(dev, irq, nokia_modem_rst_ind_isr,
1618c2ecf20Sopenharmony_ci				pflags, "modem_rst_ind", modem);
1628c2ecf20Sopenharmony_ci	if (err < 0) {
1638c2ecf20Sopenharmony_ci		dev_err(dev, "Request rst_ind irq(%d) failed (flags %d)\n",
1648c2ecf20Sopenharmony_ci								irq, pflags);
1658c2ecf20Sopenharmony_ci		return err;
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci	enable_irq_wake(irq);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	if (pm) {
1708c2ecf20Sopenharmony_ci		err = nokia_modem_gpio_probe(dev);
1718c2ecf20Sopenharmony_ci		if (err < 0) {
1728c2ecf20Sopenharmony_ci			dev_err(dev, "Could not probe GPIOs\n");
1738c2ecf20Sopenharmony_ci			goto error1;
1748c2ecf20Sopenharmony_ci		}
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	ssip.name = "ssi-protocol";
1788c2ecf20Sopenharmony_ci	ssip.tx_cfg = cl->tx_cfg;
1798c2ecf20Sopenharmony_ci	ssip.rx_cfg = cl->rx_cfg;
1808c2ecf20Sopenharmony_ci	ssip.platform_data = NULL;
1818c2ecf20Sopenharmony_ci	ssip.archdata = NULL;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	modem->ssi_protocol = hsi_new_client(port, &ssip);
1848c2ecf20Sopenharmony_ci	if (!modem->ssi_protocol) {
1858c2ecf20Sopenharmony_ci		dev_err(dev, "Could not register ssi-protocol device\n");
1868c2ecf20Sopenharmony_ci		err = -ENOMEM;
1878c2ecf20Sopenharmony_ci		goto error2;
1888c2ecf20Sopenharmony_ci	}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	err = device_attach(&modem->ssi_protocol->device);
1918c2ecf20Sopenharmony_ci	if (err == 0) {
1928c2ecf20Sopenharmony_ci		dev_dbg(dev, "Missing ssi-protocol driver\n");
1938c2ecf20Sopenharmony_ci		err = -EPROBE_DEFER;
1948c2ecf20Sopenharmony_ci		goto error3;
1958c2ecf20Sopenharmony_ci	} else if (err < 0) {
1968c2ecf20Sopenharmony_ci		dev_err(dev, "Could not load ssi-protocol driver (%d)\n", err);
1978c2ecf20Sopenharmony_ci		goto error3;
1988c2ecf20Sopenharmony_ci	}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	cmtspeech.name = "cmt-speech";
2018c2ecf20Sopenharmony_ci	cmtspeech.tx_cfg = cl->tx_cfg;
2028c2ecf20Sopenharmony_ci	cmtspeech.rx_cfg = cl->rx_cfg;
2038c2ecf20Sopenharmony_ci	cmtspeech.platform_data = NULL;
2048c2ecf20Sopenharmony_ci	cmtspeech.archdata = NULL;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	modem->cmt_speech = hsi_new_client(port, &cmtspeech);
2078c2ecf20Sopenharmony_ci	if (!modem->cmt_speech) {
2088c2ecf20Sopenharmony_ci		dev_err(dev, "Could not register cmt-speech device\n");
2098c2ecf20Sopenharmony_ci		err = -ENOMEM;
2108c2ecf20Sopenharmony_ci		goto error3;
2118c2ecf20Sopenharmony_ci	}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	err = device_attach(&modem->cmt_speech->device);
2148c2ecf20Sopenharmony_ci	if (err == 0) {
2158c2ecf20Sopenharmony_ci		dev_dbg(dev, "Missing cmt-speech driver\n");
2168c2ecf20Sopenharmony_ci		err = -EPROBE_DEFER;
2178c2ecf20Sopenharmony_ci		goto error4;
2188c2ecf20Sopenharmony_ci	} else if (err < 0) {
2198c2ecf20Sopenharmony_ci		dev_err(dev, "Could not load cmt-speech driver (%d)\n", err);
2208c2ecf20Sopenharmony_ci		goto error4;
2218c2ecf20Sopenharmony_ci	}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	dev_info(dev, "Registered Nokia HSI modem\n");
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	return 0;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cierror4:
2288c2ecf20Sopenharmony_ci	hsi_remove_client(&modem->cmt_speech->device, NULL);
2298c2ecf20Sopenharmony_cierror3:
2308c2ecf20Sopenharmony_ci	hsi_remove_client(&modem->ssi_protocol->device, NULL);
2318c2ecf20Sopenharmony_cierror2:
2328c2ecf20Sopenharmony_ci	nokia_modem_gpio_unexport(dev);
2338c2ecf20Sopenharmony_cierror1:
2348c2ecf20Sopenharmony_ci	disable_irq_wake(modem->nokia_modem_rst_ind_irq);
2358c2ecf20Sopenharmony_ci	tasklet_kill(&modem->nokia_modem_rst_ind_tasklet);
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	return err;
2388c2ecf20Sopenharmony_ci}
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_cistatic int nokia_modem_remove(struct device *dev)
2418c2ecf20Sopenharmony_ci{
2428c2ecf20Sopenharmony_ci	struct nokia_modem_device *modem = dev_get_drvdata(dev);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	if (!modem)
2458c2ecf20Sopenharmony_ci		return 0;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	if (modem->cmt_speech) {
2488c2ecf20Sopenharmony_ci		hsi_remove_client(&modem->cmt_speech->device, NULL);
2498c2ecf20Sopenharmony_ci		modem->cmt_speech = NULL;
2508c2ecf20Sopenharmony_ci	}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	if (modem->ssi_protocol) {
2538c2ecf20Sopenharmony_ci		hsi_remove_client(&modem->ssi_protocol->device, NULL);
2548c2ecf20Sopenharmony_ci		modem->ssi_protocol = NULL;
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	nokia_modem_gpio_unexport(dev);
2588c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, NULL);
2598c2ecf20Sopenharmony_ci	disable_irq_wake(modem->nokia_modem_rst_ind_irq);
2608c2ecf20Sopenharmony_ci	tasklet_kill(&modem->nokia_modem_rst_ind_tasklet);
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	return 0;
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
2668c2ecf20Sopenharmony_cistatic const struct of_device_id nokia_modem_of_match[] = {
2678c2ecf20Sopenharmony_ci	{ .compatible = "nokia,n900-modem", },
2688c2ecf20Sopenharmony_ci	{ .compatible = "nokia,n950-modem", },
2698c2ecf20Sopenharmony_ci	{ .compatible = "nokia,n9-modem", },
2708c2ecf20Sopenharmony_ci	{},
2718c2ecf20Sopenharmony_ci};
2728c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, nokia_modem_of_match);
2738c2ecf20Sopenharmony_ci#endif
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_cistatic struct hsi_client_driver nokia_modem_driver = {
2768c2ecf20Sopenharmony_ci	.driver = {
2778c2ecf20Sopenharmony_ci		.name	= "nokia-modem",
2788c2ecf20Sopenharmony_ci		.owner	= THIS_MODULE,
2798c2ecf20Sopenharmony_ci		.probe	= nokia_modem_probe,
2808c2ecf20Sopenharmony_ci		.remove	= nokia_modem_remove,
2818c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(nokia_modem_of_match),
2828c2ecf20Sopenharmony_ci	},
2838c2ecf20Sopenharmony_ci};
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_cistatic int __init nokia_modem_init(void)
2868c2ecf20Sopenharmony_ci{
2878c2ecf20Sopenharmony_ci	return hsi_register_client_driver(&nokia_modem_driver);
2888c2ecf20Sopenharmony_ci}
2898c2ecf20Sopenharmony_cimodule_init(nokia_modem_init);
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_cistatic void __exit nokia_modem_exit(void)
2928c2ecf20Sopenharmony_ci{
2938c2ecf20Sopenharmony_ci	hsi_unregister_client_driver(&nokia_modem_driver);
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_cimodule_exit(nokia_modem_exit);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ciMODULE_ALIAS("hsi:nokia-modem");
2988c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
2998c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HSI driver module for Nokia N900 Modem");
3008c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
301