326 lines
7.7 KiB
C
326 lines
7.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* lpc_sunway_chip3.c - LPC interface for SUNWAY CHIP3
|
|
*
|
|
* LPC bridge function contains many other functional units,
|
|
* such as Interrupt controllers, Timers, Power Management,
|
|
* System Management, GPIO, RTC, and LPC Configuration
|
|
* Registers.
|
|
*
|
|
* Copyright (c) 2014 JN
|
|
* Author: Weiqiang Su <paper_purple@163.com>
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/mfd/core.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sizes.h>
|
|
#include <linux/mtd/physmap.h>
|
|
|
|
enum features {
|
|
LPC_USE_MSI = (1 << 0),
|
|
LPC_USE_INTX = (1 << 1),
|
|
};
|
|
|
|
enum {
|
|
LPC_HST_BAR = 0,
|
|
LPC_MEM_BAR = 2,
|
|
LPC_FWH_BAR = 4,
|
|
};
|
|
|
|
enum {
|
|
LPC_CTL = 0x0,
|
|
LPC_IRQ = 0x4,
|
|
LPC_IRQ_MASK = 0x8,
|
|
LPC_STAT = 0xc,
|
|
LPC_ERR_INF = 0x10,
|
|
LPC_MEM_HADDR = 0x14,
|
|
LPC_FWH_IDSEL_R1 = 0x18,
|
|
LPC_FWH_IDSEL_R2 = 0x1c,
|
|
LPC_FWH_IDSEL_R3 = 0x20,
|
|
LPC_FWH_IDSEL_R4 = 0x24,
|
|
LPC_FWH_IDSEL_R5 = 0x28,
|
|
LPC_FWH_DEC_EN1 = 0x2c,
|
|
LPC_FWH_DEC_EN2 = 0x30,
|
|
LPC_DMA_CTL = 0x34,
|
|
LPC_CH_STAT = 0x38,
|
|
LPC_CH0_ADDR = 0x3c,
|
|
LPC_CH1_ADDR = 0x40,
|
|
LPC_CH2_ADDR = 0x44,
|
|
LPC_CH3_ADDR = 0x48,
|
|
LPC_CH0_LENG = 0x4c,
|
|
LPC_CH1_LENG = 0x50,
|
|
LPC_CH2_LENG = 0x54,
|
|
LPC_CH3_LENG = 0x58,
|
|
LPC_CH0_MODE = 0x5c,
|
|
LPC_CH1_MODE = 0x60,
|
|
LPC_CH2_MODE = 0x64,
|
|
LPC_CH3_MODE = 0x68,
|
|
LPC_CH_MASK = 0x6c,
|
|
LPC_DMA_SWRST = 0x70,
|
|
};
|
|
|
|
struct lpc_chip3_adapter {
|
|
void __iomem *hst_regs;
|
|
struct device *dev;
|
|
int irq;
|
|
unsigned int features;
|
|
};
|
|
|
|
static struct resource superio_chip3_resources[] = {
|
|
{
|
|
.flags = IORESOURCE_IO,
|
|
}
|
|
};
|
|
|
|
static struct resource mem_flash_resource = {
|
|
.flags = IORESOURCE_MEM,
|
|
};
|
|
|
|
static struct resource fw_flash_resource = {
|
|
.flags = IORESOURCE_MEM,
|
|
};
|
|
|
|
static struct physmap_flash_data mem_flash_data = {
|
|
.width = 1,
|
|
};
|
|
|
|
static struct physmap_flash_data fw_flash_data = {
|
|
.width = 1,
|
|
};
|
|
|
|
static struct mfd_cell lpc_chip3_cells[] = {
|
|
{
|
|
.name = "sunway_superio_ast2400",
|
|
.id = 0,
|
|
.num_resources = ARRAY_SIZE(superio_chip3_resources),
|
|
.resources = superio_chip3_resources,
|
|
},
|
|
{
|
|
.name = "chip3-flash",
|
|
.id = 0,
|
|
.num_resources = 1,
|
|
.resources = &mem_flash_resource,
|
|
.platform_data = &mem_flash_data,
|
|
.pdata_size = sizeof(mem_flash_data),
|
|
},
|
|
{
|
|
.name = "chip3_fwh-flash",
|
|
.id = 0,
|
|
.num_resources = 1,
|
|
.resources = &fw_flash_resource,
|
|
.platform_data = &fw_flash_data,
|
|
.pdata_size = sizeof(fw_flash_data),
|
|
}
|
|
};
|
|
|
|
static inline void lpc_writel(void *address, int reg_base, int value)
|
|
{
|
|
unsigned long addr = (unsigned long)address + reg_base;
|
|
|
|
writel(value, (void *)addr);
|
|
}
|
|
|
|
static inline int lpc_readl(void *address, int reg_base)
|
|
{
|
|
unsigned long addr = (unsigned long)address + reg_base;
|
|
int value = readl((void *)addr);
|
|
|
|
return value;
|
|
}
|
|
|
|
static void lpc_enable(struct lpc_chip3_adapter *lpc_adapter)
|
|
{
|
|
unsigned int value;
|
|
|
|
value = lpc_readl(lpc_adapter->hst_regs, LPC_CTL);
|
|
value |= 0x1600;
|
|
|
|
/* LPC host enable */
|
|
lpc_writel(lpc_adapter->hst_regs, LPC_CTL, value);
|
|
}
|
|
|
|
static void lpc_mem_flash_init(struct platform_device *pdev,
|
|
struct lpc_chip3_adapter *lpc_adapter)
|
|
{
|
|
mem_flash_resource.start =
|
|
(((unsigned long)(lpc_adapter->hst_regs) & (~(0xfUL << 28))) | (0x2UL << 28));
|
|
mem_flash_resource.end = mem_flash_resource.start + SZ_256M - 1;
|
|
|
|
writel(0x1f, lpc_adapter->hst_regs + LPC_MEM_HADDR);
|
|
}
|
|
|
|
static void lpc_fw_flash_init(struct platform_device *pdev,
|
|
struct lpc_chip3_adapter *lpc_adapter)
|
|
{
|
|
fw_flash_resource.start =
|
|
(((unsigned long)(lpc_adapter->hst_regs) & (~(0xfUL << 28))) | (0x3UL << 28));
|
|
fw_flash_resource.end = fw_flash_resource.start + SZ_256M - 1;
|
|
|
|
writel(0xff0f, lpc_adapter->hst_regs + LPC_FWH_DEC_EN1);
|
|
writel(0xffff11ff, lpc_adapter->hst_regs + LPC_FWH_IDSEL_R5);
|
|
writel(0xffffffff, lpc_adapter->hst_regs + LPC_FWH_IDSEL_R4);
|
|
writel(0xffffffff, lpc_adapter->hst_regs + LPC_FWH_IDSEL_R3);
|
|
writel(0xffffffff, lpc_adapter->hst_regs + LPC_FWH_IDSEL_R2);
|
|
writel(0xffffffff, lpc_adapter->hst_regs + LPC_FWH_IDSEL_R1);
|
|
|
|
}
|
|
|
|
static int lpc_chip3_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
struct lpc_chip3_adapter *lpc_adapter;
|
|
struct resource *mem;
|
|
|
|
lpc_adapter = kzalloc(sizeof(*lpc_adapter), GFP_KERNEL);
|
|
if (lpc_adapter == NULL) {
|
|
dev_err(&pdev->dev, "%s kzalloc failed !\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, lpc_adapter);
|
|
/* Get basic io resource and map it */
|
|
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!mem) {
|
|
dev_err(&pdev->dev, "no mem resource?\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
lpc_adapter->hst_regs = devm_ioremap_resource(&pdev->dev, mem);
|
|
if (IS_ERR(lpc_adapter->hst_regs)) {
|
|
dev_err(&pdev->dev, "lpc region map failed\n");
|
|
return PTR_ERR(lpc_adapter->hst_regs);
|
|
}
|
|
|
|
lpc_adapter->dev = &pdev->dev;
|
|
lpc_adapter->features = 0;
|
|
|
|
lpc_enable(lpc_adapter);
|
|
|
|
lpc_mem_flash_init(pdev, lpc_adapter);
|
|
lpc_fw_flash_init(pdev, lpc_adapter);
|
|
|
|
ret = mfd_add_devices(&pdev->dev, 0,
|
|
lpc_chip3_cells, ARRAY_SIZE(lpc_chip3_cells),
|
|
NULL, 0, NULL);
|
|
if (ret)
|
|
goto out_dev;
|
|
|
|
dev_info(lpc_adapter->dev, "probe succeed !\n");
|
|
|
|
return ret;
|
|
|
|
out_dev:
|
|
dev_info(lpc_adapter->dev, "probe failed !\n");
|
|
|
|
mfd_remove_devices(&pdev->dev);
|
|
kfree(lpc_adapter);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int lpc_chip3_remove(struct platform_device *pdev)
|
|
{
|
|
struct lpc_chip3_adapter *lpc_adapter = platform_get_drvdata(pdev);
|
|
|
|
mfd_remove_devices(&pdev->dev);
|
|
iounmap(lpc_adapter->hst_regs);
|
|
kfree(lpc_adapter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id chip3_lpc_of_match[] = {
|
|
{.compatible = "sunway,chip3_lpc",},
|
|
{ /* end of table */ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, chip3_lpc_of_match);
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
unsigned int lpc_irq_ctrl_value;
|
|
unsigned int lpc_irq_irq_value;
|
|
unsigned int lpc_irq_mask_value;
|
|
|
|
/**
|
|
* chip3_lpc_platform_suspend - Suspend an chip3_lpc-platform device
|
|
* @dev: the platform device to suspend
|
|
*
|
|
* This function stores the lpc controller register values and
|
|
* restores them when the machine wakes up.
|
|
*/
|
|
int chip3_lpc_platform_suspend(struct device *dev)
|
|
{
|
|
struct lpc_chip3_adapter *lpc_adapter = dev_get_drvdata(dev);
|
|
|
|
lpc_irq_ctrl_value = lpc_readl(lpc_adapter->hst_regs, LPC_CTL);
|
|
lpc_irq_irq_value = lpc_readl(lpc_adapter->hst_regs, LPC_IRQ);
|
|
lpc_irq_mask_value = lpc_readl(lpc_adapter->hst_regs, LPC_IRQ_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* chip3_lpc_platform_resume - Resume an chip3_lpc-platform device
|
|
* @dev: the platform device to resume
|
|
*
|
|
* This function restores the register value before the suspend.
|
|
*/
|
|
int chip3_lpc_platform_resume(struct device *dev)
|
|
{
|
|
struct lpc_chip3_adapter *lpc_adapter = dev_get_drvdata(dev);
|
|
|
|
lpc_writel(lpc_adapter->hst_regs, LPC_CTL, lpc_irq_ctrl_value);
|
|
lpc_writel(lpc_adapter->hst_regs, LPC_IRQ, lpc_irq_irq_value);
|
|
lpc_writel(lpc_adapter->hst_regs, LPC_IRQ_MASK, lpc_irq_mask_value);
|
|
|
|
return 0;
|
|
}
|
|
static SIMPLE_DEV_PM_OPS(chip3_lpc_pm_ops, chip3_lpc_platform_suspend,
|
|
chip3_lpc_platform_resume);
|
|
#endif
|
|
|
|
|
|
static struct platform_driver chip3_lpc_platform_driver = {
|
|
.driver = {
|
|
.name = "chip3_lpc",
|
|
.of_match_table = chip3_lpc_of_match,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &chip3_lpc_pm_ops,
|
|
#endif
|
|
},
|
|
.remove = lpc_chip3_remove,
|
|
};
|
|
|
|
static int __init chip3_lpc_drvinit(void)
|
|
{
|
|
return platform_driver_probe(&chip3_lpc_platform_driver,
|
|
lpc_chip3_probe);
|
|
}
|
|
|
|
/*
|
|
* lpc controller init configure before serial drivers;
|
|
* The lpc & ast2400 should be initialized much before
|
|
* the serial initialized functions are called.
|
|
*/
|
|
subsys_initcall_sync(chip3_lpc_drvinit);
|
|
|
|
static void __exit chip3_lpc_drvexit(void)
|
|
{
|
|
platform_driver_unregister(&chip3_lpc_platform_driver);
|
|
}
|
|
|
|
module_exit(chip3_lpc_drvexit);
|
|
|
|
MODULE_AUTHOR("Weiqiang Su <paper_purple@163.com>");
|
|
MODULE_DESCRIPTION("LPC Interface for CHIP3");
|
|
MODULE_LICENSE("GPL");
|