378 lines
9.8 KiB
C
378 lines
9.8 KiB
C
/*
|
|
* Copyright (c) 2017 MediaTek Inc.
|
|
* Author: Star Chang <star.chang@mediatek.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.
|
|
*
|
|
* 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/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_pci.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/resource.h>
|
|
#include <linux/types.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
|
|
/* platform device & platform driver match name */
|
|
#define OF_RBUS_NAME "mediatek,wbsys"
|
|
#define OF_PIO_NAME "mediatek,mt7622-pctl-a-syscfg"
|
|
|
|
#define RBUS_VENDOR_ID_OFFSET 0
|
|
#define RBUS_CHIP_ID_OFFSET 2
|
|
#define RBUS_BAR_OFFSET 0x10
|
|
#define RBUS_DEFAULT_CHIP_ID 0x7622
|
|
#define RBUS_DEFAULT_VEND_ID 0x14c3
|
|
#define RBUS_TSSI_CTRL_OFFSET 0x34
|
|
#define RBUS_TSSI_CTRL_MASK 0x1
|
|
#define RBUS_PA_LNA_CTRL_OFFSET 0x38
|
|
#define RBUS_PA_LNA_CTRL_MASK 0x3
|
|
|
|
#define GPIO_G2_MISC_OFFSET 0x00000AF0
|
|
#define GPIO_G2_MISC_MASK 0xffffff00
|
|
|
|
static char rbus_string[] = "rbus";
|
|
int multi_intr_2nd = -ENXIO;
|
|
int multi_intr_3rd = -ENXIO;
|
|
int multi_intr_4th = -ENXIO;
|
|
EXPORT_SYMBOL(multi_intr_2nd);
|
|
EXPORT_SYMBOL(multi_intr_3rd);
|
|
EXPORT_SYMBOL(multi_intr_4th);
|
|
|
|
static const struct of_device_id rbus_of_ids[] = {
|
|
{ .compatible = OF_RBUS_NAME, },
|
|
{ },
|
|
};
|
|
|
|
struct rbus_dev {
|
|
char name[36];
|
|
struct device *dev;
|
|
struct resource *res;
|
|
struct list_head resources;
|
|
unsigned int base_addr;
|
|
unsigned int irq;
|
|
unsigned int chip_id;
|
|
unsigned int vend_id;
|
|
};
|
|
|
|
enum {
|
|
TSSI_MODE_DIS=0,
|
|
TSSI_MODE_EN=1
|
|
};
|
|
|
|
enum {
|
|
IPA_ILNA_MODE=0,
|
|
IPA_ELNA_MODE=1,
|
|
EPA_ELNA_MODE=2,
|
|
EPA_ILNA_MODE=3
|
|
};
|
|
|
|
#define RBUS_IO_READ32(_A, _R, _pV) (*(_pV) = readl((void *)(_A + _R)))
|
|
#define RBUS_IO_WRITE32(_A, _R, _V) writel(_V, (void *)(_A + _R))
|
|
|
|
/*fake configure space*/
|
|
static unsigned char rbus_conf_space[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x76, 0xc3, 0x14,
|
|
0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x60, 0x61, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x05, 0x78, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0xc3, 0x01, 0x08, 0x00, 0x00, 0x00,
|
|
0x10, 0x00, 0x02, 0x00, 0x40, 0x83, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x12, 0x8c, 0x40, 0x01,
|
|
0x43, 0x00, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
static int
|
|
rbus_tssi_config(struct platform_device *pdev, unsigned char mode)
|
|
{
|
|
struct device_node *node;
|
|
void __iomem *pio_base;
|
|
unsigned long addr;
|
|
unsigned int value = 0;
|
|
|
|
node = of_find_compatible_node(NULL, NULL, OF_PIO_NAME);
|
|
if (!node) {
|
|
dev_err(&pdev->dev, "%s(): can't find node for %s\n", __func__, OF_PIO_NAME);
|
|
return -ENODEV;
|
|
}
|
|
|
|
pio_base = of_iomap(node, 0);
|
|
of_node_put(node);
|
|
|
|
if (!pio_base)
|
|
return -ENOMEM;
|
|
|
|
addr = (unsigned long)pio_base;
|
|
|
|
RBUS_IO_READ32(addr, GPIO_G2_MISC_OFFSET, &value);
|
|
|
|
if (mode == TSSI_MODE_EN) {
|
|
value &= GPIO_G2_MISC_MASK;
|
|
RBUS_IO_WRITE32(addr, GPIO_G2_MISC_OFFSET, value);
|
|
}
|
|
|
|
RBUS_IO_READ32(addr, GPIO_G2_MISC_OFFSET, &value);
|
|
|
|
iounmap(pio_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rbus_pa_lan_config(struct platform_device *pdev, unsigned int devfn, unsigned char mode)
|
|
{
|
|
struct pinctrl *p;
|
|
struct pinctrl_state *s;
|
|
unsigned char state[32] = "";
|
|
int ret = 0;
|
|
|
|
if (mode != IPA_ELNA_MODE && mode != EPA_ELNA_MODE)
|
|
return ret;
|
|
|
|
p = devm_pinctrl_get(&pdev->dev);
|
|
if (IS_ERR(p)) {
|
|
dev_err(&pdev->dev, "%s(): can't get pinctrl by dev:%p\n", __func__, &pdev->dev);
|
|
return ret;
|
|
}
|
|
|
|
strcpy(state, "state_epa");
|
|
|
|
s = pinctrl_lookup_state(p, state);
|
|
if (IS_ERR(s)) {
|
|
devm_pinctrl_put(p);
|
|
dev_err(&pdev->dev, "%s(): can't find pinctrl state: state_epa\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
ret = pinctrl_select_state(p, s);
|
|
if (ret < 0) {
|
|
devm_pinctrl_put(p);
|
|
dev_err(&pdev->dev, "%s(): pinctrl select to state_epa fail!, ret=%d\n", __func__, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
rbus_init_config(struct rbus_dev *rbus)
|
|
{
|
|
rbus_conf_space[RBUS_VENDOR_ID_OFFSET] = rbus->vend_id & 0xff;
|
|
rbus_conf_space[RBUS_VENDOR_ID_OFFSET + 1] = (rbus->vend_id >> 8) & 0xff;
|
|
rbus_conf_space[RBUS_CHIP_ID_OFFSET] = rbus->chip_id & 0xff;
|
|
rbus_conf_space[RBUS_CHIP_ID_OFFSET + 1] = (rbus->chip_id >> 8) & 0xff;
|
|
rbus_conf_space[RBUS_BAR_OFFSET + 3] = (rbus->base_addr >> 24) & 0xff;
|
|
rbus_conf_space[RBUS_BAR_OFFSET + 2] = (rbus->base_addr >> 16) & 0xff;
|
|
rbus_conf_space[RBUS_BAR_OFFSET + 1] = (rbus->base_addr >> 8) & 0xff;
|
|
}
|
|
|
|
static int
|
|
rbus_read_config(struct pci_bus *bus, unsigned int devfn, int where,
|
|
int size, u32 *value)
|
|
{
|
|
u32 *cr;
|
|
|
|
if ((size_t)where >= sizeof(rbus_conf_space))
|
|
return PCIBIOS_BUFFER_TOO_SMALL;
|
|
|
|
cr = (u32 *)&rbus_conf_space[where];
|
|
|
|
if (devfn == 0)
|
|
*value = *cr;
|
|
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
static int
|
|
rbus_write_config(struct pci_bus *bus, unsigned int devfn, int where,
|
|
int size, u32 value)
|
|
{
|
|
int i;
|
|
struct platform_device *pdev = bus->sysdata;
|
|
|
|
if (devfn != 0)
|
|
goto end;
|
|
|
|
for (i = 0 ; i < size ; i++)
|
|
rbus_conf_space[where + i] = (value << (i * 8)) & 0xff;
|
|
|
|
/* handle vendor specific action */
|
|
switch(where) {
|
|
case RBUS_TSSI_CTRL_OFFSET:
|
|
rbus_tssi_config(pdev, (value & RBUS_TSSI_CTRL_MASK));
|
|
break;
|
|
case RBUS_PA_LNA_CTRL_OFFSET:
|
|
rbus_pa_lan_config(pdev, devfn, (value & RBUS_PA_LNA_CTRL_MASK));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
end:
|
|
return PCIBIOS_SUCCESSFUL;
|
|
}
|
|
|
|
struct pci_ops rbus_ops = {
|
|
.read = rbus_read_config,
|
|
.write = rbus_write_config,
|
|
};
|
|
|
|
static int rbus_map_irq(const struct pci_dev *a, u8 b, u8 c)
|
|
{
|
|
return a->irq;
|
|
}
|
|
|
|
static int rbus_add_port(struct rbus_dev *rbus,
|
|
struct platform_device *pdev)
|
|
{
|
|
struct pci_bus *bus;
|
|
struct pci_dev *pci;
|
|
struct pci_host_bridge *hbrg;
|
|
|
|
bus = pci_scan_root_bus(&pdev->dev, 0, &rbus_ops,
|
|
pdev, &rbus->resources);
|
|
if (!bus)
|
|
return -ENOMEM;
|
|
|
|
pci_bus_add_devices(bus);
|
|
|
|
pci = pci_scan_single_device(bus, 0);
|
|
if (pci) {
|
|
/* re-assign hw resource */
|
|
pci->irq = rbus->irq;
|
|
pci->resource[0].start = rbus->res->start;
|
|
pci->resource[0].end = rbus->res->end;
|
|
|
|
/* Hack: add map_irq for RBUS devices validation as PCI */
|
|
hbrg = pci_find_host_bridge(pci->bus);
|
|
hbrg->map_irq = rbus_map_irq;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rbus_add_res(struct rbus_dev *rbus)
|
|
{
|
|
struct device *dev = rbus->dev;
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct resource bus_range;
|
|
|
|
INIT_LIST_HEAD(&rbus->resources);
|
|
|
|
/* resource allocate */
|
|
rbus->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
rbus->irq = platform_get_irq(pdev, 0);
|
|
rbus->base_addr = (unsigned int)rbus->res->start;
|
|
|
|
if (rbus->chip_id == 0x7981 ||
|
|
rbus->chip_id == 0x7986) {
|
|
multi_intr_2nd = platform_get_irq(pdev, 1);
|
|
multi_intr_3rd = platform_get_irq(pdev, 2);
|
|
multi_intr_4th = platform_get_irq(pdev, 3);
|
|
}
|
|
|
|
pci_add_resource(&rbus->resources, rbus->res);
|
|
|
|
bus_range = (struct resource) {
|
|
.name = "rbus_range",
|
|
.start = 0,
|
|
.end = 0xff,
|
|
.flags = IORESOURCE_BUS,
|
|
};
|
|
|
|
pci_add_resource(&rbus->resources, &bus_range);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rbus_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node = NULL;
|
|
struct rbus_dev *rbus;
|
|
|
|
node = of_find_compatible_node(NULL, NULL, OF_RBUS_NAME);
|
|
if (!node)
|
|
return -ENODEV;
|
|
|
|
rbus = devm_kzalloc(&pdev->dev, sizeof(*rbus), GFP_KERNEL);
|
|
if (!rbus)
|
|
return -ENOMEM;
|
|
|
|
rbus->dev = &pdev->dev;
|
|
|
|
if (of_property_read_u32_index(node, "chip_id", 0, &rbus->chip_id))
|
|
rbus->chip_id = RBUS_DEFAULT_CHIP_ID;
|
|
|
|
if (of_property_read_u32_index(node, "vend_id", 0, &rbus->vend_id))
|
|
rbus->vend_id = RBUS_DEFAULT_VEND_ID;
|
|
|
|
/* set priv_data to pdev */
|
|
snprintf(rbus->name, sizeof(rbus->name), "mediatek-rbus");
|
|
platform_set_drvdata(pdev, rbus);
|
|
rbus_add_res(rbus);
|
|
|
|
/* init config, need run before add port */
|
|
rbus_init_config(rbus);
|
|
|
|
/* add pci bus & device */
|
|
rbus_add_port(rbus, pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rbus_remove(struct platform_device *pdev)
|
|
{
|
|
struct rbus_dev *rbus = platform_get_drvdata(pdev);
|
|
|
|
dev_err(&pdev->dev, "remove rbus name: %s\n", rbus->name);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* global resource preparing
|
|
*/
|
|
static struct platform_driver rbus_driver = {
|
|
.probe = rbus_probe,
|
|
.remove = rbus_remove,
|
|
.driver = {
|
|
.name = rbus_string,
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = rbus_of_ids,
|
|
#endif /*CONFIG_OF*/
|
|
},
|
|
};
|
|
|
|
/* PCIe driver does not allow module unload */
|
|
static int __init rbus_init(void)
|
|
{
|
|
return platform_driver_probe(&rbus_driver, rbus_probe);
|
|
}
|
|
|
|
subsys_initcall_sync(rbus_init);
|
|
|
|
MODULE_DESCRIPTION("Mediatek RBUS host controller driver");
|
|
MODULE_LICENSE("GPL v2");
|