Add sdx ipc driver to facilitate communication between external AP and modem using IPC gpios. Modem is intimated of AP's state based on gpio events and vice-versa. Change-Id: Ic9c0a0688a1e136d8da5bbc5e6554194b3b2af01 Signed-off-by: Rishabh Bhatnagar <rishabhb@codeaurora.org>tirimbino
parent
aac560a658
commit
18fcb15ec0
@ -0,0 +1,31 @@ |
|||||||
|
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>; |
||||||
|
}; |
@ -0,0 +1,267 @@ |
|||||||
|
/* 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…
Reference in new issue