diff options
author | Simon Shields <simon@lineageos.org> | 2018-04-05 21:21:20 +1000 |
---|---|---|
committer | Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org> | 2021-11-25 11:05:00 +0100 |
commit | d7f49459e9f480d70f41c48f8e6aa833f2e57490 (patch) | |
tree | fcccbeca690c2d39d12e869389f35c5bc6942b42 | |
parent | ec137f0c80f6c67705c2f293b0e8bc7047741ff0 (diff) | |
download | kernel_replicant_linux-d7f49459e9f480d70f41c48f8e6aa833f2e57490.tar.gz kernel_replicant_linux-d7f49459e9f480d70f41c48f8e6aa833f2e57490.tar.bz2 kernel_replicant_linux-d7f49459e9f480d70f41c48f8e6aa833f2e57490.zip |
HACK: add modem power on/off driver
This should probably be handled in userspace.
Signed-off-by: Simon Shields <simon@lineageos.org>
GNUtoo@cyberdimension.org: rebase, small fixes
Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
-rw-r--r-- | arch/arm/boot/dts/exynos4412-midas-3g.dtsi | 1 | ||||
-rw-r--r-- | drivers/misc/Kconfig | 6 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/gpiohack.c | 353 |
4 files changed, 360 insertions, 1 deletions
diff --git a/arch/arm/boot/dts/exynos4412-midas-3g.dtsi b/arch/arm/boot/dts/exynos4412-midas-3g.dtsi index 8460a8e4db52..f7a7ce703c9d 100644 --- a/arch/arm/boot/dts/exynos4412-midas-3g.dtsi +++ b/arch/arm/boot/dts/exynos4412-midas-3g.dtsi @@ -99,7 +99,6 @@ reset-req-gpios = <&gpm3 3 GPIO_ACTIVE_HIGH>; pda-active-gpios = <&gpf1 6 GPIO_ACTIVE_HIGH>; link-active-gpios = <&gpf1 1 GPIO_ACTIVE_HIGH>; - link-hostwake-gpios = <&gpx1 1 GPIO_ACTIVE_HIGH>; link-slavewake-gpios = <&gpx1 0 GPIO_ACTIVE_HIGH>; suspend-req-gpios = <&gpm2 4 GPIO_ACTIVE_HIGH>; diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index fafa8b0d8099..9d76b94a3907 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -466,6 +466,12 @@ config HISI_HIKEY_USB switching between the dual-role USB-C port and the USB-A host ports using only one USB controller. +config SAMSUNG_MODEMCTL + tristate "Samsung modem control" + depends on OF + help + Enable this if you have a modem you'd like to control. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index d23231e73330..6e0e5a89ba33 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ obj-$(CONFIG_UACCE) += uacce/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o +obj-$(CONFIG_SAMSUNG_MODEMCTL) += gpiohack.o diff --git a/drivers/misc/gpiohack.c b/drivers/misc/gpiohack.c new file mode 100644 index 000000000000..e226e66a1b10 --- /dev/null +++ b/drivers/misc/gpiohack.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Copyright (C) 2018 Simon Shields <simon@lineageos.org> +// +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/platform_device.h> + +struct gpiohack { + struct device *dev; + struct gpio_desc *reset_req; + struct gpio_desc *cp_on; + struct gpio_desc *cp_reset; + struct gpio_desc *phone_active; + struct gpio_desc *cp_dump; + struct gpio_desc *pda_active; + struct gpio_desc *link_active; + struct gpio_desc *link_hostwake; + struct gpio_desc *link_slavewake; + struct gpio_desc *suspend_req; + + struct clk *clk; + + int irq_phone_active; + int state; +}; + +static void reset_modem_control(struct gpiohack *dev) { + gpiod_direction_output(dev->pda_active, 0); + gpiod_direction_output(dev->cp_dump, 0); + gpiod_direction_output(dev->link_active, 0); + gpiod_direction_output(dev->link_slavewake, 0); + + gpiod_direction_output(dev->reset_req, 0); + gpiod_direction_output(dev->cp_on, 0); + gpiod_direction_output(dev->cp_reset, 0); + + msleep(20); +} + +static void gpiohack_modem_on(struct gpiohack *dev) { + reset_modem_control(dev); + + gpiod_set_value(dev->reset_req, 0); + gpiod_set_value(dev->cp_on, 0); + gpiod_set_value(dev->cp_reset, 0); + msleep(100); + + gpiod_set_value(dev->cp_reset, 1); + msleep(50); + gpiod_set_value(dev->reset_req, 1); + + gpiod_set_value(dev->suspend_req, 1); + + gpiod_set_value(dev->cp_on, 1); + udelay(60); + gpiod_set_value(dev->cp_on, 0); + msleep(20); + + gpiod_direction_input(dev->cp_dump); + + gpiod_set_value(dev->pda_active, 1); +} + +static void gpiohack_modem_off(struct gpiohack *dev) { + gpiod_set_value(dev->cp_on, 0); + gpiod_set_value(dev->cp_reset, 0); + reset_modem_control(dev); +} + +static ssize_t gpiohack_hostwake_show(struct device *_dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(_dev); + struct gpiohack *dev = platform_get_drvdata(pdev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", gpiod_get_value(dev->link_hostwake)); +} + +static irqreturn_t phone_active_irq_handler(int irq, void *_dev) +{ + struct gpiohack *dev = _dev; + int pa, cpr, cpd; + + disable_irq_nosync(dev->irq_phone_active); + + pa = gpiod_get_value(dev->phone_active); + cpr = gpiod_get_value(dev->cp_reset); + cpd = gpiod_get_value(dev->cp_dump); + + dev_info(dev->dev, "phone_active: cp_reset=%d, phone_active=%d, cp_dump=%d\n", + cpr, pa, cpd); + + if (cpr && pa) + dev_info(dev->dev, "BOOTING\n"); + else if (cpr && !pa) { + if (cpd) + dev_info(dev->dev, "CRASH EXIT\n"); + else + dev_info(dev->dev, "CRASH RESET\n"); + } else + dev_info(dev->dev, "OFFLINE\n"); + + if (pa) + irq_set_irq_type(dev->irq_phone_active, IRQ_TYPE_LEVEL_LOW); + else + irq_set_irq_type(dev->irq_phone_active, IRQ_TYPE_LEVEL_HIGH); + + enable_irq(dev->irq_phone_active); + + return IRQ_HANDLED; +} + +static ssize_t gpiohack_phone_active_show(struct device *_dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(_dev); + struct gpiohack *dev = platform_get_drvdata(pdev); + int pa, cpr, cpd; + pa = gpiod_get_value(dev->phone_active); + cpr = gpiod_get_value(dev->cp_reset); + cpd = gpiod_get_value(dev->cp_dump); + + if (cpr && pa) { + return scnprintf(buf, PAGE_SIZE, "booting pa=%d cpr=%d cpd=%d\n", pa, cpr, cpd); + } else if (cpr && !pa) { + if (cpd) + return scnprintf(buf, PAGE_SIZE, "crash exit pa=%d cpr=%d cpd=%d\n", pa, cpr, cpd); + else + return scnprintf(buf, PAGE_SIZE, "crash reset pa=%d cpr=%d cpd=%d\n", pa, cpr, cpd); + } else { + return scnprintf(buf, PAGE_SIZE, "offline pa=%d cpr=%d cpd=%d\n", pa, cpr, cpd); + } +} + + +static ssize_t gpiohack_link_active_store(struct device *_dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct platform_device *pdev = to_platform_device(_dev); + struct gpiohack *dev = platform_get_drvdata(pdev); + int state; + + if (kstrtoint(buf, 0, &state) < 0) + return -EINVAL; + + state = !!state; + dev_info(_dev, "link active? %d\n", state); + gpiod_set_value(dev->link_active, state); + return len; +} + +static ssize_t gpiohack_slavewake_store(struct device *_dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct platform_device *pdev = to_platform_device(_dev); + struct gpiohack *dev = platform_get_drvdata(pdev); + int state; + + if (kstrtoint(buf, 0, &state) < 0) + return -EINVAL; + + state = !!state; + dev_info(_dev, "slave wake => %d\n", state); + gpiod_set_value(dev->link_slavewake, state); + return len; +} + +static ssize_t gpiohack_sysfs_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpiohack *hack = platform_get_drvdata(pdev); + int new_state; + + if (kstrtoint(buf, 0, &new_state) < 0) + return -EINVAL; + + dev_info(dev, "new state: %d\n", new_state); + if (!new_state && hack->state) { + /* currently on, power off */ + hack->state = 0; + gpiohack_modem_off(hack); + } else if (new_state && !hack->state) { + /* currently off, power on */ + hack->state = 1; + gpiohack_modem_on(hack); + } + + return len; +} + +static ssize_t gpiohack_sysfs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpiohack *hack = platform_get_drvdata(pdev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", hack->state ? "on" : "off"); +} + +static ssize_t gpiohack_pda_active_store(struct device *_dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct platform_device *pdev = to_platform_device(_dev); + struct gpiohack *dev = platform_get_drvdata(pdev); + int state; + + if (kstrtoint(buf, 0, &state) < 0) + return -EINVAL; + + state = !!state; + dev_info(_dev, "pda active => %d\n", state); + gpiod_set_value(dev->pda_active, state); + return len; +} + + +static DEVICE_ATTR(modem_power, 0644, gpiohack_sysfs_show, gpiohack_sysfs_store); +static DEVICE_ATTR(hostwake, 0444, gpiohack_hostwake_show, NULL); +static DEVICE_ATTR(phone_active, 0444, gpiohack_phone_active_show, NULL); +static DEVICE_ATTR(slavewake, 0200, NULL, gpiohack_slavewake_store); +static DEVICE_ATTR(link_active, 0200, NULL, gpiohack_link_active_store); +static DEVICE_ATTR(pda_active, 0200, NULL, gpiohack_pda_active_store); + +static int gpiohack_probe(struct platform_device *pdev) { + struct gpiohack *dev; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->dev = &pdev->dev; + + dev->reset_req = devm_gpiod_get(dev->dev, "reset-req", GPIOD_OUT_LOW); + if (IS_ERR(dev->reset_req)) { + dev_err(dev->dev, "ernk reset-req: %ld\n", PTR_ERR(dev->reset_req)); + return PTR_ERR(dev->reset_req); + } + + dev->cp_on = devm_gpiod_get(dev->dev, "cp-on", GPIOD_OUT_LOW); + if (IS_ERR(dev->cp_on)) { + dev_err(dev->dev, "ernk cp_on: %ld\n", PTR_ERR(dev->cp_on)); + return PTR_ERR(dev->cp_on); + } + dev->cp_reset = devm_gpiod_get(dev->dev, "cp-reset", GPIOD_OUT_LOW); + if (IS_ERR(dev->cp_reset)) { + dev_err(dev->dev, "ernk cp-reset: %ld\n", PTR_ERR(dev->cp_reset)); + return PTR_ERR(dev->cp_reset); + } + + dev->link_active = devm_gpiod_get(dev->dev, "link-active", GPIOD_OUT_LOW); + if (IS_ERR(dev->link_active)) { + dev_err(dev->dev, "ernk link_active: %ld\n", PTR_ERR(dev->link_active)); + return PTR_ERR(dev->link_active); + } + + dev->phone_active = devm_gpiod_get(dev->dev, "phone-active", GPIOD_IN); + if (IS_ERR(dev->phone_active)) { + dev_err(dev->dev, "ernk phone_active: %ld\n", PTR_ERR(dev->phone_active)); + return PTR_ERR(dev->phone_active); + } + + dev->cp_dump = devm_gpiod_get(dev->dev, "cp-dump", GPIOD_IN); + if (IS_ERR(dev->cp_dump)) { + dev_err(dev->dev, "ernk cp_dump: %ld\n", PTR_ERR(dev->cp_dump)); + return PTR_ERR(dev->cp_dump); + } + + dev->suspend_req = devm_gpiod_get(dev->dev, "suspend-req", GPIOD_OUT_LOW); + if (IS_ERR(dev->suspend_req)) { + dev_err(dev->dev, "ernk suspend_req: %ld\n", PTR_ERR(dev->suspend_req)); + return PTR_ERR(dev->suspend_req); + } + + dev->link_slavewake = devm_gpiod_get(dev->dev, "link-slavewake", GPIOD_OUT_LOW); + if (IS_ERR(dev->link_slavewake)) { + dev_err(dev->dev, "ernk link_slavewake: %ld\n", PTR_ERR(dev->link_slavewake)); + return PTR_ERR(dev->link_slavewake); + } + + dev_err(dev->dev, "Loaded all GPIOs\n"); + + dev->pda_active = devm_gpiod_get(dev->dev, "pda-active", GPIOD_OUT_LOW); + if (IS_ERR(dev->pda_active)) { + dev_err(dev->dev, "ernk pda-active: %ld\n", PTR_ERR(dev->pda_active)); + return PTR_ERR(dev->pda_active); + } + + dev_err(dev->dev, "Loaded enable gpio\n"); + + dev->irq_phone_active = gpiod_to_irq(dev->phone_active); + ret = devm_request_irq(dev->dev, dev->irq_phone_active, phone_active_irq_handler, + IRQF_NO_SUSPEND | IRQF_TRIGGER_HIGH, + "phone_active", dev); + if (ret) { + dev_err(dev->dev, "Failed to request phone_active irq: %d\n", ret); + return ret; + } + + dev->clk = devm_clk_get(dev->dev, "cp_clk"); + if (IS_ERR_OR_NULL(dev->clk)) { + dev_err(dev->dev, "Failed to get CP clock with error %ld\n", + PTR_ERR(dev->clk)); + return PTR_ERR(dev->clk); + } + + clk_prepare_enable(dev->clk); + + device_create_file(dev->dev, &dev_attr_modem_power); + device_create_file(dev->dev, &dev_attr_phone_active); + device_create_file(dev->dev, &dev_attr_hostwake); + device_create_file(dev->dev, &dev_attr_slavewake); + device_create_file(dev->dev, &dev_attr_link_active); + device_create_file(dev->dev, &dev_attr_pda_active); + dev->state = 0; + platform_set_drvdata(pdev, dev); + return 0; +} + +static int gpiohack_remove(struct platform_device *pdev) { + struct gpiohack *dev = platform_get_drvdata(pdev); + clk_disable_unprepare(dev->clk); + dev_err(&pdev->dev, "bye\n"); + return 0; +} + +static const struct of_device_id samsung_modem_ctl_of_ids[] = { + { .compatible = "samsung,modem-ctl", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, ids); + +static struct platform_driver modemctl_driver = { + .driver = { + .name = "gpiohack", + .of_match_table = of_match_ptr(samsung_modem_ctl_of_ids), + }, + .probe = gpiohack_probe, + .remove = gpiohack_remove, +}; + +module_platform_driver(modemctl_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Simon Shields <simon@lineageos.org>"); |