Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c2600beb authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "drivers: soc: Add sdx ipc driver to communicate with external AP"

parents 92e42c97 37c69861
Loading
Loading
Loading
Loading
+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>;
	};
+10 −0
Original line number Diff line number Diff line
@@ -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"
+1 −0
Original line number Diff line number Diff line
@@ -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
+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");