Loading Documentation/devicetree/bindings/arm/msm/sdx-ext-ipc.txt 0 → 100644 +31 −0 Original line number Diff line number Diff line Modem chipset attached to Application processor Modem chipset can be connected to an external apss processor. The control channel between the two chipsets consists of gpios that can relay the state of one subsytem to another. Modem can indicate different events (bootup/crash etc.) to AP and can get the same information from AP. Required Properties: - compatible: "qcom,sdx-ext-ipc". Required named gpio properties: - qcom,mdm2ap-status-gpio: gpio for modem to indicate the boot status to APQ. - qcom,ap2mdm-status-gpio: gpio for APQ to indicate the boot status to modem. Optional named gpio properties: - qcom,mdm2ap-status2-gpio: gpio for modem to indicate to APQ that it is in E911 call or doing firmware upgrade. - qcom,ap2mdm-status2-gpio: gpio for APQ to indicate graceful shutdown to modem. Example: sdx_ext_ipc: qcom,sdx_ext_ipc { compatible = "qcom,sdx-ext-ipc"; qcom,ap2mdm-status-gpio = <&tlmm 64 0x00>; qcom,ap2mdm-status2-gpio = <&tlmm 65 0x00>; qcom,mdm2ap-status-gpio = <&tlmm 63 0x00>; qcom,mdm2ap-status2-gpio = <&tlmm 66 0x00>; }; drivers/soc/qcom/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -274,6 +274,16 @@ config MSM_GLADIATOR_ERP If unsure, say N. config SDX_EXT_IPC tristate "QCOM external ipc driver" help This enables the module to help modem communicate with external Application processor connected to Qualcomm Technologies, Inc modem chipset. The modem and APQ can understand each other's state by reading ipc gpios. If unsure, say N. config PANIC_ON_GLADIATOR_ERROR depends on MSM_GLADIATOR_ERP bool "Panic on GLADIATOR error report" Loading drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o obj-$(CONFIG_MSM_BGCOM) += bgcom_spi.o obj-$(CONFIG_MSM_PERFORMANCE) += msm_performance.o obj-$(CONFIG_SDX_EXT_IPC) += sdx_ext_ipc.o ifdef CONFIG_MSM_SUBSYSTEM_RESTART obj-y += subsystem_notif.o Loading drivers/soc/qcom/sdx_ext_ipc.c 0 → 100644 +267 −0 Original line number Diff line number Diff line /* Copyright (c) 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/delay.h> #include <linux/of.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/interrupt.h> enum subsys_policies { SUBSYS_PANIC = 0, SUBSYS_NOP, }; static const char * const policies[] = { [SUBSYS_PANIC] = "PANIC", [SUBSYS_NOP] = "NOP", }; enum gpios { AP2MDM_STATUS = 0, MDM2AP_STATUS, MDM2AP_STATUS2, NUM_GPIOS, }; static const char * const gpio_map[] = { [AP2MDM_STATUS] = "qcom,ap2mdm-status-gpio", [MDM2AP_STATUS] = "qcom,mdm2ap-status-gpio", [MDM2AP_STATUS2] = "qcom,mdm2ap-status2-gpio", }; struct gpio_cntrl { unsigned int gpios[NUM_GPIOS]; int status_irq; int policy; struct device *dev; struct mutex policy_lock; struct notifier_block panic_blk; }; static ssize_t policy_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct gpio_cntrl *mdm = dev_get_drvdata(dev); mutex_lock(&mdm->policy_lock); ret = scnprintf(buf, strlen(policies[mdm->policy]) + 1, policies[mdm->policy]); mutex_unlock(&mdm->policy_lock); return ret; } static ssize_t policy_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct gpio_cntrl *mdm = dev_get_drvdata(dev); const char *p; int i, orig_count = count; p = memchr(buf, '\n', count); if (p) count = p - buf; for (i = 0; i < ARRAY_SIZE(policies); i++) if (!strncasecmp(buf, policies[i], count)) { mutex_lock(&mdm->policy_lock); mdm->policy = i; mutex_unlock(&mdm->policy_lock); return orig_count; } return -EPERM; } static DEVICE_ATTR_RW(policy); static irqreturn_t ap_status_change(int irq, void *dev_id) { struct gpio_cntrl *mdm = dev_id; int state; struct gpio_desc *gp_status = gpio_to_desc(mdm->gpios[AP2MDM_STATUS]); int active_low = 0; if (gp_status) active_low = gpiod_is_active_low(gp_status); state = gpio_get_value(mdm->gpios[AP2MDM_STATUS]); if ((!active_low && !state) || (active_low && state)) { if (mdm->policy) dev_info(mdm->dev, "Host undergoing SSR, leaving SDX as it is\n"); else panic("Host undergoing SSR, panicking SDX\n"); } else dev_info(mdm->dev, "HOST booted\n"); return IRQ_HANDLED; } static void remove_ipc(struct gpio_cntrl *mdm) { int i; for (i = 0; i < NUM_GPIOS; ++i) { if (gpio_is_valid(mdm->gpios[i])) gpio_free(mdm->gpios[i]); } } static int setup_ipc(struct gpio_cntrl *mdm) { int i, val, ret, irq; struct device_node *node; node = mdm->dev->of_node; for (i = 0; i < ARRAY_SIZE(gpio_map); i++) { val = of_get_named_gpio(node, gpio_map[i], 0); if (val >= 0) mdm->gpios[i] = val; } ret = gpio_request(mdm->gpios[AP2MDM_STATUS], "AP2MDM_STATUS"); if (ret) { dev_err(mdm->dev, "Failed to configure AP2MDM_STATUS gpio\n"); return ret; } gpio_direction_input(mdm->gpios[AP2MDM_STATUS]); ret = gpio_request(mdm->gpios[MDM2AP_STATUS], "MDM2AP_STATUS"); if (ret) { dev_err(mdm->dev, "Failed to configure MDM2AP_STATUS gpio\n"); return ret; } gpio_direction_output(mdm->gpios[MDM2AP_STATUS], 1); ret = gpio_request(mdm->gpios[MDM2AP_STATUS2], "MDM2AP_STATUS2"); if (ret) { dev_err(mdm->dev, "Failed to configure MDM2AP_STATUS2 gpio\n"); return ret; } gpio_direction_output(mdm->gpios[MDM2AP_STATUS2], 0); irq = gpio_to_irq(mdm->gpios[AP2MDM_STATUS]); if (irq < 0) { dev_err(mdm->dev, "bad AP2MDM_STATUS IRQ resource\n"); return irq; } mdm->status_irq = irq; return 0; } static int sdx_ext_ipc_panic(struct notifier_block *this, unsigned long event, void *ptr) { struct gpio_cntrl *mdm = container_of(this, struct gpio_cntrl, panic_blk); gpio_set_value(mdm->gpios[MDM2AP_STATUS], 0); return NOTIFY_DONE; } static int sdx_ext_ipc_probe(struct platform_device *pdev) { int ret; struct device_node *node; struct gpio_cntrl *mdm; node = pdev->dev.of_node; mdm = devm_kzalloc(&pdev->dev, sizeof(*mdm), GFP_KERNEL); if (!mdm) return -ENOMEM; mdm->dev = &pdev->dev; ret = setup_ipc(mdm); if (ret) { dev_err(mdm->dev, "Error setting up gpios\n"); devm_kfree(&pdev->dev, mdm); return ret; } mdm->panic_blk.notifier_call = sdx_ext_ipc_panic; atomic_notifier_chain_register(&panic_notifier_list, &mdm->panic_blk); mutex_init(&mdm->policy_lock); mdm->policy = SUBSYS_PANIC; ret = device_create_file(mdm->dev, &dev_attr_policy); if (ret) { dev_err(mdm->dev, "cannot create sysfs attribute\n"); goto sys_fail; } platform_set_drvdata(pdev, mdm); ret = devm_request_irq(mdm->dev, mdm->status_irq, ap_status_change, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "ap status", mdm); if (ret < 0) { dev_err(mdm->dev, "%s: AP2MDM_STATUS IRQ#%d request failed,\n", __func__, mdm->status_irq); goto irq_fail; } irq_set_irq_wake(mdm->status_irq, 1); return 0; irq_fail: device_remove_file(mdm->dev, &dev_attr_policy); sys_fail: atomic_notifier_chain_unregister(&panic_notifier_list, &mdm->panic_blk); remove_ipc(mdm); devm_kfree(&pdev->dev, mdm); return ret; } static int sdx_ext_ipc_remove(struct platform_device *pdev) { struct gpio_cntrl *mdm; mdm = dev_get_drvdata(&pdev->dev); disable_irq_wake(mdm->status_irq); atomic_notifier_chain_unregister(&panic_notifier_list, &mdm->panic_blk); remove_ipc(mdm); device_remove_file(mdm->dev, &dev_attr_policy); return 0; } static const struct of_device_id sdx_ext_ipc_of_match[] = { { .compatible = "qcom,sdx-ext-ipc"}, {}, }; static struct platform_driver sdx_ext_ipc_driver = { .probe = sdx_ext_ipc_probe, .remove = sdx_ext_ipc_remove, .driver = { .name = "sdx-ext-ipc", .owner = THIS_MODULE, .of_match_table = sdx_ext_ipc_of_match, }, }; static int __init sdx_ext_ipc_register(void) { return platform_driver_register(&sdx_ext_ipc_driver); } subsys_initcall(sdx_ext_ipc_register); static void __exit sdx_ext_ipc_unregister(void) { platform_driver_unregister(&sdx_ext_ipc_driver); } module_exit(sdx_ext_ipc_unregister); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/arm/msm/sdx-ext-ipc.txt 0 → 100644 +31 −0 Original line number Diff line number Diff line Modem chipset attached to Application processor Modem chipset can be connected to an external apss processor. The control channel between the two chipsets consists of gpios that can relay the state of one subsytem to another. Modem can indicate different events (bootup/crash etc.) to AP and can get the same information from AP. Required Properties: - compatible: "qcom,sdx-ext-ipc". Required named gpio properties: - qcom,mdm2ap-status-gpio: gpio for modem to indicate the boot status to APQ. - qcom,ap2mdm-status-gpio: gpio for APQ to indicate the boot status to modem. Optional named gpio properties: - qcom,mdm2ap-status2-gpio: gpio for modem to indicate to APQ that it is in E911 call or doing firmware upgrade. - qcom,ap2mdm-status2-gpio: gpio for APQ to indicate graceful shutdown to modem. Example: sdx_ext_ipc: qcom,sdx_ext_ipc { compatible = "qcom,sdx-ext-ipc"; qcom,ap2mdm-status-gpio = <&tlmm 64 0x00>; qcom,ap2mdm-status2-gpio = <&tlmm 65 0x00>; qcom,mdm2ap-status-gpio = <&tlmm 63 0x00>; qcom,mdm2ap-status2-gpio = <&tlmm 66 0x00>; };
drivers/soc/qcom/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -274,6 +274,16 @@ config MSM_GLADIATOR_ERP If unsure, say N. config SDX_EXT_IPC tristate "QCOM external ipc driver" help This enables the module to help modem communicate with external Application processor connected to Qualcomm Technologies, Inc modem chipset. The modem and APQ can understand each other's state by reading ipc gpios. If unsure, say N. config PANIC_ON_GLADIATOR_ERROR depends on MSM_GLADIATOR_ERP bool "Panic on GLADIATOR error report" Loading
drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o obj-$(CONFIG_MSM_BGCOM) += bgcom_spi.o obj-$(CONFIG_MSM_PERFORMANCE) += msm_performance.o obj-$(CONFIG_SDX_EXT_IPC) += sdx_ext_ipc.o ifdef CONFIG_MSM_SUBSYSTEM_RESTART obj-y += subsystem_notif.o Loading
drivers/soc/qcom/sdx_ext_ipc.c 0 → 100644 +267 −0 Original line number Diff line number Diff line /* Copyright (c) 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/delay.h> #include <linux/of.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/platform_device.h> #include <linux/module.h> #include <linux/interrupt.h> enum subsys_policies { SUBSYS_PANIC = 0, SUBSYS_NOP, }; static const char * const policies[] = { [SUBSYS_PANIC] = "PANIC", [SUBSYS_NOP] = "NOP", }; enum gpios { AP2MDM_STATUS = 0, MDM2AP_STATUS, MDM2AP_STATUS2, NUM_GPIOS, }; static const char * const gpio_map[] = { [AP2MDM_STATUS] = "qcom,ap2mdm-status-gpio", [MDM2AP_STATUS] = "qcom,mdm2ap-status-gpio", [MDM2AP_STATUS2] = "qcom,mdm2ap-status2-gpio", }; struct gpio_cntrl { unsigned int gpios[NUM_GPIOS]; int status_irq; int policy; struct device *dev; struct mutex policy_lock; struct notifier_block panic_blk; }; static ssize_t policy_show(struct device *dev, struct device_attribute *attr, char *buf) { int ret; struct gpio_cntrl *mdm = dev_get_drvdata(dev); mutex_lock(&mdm->policy_lock); ret = scnprintf(buf, strlen(policies[mdm->policy]) + 1, policies[mdm->policy]); mutex_unlock(&mdm->policy_lock); return ret; } static ssize_t policy_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct gpio_cntrl *mdm = dev_get_drvdata(dev); const char *p; int i, orig_count = count; p = memchr(buf, '\n', count); if (p) count = p - buf; for (i = 0; i < ARRAY_SIZE(policies); i++) if (!strncasecmp(buf, policies[i], count)) { mutex_lock(&mdm->policy_lock); mdm->policy = i; mutex_unlock(&mdm->policy_lock); return orig_count; } return -EPERM; } static DEVICE_ATTR_RW(policy); static irqreturn_t ap_status_change(int irq, void *dev_id) { struct gpio_cntrl *mdm = dev_id; int state; struct gpio_desc *gp_status = gpio_to_desc(mdm->gpios[AP2MDM_STATUS]); int active_low = 0; if (gp_status) active_low = gpiod_is_active_low(gp_status); state = gpio_get_value(mdm->gpios[AP2MDM_STATUS]); if ((!active_low && !state) || (active_low && state)) { if (mdm->policy) dev_info(mdm->dev, "Host undergoing SSR, leaving SDX as it is\n"); else panic("Host undergoing SSR, panicking SDX\n"); } else dev_info(mdm->dev, "HOST booted\n"); return IRQ_HANDLED; } static void remove_ipc(struct gpio_cntrl *mdm) { int i; for (i = 0; i < NUM_GPIOS; ++i) { if (gpio_is_valid(mdm->gpios[i])) gpio_free(mdm->gpios[i]); } } static int setup_ipc(struct gpio_cntrl *mdm) { int i, val, ret, irq; struct device_node *node; node = mdm->dev->of_node; for (i = 0; i < ARRAY_SIZE(gpio_map); i++) { val = of_get_named_gpio(node, gpio_map[i], 0); if (val >= 0) mdm->gpios[i] = val; } ret = gpio_request(mdm->gpios[AP2MDM_STATUS], "AP2MDM_STATUS"); if (ret) { dev_err(mdm->dev, "Failed to configure AP2MDM_STATUS gpio\n"); return ret; } gpio_direction_input(mdm->gpios[AP2MDM_STATUS]); ret = gpio_request(mdm->gpios[MDM2AP_STATUS], "MDM2AP_STATUS"); if (ret) { dev_err(mdm->dev, "Failed to configure MDM2AP_STATUS gpio\n"); return ret; } gpio_direction_output(mdm->gpios[MDM2AP_STATUS], 1); ret = gpio_request(mdm->gpios[MDM2AP_STATUS2], "MDM2AP_STATUS2"); if (ret) { dev_err(mdm->dev, "Failed to configure MDM2AP_STATUS2 gpio\n"); return ret; } gpio_direction_output(mdm->gpios[MDM2AP_STATUS2], 0); irq = gpio_to_irq(mdm->gpios[AP2MDM_STATUS]); if (irq < 0) { dev_err(mdm->dev, "bad AP2MDM_STATUS IRQ resource\n"); return irq; } mdm->status_irq = irq; return 0; } static int sdx_ext_ipc_panic(struct notifier_block *this, unsigned long event, void *ptr) { struct gpio_cntrl *mdm = container_of(this, struct gpio_cntrl, panic_blk); gpio_set_value(mdm->gpios[MDM2AP_STATUS], 0); return NOTIFY_DONE; } static int sdx_ext_ipc_probe(struct platform_device *pdev) { int ret; struct device_node *node; struct gpio_cntrl *mdm; node = pdev->dev.of_node; mdm = devm_kzalloc(&pdev->dev, sizeof(*mdm), GFP_KERNEL); if (!mdm) return -ENOMEM; mdm->dev = &pdev->dev; ret = setup_ipc(mdm); if (ret) { dev_err(mdm->dev, "Error setting up gpios\n"); devm_kfree(&pdev->dev, mdm); return ret; } mdm->panic_blk.notifier_call = sdx_ext_ipc_panic; atomic_notifier_chain_register(&panic_notifier_list, &mdm->panic_blk); mutex_init(&mdm->policy_lock); mdm->policy = SUBSYS_PANIC; ret = device_create_file(mdm->dev, &dev_attr_policy); if (ret) { dev_err(mdm->dev, "cannot create sysfs attribute\n"); goto sys_fail; } platform_set_drvdata(pdev, mdm); ret = devm_request_irq(mdm->dev, mdm->status_irq, ap_status_change, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "ap status", mdm); if (ret < 0) { dev_err(mdm->dev, "%s: AP2MDM_STATUS IRQ#%d request failed,\n", __func__, mdm->status_irq); goto irq_fail; } irq_set_irq_wake(mdm->status_irq, 1); return 0; irq_fail: device_remove_file(mdm->dev, &dev_attr_policy); sys_fail: atomic_notifier_chain_unregister(&panic_notifier_list, &mdm->panic_blk); remove_ipc(mdm); devm_kfree(&pdev->dev, mdm); return ret; } static int sdx_ext_ipc_remove(struct platform_device *pdev) { struct gpio_cntrl *mdm; mdm = dev_get_drvdata(&pdev->dev); disable_irq_wake(mdm->status_irq); atomic_notifier_chain_unregister(&panic_notifier_list, &mdm->panic_blk); remove_ipc(mdm); device_remove_file(mdm->dev, &dev_attr_policy); return 0; } static const struct of_device_id sdx_ext_ipc_of_match[] = { { .compatible = "qcom,sdx-ext-ipc"}, {}, }; static struct platform_driver sdx_ext_ipc_driver = { .probe = sdx_ext_ipc_probe, .remove = sdx_ext_ipc_remove, .driver = { .name = "sdx-ext-ipc", .owner = THIS_MODULE, .of_match_table = sdx_ext_ipc_of_match, }, }; static int __init sdx_ext_ipc_register(void) { return platform_driver_register(&sdx_ext_ipc_driver); } subsys_initcall(sdx_ext_ipc_register); static void __exit sdx_ext_ipc_unregister(void) { platform_driver_unregister(&sdx_ext_ipc_driver); } module_exit(sdx_ext_ipc_unregister); MODULE_LICENSE("GPL v2");