android13/kernel-5.10/drivers/sprd_pcie/mcd/modem_ctrl.c

815 lines
21 KiB
C
Executable File

/*
* Copyright (C) 2019 Spreadtrum Communications Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/of_device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/gpio/consumer.h>
#include <linux/reboot.h>
#ifdef CONFIG_PCIE_PM_NOTIFY
#include <linux/pcie_notifier.h>
#endif
#include "../include/sprd_pcie_resource.h"
#include "../include/sipc.h"
#include "../include/mdm_ctrl.h"
enum {
ROC1_SOC = 0,
ORCA_SOC
};
static char *const mdm_stat[] = {
"mdm_power_off", "mdm_power_on", "mdm_warm_reset", "mdm_cold_reset",
"mdm_watchdog_reset", "mdm_assert", "mdm_panic"
};
#define REBOOT_MODEM_DELAY 1000
#define POWERREST_MODEM_DELAY 2000
#define RESET_MODEM_DELAY 50
char cdev_name[] = "mdm_ctrl";
struct modem_ctrl_init_data {
char *name;
struct gpio_desc *gpio_poweron; /* Poweron */
struct gpio_desc *gpio_reset; /* Reset modem */
struct gpio_desc *gpio_preset; /* Pcie reset */
struct gpio_desc *gpio_cpwatchdog;
struct gpio_desc *gpio_cpassert;
struct gpio_desc *gpio_cppanic;
struct gpio_desc *gpio_cppoweroff;
u32 irq_cpwatchdog;
u32 irq_cpassert;
u32 irq_cppanic;
u32 irq_cppoweroff;
u32 modem_status;
bool enable_cp_event;
};
struct modem_ctrl_device {
struct modem_ctrl_init_data *init;
int major;
int minor;
struct cdev cdev;
struct device *dev;
int soc_type;
};
static struct class *modem_ctrl_class;
static struct modem_ctrl_device *mcd_dev;
/* modem control evnet notify */
static ATOMIC_NOTIFIER_HEAD(modem_ctrl_chain);
int modem_ctrl_register_notifier(struct notifier_block *nb)
{
return atomic_notifier_chain_register(&modem_ctrl_chain, nb);
}
EXPORT_SYMBOL(modem_ctrl_register_notifier);
void modem_ctrl_unregister_notifier(struct notifier_block *nb)
{
atomic_notifier_chain_unregister(&modem_ctrl_chain, nb);
}
EXPORT_SYMBOL(modem_ctrl_unregister_notifier);
static void send_event_msg(struct kobject *kobj)
{
char *msg[3];
char buff[100];
char mbuff[100];
memset(mbuff, 0, sizeof(mbuff));
if (!mcd_dev || !mcd_dev->init || !kobj)
return;
snprintf(buff, sizeof(buff), "MODEM_STAT=%d",
mcd_dev->init->modem_status);
snprintf(mbuff, sizeof(mbuff), "MODEM_EVENT=%s",
mdm_stat[mcd_dev->init->modem_status]);
msg[0] = buff;
msg[1] = mbuff;
msg[2] = NULL;
kobject_uevent_env(kobj, KOBJ_CHANGE, msg);
dev_dbg(mcd_dev->dev, "send uevent to userspace\n");
}
static irqreturn_t cpwatchdogtriger_handler(int irq, void *dev_id)
{
if (!mcd_dev || !mcd_dev->init || !mcd_dev->init->enable_cp_event)
return IRQ_NONE;
mcd_dev->init->modem_status = MDM_WATCHDOG_RESET;
atomic_notifier_call_chain(&modem_ctrl_chain, MDM_WATCHDOG_RESET, NULL);
send_event_msg(&mcd_dev->dev->kobj);
return IRQ_HANDLED;
}
static irqreturn_t cpasserttriger_handler(int irq, void *dev_id)
{
if (!mcd_dev || !mcd_dev->init || !mcd_dev->init->enable_cp_event)
return IRQ_NONE;
mcd_dev->init->modem_status = MDM_ASSERT;
atomic_notifier_call_chain(&modem_ctrl_chain, MDM_ASSERT, NULL);
send_event_msg(&mcd_dev->dev->kobj);
return IRQ_HANDLED;
}
static irqreturn_t cppanictriger_handler(int irq, void *dev_id)
{
if (!mcd_dev || !mcd_dev->init || !mcd_dev->init->enable_cp_event)
return IRQ_NONE;
mcd_dev->init->modem_status = MDM_PANIC;
atomic_notifier_call_chain(&modem_ctrl_chain, MDM_PANIC, NULL);
send_event_msg(&mcd_dev->dev->kobj);
return IRQ_HANDLED;
}
static irqreturn_t cppoweroff_handler(int irq, void *dev_id)
{
if (!mcd_dev || !mcd_dev->init)
return IRQ_NONE;
/* To this reserve here for receve power off event from AP*/
atomic_notifier_call_chain(&modem_ctrl_chain,
MDM_POWER_OFF, NULL);
kernel_power_off();
return IRQ_HANDLED;
}
static int request_gpio_to_irq(struct gpio_desc *cp_gpio,
struct modem_ctrl_device *mcd_dev)
{
int ret = 0;
if (!mcd_dev || !mcd_dev->init)
return -EINVAL;
ret = gpiod_to_irq(cp_gpio);
if (ret < 0) {
dev_err(mcd_dev->dev, "requset irq %d failed\n", ret);
return ret;
}
dev_dbg(mcd_dev->dev, "gpio to irq %d\n", ret);
if (cp_gpio == mcd_dev->init->gpio_cpwatchdog) {
mcd_dev->init->irq_cpwatchdog = ret;
ret = devm_request_threaded_irq(mcd_dev->dev,
mcd_dev->init->irq_cpwatchdog,
NULL, cpwatchdogtriger_handler,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
"cpwatchdog_irq", mcd_dev);
if (ret < 0) {
dev_err(mcd_dev->dev, "can not request irq for cp watchdog\n");
return ret;
}
enable_irq_wake(mcd_dev->init->irq_cpwatchdog);
} else if (cp_gpio == mcd_dev->init->gpio_cpassert) {
mcd_dev->init->irq_cpassert = ret;
ret = devm_request_threaded_irq(mcd_dev->dev,
mcd_dev->init->irq_cpassert,
NULL, cpasserttriger_handler,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
"cpassert_irq", mcd_dev);
if (ret < 0) {
dev_err(mcd_dev->dev, "can not request irq for cp assert\n");
return ret;
}
enable_irq_wake(mcd_dev->init->irq_cpassert);
} else if (cp_gpio == mcd_dev->init->gpio_cppanic) {
mcd_dev->init->irq_cppanic = ret;
ret = devm_request_threaded_irq(mcd_dev->dev,
mcd_dev->init->irq_cppanic,
NULL, cppanictriger_handler,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
"cppanic_irq", mcd_dev);
if (ret < 0) {
dev_err(mcd_dev->dev,
"can not request irq for panic\n");
return ret;
}
enable_irq_wake(mcd_dev->init->irq_cppanic);
} else if (cp_gpio == mcd_dev->init->gpio_cppoweroff) {
mcd_dev->init->irq_cppoweroff = ret;
ret = devm_request_threaded_irq(mcd_dev->dev,
mcd_dev->init->irq_cppoweroff,
NULL, cppoweroff_handler,
IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"cppoweroff_irq", mcd_dev);
if (ret < 0) {
dev_err(mcd_dev->dev,
"can not request irq for cppoweroff\n");
return ret;
}
enable_irq_wake(mcd_dev->init->irq_cppoweroff);
}
return 0;
}
static int modem_gpios_init(struct modem_ctrl_device *mcd_dev, int soc_type)
{
int ret;
if (!mcd_dev || !mcd_dev->init)
return -EINVAL;
if (soc_type == ROC1_SOC) {
gpiod_direction_input(mcd_dev->init->gpio_cpwatchdog);
gpiod_direction_input(mcd_dev->init->gpio_cpassert);
gpiod_direction_input(mcd_dev->init->gpio_cppanic);
ret = request_gpio_to_irq(mcd_dev->init->gpio_cpwatchdog,
mcd_dev);
if (ret)
return ret;
ret = request_gpio_to_irq(mcd_dev->init->gpio_cpassert,
mcd_dev);
if (ret)
return ret;
ret = request_gpio_to_irq(mcd_dev->init->gpio_cppanic,
mcd_dev);
if (ret)
return ret;
/* IRQF_TRIGGER_LOW, default must set to high */
gpiod_set_value_cansleep(mcd_dev->init->gpio_cppoweroff, 1);
} else {
gpiod_direction_input(mcd_dev->init->gpio_cppoweroff);
ret = request_gpio_to_irq(mcd_dev->init->gpio_cppoweroff,
mcd_dev);
if (ret)
return ret;
/* TRIGGER_FALLING, defaultmust set to high */
gpiod_set_value_cansleep(mcd_dev->init->gpio_cpwatchdog, 1);
gpiod_set_value_cansleep(mcd_dev->init->gpio_cpassert, 1);
gpiod_set_value_cansleep(mcd_dev->init->gpio_cppanic, 1);
}
return 0;
}
void modem_ctrl_enable_cp_event(void)
{
if (mcd_dev && mcd_dev->init)
mcd_dev->init->enable_cp_event = true;
}
EXPORT_SYMBOL_GPL(modem_ctrl_enable_cp_event);
void modem_ctrl_send_abnormal_to_ap(int status)
{
struct gpio_desc *gpiodesc;
if (!mcd_dev || !mcd_dev->init)
return;
if (mcd_dev->soc_type != ORCA_SOC) {
dev_err(mcd_dev->dev, "operation not be allowed for %d\n",
mcd_dev->soc_type);
return;
}
switch (status) {
case MDM_WATCHDOG_RESET:
gpiodesc = mcd_dev->init->gpio_cpwatchdog;
break;
case MDM_ASSERT:
gpiodesc = mcd_dev->init->gpio_cpassert;
break;
case MDM_PANIC:
gpiodesc = mcd_dev->init->gpio_cppanic;
break;
default:
dev_info(mcd_dev->dev,
"get status %d is not right for operation\n", status);
return;
}
mcd_dev->init->modem_status = status;
dev_info(mcd_dev->dev,
"operation unnormal status %d send to ap\n",
status);
if (!IS_ERR(gpiodesc))
gpiod_set_value_cansleep(gpiodesc, 0);
}
static void modem_ctrl_send_cmd_to_cp(int status)
{
struct gpio_desc *gpiodesc = NULL;
if (!mcd_dev || !mcd_dev->init)
return;
if (mcd_dev->soc_type != ROC1_SOC) {
dev_err(mcd_dev->dev, "operation not be allowed for %d\n",
mcd_dev->soc_type);
return;
}
if (status == MDM_POWER_OFF)
gpiodesc = mcd_dev->init->gpio_cppoweroff;
mcd_dev->init->modem_status = status;
dev_info(mcd_dev->dev,
"operation cmd %d ms send to cp\n",
status);
if (!IS_ERR(gpiodesc)) {
gpiod_set_value_cansleep(gpiodesc, 0);
msleep(20);
gpiod_set_value_cansleep(gpiodesc, 20);
}
}
static void modem_ctrl_notify_abnormal_status(int status)
{
if (!mcd_dev || !mcd_dev->init)
return;
if (mcd_dev->soc_type != ORCA_SOC) {
dev_err(mcd_dev->dev, "operation not be allowed for %d\n",
mcd_dev->soc_type);
return;
}
if (status < MDM_WATCHDOG_RESET || status > MDM_PANIC) {
dev_err(mcd_dev->dev,
"operation not be allowed for status %d\n", status);
return;
}
modem_ctrl_send_abnormal_to_ap(status);
}
void modem_ctrl_poweron_modem(int on)
{
if (!mcd_dev || !mcd_dev->init)
return;
switch (on) {
case MDM_CTRL_POWER_ON:
if (!IS_ERR(mcd_dev->init->gpio_poweron)) {
atomic_notifier_call_chain(&modem_ctrl_chain,
MDM_CTRL_POWER_ON, NULL);
dev_info(mcd_dev->dev, "set modem_poweron: %d\n", on);
gpiod_set_value_cansleep(mcd_dev->init->gpio_poweron,
1);
/* Base the spec modem boot flow that need to wait 1s */
msleep(REBOOT_MODEM_DELAY);
mcd_dev->init->modem_status = MDM_CTRL_POWER_ON;
gpiod_set_value_cansleep(mcd_dev->init->gpio_poweron,
0);
}
break;
case MDM_CTRL_POWER_OFF:
/*
*To do
*/
break;
case MDM_CTRL_SET_CFG:
/*
*To do
*/
break;
case MDM_CTRL_WARM_RESET:
if (!IS_ERR(mcd_dev->init->gpio_reset)) {
atomic_notifier_call_chain(&modem_ctrl_chain,
MDM_CTRL_WARM_RESET, NULL);
dev_dbg(mcd_dev->dev, "set warm reset: %d\n", on);
gpiod_set_value_cansleep(mcd_dev->init->gpio_reset, 1);
/* Base the spec modem that need to wait 50ms */
msleep(RESET_MODEM_DELAY);
mcd_dev->init->modem_status = MDM_CTRL_WARM_RESET;
gpiod_set_value_cansleep(mcd_dev->init->gpio_reset, 0);
}
break;
case MDM_CTRL_COLD_RESET:
if (!IS_ERR(mcd_dev->init->gpio_poweron)) {
mcd_dev->init->enable_cp_event = false;
atomic_notifier_call_chain(&modem_ctrl_chain,
MDM_CTRL_COLD_RESET, NULL);
dev_info(mcd_dev->dev, "modem_power reset: %d\n", on);
gpiod_set_value_cansleep(mcd_dev->init->gpio_poweron,
1);
/* Base the spec modem boot flow that need to wait 2s */
msleep(POWERREST_MODEM_DELAY);
mcd_dev->init->modem_status = MDM_CTRL_COLD_RESET;
gpiod_set_value_cansleep(mcd_dev->init->gpio_poweron,
0);
}
break;
case MDM_CTRL_PCIE_RECOVERY:
#ifdef CONFIG_PCIE_PM_NOTIFY
pcie_ep_pm_notify(PCIE_EP_POWER_OFF);
/* PCIE poweroff to poweron need 100ms*/
msleep(100);
pcie_ep_pm_notify(PCIE_EP_POWER_ON);
#endif
break;
case MDM_POWER_OFF:
atomic_notifier_call_chain(&modem_ctrl_chain,
MDM_POWER_OFF, NULL);
modem_ctrl_send_cmd_to_cp(MDM_POWER_OFF);
break;
default:
dev_err(mcd_dev->dev, "cmd not support: %d\n", on);
}
}
EXPORT_SYMBOL_GPL(modem_ctrl_poweron_modem);
#if defined(CONFIG_DEBUG_FS)
static int modem_ctrl_debug_show(struct seq_file *m, void *private)
{
dev_dbg(mcd_dev->dev, "%s\n", __func__);
return 0;
}
static int modem_ctrl_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, modem_ctrl_debug_show, inode->i_private);
}
static const struct file_operations modem_ctrl_debug_fops = {
.open = modem_ctrl_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif /* CONFIG_DEBUG_FS */
static int modem_ctrl_open(struct inode *inode, struct file *filp)
{
struct modem_ctrl_device *modem_ctrl;
modem_ctrl = container_of(inode->i_cdev,
struct modem_ctrl_device, cdev);
filp->private_data = modem_ctrl;
dev_dbg(modem_ctrl->dev, "modem_ctrl: %s\n", __func__);
return 0;
}
static int modem_ctrl_release(struct inode *inode, struct file *filp)
{
struct modem_ctrl_device *modem_ctrl;
modem_ctrl = container_of(inode->i_cdev,
struct modem_ctrl_device, cdev);
dev_dbg(modem_ctrl->dev, "modem_ctrl: %s\n", __func__);
return 0;
}
static ssize_t modem_ctrl_read(struct file *filp,
char __user *buf,
size_t count,
loff_t *ppos)
{
char tmpbuf[30];
int r;
struct modem_ctrl_device *mcd_dev = filp->private_data;
if (!mcd_dev || !mcd_dev->init)
return -EINVAL;
r = snprintf(tmpbuf, sizeof(tmpbuf), "%s\n",
mdm_stat[mcd_dev->init->modem_status]);
return simple_read_from_buffer(buf, count, ppos, tmpbuf, r);
}
static ssize_t modem_ctrl_write(struct file *filp,
const char __user *buf,
size_t count, loff_t *ppos)
{
char sbuf[100];
int ret;
u32 mcd_cmd;
struct modem_ctrl_device *mcd_dev = filp->private_data;
if (!mcd_dev)
return -EINVAL;
if (unalign_copy_from_user((void *)sbuf, buf, count)) {
dev_err(mcd_dev->dev, "copy buf %s error\n", buf);
return -EFAULT;
}
dev_dbg(mcd_dev->dev, "get info:%s", sbuf);
sbuf[count - 1] = '\0';
ret = kstrtouint(sbuf, 10, &mcd_cmd);
if (ret) {
dev_err(mcd_dev->dev, "Invalid input!\n");
return ret;
}
if (mcd_dev->soc_type == ROC1_SOC) {
if (mcd_cmd >= MDM_CTRL_POWER_OFF &&
mcd_cmd <= MDM_CTRL_SET_CFG)
modem_ctrl_poweron_modem(mcd_cmd);
else
dev_info(mcd_dev->dev, "cmd not support!\n");
} else {
modem_ctrl_notify_abnormal_status(mcd_cmd);
}
return count;
}
static long modem_ctrl_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
if (!mcd_dev || mcd_dev->soc_type == ORCA_SOC)
return -EINVAL;
switch (cmd) {
case MDM_CTRL_POWER_OFF:
modem_ctrl_poweron_modem(MDM_CTRL_POWER_OFF);
break;
case MDM_CTRL_POWER_ON:
modem_ctrl_poweron_modem(MDM_CTRL_POWER_ON);
break;
case MDM_CTRL_WARM_RESET:
modem_ctrl_poweron_modem(MDM_CTRL_WARM_RESET);
break;
case MDM_CTRL_COLD_RESET:
modem_ctrl_poweron_modem(MDM_CTRL_COLD_RESET);
break;
case MDM_CTRL_PCIE_RECOVERY:
modem_ctrl_poweron_modem(MDM_CTRL_PCIE_RECOVERY);
break;
case MDM_CTRL_SET_CFG:
break;
default:
return -EINVAL;
}
return 0;
}
static const struct file_operations modem_ctrl_fops = {
.open = modem_ctrl_open,
.release = modem_ctrl_release,
.read = modem_ctrl_read,
.write = modem_ctrl_write,
.unlocked_ioctl = modem_ctrl_ioctl,
.owner = THIS_MODULE,
.llseek = default_llseek,
};
static int modem_ctrl_parse_modem_dt(struct modem_ctrl_init_data **init,
struct device *dev)
{
struct modem_ctrl_init_data *pdata = NULL;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
pdata->name = cdev_name;
/* Triger watchdog,assert,panic of orca */
pdata->gpio_cpwatchdog = devm_gpiod_get(dev,
"cpwatchdog",
GPIOD_OUT_HIGH);
if (IS_ERR(pdata->gpio_cpwatchdog))
return PTR_ERR(pdata->gpio_cpwatchdog);
pdata->gpio_cpassert = devm_gpiod_get(dev, "cpassert", GPIOD_OUT_HIGH);
if (IS_ERR(pdata->gpio_cpassert))
return PTR_ERR(pdata->gpio_cpassert);
pdata->gpio_cppanic = devm_gpiod_get(dev, "cppanic", GPIOD_OUT_HIGH);
if (IS_ERR(pdata->gpio_cppanic))
return PTR_ERR(pdata->gpio_cppanic);
pdata->gpio_cppoweroff = devm_gpiod_get(dev, "cppoweroff", GPIOD_IN);
if (IS_ERR(pdata->gpio_cpassert))
return PTR_ERR(pdata->gpio_cppoweroff);
*init = pdata;
return 0;
}
static int modem_ctrl_parse_dt(struct modem_ctrl_init_data **init,
struct device *dev)
{
struct modem_ctrl_init_data *pdata;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
pdata->name = cdev_name;
pdata->gpio_poweron = devm_gpiod_get(dev, "poweron", GPIOD_OUT_LOW);
if (IS_ERR(pdata->gpio_poweron))
return PTR_ERR(pdata->gpio_poweron);
pdata->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(pdata->gpio_reset))
return PTR_ERR(pdata->gpio_reset);
/* Triger watchdog,assert,panic of orca */
pdata->gpio_cpwatchdog = devm_gpiod_get(dev, "cpwatchdog", GPIOD_IN);
if (IS_ERR(pdata->gpio_cpwatchdog))
return PTR_ERR(pdata->gpio_cpwatchdog);
pdata->gpio_cpassert = devm_gpiod_get(dev, "cpassert", GPIOD_IN);
if (IS_ERR(pdata->gpio_cpassert))
return PTR_ERR(pdata->gpio_cpassert);
pdata->gpio_cppanic = devm_gpiod_get(dev, "cppanic", GPIOD_IN);
if (IS_ERR(pdata->gpio_cppanic))
return PTR_ERR(pdata->gpio_cppanic);
pdata->gpio_cppoweroff = devm_gpiod_get(dev,
"cppoweroff", GPIOD_OUT_HIGH);
if (IS_ERR(pdata->gpio_cpassert))
return PTR_ERR(pdata->gpio_cppoweroff);
pdata->modem_status = MDM_CTRL_POWER_OFF;
*init = pdata;
return 0;
}
static inline void
modem_ctrl_destroy_pdata(struct modem_ctrl_init_data **init)
{
struct modem_ctrl_init_data *pdata = *init;
pdata = NULL;
}
static int modem_ctrl_restart_handle(struct notifier_block *this,
unsigned long mode, void *cmd)
{
if (!mcd_dev || mcd_dev->soc_type == ROC1_SOC)
return NOTIFY_DONE;
modem_ctrl_notify_abnormal_status(MDM_PANIC);
while (1)
;
return NOTIFY_DONE;
}
static struct notifier_block modem_ctrl_restart_handler = {
.notifier_call = modem_ctrl_restart_handle,
.priority = 150,
};
static int modem_ctrl_probe(struct platform_device *pdev)
{
struct modem_ctrl_init_data *init = pdev->dev.platform_data;
struct modem_ctrl_device *modem_ctrl_dev;
dev_t devid;
int rval;
struct device *dev = &pdev->dev;
modem_ctrl_dev = devm_kzalloc(dev, sizeof(*modem_ctrl_dev), GFP_KERNEL);
if (!modem_ctrl_dev)
return -ENOMEM;
mcd_dev = modem_ctrl_dev;
if (of_device_is_compatible(pdev->dev.of_node, "sprd,roc1-modem-ctrl"))
modem_ctrl_dev->soc_type = ROC1_SOC;
else
modem_ctrl_dev->soc_type = ORCA_SOC;
if (modem_ctrl_dev->soc_type == ROC1_SOC) {
rval = modem_ctrl_parse_dt(&init, &pdev->dev);
if (rval) {
dev_err(dev,
"Failed to parse modem_ctrl device tree, ret=%d\n",
rval);
return rval;
}
} else {
rval = modem_ctrl_parse_modem_dt(&init, &pdev->dev);
if (rval) {
dev_err(dev,
"Failed to parse modem_ctrl device tree, ret=%d\n",
rval);
return rval;
}
}
dev_dbg(dev, "after parse device tree, name=%s soctype=%d\n",
init->name,
modem_ctrl_dev->soc_type);
rval = alloc_chrdev_region(&devid, 0, 1, init->name);
if (rval != 0) {
dev_err(dev, "Failed to alloc modem_ctrl chrdev\n");
goto error3;
}
cdev_init(&modem_ctrl_dev->cdev, &modem_ctrl_fops);
rval = cdev_add(&modem_ctrl_dev->cdev, devid, 1);
if (rval != 0) {
dev_err(dev, "Failed to add modem_ctrl cdev\n");
goto error2;
}
modem_ctrl_dev->major = MAJOR(devid);
modem_ctrl_dev->minor = MINOR(devid);
modem_ctrl_dev->dev = device_create(modem_ctrl_class, NULL,
MKDEV(modem_ctrl_dev->major,
modem_ctrl_dev->minor),
NULL, "%s", init->name);
if (!modem_ctrl_dev->dev) {
dev_err(dev, "create dev failed\n");
rval = -ENODEV;
goto error1;
}
modem_ctrl_dev->init = init;
platform_set_drvdata(pdev, modem_ctrl_dev);
rval = modem_gpios_init(modem_ctrl_dev, modem_ctrl_dev->soc_type);
if (rval) {
dev_err(dev, "request gpios error\n");
goto error0;
}
rval = register_restart_handler(&modem_ctrl_restart_handler);
if (rval) {
dev_err(dev, "cannot register restart handler err=%d\n", rval);
goto error0;
}
return 0;
error0:
device_destroy(modem_ctrl_class,
MKDEV(modem_ctrl_dev->major,
modem_ctrl_dev->minor));
error1:
cdev_del(&modem_ctrl_dev->cdev);
error2:
unregister_chrdev_region(devid, 1);
error3:
modem_ctrl_destroy_pdata(&init);
return rval;
}
static int modem_ctrl_remove(struct platform_device *pdev)
{
struct modem_ctrl_device *modem_ctrl_dev = platform_get_drvdata(pdev);
unregister_reboot_notifier(&modem_ctrl_restart_handler);
device_destroy(modem_ctrl_class,
MKDEV(modem_ctrl_dev->major,
modem_ctrl_dev->minor));
cdev_del(&modem_ctrl_dev->cdev);
unregister_chrdev_region(MKDEV(modem_ctrl_dev->major,
modem_ctrl_dev->minor), 1);
modem_ctrl_destroy_pdata(&modem_ctrl_dev->init);
platform_set_drvdata(pdev, NULL);
return 0;
}
static void modem_ctrl_shutdown(struct platform_device *pdev)
{
if (mcd_dev->soc_type == ROC1_SOC) {
atomic_notifier_call_chain(&modem_ctrl_chain,
MDM_POWER_OFF, NULL);
/*
* sleep 50 ms for other module to do something
* before orca power down.
*/
msleep(50);
modem_ctrl_send_cmd_to_cp(MDM_POWER_OFF);
/* Sleep 500ms for cp to deal power down process otherwise
* cp will not power down clearly.
*/
msleep(500);
}
}
static const struct of_device_id modem_ctrl_match_table[] = {
{.compatible = "sprd,roc1-modem-ctrl", },
{.compatible = "sprd,orca-modem-ctrl", },
};
static struct platform_driver modem_ctrl_driver = {
.driver = {
.name = "modem_ctrl",
.of_match_table = modem_ctrl_match_table,
},
.probe = modem_ctrl_probe,
.remove = modem_ctrl_remove,
.shutdown = modem_ctrl_shutdown,
};
int modem_ctrl_init(void)
{
modem_ctrl_class = class_create(THIS_MODULE, "modem_ctrl");
if (IS_ERR(modem_ctrl_class))
return PTR_ERR(modem_ctrl_class);
return platform_driver_register(&modem_ctrl_driver);
}
EXPORT_SYMBOL_GPL(modem_ctrl_init);
void modem_ctrl_exit(void)
{
class_destroy(modem_ctrl_class);
platform_driver_unregister(&modem_ctrl_driver);
}
EXPORT_SYMBOL_GPL(modem_ctrl_exit);