// 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 * */ #include #include #include #include #include #include #include #include #include #include #include #include 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 "); MODULE_DESCRIPTION("LPC Interface for CHIP3"); MODULE_LICENSE("GPL");