sdk-hwV1.3/lichee/linux-4.9/drivers/mailbox/sunxi_rv32_standby.c

420 lines
9.7 KiB
C
Raw Normal View History

2024-05-07 10:09:20 +00:00
// SPDX-License-Identifier: GPL-2.0
/*
* sunxi's RV32 Mailbox Standby Driver
*
* Copyright (C) 2021 Allwinnertech - All Rights Reserved
*
* Author: lijiajian <lijiajian@allwinnertech.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/elf.h>
#include <linux/firmware.h>
#include <linux/platform_device.h>
#include <linux/mailbox_client.h>
#define MBOX_NAME "mbox-chan"
#define PM_RV32_CLK_REG1 (0x02001000 | 0x0d04)
#define PM_RV32_CLK_REG2 (0x02001000 | 0x0d0C)
#define PM_RV32_CLK_REG3 (0x02001000 | 0x0d00)
#define PM_RV32_CLK_REG4 (0x06010000 | 0x0204)
#define PM_RV32_POWER_SUSPEND 0xf3f30101
#define PM_RV32_POWER_ACK 0xf3f30102
#define PM_RV32_POWER_DEAD 0xf3f30103
#define PM_RV32_POWER_RESUME 0xf3f30204
#define SUSPEND_TIMEOUT msecs_to_jiffies(100)
#define RESUME_TIMEOUT msecs_to_jiffies(100)
struct sunxi_rv32_power {
struct device *dev;
struct mbox_chan *chan;
struct mbox_client client;
struct platform_device *pdev;
const char *firmware;
uint32_t boot;
struct completion complete;
struct completion ack;
void __iomem *clk1;
void __iomem *clk2;
void __iomem *clk3;
void __iomem *clk4;
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP)
int status;
struct dentry *dir;
#endif
};
static void sunxi_mb_rx_callback(struct mbox_client *cl, void *data)
{
u32 tmp = *(uint32_t *)data;
struct sunxi_rv32_power *ddata = dev_get_drvdata(cl->dev);
switch (tmp) {
case PM_RV32_POWER_ACK:
complete(&ddata->ack);
break;
case PM_RV32_POWER_DEAD:
complete(&ddata->complete);
break;
default:
dev_warn(ddata->dev, "Undown data(0x%08x)\n", tmp);
}
}
static int sunxi_request_mbox(struct sunxi_rv32_power *ddata)
{
struct device *dev = ddata->dev;
ddata->client.rx_callback = sunxi_mb_rx_callback;
ddata->client.tx_block = true;
ddata->client.tx_done = NULL;
ddata->client.dev = ddata->dev;
ddata->chan = mbox_request_channel_byname(&ddata->client, MBOX_NAME);
if (IS_ERR(ddata->chan)) {
dev_warn(dev, "cannot get %s channel (ret=%ld)\n", MBOX_NAME,
PTR_ERR(ddata->chan));
goto err_probe;
}
return 0;
err_probe:
return -EPROBE_DEFER;
}
static void sunxi_rv32_free_mbox(struct sunxi_rv32_power *ddata)
{
if (ddata->chan)
mbox_free_channel(ddata->chan);
ddata->chan = NULL;
}
#ifdef CONFIG_PM_SLEEP
static void sunxi_e907_get_boot(struct sunxi_rv32_power *ddata)
{
ddata->boot = readl(ddata->clk4);
if (ddata->boot == 0)
dev_warn(ddata->dev, "fail to get e907 boot addr\n");
dev_dbg(ddata->dev, "Get boot addr:0x%08x\n", ddata->boot);
}
static void sunxi_e907_clk_config(struct sunxi_rv32_power *ddata, bool status)
{
uint32_t reg_val;
if (status) {
/* turn off clk and asserd cpu */
reg_val = (0x16aa << 16 | 0x4);
writel(reg_val, ddata->clk1);
/* Reset Cpu */
reg_val = (1 << 16) | (1 << 0);
writel(reg_val, ddata->clk2);
/* Set Cpu boot addr */
writel(ddata->boot, ddata->clk4);
reg_val = (0x16aa << 16 | 0x4);
writel(reg_val, ddata->clk1);
/* Set e907 freq to 600MHz */
reg_val = (3 << 24) | (1 << 8);
writel(reg_val, ddata->clk3);
/* turn on clk and deassert cpu */
reg_val = (0x16aa << 16 | 0x07);
writel(reg_val, ddata->clk1);
} else {
/* turn off clk and asserd cpu */
reg_val = (0x16aa << 16);
writel(reg_val, ddata->clk1);
}
}
static int sunxi_rv32_power_suspend(struct device *dev)
{
int ret = 0;
u32 data = PM_RV32_POWER_SUSPEND;
struct sunxi_rv32_power *ddata = dev_get_drvdata(dev);
ret = mbox_send_message(ddata->chan, &data);
if (ret < 0) {
dev_err(dev, "fail to send mbox msg\n");
return ret;
}
/* save e907 boot addr */
sunxi_e907_get_boot(ddata);
/* wait e907 ack this msg */
ret = wait_for_completion_timeout(&ddata->ack, SUSPEND_TIMEOUT);
if (!ret) {
dev_err(dev, "timeout wait rv32 ack\n");
return 0;
}
return 0;
}
static int sunxi_rv32_power_suspend_late(struct device *dev)
{
int ret = 0;
struct sunxi_rv32_power *ddata = dev_get_drvdata(dev);
/* wait e907 send dead msg */
ret = wait_for_completion_timeout(&ddata->complete, SUSPEND_TIMEOUT);
if (!ret)
dev_err(dev, "timeout wait rv32 suspend\n");
sunxi_e907_clk_config(ddata, false);
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP)
ddata->status = 1;
#endif
return 0;
}
static int sunxi_rv32_power_resume(struct device *dev)
{
struct sunxi_rv32_power *ddata = dev_get_drvdata(dev);
int ret = 0;
u32 data = PM_RV32_POWER_RESUME;
sunxi_e907_clk_config(ddata, true);
udelay(10);
ret = mbox_send_message(ddata->chan, &data);
if (ret < 0) {
dev_err(dev, "fail to send mbox msg\n");
return ret;
}
/* wait e907 ack this msg */
ret = wait_for_completion_timeout(&ddata->ack, RESUME_TIMEOUT);
if (!ret) {
dev_err(dev, "timeout wait rv32 resume\n");
return -EBUSY;
}
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP)
ddata->status = 0;
#endif
return 0;
}
static struct dev_pm_ops sunxi_rv32_pm_ops = {
.suspend = sunxi_rv32_power_suspend,
.suspend_late = sunxi_rv32_power_suspend_late,
.resume = sunxi_rv32_power_resume,
};
#endif
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP)
static const char *status_string[2] = {
"resume", "suspend"
};
static ssize_t sunxi_status_read(struct file *filp, char __user *userbuf,
size_t count, loff_t *ppos)
{
struct sunxi_rv32_power *ddata = filp->private_data;
int status = 0;
int len;
status = ddata->status == 0 ? 0 : 1;
len = strlen(status_string[status]);
return simple_read_from_buffer(userbuf, count, ppos,
status_string[status], len);
}
static ssize_t sunxi_status_write(struct file *filp, const char __user *userbuf,
size_t count, loff_t *ppos)
{
struct sunxi_rv32_power *ddata = filp->private_data;
char buf[16];
int ret = 0;
if (count > sizeof(buf) || count <= 0)
return -EINVAL;
ret = copy_from_user(buf, userbuf, count);
if (ret)
return -EFAULT;
buf[count - 1] = '\0';
if (!strncmp(buf, status_string[0], count)) {
if (ddata->status != 0) {
dev_dbg(ddata->dev, "resume device.\n");
sunxi_rv32_power_resume(ddata->dev);
} else
dev_dbg(ddata->dev, "device already resume\n");
} else if (!strncmp(buf, status_string[1], count)) {
if (ddata->status != 1) {
dev_dbg(ddata->dev, "suspend device.\n");
sunxi_rv32_power_suspend(ddata->dev);
sunxi_rv32_power_suspend_late(ddata->dev);
} else
dev_dbg(ddata->dev, "device already suspend\n");
}
return count;
}
static const struct file_operations sunxi_power_status_ops = {
.write = sunxi_status_write,
.read = sunxi_status_read,
.open = simple_open,
};
void sunxi_rv32_create_file(struct sunxi_rv32_power *ddata)
{
debugfs_create_file("status", 0600, ddata->dir,
ddata, &sunxi_power_status_ops);
}
#endif
static const struct of_device_id sunxi_rv32_match[] = {
{ .compatible = "allwinner,sunxi-e907-standby" },
{},
};
MODULE_DEVICE_TABLE(of, sunxi_rv32_match);
static int sunxi_rv32_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sunxi_rv32_power *ddata;
int ret;
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
return -ENOMEM;
ddata->dev = dev;
ddata->pdev = pdev;
ddata->boot = 0;
ret = of_property_read_string(dev->of_node, "firmware", &ddata->firmware);
if (ret) {
dev_err(dev, "failed to read firmware property\n");
return ret;
}
dev_dbg(dev, "firmware name:%s\n", ddata->firmware);
ddata->clk1 = devm_ioremap(dev, PM_RV32_CLK_REG1, 4);
if (!ddata->clk1) {
dev_err(dev, "ioremap failed\r\n");
ret = -ENOMEM;
goto out;
}
ddata->clk2 = devm_ioremap(dev, PM_RV32_CLK_REG2, 4);
if (!ddata->clk2) {
dev_err(dev, "ioremap failed\r\n");
ret = -ENOMEM;
goto out;
}
ddata->clk3 = devm_ioremap(dev, PM_RV32_CLK_REG3, 4);
if (!ddata->clk3) {
dev_err(dev, "ioremap failed\r\n");
ret = -ENOMEM;
goto out;
}
ddata->clk4 = devm_ioremap(dev, PM_RV32_CLK_REG4, 4);
if (!ddata->clk4) {
dev_err(dev, "ioremap failed\r\n");
ret = -ENOMEM;
goto out;
}
ret = sunxi_request_mbox(ddata);
if (ret < 0) {
dev_err(dev, "sunxi_rv32_request_mbox failed\n");
goto out;
}
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP)
if (debugfs_initialized()) {
ddata->dir = debugfs_create_dir(KBUILD_MODNAME, NULL);
if (!ddata->dir)
dev_err(dev, "can't create debugfs dir\n");
else
sunxi_rv32_create_file(ddata);
}
#endif
init_completion(&ddata->ack);
init_completion(&ddata->complete);
platform_set_drvdata(pdev, ddata);
return 0;
out:
return ret;
}
static int sunxi_rv32_remove(struct platform_device *pdev)
{
struct sunxi_rv32_power *ddata = platform_get_drvdata(pdev);
#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP)
debugfs_remove(ddata->dir);
#endif
reinit_completion(&ddata->ack);
reinit_completion(&ddata->complete);
sunxi_rv32_free_mbox(ddata);
return 0;
}
static struct platform_driver sunxi_rv32_standby_driver = {
.probe = sunxi_rv32_probe,
.remove = sunxi_rv32_remove,
.driver = {
.name = "sunxi-rv32-standby",
.of_match_table = sunxi_rv32_match,
#ifdef CONFIG_PM_SLEEP
.pm = &sunxi_rv32_pm_ops,
#endif
},
};
static int __init sunxi_rv32_standby_init(void)
{
int ret;
ret = platform_driver_register(&sunxi_rv32_standby_driver);
return ret;
}
static void __exit sunxi_rv32_standby_exit(void)
{
platform_driver_unregister(&sunxi_rv32_standby_driver);
}
device_initcall(sunxi_rv32_standby_init);
module_exit(sunxi_rv32_standby_exit);
MODULE_DESCRIPTION("RV32 Mailbox Standby Driver");
MODULE_AUTHOR("lijiajian <lijiajian@allwinnertech.com>");
MODULE_LICENSE("GPL v2");