aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Shields <simon@lineageos.org>2018-04-05 21:21:20 +1000
committerDenis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>2021-11-25 11:05:00 +0100
commitd7f49459e9f480d70f41c48f8e6aa833f2e57490 (patch)
treefcccbeca690c2d39d12e869389f35c5bc6942b42
parentec137f0c80f6c67705c2f293b0e8bc7047741ff0 (diff)
downloadkernel_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.dtsi1
-rw-r--r--drivers/misc/Kconfig6
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/gpiohack.c353
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>");