diff options
Diffstat (limited to 'drivers/soc')
33 files changed, 3386 insertions, 177 deletions
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index c07b4a85253f..75bdbb2c5140 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -2,10 +2,12 @@ menu "SOC (System On Chip) specific Drivers" source "drivers/soc/actions/Kconfig" source "drivers/soc/amlogic/Kconfig" +source "drivers/soc/aspeed/Kconfig" source "drivers/soc/atmel/Kconfig" source "drivers/soc/bcm/Kconfig" source "drivers/soc/fsl/Kconfig" source "drivers/soc/imx/Kconfig" +source "drivers/soc/ixp4xx/Kconfig" source "drivers/soc/mediatek/Kconfig" source "drivers/soc/qcom/Kconfig" source "drivers/soc/renesas/Kconfig" diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 90b686e586c6..524ecdc2a9bb 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -4,6 +4,7 @@ # obj-$(CONFIG_ARCH_ACTIONS) += actions/ +obj-$(CONFIG_SOC_ASPEED) += aspeed/ obj-$(CONFIG_ARCH_AT91) += atmel/ obj-y += bcm/ obj-$(CONFIG_ARCH_DOVE) += dove/ @@ -11,6 +12,7 @@ obj-$(CONFIG_MACH_DOVE) += dove/ obj-y += fsl/ obj-$(CONFIG_ARCH_GEMINI) += gemini/ obj-$(CONFIG_ARCH_MXC) += imx/ +obj-$(CONFIG_ARCH_IXP4XX) += ixp4xx/ obj-$(CONFIG_SOC_XWAY) += lantiq/ obj-y += mediatek/ obj-y += amlogic/ diff --git a/drivers/soc/amlogic/meson-gx-pwrc-vpu.c b/drivers/soc/amlogic/meson-gx-pwrc-vpu.c index 6289965c42e9..511b6856225d 100644 --- a/drivers/soc/amlogic/meson-gx-pwrc-vpu.c +++ b/drivers/soc/amlogic/meson-gx-pwrc-vpu.c @@ -11,6 +11,7 @@ #include <linux/bitfield.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> +#include <linux/of_device.h> #include <linux/reset.h> #include <linux/clk.h> @@ -26,6 +27,7 @@ #define HHI_MEM_PD_REG0 (0x40 << 2) #define HHI_VPU_MEM_PD_REG0 (0x41 << 2) #define HHI_VPU_MEM_PD_REG1 (0x42 << 2) +#define HHI_VPU_MEM_PD_REG2 (0x4d << 2) struct meson_gx_pwrc_vpu { struct generic_pm_domain genpd; @@ -54,12 +56,55 @@ static int meson_gx_pwrc_vpu_power_off(struct generic_pm_domain *genpd) /* Power Down Memories */ for (i = 0; i < 32; i += 2) { regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, - 0x2 << i, 0x3 << i); + 0x3 << i, 0x3 << i); udelay(5); } for (i = 0; i < 32; i += 2) { regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, - 0x2 << i, 0x3 << i); + 0x3 << i, 0x3 << i); + udelay(5); + } + for (i = 8; i < 16; i++) { + regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, + BIT(i), BIT(i)); + udelay(5); + } + udelay(20); + + regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VPU_HDMI, GEN_PWR_VPU_HDMI); + + msleep(20); + + clk_disable_unprepare(pd->vpu_clk); + clk_disable_unprepare(pd->vapb_clk); + + return 0; +} + +static int meson_g12a_pwrc_vpu_power_off(struct generic_pm_domain *genpd) +{ + struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); + int i; + + regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VPU_HDMI_ISO, GEN_PWR_VPU_HDMI_ISO); + udelay(20); + + /* Power Down Memories */ + for (i = 0; i < 32; i += 2) { + regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, + 0x3 << i, 0x3 << i); + udelay(5); + } + for (i = 0; i < 32; i += 2) { + regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, + 0x3 << i, 0x3 << i); + udelay(5); + } + for (i = 0; i < 32; i += 2) { + regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG2, + 0x3 << i, 0x3 << i); udelay(5); } for (i = 8; i < 16; i++) { @@ -108,13 +153,67 @@ static int meson_gx_pwrc_vpu_power_on(struct generic_pm_domain *genpd) /* Power Up Memories */ for (i = 0; i < 32; i += 2) { regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, - 0x2 << i, 0); + 0x3 << i, 0); + udelay(5); + } + + for (i = 0; i < 32; i += 2) { + regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, + 0x3 << i, 0); + udelay(5); + } + + for (i = 8; i < 16; i++) { + regmap_update_bits(pd->regmap_hhi, HHI_MEM_PD_REG0, + BIT(i), 0); + udelay(5); + } + udelay(20); + + ret = reset_control_assert(pd->rstc); + if (ret) + return ret; + + regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VPU_HDMI_ISO, 0); + + ret = reset_control_deassert(pd->rstc); + if (ret) + return ret; + + ret = meson_gx_pwrc_vpu_setup_clk(pd); + if (ret) + return ret; + + return 0; +} + +static int meson_g12a_pwrc_vpu_power_on(struct generic_pm_domain *genpd) +{ + struct meson_gx_pwrc_vpu *pd = genpd_to_pd(genpd); + int ret; + int i; + + regmap_update_bits(pd->regmap_ao, AO_RTI_GEN_PWR_SLEEP0, + GEN_PWR_VPU_HDMI, 0); + udelay(20); + + /* Power Up Memories */ + for (i = 0; i < 32; i += 2) { + regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG0, + 0x3 << i, 0); udelay(5); } for (i = 0; i < 32; i += 2) { regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG1, - 0x2 << i, 0); + 0x3 << i, 0); + udelay(5); + } + + for (i = 0; i < 32; i += 2) { + regmap_update_bits(pd->regmap_hhi, HHI_VPU_MEM_PD_REG2, + 0x3 << i, 0); udelay(5); } @@ -160,15 +259,37 @@ static struct meson_gx_pwrc_vpu vpu_hdmi_pd = { }, }; +static struct meson_gx_pwrc_vpu vpu_hdmi_pd_g12a = { + .genpd = { + .name = "vpu_hdmi", + .power_off = meson_g12a_pwrc_vpu_power_off, + .power_on = meson_g12a_pwrc_vpu_power_on, + }, +}; + static int meson_gx_pwrc_vpu_probe(struct platform_device *pdev) { + const struct meson_gx_pwrc_vpu *vpu_pd_match; struct regmap *regmap_ao, *regmap_hhi; + struct meson_gx_pwrc_vpu *vpu_pd; struct reset_control *rstc; struct clk *vpu_clk; struct clk *vapb_clk; bool powered_off; int ret; + vpu_pd_match = of_device_get_match_data(&pdev->dev); + if (!vpu_pd_match) { + dev_err(&pdev->dev, "failed to get match data\n"); + return -ENODEV; + } + + vpu_pd = devm_kzalloc(&pdev->dev, sizeof(*vpu_pd), GFP_KERNEL); + if (!vpu_pd) + return -ENOMEM; + + memcpy(vpu_pd, vpu_pd_match, sizeof(*vpu_pd)); + regmap_ao = syscon_node_to_regmap(of_get_parent(pdev->dev.of_node)); if (IS_ERR(regmap_ao)) { dev_err(&pdev->dev, "failed to get regmap\n"); @@ -201,39 +322,46 @@ static int meson_gx_pwrc_vpu_probe(struct platform_device *pdev) return PTR_ERR(vapb_clk); } - vpu_hdmi_pd.regmap_ao = regmap_ao; - vpu_hdmi_pd.regmap_hhi = regmap_hhi; - vpu_hdmi_pd.rstc = rstc; - vpu_hdmi_pd.vpu_clk = vpu_clk; - vpu_hdmi_pd.vapb_clk = vapb_clk; + vpu_pd->regmap_ao = regmap_ao; + vpu_pd->regmap_hhi = regmap_hhi; + vpu_pd->rstc = rstc; + vpu_pd->vpu_clk = vpu_clk; + vpu_pd->vapb_clk = vapb_clk; + + platform_set_drvdata(pdev, vpu_pd); - powered_off = meson_gx_pwrc_vpu_get_power(&vpu_hdmi_pd); + powered_off = meson_gx_pwrc_vpu_get_power(vpu_pd); /* If already powered, sync the clock states */ if (!powered_off) { - ret = meson_gx_pwrc_vpu_setup_clk(&vpu_hdmi_pd); + ret = meson_gx_pwrc_vpu_setup_clk(vpu_pd); if (ret) return ret; } - pm_genpd_init(&vpu_hdmi_pd.genpd, &pm_domain_always_on_gov, + pm_genpd_init(&vpu_pd->genpd, &pm_domain_always_on_gov, powered_off); return of_genpd_add_provider_simple(pdev->dev.of_node, - &vpu_hdmi_pd.genpd); + &vpu_pd->genpd); } static void meson_gx_pwrc_vpu_shutdown(struct platform_device *pdev) { + struct meson_gx_pwrc_vpu *vpu_pd = platform_get_drvdata(pdev); bool powered_off; - powered_off = meson_gx_pwrc_vpu_get_power(&vpu_hdmi_pd); + powered_off = meson_gx_pwrc_vpu_get_power(vpu_pd); if (!powered_off) - meson_gx_pwrc_vpu_power_off(&vpu_hdmi_pd.genpd); + vpu_pd->genpd.power_off(&vpu_pd->genpd); } static const struct of_device_id meson_gx_pwrc_vpu_match_table[] = { - { .compatible = "amlogic,meson-gx-pwrc-vpu" }, + { .compatible = "amlogic,meson-gx-pwrc-vpu", .data = &vpu_hdmi_pd }, + { + .compatible = "amlogic,meson-g12a-pwrc-vpu", + .data = &vpu_hdmi_pd_g12a + }, { /* sentinel */ } }; diff --git a/drivers/soc/amlogic/meson-gx-socinfo.c b/drivers/soc/amlogic/meson-gx-socinfo.c index 37ea0a1c24c8..bca34954518e 100644 --- a/drivers/soc/amlogic/meson-gx-socinfo.c +++ b/drivers/soc/amlogic/meson-gx-socinfo.c @@ -37,26 +37,34 @@ static const struct meson_gx_soc_id { { "AXG", 0x25 }, { "GXLX", 0x26 }, { "TXHD", 0x27 }, + { "G12A", 0x28 }, + { "G12B", 0x29 }, }; static const struct meson_gx_package_id { const char *name; unsigned int major_id; unsigned int pack_id; + unsigned int pack_mask; } soc_packages[] = { - { "S905", 0x1f, 0 }, - { "S905H", 0x1f, 0x13 }, - { "S905M", 0x1f, 0x20 }, - { "S905D", 0x21, 0 }, - { "S905X", 0x21, 0x80 }, - { "S905W", 0x21, 0xa0 }, - { "S905L", 0x21, 0xc0 }, - { "S905M2", 0x21, 0xe0 }, - { "S912", 0x22, 0 }, - { "962X", 0x24, 0x10 }, - { "962E", 0x24, 0x20 }, - { "A113X", 0x25, 0x37 }, - { "A113D", 0x25, 0x22 }, + { "S905", 0x1f, 0, 0x20 }, /* pack_id != 0x20 */ + { "S905H", 0x1f, 0x3, 0xf }, /* pack_id & 0xf == 0x3 */ + { "S905M", 0x1f, 0x20, 0xf0 }, /* pack_id == 0x20 */ + { "S905D", 0x21, 0, 0xf0 }, + { "S905X", 0x21, 0x80, 0xf0 }, + { "S905W", 0x21, 0xa0, 0xf0 }, + { "S905L", 0x21, 0xc0, 0xf0 }, + { "S905M2", 0x21, 0xe0, 0xf0 }, + { "S805X", 0x21, 0x30, 0xf0 }, + { "S805Y", 0x21, 0xb0, 0xf0 }, + { "S912", 0x22, 0, 0x0 }, /* Only S912 is known for GXM */ + { "962X", 0x24, 0x10, 0xf0 }, + { "962E", 0x24, 0x20, 0xf0 }, + { "A113X", 0x25, 0x37, 0xff }, + { "A113D", 0x25, 0x22, 0xff }, + { "S905D2", 0x28, 0x10, 0xf0 }, + { "S905X2", 0x28, 0x40, 0xf0 }, + { "S922X", 0x29, 0x40, 0xf0 }, }; static inline unsigned int socinfo_to_major(u32 socinfo) @@ -81,13 +89,14 @@ static inline unsigned int socinfo_to_misc(u32 socinfo) static const char *socinfo_to_package_id(u32 socinfo) { - unsigned int pack = socinfo_to_pack(socinfo) & 0xf0; + unsigned int pack = socinfo_to_pack(socinfo); unsigned int major = socinfo_to_major(socinfo); int i; for (i = 0 ; i < ARRAY_SIZE(soc_packages) ; ++i) { if (soc_packages[i].major_id == major && - soc_packages[i].pack_id == pack) + soc_packages[i].pack_id == + (pack & soc_packages[i].pack_mask)) return soc_packages[i].name; } @@ -123,8 +132,10 @@ static int __init meson_gx_socinfo_init(void) return -ENODEV; /* check if interface is enabled */ - if (!of_device_is_available(np)) + if (!of_device_is_available(np)) { + of_node_put(np); return -ENODEV; + } /* check if chip-id is available */ if (!of_property_read_bool(np, "amlogic,has-chip-id")) diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig new file mode 100644 index 000000000000..765d10191387 --- /dev/null +++ b/drivers/soc/aspeed/Kconfig @@ -0,0 +1,31 @@ +menu "Aspeed SoC drivers" + +config SOC_ASPEED + def_bool y + depends on ARCH_ASPEED || COMPILE_TEST + +config ASPEED_LPC_CTRL + depends on SOC_ASPEED && REGMAP && MFD_SYSCON + tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control" + ---help--- + Control Aspeed ast2400/2500 HOST LPC to BMC mappings through + ioctl()s, the driver also provides a read/write interface to a BMC ram + region where the host LPC read/write region can be buffered. + +config ASPEED_LPC_SNOOP + tristate "Aspeed ast2500 HOST LPC snoop support" + depends on SOC_ASPEED && REGMAP && MFD_SYSCON + help + Provides a driver to control the LPC snoop interface which + allows the BMC to listen on and save the data written by + the host to an arbitrary LPC I/O port. + +config ASPEED_P2A_CTRL + depends on SOC_ASPEED && REGMAP && MFD_SYSCON + tristate "Aspeed ast2400/2500 HOST P2A VGA MMIO to BMC bridge control" + help + Control Aspeed ast2400/2500 HOST P2A VGA MMIO to BMC mappings through + ioctl()s, the driver also provides an interface for userspace mappings to + a pre-defined region. + +endmenu diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile new file mode 100644 index 000000000000..2f7b6da7be79 --- /dev/null +++ b/drivers/soc/aspeed/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o +obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o +obj-$(CONFIG_ASPEED_P2A_CTRL) += aspeed-p2a-ctrl.o diff --git a/drivers/soc/aspeed/aspeed-lpc-ctrl.c b/drivers/soc/aspeed/aspeed-lpc-ctrl.c new file mode 100644 index 000000000000..a024f8042259 --- /dev/null +++ b/drivers/soc/aspeed/aspeed-lpc-ctrl.c @@ -0,0 +1,300 @@ +/* + * Copyright 2017 IBM Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> + +#include <linux/aspeed-lpc-ctrl.h> + +#define DEVICE_NAME "aspeed-lpc-ctrl" + +#define HICR5 0x0 +#define HICR5_ENL2H BIT(8) +#define HICR5_ENFWH BIT(10) + +#define HICR7 0x8 +#define HICR8 0xc + +struct aspeed_lpc_ctrl { + struct miscdevice miscdev; + struct regmap *regmap; + struct clk *clk; + phys_addr_t mem_base; + resource_size_t mem_size; + u32 pnor_size; + u32 pnor_base; +}; + +static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file) +{ + return container_of(file->private_data, struct aspeed_lpc_ctrl, + miscdev); +} + +static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file); + unsigned long vsize = vma->vm_end - vma->vm_start; + pgprot_t prot = vma->vm_page_prot; + + if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size) + return -EINVAL; + + /* ast2400/2500 AHB accesses are not cache coherent */ + prot = pgprot_noncached(prot); + + if (remap_pfn_range(vma, vma->vm_start, + (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff, + vsize, prot)) + return -EAGAIN; + + return 0; +} + +static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd, + unsigned long param) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file); + void __user *p = (void __user *)param; + struct aspeed_lpc_ctrl_mapping map; + u32 addr; + u32 size; + long rc; + + if (copy_from_user(&map, p, sizeof(map))) + return -EFAULT; + + if (map.flags != 0) + return -EINVAL; + + switch (cmd) { + case ASPEED_LPC_CTRL_IOCTL_GET_SIZE: + /* The flash windows don't report their size */ + if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY) + return -EINVAL; + + /* Support more than one window id in the future */ + if (map.window_id != 0) + return -EINVAL; + + map.size = lpc_ctrl->mem_size; + + return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0; + case ASPEED_LPC_CTRL_IOCTL_MAP: + + /* + * The top half of HICR7 is the MSB of the BMC address of the + * mapping. + * The bottom half of HICR7 is the MSB of the HOST LPC + * firmware space address of the mapping. + * + * The 1 bits in the top of half of HICR8 represent the bits + * (in the requested address) that should be ignored and + * replaced with those from the top half of HICR7. + * The 1 bits in the bottom half of HICR8 represent the bits + * (in the requested address) that should be kept and pass + * into the BMC address space. + */ + + /* + * It doesn't make sense to talk about a size or offset with + * low 16 bits set. Both HICR7 and HICR8 talk about the top 16 + * bits of addresses and sizes. + */ + + if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff)) + return -EINVAL; + + /* + * Because of the way the masks work in HICR8 offset has to + * be a multiple of size. + */ + if (map.offset & (map.size - 1)) + return -EINVAL; + + if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) { + addr = lpc_ctrl->pnor_base; + size = lpc_ctrl->pnor_size; + } else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) { + addr = lpc_ctrl->mem_base; + size = lpc_ctrl->mem_size; + } else { + return -EINVAL; + } + + /* Check overflow first! */ + if (map.offset + map.size < map.offset || + map.offset + map.size > size) + return -EINVAL; + + if (map.size == 0 || map.size > size) + return -EINVAL; + + addr += map.offset; + + /* + * addr (host lpc address) is safe regardless of values. This + * simply changes the address the host has to request on its + * side of the LPC bus. This cannot impact the hosts own + * memory space by surprise as LPC specific accessors are + * required. The only strange thing that could be done is + * setting the lower 16 bits but the shift takes care of that. + */ + + rc = regmap_write(lpc_ctrl->regmap, HICR7, + (addr | (map.addr >> 16))); + if (rc) + return rc; + + rc = regmap_write(lpc_ctrl->regmap, HICR8, + (~(map.size - 1)) | ((map.size >> 16) - 1)); + if (rc) + return rc; + + /* + * Enable LPC FHW cycles. This is required for the host to + * access the regions specified. + */ + return regmap_update_bits(lpc_ctrl->regmap, HICR5, + HICR5_ENFWH | HICR5_ENL2H, + HICR5_ENFWH | HICR5_ENL2H); + } + + return -EINVAL; +} + +static const struct file_operations aspeed_lpc_ctrl_fops = { + .owner = THIS_MODULE, + .mmap = aspeed_lpc_ctrl_mmap, + .unlocked_ioctl = aspeed_lpc_ctrl_ioctl, +}; + +static int aspeed_lpc_ctrl_probe(struct platform_device *pdev) +{ + struct aspeed_lpc_ctrl *lpc_ctrl; + struct device_node *node; + struct resource resm; + struct device *dev; + int rc; + + dev = &pdev->dev; + + lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL); + if (!lpc_ctrl) + return -ENOMEM; + + node = of_parse_phandle(dev->of_node, "flash", 0); + if (!node) { + dev_err(dev, "Didn't find host pnor flash node\n"); + return -ENODEV; + } + + rc = of_address_to_resource(node, 1, &resm); + of_node_put(node); + if (rc) { + dev_err(dev, "Couldn't address to resource for flash\n"); + return rc; + } + + lpc_ctrl->pnor_size = resource_size(&resm); + lpc_ctrl->pnor_base = resm.start; + + dev_set_drvdata(&pdev->dev, lpc_ctrl); + + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) { + dev_err(dev, "Didn't find reserved memory\n"); + return -EINVAL; + } + + rc = of_address_to_resource(node, 0, &resm); + of_node_put(node); + if (rc) { + dev_err(dev, "Couldn't address to resource for reserved memory\n"); + return -ENOMEM; + } + + lpc_ctrl->mem_size = resource_size(&resm); + lpc_ctrl->mem_base = resm.start; + + lpc_ctrl->regmap = syscon_node_to_regmap( + pdev->dev.parent->of_node); + if (IS_ERR(lpc_ctrl->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + lpc_ctrl->clk = devm_clk_get(dev, NULL); + if (IS_ERR(lpc_ctrl->clk)) { + dev_err(dev, "couldn't get clock\n"); + return PTR_ERR(lpc_ctrl->clk); + } + rc = clk_prepare_enable(lpc_ctrl->clk); + if (rc) { + dev_err(dev, "couldn't enable clock\n"); + return rc; + } + + lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_ctrl->miscdev.name = DEVICE_NAME; + lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops; + lpc_ctrl->miscdev.parent = dev; + rc = misc_register(&lpc_ctrl->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + goto err; + } + + dev_info(dev, "Loaded at %pr\n", &resm); + + return 0; + +err: + clk_disable_unprepare(lpc_ctrl->clk); + return rc; +} + +static int aspeed_lpc_ctrl_remove(struct platform_device *pdev) +{ + struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev); + + misc_deregister(&lpc_ctrl->miscdev); + clk_disable_unprepare(lpc_ctrl->clk); + + return 0; +} + +static const struct of_device_id aspeed_lpc_ctrl_match[] = { + { .compatible = "aspeed,ast2400-lpc-ctrl" }, + { .compatible = "aspeed,ast2500-lpc-ctrl" }, + { }, +}; + +static struct platform_driver aspeed_lpc_ctrl_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_lpc_ctrl_match, + }, + .probe = aspeed_lpc_ctrl_probe, + .remove = aspeed_lpc_ctrl_remove, +}; + +module_platform_driver(aspeed_lpc_ctrl_driver); + +MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>"); +MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings"); diff --git a/drivers/soc/aspeed/aspeed-lpc-snoop.c b/drivers/soc/aspeed/aspeed-lpc-snoop.c new file mode 100644 index 000000000000..2feb4347d67f --- /dev/null +++ b/drivers/soc/aspeed/aspeed-lpc-snoop.c @@ -0,0 +1,349 @@ +/* + * Copyright 2017 Google Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Provides a simple driver to control the ASPEED LPC snoop interface which + * allows the BMC to listen on and save the data written by + * the host to an arbitrary LPC I/O port. + * + * Typically used by the BMC to "watch" host boot progress via port + * 0x80 writes made by the BIOS during the boot process. + */ + +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/fs.h> +#include <linux/kfifo.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> + +#define DEVICE_NAME "aspeed-lpc-snoop" + +#define NUM_SNOOP_CHANNELS 2 +#define SNOOP_FIFO_SIZE 2048 + +#define HICR5 0x0 +#define HICR5_EN_SNP0W BIT(0) +#define HICR5_ENINT_SNP0W BIT(1) +#define HICR5_EN_SNP1W BIT(2) +#define HICR5_ENINT_SNP1W BIT(3) + +#define HICR6 0x4 +#define HICR6_STR_SNP0W BIT(0) +#define HICR6_STR_SNP1W BIT(1) +#define SNPWADR 0x10 +#define SNPWADR_CH0_MASK GENMASK(15, 0) +#define SNPWADR_CH0_SHIFT 0 +#define SNPWADR_CH1_MASK GENMASK(31, 16) +#define SNPWADR_CH1_SHIFT 16 +#define SNPWDR 0x14 +#define SNPWDR_CH0_MASK GENMASK(7, 0) +#define SNPWDR_CH0_SHIFT 0 +#define SNPWDR_CH1_MASK GENMASK(15, 8) +#define SNPWDR_CH1_SHIFT 8 +#define HICRB 0x80 +#define HICRB_ENSNP0D BIT(14) +#define HICRB_ENSNP1D BIT(15) + +struct aspeed_lpc_snoop_model_data { + /* The ast2400 has bits 14 and 15 as reserved, whereas the ast2500 + * can use them. + */ + unsigned int has_hicrb_ensnp; +}; + +struct aspeed_lpc_snoop_channel { + struct kfifo fifo; + wait_queue_head_t wq; + struct miscdevice miscdev; +}; + +struct aspeed_lpc_snoop { + struct regmap *regmap; + int irq; + struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS]; +}; + +static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file) +{ + return container_of(file->private_data, + struct aspeed_lpc_snoop_channel, + miscdev); +} + +static ssize_t snoop_file_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); + unsigned int copied; + int ret = 0; + + if (kfifo_is_empty(&chan->fifo)) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + ret = wait_event_interruptible(chan->wq, + !kfifo_is_empty(&chan->fifo)); + if (ret == -ERESTARTSYS) + return -EINTR; + } + ret = kfifo_to_user(&chan->fifo, buffer, count, &copied); + + return ret ? ret : copied; +} + +static unsigned int snoop_file_poll(struct file *file, + struct poll_table_struct *pt) +{ + struct aspeed_lpc_snoop_channel *chan = snoop_file_to_chan(file); + + poll_wait(file, &chan->wq, pt); + return !kfifo_is_empty(&chan->fifo) ? POLLIN : 0; +} + +static const struct file_operations snoop_fops = { + .owner = THIS_MODULE, + .read = snoop_file_read, + .poll = snoop_file_poll, + .llseek = noop_llseek, +}; + +/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ +static void put_fifo_with_discard(struct aspeed_lpc_snoop_channel *chan, u8 val) +{ + if (!kfifo_initialized(&chan->fifo)) + return; + if (kfifo_is_full(&chan->fifo)) + kfifo_skip(&chan->fifo); + kfifo_put(&chan->fifo, val); + wake_up_interruptible(&chan->wq); +} + +static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) +{ + struct aspeed_lpc_snoop *lpc_snoop = arg; + u32 reg, data; + + if (regmap_read(lpc_snoop->regmap, HICR6, ®)) + return IRQ_NONE; + + /* Check if one of the snoop channels is interrupting */ + reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W); + if (!reg) + return IRQ_NONE; + + /* Ack pending IRQs */ + regmap_write(lpc_snoop->regmap, HICR6, reg); + + /* Read and save most recent snoop'ed data byte to FIFO */ + regmap_read(lpc_snoop->regmap, SNPWDR, &data); + + if (reg & HICR6_STR_SNP0W) { + u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; + + put_fifo_with_discard(&lpc_snoop->chan[0], val); + } + if (reg & HICR6_STR_SNP1W) { + u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; + + put_fifo_with_discard(&lpc_snoop->chan[1], val); + } + + return IRQ_HANDLED; +} + +static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc; + + lpc_snoop->irq = platform_get_irq(pdev, 0); + if (!lpc_snoop->irq) + return -ENODEV; + + rc = devm_request_irq(dev, lpc_snoop->irq, + aspeed_lpc_snoop_irq, IRQF_SHARED, + DEVICE_NAME, lpc_snoop); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq); + lpc_snoop->irq = 0; + return rc; + } + + return 0; +} + +static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, + struct device *dev, + int channel, u16 lpc_port) +{ + int rc = 0; + u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en; + const struct aspeed_lpc_snoop_model_data *model_data = + of_device_get_match_data(dev); + + init_waitqueue_head(&lpc_snoop->chan[channel].wq); + /* Create FIFO datastructure */ + rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo, + SNOOP_FIFO_SIZE, GFP_KERNEL); + if (rc) + return rc; + + lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR; + lpc_snoop->chan[channel].miscdev.name = + devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel); + lpc_snoop->chan[channel].miscdev.fops = &snoop_fops; + lpc_snoop->chan[channel].miscdev.parent = dev; + rc = misc_register(&lpc_snoop->chan[channel].miscdev); + if (rc) + return rc; + + /* Enable LPC snoop channel at requested port */ + switch (channel) { + case 0: + hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W; + snpwadr_mask = SNPWADR_CH0_MASK; + snpwadr_shift = SNPWADR_CH0_SHIFT; + hicrb_en = HICRB_ENSNP0D; + break; + case 1: + hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W; + snpwadr_mask = SNPWADR_CH1_MASK; + snpwadr_shift = SNPWADR_CH1_SHIFT; + hicrb_en = HICRB_ENSNP1D; + break; + default: + return -EINVAL; + } + + regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); + regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask, + lpc_port << snpwadr_shift); + if (model_data->has_hicrb_ensnp) + regmap_update_bits(lpc_snoop->regmap, HICRB, + hicrb_en, hicrb_en); + + return rc; +} + +static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, + int channel) +{ + switch (channel) { + case 0: + regmap_update_bits(lpc_snoop->regmap, HICR5, + HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, + 0); + break; + case 1: + regmap_update_bits(lpc_snoop->regmap, HICR5, + HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, + 0); + break; + default: + return; + } + + kfifo_free(&lpc_snoop->chan[channel].fifo); + misc_deregister(&lpc_snoop->chan[channel].miscdev); +} + +static int aspeed_lpc_snoop_probe(struct platform_device *pdev) +{ + struct aspeed_lpc_snoop *lpc_snoop; + struct device *dev; + u32 port; + int rc; + + dev = &pdev->dev; + + lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL); + if (!lpc_snoop) + return -ENOMEM; + + lpc_snoop->regmap = syscon_node_to_regmap( + pdev->dev.parent->of_node); + if (IS_ERR(lpc_snoop->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + dev_set_drvdata(&pdev->dev, lpc_snoop); + + rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); + if (rc) { + dev_err(dev, "no snoop ports configured\n"); + return -ENODEV; + } + + rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); + if (rc) + return rc; + + rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port); + if (rc) + return rc; + + /* Configuration of 2nd snoop channel port is optional */ + if (of_property_read_u32_index(dev->of_node, "snoop-ports", + 1, &port) == 0) { + rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 1, port); + if (rc) + aspeed_lpc_disable_snoop(lpc_snoop, 0); + } + + return rc; +} + +static int aspeed_lpc_snoop_remove(struct platform_device *pdev) +{ + struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); + + /* Disable both snoop channels */ + aspeed_lpc_disable_snoop(lpc_snoop, 0); + aspeed_lpc_disable_snoop(lpc_snoop, 1); + + return 0; +} + +static const struct aspeed_lpc_snoop_model_data ast2400_model_data = { + .has_hicrb_ensnp = 0, +}; + +static const struct aspeed_lpc_snoop_model_data ast2500_model_data = { + .has_hicrb_ensnp = 1, +}; + +static const struct of_device_id aspeed_lpc_snoop_match[] = { + { .compatible = "aspeed,ast2400-lpc-snoop", + .data = &ast2400_model_data }, + { .compatible = "aspeed,ast2500-lpc-snoop", + .data = &ast2500_model_data }, + { }, +}; + +static struct platform_driver aspeed_lpc_snoop_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_lpc_snoop_match, + }, + .probe = aspeed_lpc_snoop_probe, + .remove = aspeed_lpc_snoop_remove, +}; + +module_platform_driver(aspeed_lpc_snoop_driver); + +MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Lippert <rlippert@google.com>"); +MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality"); diff --git a/drivers/soc/aspeed/aspeed-p2a-ctrl.c b/drivers/soc/aspeed/aspeed-p2a-ctrl.c new file mode 100644 index 000000000000..b60fbeaffcbd --- /dev/null +++ b/drivers/soc/aspeed/aspeed-p2a-ctrl.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 Google Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Provides a simple driver to control the ASPEED P2A interface which allows + * the host to read and write to various regions of the BMC's memory. + */ + +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include <linux/aspeed-p2a-ctrl.h> + +#define DEVICE_NAME "aspeed-p2a-ctrl" + +/* SCU2C is a Misc. Control Register. */ +#define SCU2C 0x2c +/* SCU180 is the PCIe Configuration Setting Control Register. */ +#define SCU180 0x180 +/* Bit 1 controls the P2A bridge, while bit 0 controls the entire VGA device + * on the PCI bus. + */ +#define SCU180_ENP2A BIT(1) + +/* The ast2400/2500 both have six ranges. */ +#define P2A_REGION_COUNT 6 + +struct region { + u64 min; + u64 max; + u32 bit; +}; + +struct aspeed_p2a_model_data { + /* min, max, bit */ + struct region regions[P2A_REGION_COUNT]; +}; + +struct aspeed_p2a_ctrl { + struct miscdevice miscdev; + struct regmap *regmap; + + const struct aspeed_p2a_model_data *config; + + /* Access to these needs to be locked, held via probe, mapping ioctl, + * and release, remove. + */ + struct mutex tracking; + u32 readers; + u32 readerwriters[P2A_REGION_COUNT]; + + phys_addr_t mem_base; + resource_size_t mem_size; +}; + +struct aspeed_p2a_user { + struct file *file; + struct aspeed_p2a_ctrl *parent; + + /* The entire memory space is opened for reading once the bridge is + * enabled, therefore this needs only to be tracked once per user. + * If any user has it open for read, the bridge must stay enabled. + */ + u32 read; + + /* Each entry of the array corresponds to a P2A Region. If the user + * opens for read or readwrite, the reference goes up here. On + * release, this array is walked and references adjusted accordingly. + */ + u32 readwrite[P2A_REGION_COUNT]; +}; + +static void aspeed_p2a_enable_bridge(struct aspeed_p2a_ctrl *p2a_ctrl) +{ + regmap_update_bits(p2a_ctrl->regmap, + SCU180, SCU180_ENP2A, SCU180_ENP2A); +} + +static void aspeed_p2a_disable_bridge(struct aspeed_p2a_ctrl *p2a_ctrl) +{ + regmap_update_bits(p2a_ctrl->regmap, SCU180, SCU180_ENP2A, 0); +} + +static int aspeed_p2a_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long vsize; + pgprot_t prot; + struct aspeed_p2a_user *priv = file->private_data; + struct aspeed_p2a_ctrl *ctrl = priv->parent; + + if (ctrl->mem_base == 0 && ctrl->mem_size == 0) + return -EINVAL; + + vsize = vma->vm_end - vma->vm_start; + prot = vma->vm_page_prot; + + if (vma->vm_pgoff + vsize > ctrl->mem_base + ctrl->mem_size) + return -EINVAL; + + /* ast2400/2500 AHB accesses are not cache coherent */ + prot = pgprot_noncached(prot); + + if (remap_pfn_range(vma, vma->vm_start, + (ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff, + vsize, prot)) + return -EAGAIN; + + return 0; +} + +static bool aspeed_p2a_region_acquire(struct aspeed_p2a_user *priv, + struct aspeed_p2a_ctrl *ctrl, + struct aspeed_p2a_ctrl_mapping *map) +{ + int i; + u64 base, end; + bool matched = false; + + base = map->addr; + end = map->addr + (map->length - 1); + + /* If the value is a legal u32, it will find a match. */ + for (i = 0; i < P2A_REGION_COUNT; i++) { + const struct region *curr = &ctrl->config->regions[i]; + + /* If the top of this region is lower than your base, skip it. + */ + if (curr->max < base) + continue; + + /* If the bottom of this region is higher than your end, bail. + */ + if (curr->min > end) + break; + + /* Lock this and update it, therefore it someone else is + * closing their file out, this'll preserve the increment. + */ + mutex_lock(&ctrl->tracking); + ctrl->readerwriters[i] += 1; + mutex_unlock(&ctrl->tracking); + + /* Track with the user, so when they close their file, we can + * decrement properly. + */ + priv->readwrite[i] += 1; + + /* Enable the region as read-write. */ + regmap_update_bits(ctrl->regmap, SCU2C, curr->bit, 0); + matched = true; + } + + return matched; +} + +static long aspeed_p2a_ioctl(struct file *file, unsigned int cmd, + unsigned long data) +{ + struct aspeed_p2a_user *priv = file->private_data; + struct aspeed_p2a_ctrl *ctrl = priv->parent; + void __user *arg = (void __user *)data; + struct aspeed_p2a_ctrl_mapping map; + + if (copy_from_user(&map, arg, sizeof(map))) + return -EFAULT; + + switch (cmd) { + case ASPEED_P2A_CTRL_IOCTL_SET_WINDOW: + /* If they want a region to be read-only, since the entire + * region is read-only once enabled, we just need to track this + * user wants to read from the bridge, and if it's not enabled. + * Enable it. + */ + if (map.flags == ASPEED_P2A_CTRL_READ_ONLY) { + mutex_lock(&ctrl->tracking); + ctrl->readers += 1; + mutex_unlock(&ctrl->tracking); + + /* Track with the user, so when they close their file, + * we can decrement properly. + */ + priv->read += 1; + } else if (map.flags == ASPEED_P2A_CTRL_READWRITE) { + /* If we don't acquire any region return error. */ + if (!aspeed_p2a_region_acquire(priv, ctrl, &map)) { + return -EINVAL; + } + } else { + /* Invalid map flags. */ + return -EINVAL; + } + + aspeed_p2a_enable_bridge(ctrl); + return 0; + case ASPEED_P2A_CTRL_IOCTL_GET_MEMORY_CONFIG: + /* This is a request for the memory-region and corresponding + * length that is used by the driver for mmap. + */ + + map.flags = 0; + map.addr = ctrl->mem_base; + map.length = ctrl->mem_size; + + return copy_to_user(arg, &map, sizeof(map)) ? -EFAULT : 0; + } + + return -EINVAL; +} + + +/* + * When a user opens this file, we create a structure to track their mappings. + * + * A user can map a region as read-only (bridge enabled), or read-write (bit + * flipped, and bridge enabled). Either way, this tracking is used, s.t. when + * they release the device references are handled. + * + * The bridge is not enabled until a user calls an ioctl to map a region, + * simply opening the device does not enable it. + */ +static int aspeed_p2a_open(struct inode *inode, struct file *file) +{ + struct aspeed_p2a_user *priv; + + priv = kmalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->file = file; + priv->read = 0; + memset(priv->readwrite, 0, sizeof(priv->readwrite)); + + /* The file's private_data is initialized to the p2a_ctrl. */ + priv->parent = file->private_data; + + /* Set the file's private_data to the user's data. */ + file->private_data = priv; + + return 0; +} + +/* + * This will close the users mappings. It will go through what they had opened + * for readwrite, and decrement those counts. If at the end, this is the last + * user, it'll close the bridge. + */ +static int aspeed_p2a_release(struct inode *inode, struct file *file) +{ + int i; + u32 bits = 0; + bool open_regions = false; + struct aspeed_p2a_user *priv = file->private_data; + + /* Lock others from changing these values until everything is updated + * in one pass. + */ + mutex_lock(&priv->parent->tracking); + + priv->parent->readers -= priv->read; + + for (i = 0; i < P2A_REGION_COUNT; i++) { + priv->parent->readerwriters[i] -= priv->readwrite[i]; + + if (priv->parent->readerwriters[i] > 0) + open_regions = true; + else + bits |= priv->parent->config->regions[i].bit; + } + + /* Setting a bit to 1 disables the region, so let's just OR with the + * above to disable any. + */ + + /* Note, if another user is trying to ioctl, they can't grab tracking, + * and therefore can't grab either register mutex. + * If another user is trying to close, they can't grab tracking either. + */ + regmap_update_bits(priv->parent->regmap, SCU2C, bits, bits); + + /* If parent->readers is zero and open windows is 0, disable the + * bridge. + */ + if (!open_regions && priv->parent->readers == 0) + aspeed_p2a_disable_bridge(priv->parent); + + mutex_unlock(&priv->parent->tracking); + + kfree(priv); + + return 0; +} + +static const struct file_operations aspeed_p2a_ctrl_fops = { + .owner = THIS_MODULE, + .mmap = aspeed_p2a_mmap, + .unlocked_ioctl = aspeed_p2a_ioctl, + .open = aspeed_p2a_open, + .release = aspeed_p2a_release, +}; + +/* The regions are controlled by SCU2C */ +static void aspeed_p2a_disable_all(struct aspeed_p2a_ctrl *p2a_ctrl) +{ + int i; + u32 value = 0; + + for (i = 0; i < P2A_REGION_COUNT; i++) + value |= p2a_ctrl->config->regions[i].bit; + + regmap_update_bits(p2a_ctrl->regmap, SCU2C, value, value); + + /* Disable the bridge. */ + aspeed_p2a_disable_bridge(p2a_ctrl); +} + +static int aspeed_p2a_ctrl_probe(struct platform_device *pdev) +{ + struct aspeed_p2a_ctrl *misc_ctrl; + struct device *dev; + struct resource resm; + struct device_node *node; + int rc = 0; + + dev = &pdev->dev; + + misc_ctrl = devm_kzalloc(dev, sizeof(*misc_ctrl), GFP_KERNEL); + if (!misc_ctrl) + return -ENOMEM; + + mutex_init(&misc_ctrl->tracking); + + /* optional. */ + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (node) { + rc = of_address_to_resource(node, 0, &resm); + of_node_put(node); + if (rc) { + dev_err(dev, "Couldn't address to resource for reserved memory\n"); + return -ENODEV; + } + + misc_ctrl->mem_size = resource_size(&resm); + misc_ctrl->mem_base = resm.start; + } + + misc_ctrl->regmap = syscon_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(misc_ctrl->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + misc_ctrl->config = of_device_get_match_data(dev); + + dev_set_drvdata(&pdev->dev, misc_ctrl); + + aspeed_p2a_disable_all(misc_ctrl); + + misc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR; + misc_ctrl->miscdev.name = DEVICE_NAME; + misc_ctrl->miscdev.fops = &aspeed_p2a_ctrl_fops; + misc_ctrl->miscdev.parent = dev; + + rc = misc_register(&misc_ctrl->miscdev); + if (rc) + dev_err(dev, "Unable to register device\n"); + + return rc; +} + +static int aspeed_p2a_ctrl_remove(struct platform_device *pdev) +{ + struct aspeed_p2a_ctrl *p2a_ctrl = dev_get_drvdata(&pdev->dev); + + misc_deregister(&p2a_ctrl->miscdev); + + return 0; +} + +#define SCU2C_DRAM BIT(25) +#define SCU2C_SPI BIT(24) +#define SCU2C_SOC BIT(23) +#define SCU2C_FLASH BIT(22) + +static const struct aspeed_p2a_model_data ast2400_model_data = { + .regions = { + {0x00000000, 0x17FFFFFF, SCU2C_FLASH}, + {0x18000000, 0x1FFFFFFF, SCU2C_SOC}, + {0x20000000, 0x2FFFFFFF, SCU2C_FLASH}, + {0x30000000, 0x3FFFFFFF, SCU2C_SPI}, + {0x40000000, 0x5FFFFFFF, SCU2C_DRAM}, + {0x60000000, 0xFFFFFFFF, SCU2C_SOC}, + } +}; + +static const struct aspeed_p2a_model_data ast2500_model_data = { + .regions = { + {0x00000000, 0x0FFFFFFF, SCU2C_FLASH}, + {0x10000000, 0x1FFFFFFF, SCU2C_SOC}, + {0x20000000, 0x3FFFFFFF, SCU2C_FLASH}, + {0x40000000, 0x5FFFFFFF, SCU2C_SOC}, + {0x60000000, 0x7FFFFFFF, SCU2C_SPI}, + {0x80000000, 0xFFFFFFFF, SCU2C_DRAM}, + } +}; + +static const struct of_device_id aspeed_p2a_ctrl_match[] = { + { .compatible = "aspeed,ast2400-p2a-ctrl", + .data = &ast2400_model_data }, + { .compatible = "aspeed,ast2500-p2a-ctrl", + .data = &ast2500_model_data }, + { }, +}; + +static struct platform_driver aspeed_p2a_ctrl_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_p2a_ctrl_match, + }, + .probe = aspeed_p2a_ctrl_probe, + .remove = aspeed_p2a_ctrl_remove, +}; + +module_platform_driver(aspeed_p2a_ctrl_driver); + +MODULE_DEVICE_TABLE(of, aspeed_p2a_ctrl_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Patrick Venture <venture@google.com>"); +MODULE_DESCRIPTION("Control for aspeed 2400/2500 P2A VGA HOST to BMC mappings"); diff --git a/drivers/soc/fsl/qe/gpio.c b/drivers/soc/fsl/qe/gpio.c index 819bed0f5667..51b3a47b5a55 100644 --- a/drivers/soc/fsl/qe/gpio.c +++ b/drivers/soc/fsl/qe/gpio.c @@ -179,8 +179,10 @@ struct qe_pin *qe_pin_request(struct device_node *np, int index) if (err < 0) goto err0; gc = gpio_to_chip(err); - if (WARN_ON(!gc)) + if (WARN_ON(!gc)) { + err = -ENODEV; goto err0; + } if (!of_device_is_compatible(gc->of_node, "fsl,mpc8323-qe-pario-bank")) { pr_debug("%s: tried to get a non-qe pin\n", __func__); diff --git a/drivers/soc/imx/Makefile b/drivers/soc/imx/Makefile index 506a6f3c2b9b..d6b529e06d9a 100644 --- a/drivers/soc/imx/Makefile +++ b/drivers/soc/imx/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_HAVE_IMX_GPC) += gpc.o obj-$(CONFIG_IMX_GPCV2_PM_DOMAINS) += gpcv2.o +obj-$(CONFIG_ARCH_MXC) += soc-imx8.o diff --git a/drivers/soc/imx/gpc.c b/drivers/soc/imx/gpc.c index 7d14a4b4e82a..d9231bd3c691 100644 --- a/drivers/soc/imx/gpc.c +++ b/drivers/soc/imx/gpc.c @@ -406,7 +406,6 @@ static int imx_gpc_probe(struct platform_device *pdev) const struct imx_gpc_dt_data *of_id_data = of_id->data; struct device_node *pgc_node; struct regmap *regmap; - struct resource *res; void __iomem *base; int ret; @@ -417,8 +416,7 @@ static int imx_gpc_probe(struct platform_device *pdev) !pgc_node) return 0; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); @@ -431,10 +429,19 @@ static int imx_gpc_probe(struct platform_device *pdev) return ret; } - /* Disable PU power down in normal operation if ERR009619 is present */ + /* + * Disable PU power down by runtime PM if ERR009619 is present. + * + * The PRE clock will be paused for several cycles when turning on the + * PU domain LDO from power down state. If PRE is in use at that time, + * the IPU/PRG cannot get the correct display data from the PRE. + * + * This is not a concern when the whole system enters suspend state, so + * it's safe to power down PU in this case. + */ if (of_id_data->err009619_present) imx_gpc_domains[GPC_PGC_DOMAIN_PU].base.flags |= - GENPD_FLAG_ALWAYS_ON; + GENPD_FLAG_RPM_ALWAYS_ON; /* Keep DISP always on if ERR006287 is present */ if (of_id_data->err006287_present) diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c index 176f473127b6..31b8d002d855 100644 --- a/drivers/soc/imx/gpcv2.c +++ b/drivers/soc/imx/gpcv2.c @@ -136,8 +136,8 @@ static int imx_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd, GPC_PU_PGC_SW_PUP_REQ : GPC_PU_PGC_SW_PDN_REQ; const bool enable_power_control = !on; const bool has_regulator = !IS_ERR(domain->regulator); - unsigned long deadline; int i, ret = 0; + u32 pxx_req; regmap_update_bits(domain->regmap, GPC_PGC_CPU_MAPPING, domain->bits.map, domain->bits.map); @@ -169,30 +169,19 @@ static int imx_gpc_pu_pgc_sw_pxx_req(struct generic_pm_domain *genpd, * As per "5.5.9.4 Example Code 4" in IMX7DRM.pdf wait * for PUP_REQ/PDN_REQ bit to be cleared */ - deadline = jiffies + msecs_to_jiffies(1); - while (true) { - u32 pxx_req; - - regmap_read(domain->regmap, offset, &pxx_req); - - if (!(pxx_req & domain->bits.pxx)) - break; - - if (time_after(jiffies, deadline)) { - dev_err(domain->dev, "falied to command PGC\n"); - ret = -ETIMEDOUT; - /* - * If we were in a process of enabling a - * domain and failed we might as well disable - * the regulator we just enabled. And if it - * was the opposite situation and we failed to - * power down -- keep the regulator on - */ - on = !on; - break; - } - - cpu_relax(); + ret = regmap_read_poll_timeout(domain->regmap, offset, pxx_req, + !(pxx_req & domain->bits.pxx), + 0, USEC_PER_MSEC); + if (ret) { + dev_err(domain->dev, "failed to command PGC\n"); + /* + * If we were in a process of enabling a + * domain and failed we might as well disable + * the regulator we just enabled. And if it + * was the opposite situation and we failed to + * power down -- keep the regulator on + */ + on = !on; } if (enable_power_control) @@ -574,7 +563,6 @@ static int imx_gpcv2_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *pgc_np, *np; struct regmap *regmap; - struct resource *res; void __iomem *base; int ret; @@ -584,8 +572,7 @@ static int imx_gpcv2_probe(struct platform_device *pdev) return -EINVAL; } - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(dev, res); + base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base); diff --git a/drivers/soc/imx/soc-imx8.c b/drivers/soc/imx/soc-imx8.c new file mode 100644 index 000000000000..fc6429f9170a --- /dev/null +++ b/drivers/soc/imx/soc-imx8.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP. + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +#define REV_B1 0x21 + +#define IMX8MQ_SW_INFO_B1 0x40 +#define IMX8MQ_SW_MAGIC_B1 0xff0055aa + +struct imx8_soc_data { + char *name; + u32 (*soc_revision)(void); +}; + +static u32 __init imx8mq_soc_revision(void) +{ + struct device_node *np; + void __iomem *ocotp_base; + u32 magic; + u32 rev = 0; + + np = of_find_compatible_node(NULL, NULL, "fsl,imx8mq-ocotp"); + if (!np) + goto out; + + ocotp_base = of_iomap(np, 0); + WARN_ON(!ocotp_base); + + magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1); + if (magic == IMX8MQ_SW_MAGIC_B1) + rev = REV_B1; + + iounmap(ocotp_base); + +out: + of_node_put(np); + return rev; +} + +static const struct imx8_soc_data imx8mq_soc_data = { + .name = "i.MX8MQ", + .soc_revision = imx8mq_soc_revision, +}; + +static const struct of_device_id imx8_soc_match[] = { + { .compatible = "fsl,imx8mq", .data = &imx8mq_soc_data, }, + { } +}; + +#define imx8_revision(soc_rev) \ + soc_rev ? \ + kasprintf(GFP_KERNEL, "%d.%d", (soc_rev >> 4) & 0xf, soc_rev & 0xf) : \ + "unknown" + +static int __init imx8_soc_init(void) +{ + struct soc_device_attribute *soc_dev_attr; + struct soc_device *soc_dev; + struct device_node *root; + const struct of_device_id *id; + u32 soc_rev = 0; + const struct imx8_soc_data *data; + int ret; + + soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); + if (!soc_dev_attr) + return -ENODEV; + + soc_dev_attr->family = "Freescale i.MX"; + + root = of_find_node_by_path("/"); + ret = of_property_read_string(root, "model", &soc_dev_attr->machine); + if (ret) + goto free_soc; + + id = of_match_node(imx8_soc_match, root); + if (!id) + goto free_soc; + + of_node_put(root); + + data = id->data; + if (data) { + soc_dev_attr->soc_id = data->name; + if (data->soc_revision) + soc_rev = data->soc_revision(); + } + + soc_dev_attr->revision = imx8_revision(soc_rev); + if (!soc_dev_attr->revision) + goto free_soc; + + soc_dev = soc_device_register(soc_dev_attr); + if (IS_ERR(soc_dev)) + goto free_rev; + + return 0; + +free_rev: + kfree(soc_dev_attr->revision); +free_soc: + kfree(soc_dev_attr); + of_node_put(root); + return -ENODEV; +} +device_initcall(imx8_soc_init); diff --git a/drivers/soc/ixp4xx/Kconfig b/drivers/soc/ixp4xx/Kconfig new file mode 100644 index 000000000000..de6becdc78a2 --- /dev/null +++ b/drivers/soc/ixp4xx/Kconfig @@ -0,0 +1,16 @@ +menu "IXP4xx SoC drivers" + +config IXP4XX_QMGR + tristate "IXP4xx Queue Manager support" + help + This driver supports IXP4xx built-in hardware queue manager + and is automatically selected by Ethernet and HSS drivers. + +config IXP4XX_NPE + tristate "IXP4xx Network Processor Engine support" + select FW_LOADER + help + This driver supports IXP4xx built-in network coprocessors + and is automatically selected by Ethernet and HSS drivers. + +endmenu diff --git a/drivers/soc/ixp4xx/Makefile b/drivers/soc/ixp4xx/Makefile new file mode 100644 index 000000000000..d20d99e6df65 --- /dev/null +++ b/drivers/soc/ixp4xx/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_IXP4XX_QMGR) += ixp4xx-qmgr.o +obj-$(CONFIG_IXP4XX_NPE) += ixp4xx-npe.o diff --git a/drivers/soc/ixp4xx/ixp4xx-npe.c b/drivers/soc/ixp4xx/ixp4xx-npe.c new file mode 100644 index 000000000000..15979d4376ab --- /dev/null +++ b/drivers/soc/ixp4xx/ixp4xx-npe.c @@ -0,0 +1,762 @@ +/* + * Intel IXP4xx Network Processor Engine driver for Linux + * + * Copyright (C) 2007 Krzysztof Halasa <khc@pm.waw.pl> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * The code is based on publicly available information: + * - Intel IXP4xx Developer's Manual and other e-papers + * - Intel IXP400 Access Library Software (BSD license) + * - previous works by Christian Hohnstaedt <chohnstaedt@innominate.com> + * Thanks, Christian. + */ + +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/soc/ixp4xx/npe.h> + +#define DEBUG_MSG 0 +#define DEBUG_FW 0 + +#define NPE_COUNT 3 +#define MAX_RETRIES 1000 /* microseconds */ +#define NPE_42X_DATA_SIZE 0x800 /* in dwords */ +#define NPE_46X_DATA_SIZE 0x1000 +#define NPE_A_42X_INSTR_SIZE 0x1000 +#define NPE_B_AND_C_42X_INSTR_SIZE 0x800 +#define NPE_46X_INSTR_SIZE 0x1000 +#define REGS_SIZE 0x1000 + +#define NPE_PHYS_REG 32 + +#define FW_MAGIC 0xFEEDF00D +#define FW_BLOCK_TYPE_INSTR 0x0 +#define FW_BLOCK_TYPE_DATA 0x1 +#define FW_BLOCK_TYPE_EOF 0xF + +/* NPE exec status (read) and command (write) */ +#define CMD_NPE_STEP 0x01 +#define CMD_NPE_START 0x02 +#define CMD_NPE_STOP 0x03 +#define CMD_NPE_CLR_PIPE 0x04 +#define CMD_CLR_PROFILE_CNT 0x0C +#define CMD_RD_INS_MEM 0x10 /* instruction memory */ +#define CMD_WR_INS_MEM 0x11 +#define CMD_RD_DATA_MEM 0x12 /* data memory */ +#define CMD_WR_DATA_MEM 0x13 +#define CMD_RD_ECS_REG 0x14 /* exec access register */ +#define CMD_WR_ECS_REG 0x15 + +#define STAT_RUN 0x80000000 +#define STAT_STOP 0x40000000 +#define STAT_CLEAR 0x20000000 +#define STAT_ECS_K 0x00800000 /* pipeline clean */ + +#define NPE_STEVT 0x1B +#define NPE_STARTPC 0x1C +#define NPE_REGMAP 0x1E +#define NPE_CINDEX 0x1F + +#define INSTR_WR_REG_SHORT 0x0000C000 +#define INSTR_WR_REG_BYTE 0x00004000 +#define INSTR_RD_FIFO 0x0F888220 +#define INSTR_RESET_MBOX 0x0FAC8210 + +#define ECS_BG_CTXT_REG_0 0x00 /* Background Executing Context */ +#define ECS_BG_CTXT_REG_1 0x01 /* Stack level */ +#define ECS_BG_CTXT_REG_2 0x02 +#define ECS_PRI_1_CTXT_REG_0 0x04 /* Priority 1 Executing Context */ +#define ECS_PRI_1_CTXT_REG_1 0x05 /* Stack level */ +#define ECS_PRI_1_CTXT_REG_2 0x06 +#define ECS_PRI_2_CTXT_REG_0 0x08 /* Priority 2 Executing Context */ +#define ECS_PRI_2_CTXT_REG_1 0x09 /* Stack level */ +#define ECS_PRI_2_CTXT_REG_2 0x0A +#define ECS_DBG_CTXT_REG_0 0x0C /* Debug Executing Context */ +#define ECS_DBG_CTXT_REG_1 0x0D /* Stack level */ +#define ECS_DBG_CTXT_REG_2 0x0E +#define ECS_INSTRUCT_REG 0x11 /* NPE Instruction Register */ + +#define ECS_REG_0_ACTIVE 0x80000000 /* all levels */ +#define ECS_REG_0_NEXTPC_MASK 0x1FFF0000 /* BG/PRI1/PRI2 levels */ +#define ECS_REG_0_LDUR_BITS 8 +#define ECS_REG_0_LDUR_MASK 0x00000700 /* all levels */ +#define ECS_REG_1_CCTXT_BITS 16 +#define ECS_REG_1_CCTXT_MASK 0x000F0000 /* all levels */ +#define ECS_REG_1_SELCTXT_BITS 0 +#define ECS_REG_1_SELCTXT_MASK 0x0000000F /* all levels */ +#define ECS_DBG_REG_2_IF 0x00100000 /* debug level */ +#define ECS_DBG_REG_2_IE 0x00080000 /* debug level */ + +/* NPE watchpoint_fifo register bit */ +#define WFIFO_VALID 0x80000000 + +/* NPE messaging_status register bit definitions */ +#define MSGSTAT_OFNE 0x00010000 /* OutFifoNotEmpty */ +#define MSGSTAT_IFNF 0x00020000 /* InFifoNotFull */ +#define MSGSTAT_OFNF 0x00040000 /* OutFifoNotFull */ +#define MSGSTAT_IFNE 0x00080000 /* InFifoNotEmpty */ +#define MSGSTAT_MBINT 0x00100000 /* Mailbox interrupt */ +#define MSGSTAT_IFINT 0x00200000 /* InFifo interrupt */ +#define MSGSTAT_OFINT 0x00400000 /* OutFifo interrupt */ +#define MSGSTAT_WFINT 0x00800000 /* WatchFifo interrupt */ + +/* NPE messaging_control register bit definitions */ +#define MSGCTL_OUT_FIFO 0x00010000 /* enable output FIFO */ +#define MSGCTL_IN_FIFO 0x00020000 /* enable input FIFO */ +#define MSGCTL_OUT_FIFO_WRITE 0x01000000 /* enable FIFO + WRITE */ +#define MSGCTL_IN_FIFO_WRITE 0x02000000 + +/* NPE mailbox_status value for reset */ +#define RESET_MBOX_STAT 0x0000F0F0 + +#define NPE_A_FIRMWARE "NPE-A" +#define NPE_B_FIRMWARE "NPE-B" +#define NPE_C_FIRMWARE "NPE-C" + +const char *npe_names[] = { NPE_A_FIRMWARE, NPE_B_FIRMWARE, NPE_C_FIRMWARE }; + +#define print_npe(pri, npe, fmt, ...) \ + printk(pri "%s: " fmt, npe_name(npe), ## __VA_ARGS__) + +#if DEBUG_MSG +#define debug_msg(npe, fmt, ...) \ + print_npe(KERN_DEBUG, npe, fmt, ## __VA_ARGS__) +#else +#define debug_msg(npe, fmt, ...) +#endif + +static struct { + u32 reg, val; +} ecs_reset[] = { + { ECS_BG_CTXT_REG_0, 0xA0000000 }, + { ECS_BG_CTXT_REG_1, 0x01000000 }, + { ECS_BG_CTXT_REG_2, 0x00008000 }, + { ECS_PRI_1_CTXT_REG_0, 0x20000080 }, + { ECS_PRI_1_CTXT_REG_1, 0x01000000 }, + { ECS_PRI_1_CTXT_REG_2, 0x00008000 }, + { ECS_PRI_2_CTXT_REG_0, 0x20000080 }, + { ECS_PRI_2_CTXT_REG_1, 0x01000000 }, + { ECS_PRI_2_CTXT_REG_2, 0x00008000 }, + { ECS_DBG_CTXT_REG_0, 0x20000000 }, + { ECS_DBG_CTXT_REG_1, 0x00000000 }, + { ECS_DBG_CTXT_REG_2, 0x001E0000 }, + { ECS_INSTRUCT_REG, 0x1003C00F }, +}; + +static struct npe npe_tab[NPE_COUNT] = { + { + .id = 0, + }, { + .id = 1, + }, { + .id = 2, + } +}; + +int npe_running(struct npe *npe) +{ + return (__raw_readl(&npe->regs->exec_status_cmd) & STAT_RUN) != 0; +} + +static void npe_cmd_write(struct npe *npe, u32 addr, int cmd, u32 data) +{ + __raw_writel(data, &npe->regs->exec_data); + __raw_writel(addr, &npe->regs->exec_addr); + __raw_writel(cmd, &npe->regs->exec_status_cmd); +} + +static u32 npe_cmd_read(struct npe *npe, u32 addr, int cmd) +{ + __raw_writel(addr, &npe->regs->exec_addr); + __raw_writel(cmd, &npe->regs->exec_status_cmd); + /* Iintroduce extra read cycles after issuing read command to NPE + so that we read the register after the NPE has updated it. + This is to overcome race condition between XScale and NPE */ + __raw_readl(&npe->regs->exec_data); + __raw_readl(&npe->regs->exec_data); + return __raw_readl(&npe->regs->exec_data); +} + +static void npe_clear_active(struct npe *npe, u32 reg) +{ + u32 val = npe_cmd_read(npe, reg, CMD_RD_ECS_REG); + npe_cmd_write(npe, reg, CMD_WR_ECS_REG, val & ~ECS_REG_0_ACTIVE); +} + +static void npe_start(struct npe *npe) +{ + /* ensure only Background Context Stack Level is active */ + npe_clear_active(npe, ECS_PRI_1_CTXT_REG_0); + npe_clear_active(npe, ECS_PRI_2_CTXT_REG_0); + npe_clear_active(npe, ECS_DBG_CTXT_REG_0); + + __raw_writel(CMD_NPE_CLR_PIPE, &npe->regs->exec_status_cmd); + __raw_writel(CMD_NPE_START, &npe->regs->exec_status_cmd); +} + +static void npe_stop(struct npe *npe) +{ + __raw_writel(CMD_NPE_STOP, &npe->regs->exec_status_cmd); + __raw_writel(CMD_NPE_CLR_PIPE, &npe->regs->exec_status_cmd); /*FIXME?*/ +} + +static int __must_check npe_debug_instr(struct npe *npe, u32 instr, u32 ctx, + u32 ldur) +{ + u32 wc; + int i; + + /* set the Active bit, and the LDUR, in the debug level */ + npe_cmd_write(npe, ECS_DBG_CTXT_REG_0, CMD_WR_ECS_REG, + ECS_REG_0_ACTIVE | (ldur << ECS_REG_0_LDUR_BITS)); + + /* set CCTXT at ECS DEBUG L3 to specify in which context to execute + the instruction, and set SELCTXT at ECS DEBUG Level to specify + which context store to access. + Debug ECS Level Reg 1 has form 0x000n000n, where n = context number + */ + npe_cmd_write(npe, ECS_DBG_CTXT_REG_1, CMD_WR_ECS_REG, + (ctx << ECS_REG_1_CCTXT_BITS) | + (ctx << ECS_REG_1_SELCTXT_BITS)); + + /* clear the pipeline */ + __raw_writel(CMD_NPE_CLR_PIPE, &npe->regs->exec_status_cmd); + + /* load NPE instruction into the instruction register */ + npe_cmd_write(npe, ECS_INSTRUCT_REG, CMD_WR_ECS_REG, instr); + + /* we need this value later to wait for completion of NPE execution + step */ + wc = __raw_readl(&npe->regs->watch_count); + + /* issue a Step One command via the Execution Control register */ + __raw_writel(CMD_NPE_STEP, &npe->regs->exec_status_cmd); + + /* Watch Count register increments when NPE completes an instruction */ + for (i = 0; i < MAX_RETRIES; i++) { + if (wc != __raw_readl(&npe->regs->watch_count)) + return 0; + udelay(1); + } + + print_npe(KERN_ERR, npe, "reset: npe_debug_instr(): timeout\n"); + return -ETIMEDOUT; +} + +static int __must_check npe_logical_reg_write8(struct npe *npe, u32 addr, + u8 val, u32 ctx) +{ + /* here we build the NPE assembler instruction: mov8 d0, #0 */ + u32 instr = INSTR_WR_REG_BYTE | /* OpCode */ + addr << 9 | /* base Operand */ + (val & 0x1F) << 4 | /* lower 5 bits to immediate data */ + (val & ~0x1F) << (18 - 5);/* higher 3 bits to CoProc instr. */ + return npe_debug_instr(npe, instr, ctx, 1); /* execute it */ +} + +static int __must_check npe_logical_reg_write16(struct npe *npe, u32 addr, + u16 val, u32 ctx) +{ + /* here we build the NPE assembler instruction: mov16 d0, #0 */ + u32 instr = INSTR_WR_REG_SHORT | /* OpCode */ + addr << 9 | /* base Operand */ + (val & 0x1F) << 4 | /* lower 5 bits to immediate data */ + (val & ~0x1F) << (18 - 5);/* higher 11 bits to CoProc instr. */ + return npe_debug_instr(npe, instr, ctx, 1); /* execute it */ +} + +static int __must_check npe_logical_reg_write32(struct npe *npe, u32 addr, + u32 val, u32 ctx) +{ + /* write in 16 bit steps first the high and then the low value */ + if (npe_logical_reg_write16(npe, addr, val >> 16, ctx)) + return -ETIMEDOUT; + return npe_logical_reg_write16(npe, addr + 2, val & 0xFFFF, ctx); +} + +static int npe_reset(struct npe *npe) +{ + u32 val, ctl, exec_count, ctx_reg2; + int i; + + ctl = (__raw_readl(&npe->regs->messaging_control) | 0x3F000000) & + 0x3F3FFFFF; + + /* disable parity interrupt */ + __raw_writel(ctl & 0x3F00FFFF, &npe->regs->messaging_control); + + /* pre exec - debug instruction */ + /* turn off the halt bit by clearing Execution Count register. */ + exec_count = __raw_readl(&npe->regs->exec_count); + __raw_writel(0, &npe->regs->exec_count); + /* ensure that IF and IE are on (temporarily), so that we don't end up + stepping forever */ + ctx_reg2 = npe_cmd_read(npe, ECS_DBG_CTXT_REG_2, CMD_RD_ECS_REG); + npe_cmd_write(npe, ECS_DBG_CTXT_REG_2, CMD_WR_ECS_REG, ctx_reg2 | + ECS_DBG_REG_2_IF | ECS_DBG_REG_2_IE); + + /* clear the FIFOs */ + while (__raw_readl(&npe->regs->watchpoint_fifo) & WFIFO_VALID) + ; + while (__raw_readl(&npe->regs->messaging_status) & MSGSTAT_OFNE) + /* read from the outFIFO until empty */ + print_npe(KERN_DEBUG, npe, "npe_reset: read FIFO = 0x%X\n", + __raw_readl(&npe->regs->in_out_fifo)); + + while (__raw_readl(&npe->regs->messaging_status) & MSGSTAT_IFNE) + /* step execution of the NPE intruction to read inFIFO using + the Debug Executing Context stack */ + if (npe_debug_instr(npe, INSTR_RD_FIFO, 0, 0)) + return -ETIMEDOUT; + + /* reset the mailbox reg from the XScale side */ + __raw_writel(RESET_MBOX_STAT, &npe->regs->mailbox_status); + /* from NPE side */ + if (npe_debug_instr(npe, INSTR_RESET_MBOX, 0, 0)) + return -ETIMEDOUT; + + /* Reset the physical registers in the NPE register file */ + for (val = 0; val < NPE_PHYS_REG; val++) { + if (npe_logical_reg_write16(npe, NPE_REGMAP, val >> 1, 0)) + return -ETIMEDOUT; + /* address is either 0 or 4 */ + if (npe_logical_reg_write32(npe, (val & 1) * 4, 0, 0)) + return -ETIMEDOUT; + } + + /* Reset the context store = each context's Context Store registers */ + + /* Context 0 has no STARTPC. Instead, this value is used to set NextPC + for Background ECS, to set where NPE starts executing code */ + val = npe_cmd_read(npe, ECS_BG_CTXT_REG_0, CMD_RD_ECS_REG); + val &= ~ECS_REG_0_NEXTPC_MASK; + val |= (0 /* NextPC */ << 16) & ECS_REG_0_NEXTPC_MASK; + npe_cmd_write(npe, ECS_BG_CTXT_REG_0, CMD_WR_ECS_REG, val); + + for (i = 0; i < 16; i++) { + if (i) { /* Context 0 has no STEVT nor STARTPC */ + /* STEVT = off, 0x80 */ + if (npe_logical_reg_write8(npe, NPE_STEVT, 0x80, i)) + return -ETIMEDOUT; + if (npe_logical_reg_write16(npe, NPE_STARTPC, 0, i)) + return -ETIMEDOUT; + } + /* REGMAP = d0->p0, d8->p2, d16->p4 */ + if (npe_logical_reg_write16(npe, NPE_REGMAP, 0x820, i)) + return -ETIMEDOUT; + if (npe_logical_reg_write8(npe, NPE_CINDEX, 0, i)) + return -ETIMEDOUT; + } + + /* post exec */ + /* clear active bit in debug level */ + npe_cmd_write(npe, ECS_DBG_CTXT_REG_0, CMD_WR_ECS_REG, 0); + /* clear the pipeline */ + __raw_writel(CMD_NPE_CLR_PIPE, &npe->regs->exec_status_cmd); + /* restore previous values */ + __raw_writel(exec_count, &npe->regs->exec_count); + npe_cmd_write(npe, ECS_DBG_CTXT_REG_2, CMD_WR_ECS_REG, ctx_reg2); + + /* write reset values to Execution Context Stack registers */ + for (val = 0; val < ARRAY_SIZE(ecs_reset); val++) + npe_cmd_write(npe, ecs_reset[val].reg, CMD_WR_ECS_REG, + ecs_reset[val].val); + + /* clear the profile counter */ + __raw_writel(CMD_CLR_PROFILE_CNT, &npe->regs->exec_status_cmd); + + __raw_writel(0, &npe->regs->exec_count); + __raw_writel(0, &npe->regs->action_points[0]); + __raw_writel(0, &npe->regs->action_points[1]); + __raw_writel(0, &npe->regs->action_points[2]); + __raw_writel(0, &npe->regs->action_points[3]); + __raw_writel(0, &npe->regs->watch_count); + + val = ixp4xx_read_feature_bits(); + /* reset the NPE */ + ixp4xx_write_feature_bits(val & + ~(IXP4XX_FEATURE_RESET_NPEA << npe->id)); + /* deassert reset */ + ixp4xx_write_feature_bits(val | + (IXP4XX_FEATURE_RESET_NPEA << npe->id)); + for (i = 0; i < MAX_RETRIES; i++) { + if (ixp4xx_read_feature_bits() & + (IXP4XX_FEATURE_RESET_NPEA << npe->id)) + break; /* NPE is back alive */ + udelay(1); + } + if (i == MAX_RETRIES) + return -ETIMEDOUT; + + npe_stop(npe); + + /* restore NPE configuration bus Control Register - parity settings */ + __raw_writel(ctl, &npe->regs->messaging_control); + return 0; +} + + +int npe_send_message(struct npe *npe, const void *msg, const char *what) +{ + const u32 *send = msg; + int cycles = 0; + + debug_msg(npe, "Trying to send message %s [%08X:%08X]\n", + what, send[0], send[1]); + + if (__raw_readl(&npe->regs->messaging_status) & MSGSTAT_IFNE) { + debug_msg(npe, "NPE input FIFO not empty\n"); + return -EIO; + } + + __raw_writel(send[0], &npe->regs->in_out_fifo); + + if (!(__raw_readl(&npe->regs->messaging_status) & MSGSTAT_IFNF)) { + debug_msg(npe, "NPE input FIFO full\n"); + return -EIO; + } + + __raw_writel(send[1], &npe->regs->in_out_fifo); + + while ((cycles < MAX_RETRIES) && + (__raw_readl(&npe->regs->messaging_status) & MSGSTAT_IFNE)) { + udelay(1); + cycles++; + } + + if (cycles == MAX_RETRIES) { + debug_msg(npe, "Timeout sending message\n"); + return -ETIMEDOUT; + } + +#if DEBUG_MSG > 1 + debug_msg(npe, "Sending a message took %i cycles\n", cycles); +#endif + return 0; +} + +int npe_recv_message(struct npe *npe, void *msg, const char *what) +{ + u32 *recv = msg; + int cycles = 0, cnt = 0; + + debug_msg(npe, "Trying to receive message %s\n", what); + + while (cycles < MAX_RETRIES) { + if (__raw_readl(&npe->regs->messaging_status) & MSGSTAT_OFNE) { + recv[cnt++] = __raw_readl(&npe->regs->in_out_fifo); + if (cnt == 2) + break; + } else { + udelay(1); + cycles++; + } + } + + switch(cnt) { + case 1: + debug_msg(npe, "Received [%08X]\n", recv[0]); + break; + case 2: + debug_msg(npe, "Received [%08X:%08X]\n", recv[0], recv[1]); + break; + } + + if (cycles == MAX_RETRIES) { + debug_msg(npe, "Timeout waiting for message\n"); + return -ETIMEDOUT; + } + +#if DEBUG_MSG > 1 + debug_msg(npe, "Receiving a message took %i cycles\n", cycles); +#endif + return 0; +} + +int npe_send_recv_message(struct npe *npe, void *msg, const char *what) +{ + int result; + u32 *send = msg, recv[2]; + + if ((result = npe_send_message(npe, msg, what)) != 0) + return result; + if ((result = npe_recv_message(npe, recv, what)) != 0) + return result; + + if ((recv[0] != send[0]) || (recv[1] != send[1])) { + debug_msg(npe, "Message %s: unexpected message received\n", + what); + return -EIO; + } + return 0; +} + + +int npe_load_firmware(struct npe *npe, const char *name, struct device *dev) +{ + const struct firmware *fw_entry; + + struct dl_block { + u32 type; + u32 offset; + } *blk; + + struct dl_image { + u32 magic; + u32 id; + u32 size; + union { + u32 data[0]; + struct dl_block blocks[0]; + }; + } *image; + + struct dl_codeblock { + u32 npe_addr; + u32 size; + u32 data[0]; + } *cb; + + int i, j, err, data_size, instr_size, blocks, table_end; + u32 cmd; + + if ((err = request_firmware(&fw_entry, name, dev)) != 0) + return err; + + err = -EINVAL; + if (fw_entry->size < sizeof(struct dl_image)) { + print_npe(KERN_ERR, npe, "incomplete firmware file\n"); + goto err; + } + image = (struct dl_image*)fw_entry->data; + +#if DEBUG_FW + print_npe(KERN_DEBUG, npe, "firmware: %08X %08X %08X (0x%X bytes)\n", + image->magic, image->id, image->size, image->size * 4); +#endif + + if (image->magic == swab32(FW_MAGIC)) { /* swapped file */ + image->id = swab32(image->id); + image->size = swab32(image->size); + } else if (image->magic != FW_MAGIC) { + print_npe(KERN_ERR, npe, "bad firmware file magic: 0x%X\n", + image->magic); + goto err; + } + if ((image->size * 4 + sizeof(struct dl_image)) != fw_entry->size) { + print_npe(KERN_ERR, npe, + "inconsistent size of firmware file\n"); + goto err; + } + if (((image->id >> 24) & 0xF /* NPE ID */) != npe->id) { + print_npe(KERN_ERR, npe, "firmware file NPE ID mismatch\n"); + goto err; + } + if (image->magic == swab32(FW_MAGIC)) + for (i = 0; i < image->size; i++) + image->data[i] = swab32(image->data[i]); + + if (cpu_is_ixp42x() && ((image->id >> 28) & 0xF /* device ID */)) { + print_npe(KERN_INFO, npe, "IXP43x/IXP46x firmware ignored on " + "IXP42x\n"); + goto err; + } + + if (npe_running(npe)) { + print_npe(KERN_INFO, npe, "unable to load firmware, NPE is " + "already running\n"); + err = -EBUSY; + goto err; + } +#if 0 + npe_stop(npe); + npe_reset(npe); +#endif + + print_npe(KERN_INFO, npe, "firmware functionality 0x%X, " + "revision 0x%X:%X\n", (image->id >> 16) & 0xFF, + (image->id >> 8) & 0xFF, image->id & 0xFF); + + if (cpu_is_ixp42x()) { + if (!npe->id) + instr_size = NPE_A_42X_INSTR_SIZE; + else + instr_size = NPE_B_AND_C_42X_INSTR_SIZE; + data_size = NPE_42X_DATA_SIZE; + } else { + instr_size = NPE_46X_INSTR_SIZE; + data_size = NPE_46X_DATA_SIZE; + } + + for (blocks = 0; blocks * sizeof(struct dl_block) / 4 < image->size; + blocks++) + if (image->blocks[blocks].type == FW_BLOCK_TYPE_EOF) + break; + if (blocks * sizeof(struct dl_block) / 4 >= image->size) { + print_npe(KERN_INFO, npe, "firmware EOF block marker not " + "found\n"); + goto err; + } + +#if DEBUG_FW + print_npe(KERN_DEBUG, npe, "%i firmware blocks found\n", blocks); +#endif + + table_end = blocks * sizeof(struct dl_block) / 4 + 1 /* EOF marker */; + for (i = 0, blk = image->blocks; i < blocks; i++, blk++) { + if (blk->offset > image->size - sizeof(struct dl_codeblock) / 4 + || blk->offset < table_end) { + print_npe(KERN_INFO, npe, "invalid offset 0x%X of " + "firmware block #%i\n", blk->offset, i); + goto err; + } + + cb = (struct dl_codeblock*)&image->data[blk->offset]; + if (blk->type == FW_BLOCK_TYPE_INSTR) { + if (cb->npe_addr + cb->size > instr_size) + goto too_big; + cmd = CMD_WR_INS_MEM; + } else if (blk->type == FW_BLOCK_TYPE_DATA) { + if (cb->npe_addr + cb->size > data_size) + goto too_big; + cmd = CMD_WR_DATA_MEM; + } else { + print_npe(KERN_INFO, npe, "invalid firmware block #%i " + "type 0x%X\n", i, blk->type); + goto err; + } + if (blk->offset + sizeof(*cb) / 4 + cb->size > image->size) { + print_npe(KERN_INFO, npe, "firmware block #%i doesn't " + "fit in firmware image: type %c, start 0x%X," + " length 0x%X\n", i, + blk->type == FW_BLOCK_TYPE_INSTR ? 'I' : 'D', + cb->npe_addr, cb->size); + goto err; + } + + for (j = 0; j < cb->size; j++) + npe_cmd_write(npe, cb->npe_addr + j, cmd, cb->data[j]); + } + + npe_start(npe); + if (!npe_running(npe)) + print_npe(KERN_ERR, npe, "unable to start\n"); + release_firmware(fw_entry); + return 0; + +too_big: + print_npe(KERN_INFO, npe, "firmware block #%i doesn't fit in NPE " + "memory: type %c, start 0x%X, length 0x%X\n", i, + blk->type == FW_BLOCK_TYPE_INSTR ? 'I' : 'D', + cb->npe_addr, cb->size); +err: + release_firmware(fw_entry); + return err; +} + + +struct npe *npe_request(unsigned id) +{ + if (id < NPE_COUNT) + if (npe_tab[id].valid) + if (try_module_get(THIS_MODULE)) + return &npe_tab[id]; + return NULL; +} + +void npe_release(struct npe *npe) +{ + module_put(THIS_MODULE); +} + +static int ixp4xx_npe_probe(struct platform_device *pdev) +{ + int i, found = 0; + struct device *dev = &pdev->dev; + struct resource *res; + + for (i = 0; i < NPE_COUNT; i++) { + struct npe *npe = &npe_tab[i]; + + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) + return -ENODEV; + + if (!(ixp4xx_read_feature_bits() & + (IXP4XX_FEATURE_RESET_NPEA << i))) { + dev_info(dev, "NPE%d at 0x%08x-0x%08x not available\n", + i, res->start, res->end); + continue; /* NPE already disabled or not present */ + } + npe->regs = devm_ioremap_resource(dev, res); + if (!npe->regs) + return -ENOMEM; + + if (npe_reset(npe)) { + dev_info(dev, "NPE%d at 0x%08x-0x%08x does not reset\n", + i, res->start, res->end); + continue; + } + npe->valid = 1; + dev_info(dev, "NPE%d at 0x%08x-0x%08x registered\n", + i, res->start, res->end); + found++; + } + + if (!found) + return -ENODEV; + return 0; +} + +static int ixp4xx_npe_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < NPE_COUNT; i++) + if (npe_tab[i].regs) { + npe_reset(&npe_tab[i]); + } + + return 0; +} + +static const struct of_device_id ixp4xx_npe_of_match[] = { + { + .compatible = "intel,ixp4xx-network-processing-engine", + }, + {}, +}; + +static struct platform_driver ixp4xx_npe_driver = { + .driver = { + .name = "ixp4xx-npe", + .of_match_table = of_match_ptr(ixp4xx_npe_of_match), + }, + .probe = ixp4xx_npe_probe, + .remove = ixp4xx_npe_remove, +}; +module_platform_driver(ixp4xx_npe_driver); + +MODULE_AUTHOR("Krzysztof Halasa"); +MODULE_LICENSE("GPL v2"); +MODULE_FIRMWARE(NPE_A_FIRMWARE); +MODULE_FIRMWARE(NPE_B_FIRMWARE); +MODULE_FIRMWARE(NPE_C_FIRMWARE); + +EXPORT_SYMBOL(npe_names); +EXPORT_SYMBOL(npe_running); +EXPORT_SYMBOL(npe_request); +EXPORT_SYMBOL(npe_release); +EXPORT_SYMBOL(npe_load_firmware); +EXPORT_SYMBOL(npe_send_message); +EXPORT_SYMBOL(npe_recv_message); +EXPORT_SYMBOL(npe_send_recv_message); diff --git a/drivers/soc/ixp4xx/ixp4xx-qmgr.c b/drivers/soc/ixp4xx/ixp4xx-qmgr.c new file mode 100644 index 000000000000..bb90670ec160 --- /dev/null +++ b/drivers/soc/ixp4xx/ixp4xx-qmgr.c @@ -0,0 +1,488 @@ +/* + * Intel IXP4xx Queue Manager driver for Linux + * + * Copyright (C) 2007 Krzysztof Halasa <khc@pm.waw.pl> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/soc/ixp4xx/qmgr.h> + +static struct qmgr_regs __iomem *qmgr_regs; +static int qmgr_irq_1; +static int qmgr_irq_2; +static spinlock_t qmgr_lock; +static u32 used_sram_bitmap[4]; /* 128 16-dword pages */ +static void (*irq_handlers[QUEUES])(void *pdev); +static void *irq_pdevs[QUEUES]; + +#if DEBUG_QMGR +char qmgr_queue_descs[QUEUES][32]; +#endif + +void qmgr_put_entry(unsigned int queue, u32 val) +{ +#if DEBUG_QMGR + BUG_ON(!qmgr_queue_descs[queue]); /* not yet requested */ + + printk(KERN_DEBUG "Queue %s(%i) put %X\n", + qmgr_queue_descs[queue], queue, val); +#endif + __raw_writel(val, &qmgr_regs->acc[queue][0]); +} + +u32 qmgr_get_entry(unsigned int queue) +{ + u32 val; + val = __raw_readl(&qmgr_regs->acc[queue][0]); +#if DEBUG_QMGR + BUG_ON(!qmgr_queue_descs[queue]); /* not yet requested */ + + printk(KERN_DEBUG "Queue %s(%i) get %X\n", + qmgr_queue_descs[queue], queue, val); +#endif + return val; +} + +static int __qmgr_get_stat1(unsigned int queue) +{ + return (__raw_readl(&qmgr_regs->stat1[queue >> 3]) + >> ((queue & 7) << 2)) & 0xF; +} + +static int __qmgr_get_stat2(unsigned int queue) +{ + BUG_ON(queue >= HALF_QUEUES); + return (__raw_readl(&qmgr_regs->stat2[queue >> 4]) + >> ((queue & 0xF) << 1)) & 0x3; +} + +/** + * qmgr_stat_empty() - checks if a hardware queue is empty + * @queue: queue number + * + * Returns non-zero value if the queue is empty. + */ +int qmgr_stat_empty(unsigned int queue) +{ + BUG_ON(queue >= HALF_QUEUES); + return __qmgr_get_stat1(queue) & QUEUE_STAT1_EMPTY; +} + +/** + * qmgr_stat_below_low_watermark() - checks if a queue is below low watermark + * @queue: queue number + * + * Returns non-zero value if the queue is below low watermark. + */ +int qmgr_stat_below_low_watermark(unsigned int queue) +{ + if (queue >= HALF_QUEUES) + return (__raw_readl(&qmgr_regs->statne_h) >> + (queue - HALF_QUEUES)) & 0x01; + return __qmgr_get_stat1(queue) & QUEUE_STAT1_NEARLY_EMPTY; +} + +/** + * qmgr_stat_full() - checks if a hardware queue is full + * @queue: queue number + * + * Returns non-zero value if the queue is full. + */ +int qmgr_stat_full(unsigned int queue) +{ + if (queue >= HALF_QUEUES) + return (__raw_readl(&qmgr_regs->statf_h) >> + (queue - HALF_QUEUES)) & 0x01; + return __qmgr_get_stat1(queue) & QUEUE_STAT1_FULL; +} + +/** + * qmgr_stat_overflow() - checks if a hardware queue experienced overflow + * @queue: queue number + * + * Returns non-zero value if the queue experienced overflow. + */ +int qmgr_stat_overflow(unsigned int queue) +{ + return __qmgr_get_stat2(queue) & QUEUE_STAT2_OVERFLOW; +} + +void qmgr_set_irq(unsigned int queue, int src, + void (*handler)(void *pdev), void *pdev) +{ + unsigned long flags; + + spin_lock_irqsave(&qmgr_lock, flags); + if (queue < HALF_QUEUES) { + u32 __iomem *reg; + int bit; + BUG_ON(src > QUEUE_IRQ_SRC_NOT_FULL); + reg = &qmgr_regs->irqsrc[queue >> 3]; /* 8 queues per u32 */ + bit = (queue % 8) * 4; /* 3 bits + 1 reserved bit per queue */ + __raw_writel((__raw_readl(reg) & ~(7 << bit)) | (src << bit), + reg); + } else + /* IRQ source for queues 32-63 is fixed */ + BUG_ON(src != QUEUE_IRQ_SRC_NOT_NEARLY_EMPTY); + + irq_handlers[queue] = handler; + irq_pdevs[queue] = pdev; + spin_unlock_irqrestore(&qmgr_lock, flags); +} + + +static irqreturn_t qmgr_irq1_a0(int irq, void *pdev) +{ + int i, ret = 0; + u32 en_bitmap, src, stat; + + /* ACK - it may clear any bits so don't rely on it */ + __raw_writel(0xFFFFFFFF, &qmgr_regs->irqstat[0]); + + en_bitmap = qmgr_regs->irqen[0]; + while (en_bitmap) { + i = __fls(en_bitmap); /* number of the last "low" queue */ + en_bitmap &= ~BIT(i); + src = qmgr_regs->irqsrc[i >> 3]; + stat = qmgr_regs->stat1[i >> 3]; + if (src & 4) /* the IRQ condition is inverted */ + stat = ~stat; + if (stat & BIT(src & 3)) { + irq_handlers[i](irq_pdevs[i]); + ret = IRQ_HANDLED; + } + } + return ret; +} + + +static irqreturn_t qmgr_irq2_a0(int irq, void *pdev) +{ + int i, ret = 0; + u32 req_bitmap; + + /* ACK - it may clear any bits so don't rely on it */ + __raw_writel(0xFFFFFFFF, &qmgr_regs->irqstat[1]); + + req_bitmap = qmgr_regs->irqen[1] & qmgr_regs->statne_h; + while (req_bitmap) { + i = __fls(req_bitmap); /* number of the last "high" queue */ + req_bitmap &= ~BIT(i); + irq_handlers[HALF_QUEUES + i](irq_pdevs[HALF_QUEUES + i]); + ret = IRQ_HANDLED; + } + return ret; +} + + +static irqreturn_t qmgr_irq(int irq, void *pdev) +{ + int i, half = (irq == qmgr_irq_1 ? 0 : 1); + u32 req_bitmap = __raw_readl(&qmgr_regs->irqstat[half]); + + if (!req_bitmap) + return 0; + __raw_writel(req_bitmap, &qmgr_regs->irqstat[half]); /* ACK */ + + while (req_bitmap) { + i = __fls(req_bitmap); /* number of the last queue */ + req_bitmap &= ~BIT(i); + i += half * HALF_QUEUES; + irq_handlers[i](irq_pdevs[i]); + } + return IRQ_HANDLED; +} + + +void qmgr_enable_irq(unsigned int queue) +{ + unsigned long flags; + int half = queue / 32; + u32 mask = 1 << (queue & (HALF_QUEUES - 1)); + + spin_lock_irqsave(&qmgr_lock, flags); + __raw_writel(__raw_readl(&qmgr_regs->irqen[half]) | mask, + &qmgr_regs->irqen[half]); + spin_unlock_irqrestore(&qmgr_lock, flags); +} + +void qmgr_disable_irq(unsigned int queue) +{ + unsigned long flags; + int half = queue / 32; + u32 mask = 1 << (queue & (HALF_QUEUES - 1)); + + spin_lock_irqsave(&qmgr_lock, flags); + __raw_writel(__raw_readl(&qmgr_regs->irqen[half]) & ~mask, + &qmgr_regs->irqen[half]); + __raw_writel(mask, &qmgr_regs->irqstat[half]); /* clear */ + spin_unlock_irqrestore(&qmgr_lock, flags); +} + +static inline void shift_mask(u32 *mask) +{ + mask[3] = mask[3] << 1 | mask[2] >> 31; + mask[2] = mask[2] << 1 | mask[1] >> 31; + mask[1] = mask[1] << 1 | mask[0] >> 31; + mask[0] <<= 1; +} + +#if DEBUG_QMGR +int qmgr_request_queue(unsigned int queue, unsigned int len /* dwords */, + unsigned int nearly_empty_watermark, + unsigned int nearly_full_watermark, + const char *desc_format, const char* name) +#else +int __qmgr_request_queue(unsigned int queue, unsigned int len /* dwords */, + unsigned int nearly_empty_watermark, + unsigned int nearly_full_watermark) +#endif +{ + u32 cfg, addr = 0, mask[4]; /* in 16-dwords */ + int err; + + BUG_ON(queue >= QUEUES); + + if ((nearly_empty_watermark | nearly_full_watermark) & ~7) + return -EINVAL; + + switch (len) { + case 16: + cfg = 0 << 24; + mask[0] = 0x1; + break; + case 32: + cfg = 1 << 24; + mask[0] = 0x3; + break; + case 64: + cfg = 2 << 24; + mask[0] = 0xF; + break; + case 128: + cfg = 3 << 24; + mask[0] = 0xFF; + break; + default: + return -EINVAL; + } + + cfg |= nearly_empty_watermark << 26; + cfg |= nearly_full_watermark << 29; + len /= 16; /* in 16-dwords: 1, 2, 4 or 8 */ + mask[1] = mask[2] = mask[3] = 0; + + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + + spin_lock_irq(&qmgr_lock); + if (__raw_readl(&qmgr_regs->sram[queue])) { + err = -EBUSY; + goto err; + } + + while (1) { + if (!(used_sram_bitmap[0] & mask[0]) && + !(used_sram_bitmap[1] & mask[1]) && + !(used_sram_bitmap[2] & mask[2]) && + !(used_sram_bitmap[3] & mask[3])) + break; /* found free space */ + + addr++; + shift_mask(mask); + if (addr + len > ARRAY_SIZE(qmgr_regs->sram)) { + printk(KERN_ERR "qmgr: no free SRAM space for" + " queue %i\n", queue); + err = -ENOMEM; + goto err; + } + } + + used_sram_bitmap[0] |= mask[0]; + used_sram_bitmap[1] |= mask[1]; + used_sram_bitmap[2] |= mask[2]; + used_sram_bitmap[3] |= mask[3]; + __raw_writel(cfg | (addr << 14), &qmgr_regs->sram[queue]); +#if DEBUG_QMGR + snprintf(qmgr_queue_descs[queue], sizeof(qmgr_queue_descs[0]), + desc_format, name); + printk(KERN_DEBUG "qmgr: requested queue %s(%i) addr = 0x%02X\n", + qmgr_queue_descs[queue], queue, addr); +#endif + spin_unlock_irq(&qmgr_lock); + return 0; + +err: + spin_unlock_irq(&qmgr_lock); + module_put(THIS_MODULE); + return err; +} + +void qmgr_release_queue(unsigned int queue) +{ + u32 cfg, addr, mask[4]; + + BUG_ON(queue >= QUEUES); /* not in valid range */ + + spin_lock_irq(&qmgr_lock); + cfg = __raw_readl(&qmgr_regs->sram[queue]); + addr = (cfg >> 14) & 0xFF; + + BUG_ON(!addr); /* not requested */ + + switch ((cfg >> 24) & 3) { + case 0: mask[0] = 0x1; break; + case 1: mask[0] = 0x3; break; + case 2: mask[0] = 0xF; break; + case 3: mask[0] = 0xFF; break; + } + + mask[1] = mask[2] = mask[3] = 0; + + while (addr--) + shift_mask(mask); + +#if DEBUG_QMGR + printk(KERN_DEBUG "qmgr: releasing queue %s(%i)\n", + qmgr_queue_descs[queue], queue); + qmgr_queue_descs[queue][0] = '\x0'; +#endif + + while ((addr = qmgr_get_entry(queue))) + printk(KERN_ERR "qmgr: released queue %i not empty: 0x%08X\n", + queue, addr); + + __raw_writel(0, &qmgr_regs->sram[queue]); + + used_sram_bitmap[0] &= ~mask[0]; + used_sram_bitmap[1] &= ~mask[1]; + used_sram_bitmap[2] &= ~mask[2]; + used_sram_bitmap[3] &= ~mask[3]; + irq_handlers[queue] = NULL; /* catch IRQ bugs */ + spin_unlock_irq(&qmgr_lock); + + module_put(THIS_MODULE); +} + +static int ixp4xx_qmgr_probe(struct platform_device *pdev) +{ + int i, err; + irq_handler_t handler1, handler2; + struct device *dev = &pdev->dev; + struct resource *res; + int irq1, irq2; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + qmgr_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(qmgr_regs)) + return PTR_ERR(qmgr_regs); + + irq1 = platform_get_irq(pdev, 0); + if (irq1 <= 0) + return irq1 ? irq1 : -EINVAL; + qmgr_irq_1 = irq1; + irq2 = platform_get_irq(pdev, 1); + if (irq2 <= 0) + return irq2 ? irq2 : -EINVAL; + qmgr_irq_2 = irq2; + + /* reset qmgr registers */ + for (i = 0; i < 4; i++) { + __raw_writel(0x33333333, &qmgr_regs->stat1[i]); + __raw_writel(0, &qmgr_regs->irqsrc[i]); + } + for (i = 0; i < 2; i++) { + __raw_writel(0, &qmgr_regs->stat2[i]); + __raw_writel(0xFFFFFFFF, &qmgr_regs->irqstat[i]); /* clear */ + __raw_writel(0, &qmgr_regs->irqen[i]); + } + + __raw_writel(0xFFFFFFFF, &qmgr_regs->statne_h); + __raw_writel(0, &qmgr_regs->statf_h); + + for (i = 0; i < QUEUES; i++) + __raw_writel(0, &qmgr_regs->sram[i]); + + if (cpu_is_ixp42x_rev_a0()) { + handler1 = qmgr_irq1_a0; + handler2 = qmgr_irq2_a0; + } else + handler1 = handler2 = qmgr_irq; + + err = devm_request_irq(dev, irq1, handler1, 0, "IXP4xx Queue Manager", + NULL); + if (err) { + dev_err(dev, "failed to request IRQ%i (%i)\n", + irq1, err); + return err; + } + + err = devm_request_irq(dev, irq2, handler2, 0, "IXP4xx Queue Manager", + NULL); + if (err) { + dev_err(dev, "failed to request IRQ%i (%i)\n", + irq2, err); + return err; + } + + used_sram_bitmap[0] = 0xF; /* 4 first pages reserved for config */ + spin_lock_init(&qmgr_lock); + + dev_info(dev, "IXP4xx Queue Manager initialized.\n"); + return 0; +} + +static int ixp4xx_qmgr_remove(struct platform_device *pdev) +{ + synchronize_irq(qmgr_irq_1); + synchronize_irq(qmgr_irq_2); + return 0; +} + +static const struct of_device_id ixp4xx_qmgr_of_match[] = { + { + .compatible = "intel,ixp4xx-ahb-queue-manager", + }, + {}, +}; + +static struct platform_driver ixp4xx_qmgr_driver = { + .driver = { + .name = "ixp4xx-qmgr", + .of_match_table = of_match_ptr(ixp4xx_qmgr_of_match), + }, + .probe = ixp4xx_qmgr_probe, + .remove = ixp4xx_qmgr_remove, +}; +module_platform_driver(ixp4xx_qmgr_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Krzysztof Halasa"); + +EXPORT_SYMBOL(qmgr_put_entry); +EXPORT_SYMBOL(qmgr_get_entry); +EXPORT_SYMBOL(qmgr_stat_empty); +EXPORT_SYMBOL(qmgr_stat_below_low_watermark); +EXPORT_SYMBOL(qmgr_stat_full); +EXPORT_SYMBOL(qmgr_stat_overflow); +EXPORT_SYMBOL(qmgr_set_irq); +EXPORT_SYMBOL(qmgr_enable_irq); +EXPORT_SYMBOL(qmgr_disable_irq); +#if DEBUG_QMGR +EXPORT_SYMBOL(qmgr_queue_descs); +EXPORT_SYMBOL(qmgr_request_queue); +#else +EXPORT_SYMBOL(__qmgr_request_queue); +#endif +EXPORT_SYMBOL(qmgr_release_queue); diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c index 8236a6c87e19..c4449a163991 100644 --- a/drivers/soc/mediatek/mtk-pmic-wrap.c +++ b/drivers/soc/mediatek/mtk-pmic-wrap.c @@ -381,6 +381,10 @@ enum pwrap_regs { PWRAP_EXT_GPS_AUXADC_RDATA_ADDR, PWRAP_GPSINF_0_STA, PWRAP_GPSINF_1_STA, + + /* MT8516 only regs */ + PWRAP_OP_TYPE, + PWRAP_MSB_FIRST, }; static int mt2701_regs[] = { @@ -852,6 +856,91 @@ static int mt8183_regs[] = { [PWRAP_WACS2_VLDCLR] = 0xC28, }; +static int mt8516_regs[] = { + [PWRAP_MUX_SEL] = 0x0, + [PWRAP_WRAP_EN] = 0x4, + [PWRAP_DIO_EN] = 0x8, + [PWRAP_SIDLY] = 0xc, + [PWRAP_RDDMY] = 0x10, + [PWRAP_SI_CK_CON] = 0x14, + [PWRAP_CSHEXT_WRITE] = 0x18, + [PWRAP_CSHEXT_READ] = 0x1c, + [PWRAP_CSLEXT_START] = 0x20, + [PWRAP_CSLEXT_END] = 0x24, + [PWRAP_STAUPD_PRD] = 0x28, + [PWRAP_STAUPD_GRPEN] = 0x2c, + [PWRAP_STAUPD_MAN_TRIG] = 0x40, + [PWRAP_STAUPD_STA] = 0x44, + [PWRAP_WRAP_STA] = 0x48, + [PWRAP_HARB_INIT] = 0x4c, + [PWRAP_HARB_HPRIO] = 0x50, + [PWRAP_HIPRIO_ARB_EN] = 0x54, + [PWRAP_HARB_STA0] = 0x58, + [PWRAP_HARB_STA1] = 0x5c, + [PWRAP_MAN_EN] = 0x60, + [PWRAP_MAN_CMD] = 0x64, + [PWRAP_MAN_RDATA] = 0x68, + [PWRAP_MAN_VLDCLR] = 0x6c, + [PWRAP_WACS0_EN] = 0x70, + [PWRAP_INIT_DONE0] = 0x74, + [PWRAP_WACS0_CMD] = 0x78, + [PWRAP_WACS0_RDATA] = 0x7c, + [PWRAP_WACS0_VLDCLR] = 0x80, + [PWRAP_WACS1_EN] = 0x84, + [PWRAP_INIT_DONE1] = 0x88, + [PWRAP_WACS1_CMD] = 0x8c, + [PWRAP_WACS1_RDATA] = 0x90, + [PWRAP_WACS1_VLDCLR] = 0x94, + [PWRAP_WACS2_EN] = 0x98, + [PWRAP_INIT_DONE2] = 0x9c, + [PWRAP_WACS2_CMD] = 0xa0, + [PWRAP_WACS2_RDATA] = 0xa4, + [PWRAP_WACS2_VLDCLR] = 0xa8, + [PWRAP_INT_EN] = 0xac, + [PWRAP_INT_FLG_RAW] = 0xb0, + [PWRAP_INT_FLG] = 0xb4, + [PWRAP_INT_CLR] = 0xb8, + [PWRAP_SIG_ADR] = 0xbc, + [PWRAP_SIG_MODE] = 0xc0, + [PWRAP_SIG_VALUE] = 0xc4, + [PWRAP_SIG_ERRVAL] = 0xc8, + [PWRAP_CRC_EN] = 0xcc, + [PWRAP_TIMER_EN] = 0xd0, + [PWRAP_TIMER_STA] = 0xd4, + [PWRAP_WDT_UNIT] = 0xd8, + [PWRAP_WDT_SRC_EN] = 0xdc, + [PWRAP_WDT_FLG] = 0xe0, + [PWRAP_DEBUG_INT_SEL] = 0xe4, + [PWRAP_DVFS_ADR0] = 0xe8, + [PWRAP_DVFS_WDATA0] = 0xec, + [PWRAP_DVFS_ADR1] = 0xf0, + [PWRAP_DVFS_WDATA1] = 0xf4, + [PWRAP_DVFS_ADR2] = 0xf8, + [PWRAP_DVFS_WDATA2] = 0xfc, + [PWRAP_DVFS_ADR3] = 0x100, + [PWRAP_DVFS_WDATA3] = 0x104, + [PWRAP_DVFS_ADR4] = 0x108, + [PWRAP_DVFS_WDATA4] = 0x10c, + [PWRAP_DVFS_ADR5] = 0x110, + [PWRAP_DVFS_WDATA5] = 0x114, + [PWRAP_DVFS_ADR6] = 0x118, + [PWRAP_DVFS_WDATA6] = 0x11c, + [PWRAP_DVFS_ADR7] = 0x120, + [PWRAP_DVFS_WDATA7] = 0x124, + [PWRAP_SPMINF_STA] = 0x128, + [PWRAP_CIPHER_KEY_SEL] = 0x12c, + [PWRAP_CIPHER_IV_SEL] = 0x130, + [PWRAP_CIPHER_EN] = 0x134, + [PWRAP_CIPHER_RDY] = 0x138, + [PWRAP_CIPHER_MODE] = 0x13c, + [PWRAP_CIPHER_SWRST] = 0x140, + [PWRAP_DCM_EN] = 0x144, + [PWRAP_DCM_DBC_PRD] = 0x148, + [PWRAP_SW_RST] = 0x168, + [PWRAP_OP_TYPE] = 0x16c, + [PWRAP_MSB_FIRST] = 0x170, +}; + enum pmic_type { PMIC_MT6323, PMIC_MT6351, @@ -869,6 +958,7 @@ enum pwrap_type { PWRAP_MT8135, PWRAP_MT8173, PWRAP_MT8183, + PWRAP_MT8516, }; struct pmic_wrapper; @@ -1281,7 +1371,7 @@ static bool pwrap_is_pmic_cipher_ready(struct pmic_wrapper *wrp) static int pwrap_init_cipher(struct pmic_wrapper *wrp) { int ret; - u32 rdata; + u32 rdata = 0; pwrap_writel(wrp, 0x1, PWRAP_CIPHER_SWRST); pwrap_writel(wrp, 0x0, PWRAP_CIPHER_SWRST); @@ -1297,6 +1387,7 @@ static int pwrap_init_cipher(struct pmic_wrapper *wrp) case PWRAP_MT6765: case PWRAP_MT6797: case PWRAP_MT8173: + case PWRAP_MT8516: pwrap_writel(wrp, 1, PWRAP_CIPHER_EN); break; case PWRAP_MT7622: @@ -1478,7 +1569,8 @@ static int pwrap_init(struct pmic_wrapper *wrp) { int ret; - reset_control_reset(wrp->rstc); + if (wrp->rstc) + reset_control_reset(wrp->rstc); if (wrp->rstc_bridge) reset_control_reset(wrp->rstc_bridge); @@ -1764,6 +1856,18 @@ static const struct pmic_wrapper_type pwrap_mt8183 = { .init_soc_specific = pwrap_mt8183_init_soc_specific, }; +static struct pmic_wrapper_type pwrap_mt8516 = { + .regs = mt8516_regs, + .type = PWRAP_MT8516, + .arb_en_all = 0xff, + .int_en_all = ~(u32)(BIT(31) | BIT(2)), + .spi_w = PWRAP_MAN_CMD_SPI_WRITE, + .wdt_src = PWRAP_WDT_SRC_MASK_ALL, + .caps = PWRAP_CAP_DCM, + .init_reg_clock = pwrap_mt2701_init_reg_clock, + .init_soc_specific = NULL, +}; + static const struct of_device_id of_pwrap_match_tbl[] = { { .compatible = "mediatek,mt2701-pwrap", @@ -1787,6 +1891,9 @@ static const struct of_device_id of_pwrap_match_tbl[] = { .compatible = "mediatek,mt8183-pwrap", .data = &pwrap_mt8183, }, { + .compatible = "mediatek,mt8516-pwrap", + .data = &pwrap_mt8516, + }, { /* sentinel */ } }; diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c index c701b3b010f1..f6c3d17b05c7 100644 --- a/drivers/soc/qcom/cmd-db.c +++ b/drivers/soc/qcom/cmd-db.c @@ -248,8 +248,8 @@ static int cmd_db_dev_probe(struct platform_device *pdev) } cmd_db_header = memremap(rmem->base, rmem->size, MEMREMAP_WB); - if (IS_ERR_OR_NULL(cmd_db_header)) { - ret = PTR_ERR(cmd_db_header); + if (!cmd_db_header) { + ret = -ENOMEM; cmd_db_header = NULL; return ret; } diff --git a/drivers/soc/qcom/qmi_interface.c b/drivers/soc/qcom/qmi_interface.c index c239a28e503f..f9e309f0acd3 100644 --- a/drivers/soc/qcom/qmi_interface.c +++ b/drivers/soc/qcom/qmi_interface.c @@ -345,8 +345,7 @@ int qmi_txn_wait(struct qmi_txn *txn, unsigned long timeout) struct qmi_handle *qmi = txn->qmi; int ret; - ret = wait_for_completion_interruptible_timeout(&txn->completion, - timeout); + ret = wait_for_completion_timeout(&txn->completion, timeout); mutex_lock(&qmi->txn_lock); mutex_lock(&txn->lock); @@ -354,9 +353,7 @@ int qmi_txn_wait(struct qmi_txn *txn, unsigned long timeout) mutex_unlock(&txn->lock); mutex_unlock(&qmi->txn_lock); - if (ret < 0) - return ret; - else if (ret == 0) + if (ret == 0) return -ETIMEDOUT; else return txn->result; diff --git a/drivers/soc/qcom/rmtfs_mem.c b/drivers/soc/qcom/rmtfs_mem.c index 7200d762a951..6f5e8be9689c 100644 --- a/drivers/soc/qcom/rmtfs_mem.c +++ b/drivers/soc/qcom/rmtfs_mem.c @@ -137,6 +137,26 @@ static struct class rmtfs_class = { .name = "rmtfs", }; +static int qcom_rmtfs_mem_mmap(struct file *filep, struct vm_area_struct *vma) +{ + struct qcom_rmtfs_mem *rmtfs_mem = filep->private_data; + + if (vma->vm_end - vma->vm_start > rmtfs_mem->size) { + dev_dbg(&rmtfs_mem->dev, + "vm_end[%lu] - vm_start[%lu] [%lu] > mem->size[%pa]\n", + vma->vm_end, vma->vm_start, + (vma->vm_end - vma->vm_start), &rmtfs_mem->size); + return -EINVAL; + } + + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + return remap_pfn_range(vma, + vma->vm_start, + rmtfs_mem->addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + static const struct file_operations qcom_rmtfs_mem_fops = { .owner = THIS_MODULE, .open = qcom_rmtfs_mem_open, @@ -144,6 +164,7 @@ static const struct file_operations qcom_rmtfs_mem_fops = { .write = qcom_rmtfs_mem_write, .release = qcom_rmtfs_mem_release, .llseek = default_llseek, + .mmap = qcom_rmtfs_mem_mmap, }; static void qcom_rmtfs_mem_release_device(struct device *dev) diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index 75bd9a83aef0..e278fc11fe5c 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -459,7 +459,7 @@ static int find_slots(struct tcs_group *tcs, const struct tcs_request *msg, do { slot = bitmap_find_next_zero_area(tcs->slots, MAX_TCS_SLOTS, i, msg->num_cmds, 0); - if (slot == tcs->num_tcs * tcs->ncpt) + if (slot >= tcs->num_tcs * tcs->ncpt) return -ENOMEM; i += tcs->ncpt; } while (slot + msg->num_cmds - 1 >= i); diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c index 4af96e668a2f..3299cf5365f3 100644 --- a/drivers/soc/renesas/renesas-soc.c +++ b/drivers/soc/renesas/renesas-soc.c @@ -335,6 +335,9 @@ static int __init renesas_soc_init(void) /* R-Car M3-W ES1.1 incorrectly identifies as ES2.0 */ if ((product & 0x7fff) == 0x5210) product ^= 0x11; + /* R-Car M3-W ES1.3 incorrectly identifies as ES2.1 */ + if ((product & 0x7fff) == 0x5211) + product ^= 0x12; if (soc->id && ((product >> 8) & 0xff) != soc->id) { pr_warn("SoC mismatch (product = 0x%x)\n", product); return -ENODEV; diff --git a/drivers/soc/rockchip/grf.c b/drivers/soc/rockchip/grf.c index 96882ffde67e..3b81e1d75a97 100644 --- a/drivers/soc/rockchip/grf.c +++ b/drivers/soc/rockchip/grf.c @@ -66,9 +66,11 @@ static const struct rockchip_grf_info rk3228_grf __initconst = { }; #define RK3288_GRF_SOC_CON0 0x244 +#define RK3288_GRF_SOC_CON2 0x24c static const struct rockchip_grf_value rk3288_defaults[] __initconst = { { "jtag switching", RK3288_GRF_SOC_CON0, HIWORD_UPDATE(0, 1, 12) }, + { "pwm select", RK3288_GRF_SOC_CON2, HIWORD_UPDATE(1, 1, 0) }, }; static const struct rockchip_grf_info rk3288_grf __initconst = { diff --git a/drivers/soc/sunxi/Kconfig b/drivers/soc/sunxi/Kconfig index 353b07e40176..e84eb4e59f58 100644 --- a/drivers/soc/sunxi/Kconfig +++ b/drivers/soc/sunxi/Kconfig @@ -4,6 +4,7 @@ config SUNXI_SRAM bool default ARCH_SUNXI + select REGMAP_MMIO help Say y here to enable the SRAM controller support. This device is responsible on mapping the SRAM in the sunXi SoCs diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 0df258518693..5648e5c09ef5 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -272,6 +272,14 @@ static const char * const tegra30_reset_sources[] = { "WATCHDOG", "SENSOR", "SW_MAIN", + "LP0" +}; + +static const char * const tegra210_reset_sources[] = { + "POWER_ON_RESET", + "WATCHDOG", + "SENSOR", + "SW_MAIN", "LP0", "AOTAG" }; @@ -656,10 +664,15 @@ static int tegra_genpd_power_on(struct generic_pm_domain *domain) int err; err = tegra_powergate_power_up(pg, true); - if (err) + if (err) { dev_err(dev, "failed to turn on PM domain %s: %d\n", pg->genpd.name, err); + goto out; + } + + reset_control_release(pg->reset); +out: return err; } @@ -669,10 +682,18 @@ static int tegra_genpd_power_off(struct generic_pm_domain *domain) struct device *dev = pg->pmc->dev; int err; + err = reset_control_acquire(pg->reset); + if (err < 0) { + pr_err("failed to acquire resets: %d\n", err); + return err; + } + err = tegra_powergate_power_down(pg); - if (err) + if (err) { dev_err(dev, "failed to turn off PM domain %s: %d\n", pg->genpd.name, err); + reset_control_release(pg->reset); + } return err; } @@ -937,38 +958,53 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg, struct device *dev = pg->pmc->dev; int err; - pg->reset = of_reset_control_array_get_exclusive(np); + pg->reset = of_reset_control_array_get_exclusive_released(np); if (IS_ERR(pg->reset)) { err = PTR_ERR(pg->reset); dev_err(dev, "failed to get device resets: %d\n", err); return err; } - if (off) + err = reset_control_acquire(pg->reset); + if (err < 0) { + pr_err("failed to acquire resets: %d\n", err); + goto out; + } + + if (off) { err = reset_control_assert(pg->reset); - else + } else { err = reset_control_deassert(pg->reset); + if (err < 0) + goto out; - if (err) + reset_control_release(pg->reset); + } + +out: + if (err) { + reset_control_release(pg->reset); reset_control_put(pg->reset); + } return err; } -static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) +static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) { struct device *dev = pmc->dev; struct tegra_powergate *pg; - int id, err; + int id, err = 0; bool off; pg = kzalloc(sizeof(*pg), GFP_KERNEL); if (!pg) - return; + return -ENOMEM; id = tegra_powergate_lookup(pmc, np->name); if (id < 0) { dev_err(dev, "powergate lookup failed for %pOFn: %d\n", np, id); + err = -ENODEV; goto free_mem; } @@ -1021,7 +1057,7 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) dev_dbg(dev, "added PM domain %s\n", pg->genpd.name); - return; + return 0; remove_genpd: pm_genpd_remove(&pg->genpd); @@ -1040,25 +1076,67 @@ set_available: free_mem: kfree(pg); + + return err; } -static void tegra_powergate_init(struct tegra_pmc *pmc, - struct device_node *parent) +static int tegra_powergate_init(struct tegra_pmc *pmc, + struct device_node *parent) { struct device_node *np, *child; - unsigned int i; + int err = 0; + + np = of_get_child_by_name(parent, "powergates"); + if (!np) + return 0; + + for_each_child_of_node(np, child) { + err = tegra_powergate_add(pmc, child); + if (err < 0) { + of_node_put(child); + break; + } + } + + of_node_put(np); + + return err; +} + +static void tegra_powergate_remove(struct generic_pm_domain *genpd) +{ + struct tegra_powergate *pg = to_powergate(genpd); + + reset_control_put(pg->reset); + + while (pg->num_clks--) + clk_put(pg->clks[pg->num_clks]); + + kfree(pg->clks); - /* Create a bitmap of the available and valid partitions */ - for (i = 0; i < pmc->soc->num_powergates; i++) - if (pmc->soc->powergates[i]) - set_bit(i, pmc->powergates_available); + set_bit(pg->id, pmc->powergates_available); + + kfree(pg); +} + +static void tegra_powergate_remove_all(struct device_node *parent) +{ + struct generic_pm_domain *genpd; + struct device_node *np, *child; np = of_get_child_by_name(parent, "powergates"); if (!np) return; - for_each_child_of_node(np, child) - tegra_powergate_add(pmc, child); + for_each_child_of_node(np, child) { + of_genpd_del_provider(child); + + genpd = of_genpd_remove_last(child); + if (IS_ERR(genpd)) + continue; + + tegra_powergate_remove(genpd); + } of_node_put(np); } @@ -1709,13 +1787,16 @@ static int tegra_pmc_pinctrl_init(struct tegra_pmc *pmc) static ssize_t reset_reason_show(struct device *dev, struct device_attribute *attr, char *buf) { - u32 value, rst_src; + u32 value; value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); - rst_src = (value & pmc->soc->regs->rst_source_mask) >> - pmc->soc->regs->rst_source_shift; + value &= pmc->soc->regs->rst_source_mask; + value >>= pmc->soc->regs->rst_source_shift; + + if (WARN_ON(value >= pmc->soc->num_reset_sources)) + return sprintf(buf, "%s\n", "UNKNOWN"); - return sprintf(buf, "%s\n", pmc->soc->reset_sources[rst_src]); + return sprintf(buf, "%s\n", pmc->soc->reset_sources[value]); } static DEVICE_ATTR_RO(reset_reason); @@ -1723,13 +1804,16 @@ static DEVICE_ATTR_RO(reset_reason); static ssize_t reset_level_show(struct device *dev, struct device_attribute *attr, char *buf) { - u32 value, rst_lvl; + u32 value; value = tegra_pmc_readl(pmc, pmc->soc->regs->rst_status); - rst_lvl = (value & pmc->soc->regs->rst_level_mask) >> - pmc->soc->regs->rst_level_shift; + value &= pmc->soc->regs->rst_level_mask; + value >>= pmc->soc->regs->rst_level_shift; - return sprintf(buf, "%s\n", pmc->soc->reset_levels[rst_lvl]); + if (WARN_ON(value >= pmc->soc->num_reset_levels)) + return sprintf(buf, "%s\n", "UNKNOWN"); + + return sprintf(buf, "%s\n", pmc->soc->reset_levels[value]); } static DEVICE_ATTR_RO(reset_level); @@ -1999,7 +2083,7 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_powergate_debugfs_init(); if (err < 0) - return err; + goto cleanup_sysfs; } err = register_restart_handler(&tegra_pmc_restart_handler); @@ -2013,9 +2097,13 @@ static int tegra_pmc_probe(struct platform_device *pdev) if (err) goto cleanup_restart_handler; + err = tegra_powergate_init(pmc, pdev->dev.of_node); + if (err < 0) + goto cleanup_powergates; + err = tegra_pmc_irq_init(pmc); if (err < 0) - goto cleanup_restart_handler; + goto cleanup_powergates; mutex_lock(&pmc->powergates_lock); iounmap(pmc->base); @@ -2026,10 +2114,15 @@ static int tegra_pmc_probe(struct platform_device *pdev) return 0; +cleanup_powergates: + tegra_powergate_remove_all(pdev->dev.of_node); cleanup_restart_handler: unregister_restart_handler(&tegra_pmc_restart_handler); cleanup_debugfs: debugfs_remove(pmc->debugfs); +cleanup_sysfs: + device_remove_file(&pdev->dev, &dev_attr_reset_reason); + device_remove_file(&pdev->dev, &dev_attr_reset_level); return err; } @@ -2185,7 +2278,7 @@ static const struct tegra_pmc_soc tegra30_pmc_soc = { .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .num_reset_sources = ARRAY_SIZE(tegra30_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2236,7 +2329,7 @@ static const struct tegra_pmc_soc tegra114_pmc_soc = { .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .num_reset_sources = ARRAY_SIZE(tegra30_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2347,7 +2440,7 @@ static const struct tegra_pmc_soc tegra124_pmc_soc = { .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .num_reset_sources = ARRAY_SIZE(tegra30_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2452,8 +2545,8 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = { .regs = &tegra20_pmc_regs, .init = tegra20_pmc_init, .setup_irq_polarity = tegra20_pmc_setup_irq_polarity, - .reset_sources = tegra30_reset_sources, - .num_reset_sources = 5, + .reset_sources = tegra210_reset_sources, + .num_reset_sources = ARRAY_SIZE(tegra210_reset_sources), .reset_levels = NULL, .num_reset_levels = 0, }; @@ -2578,9 +2671,9 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = { .init = NULL, .setup_irq_polarity = tegra186_pmc_setup_irq_polarity, .reset_sources = tegra186_reset_sources, - .num_reset_sources = 14, + .num_reset_sources = ARRAY_SIZE(tegra186_reset_sources), .reset_levels = tegra186_reset_levels, - .num_reset_levels = 3, + .num_reset_levels = ARRAY_SIZE(tegra186_reset_levels), .num_wake_events = ARRAY_SIZE(tegra186_wake_events), .wake_events = tegra186_wake_events, }; @@ -2719,6 +2812,7 @@ static int __init tegra_pmc_early_init(void) const struct of_device_id *match; struct device_node *np; struct resource regs; + unsigned int i; bool invert; mutex_init(&pmc->powergates_lock); @@ -2775,7 +2869,10 @@ static int __init tegra_pmc_early_init(void) if (pmc->soc->maybe_tz_only) pmc->tz_only = tegra_pmc_detect_tz_only(pmc); - tegra_powergate_init(pmc, np); + /* Create a bitmap of the available and valid partitions */ + for (i = 0; i < pmc->soc->num_powergates; i++) + if (pmc->soc->powergates[i]) + set_bit(i, pmc->powergates_available); /* * Invert the interrupt polarity if a PMC device tree node diff --git a/drivers/soc/ti/Kconfig b/drivers/soc/ti/Kconfig index be4570baad96..dbd6c60b81db 100644 --- a/drivers/soc/ti/Kconfig +++ b/drivers/soc/ti/Kconfig @@ -45,11 +45,12 @@ config KEYSTONE_NAVIGATOR_DMA config AMX3_PM tristate "AMx3 Power Management" depends on SOC_AM33XX || SOC_AM43XX - depends on WKUP_M3_IPC && TI_EMIF_SRAM && SRAM + depends on WKUP_M3_IPC && TI_EMIF_SRAM && SRAM && RTC_DRV_OMAP help Enable power management on AM335x and AM437x. Required for suspend to mem and standby states on both AM335x and AM437x platforms and for deeper cpuidle - c-states on AM335x. + c-states on AM335x. Also required for rtc and ddr in self-refresh low + power mode on AM437x platforms. config WKUP_M3_IPC tristate "TI AMx3 Wkup-M3 IPC Driver" @@ -73,4 +74,10 @@ config TI_SCI_PM_DOMAINS called ti_sci_pm_domains. Note this is needed early in boot before rootfs may be available. +config TI_SCI_INTA_MSI_DOMAIN + bool + select GENERIC_MSI_IRQ_DOMAIN + help + Driver to enable Interrupt Aggregator specific MSI Domain. + endif # SOC_TI diff --git a/drivers/soc/ti/Makefile b/drivers/soc/ti/Makefile index a22edc0b258a..b3868d392d4f 100644 --- a/drivers/soc/ti/Makefile +++ b/drivers/soc/ti/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_KEYSTONE_NAVIGATOR_DMA) += knav_dma.o obj-$(CONFIG_AMX3_PM) += pm33xx.o obj-$(CONFIG_WKUP_M3_IPC) += wkup_m3_ipc.o obj-$(CONFIG_TI_SCI_PM_DOMAINS) += ti_sci_pm_domains.o +obj-$(CONFIG_TI_SCI_INTA_MSI_DOMAIN) += ti_sci_inta_msi.o diff --git a/drivers/soc/ti/pm33xx.c b/drivers/soc/ti/pm33xx.c index d0dab323651f..fc5802ccb1c0 100644 --- a/drivers/soc/ti/pm33xx.c +++ b/drivers/soc/ti/pm33xx.c @@ -6,6 +6,7 @@ * Vaibhav Bedia, Dave Gerlach */ +#include <linux/clk.h> #include <linux/cpu.h> #include <linux/err.h> #include <linux/genalloc.h> @@ -13,9 +14,12 @@ #include <linux/init.h> #include <linux/io.h> #include <linux/module.h> +#include <linux/nvmem-consumer.h> #include <linux/of.h> #include <linux/platform_data/pm33xx.h> #include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/rtc/rtc-omap.h> #include <linux/sizes.h> #include <linux/sram.h> #include <linux/suspend.h> @@ -29,33 +33,162 @@ #define AMX3_PM_SRAM_SYMBOL_OFFSET(sym) ((unsigned long)(sym) - \ (unsigned long)pm_sram->do_wfi) +#define RTC_SCRATCH_RESUME_REG 0 +#define RTC_SCRATCH_MAGIC_REG 1 +#define RTC_REG_BOOT_MAGIC 0x8cd0 /* RTC */ +#define GIC_INT_SET_PENDING_BASE 0x200 +#define AM43XX_GIC_DIST_BASE 0x48241000 + +static u32 rtc_magic_val; + static int (*am33xx_do_wfi_sram)(unsigned long unused); static phys_addr_t am33xx_do_wfi_sram_phys; static struct gen_pool *sram_pool, *sram_pool_data; static unsigned long ocmcram_location, ocmcram_location_data; +static struct rtc_device *omap_rtc; +static void __iomem *gic_dist_base; + static struct am33xx_pm_platform_data *pm_ops; static struct am33xx_pm_sram_addr *pm_sram; static struct device *pm33xx_dev; static struct wkup_m3_ipc *m3_ipc; +#ifdef CONFIG_SUSPEND +static int rtc_only_idle; +static int retrigger_irq; static unsigned long suspend_wfi_flags; +static struct wkup_m3_wakeup_src wakeup_src = {.irq_nr = 0, + .src = "Unknown", +}; + +static struct wkup_m3_wakeup_src rtc_alarm_wakeup = { + .irq_nr = 108, .src = "RTC Alarm", +}; + +static struct wkup_m3_wakeup_src rtc_ext_wakeup = { + .irq_nr = 0, .src = "Ext wakeup", +}; +#endif + static u32 sram_suspend_address(unsigned long addr) { return ((unsigned long)am33xx_do_wfi_sram + AMX3_PM_SRAM_SYMBOL_OFFSET(addr)); } +static int am33xx_push_sram_idle(void) +{ + struct am33xx_pm_ro_sram_data ro_sram_data; + int ret; + u32 table_addr, ro_data_addr; + void *copy_addr; + + ro_sram_data.amx3_pm_sram_data_virt = ocmcram_location_data; + ro_sram_data.amx3_pm_sram_data_phys = + gen_pool_virt_to_phys(sram_pool_data, ocmcram_location_data); + ro_sram_data.rtc_base_virt = pm_ops->get_rtc_base_addr(); + + /* Save physical address to calculate resume offset during pm init */ + am33xx_do_wfi_sram_phys = gen_pool_virt_to_phys(sram_pool, + ocmcram_location); + + am33xx_do_wfi_sram = sram_exec_copy(sram_pool, (void *)ocmcram_location, + pm_sram->do_wfi, + *pm_sram->do_wfi_sz); + if (!am33xx_do_wfi_sram) { + dev_err(pm33xx_dev, + "PM: %s: am33xx_do_wfi copy to sram failed\n", + __func__); + return -ENODEV; + } + + table_addr = + sram_suspend_address((unsigned long)pm_sram->emif_sram_table); + ret = ti_emif_copy_pm_function_table(sram_pool, (void *)table_addr); + if (ret) { + dev_dbg(pm33xx_dev, + "PM: %s: EMIF function copy failed\n", __func__); + return -EPROBE_DEFER; + } + + ro_data_addr = + sram_suspend_address((unsigned long)pm_sram->ro_sram_data); + copy_addr = sram_exec_copy(sram_pool, (void *)ro_data_addr, + &ro_sram_data, + sizeof(ro_sram_data)); + if (!copy_addr) { + dev_err(pm33xx_dev, + "PM: %s: ro_sram_data copy to sram failed\n", + __func__); + return -ENODEV; + } + + return 0; +} + +static int __init am43xx_map_gic(void) +{ + gic_dist_base = ioremap(AM43XX_GIC_DIST_BASE, SZ_4K); + + if (!gic_dist_base) + return -ENOMEM; + + return 0; +} + #ifdef CONFIG_SUSPEND +struct wkup_m3_wakeup_src rtc_wake_src(void) +{ + u32 i; + + i = __raw_readl(pm_ops->get_rtc_base_addr() + 0x44) & 0x40; + + if (i) { + retrigger_irq = rtc_alarm_wakeup.irq_nr; + return rtc_alarm_wakeup; + } + + retrigger_irq = rtc_ext_wakeup.irq_nr; + + return rtc_ext_wakeup; +} + +int am33xx_rtc_only_idle(unsigned long wfi_flags) +{ + omap_rtc_power_off_program(&omap_rtc->dev); + am33xx_do_wfi_sram(wfi_flags); + return 0; +} + static int am33xx_pm_suspend(suspend_state_t suspend_state) { int i, ret = 0; - ret = pm_ops->soc_suspend((unsigned long)suspend_state, - am33xx_do_wfi_sram, suspend_wfi_flags); + if (suspend_state == PM_SUSPEND_MEM && + pm_ops->check_off_mode_enable()) { + pm_ops->prepare_rtc_suspend(); + pm_ops->save_context(); + suspend_wfi_flags |= WFI_FLAG_RTC_ONLY; + clk_save_context(); + ret = pm_ops->soc_suspend(suspend_state, am33xx_rtc_only_idle, + suspend_wfi_flags); + + suspend_wfi_flags &= ~WFI_FLAG_RTC_ONLY; + + if (!ret) { + clk_restore_context(); + pm_ops->restore_context(); + m3_ipc->ops->set_rtc_only(m3_ipc); + am33xx_push_sram_idle(); + } + } else { + ret = pm_ops->soc_suspend(suspend_state, am33xx_do_wfi_sram, + suspend_wfi_flags); + } if (ret) { dev_err(pm33xx_dev, "PM: Kernel suspend failure\n"); @@ -77,8 +210,20 @@ static int am33xx_pm_suspend(suspend_state_t suspend_state) "PM: CM3 returned unknown result = %d\n", i); ret = -1; } + + /* print the wakeup reason */ + if (rtc_only_idle) { + wakeup_src = rtc_wake_src(); + pr_info("PM: Wakeup source %s\n", wakeup_src.src); + } else { + pr_info("PM: Wakeup source %s\n", + m3_ipc->ops->request_wake_src(m3_ipc)); + } } + if (suspend_state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable()) + pm_ops->prepare_rtc_resume(); + return ret; } @@ -101,6 +246,18 @@ static int am33xx_pm_enter(suspend_state_t suspend_state) static int am33xx_pm_begin(suspend_state_t state) { int ret = -EINVAL; + struct nvmem_device *nvmem; + + if (state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable()) { + nvmem = devm_nvmem_device_get(&omap_rtc->dev, + "omap_rtc_scratch0"); + if (nvmem) + nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, 4, + (void *)&rtc_magic_val); + rtc_only_idle = 1; + } else { + rtc_only_idle = 0; + } switch (state) { case PM_SUSPEND_MEM: @@ -116,7 +273,28 @@ static int am33xx_pm_begin(suspend_state_t state) static void am33xx_pm_end(void) { + u32 val = 0; + struct nvmem_device *nvmem; + + nvmem = devm_nvmem_device_get(&omap_rtc->dev, "omap_rtc_scratch0"); m3_ipc->ops->finish_low_power(m3_ipc); + if (rtc_only_idle) { + if (retrigger_irq) + /* + * 32 bits of Interrupt Set-Pending correspond to 32 + * 32 interrupts. Compute the bit offset of the + * Interrupt and set that particular bit + * Compute the register offset by dividing interrupt + * number by 32 and mutiplying by 4 + */ + writel_relaxed(1 << (retrigger_irq & 31), + gic_dist_base + GIC_INT_SET_PENDING_BASE + + retrigger_irq / 32 * 4); + nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, 4, + (void *)&val); + } + + rtc_only_idle = 0; } static int am33xx_pm_valid(suspend_state_t state) @@ -219,51 +397,37 @@ mpu_put_node: return ret; } -static int am33xx_push_sram_idle(void) +static int am33xx_pm_rtc_setup(void) { - struct am33xx_pm_ro_sram_data ro_sram_data; - int ret; - u32 table_addr, ro_data_addr; - void *copy_addr; - - ro_sram_data.amx3_pm_sram_data_virt = ocmcram_location_data; - ro_sram_data.amx3_pm_sram_data_phys = - gen_pool_virt_to_phys(sram_pool_data, ocmcram_location_data); - ro_sram_data.rtc_base_virt = pm_ops->get_rtc_base_addr(); + struct device_node *np; + unsigned long val = 0; + struct nvmem_device *nvmem; - /* Save physical address to calculate resume offset during pm init */ - am33xx_do_wfi_sram_phys = gen_pool_virt_to_phys(sram_pool, - ocmcram_location); + np = of_find_node_by_name(NULL, "rtc"); - am33xx_do_wfi_sram = sram_exec_copy(sram_pool, (void *)ocmcram_location, - pm_sram->do_wfi, - *pm_sram->do_wfi_sz); - if (!am33xx_do_wfi_sram) { - dev_err(pm33xx_dev, - "PM: %s: am33xx_do_wfi copy to sram failed\n", - __func__); - return -ENODEV; - } - - table_addr = - sram_suspend_address((unsigned long)pm_sram->emif_sram_table); - ret = ti_emif_copy_pm_function_table(sram_pool, (void *)table_addr); - if (ret) { - dev_dbg(pm33xx_dev, - "PM: %s: EMIF function copy failed\n", __func__); - return -EPROBE_DEFER; - } + if (of_device_is_available(np)) { + omap_rtc = rtc_class_open("rtc0"); + if (!omap_rtc) { + pr_warn("PM: rtc0 not available"); + return -EPROBE_DEFER; + } - ro_data_addr = - sram_suspend_address((unsigned long)pm_sram->ro_sram_data); - copy_addr = sram_exec_copy(sram_pool, (void *)ro_data_addr, - &ro_sram_data, - sizeof(ro_sram_data)); - if (!copy_addr) { - dev_err(pm33xx_dev, - "PM: %s: ro_sram_data copy to sram failed\n", - __func__); - return -ENODEV; + nvmem = devm_nvmem_device_get(&omap_rtc->dev, + "omap_rtc_scratch0"); + if (nvmem) { + nvmem_device_read(nvmem, RTC_SCRATCH_MAGIC_REG * 4, + 4, (void *)&rtc_magic_val); + if ((rtc_magic_val & 0xffff) != RTC_REG_BOOT_MAGIC) + pr_warn("PM: bootloader does not support rtc-only!\n"); + + nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, + 4, (void *)&val); + val = pm_sram->resume_address; + nvmem_device_write(nvmem, RTC_SCRATCH_RESUME_REG * 4, + 4, (void *)&val); + } + } else { + pr_warn("PM: no-rtc available, rtc-only mode disabled.\n"); } return 0; @@ -284,34 +448,42 @@ static int am33xx_pm_probe(struct platform_device *pdev) return -ENODEV; } + ret = am43xx_map_gic(); + if (ret) { + pr_err("PM: Could not ioremap GIC base\n"); + return ret; + } + pm_sram = pm_ops->get_sram_addrs(); if (!pm_sram) { dev_err(dev, "PM: Cannot get PM asm function addresses!!\n"); return -ENODEV; } + m3_ipc = wkup_m3_ipc_get(); + if (!m3_ipc) { + pr_err("PM: Cannot get wkup_m3_ipc handle\n"); + return -EPROBE_DEFER; + } + pm33xx_dev = dev; ret = am33xx_pm_alloc_sram(); if (ret) return ret; - ret = am33xx_push_sram_idle(); + ret = am33xx_pm_rtc_setup(); if (ret) goto err_free_sram; - m3_ipc = wkup_m3_ipc_get(); - if (!m3_ipc) { - dev_dbg(dev, "PM: Cannot get wkup_m3_ipc handle\n"); - ret = -EPROBE_DEFER; + ret = am33xx_push_sram_idle(); + if (ret) goto err_free_sram; - } am33xx_pm_set_ipc_ops(); #ifdef CONFIG_SUSPEND suspend_set_ops(&am33xx_pm_ops); -#endif /* CONFIG_SUSPEND */ /* * For a system suspend we must flush the caches, we want @@ -323,6 +495,7 @@ static int am33xx_pm_probe(struct platform_device *pdev) suspend_wfi_flags |= WFI_FLAG_SELF_REFRESH; suspend_wfi_flags |= WFI_FLAG_SAVE_EMIF; suspend_wfi_flags |= WFI_FLAG_WAKE_M3; +#endif /* CONFIG_SUSPEND */ ret = pm_ops->init(); if (ret) { diff --git a/drivers/soc/ti/ti_sci_inta_msi.c b/drivers/soc/ti/ti_sci_inta_msi.c new file mode 100644 index 000000000000..0eb9462f609e --- /dev/null +++ b/drivers/soc/ti/ti_sci_inta_msi.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments' K3 Interrupt Aggregator MSI bus + * + * Copyright (C) 2018-2019 Texas Instruments Incorporated - http://www.ti.com/ + * Lokesh Vutla <lokeshvutla@ti.com> + */ + +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/msi.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/soc/ti/ti_sci_inta_msi.h> +#include <linux/soc/ti/ti_sci_protocol.h> + +static void ti_sci_inta_msi_write_msg(struct irq_data *data, + struct msi_msg *msg) +{ + /* Nothing to do */ +} + +static void ti_sci_inta_msi_compose_msi_msg(struct irq_data *data, + struct msi_msg *msg) +{ + /* Nothing to do */ +} + +static void ti_sci_inta_msi_update_chip_ops(struct msi_domain_info *info) +{ + struct irq_chip *chip = info->chip; + + if (WARN_ON(!chip)) + return; + + chip->irq_request_resources = irq_chip_request_resources_parent; + chip->irq_release_resources = irq_chip_release_resources_parent; + chip->irq_compose_msi_msg = ti_sci_inta_msi_compose_msi_msg; + chip->irq_write_msi_msg = ti_sci_inta_msi_write_msg; + chip->irq_set_type = irq_chip_set_type_parent; + chip->irq_unmask = irq_chip_unmask_parent; + chip->irq_mask = irq_chip_mask_parent; + chip->irq_ack = irq_chip_ack_parent; +} + +struct irq_domain *ti_sci_inta_msi_create_irq_domain(struct fwnode_handle *fwnode, + struct msi_domain_info *info, + struct irq_domain *parent) +{ + struct irq_domain *domain; + + ti_sci_inta_msi_update_chip_ops(info); + + domain = msi_create_irq_domain(fwnode, info, parent); + if (domain) + irq_domain_update_bus_token(domain, DOMAIN_BUS_TI_SCI_INTA_MSI); + + return domain; +} +EXPORT_SYMBOL_GPL(ti_sci_inta_msi_create_irq_domain); + +static void ti_sci_inta_msi_free_descs(struct device *dev) +{ + struct msi_desc *desc, *tmp; + + list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) { + list_del(&desc->list); + free_msi_entry(desc); + } +} + +static int ti_sci_inta_msi_alloc_descs(struct device *dev, + struct ti_sci_resource *res) +{ + struct msi_desc *msi_desc; + int set, i, count = 0; + + for (set = 0; set < res->sets; set++) { + for (i = 0; i < res->desc[set].num; i++) { + msi_desc = alloc_msi_entry(dev, 1, NULL); + if (!msi_desc) { + ti_sci_inta_msi_free_descs(dev); + return -ENOMEM; + } + + msi_desc->inta.dev_index = res->desc[set].start + i; + INIT_LIST_HEAD(&msi_desc->list); + list_add_tail(&msi_desc->list, dev_to_msi_list(dev)); + count++; + } + } + + return count; +} + +int ti_sci_inta_msi_domain_alloc_irqs(struct device *dev, + struct ti_sci_resource *res) +{ + struct platform_device *pdev = to_platform_device(dev); + struct irq_domain *msi_domain; + int ret, nvec; + + msi_domain = dev_get_msi_domain(dev); + if (!msi_domain) + return -EINVAL; + + if (pdev->id < 0) + return -ENODEV; + + nvec = ti_sci_inta_msi_alloc_descs(dev, res); + if (nvec <= 0) + return nvec; + + ret = msi_domain_alloc_irqs(msi_domain, dev, nvec); + if (ret) { + dev_err(dev, "Failed to allocate IRQs %d\n", ret); + goto cleanup; + } + + return 0; + +cleanup: + ti_sci_inta_msi_free_descs(&pdev->dev); + return ret; +} +EXPORT_SYMBOL_GPL(ti_sci_inta_msi_domain_alloc_irqs); + +void ti_sci_inta_msi_domain_free_irqs(struct device *dev) +{ + msi_domain_free_irqs(dev->msi_domain, dev); + ti_sci_inta_msi_free_descs(dev); +} +EXPORT_SYMBOL_GPL(ti_sci_inta_msi_domain_free_irqs); + +unsigned int ti_sci_inta_msi_get_virq(struct device *dev, u32 dev_index) +{ + struct msi_desc *desc; + + for_each_msi_entry(desc, dev) + if (desc->inta.dev_index == dev_index) + return desc->irq; + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(ti_sci_inta_msi_get_virq); diff --git a/drivers/soc/xilinx/zynqmp_pm_domains.c b/drivers/soc/xilinx/zynqmp_pm_domains.c index 354d256e6e00..600f57cf0c2e 100644 --- a/drivers/soc/xilinx/zynqmp_pm_domains.c +++ b/drivers/soc/xilinx/zynqmp_pm_domains.c @@ -23,6 +23,8 @@ /* Flag stating if PM nodes mapped to the PM domain has been requested */ #define ZYNQMP_PM_DOMAIN_REQUESTED BIT(0) +static const struct zynqmp_eemi_ops *eemi_ops; + /** * struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain * @gpd: Generic power domain @@ -71,9 +73,8 @@ static int zynqmp_gpd_power_on(struct generic_pm_domain *domain) { int ret; struct zynqmp_pm_domain *pd; - const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); - if (!eemi_ops || !eemi_ops->set_requirement) + if (!eemi_ops->set_requirement) return -ENXIO; pd = container_of(domain, struct zynqmp_pm_domain, gpd); @@ -107,9 +108,8 @@ static int zynqmp_gpd_power_off(struct generic_pm_domain *domain) struct zynqmp_pm_domain *pd; u32 capabilities = 0; bool may_wakeup; - const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); - if (!eemi_ops || !eemi_ops->set_requirement) + if (!eemi_ops->set_requirement) return -ENXIO; pd = container_of(domain, struct zynqmp_pm_domain, gpd); @@ -160,9 +160,8 @@ static int zynqmp_gpd_attach_dev(struct generic_pm_domain *domain, { int ret; struct zynqmp_pm_domain *pd; - const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); - if (!eemi_ops || !eemi_ops->request_node) + if (!eemi_ops->request_node) return -ENXIO; pd = container_of(domain, struct zynqmp_pm_domain, gpd); @@ -197,9 +196,8 @@ static void zynqmp_gpd_detach_dev(struct generic_pm_domain *domain, { int ret; struct zynqmp_pm_domain *pd; - const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); - if (!eemi_ops || !eemi_ops->release_node) + if (!eemi_ops->release_node) return; pd = container_of(domain, struct zynqmp_pm_domain, gpd); @@ -266,6 +264,10 @@ static int zynqmp_gpd_probe(struct platform_device *pdev) struct zynqmp_pm_domain *pd; struct device *dev = &pdev->dev; + eemi_ops = zynqmp_pm_get_eemi_ops(); + if (IS_ERR(eemi_ops)) + return PTR_ERR(eemi_ops); + pd = devm_kcalloc(dev, ZYNQMP_NUM_DOMAINS, sizeof(*pd), GFP_KERNEL); if (!pd) return -ENOMEM; diff --git a/drivers/soc/xilinx/zynqmp_power.c b/drivers/soc/xilinx/zynqmp_power.c index 771cb59b9d22..1b9d14411a15 100644 --- a/drivers/soc/xilinx/zynqmp_power.c +++ b/drivers/soc/xilinx/zynqmp_power.c @@ -31,6 +31,7 @@ static const char *const suspend_modes[] = { }; static enum pm_suspend_mode suspend_mode = PM_SUSPEND_MODE_STD; +static const struct zynqmp_eemi_ops *eemi_ops; enum pm_api_cb_id { PM_INIT_SUSPEND_CB = 30, @@ -92,9 +93,8 @@ static ssize_t suspend_mode_store(struct device *dev, const char *buf, size_t count) { int md, ret = -EINVAL; - const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); - if (!eemi_ops || !eemi_ops->set_suspend_mode) + if (!eemi_ops->set_suspend_mode) return ret; for (md = PM_SUSPEND_MODE_FIRST; md < ARRAY_SIZE(suspend_modes); md++) @@ -120,9 +120,11 @@ static int zynqmp_pm_probe(struct platform_device *pdev) int ret, irq; u32 pm_api_version; - const struct zynqmp_eemi_ops *eemi_ops = zynqmp_pm_get_eemi_ops(); + eemi_ops = zynqmp_pm_get_eemi_ops(); + if (IS_ERR(eemi_ops)) + return PTR_ERR(eemi_ops); - if (!eemi_ops || !eemi_ops->get_api_version || !eemi_ops->init_finalize) + if (!eemi_ops->get_api_version || !eemi_ops->init_finalize) return -ENXIO; eemi_ops->init_finalize(); |