android13/kernel-5.10/drivers/sprd_pcie/sipc/sipc.c

525 lines
11 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/debugfs.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/sizes.h>
#include <linux/version.h>
#include <linux/dma-mapping.h>
#include <linux/highmem.h>
#include "../include/sipc.h"
#include "sipc_priv.h"
#define MBOX_BAMK "mbox"
#define PCIE_BAMK "pcie"
enum {
NORMAL_MODE = 0,
CHARGE_MODE,
CALI_MODE
};
#define CALI_LATENCY (10000 * 1000)
#define NORMAL_LATENCY (1 * 1000)
/*
* In charge mode, will only boot pm system,
* so just create pm systen sipc.
*/
static u8 g_boot_mode = NORMAL_MODE;
/*
static int __init sipc_early_mode(char *str)
{
if (!memcmp(str, "charger", 7))
g_boot_mode = CHARGE_MODE;
else if (!memcmp(str, "cali", 4))
g_boot_mode = CALI_MODE;
else
g_boot_mode = NORMAL_MODE;
return 0;
}
early_param("androidboot.mode", sipc_early_mode);
*/
#if defined(CONFIG_DEBUG_FS)
void sipc_debug_putline(struct seq_file *m, char c, int n)
{
char buf[300];
int i, max, len;
/* buf will end with '\n' and 0 */
max = ARRAY_SIZE(buf) - 2;
len = (n > max) ? max : n;
for (i = 0; i < len; i++)
buf[i] = c;
buf[i] = '\n';
buf[i + 1] = 0;
seq_puts(m, buf);
}
EXPORT_SYMBOL_GPL(sipc_debug_putline);
#endif
static u32 sipc_rxirq_status(u8 dst)
{
return 0;
}
static void sipc_rxirq_clear(u8 dst)
{
}
static void sipc_txirq_trigger(u8 dst, u64 msg)
{
struct smsg_ipc *ipc;
ipc = smsg_ipcs[dst];
if (ipc) {
#ifdef CONFIG_SPRD_MAILBOX
if (ipc->type == SIPC_BASE_MBOX) {
mbox_raw_sent(ipc->core_id, msg);
return;
}
#endif
if (ipc->type == SIPC_BASE_PCIE) {
#ifdef CONFIG_SPRD_PCIE_EP_DEVICE
sprd_ep_dev_raise_irq(ipc->ep_dev, PCIE_DBELL_SIPC_IRQ);
#endif
#ifdef CONFIG_PCIE_EPF_SPRD
sprd_pci_epf_raise_irq(ipc->ep_fun, PCIE_MSI_SIPC_IRQ);
#endif
return;
}
}
}
#ifdef SPRD_PCIE_USE_DTS
static int sipc_parse_dt(struct smsg_ipc *ipc,
struct device_node *np, struct device *dev)
{
u32 val[3];
int ret;
const char *type;
/* get name */
ret = of_property_read_string(np, "sprd,name", &ipc->name);
if (ret)
return ret;
pr_info("sipc: name=%s\n", ipc->name);
/* get sipc type, optional */
if (of_property_read_string(np, "sprd,type", &type) == 0) {
pr_info("sipc: type=%s\n", type);
if (strcmp(MBOX_BAMK, type) == 0)
ipc->type = SIPC_BASE_MBOX;
else if (strcmp(PCIE_BAMK, type) == 0)
ipc->type = SIPC_BASE_PCIE;
}
/* get sipc client, optional */
if (of_property_read_u32_array(np, "sprd,client", val, 1) == 0) {
ipc->client = (u8)val[0];
pr_info("sipc: client=%d\n", ipc->client);
}
/* get sipc dst */
ret = of_property_read_u32_array(np, "sprd,dst", val, 1);
if (!ret) {
ipc->dst = (u8)val[0];
pr_info("sipc: dst =%d\n", ipc->dst);
}
if (ret || ipc->dst >= SIPC_ID_NR) {
pr_err("sipc: dst err, ret =%d.\n", ret);
return ret;
}
#ifdef CONFIG_SPRD_MAILBOX
if (ipc->type == SIPC_BASE_MBOX) {
/* get core id */
ipc->core_id = (u8)MBOX_INVALID_CORE;
ret = of_property_read_u32_array(np, "sprd,core", val, 1);
if (!ret) {
ipc->core_id = (u8)val[0];
pr_info("sipc: core=%d\n", ipc->core_id);
} else {
pr_err("sipc: core err, ret =%d.\n", ret);
return ret;
}
/* get core sensor id, optional*/
ipc->core_sensor_id = (u8)MBOX_INVALID_CORE;
if (of_property_read_u32_array(np, "sprd,core_sensor",
val, 1) == 0) {
ipc->core_sensor_id = (u8)val[0];
pr_info("sipc: core_sensor=%d\n", ipc->core_sensor_id);
}
}
#endif
#ifdef CONFIG_SPRD_PCIE_EP_DEVICE
if (ipc->type == SIPC_BASE_PCIE) {
#ifdef CONFIG_SPRD_PCIE
struct device_node *pdev_node;
#endif
ret = of_property_read_u32_array(np,
"sprd,ep-dev",
&ipc->ep_dev,
1);
pr_info("sipc: ep_dev=%d\n", ipc->ep_dev);
if (ret || ipc->ep_dev >= PCIE_EP_NR) {
pr_err("sipc: ep_dev err, ret =%d.\n", ret);
return ret;
}
#ifdef CONFIG_SPRD_PCIE
/* get pcie rc ctrl device */
pdev_node = of_parse_phandle(np, "sprd,rc-ctrl", 0);
if (!pdev_node) {
pr_err("sipc: sprd,rc-ctrl err.\n");
return -ENODEV;
}
ipc->pcie_dev = of_find_device_by_node(pdev_node);
of_node_put(pdev_node);
if (!ipc->pcie_dev) {
pr_err("sipc: find pcie_dev err.\n");
return -ENODEV;
}
#endif
}
#endif
#ifdef CONFIG_PCIE_EPF_SPRD
if (ipc->type == SIPC_BASE_PCIE) {
ret = of_property_read_u32_array(np,
"sprd,ep-fun",
&ipc->ep_fun,
1);
pr_info("sipc: ep_fun=%d\n", ipc->ep_fun);
if (ret || ipc->ep_fun >= SPRD_FUNCTION_MAX) {
pr_err("sipc: ep_fun err, ret =%d.\n", ret);
return ret;
}
/* parse doolbell irq */
ret = of_irq_get(np, 0);
if (ret < 0) {
pr_err("sipc: doorbell irq err, ret=%d\n", ret);
return -EINVAL;
}
ipc->irq = ret;
pr_info("sipc: irq=%d\n", ipc->irq);
}
#endif
/* get smem type */
ret = of_property_read_u32_array(np,
"sprd,smem-type",
&val[0],
1);
if (!ret)
ipc->smem_type = (enum smem_type)val[0];
else
ipc->smem_type = SMEM_LOCAL;
pr_info("sipc: smem_type = %d, ret =%d\n", ipc->smem_type, ret);
/* get smem info */
ret = of_property_read_u32_array(np,
"sprd,smem-info",
val,
3);
if (ret) {
pr_err("sipc: parse smem info failed.\n");
return ret;
}
ipc->smem_base = val[0];
ipc->dst_smem_base = val[1];
ipc->smem_size = val[2];
pr_info("sipc: smem_base=0x%x, dst_smem_base=0x%x, smem_size=0x%x\n",
ipc->smem_base, ipc->dst_smem_base, ipc->smem_size);
#ifdef CONFIG_PHYS_ADDR_T_64BIT
/* try to get high_offset */
ret = of_property_read_u32(np,
"sprd,high-offset",
val);
if (!ret) {
ipc->high_offset = val[0];
pr_info("sipc: high_offset=0x%xn", ipc->high_offset);
}
#endif
if (ipc->type == SIPC_BASE_PCIE) {
/* pcie sipc, the host must use loacal SMEM_LOCAL */
if (!ipc->client && ipc->smem_type != SMEM_LOCAL) {
pr_err("sipc: host must use local smem!");
return -EINVAL;
}
if (ipc->client && ipc->smem_type != SMEM_PCIE) {
pr_err("sipc: client must use pcie smem!");
return -EINVAL;
}
}
return 0;
}
#else
static u32 sipc_get_smem_base(size_t size)
{
unsigned long order = get_order(size);
struct page *page, *p, *e;
page = alloc_pages(GFP_KERNEL|GFP_DMA32, order);
if(page == NULL) {
printk("sipc alloc pages fail\n");
return 0;
}
split_page(page, order);
for (p = page +(size >> PAGE_SHIFT), e = page + (1 << order); p < e; p++)
__free_page(p);
if (PageHighMem(page)) {
phys_addr_t base = __pfn_to_phys(page_to_pfn(page));
//phys_addr_t end = base + size;
while (size > 0) {
void *ptr = kmap_atomic(page);
memset(ptr, 0, PAGE_SIZE);
kunmap_atomic(ptr);
page++;
size -= PAGE_SIZE;
}
return base;
} else {
void *ptr = page_address(page);
memset(ptr, 0, size);
return __pa(ptr);
}
}
static int sipc_parse_dt(struct smsg_ipc *ipc,
struct device_node *np, struct device *dev)
{
u32 val[3];
int ret = 0;
//dma_addr_t *dma_handle;
/* get name */
ipc->name = "sprd,sipc";
pr_info("sipc: name=%s\n", ipc->name);
/* get sipc type, optional */
ipc->type = SIPC_BASE_PCIE;
pr_info("sipc: type=%d\n", ipc->type);
/* get sipc client, optional */
/* get sipc dst */
ipc->dst = 1;
pr_info("sipc: dst =%d\n", ipc->dst);
if (ipc->dst >= SIPC_ID_NR) {
pr_err("sipc: dst err\n");
return ret;
}
#ifdef CONFIG_SPRD_PCIE_EP_DEVICE
if (ipc->type == SIPC_BASE_PCIE) {
ipc->ep_dev = 0;
pr_info("sipc: ep_dev=%d\n", ipc->ep_dev);
if (ipc->ep_dev >= PCIE_EP_NR) {
pr_err("sipc: ep_dev err\n");
return -1;
}
}
#endif
/* get smem type */
ipc->smem_type = SMEM_LOCAL;
pr_info("sipc: smem_type = %d\n", ipc->smem_type);
/* get smem info */
val[0] = sipc_get_smem_base(0x0300000);
val[1] = val[0];
val[2] = 0x0300000;
ipc->smem_base = val[0];
ipc->dst_smem_base = val[1];
ipc->smem_size = val[2];
pr_info("sipc: smem_base=0x%x, dst_smem_base=0x%x, smem_size=0x%x\n",
ipc->smem_base, ipc->dst_smem_base, ipc->smem_size);
#ifdef CONFIG_PHYS_ADDR_T_64BIT
/* try to get high_offset */
ipc->high_offset = 0;
pr_info("sipc: high_offset=0x%xn", ipc->high_offset);
#endif
if (ipc->type == SIPC_BASE_PCIE) {
/* pcie sipc, the host must use loacal SMEM_LOCAL */
if (!ipc->client && ipc->smem_type != SMEM_LOCAL) {
pr_err("sipc: host must use local smem!");
return -EINVAL;
}
if (ipc->client && ipc->smem_type != SMEM_PCIE) {
pr_err("sipc: client must use pcie smem!");
return -EINVAL;
}
}
return 0;
}
#endif
static int sipc_probe(struct platform_device *pdev)
{
struct smsg_ipc *ipc;
struct device_node *np;
if (1) {
np = pdev->dev.of_node;
ipc = devm_kzalloc(&pdev->dev,
sizeof(struct smsg_ipc),
GFP_KERNEL);
if (!ipc)
return -ENOMEM;
if (sipc_parse_dt(ipc, np, &pdev->dev)) {
pr_err("%s: failed to parse dt!\n", __func__);
return -ENODEV;
}
/*
* In charge mode, will only boot pm system,
* so just create pm systen sipc.
*/
if (g_boot_mode == CHARGE_MODE && ipc->dst != SIPC_ID_PM_SYS)
return -ENODEV;
ipc->rxirq_status = sipc_rxirq_status;
ipc->rxirq_clear = sipc_rxirq_clear;
ipc->txirq_trigger = sipc_txirq_trigger;
spin_lock_init(&ipc->txpinlock);
if (ipc->type == SIPC_BASE_PCIE) {
/* init mpm delay enter idle time for pcie. */
if (g_boot_mode == CALI_MODE)
ipc->latency = CALI_LATENCY;
else
ipc->latency = NORMAL_LATENCY;
}
smsg_ipc_create(ipc);
platform_set_drvdata(pdev, ipc);
}
return 0;
}
static int sipc_remove(struct platform_device *pdev)
{
struct smsg_ipc *ipc = platform_get_drvdata(pdev);
smsg_ipc_destroy(ipc);
devm_kfree(&pdev->dev, ipc);
return 0;
}
#ifdef SPRD_PCIE_USE_DTS
static const struct of_device_id sipc_match_table[] = {
{ .compatible = "sprd,sipc", },
{ },
};
#endif
static struct platform_driver sipc_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "sipc",
#ifdef SPRD_PCIE_USE_DTS
.of_match_table = sipc_match_table,
#endif
},
.probe = sipc_probe,
.remove = sipc_remove,
};
#ifndef SPRD_PCIE_USE_DTS
static void sipc_platform_device_release(struct device *dev) {}
static struct platform_device sipc_device = {
.name = "sipc",
.id = -1,
.dev = {
.release = sipc_platform_device_release,
}
};
#endif
int sipc_init(void)
{
int ret;
smsg_init_channel2index();
#ifndef SPRD_PCIE_USE_DTS
if((ret = platform_device_register(&sipc_device)))
return ret;
#endif
if((ret = platform_driver_register(&sipc_driver))) {
#ifndef SPRD_PCIE_USE_DTS
platform_device_unregister(&sipc_device);
#endif
return ret;
}
return ret;
}
EXPORT_SYMBOL_GPL(sipc_init);
void sipc_exit(void)
{
platform_driver_unregister(&sipc_driver);
printk("dayin is here0\n");
#ifndef SPRD_PCIE_USE_DTS
platform_device_unregister(&sipc_device);
#endif
}
EXPORT_SYMBOL_GPL(sipc_exit);