diff options
Diffstat (limited to 'drivers')
219 files changed, 9410 insertions, 4879 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 6dc177b..d1c0b60 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -33,7 +33,6 @@ config HISILICON_LPC bool "Support for ISA I/O space on HiSilicon Hip06/7" depends on ARM64 && (ARCH_HISI || COMPILE_TEST) select INDIRECT_PIO - select MFD_CORE if ACPI help Driver to enable I/O access to devices attached to the Low Pin Count bus on the HiSilicon Hip06/7 SoC. diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c index 443e4c3..b8184a9 100644 --- a/drivers/bus/arm-cci.c +++ b/drivers/bus/arm-cci.c @@ -371,8 +371,6 @@ asmlinkage void __naked cci_enable_port_for_self(void) [sizeof_struct_cpu_port] "i" (sizeof(struct cpu_port)), [sizeof_struct_ace_port] "i" (sizeof(struct cci_ace_port)), [offsetof_port_phys] "i" (offsetof(struct cci_ace_port, phys)) ); - - unreachable(); } /** diff --git a/drivers/bus/hisi_lpc.c b/drivers/bus/hisi_lpc.c index 2d4611e..d5f8545 100644 --- a/drivers/bus/hisi_lpc.c +++ b/drivers/bus/hisi_lpc.c @@ -11,12 +11,12 @@ #include <linux/delay.h> #include <linux/io.h> #include <linux/logic_pio.h> -#include <linux/mfd/core.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_platform.h> #include <linux/pci.h> +#include <linux/serial_8250.h> #include <linux/slab.h> #define DRV_NAME "hisi-lpc" @@ -341,15 +341,6 @@ static const struct logic_pio_host_ops hisi_lpc_ops = { }; #ifdef CONFIG_ACPI -#define MFD_CHILD_NAME_PREFIX DRV_NAME"-" -#define MFD_CHILD_NAME_LEN (ACPI_ID_LEN + sizeof(MFD_CHILD_NAME_PREFIX) - 1) - -struct hisi_lpc_mfd_cell { - struct mfd_cell_acpi_match acpi_match; - char name[MFD_CHILD_NAME_LEN]; - char pnpid[ACPI_ID_LEN]; -}; - static int hisi_lpc_acpi_xlat_io_res(struct acpi_device *adev, struct acpi_device *host, struct resource *res) @@ -368,7 +359,7 @@ static int hisi_lpc_acpi_xlat_io_res(struct acpi_device *adev, } /* - * hisi_lpc_acpi_set_io_res - set the resources for a child's MFD + * hisi_lpc_acpi_set_io_res - set the resources for a child * @child: the device node to be updated the I/O resource * @hostdev: the device node associated with host controller * @res: double pointer to be set to the address of translated resources @@ -452,78 +443,122 @@ static int hisi_lpc_acpi_set_io_res(struct device *child, return 0; } +static int hisi_lpc_acpi_remove_subdev(struct device *dev, void *unused) +{ + platform_device_unregister(to_platform_device(dev)); + return 0; +} + +struct hisi_lpc_acpi_cell { + const char *hid; + const char *name; + void *pdata; + size_t pdata_size; +}; + /* * hisi_lpc_acpi_probe - probe children for ACPI FW * @hostdev: LPC host device pointer * * Returns 0 when successful, and a negative value for failure. * - * Scan all child devices and create a per-device MFD with - * logical PIO translated IO resources. + * Create a platform device per child, fixing up the resources + * from bus addresses to Logical PIO addresses. + * */ static int hisi_lpc_acpi_probe(struct device *hostdev) { struct acpi_device *adev = ACPI_COMPANION(hostdev); - struct hisi_lpc_mfd_cell *hisi_lpc_mfd_cells; - struct mfd_cell *mfd_cells; struct acpi_device *child; - int size, ret, count = 0, cell_num = 0; - - list_for_each_entry(child, &adev->children, node) - cell_num++; - - /* allocate the mfd cell and companion ACPI info, one per child */ - size = sizeof(*mfd_cells) + sizeof(*hisi_lpc_mfd_cells); - mfd_cells = devm_kcalloc(hostdev, cell_num, size, GFP_KERNEL); - if (!mfd_cells) - return -ENOMEM; + int ret; - hisi_lpc_mfd_cells = (struct hisi_lpc_mfd_cell *)&mfd_cells[cell_num]; /* Only consider the children of the host */ list_for_each_entry(child, &adev->children, node) { - struct mfd_cell *mfd_cell = &mfd_cells[count]; - struct hisi_lpc_mfd_cell *hisi_lpc_mfd_cell = - &hisi_lpc_mfd_cells[count]; - struct mfd_cell_acpi_match *acpi_match = - &hisi_lpc_mfd_cell->acpi_match; - char *name = hisi_lpc_mfd_cell[count].name; - char *pnpid = hisi_lpc_mfd_cell[count].pnpid; - struct mfd_cell_acpi_match match = { - .pnpid = pnpid, + const char *hid = acpi_device_hid(child); + const struct hisi_lpc_acpi_cell *cell; + struct platform_device *pdev; + const struct resource *res; + bool found = false; + int num_res; + + ret = hisi_lpc_acpi_set_io_res(&child->dev, &adev->dev, &res, + &num_res); + if (ret) { + dev_warn(hostdev, "set resource fail (%d)\n", ret); + goto fail; + } + + cell = (struct hisi_lpc_acpi_cell []){ + /* ipmi */ + { + .hid = "IPI0001", + .name = "hisi-lpc-ipmi", + }, + /* 8250-compatible uart */ + { + .hid = "HISI1031", + .name = "serial8250", + .pdata = (struct plat_serial8250_port []) { + { + .iobase = res->start, + .uartclk = 1843200, + .iotype = UPIO_PORT, + .flags = UPF_BOOT_AUTOCONF, + }, + {} + }, + .pdata_size = 2 * + sizeof(struct plat_serial8250_port), + }, + {} }; - /* - * For any instances of this host controller (Hip06 and Hip07 - * are the only chipsets), we would not have multiple slaves - * with the same HID. And in any system we would have just one - * controller active. So don't worrry about MFD name clashes. - */ - snprintf(name, MFD_CHILD_NAME_LEN, MFD_CHILD_NAME_PREFIX"%s", - acpi_device_hid(child)); - snprintf(pnpid, ACPI_ID_LEN, "%s", acpi_device_hid(child)); - - memcpy(acpi_match, &match, sizeof(*acpi_match)); - mfd_cell->name = name; - mfd_cell->acpi_match = acpi_match; - - ret = hisi_lpc_acpi_set_io_res(&child->dev, &adev->dev, - &mfd_cell->resources, - &mfd_cell->num_resources); - if (ret) { - dev_warn(&child->dev, "set resource fail (%d)\n", ret); - return ret; + for (; cell && cell->name; cell++) { + if (!strcmp(cell->hid, hid)) { + found = true; + break; + } } - count++; - } - ret = mfd_add_devices(hostdev, PLATFORM_DEVID_NONE, - mfd_cells, cell_num, NULL, 0, NULL); - if (ret) { - dev_err(hostdev, "failed to add mfd cells (%d)\n", ret); - return ret; + if (!found) { + dev_warn(hostdev, + "could not find cell for child device (%s)\n", + hid); + ret = -ENODEV; + goto fail; + } + + pdev = platform_device_alloc(cell->name, PLATFORM_DEVID_AUTO); + if (!pdev) { + ret = -ENOMEM; + goto fail; + } + + pdev->dev.parent = hostdev; + ACPI_COMPANION_SET(&pdev->dev, child); + + ret = platform_device_add_resources(pdev, res, num_res); + if (ret) + goto fail; + + ret = platform_device_add_data(pdev, cell->pdata, + cell->pdata_size); + if (ret) + goto fail; + + ret = platform_device_add(pdev); + if (ret) + goto fail; + + acpi_device_set_enumerated(child); } return 0; + +fail: + device_for_each_child(hostdev, NULL, + hisi_lpc_acpi_remove_subdev); + return ret; } static const struct acpi_device_id hisi_lpc_acpi_match[] = { diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c index 7cd2fd0..1cc2962 100644 --- a/drivers/bus/ti-sysc.c +++ b/drivers/bus/ti-sysc.c @@ -19,6 +19,7 @@ #include <linux/platform_device.h> #include <linux/pm_domain.h> #include <linux/pm_runtime.h> +#include <linux/reset.h> #include <linux/of_address.h> #include <linux/of_platform.h> #include <linux/slab.h> @@ -32,10 +33,18 @@ static const char * const reg_names[] = { "rev", "sysc", "syss", }; enum sysc_clocks { SYSC_FCK, SYSC_ICK, + SYSC_OPTFCK0, + SYSC_OPTFCK1, + SYSC_OPTFCK2, + SYSC_OPTFCK3, + SYSC_OPTFCK4, + SYSC_OPTFCK5, + SYSC_OPTFCK6, + SYSC_OPTFCK7, SYSC_MAX_CLOCKS, }; -static const char * const clock_names[] = { "fck", "ick", }; +static const char * const clock_names[SYSC_ICK + 1] = { "fck", "ick", }; #define SYSC_IDLEMODE_MASK 3 #define SYSC_CLOCKACTIVITY_MASK 3 @@ -48,6 +57,8 @@ static const char * const clock_names[] = { "fck", "ick", }; * @module_va: virtual address of the interconnect target module * @offsets: register offsets from module base * @clocks: clocks used by the interconnect target module + * @clock_roles: clock role names for the found clocks + * @nr_clocks: number of clocks used by the interconnect target module * @legacy_mode: configured for legacy mode if set * @cap: interconnect target module capabilities * @cfg: interconnect target module configuration @@ -61,7 +72,10 @@ struct sysc { u32 module_size; void __iomem *module_va; int offsets[SYSC_MAX_REGS]; - struct clk *clocks[SYSC_MAX_CLOCKS]; + struct clk **clocks; + const char **clock_roles; + int nr_clocks; + struct reset_control *rsts; const char *legacy_mode; const struct sysc_capabilities *cap; struct sysc_config cfg; @@ -88,6 +102,11 @@ static u32 sysc_read(struct sysc *ddata, int offset) return readl_relaxed(ddata->module_va + offset); } +static bool sysc_opt_clks_needed(struct sysc *ddata) +{ + return !!(ddata->cfg.quirks & SYSC_QUIRK_OPT_CLKS_NEEDED); +} + static u32 sysc_read_revision(struct sysc *ddata) { int offset = ddata->offsets[SYSC_REVISION]; @@ -98,21 +117,28 @@ static u32 sysc_read_revision(struct sysc *ddata) return sysc_read(ddata, offset); } -static int sysc_get_one_clock(struct sysc *ddata, - enum sysc_clocks index) +static int sysc_get_one_clock(struct sysc *ddata, const char *name) { - const char *name; - int error; + int error, i, index = -ENODEV; + + if (!strncmp(clock_names[SYSC_FCK], name, 3)) + index = SYSC_FCK; + else if (!strncmp(clock_names[SYSC_ICK], name, 3)) + index = SYSC_ICK; + + if (index < 0) { + for (i = SYSC_OPTFCK0; i < SYSC_MAX_CLOCKS; i++) { + if (!ddata->clocks[i]) { + index = i; + break; + } + } + } - switch (index) { - case SYSC_FCK: - break; - case SYSC_ICK: - break; - default: - return -EINVAL; + if (index < 0) { + dev_err(ddata->dev, "clock %s not added\n", name); + return index; } - name = clock_names[index]; ddata->clocks[index] = devm_clk_get(ddata->dev, name); if (IS_ERR(ddata->clocks[index])) { @@ -138,10 +164,50 @@ static int sysc_get_one_clock(struct sysc *ddata, static int sysc_get_clocks(struct sysc *ddata) { - int i, error; + struct device_node *np = ddata->dev->of_node; + struct property *prop; + const char *name; + int nr_fck = 0, nr_ick = 0, i, error = 0; - for (i = 0; i < SYSC_MAX_CLOCKS; i++) { - error = sysc_get_one_clock(ddata, i); + ddata->clock_roles = devm_kzalloc(ddata->dev, + sizeof(*ddata->clock_roles) * + SYSC_MAX_CLOCKS, + GFP_KERNEL); + if (!ddata->clock_roles) + return -ENOMEM; + + of_property_for_each_string(np, "clock-names", prop, name) { + if (!strncmp(clock_names[SYSC_FCK], name, 3)) + nr_fck++; + if (!strncmp(clock_names[SYSC_ICK], name, 3)) + nr_ick++; + ddata->clock_roles[ddata->nr_clocks] = name; + ddata->nr_clocks++; + } + + if (ddata->nr_clocks < 1) + return 0; + + if (ddata->nr_clocks > SYSC_MAX_CLOCKS) { + dev_err(ddata->dev, "too many clocks for %pOF\n", np); + + return -EINVAL; + } + + if (nr_fck > 1 || nr_ick > 1) { + dev_err(ddata->dev, "max one fck and ick for %pOF\n", np); + + return -EINVAL; + } + + ddata->clocks = devm_kzalloc(ddata->dev, + sizeof(*ddata->clocks) * ddata->nr_clocks, + GFP_KERNEL); + if (!ddata->clocks) + return -ENOMEM; + + for (i = 0; i < ddata->nr_clocks; i++) { + error = sysc_get_one_clock(ddata, ddata->clock_roles[i]); if (error && error != -ENOENT) return error; } @@ -150,6 +216,42 @@ static int sysc_get_clocks(struct sysc *ddata) } /** + * sysc_init_resets - reset module on init + * @ddata: device driver data + * + * A module can have both OCP softreset control and external rstctrl. + * If more complicated rstctrl resets are needed, please handle these + * directly from the child device driver and map only the module reset + * for the parent interconnect target module device. + * + * Automatic reset of the module on init can be skipped with the + * "ti,no-reset-on-init" device tree property. + */ +static int sysc_init_resets(struct sysc *ddata) +{ + int error; + + ddata->rsts = + devm_reset_control_array_get_optional_exclusive(ddata->dev); + if (IS_ERR(ddata->rsts)) + return PTR_ERR(ddata->rsts); + + if (ddata->cfg.quirks & SYSC_QUIRK_NO_RESET_ON_INIT) + goto deassert; + + error = reset_control_assert(ddata->rsts); + if (error) + return error; + +deassert: + error = reset_control_deassert(ddata->rsts); + if (error) + return error; + + return 0; +} + +/** * sysc_parse_and_check_child_range - parses module IO region from ranges * @ddata: device driver data * @@ -533,9 +635,13 @@ static int __maybe_unused sysc_runtime_suspend(struct device *dev) goto idled; } - for (i = 0; i < SYSC_MAX_CLOCKS; i++) { + for (i = 0; i < ddata->nr_clocks; i++) { if (IS_ERR_OR_NULL(ddata->clocks[i])) continue; + + if (i >= SYSC_OPTFCK0 && !sysc_opt_clks_needed(ddata)) + break; + clk_disable(ddata->clocks[i]); } @@ -572,9 +678,13 @@ static int __maybe_unused sysc_runtime_resume(struct device *dev) goto awake; } - for (i = 0; i < SYSC_MAX_CLOCKS; i++) { + for (i = 0; i < ddata->nr_clocks; i++) { if (IS_ERR_OR_NULL(ddata->clocks[i])) continue; + + if (i >= SYSC_OPTFCK0 && !sysc_opt_clks_needed(ddata)) + break; + error = clk_enable(ddata->clocks[i]); if (error) return error; @@ -590,23 +700,103 @@ awake: static int sysc_suspend(struct device *dev) { struct sysc *ddata; + int error; ddata = dev_get_drvdata(dev); + if (ddata->cfg.quirks & (SYSC_QUIRK_RESOURCE_PROVIDER | + SYSC_QUIRK_LEGACY_IDLE)) + return 0; + if (!ddata->enabled) return 0; + dev_dbg(ddata->dev, "%s %s\n", __func__, + ddata->name ? ddata->name : ""); + + error = pm_runtime_put_sync_suspend(dev); + if (error < 0) { + dev_warn(ddata->dev, "%s not idle %i %s\n", + __func__, error, + ddata->name ? ddata->name : ""); + + return 0; + } + ddata->needs_resume = true; - return sysc_runtime_suspend(dev); + return 0; } static int sysc_resume(struct device *dev) { struct sysc *ddata; + int error; + + ddata = dev_get_drvdata(dev); + + if (ddata->cfg.quirks & (SYSC_QUIRK_RESOURCE_PROVIDER | + SYSC_QUIRK_LEGACY_IDLE)) + return 0; + + if (ddata->needs_resume) { + dev_dbg(ddata->dev, "%s %s\n", __func__, + ddata->name ? ddata->name : ""); + + error = pm_runtime_get_sync(dev); + if (error < 0) { + dev_err(ddata->dev, "%s error %i %s\n", + __func__, error, + ddata->name ? ddata->name : ""); + + return error; + } + + ddata->needs_resume = false; + } + + return 0; +} + +static int sysc_noirq_suspend(struct device *dev) +{ + struct sysc *ddata; ddata = dev_get_drvdata(dev); + + if (ddata->cfg.quirks & SYSC_QUIRK_LEGACY_IDLE) + return 0; + + if (!(ddata->cfg.quirks & SYSC_QUIRK_RESOURCE_PROVIDER)) + return 0; + + if (!ddata->enabled) + return 0; + + dev_dbg(ddata->dev, "%s %s\n", __func__, + ddata->name ? ddata->name : ""); + + ddata->needs_resume = true; + + return sysc_runtime_suspend(dev); +} + +static int sysc_noirq_resume(struct device *dev) +{ + struct sysc *ddata; + + ddata = dev_get_drvdata(dev); + + if (ddata->cfg.quirks & SYSC_QUIRK_LEGACY_IDLE) + return 0; + + if (!(ddata->cfg.quirks & SYSC_QUIRK_RESOURCE_PROVIDER)) + return 0; + if (ddata->needs_resume) { + dev_dbg(ddata->dev, "%s %s\n", __func__, + ddata->name ? ddata->name : ""); + ddata->needs_resume = false; return sysc_runtime_resume(dev); @@ -618,6 +808,7 @@ static int sysc_resume(struct device *dev) static const struct dev_pm_ops sysc_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(sysc_suspend, sysc_resume) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(sysc_noirq_suspend, sysc_noirq_resume) SET_RUNTIME_PM_OPS(sysc_runtime_suspend, sysc_runtime_resume, NULL) @@ -649,9 +840,29 @@ struct sysc_revision_quirk { } static const struct sysc_revision_quirk sysc_revision_quirks[] = { + /* These need to use noirq_suspend */ + SYSC_QUIRK("control", 0, 0, 0x10, -1, 0x40000900, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("i2c", 0, 0, 0x10, 0x90, 0x5040000a, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("mcspi", 0, 0, 0x10, -1, 0x40300a0b, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("prcm", 0, 0, -1, -1, 0x40000100, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("ocp2scp", 0, 0, 0x10, 0x14, 0x50060005, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("padconf", 0, 0, 0x10, -1, 0x4fff0800, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("scm", 0, 0, 0x10, -1, 0x40000900, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("scrm", 0, 0, -1, -1, 0x00000010, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + SYSC_QUIRK("sdma", 0, 0, 0x2c, 0x28, 0x00010900, 0xffffffff, + SYSC_QUIRK_RESOURCE_PROVIDER), + /* These drivers need to be fixed to not use pm_runtime_irq_safe() */ SYSC_QUIRK("gpio", 0, 0, 0x10, 0x114, 0x50600801, 0xffffffff, - SYSC_QUIRK_LEGACY_IDLE), + SYSC_QUIRK_LEGACY_IDLE | SYSC_QUIRK_OPT_CLKS_IN_RESET), SYSC_QUIRK("mmu", 0, 0, 0x10, 0x14, 0x00000020, 0xffffffff, SYSC_QUIRK_LEGACY_IDLE), SYSC_QUIRK("mmu", 0, 0, 0x10, 0x14, 0x00000030, 0xffffffff, @@ -664,8 +875,40 @@ static const struct sysc_revision_quirk sysc_revision_quirks[] = { SYSC_QUIRK_LEGACY_IDLE), SYSC_QUIRK("timer", 0, 0, 0x10, 0x14, 0x00000015, 0xffffffff, SYSC_QUIRK_LEGACY_IDLE), + /* Some timers on omap4 and later */ + SYSC_QUIRK("timer", 0, 0, 0x10, -1, 0x4fff1301, 0xffffffff, + SYSC_QUIRK_LEGACY_IDLE), SYSC_QUIRK("uart", 0, 0x50, 0x54, 0x58, 0x00000052, 0xffffffff, SYSC_QUIRK_LEGACY_IDLE), + /* Uarts on omap4 and later */ + SYSC_QUIRK("uart", 0, 0x50, 0x54, 0x58, 0x50411e03, 0xffffffff, + SYSC_QUIRK_LEGACY_IDLE), + + /* These devices don't yet suspend properly without legacy setting */ + SYSC_QUIRK("sdio", 0, 0, 0x10, -1, 0x40202301, 0xffffffff, + SYSC_QUIRK_LEGACY_IDLE), + SYSC_QUIRK("wdt", 0, 0, 0x10, 0x14, 0x502a0500, 0xffffffff, + SYSC_QUIRK_LEGACY_IDLE), + SYSC_QUIRK("wdt", 0, 0, 0x10, 0x14, 0x502a0d00, 0xffffffff, + SYSC_QUIRK_LEGACY_IDLE), + +#ifdef DEBUG + SYSC_QUIRK("aess", 0, 0, 0x10, -1, 0x40000000, 0xffffffff, 0), + SYSC_QUIRK("gpu", 0, 0x1fc00, 0x1fc10, -1, 0, 0, 0), + SYSC_QUIRK("hdq1w", 0, 0, 0x14, 0x18, 0x00000006, 0xffffffff, 0), + SYSC_QUIRK("hsi", 0, 0, 0x10, 0x14, 0x50043101, 0xffffffff, 0), + SYSC_QUIRK("iss", 0, 0, 0x10, -1, 0x40000101, 0xffffffff, 0), + SYSC_QUIRK("mcasp", 0, 0, 0x4, -1, 0x44306302, 0xffffffff, 0), + SYSC_QUIRK("mcbsp", 0, -1, 0x8c, -1, 0, 0, 0), + SYSC_QUIRK("mailbox", 0, 0, 0x10, -1, 0x00000400, 0xffffffff, 0), + SYSC_QUIRK("slimbus", 0, 0, 0x10, -1, 0x40000902, 0xffffffff, 0), + SYSC_QUIRK("slimbus", 0, 0, 0x10, -1, 0x40002903, 0xffffffff, 0), + SYSC_QUIRK("spinlock", 0, 0, 0x10, -1, 0x50020000, 0xffffffff, 0), + SYSC_QUIRK("usbhstll", 0, 0, 0x10, 0x14, 0x00000004, 0xffffffff, 0), + SYSC_QUIRK("usb_host_hs", 0, 0, 0x10, 0x14, 0x50700100, 0xffffffff, 0), + SYSC_QUIRK("usb_otg_hs", 0, 0x400, 0x404, 0x408, 0x00000050, + 0xffffffff, 0), +#endif }; static void sysc_init_revision_quirks(struct sysc *ddata) @@ -716,6 +959,7 @@ static int sysc_init_module(struct sysc *ddata) return 0; } + ddata->revision = sysc_read_revision(ddata); pm_runtime_put_sync(ddata->dev); @@ -811,29 +1055,58 @@ static int sysc_init_syss_mask(struct sysc *ddata) } /* - * Many child device drivers need to have fck available to get the clock - * rate for device internal configuration. + * Many child device drivers need to have fck and opt clocks available + * to get the clock rate for device internal configuration etc. */ -static int sysc_child_add_fck(struct sysc *ddata, - struct device *child) +static int sysc_child_add_named_clock(struct sysc *ddata, + struct device *child, + const char *name) { - struct clk *fck; + struct clk *clk; struct clk_lookup *l; - const char *name = clock_names[SYSC_FCK]; + int error = 0; - if (IS_ERR_OR_NULL(ddata->clocks[SYSC_FCK])) + if (!name) return 0; - fck = clk_get(child, name); - if (!IS_ERR(fck)) { - clk_put(fck); + clk = clk_get(child, name); + if (!IS_ERR(clk)) { + clk_put(clk); return -EEXIST; } - l = clkdev_create(ddata->clocks[SYSC_FCK], name, dev_name(child)); + clk = clk_get(ddata->dev, name); + if (IS_ERR(clk)) + return -ENODEV; + + l = clkdev_create(clk, name, dev_name(child)); + if (!l) + error = -ENOMEM; + + clk_put(clk); + + return error; +} + +static int sysc_child_add_clocks(struct sysc *ddata, + struct device *child) +{ + int i, error; + + for (i = 0; i < ddata->nr_clocks; i++) { + error = sysc_child_add_named_clock(ddata, + child, + ddata->clock_roles[i]); + if (error && error != -EEXIST) { + dev_err(ddata->dev, "could not add child clock %s: %i\n", + ddata->clock_roles[i], error); + + return error; + } + } - return l ? 0 : -ENODEV; + return 0; } static struct device_type sysc_device_type = { @@ -891,18 +1164,33 @@ static int sysc_child_suspend_noirq(struct device *dev) ddata = sysc_child_to_parent(dev); + dev_dbg(ddata->dev, "%s %s\n", __func__, + ddata->name ? ddata->name : ""); + error = pm_generic_suspend_noirq(dev); - if (error) + if (error) { + dev_err(dev, "%s error at %i: %i\n", + __func__, __LINE__, error); + return error; + } if (!pm_runtime_status_suspended(dev)) { error = pm_generic_runtime_suspend(dev); - if (error) + if (error) { + dev_err(dev, "%s error at %i: %i\n", + __func__, __LINE__, error); + return error; + } error = sysc_runtime_suspend(ddata->dev); - if (error) + if (error) { + dev_err(dev, "%s error at %i: %i\n", + __func__, __LINE__, error); + return error; + } ddata->child_needs_resume = true; } @@ -917,6 +1205,9 @@ static int sysc_child_resume_noirq(struct device *dev) ddata = sysc_child_to_parent(dev); + dev_dbg(ddata->dev, "%s %s\n", __func__, + ddata->name ? ddata->name : ""); + if (ddata->child_needs_resume) { ddata->child_needs_resume = false; @@ -983,10 +1274,9 @@ static int sysc_notifier_call(struct notifier_block *nb, switch (event) { case BUS_NOTIFY_ADD_DEVICE: - error = sysc_child_add_fck(ddata, dev); - if (error && error != -EEXIST) - dev_warn(ddata->dev, "could not add %s fck: %i\n", - dev_name(dev), error); + error = sysc_child_add_clocks(ddata, dev); + if (error) + return error; sysc_legacy_idle_quirk(ddata, dev); break; default: @@ -1314,6 +1604,11 @@ static void ti_sysc_idle(struct work_struct *work) pm_runtime_put_sync(ddata->dev); } +static const struct of_device_id sysc_match_table[] = { + { .compatible = "simple-bus", }, + { /* sentinel */ }, +}; + static int sysc_probe(struct platform_device *pdev) { struct ti_sysc_platform_data *pdata = dev_get_platdata(&pdev->dev); @@ -1359,8 +1654,11 @@ static int sysc_probe(struct platform_device *pdev) if (error) goto unprepare; - pm_runtime_enable(ddata->dev); + error = sysc_init_resets(ddata); + if (error) + return error; + pm_runtime_enable(ddata->dev); error = sysc_init_module(ddata); if (error) goto unprepare; @@ -1375,8 +1673,8 @@ static int sysc_probe(struct platform_device *pdev) sysc_show_registers(ddata); ddata->dev->type = &sysc_device_type; - error = of_platform_populate(ddata->dev->of_node, - NULL, pdata ? pdata->auxdata : NULL, + error = of_platform_populate(ddata->dev->of_node, sysc_match_table, + pdata ? pdata->auxdata : NULL, ddata->dev); if (error) goto err; @@ -1391,6 +1689,9 @@ static int sysc_probe(struct platform_device *pdev) pm_runtime_put(&pdev->dev); } + if (!of_get_available_child_count(ddata->dev->of_node)) + reset_control_assert(ddata->rsts); + return 0; err: @@ -1420,6 +1721,7 @@ static int sysc_remove(struct platform_device *pdev) pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); + reset_control_assert(ddata->rsts); unprepare: sysc_unprepare(ddata); diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c index b4dbc774..50b1551 100644 --- a/drivers/cpufreq/scmi-cpufreq.c +++ b/drivers/cpufreq/scmi-cpufreq.c @@ -117,7 +117,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy) return -ENODEV; } - ret = handle->perf_ops->add_opps_to_device(handle, cpu_dev); + ret = handle->perf_ops->device_opps_add(handle, cpu_dev); if (ret) { dev_warn(cpu_dev, "failed to add opps to the device\n"); return ret; @@ -164,7 +164,7 @@ static int scmi_cpufreq_init(struct cpufreq_policy *policy) /* SCMI allows DVFS request for any domain from any CPU */ policy->dvfs_possible_from_any_cpu = true; - latency = handle->perf_ops->get_transition_latency(handle, cpu_dev); + latency = handle->perf_ops->transition_latency_get(handle, cpu_dev); if (!latency) latency = CPUFREQ_ETERNAL; diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c index 0d3806c..9dff33e 100644 --- a/drivers/firmware/arm_scmi/base.c +++ b/drivers/firmware/arm_scmi/base.c @@ -26,7 +26,7 @@ struct scmi_msg_resp_base_attributes { * scmi_base_attributes_get() - gets the implementation details * that are associated with the base protocol. * - * @handle - SCMI entity handle + * @handle: SCMI entity handle * * Return: 0 on success, else appropriate SCMI error. */ @@ -37,7 +37,7 @@ static int scmi_base_attributes_get(const struct scmi_handle *handle) struct scmi_msg_resp_base_attributes *attr_info; struct scmi_revision_info *rev = handle->version; - ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES, + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, SCMI_PROTOCOL_BASE, 0, sizeof(*attr_info), &t); if (ret) return ret; @@ -49,15 +49,16 @@ static int scmi_base_attributes_get(const struct scmi_handle *handle) rev->num_agents = attr_info->num_agents; } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); + return ret; } /** * scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string. * - * @handle - SCMI entity handle - * @sub_vendor - specify true if sub-vendor ID is needed + * @handle: SCMI entity handle + * @sub_vendor: specify true if sub-vendor ID is needed * * Return: 0 on success, else appropriate SCMI error. */ @@ -80,7 +81,7 @@ scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor) size = ARRAY_SIZE(rev->vendor_id); } - ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_BASE, 0, size, &t); + ret = scmi_xfer_get_init(handle, cmd, SCMI_PROTOCOL_BASE, 0, size, &t); if (ret) return ret; @@ -88,7 +89,8 @@ scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor) if (!ret) memcpy(vendor_id, t->rx.buf, size); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); + return ret; } @@ -97,7 +99,7 @@ scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor) * implementation 32-bit version. The format of the version number is * vendor-specific * - * @handle - SCMI entity handle + * @handle: SCMI entity handle * * Return: 0 on success, else appropriate SCMI error. */ @@ -109,7 +111,7 @@ scmi_base_implementation_version_get(const struct scmi_handle *handle) struct scmi_xfer *t; struct scmi_revision_info *rev = handle->version; - ret = scmi_one_xfer_init(handle, BASE_DISCOVER_IMPLEMENT_VERSION, + ret = scmi_xfer_get_init(handle, BASE_DISCOVER_IMPLEMENT_VERSION, SCMI_PROTOCOL_BASE, 0, sizeof(*impl_ver), &t); if (ret) return ret; @@ -120,7 +122,8 @@ scmi_base_implementation_version_get(const struct scmi_handle *handle) rev->impl_ver = le32_to_cpu(*impl_ver); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); + return ret; } @@ -128,8 +131,8 @@ scmi_base_implementation_version_get(const struct scmi_handle *handle) * scmi_base_implementation_list_get() - gets the list of protocols it is * OSPM is allowed to access * - * @handle - SCMI entity handle - * @protocols_imp - pointer to hold the list of protocol identifiers + * @handle: SCMI entity handle + * @protocols_imp: pointer to hold the list of protocol identifiers * * Return: 0 on success, else appropriate SCMI error. */ @@ -143,7 +146,7 @@ static int scmi_base_implementation_list_get(const struct scmi_handle *handle, u32 tot_num_ret = 0, loop_num_ret; struct device *dev = handle->dev; - ret = scmi_one_xfer_init(handle, BASE_DISCOVER_LIST_PROTOCOLS, + ret = scmi_xfer_get_init(handle, BASE_DISCOVER_LIST_PROTOCOLS, SCMI_PROTOCOL_BASE, sizeof(*num_skip), 0, &t); if (ret) return ret; @@ -172,16 +175,17 @@ static int scmi_base_implementation_list_get(const struct scmi_handle *handle, tot_num_ret += loop_num_ret; } while (loop_num_ret); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); + return ret; } /** * scmi_base_discover_agent_get() - discover the name of an agent * - * @handle - SCMI entity handle - * @id - Agent identifier - * @name - Agent identifier ASCII string + * @handle: SCMI entity handle + * @id: Agent identifier + * @name: Agent identifier ASCII string * * An agent id of 0 is reserved to identify the platform itself. * Generally operating system is represented as "OSPM" @@ -194,7 +198,7 @@ static int scmi_base_discover_agent_get(const struct scmi_handle *handle, int ret; struct scmi_xfer *t; - ret = scmi_one_xfer_init(handle, BASE_DISCOVER_AGENT, + ret = scmi_xfer_get_init(handle, BASE_DISCOVER_AGENT, SCMI_PROTOCOL_BASE, sizeof(__le32), SCMI_MAX_STR_SIZE, &t); if (ret) @@ -206,7 +210,8 @@ static int scmi_base_discover_agent_get(const struct scmi_handle *handle, if (!ret) memcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); + return ret; } diff --git a/drivers/firmware/arm_scmi/bus.c b/drivers/firmware/arm_scmi/bus.c index f2760a5..472c88a 100644 --- a/drivers/firmware/arm_scmi/bus.c +++ b/drivers/firmware/arm_scmi/bus.c @@ -125,13 +125,13 @@ scmi_device_create(struct device_node *np, struct device *parent, int protocol) int id, retval; struct scmi_device *scmi_dev; - id = ida_simple_get(&scmi_bus_id, 1, 0, GFP_KERNEL); - if (id < 0) - return NULL; - scmi_dev = kzalloc(sizeof(*scmi_dev), GFP_KERNEL); if (!scmi_dev) - goto no_mem; + return NULL; + + id = ida_simple_get(&scmi_bus_id, 1, 0, GFP_KERNEL); + if (id < 0) + goto free_mem; scmi_dev->id = id; scmi_dev->protocol_id = protocol; @@ -141,13 +141,15 @@ scmi_device_create(struct device_node *np, struct device *parent, int protocol) dev_set_name(&scmi_dev->dev, "scmi_dev.%d", id); retval = device_register(&scmi_dev->dev); - if (!retval) - return scmi_dev; + if (retval) + goto put_dev; + return scmi_dev; +put_dev: put_device(&scmi_dev->dev); - kfree(scmi_dev); -no_mem: ida_simple_remove(&scmi_bus_id, id); +free_mem: + kfree(scmi_dev); return NULL; } @@ -171,9 +173,9 @@ int scmi_protocol_register(int protocol_id, scmi_prot_init_fn_t fn) spin_lock(&protocol_lock); ret = idr_alloc(&scmi_protocols, fn, protocol_id, protocol_id + 1, GFP_ATOMIC); + spin_unlock(&protocol_lock); if (ret != protocol_id) pr_err("unable to allocate SCMI idr slot, err %d\n", ret); - spin_unlock(&protocol_lock); return ret; } diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index 2b90606..e4119eb 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -77,7 +77,7 @@ static int scmi_clock_protocol_attributes_get(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_resp_clock_protocol_attributes *attr; - ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES, + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, SCMI_PROTOCOL_CLOCK, 0, sizeof(*attr), &t); if (ret) return ret; @@ -90,7 +90,7 @@ static int scmi_clock_protocol_attributes_get(const struct scmi_handle *handle, ci->max_async_req = attr->max_async_req; } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -101,7 +101,7 @@ static int scmi_clock_attributes_get(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_resp_clock_attributes *attr; - ret = scmi_one_xfer_init(handle, CLOCK_ATTRIBUTES, SCMI_PROTOCOL_CLOCK, + ret = scmi_xfer_get_init(handle, CLOCK_ATTRIBUTES, SCMI_PROTOCOL_CLOCK, sizeof(clk_id), sizeof(*attr), &t); if (ret) return ret; @@ -115,7 +115,7 @@ static int scmi_clock_attributes_get(const struct scmi_handle *handle, else clk->name[0] = '\0'; - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -132,7 +132,7 @@ scmi_clock_describe_rates_get(const struct scmi_handle *handle, u32 clk_id, struct scmi_msg_clock_describe_rates *clk_desc; struct scmi_msg_resp_clock_describe_rates *rlist; - ret = scmi_one_xfer_init(handle, CLOCK_DESCRIBE_RATES, + ret = scmi_xfer_get_init(handle, CLOCK_DESCRIBE_RATES, SCMI_PROTOCOL_CLOCK, sizeof(*clk_desc), 0, &t); if (ret) return ret; @@ -186,7 +186,7 @@ scmi_clock_describe_rates_get(const struct scmi_handle *handle, u32 clk_id, clk->list.num_rates = tot_rate_cnt; err: - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -196,7 +196,7 @@ scmi_clock_rate_get(const struct scmi_handle *handle, u32 clk_id, u64 *value) int ret; struct scmi_xfer *t; - ret = scmi_one_xfer_init(handle, CLOCK_RATE_GET, SCMI_PROTOCOL_CLOCK, + ret = scmi_xfer_get_init(handle, CLOCK_RATE_GET, SCMI_PROTOCOL_CLOCK, sizeof(__le32), sizeof(u64), &t); if (ret) return ret; @@ -211,7 +211,7 @@ scmi_clock_rate_get(const struct scmi_handle *handle, u32 clk_id, u64 *value) *value |= (u64)le32_to_cpu(*(pval + 1)) << 32; } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -222,7 +222,7 @@ static int scmi_clock_rate_set(const struct scmi_handle *handle, u32 clk_id, struct scmi_xfer *t; struct scmi_clock_set_rate *cfg; - ret = scmi_one_xfer_init(handle, CLOCK_RATE_SET, SCMI_PROTOCOL_CLOCK, + ret = scmi_xfer_get_init(handle, CLOCK_RATE_SET, SCMI_PROTOCOL_CLOCK, sizeof(*cfg), 0, &t); if (ret) return ret; @@ -235,7 +235,7 @@ static int scmi_clock_rate_set(const struct scmi_handle *handle, u32 clk_id, ret = scmi_do_xfer(handle, t); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -246,7 +246,7 @@ scmi_clock_config_set(const struct scmi_handle *handle, u32 clk_id, u32 config) struct scmi_xfer *t; struct scmi_clock_set_config *cfg; - ret = scmi_one_xfer_init(handle, CLOCK_CONFIG_SET, SCMI_PROTOCOL_CLOCK, + ret = scmi_xfer_get_init(handle, CLOCK_CONFIG_SET, SCMI_PROTOCOL_CLOCK, sizeof(*cfg), 0, &t); if (ret) return ret; @@ -257,7 +257,7 @@ scmi_clock_config_set(const struct scmi_handle *handle, u32 clk_id, u32 config) ret = scmi_do_xfer(handle, t); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 0c30234..937a930 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -7,6 +7,7 @@ * Copyright (C) 2018 ARM Ltd. */ +#include <linux/bitfield.h> #include <linux/completion.h> #include <linux/device.h> #include <linux/errno.h> @@ -14,10 +15,10 @@ #include <linux/scmi_protocol.h> #include <linux/types.h> -#define PROTOCOL_REV_MINOR_BITS 16 -#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1) -#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS) -#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK) +#define PROTOCOL_REV_MINOR_MASK GENMASK(15, 0) +#define PROTOCOL_REV_MAJOR_MASK GENMASK(31, 16) +#define PROTOCOL_REV_MAJOR(x) (u16)(FIELD_GET(PROTOCOL_REV_MAJOR_MASK, (x))) +#define PROTOCOL_REV_MINOR(x) (u16)(FIELD_GET(PROTOCOL_REV_MINOR_MASK, (x))) #define MAX_PROTOCOLS_IMP 16 #define MAX_OPPS 16 @@ -50,8 +51,11 @@ struct scmi_msg_resp_prot_version { * @id: The identifier of the command being sent * @protocol_id: The identifier of the protocol used to send @id command * @seq: The token to identify the message. when a message/command returns, - * the platform returns the whole message header unmodified including - * the token. + * the platform returns the whole message header unmodified including + * the token + * @status: Status of the transfer once it's complete + * @poll_completion: Indicate if the transfer needs to be polled for + * completion or interrupt mode is used */ struct scmi_msg_hdr { u8 id; @@ -82,18 +86,16 @@ struct scmi_msg { * buffer for the rx path as we use for the tx path. * @done: completion event */ - struct scmi_xfer { - void *con_priv; struct scmi_msg_hdr hdr; struct scmi_msg tx; struct scmi_msg rx; struct completion done; }; -void scmi_one_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer); +void scmi_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer); int scmi_do_xfer(const struct scmi_handle *h, struct scmi_xfer *xfer); -int scmi_one_xfer_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id, +int scmi_xfer_get_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id, size_t tx_size, size_t rx_size, struct scmi_xfer **p); int scmi_handle_put(const struct scmi_handle *handle); struct scmi_handle *scmi_handle_get(struct device *dev); diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index 2455be8..8f952f2 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -29,16 +29,12 @@ #include "common.h" -#define MSG_ID_SHIFT 0 -#define MSG_ID_MASK 0xff -#define MSG_TYPE_SHIFT 8 -#define MSG_TYPE_MASK 0x3 -#define MSG_PROTOCOL_ID_SHIFT 10 -#define MSG_PROTOCOL_ID_MASK 0xff -#define MSG_TOKEN_ID_SHIFT 18 -#define MSG_TOKEN_ID_MASK 0x3ff -#define MSG_XTRACT_TOKEN(header) \ - (((header) >> MSG_TOKEN_ID_SHIFT) & MSG_TOKEN_ID_MASK) +#define MSG_ID_MASK GENMASK(7, 0) +#define MSG_TYPE_MASK GENMASK(9, 8) +#define MSG_PROTOCOL_ID_MASK GENMASK(17, 10) +#define MSG_TOKEN_ID_MASK GENMASK(27, 18) +#define MSG_XTRACT_TOKEN(hdr) FIELD_GET(MSG_TOKEN_ID_MASK, (hdr)) +#define MSG_TOKEN_MAX (MSG_XTRACT_TOKEN(MSG_TOKEN_ID_MASK) + 1) enum scmi_error_codes { SCMI_SUCCESS = 0, /* Success */ @@ -55,7 +51,7 @@ enum scmi_error_codes { SCMI_ERR_MAX }; -/* List of all SCMI devices active in system */ +/* List of all SCMI devices active in system */ static LIST_HEAD(scmi_list); /* Protection for the entire list */ static DEFINE_MUTEX(scmi_list_mutex); @@ -72,7 +68,6 @@ static DEFINE_MUTEX(scmi_list_mutex); struct scmi_xfers_info { struct scmi_xfer *xfer_block; unsigned long *xfer_alloc_table; - /* protect transfer allocation */ spinlock_t xfer_lock; }; @@ -98,6 +93,7 @@ struct scmi_desc { * @payload: Transmit/Receive mailbox channel payload area * @dev: Reference to device in the SCMI hierarchy corresponding to this * channel + * @handle: Pointer to SCMI entity handle */ struct scmi_chan_info { struct mbox_client cl; @@ -108,7 +104,7 @@ struct scmi_chan_info { }; /** - * struct scmi_info - Structure representing a SCMI instance + * struct scmi_info - Structure representing a SCMI instance * * @dev: Device pointer * @desc: SoC description for this instance @@ -117,9 +113,9 @@ struct scmi_chan_info { * implementation version and (sub-)vendor identification. * @minfo: Message info * @tx_idr: IDR object to map protocol id to channel info pointer - * @protocols_imp: list of protocols implemented, currently maximum of + * @protocols_imp: List of protocols implemented, currently maximum of * MAX_PROTOCOLS_IMP elements allocated by the base protocol - * @node: list head + * @node: List head * @users: Number of users of this instance */ struct scmi_info { @@ -225,9 +221,7 @@ static void scmi_rx_callback(struct mbox_client *cl, void *m) xfer_id = MSG_XTRACT_TOKEN(ioread32(&mem->msg_header)); - /* - * Are we even expecting this? - */ + /* Are we even expecting this? */ if (!test_bit(xfer_id, minfo->xfer_alloc_table)) { dev_err(dev, "message for %d is not expected!\n", xfer_id); return; @@ -252,12 +246,14 @@ static void scmi_rx_callback(struct mbox_client *cl, void *m) * * @hdr: pointer to header containing all the information on message id, * protocol id and sequence id. + * + * Return: 32-bit packed command header to be sent to the platform. */ static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr) { - return ((hdr->id & MSG_ID_MASK) << MSG_ID_SHIFT) | - ((hdr->seq & MSG_TOKEN_ID_MASK) << MSG_TOKEN_ID_SHIFT) | - ((hdr->protocol_id & MSG_PROTOCOL_ID_MASK) << MSG_PROTOCOL_ID_SHIFT); + return FIELD_PREP(MSG_ID_MASK, hdr->id) | + FIELD_PREP(MSG_TOKEN_ID_MASK, hdr->seq) | + FIELD_PREP(MSG_PROTOCOL_ID_MASK, hdr->protocol_id); } /** @@ -286,9 +282,9 @@ static void scmi_tx_prepare(struct mbox_client *cl, void *m) } /** - * scmi_one_xfer_get() - Allocate one message + * scmi_xfer_get() - Allocate one message * - * @handle: SCMI entity handle + * @handle: Pointer to SCMI entity handle * * Helper function which is used by various command functions that are * exposed to clients of this driver for allocating a message traffic event. @@ -299,7 +295,7 @@ static void scmi_tx_prepare(struct mbox_client *cl, void *m) * * Return: 0 if all went fine, else corresponding error. */ -static struct scmi_xfer *scmi_one_xfer_get(const struct scmi_handle *handle) +static struct scmi_xfer *scmi_xfer_get(const struct scmi_handle *handle) { u16 xfer_id; struct scmi_xfer *xfer; @@ -328,14 +324,14 @@ static struct scmi_xfer *scmi_one_xfer_get(const struct scmi_handle *handle) } /** - * scmi_one_xfer_put() - Release a message + * scmi_xfer_put() - Release a message * - * @minfo: transfer info pointer - * @xfer: message that was reserved by scmi_one_xfer_get + * @handle: Pointer to SCMI entity handle + * @xfer: message that was reserved by scmi_xfer_get * * This holds a spinlock to maintain integrity of internal data structures. */ -void scmi_one_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer) +void scmi_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer) { unsigned long flags; struct scmi_info *info = handle_to_scmi_info(handle); @@ -378,12 +374,12 @@ static bool scmi_xfer_done_no_timeout(const struct scmi_chan_info *cinfo, /** * scmi_do_xfer() - Do one transfer * - * @info: Pointer to SCMI entity information + * @handle: Pointer to SCMI entity handle * @xfer: Transfer to initiate and wait for response * * Return: -ETIMEDOUT in case of no response, if transmit error, - * return corresponding error, else if all goes well, - * return 0. + * return corresponding error, else if all goes well, + * return 0. */ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer) { @@ -440,22 +436,22 @@ int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer) } /** - * scmi_one_xfer_init() - Allocate and initialise one message + * scmi_xfer_get_init() - Allocate and initialise one message * - * @handle: SCMI entity handle + * @handle: Pointer to SCMI entity handle * @msg_id: Message identifier - * @msg_prot_id: Protocol identifier for the message + * @prot_id: Protocol identifier for the message * @tx_size: transmit message size * @rx_size: receive message size * @p: pointer to the allocated and initialised message * - * This function allocates the message using @scmi_one_xfer_get and + * This function allocates the message using @scmi_xfer_get and * initialise the header. * * Return: 0 if all went fine with @p pointing to message, else * corresponding error. */ -int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id, +int scmi_xfer_get_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id, size_t tx_size, size_t rx_size, struct scmi_xfer **p) { int ret; @@ -468,7 +464,7 @@ int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id, tx_size > info->desc->max_msg_size) return -ERANGE; - xfer = scmi_one_xfer_get(handle); + xfer = scmi_xfer_get(handle); if (IS_ERR(xfer)) { ret = PTR_ERR(xfer); dev_err(dev, "failed to get free message slot(%d)\n", ret); @@ -482,13 +478,16 @@ int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id, xfer->hdr.poll_completion = false; *p = xfer; + return 0; } /** * scmi_version_get() - command to get the revision of the SCMI entity * - * @handle: Handle to SCMI entity information + * @handle: Pointer to SCMI entity handle + * @protocol: Protocol identifier for the message + * @version: Holds returned version of protocol. * * Updates the SCMI information in the internal data structure. * @@ -501,7 +500,7 @@ int scmi_version_get(const struct scmi_handle *handle, u8 protocol, __le32 *rev_info; struct scmi_xfer *t; - ret = scmi_one_xfer_init(handle, PROTOCOL_VERSION, protocol, 0, + ret = scmi_xfer_get_init(handle, PROTOCOL_VERSION, protocol, 0, sizeof(*version), &t); if (ret) return ret; @@ -512,7 +511,7 @@ int scmi_version_get(const struct scmi_handle *handle, u8 protocol, *version = le32_to_cpu(*rev_info); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -540,12 +539,12 @@ scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id) } /** - * scmi_handle_get() - Get the SCMI handle for a device + * scmi_handle_get() - Get the SCMI handle for a device * * @dev: pointer to device for which we want SCMI handle * * NOTE: The function does not track individual clients of the framework - * and is expected to be maintained by caller of SCMI protocol library. + * and is expected to be maintained by caller of SCMI protocol library. * scmi_handle_put must be balanced with successful scmi_handle_get * * Return: pointer to handle if successful, NULL on error @@ -576,7 +575,7 @@ struct scmi_handle *scmi_handle_get(struct device *dev) * @handle: handle acquired by scmi_handle_get * * NOTE: The function does not track individual clients of the framework - * and is expected to be maintained by caller of SCMI protocol library. + * and is expected to be maintained by caller of SCMI protocol library. * scmi_handle_put must be balanced with successful scmi_handle_get * * Return: 0 is successfully released @@ -599,7 +598,7 @@ int scmi_handle_put(const struct scmi_handle *handle) } static const struct scmi_desc scmi_generic_desc = { - .max_rx_timeout_ms = 30, /* we may increase this if required */ + .max_rx_timeout_ms = 30, /* We may increase this if required */ .max_msg = 20, /* Limited by MBOX_TX_QUEUE_LEN */ .max_msg_size = 128, }; @@ -621,9 +620,9 @@ static int scmi_xfer_info_init(struct scmi_info *sinfo) struct scmi_xfers_info *info = &sinfo->minfo; /* Pre-allocated messages, no more than what hdr.seq can support */ - if (WARN_ON(desc->max_msg >= (MSG_TOKEN_ID_MASK + 1))) { - dev_err(dev, "Maximum message of %d exceeds supported %d\n", - desc->max_msg, MSG_TOKEN_ID_MASK + 1); + if (WARN_ON(desc->max_msg >= MSG_TOKEN_MAX)) { + dev_err(dev, "Maximum message of %d exceeds supported %ld\n", + desc->max_msg, MSG_TOKEN_MAX); return -EINVAL; } @@ -637,8 +636,6 @@ static int scmi_xfer_info_init(struct scmi_info *sinfo) if (!info->xfer_alloc_table) return -ENOMEM; - bitmap_zero(info->xfer_alloc_table, desc->max_msg); - /* Pre-initialize the buffer pointer to pre-allocated buffers */ for (i = 0, xfer = info->xfer_block; i < desc->max_msg; i++, xfer++) { xfer->rx.buf = devm_kcalloc(dev, sizeof(u8), desc->max_msg_size, @@ -690,11 +687,12 @@ static int scmi_remove(struct platform_device *pdev) list_del(&info->node); mutex_unlock(&scmi_list_mutex); - if (!ret) { - /* Safe to free channels since no more users */ - ret = idr_for_each(idr, scmi_mbox_free_channel, idr); - idr_destroy(&info->tx_idr); - } + if (ret) + return ret; + + /* Safe to free channels since no more users */ + ret = idr_for_each(idr, scmi_mbox_free_channel, idr); + idr_destroy(&info->tx_idr); return ret; } @@ -841,7 +839,8 @@ static int scmi_probe(struct platform_device *pdev) if (of_property_read_u32(child, "reg", &prot_id)) continue; - prot_id &= MSG_PROTOCOL_ID_MASK; + if (!FIELD_FIT(MSG_PROTOCOL_ID_MASK, prot_id)) + dev_err(dev, "Out of range protocol %d\n", prot_id); if (!scmi_is_protocol_implemented(handle, prot_id)) { dev_err(dev, "SCMI protocol %d not implemented\n", diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c index 987c64d..2a219b1 100644 --- a/drivers/firmware/arm_scmi/perf.c +++ b/drivers/firmware/arm_scmi/perf.c @@ -115,7 +115,7 @@ static int scmi_perf_attributes_get(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_resp_perf_attributes *attr; - ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES, + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, SCMI_PROTOCOL_PERF, 0, sizeof(*attr), &t); if (ret) return ret; @@ -133,7 +133,7 @@ static int scmi_perf_attributes_get(const struct scmi_handle *handle, pi->stats_size = le32_to_cpu(attr->stats_size); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -145,7 +145,7 @@ scmi_perf_domain_attributes_get(const struct scmi_handle *handle, u32 domain, struct scmi_xfer *t; struct scmi_msg_resp_perf_domain_attributes *attr; - ret = scmi_one_xfer_init(handle, PERF_DOMAIN_ATTRIBUTES, + ret = scmi_xfer_get_init(handle, PERF_DOMAIN_ATTRIBUTES, SCMI_PROTOCOL_PERF, sizeof(domain), sizeof(*attr), &t); if (ret) @@ -171,7 +171,7 @@ scmi_perf_domain_attributes_get(const struct scmi_handle *handle, u32 domain, memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -194,7 +194,7 @@ scmi_perf_describe_levels_get(const struct scmi_handle *handle, u32 domain, struct scmi_msg_perf_describe_levels *dom_info; struct scmi_msg_resp_perf_describe_levels *level_info; - ret = scmi_one_xfer_init(handle, PERF_DESCRIBE_LEVELS, + ret = scmi_xfer_get_init(handle, PERF_DESCRIBE_LEVELS, SCMI_PROTOCOL_PERF, sizeof(*dom_info), 0, &t); if (ret) return ret; @@ -237,7 +237,7 @@ scmi_perf_describe_levels_get(const struct scmi_handle *handle, u32 domain, } while (num_returned && num_remaining); perf_dom->opp_count = tot_opp_cnt; - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); sort(perf_dom->opp, tot_opp_cnt, sizeof(*opp), opp_cmp_func, NULL); return ret; @@ -250,7 +250,7 @@ static int scmi_perf_limits_set(const struct scmi_handle *handle, u32 domain, struct scmi_xfer *t; struct scmi_perf_set_limits *limits; - ret = scmi_one_xfer_init(handle, PERF_LIMITS_SET, SCMI_PROTOCOL_PERF, + ret = scmi_xfer_get_init(handle, PERF_LIMITS_SET, SCMI_PROTOCOL_PERF, sizeof(*limits), 0, &t); if (ret) return ret; @@ -262,7 +262,7 @@ static int scmi_perf_limits_set(const struct scmi_handle *handle, u32 domain, ret = scmi_do_xfer(handle, t); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -273,7 +273,7 @@ static int scmi_perf_limits_get(const struct scmi_handle *handle, u32 domain, struct scmi_xfer *t; struct scmi_perf_get_limits *limits; - ret = scmi_one_xfer_init(handle, PERF_LIMITS_GET, SCMI_PROTOCOL_PERF, + ret = scmi_xfer_get_init(handle, PERF_LIMITS_GET, SCMI_PROTOCOL_PERF, sizeof(__le32), 0, &t); if (ret) return ret; @@ -288,7 +288,7 @@ static int scmi_perf_limits_get(const struct scmi_handle *handle, u32 domain, *min_perf = le32_to_cpu(limits->min_level); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -299,7 +299,7 @@ static int scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, struct scmi_xfer *t; struct scmi_perf_set_level *lvl; - ret = scmi_one_xfer_init(handle, PERF_LEVEL_SET, SCMI_PROTOCOL_PERF, + ret = scmi_xfer_get_init(handle, PERF_LEVEL_SET, SCMI_PROTOCOL_PERF, sizeof(*lvl), 0, &t); if (ret) return ret; @@ -311,7 +311,7 @@ static int scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, ret = scmi_do_xfer(handle, t); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -321,7 +321,7 @@ static int scmi_perf_level_get(const struct scmi_handle *handle, u32 domain, int ret; struct scmi_xfer *t; - ret = scmi_one_xfer_init(handle, PERF_LEVEL_GET, SCMI_PROTOCOL_PERF, + ret = scmi_xfer_get_init(handle, PERF_LEVEL_GET, SCMI_PROTOCOL_PERF, sizeof(u32), sizeof(u32), &t); if (ret) return ret; @@ -333,7 +333,7 @@ static int scmi_perf_level_get(const struct scmi_handle *handle, u32 domain, if (!ret) *level = le32_to_cpu(*(__le32 *)t->rx.buf); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -349,8 +349,8 @@ static int scmi_dev_domain_id(struct device *dev) return clkspec.args[0]; } -static int scmi_dvfs_add_opps_to_device(const struct scmi_handle *handle, - struct device *dev) +static int scmi_dvfs_device_opps_add(const struct scmi_handle *handle, + struct device *dev) { int idx, ret, domain; unsigned long freq; @@ -383,7 +383,7 @@ static int scmi_dvfs_add_opps_to_device(const struct scmi_handle *handle, return 0; } -static int scmi_dvfs_get_transition_latency(const struct scmi_handle *handle, +static int scmi_dvfs_transition_latency_get(const struct scmi_handle *handle, struct device *dev) { struct perf_dom_info *dom; @@ -432,8 +432,8 @@ static struct scmi_perf_ops perf_ops = { .level_set = scmi_perf_level_set, .level_get = scmi_perf_level_get, .device_domain_id = scmi_dev_domain_id, - .get_transition_latency = scmi_dvfs_get_transition_latency, - .add_opps_to_device = scmi_dvfs_add_opps_to_device, + .transition_latency_get = scmi_dvfs_transition_latency_get, + .device_opps_add = scmi_dvfs_device_opps_add, .freq_set = scmi_dvfs_freq_set, .freq_get = scmi_dvfs_freq_get, }; diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c index 087c287..cfa033b 100644 --- a/drivers/firmware/arm_scmi/power.c +++ b/drivers/firmware/arm_scmi/power.c @@ -63,7 +63,7 @@ static int scmi_power_attributes_get(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_resp_power_attributes *attr; - ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES, + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, SCMI_PROTOCOL_POWER, 0, sizeof(*attr), &t); if (ret) return ret; @@ -78,7 +78,7 @@ static int scmi_power_attributes_get(const struct scmi_handle *handle, pi->stats_size = le32_to_cpu(attr->stats_size); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -90,7 +90,7 @@ scmi_power_domain_attributes_get(const struct scmi_handle *handle, u32 domain, struct scmi_xfer *t; struct scmi_msg_resp_power_domain_attributes *attr; - ret = scmi_one_xfer_init(handle, POWER_DOMAIN_ATTRIBUTES, + ret = scmi_xfer_get_init(handle, POWER_DOMAIN_ATTRIBUTES, SCMI_PROTOCOL_POWER, sizeof(domain), sizeof(*attr), &t); if (ret) @@ -109,7 +109,7 @@ scmi_power_domain_attributes_get(const struct scmi_handle *handle, u32 domain, memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -120,7 +120,7 @@ scmi_power_state_set(const struct scmi_handle *handle, u32 domain, u32 state) struct scmi_xfer *t; struct scmi_power_set_state *st; - ret = scmi_one_xfer_init(handle, POWER_STATE_SET, SCMI_PROTOCOL_POWER, + ret = scmi_xfer_get_init(handle, POWER_STATE_SET, SCMI_PROTOCOL_POWER, sizeof(*st), 0, &t); if (ret) return ret; @@ -132,7 +132,7 @@ scmi_power_state_set(const struct scmi_handle *handle, u32 domain, u32 state) ret = scmi_do_xfer(handle, t); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -142,7 +142,7 @@ scmi_power_state_get(const struct scmi_handle *handle, u32 domain, u32 *state) int ret; struct scmi_xfer *t; - ret = scmi_one_xfer_init(handle, POWER_STATE_GET, SCMI_PROTOCOL_POWER, + ret = scmi_xfer_get_init(handle, POWER_STATE_GET, SCMI_PROTOCOL_POWER, sizeof(u32), sizeof(u32), &t); if (ret) return ret; @@ -153,7 +153,7 @@ scmi_power_state_get(const struct scmi_handle *handle, u32 domain, u32 *state) if (!ret) *state = le32_to_cpu(*(__le32 *)t->rx.buf); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c index bbb469f..27f2092 100644 --- a/drivers/firmware/arm_scmi/sensors.c +++ b/drivers/firmware/arm_scmi/sensors.c @@ -79,7 +79,7 @@ static int scmi_sensor_attributes_get(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_resp_sensor_attributes *attr; - ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES, + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, SCMI_PROTOCOL_SENSOR, 0, sizeof(*attr), &t); if (ret) return ret; @@ -95,7 +95,7 @@ static int scmi_sensor_attributes_get(const struct scmi_handle *handle, si->reg_size = le32_to_cpu(attr->reg_size); } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -108,7 +108,7 @@ static int scmi_sensor_description_get(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_resp_sensor_description *buf; - ret = scmi_one_xfer_init(handle, SENSOR_DESCRIPTION_GET, + ret = scmi_xfer_get_init(handle, SENSOR_DESCRIPTION_GET, SCMI_PROTOCOL_SENSOR, sizeof(__le32), 0, &t); if (ret) return ret; @@ -150,7 +150,7 @@ static int scmi_sensor_description_get(const struct scmi_handle *handle, */ } while (num_returned && num_remaining); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -162,7 +162,7 @@ scmi_sensor_configuration_set(const struct scmi_handle *handle, u32 sensor_id) struct scmi_xfer *t; struct scmi_msg_set_sensor_config *cfg; - ret = scmi_one_xfer_init(handle, SENSOR_CONFIG_SET, + ret = scmi_xfer_get_init(handle, SENSOR_CONFIG_SET, SCMI_PROTOCOL_SENSOR, sizeof(*cfg), 0, &t); if (ret) return ret; @@ -173,7 +173,7 @@ scmi_sensor_configuration_set(const struct scmi_handle *handle, u32 sensor_id) ret = scmi_do_xfer(handle, t); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -185,7 +185,7 @@ static int scmi_sensor_trip_point_set(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_set_sensor_trip_point *trip; - ret = scmi_one_xfer_init(handle, SENSOR_TRIP_POINT_SET, + ret = scmi_xfer_get_init(handle, SENSOR_TRIP_POINT_SET, SCMI_PROTOCOL_SENSOR, sizeof(*trip), 0, &t); if (ret) return ret; @@ -198,7 +198,7 @@ static int scmi_sensor_trip_point_set(const struct scmi_handle *handle, ret = scmi_do_xfer(handle, t); - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } @@ -209,7 +209,7 @@ static int scmi_sensor_reading_get(const struct scmi_handle *handle, struct scmi_xfer *t; struct scmi_msg_sensor_reading_get *sensor; - ret = scmi_one_xfer_init(handle, SENSOR_READING_GET, + ret = scmi_xfer_get_init(handle, SENSOR_READING_GET, SCMI_PROTOCOL_SENSOR, sizeof(*sensor), sizeof(u64), &t); if (ret) @@ -227,7 +227,7 @@ static int scmi_sensor_reading_get(const struct scmi_handle *handle, *value |= (u64)le32_to_cpu(*(pval + 1)) << 32; } - scmi_one_xfer_put(handle, t); + scmi_xfer_put(handle, t); return ret; } diff --git a/drivers/firmware/broadcom/bcm47xx_nvram.c b/drivers/firmware/broadcom/bcm47xx_nvram.c index 0b631e5..d25f080 100644 --- a/drivers/firmware/broadcom/bcm47xx_nvram.c +++ b/drivers/firmware/broadcom/bcm47xx_nvram.c @@ -36,7 +36,7 @@ struct nvram_header { static char nvram_buf[NVRAM_SPACE]; static size_t nvram_len; -static const u32 nvram_sizes[] = {0x8000, 0xF000, 0x10000}; +static const u32 nvram_sizes[] = {0x6000, 0x8000, 0xF000, 0x10000}; static u32 find_nvram_size(void __iomem *end) { diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index 5a7d6930..e778af7 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -603,6 +603,9 @@ static const struct of_device_id qcom_scm_dt_match[] = { { .compatible = "qcom,scm-msm8996", .data = NULL, /* no clocks */ }, + { .compatible = "qcom,scm-ipq4019", + .data = NULL, /* no clocks */ + }, { .compatible = "qcom,scm", .data = (void *)(SCM_HAS_CORE_CLK | SCM_HAS_IFACE_CLK diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index a7d9a20..7fa7447 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -1,17 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Texas Instruments System Control Interface Protocol Driver * * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/ * Nishanth Menon - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) "%s: " fmt, __func__ diff --git a/drivers/firmware/ti_sci.h b/drivers/firmware/ti_sci.h index 9b611e9..12bf316 100644 --- a/drivers/firmware/ti_sci.h +++ b/drivers/firmware/ti_sci.h @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: BSD-3-Clause /* * Texas Instruments System Control Interface (TISCI) Protocol * @@ -6,35 +7,6 @@ * See: http://processors.wiki.ti.com/index.php/TISCI for details * * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/ - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * */ #ifndef __TI_SCI_H diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig index f0f4679..e895d29 100644 --- a/drivers/hwspinlock/Kconfig +++ b/drivers/hwspinlock/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 # # Generic HWSPINLOCK framework # diff --git a/drivers/hwspinlock/hwspinlock_core.c b/drivers/hwspinlock/hwspinlock_core.c index 4074441..d16e6a3 100644 --- a/drivers/hwspinlock/hwspinlock_core.c +++ b/drivers/hwspinlock/hwspinlock_core.c @@ -1,18 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Hardware spinlock framework * * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com * * Contact: Ohad Ben-Cohen <ohad@wizery.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -71,10 +63,16 @@ static DEFINE_MUTEX(hwspinlock_tree_lock); * This function attempts to lock an hwspinlock, and will immediately * fail if the hwspinlock is already taken. * - * Upon a successful return from this function, preemption (and possibly - * interrupts) is disabled, so the caller must not sleep, and is advised to - * release the hwspinlock as soon as possible. This is required in order to - * minimize remote cores polling on the hardware interconnect. + * Caution: If the mode is HWLOCK_RAW, that means user must protect the routine + * of getting hardware lock with mutex or spinlock. Since in some scenarios, + * user need some time-consuming or sleepable operations under the hardware + * lock, they need one sleepable lock (like mutex) to protect the operations. + * + * If the mode is not HWLOCK_RAW, upon a successful return from this function, + * preemption (and possibly interrupts) is disabled, so the caller must not + * sleep, and is advised to release the hwspinlock as soon as possible. This is + * required in order to minimize remote cores polling on the hardware + * interconnect. * * The user decides whether local interrupts are disabled or not, and if yes, * whether he wants their previous state to be saved. It is up to the user @@ -106,12 +104,20 @@ int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) * problems with hwspinlock usage (e.g. scheduler checks like * 'scheduling while atomic' etc.) */ - if (mode == HWLOCK_IRQSTATE) + switch (mode) { + case HWLOCK_IRQSTATE: ret = spin_trylock_irqsave(&hwlock->lock, *flags); - else if (mode == HWLOCK_IRQ) + break; + case HWLOCK_IRQ: ret = spin_trylock_irq(&hwlock->lock); - else + break; + case HWLOCK_RAW: + ret = 1; + break; + default: ret = spin_trylock(&hwlock->lock); + break; + } /* is lock already taken by another context on the local cpu ? */ if (!ret) @@ -122,12 +128,20 @@ int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) /* if hwlock is already taken, undo spin_trylock_* and exit */ if (!ret) { - if (mode == HWLOCK_IRQSTATE) + switch (mode) { + case HWLOCK_IRQSTATE: spin_unlock_irqrestore(&hwlock->lock, *flags); - else if (mode == HWLOCK_IRQ) + break; + case HWLOCK_IRQ: spin_unlock_irq(&hwlock->lock); - else + break; + case HWLOCK_RAW: + /* Nothing to do */ + break; + default: spin_unlock(&hwlock->lock); + break; + } return -EBUSY; } @@ -160,9 +174,14 @@ EXPORT_SYMBOL_GPL(__hwspin_trylock); * is already taken, the function will busy loop waiting for it to * be released, but give up after @timeout msecs have elapsed. * - * Upon a successful return from this function, preemption is disabled - * (and possibly local interrupts, too), so the caller must not sleep, - * and is advised to release the hwspinlock as soon as possible. + * Caution: If the mode is HWLOCK_RAW, that means user must protect the routine + * of getting hardware lock with mutex or spinlock. Since in some scenarios, + * user need some time-consuming or sleepable operations under the hardware + * lock, they need one sleepable lock (like mutex) to protect the operations. + * + * If the mode is not HWLOCK_RAW, upon a successful return from this function, + * preemption is disabled (and possibly local interrupts, too), so the caller + * must not sleep, and is advised to release the hwspinlock as soon as possible. * This is required in order to minimize remote cores polling on the * hardware interconnect. * @@ -249,12 +268,20 @@ void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) hwlock->bank->ops->unlock(hwlock); /* Undo the spin_trylock{_irq, _irqsave} called while locking */ - if (mode == HWLOCK_IRQSTATE) + switch (mode) { + case HWLOCK_IRQSTATE: spin_unlock_irqrestore(&hwlock->lock, *flags); - else if (mode == HWLOCK_IRQ) + break; + case HWLOCK_IRQ: spin_unlock_irq(&hwlock->lock); - else + break; + case HWLOCK_RAW: + /* Nothing to do */ + break; + default: spin_unlock(&hwlock->lock); + break; + } } EXPORT_SYMBOL_GPL(__hwspin_unlock); diff --git a/drivers/hwspinlock/hwspinlock_internal.h b/drivers/hwspinlock/hwspinlock_internal.h index d26f78b..9eb6bd0 100644 --- a/drivers/hwspinlock/hwspinlock_internal.h +++ b/drivers/hwspinlock/hwspinlock_internal.h @@ -1,18 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Hardware spinlocks internal header * * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com * * Contact: Ohad Ben-Cohen <ohad@wizery.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #ifndef __HWSPINLOCK_HWSPINLOCK_H diff --git a/drivers/hwspinlock/omap_hwspinlock.c b/drivers/hwspinlock/omap_hwspinlock.c index d897e52..625844e 100644 --- a/drivers/hwspinlock/omap_hwspinlock.c +++ b/drivers/hwspinlock/omap_hwspinlock.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * OMAP hardware spinlock driver * @@ -6,15 +7,6 @@ * Contact: Simon Que <sque@ti.com> * Hari Kanigeri <h-kanigeri2@ti.com> * Ohad Ben-Cohen <ohad@wizery.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. */ #include <linux/kernel.h> diff --git a/drivers/hwspinlock/qcom_hwspinlock.c b/drivers/hwspinlock/qcom_hwspinlock.c index fa6880b..6da7447 100644 --- a/drivers/hwspinlock/qcom_hwspinlock.c +++ b/drivers/hwspinlock/qcom_hwspinlock.c @@ -1,15 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2013, The Linux Foundation. All rights reserved. * Copyright (c) 2015, Sony Mobile Communications AB - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/hwspinlock.h> diff --git a/drivers/hwspinlock/sirf_hwspinlock.c b/drivers/hwspinlock/sirf_hwspinlock.c index cb38e48..1f625cd 100644 --- a/drivers/hwspinlock/sirf_hwspinlock.c +++ b/drivers/hwspinlock/sirf_hwspinlock.c @@ -1,9 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * SIRF hardware spinlock driver * * Copyright (c) 2015 Cambridge Silicon Radio Limited, a CSR plc group company. - * - * Licensed under GPLv2. */ #include <linux/kernel.h> diff --git a/drivers/hwspinlock/sprd_hwspinlock.c b/drivers/hwspinlock/sprd_hwspinlock.c index 638e64a..dc42bf5 100644 --- a/drivers/hwspinlock/sprd_hwspinlock.c +++ b/drivers/hwspinlock/sprd_hwspinlock.c @@ -1,15 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Spreadtrum hardware spinlock driver * Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. */ #include <linux/bitops.h> diff --git a/drivers/hwspinlock/u8500_hsem.c b/drivers/hwspinlock/u8500_hsem.c index 0128d8f..572ca79 100644 --- a/drivers/hwspinlock/u8500_hsem.c +++ b/drivers/hwspinlock/u8500_hsem.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * u8500 HWSEM driver * @@ -10,15 +11,6 @@ * Simon Que <sque@ti.com> * Hari Kanigeri <h-kanigeri2@ti.com> * Ohad Ben-Cohen <ohad@wizery.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. */ #include <linux/module.h> diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 8ea77ef..e055d22 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -107,7 +107,6 @@ config IOMMU_PGTABLES_L2 # AMD IOMMU support config AMD_IOMMU bool "AMD IOMMU support" - select DMA_DIRECT_OPS select SWIOTLB select PCI_MSI select PCI_ATS diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index 0cea80be..596b95c 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -2596,32 +2596,51 @@ static void *alloc_coherent(struct device *dev, size_t size, unsigned long attrs) { u64 dma_mask = dev->coherent_dma_mask; - struct protection_domain *domain = get_domain(dev); - bool is_direct = false; - void *virt_addr; + struct protection_domain *domain; + struct dma_ops_domain *dma_dom; + struct page *page; + + domain = get_domain(dev); + if (PTR_ERR(domain) == -EINVAL) { + page = alloc_pages(flag, get_order(size)); + *dma_addr = page_to_phys(page); + return page_address(page); + } else if (IS_ERR(domain)) + return NULL; - if (IS_ERR(domain)) { - if (PTR_ERR(domain) != -EINVAL) + dma_dom = to_dma_ops_domain(domain); + size = PAGE_ALIGN(size); + dma_mask = dev->coherent_dma_mask; + flag &= ~(__GFP_DMA | __GFP_HIGHMEM | __GFP_DMA32); + flag |= __GFP_ZERO; + + page = alloc_pages(flag | __GFP_NOWARN, get_order(size)); + if (!page) { + if (!gfpflags_allow_blocking(flag)) return NULL; - is_direct = true; - } - virt_addr = dma_direct_alloc(dev, size, dma_addr, flag, attrs); - if (!virt_addr || is_direct) - return virt_addr; + page = dma_alloc_from_contiguous(dev, size >> PAGE_SHIFT, + get_order(size), flag); + if (!page) + return NULL; + } if (!dma_mask) dma_mask = *dev->dma_mask; - *dma_addr = __map_single(dev, to_dma_ops_domain(domain), - virt_to_phys(virt_addr), PAGE_ALIGN(size), - DMA_BIDIRECTIONAL, dma_mask); + *dma_addr = __map_single(dev, dma_dom, page_to_phys(page), + size, DMA_BIDIRECTIONAL, dma_mask); + if (*dma_addr == AMD_IOMMU_MAPPING_ERROR) goto out_free; - return virt_addr; + + return page_address(page); out_free: - dma_direct_free(dev, size, virt_addr, *dma_addr, attrs); + + if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT)) + __free_pages(page, get_order(size)); + return NULL; } @@ -2632,17 +2651,24 @@ static void free_coherent(struct device *dev, size_t size, void *virt_addr, dma_addr_t dma_addr, unsigned long attrs) { - struct protection_domain *domain = get_domain(dev); + struct protection_domain *domain; + struct dma_ops_domain *dma_dom; + struct page *page; + page = virt_to_page(virt_addr); size = PAGE_ALIGN(size); - if (!IS_ERR(domain)) { - struct dma_ops_domain *dma_dom = to_dma_ops_domain(domain); + domain = get_domain(dev); + if (IS_ERR(domain)) + goto free_mem; - __unmap_single(dma_dom, dma_addr, size, DMA_BIDIRECTIONAL); - } + dma_dom = to_dma_ops_domain(domain); + + __unmap_single(dma_dom, dma_addr, size, DMA_BIDIRECTIONAL); - dma_direct_free(dev, size, virt_addr, dma_addr, attrs); +free_mem: + if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT)) + __free_pages(page, get_order(size)); } /* diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig index edff083..8b8c123 100644 --- a/drivers/md/Kconfig +++ b/drivers/md/Kconfig @@ -334,6 +334,17 @@ config DM_CACHE_SMQ of less memory utilization, improved performance and increased adaptability in the face of changing workloads. +config DM_WRITECACHE + tristate "Writecache target" + depends on BLK_DEV_DM + ---help--- + The writecache target caches writes on persistent memory or SSD. + It is intended for databases or other programs that need extremely + low commit latency. + + The writecache target doesn't cache reads because reads are supposed + to be cached in standard RAM. + config DM_ERA tristate "Era target (EXPERIMENTAL)" depends on BLK_DEV_DM diff --git a/drivers/md/Makefile b/drivers/md/Makefile index 63255f3..822f4e8 100644 --- a/drivers/md/Makefile +++ b/drivers/md/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_DM_ERA) += dm-era.o obj-$(CONFIG_DM_LOG_WRITES) += dm-log-writes.o obj-$(CONFIG_DM_INTEGRITY) += dm-integrity.o obj-$(CONFIG_DM_ZONED) += dm-zoned.o +obj-$(CONFIG_DM_WRITECACHE) += dm-writecache.o ifeq ($(CONFIG_DM_UEVENT),y) dm-mod-objs += dm-uevent.o diff --git a/drivers/md/dm-bio-prison-v1.c b/drivers/md/dm-bio-prison-v1.c index e794e36..b538989 100644 --- a/drivers/md/dm-bio-prison-v1.c +++ b/drivers/md/dm-bio-prison-v1.c @@ -19,8 +19,8 @@ struct dm_bio_prison { spinlock_t lock; - mempool_t cell_pool; struct rb_root cells; + mempool_t cell_pool; }; static struct kmem_cache *_cell_cache; diff --git a/drivers/md/dm-bio-prison-v2.c b/drivers/md/dm-bio-prison-v2.c index f866bc9..b092cdc 100644 --- a/drivers/md/dm-bio-prison-v2.c +++ b/drivers/md/dm-bio-prison-v2.c @@ -21,8 +21,8 @@ struct dm_bio_prison_v2 { struct workqueue_struct *wq; spinlock_t lock; - mempool_t cell_pool; struct rb_root cells; + mempool_t cell_pool; }; static struct kmem_cache *_cell_cache; diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 001c712..ce14a3d 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -371,7 +371,13 @@ struct cache_stats { struct cache { struct dm_target *ti; - struct dm_target_callbacks callbacks; + spinlock_t lock; + + /* + * Fields for converting from sectors to blocks. + */ + int sectors_per_block_shift; + sector_t sectors_per_block; struct dm_cache_metadata *cmd; @@ -402,13 +408,11 @@ struct cache { dm_cblock_t cache_size; /* - * Fields for converting from sectors to blocks. + * Invalidation fields. */ - sector_t sectors_per_block; - int sectors_per_block_shift; + spinlock_t invalidation_lock; + struct list_head invalidation_requests; - spinlock_t lock; - struct bio_list deferred_bios; sector_t migration_threshold; wait_queue_head_t migration_wait; atomic_t nr_allocated_migrations; @@ -419,13 +423,11 @@ struct cache { */ atomic_t nr_io_migrations; + struct bio_list deferred_bios; + struct rw_semaphore quiesce_lock; - /* - * cache_size entries, dirty if set - */ - atomic_t nr_dirty; - unsigned long *dirty_bitset; + struct dm_target_callbacks callbacks; /* * origin_blocks entries, discarded if set. @@ -442,17 +444,27 @@ struct cache { const char **ctr_args; struct dm_kcopyd_client *copier; - struct workqueue_struct *wq; struct work_struct deferred_bio_worker; struct work_struct migration_worker; + struct workqueue_struct *wq; struct delayed_work waker; struct dm_bio_prison_v2 *prison; - struct bio_set bs; - mempool_t migration_pool; + /* + * cache_size entries, dirty if set + */ + unsigned long *dirty_bitset; + atomic_t nr_dirty; - struct dm_cache_policy *policy; unsigned policy_nr_args; + struct dm_cache_policy *policy; + + /* + * Cache features such as write-through. + */ + struct cache_features features; + + struct cache_stats stats; bool need_tick_bio:1; bool sized:1; @@ -461,25 +473,16 @@ struct cache { bool loaded_mappings:1; bool loaded_discards:1; - /* - * Cache features such as write-through. - */ - struct cache_features features; - - struct cache_stats stats; + struct rw_semaphore background_work_lock; - /* - * Invalidation fields. - */ - spinlock_t invalidation_lock; - struct list_head invalidation_requests; + struct batcher committer; + struct work_struct commit_ws; struct io_tracker tracker; - struct work_struct commit_ws; - struct batcher committer; + mempool_t migration_pool; - struct rw_semaphore background_work_lock; + struct bio_set bs; }; struct per_bio_data { diff --git a/drivers/md/dm-core.h b/drivers/md/dm-core.h index f21c5d2..7d480c9 100644 --- a/drivers/md/dm-core.h +++ b/drivers/md/dm-core.h @@ -31,6 +31,9 @@ struct dm_kobject_holder { struct mapped_device { struct mutex suspend_lock; + struct mutex table_devices_lock; + struct list_head table_devices; + /* * The current mapping (struct dm_table *). * Use dm_get_live_table{_fast} or take suspend_lock for @@ -38,17 +41,14 @@ struct mapped_device { */ void __rcu *map; - struct list_head table_devices; - struct mutex table_devices_lock; - unsigned long flags; - struct request_queue *queue; - int numa_node_id; - - enum dm_queue_mode type; /* Protect queue and type against concurrent access. */ struct mutex type_lock; + enum dm_queue_mode type; + + int numa_node_id; + struct request_queue *queue; atomic_t holders; atomic_t open_count; @@ -56,21 +56,21 @@ struct mapped_device { struct dm_target *immutable_target; struct target_type *immutable_target_type; + char name[16]; struct gendisk *disk; struct dax_device *dax_dev; - char name[16]; - - void *interface_ptr; /* * A list of ios that arrived while we were suspended. */ - atomic_t pending[2]; - wait_queue_head_t wait; struct work_struct work; + wait_queue_head_t wait; + atomic_t pending[2]; spinlock_t deferred_lock; struct bio_list deferred; + void *interface_ptr; + /* * Event handling. */ @@ -84,17 +84,17 @@ struct mapped_device { unsigned internal_suspend_count; /* - * Processing queue (flush) - */ - struct workqueue_struct *wq; - - /* * io objects are allocated from here. */ struct bio_set io_bs; struct bio_set bs; /* + * Processing queue (flush) + */ + struct workqueue_struct *wq; + + /* * freeze/thaw support require holding onto a super block */ struct super_block *frozen_sb; @@ -102,11 +102,11 @@ struct mapped_device { /* forced geometry settings */ struct hd_geometry geometry; - struct block_device *bdev; - /* kobject and completion */ struct dm_kobject_holder kobj_holder; + struct block_device *bdev; + /* zero-length flush that will be cloned and submitted to targets */ struct bio flush_bio; diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 57ca92d..b61b069 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -139,25 +139,13 @@ struct crypt_config { struct dm_dev *dev; sector_t start; - /* - * pool for per bio private data, crypto requests, - * encryption requeusts/buffer pages and integrity tags - */ - mempool_t req_pool; - mempool_t page_pool; - mempool_t tag_pool; - unsigned tag_pool_max_sectors; - struct percpu_counter n_allocated_pages; - struct bio_set bs; - struct mutex bio_alloc_lock; - struct workqueue_struct *io_queue; struct workqueue_struct *crypt_queue; - struct task_struct *write_thread; wait_queue_head_t write_thread_wait; + struct task_struct *write_thread; struct rb_root write_tree; char *cipher; @@ -213,6 +201,18 @@ struct crypt_config { unsigned int integrity_iv_size; unsigned int on_disk_tag_size; + /* + * pool for per bio private data, crypto requests, + * encryption requeusts/buffer pages and integrity tags + */ + unsigned tag_pool_max_sectors; + mempool_t tag_pool; + mempool_t req_pool; + mempool_t page_pool; + + struct bio_set bs; + struct mutex bio_alloc_lock; + u8 *authenc_key; /* space for keys in authenc() format (if used) */ u8 key[0]; }; diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index 5acf77d..b810ea7 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -1344,7 +1344,8 @@ static int table_load(struct file *filp, struct dm_ioctl *param, size_t param_si goto err_unlock_md_type; } } else if (!is_valid_type(dm_get_md_type(md), dm_table_get_type(t))) { - DMWARN("can't change device type after initial table load."); + DMWARN("can't change device type (old=%u vs new=%u) after initial table load.", + dm_get_md_type(md), dm_table_get_type(t)); r = -EINVAL; goto err_unlock_md_type; } diff --git a/drivers/md/dm-kcopyd.c b/drivers/md/dm-kcopyd.c index ce7efc7..3c7547a 100644 --- a/drivers/md/dm-kcopyd.c +++ b/drivers/md/dm-kcopyd.c @@ -45,7 +45,6 @@ struct dm_kcopyd_client { struct dm_io_client *io_client; wait_queue_head_t destroyq; - atomic_t nr_jobs; mempool_t job_pool; @@ -54,6 +53,8 @@ struct dm_kcopyd_client { struct dm_kcopyd_throttle *throttle; + atomic_t nr_jobs; + /* * We maintain three lists of jobs: * diff --git a/drivers/md/dm-region-hash.c b/drivers/md/dm-region-hash.c index bc77950..1f76045 100644 --- a/drivers/md/dm-region-hash.c +++ b/drivers/md/dm-region-hash.c @@ -63,27 +63,28 @@ struct dm_region_hash { /* hash table */ rwlock_t hash_lock; - mempool_t region_pool; unsigned mask; unsigned nr_buckets; unsigned prime; unsigned shift; struct list_head *buckets; + /* + * If there was a flush failure no regions can be marked clean. + */ + int flush_failure; + unsigned max_recovery; /* Max # of regions to recover in parallel */ spinlock_t region_lock; atomic_t recovery_in_flight; - struct semaphore recovery_count; struct list_head clean_regions; struct list_head quiesced_regions; struct list_head recovered_regions; struct list_head failed_recovered_regions; + struct semaphore recovery_count; - /* - * If there was a flush failure no regions can be marked clean. - */ - int flush_failure; + mempool_t region_pool; void *context; sector_t target_begin; diff --git a/drivers/md/dm-thin.c b/drivers/md/dm-thin.c index a913325..7945238d 100644 --- a/drivers/md/dm-thin.c +++ b/drivers/md/dm-thin.c @@ -240,9 +240,9 @@ struct pool { struct dm_bio_prison *prison; struct dm_kcopyd_client *copier; + struct work_struct worker; struct workqueue_struct *wq; struct throttle throttle; - struct work_struct worker; struct delayed_work waker; struct delayed_work no_space_timeout; @@ -260,7 +260,6 @@ struct pool { struct dm_deferred_set *all_io_ds; struct dm_thin_new_mapping *next_mapping; - mempool_t mapping_pool; process_bio_fn process_bio; process_bio_fn process_discard; @@ -273,6 +272,8 @@ struct pool { process_mapping_fn process_prepared_discard_pt2; struct dm_bio_prison_cell **cell_sort_array; + + mempool_t mapping_pool; }; static enum pool_mode get_pool_mode(struct pool *pool); diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c new file mode 100644 index 0000000..5961c77 --- /dev/null +++ b/drivers/md/dm-writecache.c @@ -0,0 +1,2305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Red Hat. All rights reserved. + * + * This file is released under the GPL. + */ + +#include <linux/device-mapper.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/kthread.h> +#include <linux/dm-io.h> +#include <linux/dm-kcopyd.h> +#include <linux/dax.h> +#include <linux/pfn_t.h> +#include <linux/libnvdimm.h> + +#define DM_MSG_PREFIX "writecache" + +#define HIGH_WATERMARK 50 +#define LOW_WATERMARK 45 +#define MAX_WRITEBACK_JOBS 0 +#define ENDIO_LATENCY 16 +#define WRITEBACK_LATENCY 64 +#define AUTOCOMMIT_BLOCKS_SSD 65536 +#define AUTOCOMMIT_BLOCKS_PMEM 64 +#define AUTOCOMMIT_MSEC 1000 + +#define BITMAP_GRANULARITY 65536 +#if BITMAP_GRANULARITY < PAGE_SIZE +#undef BITMAP_GRANULARITY +#define BITMAP_GRANULARITY PAGE_SIZE +#endif + +#if IS_ENABLED(CONFIG_ARCH_HAS_PMEM_API) && IS_ENABLED(CONFIG_DAX_DRIVER) +#define DM_WRITECACHE_HAS_PMEM +#endif + +#ifdef DM_WRITECACHE_HAS_PMEM +#define pmem_assign(dest, src) \ +do { \ + typeof(dest) uniq = (src); \ + memcpy_flushcache(&(dest), &uniq, sizeof(dest)); \ +} while (0) +#else +#define pmem_assign(dest, src) ((dest) = (src)) +#endif + +#if defined(__HAVE_ARCH_MEMCPY_MCSAFE) && defined(DM_WRITECACHE_HAS_PMEM) +#define DM_WRITECACHE_HANDLE_HARDWARE_ERRORS +#endif + +#define MEMORY_SUPERBLOCK_MAGIC 0x23489321 +#define MEMORY_SUPERBLOCK_VERSION 1 + +struct wc_memory_entry { + __le64 original_sector; + __le64 seq_count; +}; + +struct wc_memory_superblock { + union { + struct { + __le32 magic; + __le32 version; + __le32 block_size; + __le32 pad; + __le64 n_blocks; + __le64 seq_count; + }; + __le64 padding[8]; + }; + struct wc_memory_entry entries[0]; +}; + +struct wc_entry { + struct rb_node rb_node; + struct list_head lru; + unsigned short wc_list_contiguous; + bool write_in_progress +#if BITS_PER_LONG == 64 + :1 +#endif + ; + unsigned long index +#if BITS_PER_LONG == 64 + :47 +#endif + ; +#ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS + uint64_t original_sector; + uint64_t seq_count; +#endif +}; + +#ifdef DM_WRITECACHE_HAS_PMEM +#define WC_MODE_PMEM(wc) ((wc)->pmem_mode) +#define WC_MODE_FUA(wc) ((wc)->writeback_fua) +#else +#define WC_MODE_PMEM(wc) false +#define WC_MODE_FUA(wc) false +#endif +#define WC_MODE_SORT_FREELIST(wc) (!WC_MODE_PMEM(wc)) + +struct dm_writecache { + struct mutex lock; + struct list_head lru; + union { + struct list_head freelist; + struct { + struct rb_root freetree; + struct wc_entry *current_free; + }; + }; + struct rb_root tree; + + size_t freelist_size; + size_t writeback_size; + size_t freelist_high_watermark; + size_t freelist_low_watermark; + + unsigned uncommitted_blocks; + unsigned autocommit_blocks; + unsigned max_writeback_jobs; + + int error; + + unsigned long autocommit_jiffies; + struct timer_list autocommit_timer; + struct wait_queue_head freelist_wait; + + atomic_t bio_in_progress[2]; + struct wait_queue_head bio_in_progress_wait[2]; + + struct dm_target *ti; + struct dm_dev *dev; + struct dm_dev *ssd_dev; + void *memory_map; + uint64_t memory_map_size; + size_t metadata_sectors; + size_t n_blocks; + uint64_t seq_count; + void *block_start; + struct wc_entry *entries; + unsigned block_size; + unsigned char block_size_bits; + + bool pmem_mode:1; + bool writeback_fua:1; + + bool overwrote_committed:1; + bool memory_vmapped:1; + + bool high_wm_percent_set:1; + bool low_wm_percent_set:1; + bool max_writeback_jobs_set:1; + bool autocommit_blocks_set:1; + bool autocommit_time_set:1; + bool writeback_fua_set:1; + bool flush_on_suspend:1; + + unsigned writeback_all; + struct workqueue_struct *writeback_wq; + struct work_struct writeback_work; + struct work_struct flush_work; + + struct dm_io_client *dm_io; + + raw_spinlock_t endio_list_lock; + struct list_head endio_list; + struct task_struct *endio_thread; + + struct task_struct *flush_thread; + struct bio_list flush_list; + + struct dm_kcopyd_client *dm_kcopyd; + unsigned long *dirty_bitmap; + unsigned dirty_bitmap_size; + + struct bio_set bio_set; + mempool_t copy_pool; +}; + +#define WB_LIST_INLINE 16 + +struct writeback_struct { + struct list_head endio_entry; + struct dm_writecache *wc; + struct wc_entry **wc_list; + unsigned wc_list_n; + unsigned page_offset; + struct page *page; + struct wc_entry *wc_list_inline[WB_LIST_INLINE]; + struct bio bio; +}; + +struct copy_struct { + struct list_head endio_entry; + struct dm_writecache *wc; + struct wc_entry *e; + unsigned n_entries; + int error; +}; + +DECLARE_DM_KCOPYD_THROTTLE_WITH_MODULE_PARM(dm_writecache_throttle, + "A percentage of time allocated for data copying"); + +static void wc_lock(struct dm_writecache *wc) +{ + mutex_lock(&wc->lock); +} + +static void wc_unlock(struct dm_writecache *wc) +{ + mutex_unlock(&wc->lock); +} + +#ifdef DM_WRITECACHE_HAS_PMEM +static int persistent_memory_claim(struct dm_writecache *wc) +{ + int r; + loff_t s; + long p, da; + pfn_t pfn; + int id; + struct page **pages; + + wc->memory_vmapped = false; + + if (!wc->ssd_dev->dax_dev) { + r = -EOPNOTSUPP; + goto err1; + } + s = wc->memory_map_size; + p = s >> PAGE_SHIFT; + if (!p) { + r = -EINVAL; + goto err1; + } + if (p != s >> PAGE_SHIFT) { + r = -EOVERFLOW; + goto err1; + } + + id = dax_read_lock(); + + da = dax_direct_access(wc->ssd_dev->dax_dev, 0, p, &wc->memory_map, &pfn); + if (da < 0) { + wc->memory_map = NULL; + r = da; + goto err2; + } + if (!pfn_t_has_page(pfn)) { + wc->memory_map = NULL; + r = -EOPNOTSUPP; + goto err2; + } + if (da != p) { + long i; + wc->memory_map = NULL; + pages = kvmalloc(p * sizeof(struct page *), GFP_KERNEL); + if (!pages) { + r = -ENOMEM; + goto err2; + } + i = 0; + do { + long daa; + void *dummy_addr; + daa = dax_direct_access(wc->ssd_dev->dax_dev, i, p - i, + &dummy_addr, &pfn); + if (daa <= 0) { + r = daa ? daa : -EINVAL; + goto err3; + } + if (!pfn_t_has_page(pfn)) { + r = -EOPNOTSUPP; + goto err3; + } + while (daa-- && i < p) { + pages[i++] = pfn_t_to_page(pfn); + pfn.val++; + } + } while (i < p); + wc->memory_map = vmap(pages, p, VM_MAP, PAGE_KERNEL); + if (!wc->memory_map) { + r = -ENOMEM; + goto err3; + } + kvfree(pages); + wc->memory_vmapped = true; + } + + dax_read_unlock(id); + return 0; +err3: + kvfree(pages); +err2: + dax_read_unlock(id); +err1: + return r; +} +#else +static int persistent_memory_claim(struct dm_writecache *wc) +{ + BUG(); +} +#endif + +static void persistent_memory_release(struct dm_writecache *wc) +{ + if (wc->memory_vmapped) + vunmap(wc->memory_map); +} + +static struct page *persistent_memory_page(void *addr) +{ + if (is_vmalloc_addr(addr)) + return vmalloc_to_page(addr); + else + return virt_to_page(addr); +} + +static unsigned persistent_memory_page_offset(void *addr) +{ + return (unsigned long)addr & (PAGE_SIZE - 1); +} + +static void persistent_memory_flush_cache(void *ptr, size_t size) +{ + if (is_vmalloc_addr(ptr)) + flush_kernel_vmap_range(ptr, size); +} + +static void persistent_memory_invalidate_cache(void *ptr, size_t size) +{ + if (is_vmalloc_addr(ptr)) + invalidate_kernel_vmap_range(ptr, size); +} + +static struct wc_memory_superblock *sb(struct dm_writecache *wc) +{ + return wc->memory_map; +} + +static struct wc_memory_entry *memory_entry(struct dm_writecache *wc, struct wc_entry *e) +{ + if (is_power_of_2(sizeof(struct wc_entry)) && 0) + return &sb(wc)->entries[e - wc->entries]; + else + return &sb(wc)->entries[e->index]; +} + +static void *memory_data(struct dm_writecache *wc, struct wc_entry *e) +{ + return (char *)wc->block_start + (e->index << wc->block_size_bits); +} + +static sector_t cache_sector(struct dm_writecache *wc, struct wc_entry *e) +{ + return wc->metadata_sectors + + ((sector_t)e->index << (wc->block_size_bits - SECTOR_SHIFT)); +} + +static uint64_t read_original_sector(struct dm_writecache *wc, struct wc_entry *e) +{ +#ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS + return e->original_sector; +#else + return le64_to_cpu(memory_entry(wc, e)->original_sector); +#endif +} + +static uint64_t read_seq_count(struct dm_writecache *wc, struct wc_entry *e) +{ +#ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS + return e->seq_count; +#else + return le64_to_cpu(memory_entry(wc, e)->seq_count); +#endif +} + +static void clear_seq_count(struct dm_writecache *wc, struct wc_entry *e) +{ +#ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS + e->seq_count = -1; +#endif + pmem_assign(memory_entry(wc, e)->seq_count, cpu_to_le64(-1)); +} + +static void write_original_sector_seq_count(struct dm_writecache *wc, struct wc_entry *e, + uint64_t original_sector, uint64_t seq_count) +{ + struct wc_memory_entry me; +#ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS + e->original_sector = original_sector; + e->seq_count = seq_count; +#endif + me.original_sector = cpu_to_le64(original_sector); + me.seq_count = cpu_to_le64(seq_count); + pmem_assign(*memory_entry(wc, e), me); +} + +#define writecache_error(wc, err, msg, arg...) \ +do { \ + if (!cmpxchg(&(wc)->error, 0, err)) \ + DMERR(msg, ##arg); \ + wake_up(&(wc)->freelist_wait); \ +} while (0) + +#define writecache_has_error(wc) (unlikely(READ_ONCE((wc)->error))) + +static void writecache_flush_all_metadata(struct dm_writecache *wc) +{ + if (!WC_MODE_PMEM(wc)) + memset(wc->dirty_bitmap, -1, wc->dirty_bitmap_size); +} + +static void writecache_flush_region(struct dm_writecache *wc, void *ptr, size_t size) +{ + if (!WC_MODE_PMEM(wc)) + __set_bit(((char *)ptr - (char *)wc->memory_map) / BITMAP_GRANULARITY, + wc->dirty_bitmap); +} + +static void writecache_disk_flush(struct dm_writecache *wc, struct dm_dev *dev); + +struct io_notify { + struct dm_writecache *wc; + struct completion c; + atomic_t count; +}; + +static void writecache_notify_io(unsigned long error, void *context) +{ + struct io_notify *endio = context; + + if (unlikely(error != 0)) + writecache_error(endio->wc, -EIO, "error writing metadata"); + BUG_ON(atomic_read(&endio->count) <= 0); + if (atomic_dec_and_test(&endio->count)) + complete(&endio->c); +} + +static void ssd_commit_flushed(struct dm_writecache *wc) +{ + struct dm_io_region region; + struct dm_io_request req; + struct io_notify endio = { + wc, + COMPLETION_INITIALIZER_ONSTACK(endio.c), + ATOMIC_INIT(1), + }; + unsigned bitmap_bits = wc->dirty_bitmap_size * BITS_PER_LONG; + unsigned i = 0; + + while (1) { + unsigned j; + i = find_next_bit(wc->dirty_bitmap, bitmap_bits, i); + if (unlikely(i == bitmap_bits)) + break; + j = find_next_zero_bit(wc->dirty_bitmap, bitmap_bits, i); + + region.bdev = wc->ssd_dev->bdev; + region.sector = (sector_t)i * (BITMAP_GRANULARITY >> SECTOR_SHIFT); + region.count = (sector_t)(j - i) * (BITMAP_GRANULARITY >> SECTOR_SHIFT); + + if (unlikely(region.sector >= wc->metadata_sectors)) + break; + if (unlikely(region.sector + region.count > wc->metadata_sectors)) + region.count = wc->metadata_sectors - region.sector; + + atomic_inc(&endio.count); + req.bi_op = REQ_OP_WRITE; + req.bi_op_flags = REQ_SYNC; + req.mem.type = DM_IO_VMA; + req.mem.ptr.vma = (char *)wc->memory_map + (size_t)i * BITMAP_GRANULARITY; + req.client = wc->dm_io; + req.notify.fn = writecache_notify_io; + req.notify.context = &endio; + + /* writing via async dm-io (implied by notify.fn above) won't return an error */ + (void) dm_io(&req, 1, ®ion, NULL); + i = j; + } + + writecache_notify_io(0, &endio); + wait_for_completion_io(&endio.c); + + writecache_disk_flush(wc, wc->ssd_dev); + + memset(wc->dirty_bitmap, 0, wc->dirty_bitmap_size); +} + +static void writecache_commit_flushed(struct dm_writecache *wc) +{ + if (WC_MODE_PMEM(wc)) + wmb(); + else + ssd_commit_flushed(wc); +} + +static void writecache_disk_flush(struct dm_writecache *wc, struct dm_dev *dev) +{ + int r; + struct dm_io_region region; + struct dm_io_request req; + + region.bdev = dev->bdev; + region.sector = 0; + region.count = 0; + req.bi_op = REQ_OP_WRITE; + req.bi_op_flags = REQ_PREFLUSH; + req.mem.type = DM_IO_KMEM; + req.mem.ptr.addr = NULL; + req.client = wc->dm_io; + req.notify.fn = NULL; + + r = dm_io(&req, 1, ®ion, NULL); + if (unlikely(r)) + writecache_error(wc, r, "error flushing metadata: %d", r); +} + +static void writecache_wait_for_ios(struct dm_writecache *wc, int direction) +{ + wait_event(wc->bio_in_progress_wait[direction], + !atomic_read(&wc->bio_in_progress[direction])); +} + +#define WFE_RETURN_FOLLOWING 1 +#define WFE_LOWEST_SEQ 2 + +static struct wc_entry *writecache_find_entry(struct dm_writecache *wc, + uint64_t block, int flags) +{ + struct wc_entry *e; + struct rb_node *node = wc->tree.rb_node; + + if (unlikely(!node)) + return NULL; + + while (1) { + e = container_of(node, struct wc_entry, rb_node); + if (read_original_sector(wc, e) == block) + break; + node = (read_original_sector(wc, e) >= block ? + e->rb_node.rb_left : e->rb_node.rb_right); + if (unlikely(!node)) { + if (!(flags & WFE_RETURN_FOLLOWING)) { + return NULL; + } + if (read_original_sector(wc, e) >= block) { + break; + } else { + node = rb_next(&e->rb_node); + if (unlikely(!node)) { + return NULL; + } + e = container_of(node, struct wc_entry, rb_node); + break; + } + } + } + + while (1) { + struct wc_entry *e2; + if (flags & WFE_LOWEST_SEQ) + node = rb_prev(&e->rb_node); + else + node = rb_next(&e->rb_node); + if (!node) + return e; + e2 = container_of(node, struct wc_entry, rb_node); + if (read_original_sector(wc, e2) != block) + return e; + e = e2; + } +} + +static void writecache_insert_entry(struct dm_writecache *wc, struct wc_entry *ins) +{ + struct wc_entry *e; + struct rb_node **node = &wc->tree.rb_node, *parent = NULL; + + while (*node) { + e = container_of(*node, struct wc_entry, rb_node); + parent = &e->rb_node; + if (read_original_sector(wc, e) > read_original_sector(wc, ins)) + node = &parent->rb_left; + else + node = &parent->rb_right; + } + rb_link_node(&ins->rb_node, parent, node); + rb_insert_color(&ins->rb_node, &wc->tree); + list_add(&ins->lru, &wc->lru); +} + +static void writecache_unlink(struct dm_writecache *wc, struct wc_entry *e) +{ + list_del(&e->lru); + rb_erase(&e->rb_node, &wc->tree); +} + +static void writecache_add_to_freelist(struct dm_writecache *wc, struct wc_entry *e) +{ + if (WC_MODE_SORT_FREELIST(wc)) { + struct rb_node **node = &wc->freetree.rb_node, *parent = NULL; + if (unlikely(!*node)) + wc->current_free = e; + while (*node) { + parent = *node; + if (&e->rb_node < *node) + node = &parent->rb_left; + else + node = &parent->rb_right; + } + rb_link_node(&e->rb_node, parent, node); + rb_insert_color(&e->rb_node, &wc->freetree); + } else { + list_add_tail(&e->lru, &wc->freelist); + } + wc->freelist_size++; +} + +static struct wc_entry *writecache_pop_from_freelist(struct dm_writecache *wc) +{ + struct wc_entry *e; + + if (WC_MODE_SORT_FREELIST(wc)) { + struct rb_node *next; + if (unlikely(!wc->current_free)) + return NULL; + e = wc->current_free; + next = rb_next(&e->rb_node); + rb_erase(&e->rb_node, &wc->freetree); + if (unlikely(!next)) + next = rb_first(&wc->freetree); + wc->current_free = next ? container_of(next, struct wc_entry, rb_node) : NULL; + } else { + if (unlikely(list_empty(&wc->freelist))) + return NULL; + e = container_of(wc->freelist.next, struct wc_entry, lru); + list_del(&e->lru); + } + wc->freelist_size--; + if (unlikely(wc->freelist_size + wc->writeback_size <= wc->freelist_high_watermark)) + queue_work(wc->writeback_wq, &wc->writeback_work); + + return e; +} + +static void writecache_free_entry(struct dm_writecache *wc, struct wc_entry *e) +{ + writecache_unlink(wc, e); + writecache_add_to_freelist(wc, e); + clear_seq_count(wc, e); + writecache_flush_region(wc, memory_entry(wc, e), sizeof(struct wc_memory_entry)); + if (unlikely(waitqueue_active(&wc->freelist_wait))) + wake_up(&wc->freelist_wait); +} + +static void writecache_wait_on_freelist(struct dm_writecache *wc) +{ + DEFINE_WAIT(wait); + + prepare_to_wait(&wc->freelist_wait, &wait, TASK_UNINTERRUPTIBLE); + wc_unlock(wc); + io_schedule(); + finish_wait(&wc->freelist_wait, &wait); + wc_lock(wc); +} + +static void writecache_poison_lists(struct dm_writecache *wc) +{ + /* + * Catch incorrect access to these values while the device is suspended. + */ + memset(&wc->tree, -1, sizeof wc->tree); + wc->lru.next = LIST_POISON1; + wc->lru.prev = LIST_POISON2; + wc->freelist.next = LIST_POISON1; + wc->freelist.prev = LIST_POISON2; +} + +static void writecache_flush_entry(struct dm_writecache *wc, struct wc_entry *e) +{ + writecache_flush_region(wc, memory_entry(wc, e), sizeof(struct wc_memory_entry)); + if (WC_MODE_PMEM(wc)) + writecache_flush_region(wc, memory_data(wc, e), wc->block_size); +} + +static bool writecache_entry_is_committed(struct dm_writecache *wc, struct wc_entry *e) +{ + return read_seq_count(wc, e) < wc->seq_count; +} + +static void writecache_flush(struct dm_writecache *wc) +{ + struct wc_entry *e, *e2; + bool need_flush_after_free; + + wc->uncommitted_blocks = 0; + del_timer(&wc->autocommit_timer); + + if (list_empty(&wc->lru)) + return; + + e = container_of(wc->lru.next, struct wc_entry, lru); + if (writecache_entry_is_committed(wc, e)) { + if (wc->overwrote_committed) { + writecache_wait_for_ios(wc, WRITE); + writecache_disk_flush(wc, wc->ssd_dev); + wc->overwrote_committed = false; + } + return; + } + while (1) { + writecache_flush_entry(wc, e); + if (unlikely(e->lru.next == &wc->lru)) + break; + e2 = container_of(e->lru.next, struct wc_entry, lru); + if (writecache_entry_is_committed(wc, e2)) + break; + e = e2; + cond_resched(); + } + writecache_commit_flushed(wc); + + writecache_wait_for_ios(wc, WRITE); + + wc->seq_count++; + pmem_assign(sb(wc)->seq_count, cpu_to_le64(wc->seq_count)); + writecache_flush_region(wc, &sb(wc)->seq_count, sizeof sb(wc)->seq_count); + writecache_commit_flushed(wc); + + wc->overwrote_committed = false; + + need_flush_after_free = false; + while (1) { + /* Free another committed entry with lower seq-count */ + struct rb_node *rb_node = rb_prev(&e->rb_node); + + if (rb_node) { + e2 = container_of(rb_node, struct wc_entry, rb_node); + if (read_original_sector(wc, e2) == read_original_sector(wc, e) && + likely(!e2->write_in_progress)) { + writecache_free_entry(wc, e2); + need_flush_after_free = true; + } + } + if (unlikely(e->lru.prev == &wc->lru)) + break; + e = container_of(e->lru.prev, struct wc_entry, lru); + cond_resched(); + } + + if (need_flush_after_free) + writecache_commit_flushed(wc); +} + +static void writecache_flush_work(struct work_struct *work) +{ + struct dm_writecache *wc = container_of(work, struct dm_writecache, flush_work); + + wc_lock(wc); + writecache_flush(wc); + wc_unlock(wc); +} + +static void writecache_autocommit_timer(struct timer_list *t) +{ + struct dm_writecache *wc = from_timer(wc, t, autocommit_timer); + if (!writecache_has_error(wc)) + queue_work(wc->writeback_wq, &wc->flush_work); +} + +static void writecache_schedule_autocommit(struct dm_writecache *wc) +{ + if (!timer_pending(&wc->autocommit_timer)) + mod_timer(&wc->autocommit_timer, jiffies + wc->autocommit_jiffies); +} + +static void writecache_discard(struct dm_writecache *wc, sector_t start, sector_t end) +{ + struct wc_entry *e; + bool discarded_something = false; + + e = writecache_find_entry(wc, start, WFE_RETURN_FOLLOWING | WFE_LOWEST_SEQ); + if (unlikely(!e)) + return; + + while (read_original_sector(wc, e) < end) { + struct rb_node *node = rb_next(&e->rb_node); + + if (likely(!e->write_in_progress)) { + if (!discarded_something) { + writecache_wait_for_ios(wc, READ); + writecache_wait_for_ios(wc, WRITE); + discarded_something = true; + } + writecache_free_entry(wc, e); + } + + if (!node) + break; + + e = container_of(node, struct wc_entry, rb_node); + } + + if (discarded_something) + writecache_commit_flushed(wc); +} + +static bool writecache_wait_for_writeback(struct dm_writecache *wc) +{ + if (wc->writeback_size) { + writecache_wait_on_freelist(wc); + return true; + } + return false; +} + +static void writecache_suspend(struct dm_target *ti) +{ + struct dm_writecache *wc = ti->private; + bool flush_on_suspend; + + del_timer_sync(&wc->autocommit_timer); + + wc_lock(wc); + writecache_flush(wc); + flush_on_suspend = wc->flush_on_suspend; + if (flush_on_suspend) { + wc->flush_on_suspend = false; + wc->writeback_all++; + queue_work(wc->writeback_wq, &wc->writeback_work); + } + wc_unlock(wc); + + flush_workqueue(wc->writeback_wq); + + wc_lock(wc); + if (flush_on_suspend) + wc->writeback_all--; + while (writecache_wait_for_writeback(wc)); + + if (WC_MODE_PMEM(wc)) + persistent_memory_flush_cache(wc->memory_map, wc->memory_map_size); + + writecache_poison_lists(wc); + + wc_unlock(wc); +} + +static int writecache_alloc_entries(struct dm_writecache *wc) +{ + size_t b; + + if (wc->entries) + return 0; + wc->entries = vmalloc(sizeof(struct wc_entry) * wc->n_blocks); + if (!wc->entries) + return -ENOMEM; + for (b = 0; b < wc->n_blocks; b++) { + struct wc_entry *e = &wc->entries[b]; + e->index = b; + e->write_in_progress = false; + } + + return 0; +} + +static void writecache_resume(struct dm_target *ti) +{ + struct dm_writecache *wc = ti->private; + size_t b; + bool need_flush = false; + __le64 sb_seq_count; + int r; + + wc_lock(wc); + + if (WC_MODE_PMEM(wc)) + persistent_memory_invalidate_cache(wc->memory_map, wc->memory_map_size); + + wc->tree = RB_ROOT; + INIT_LIST_HEAD(&wc->lru); + if (WC_MODE_SORT_FREELIST(wc)) { + wc->freetree = RB_ROOT; + wc->current_free = NULL; + } else { + INIT_LIST_HEAD(&wc->freelist); + } + wc->freelist_size = 0; + + r = memcpy_mcsafe(&sb_seq_count, &sb(wc)->seq_count, sizeof(uint64_t)); + if (r) { + writecache_error(wc, r, "hardware memory error when reading superblock: %d", r); + sb_seq_count = cpu_to_le64(0); + } + wc->seq_count = le64_to_cpu(sb_seq_count); + +#ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS + for (b = 0; b < wc->n_blocks; b++) { + struct wc_entry *e = &wc->entries[b]; + struct wc_memory_entry wme; + if (writecache_has_error(wc)) { + e->original_sector = -1; + e->seq_count = -1; + continue; + } + r = memcpy_mcsafe(&wme, memory_entry(wc, e), sizeof(struct wc_memory_entry)); + if (r) { + writecache_error(wc, r, "hardware memory error when reading metadata entry %lu: %d", + (unsigned long)b, r); + e->original_sector = -1; + e->seq_count = -1; + } else { + e->original_sector = le64_to_cpu(wme.original_sector); + e->seq_count = le64_to_cpu(wme.seq_count); + } + } +#endif + for (b = 0; b < wc->n_blocks; b++) { + struct wc_entry *e = &wc->entries[b]; + if (!writecache_entry_is_committed(wc, e)) { + if (read_seq_count(wc, e) != -1) { +erase_this: + clear_seq_count(wc, e); + need_flush = true; + } + writecache_add_to_freelist(wc, e); + } else { + struct wc_entry *old; + + old = writecache_find_entry(wc, read_original_sector(wc, e), 0); + if (!old) { + writecache_insert_entry(wc, e); + } else { + if (read_seq_count(wc, old) == read_seq_count(wc, e)) { + writecache_error(wc, -EINVAL, + "two identical entries, position %llu, sector %llu, sequence %llu", + (unsigned long long)b, (unsigned long long)read_original_sector(wc, e), + (unsigned long long)read_seq_count(wc, e)); + } + if (read_seq_count(wc, old) > read_seq_count(wc, e)) { + goto erase_this; + } else { + writecache_free_entry(wc, old); + writecache_insert_entry(wc, e); + need_flush = true; + } + } + } + cond_resched(); + } + + if (need_flush) { + writecache_flush_all_metadata(wc); + writecache_commit_flushed(wc); + } + + wc_unlock(wc); +} + +static int process_flush_mesg(unsigned argc, char **argv, struct dm_writecache *wc) +{ + if (argc != 1) + return -EINVAL; + + wc_lock(wc); + if (dm_suspended(wc->ti)) { + wc_unlock(wc); + return -EBUSY; + } + if (writecache_has_error(wc)) { + wc_unlock(wc); + return -EIO; + } + + writecache_flush(wc); + wc->writeback_all++; + queue_work(wc->writeback_wq, &wc->writeback_work); + wc_unlock(wc); + + flush_workqueue(wc->writeback_wq); + + wc_lock(wc); + wc->writeback_all--; + if (writecache_has_error(wc)) { + wc_unlock(wc); + return -EIO; + } + wc_unlock(wc); + + return 0; +} + +static int process_flush_on_suspend_mesg(unsigned argc, char **argv, struct dm_writecache *wc) +{ + if (argc != 1) + return -EINVAL; + + wc_lock(wc); + wc->flush_on_suspend = true; + wc_unlock(wc); + + return 0; +} + +static int writecache_message(struct dm_target *ti, unsigned argc, char **argv, + char *result, unsigned maxlen) +{ + int r = -EINVAL; + struct dm_writecache *wc = ti->private; + + if (!strcasecmp(argv[0], "flush")) + r = process_flush_mesg(argc, argv, wc); + else if (!strcasecmp(argv[0], "flush_on_suspend")) + r = process_flush_on_suspend_mesg(argc, argv, wc); + else + DMERR("unrecognised message received: %s", argv[0]); + + return r; +} + +static void bio_copy_block(struct dm_writecache *wc, struct bio *bio, void *data) +{ + void *buf; + unsigned long flags; + unsigned size; + int rw = bio_data_dir(bio); + unsigned remaining_size = wc->block_size; + + do { + struct bio_vec bv = bio_iter_iovec(bio, bio->bi_iter); + buf = bvec_kmap_irq(&bv, &flags); + size = bv.bv_len; + if (unlikely(size > remaining_size)) + size = remaining_size; + + if (rw == READ) { + int r; + r = memcpy_mcsafe(buf, data, size); + flush_dcache_page(bio_page(bio)); + if (unlikely(r)) { + writecache_error(wc, r, "hardware memory error when reading data: %d", r); + bio->bi_status = BLK_STS_IOERR; + } + } else { + flush_dcache_page(bio_page(bio)); + memcpy_flushcache(data, buf, size); + } + + bvec_kunmap_irq(buf, &flags); + + data = (char *)data + size; + remaining_size -= size; + bio_advance(bio, size); + } while (unlikely(remaining_size)); +} + +static int writecache_flush_thread(void *data) +{ + struct dm_writecache *wc = data; + + while (1) { + struct bio *bio; + + wc_lock(wc); + bio = bio_list_pop(&wc->flush_list); + if (!bio) { + set_current_state(TASK_INTERRUPTIBLE); + wc_unlock(wc); + + if (unlikely(kthread_should_stop())) { + set_current_state(TASK_RUNNING); + break; + } + + schedule(); + continue; + } + + if (bio_op(bio) == REQ_OP_DISCARD) { + writecache_discard(wc, bio->bi_iter.bi_sector, + bio_end_sector(bio)); + wc_unlock(wc); + bio_set_dev(bio, wc->dev->bdev); + generic_make_request(bio); + } else { + writecache_flush(wc); + wc_unlock(wc); + if (writecache_has_error(wc)) + bio->bi_status = BLK_STS_IOERR; + bio_endio(bio); + } + } + + return 0; +} + +static void writecache_offload_bio(struct dm_writecache *wc, struct bio *bio) +{ + if (bio_list_empty(&wc->flush_list)) + wake_up_process(wc->flush_thread); + bio_list_add(&wc->flush_list, bio); +} + +static int writecache_map(struct dm_target *ti, struct bio *bio) +{ + struct wc_entry *e; + struct dm_writecache *wc = ti->private; + + bio->bi_private = NULL; + + wc_lock(wc); + + if (unlikely(bio->bi_opf & REQ_PREFLUSH)) { + if (writecache_has_error(wc)) + goto unlock_error; + if (WC_MODE_PMEM(wc)) { + writecache_flush(wc); + if (writecache_has_error(wc)) + goto unlock_error; + goto unlock_submit; + } else { + writecache_offload_bio(wc, bio); + goto unlock_return; + } + } + + bio->bi_iter.bi_sector = dm_target_offset(ti, bio->bi_iter.bi_sector); + + if (unlikely((((unsigned)bio->bi_iter.bi_sector | bio_sectors(bio)) & + (wc->block_size / 512 - 1)) != 0)) { + DMERR("I/O is not aligned, sector %llu, size %u, block size %u", + (unsigned long long)bio->bi_iter.bi_sector, + bio->bi_iter.bi_size, wc->block_size); + goto unlock_error; + } + + if (unlikely(bio_op(bio) == REQ_OP_DISCARD)) { + if (writecache_has_error(wc)) + goto unlock_error; + if (WC_MODE_PMEM(wc)) { + writecache_discard(wc, bio->bi_iter.bi_sector, bio_end_sector(bio)); + goto unlock_remap_origin; + } else { + writecache_offload_bio(wc, bio); + goto unlock_return; + } + } + + if (bio_data_dir(bio) == READ) { +read_next_block: + e = writecache_find_entry(wc, bio->bi_iter.bi_sector, WFE_RETURN_FOLLOWING); + if (e && read_original_sector(wc, e) == bio->bi_iter.bi_sector) { + if (WC_MODE_PMEM(wc)) { + bio_copy_block(wc, bio, memory_data(wc, e)); + if (bio->bi_iter.bi_size) + goto read_next_block; + goto unlock_submit; + } else { + dm_accept_partial_bio(bio, wc->block_size >> SECTOR_SHIFT); + bio_set_dev(bio, wc->ssd_dev->bdev); + bio->bi_iter.bi_sector = cache_sector(wc, e); + if (!writecache_entry_is_committed(wc, e)) + writecache_wait_for_ios(wc, WRITE); + goto unlock_remap; + } + } else { + if (e) { + sector_t next_boundary = + read_original_sector(wc, e) - bio->bi_iter.bi_sector; + if (next_boundary < bio->bi_iter.bi_size >> SECTOR_SHIFT) { + dm_accept_partial_bio(bio, next_boundary); + } + } + goto unlock_remap_origin; + } + } else { + do { + if (writecache_has_error(wc)) + goto unlock_error; + e = writecache_find_entry(wc, bio->bi_iter.bi_sector, 0); + if (e) { + if (!writecache_entry_is_committed(wc, e)) + goto bio_copy; + if (!WC_MODE_PMEM(wc) && !e->write_in_progress) { + wc->overwrote_committed = true; + goto bio_copy; + } + } + e = writecache_pop_from_freelist(wc); + if (unlikely(!e)) { + writecache_wait_on_freelist(wc); + continue; + } + write_original_sector_seq_count(wc, e, bio->bi_iter.bi_sector, wc->seq_count); + writecache_insert_entry(wc, e); + wc->uncommitted_blocks++; +bio_copy: + if (WC_MODE_PMEM(wc)) { + bio_copy_block(wc, bio, memory_data(wc, e)); + } else { + dm_accept_partial_bio(bio, wc->block_size >> SECTOR_SHIFT); + bio_set_dev(bio, wc->ssd_dev->bdev); + bio->bi_iter.bi_sector = cache_sector(wc, e); + if (unlikely(wc->uncommitted_blocks >= wc->autocommit_blocks)) { + wc->uncommitted_blocks = 0; + queue_work(wc->writeback_wq, &wc->flush_work); + } else { + writecache_schedule_autocommit(wc); + } + goto unlock_remap; + } + } while (bio->bi_iter.bi_size); + + if (unlikely(wc->uncommitted_blocks >= wc->autocommit_blocks)) + writecache_flush(wc); + else + writecache_schedule_autocommit(wc); + goto unlock_submit; + } + +unlock_remap_origin: + bio_set_dev(bio, wc->dev->bdev); + wc_unlock(wc); + return DM_MAPIO_REMAPPED; + +unlock_remap: + /* make sure that writecache_end_io decrements bio_in_progress: */ + bio->bi_private = (void *)1; + atomic_inc(&wc->bio_in_progress[bio_data_dir(bio)]); + wc_unlock(wc); + return DM_MAPIO_REMAPPED; + +unlock_submit: + wc_unlock(wc); + bio_endio(bio); + return DM_MAPIO_SUBMITTED; + +unlock_return: + wc_unlock(wc); + return DM_MAPIO_SUBMITTED; + +unlock_error: + wc_unlock(wc); + bio_io_error(bio); + return DM_MAPIO_SUBMITTED; +} + +static int writecache_end_io(struct dm_target *ti, struct bio *bio, blk_status_t *status) +{ + struct dm_writecache *wc = ti->private; + + if (bio->bi_private != NULL) { + int dir = bio_data_dir(bio); + if (atomic_dec_and_test(&wc->bio_in_progress[dir])) + if (unlikely(waitqueue_active(&wc->bio_in_progress_wait[dir]))) + wake_up(&wc->bio_in_progress_wait[dir]); + } + return 0; +} + +static int writecache_iterate_devices(struct dm_target *ti, + iterate_devices_callout_fn fn, void *data) +{ + struct dm_writecache *wc = ti->private; + + return fn(ti, wc->dev, 0, ti->len, data); +} + +static void writecache_io_hints(struct dm_target *ti, struct queue_limits *limits) +{ + struct dm_writecache *wc = ti->private; + + if (limits->logical_block_size < wc->block_size) + limits->logical_block_size = wc->block_size; + + if (limits->physical_block_size < wc->block_size) + limits->physical_block_size = wc->block_size; + + if (limits->io_min < wc->block_size) + limits->io_min = wc->block_size; +} + + +static void writecache_writeback_endio(struct bio *bio) +{ + struct writeback_struct *wb = container_of(bio, struct writeback_struct, bio); + struct dm_writecache *wc = wb->wc; + unsigned long flags; + + raw_spin_lock_irqsave(&wc->endio_list_lock, flags); + if (unlikely(list_empty(&wc->endio_list))) + wake_up_process(wc->endio_thread); + list_add_tail(&wb->endio_entry, &wc->endio_list); + raw_spin_unlock_irqrestore(&wc->endio_list_lock, flags); +} + +static void writecache_copy_endio(int read_err, unsigned long write_err, void *ptr) +{ + struct copy_struct *c = ptr; + struct dm_writecache *wc = c->wc; + + c->error = likely(!(read_err | write_err)) ? 0 : -EIO; + + raw_spin_lock_irq(&wc->endio_list_lock); + if (unlikely(list_empty(&wc->endio_list))) + wake_up_process(wc->endio_thread); + list_add_tail(&c->endio_entry, &wc->endio_list); + raw_spin_unlock_irq(&wc->endio_list_lock); +} + +static void __writecache_endio_pmem(struct dm_writecache *wc, struct list_head *list) +{ + unsigned i; + struct writeback_struct *wb; + struct wc_entry *e; + unsigned long n_walked = 0; + + do { + wb = list_entry(list->next, struct writeback_struct, endio_entry); + list_del(&wb->endio_entry); + + if (unlikely(wb->bio.bi_status != BLK_STS_OK)) + writecache_error(wc, blk_status_to_errno(wb->bio.bi_status), + "write error %d", wb->bio.bi_status); + i = 0; + do { + e = wb->wc_list[i]; + BUG_ON(!e->write_in_progress); + e->write_in_progress = false; + INIT_LIST_HEAD(&e->lru); + if (!writecache_has_error(wc)) + writecache_free_entry(wc, e); + BUG_ON(!wc->writeback_size); + wc->writeback_size--; + n_walked++; + if (unlikely(n_walked >= ENDIO_LATENCY)) { + writecache_commit_flushed(wc); + wc_unlock(wc); + wc_lock(wc); + n_walked = 0; + } + } while (++i < wb->wc_list_n); + + if (wb->wc_list != wb->wc_list_inline) + kfree(wb->wc_list); + bio_put(&wb->bio); + } while (!list_empty(list)); +} + +static void __writecache_endio_ssd(struct dm_writecache *wc, struct list_head *list) +{ + struct copy_struct *c; + struct wc_entry *e; + + do { + c = list_entry(list->next, struct copy_struct, endio_entry); + list_del(&c->endio_entry); + + if (unlikely(c->error)) + writecache_error(wc, c->error, "copy error"); + + e = c->e; + do { + BUG_ON(!e->write_in_progress); + e->write_in_progress = false; + INIT_LIST_HEAD(&e->lru); + if (!writecache_has_error(wc)) + writecache_free_entry(wc, e); + + BUG_ON(!wc->writeback_size); + wc->writeback_size--; + e++; + } while (--c->n_entries); + mempool_free(c, &wc->copy_pool); + } while (!list_empty(list)); +} + +static int writecache_endio_thread(void *data) +{ + struct dm_writecache *wc = data; + + while (1) { + struct list_head list; + + raw_spin_lock_irq(&wc->endio_list_lock); + if (!list_empty(&wc->endio_list)) + goto pop_from_list; + set_current_state(TASK_INTERRUPTIBLE); + raw_spin_unlock_irq(&wc->endio_list_lock); + + if (unlikely(kthread_should_stop())) { + set_current_state(TASK_RUNNING); + break; + } + + schedule(); + + continue; + +pop_from_list: + list = wc->endio_list; + list.next->prev = list.prev->next = &list; + INIT_LIST_HEAD(&wc->endio_list); + raw_spin_unlock_irq(&wc->endio_list_lock); + + if (!WC_MODE_FUA(wc)) + writecache_disk_flush(wc, wc->dev); + + wc_lock(wc); + + if (WC_MODE_PMEM(wc)) { + __writecache_endio_pmem(wc, &list); + } else { + __writecache_endio_ssd(wc, &list); + writecache_wait_for_ios(wc, READ); + } + + writecache_commit_flushed(wc); + + wc_unlock(wc); + } + + return 0; +} + +static bool wc_add_block(struct writeback_struct *wb, struct wc_entry *e, gfp_t gfp) +{ + struct dm_writecache *wc = wb->wc; + unsigned block_size = wc->block_size; + void *address = memory_data(wc, e); + + persistent_memory_flush_cache(address, block_size); + return bio_add_page(&wb->bio, persistent_memory_page(address), + block_size, persistent_memory_page_offset(address)) != 0; +} + +struct writeback_list { + struct list_head list; + size_t size; +}; + +static void __writeback_throttle(struct dm_writecache *wc, struct writeback_list *wbl) +{ + if (unlikely(wc->max_writeback_jobs)) { + if (READ_ONCE(wc->writeback_size) - wbl->size >= wc->max_writeback_jobs) { + wc_lock(wc); + while (wc->writeback_size - wbl->size >= wc->max_writeback_jobs) + writecache_wait_on_freelist(wc); + wc_unlock(wc); + } + } + cond_resched(); +} + +static void __writecache_writeback_pmem(struct dm_writecache *wc, struct writeback_list *wbl) +{ + struct wc_entry *e, *f; + struct bio *bio; + struct writeback_struct *wb; + unsigned max_pages; + + while (wbl->size) { + wbl->size--; + e = container_of(wbl->list.prev, struct wc_entry, lru); + list_del(&e->lru); + + max_pages = e->wc_list_contiguous; + + bio = bio_alloc_bioset(GFP_NOIO, max_pages, &wc->bio_set); + wb = container_of(bio, struct writeback_struct, bio); + wb->wc = wc; + wb->bio.bi_end_io = writecache_writeback_endio; + bio_set_dev(&wb->bio, wc->dev->bdev); + wb->bio.bi_iter.bi_sector = read_original_sector(wc, e); + wb->page_offset = PAGE_SIZE; + if (max_pages <= WB_LIST_INLINE || + unlikely(!(wb->wc_list = kmalloc(max_pages * sizeof(struct wc_entry *), + GFP_NOIO | __GFP_NORETRY | + __GFP_NOMEMALLOC | __GFP_NOWARN)))) { + wb->wc_list = wb->wc_list_inline; + max_pages = WB_LIST_INLINE; + } + + BUG_ON(!wc_add_block(wb, e, GFP_NOIO)); + + wb->wc_list[0] = e; + wb->wc_list_n = 1; + + while (wbl->size && wb->wc_list_n < max_pages) { + f = container_of(wbl->list.prev, struct wc_entry, lru); + if (read_original_sector(wc, f) != + read_original_sector(wc, e) + (wc->block_size >> SECTOR_SHIFT)) + break; + if (!wc_add_block(wb, f, GFP_NOWAIT | __GFP_NOWARN)) + break; + wbl->size--; + list_del(&f->lru); + wb->wc_list[wb->wc_list_n++] = f; + e = f; + } + bio_set_op_attrs(&wb->bio, REQ_OP_WRITE, WC_MODE_FUA(wc) * REQ_FUA); + if (writecache_has_error(wc)) { + bio->bi_status = BLK_STS_IOERR; + bio_endio(&wb->bio); + } else { + submit_bio(&wb->bio); + } + + __writeback_throttle(wc, wbl); + } +} + +static void __writecache_writeback_ssd(struct dm_writecache *wc, struct writeback_list *wbl) +{ + struct wc_entry *e, *f; + struct dm_io_region from, to; + struct copy_struct *c; + + while (wbl->size) { + unsigned n_sectors; + + wbl->size--; + e = container_of(wbl->list.prev, struct wc_entry, lru); + list_del(&e->lru); + + n_sectors = e->wc_list_contiguous << (wc->block_size_bits - SECTOR_SHIFT); + + from.bdev = wc->ssd_dev->bdev; + from.sector = cache_sector(wc, e); + from.count = n_sectors; + to.bdev = wc->dev->bdev; + to.sector = read_original_sector(wc, e); + to.count = n_sectors; + + c = mempool_alloc(&wc->copy_pool, GFP_NOIO); + c->wc = wc; + c->e = e; + c->n_entries = e->wc_list_contiguous; + + while ((n_sectors -= wc->block_size >> SECTOR_SHIFT)) { + wbl->size--; + f = container_of(wbl->list.prev, struct wc_entry, lru); + BUG_ON(f != e + 1); + list_del(&f->lru); + e = f; + } + + dm_kcopyd_copy(wc->dm_kcopyd, &from, 1, &to, 0, writecache_copy_endio, c); + + __writeback_throttle(wc, wbl); + } +} + +static void writecache_writeback(struct work_struct *work) +{ + struct dm_writecache *wc = container_of(work, struct dm_writecache, writeback_work); + struct blk_plug plug; + struct wc_entry *e, *f, *g; + struct rb_node *node, *next_node; + struct list_head skipped; + struct writeback_list wbl; + unsigned long n_walked; + + wc_lock(wc); +restart: + if (writecache_has_error(wc)) { + wc_unlock(wc); + return; + } + + if (unlikely(wc->writeback_all)) { + if (writecache_wait_for_writeback(wc)) + goto restart; + } + + if (wc->overwrote_committed) { + writecache_wait_for_ios(wc, WRITE); + } + + n_walked = 0; + INIT_LIST_HEAD(&skipped); + INIT_LIST_HEAD(&wbl.list); + wbl.size = 0; + while (!list_empty(&wc->lru) && + (wc->writeback_all || + wc->freelist_size + wc->writeback_size <= wc->freelist_low_watermark)) { + + n_walked++; + if (unlikely(n_walked > WRITEBACK_LATENCY) && + likely(!wc->writeback_all) && likely(!dm_suspended(wc->ti))) { + queue_work(wc->writeback_wq, &wc->writeback_work); + break; + } + + e = container_of(wc->lru.prev, struct wc_entry, lru); + BUG_ON(e->write_in_progress); + if (unlikely(!writecache_entry_is_committed(wc, e))) { + writecache_flush(wc); + } + node = rb_prev(&e->rb_node); + if (node) { + f = container_of(node, struct wc_entry, rb_node); + if (unlikely(read_original_sector(wc, f) == + read_original_sector(wc, e))) { + BUG_ON(!f->write_in_progress); + list_del(&e->lru); + list_add(&e->lru, &skipped); + cond_resched(); + continue; + } + } + wc->writeback_size++; + list_del(&e->lru); + list_add(&e->lru, &wbl.list); + wbl.size++; + e->write_in_progress = true; + e->wc_list_contiguous = 1; + + f = e; + + while (1) { + next_node = rb_next(&f->rb_node); + if (unlikely(!next_node)) + break; + g = container_of(next_node, struct wc_entry, rb_node); + if (read_original_sector(wc, g) == + read_original_sector(wc, f)) { + f = g; + continue; + } + if (read_original_sector(wc, g) != + read_original_sector(wc, f) + (wc->block_size >> SECTOR_SHIFT)) + break; + if (unlikely(g->write_in_progress)) + break; + if (unlikely(!writecache_entry_is_committed(wc, g))) + break; + + if (!WC_MODE_PMEM(wc)) { + if (g != f + 1) + break; + } + + n_walked++; + //if (unlikely(n_walked > WRITEBACK_LATENCY) && likely(!wc->writeback_all)) + // break; + + wc->writeback_size++; + list_del(&g->lru); + list_add(&g->lru, &wbl.list); + wbl.size++; + g->write_in_progress = true; + g->wc_list_contiguous = BIO_MAX_PAGES; + f = g; + e->wc_list_contiguous++; + if (unlikely(e->wc_list_contiguous == BIO_MAX_PAGES)) + break; + } + cond_resched(); + } + + if (!list_empty(&skipped)) { + list_splice_tail(&skipped, &wc->lru); + /* + * If we didn't do any progress, we must wait until some + * writeback finishes to avoid burning CPU in a loop + */ + if (unlikely(!wbl.size)) + writecache_wait_for_writeback(wc); + } + + wc_unlock(wc); + + blk_start_plug(&plug); + + if (WC_MODE_PMEM(wc)) + __writecache_writeback_pmem(wc, &wbl); + else + __writecache_writeback_ssd(wc, &wbl); + + blk_finish_plug(&plug); + + if (unlikely(wc->writeback_all)) { + wc_lock(wc); + while (writecache_wait_for_writeback(wc)); + wc_unlock(wc); + } +} + +static int calculate_memory_size(uint64_t device_size, unsigned block_size, + size_t *n_blocks_p, size_t *n_metadata_blocks_p) +{ + uint64_t n_blocks, offset; + struct wc_entry e; + + n_blocks = device_size; + do_div(n_blocks, block_size + sizeof(struct wc_memory_entry)); + + while (1) { + if (!n_blocks) + return -ENOSPC; + /* Verify the following entries[n_blocks] won't overflow */ + if (n_blocks >= ((size_t)-sizeof(struct wc_memory_superblock) / + sizeof(struct wc_memory_entry))) + return -EFBIG; + offset = offsetof(struct wc_memory_superblock, entries[n_blocks]); + offset = (offset + block_size - 1) & ~(uint64_t)(block_size - 1); + if (offset + n_blocks * block_size <= device_size) + break; + n_blocks--; + } + + /* check if the bit field overflows */ + e.index = n_blocks; + if (e.index != n_blocks) + return -EFBIG; + + if (n_blocks_p) + *n_blocks_p = n_blocks; + if (n_metadata_blocks_p) + *n_metadata_blocks_p = offset >> __ffs(block_size); + return 0; +} + +static int init_memory(struct dm_writecache *wc) +{ + size_t b; + int r; + + r = calculate_memory_size(wc->memory_map_size, wc->block_size, &wc->n_blocks, NULL); + if (r) + return r; + + r = writecache_alloc_entries(wc); + if (r) + return r; + + for (b = 0; b < ARRAY_SIZE(sb(wc)->padding); b++) + pmem_assign(sb(wc)->padding[b], cpu_to_le64(0)); + pmem_assign(sb(wc)->version, cpu_to_le32(MEMORY_SUPERBLOCK_VERSION)); + pmem_assign(sb(wc)->block_size, cpu_to_le32(wc->block_size)); + pmem_assign(sb(wc)->n_blocks, cpu_to_le64(wc->n_blocks)); + pmem_assign(sb(wc)->seq_count, cpu_to_le64(0)); + + for (b = 0; b < wc->n_blocks; b++) + write_original_sector_seq_count(wc, &wc->entries[b], -1, -1); + + writecache_flush_all_metadata(wc); + writecache_commit_flushed(wc); + pmem_assign(sb(wc)->magic, cpu_to_le32(MEMORY_SUPERBLOCK_MAGIC)); + writecache_flush_region(wc, &sb(wc)->magic, sizeof sb(wc)->magic); + writecache_commit_flushed(wc); + + return 0; +} + +static void writecache_dtr(struct dm_target *ti) +{ + struct dm_writecache *wc = ti->private; + + if (!wc) + return; + + if (wc->endio_thread) + kthread_stop(wc->endio_thread); + + if (wc->flush_thread) + kthread_stop(wc->flush_thread); + + bioset_exit(&wc->bio_set); + + mempool_exit(&wc->copy_pool); + + if (wc->writeback_wq) + destroy_workqueue(wc->writeback_wq); + + if (wc->dev) + dm_put_device(ti, wc->dev); + + if (wc->ssd_dev) + dm_put_device(ti, wc->ssd_dev); + + if (wc->entries) + vfree(wc->entries); + + if (wc->memory_map) { + if (WC_MODE_PMEM(wc)) + persistent_memory_release(wc); + else + vfree(wc->memory_map); + } + + if (wc->dm_kcopyd) + dm_kcopyd_client_destroy(wc->dm_kcopyd); + + if (wc->dm_io) + dm_io_client_destroy(wc->dm_io); + + if (wc->dirty_bitmap) + vfree(wc->dirty_bitmap); + + kfree(wc); +} + +static int writecache_ctr(struct dm_target *ti, unsigned argc, char **argv) +{ + struct dm_writecache *wc; + struct dm_arg_set as; + const char *string; + unsigned opt_params; + size_t offset, data_size; + int i, r; + char dummy; + int high_wm_percent = HIGH_WATERMARK; + int low_wm_percent = LOW_WATERMARK; + uint64_t x; + struct wc_memory_superblock s; + + static struct dm_arg _args[] = { + {0, 10, "Invalid number of feature args"}, + }; + + as.argc = argc; + as.argv = argv; + + wc = kzalloc(sizeof(struct dm_writecache), GFP_KERNEL); + if (!wc) { + ti->error = "Cannot allocate writecache structure"; + r = -ENOMEM; + goto bad; + } + ti->private = wc; + wc->ti = ti; + + mutex_init(&wc->lock); + writecache_poison_lists(wc); + init_waitqueue_head(&wc->freelist_wait); + timer_setup(&wc->autocommit_timer, writecache_autocommit_timer, 0); + + for (i = 0; i < 2; i++) { + atomic_set(&wc->bio_in_progress[i], 0); + init_waitqueue_head(&wc->bio_in_progress_wait[i]); + } + + wc->dm_io = dm_io_client_create(); + if (IS_ERR(wc->dm_io)) { + r = PTR_ERR(wc->dm_io); + ti->error = "Unable to allocate dm-io client"; + wc->dm_io = NULL; + goto bad; + } + + wc->writeback_wq = alloc_workqueue("writecache-writeabck", WQ_MEM_RECLAIM, 1); + if (!wc->writeback_wq) { + r = -ENOMEM; + ti->error = "Could not allocate writeback workqueue"; + goto bad; + } + INIT_WORK(&wc->writeback_work, writecache_writeback); + INIT_WORK(&wc->flush_work, writecache_flush_work); + + raw_spin_lock_init(&wc->endio_list_lock); + INIT_LIST_HEAD(&wc->endio_list); + wc->endio_thread = kthread_create(writecache_endio_thread, wc, "writecache_endio"); + if (IS_ERR(wc->endio_thread)) { + r = PTR_ERR(wc->endio_thread); + wc->endio_thread = NULL; + ti->error = "Couldn't spawn endio thread"; + goto bad; + } + wake_up_process(wc->endio_thread); + + /* + * Parse the mode (pmem or ssd) + */ + string = dm_shift_arg(&as); + if (!string) + goto bad_arguments; + + if (!strcasecmp(string, "s")) { + wc->pmem_mode = false; + } else if (!strcasecmp(string, "p")) { +#ifdef DM_WRITECACHE_HAS_PMEM + wc->pmem_mode = true; + wc->writeback_fua = true; +#else + /* + * If the architecture doesn't support persistent memory or + * the kernel doesn't support any DAX drivers, this driver can + * only be used in SSD-only mode. + */ + r = -EOPNOTSUPP; + ti->error = "Persistent memory or DAX not supported on this system"; + goto bad; +#endif + } else { + goto bad_arguments; + } + + if (WC_MODE_PMEM(wc)) { + r = bioset_init(&wc->bio_set, BIO_POOL_SIZE, + offsetof(struct writeback_struct, bio), + BIOSET_NEED_BVECS); + if (r) { + ti->error = "Could not allocate bio set"; + goto bad; + } + } else { + r = mempool_init_kmalloc_pool(&wc->copy_pool, 1, sizeof(struct copy_struct)); + if (r) { + ti->error = "Could not allocate mempool"; + goto bad; + } + } + + /* + * Parse the origin data device + */ + string = dm_shift_arg(&as); + if (!string) + goto bad_arguments; + r = dm_get_device(ti, string, dm_table_get_mode(ti->table), &wc->dev); + if (r) { + ti->error = "Origin data device lookup failed"; + goto bad; + } + + /* + * Parse cache data device (be it pmem or ssd) + */ + string = dm_shift_arg(&as); + if (!string) + goto bad_arguments; + + r = dm_get_device(ti, string, dm_table_get_mode(ti->table), &wc->ssd_dev); + if (r) { + ti->error = "Cache data device lookup failed"; + goto bad; + } + wc->memory_map_size = i_size_read(wc->ssd_dev->bdev->bd_inode); + + if (WC_MODE_PMEM(wc)) { + r = persistent_memory_claim(wc); + if (r) { + ti->error = "Unable to map persistent memory for cache"; + goto bad; + } + } + + /* + * Parse the cache block size + */ + string = dm_shift_arg(&as); + if (!string) + goto bad_arguments; + if (sscanf(string, "%u%c", &wc->block_size, &dummy) != 1 || + wc->block_size < 512 || wc->block_size > PAGE_SIZE || + (wc->block_size & (wc->block_size - 1))) { + r = -EINVAL; + ti->error = "Invalid block size"; + goto bad; + } + wc->block_size_bits = __ffs(wc->block_size); + + wc->max_writeback_jobs = MAX_WRITEBACK_JOBS; + wc->autocommit_blocks = !WC_MODE_PMEM(wc) ? AUTOCOMMIT_BLOCKS_SSD : AUTOCOMMIT_BLOCKS_PMEM; + wc->autocommit_jiffies = msecs_to_jiffies(AUTOCOMMIT_MSEC); + + /* + * Parse optional arguments + */ + r = dm_read_arg_group(_args, &as, &opt_params, &ti->error); + if (r) + goto bad; + + while (opt_params) { + string = dm_shift_arg(&as), opt_params--; + if (!strcasecmp(string, "high_watermark") && opt_params >= 1) { + string = dm_shift_arg(&as), opt_params--; + if (sscanf(string, "%d%c", &high_wm_percent, &dummy) != 1) + goto invalid_optional; + if (high_wm_percent < 0 || high_wm_percent > 100) + goto invalid_optional; + wc->high_wm_percent_set = true; + } else if (!strcasecmp(string, "low_watermark") && opt_params >= 1) { + string = dm_shift_arg(&as), opt_params--; + if (sscanf(string, "%d%c", &low_wm_percent, &dummy) != 1) + goto invalid_optional; + if (low_wm_percent < 0 || low_wm_percent > 100) + goto invalid_optional; + wc->low_wm_percent_set = true; + } else if (!strcasecmp(string, "writeback_jobs") && opt_params >= 1) { + string = dm_shift_arg(&as), opt_params--; + if (sscanf(string, "%u%c", &wc->max_writeback_jobs, &dummy) != 1) + goto invalid_optional; + wc->max_writeback_jobs_set = true; + } else if (!strcasecmp(string, "autocommit_blocks") && opt_params >= 1) { + string = dm_shift_arg(&as), opt_params--; + if (sscanf(string, "%u%c", &wc->autocommit_blocks, &dummy) != 1) + goto invalid_optional; + wc->autocommit_blocks_set = true; + } else if (!strcasecmp(string, "autocommit_time") && opt_params >= 1) { + unsigned autocommit_msecs; + string = dm_shift_arg(&as), opt_params--; + if (sscanf(string, "%u%c", &autocommit_msecs, &dummy) != 1) + goto invalid_optional; + if (autocommit_msecs > 3600000) + goto invalid_optional; + wc->autocommit_jiffies = msecs_to_jiffies(autocommit_msecs); + wc->autocommit_time_set = true; + } else if (!strcasecmp(string, "fua")) { + if (WC_MODE_PMEM(wc)) { + wc->writeback_fua = true; + wc->writeback_fua_set = true; + } else goto invalid_optional; + } else if (!strcasecmp(string, "nofua")) { + if (WC_MODE_PMEM(wc)) { + wc->writeback_fua = false; + wc->writeback_fua_set = true; + } else goto invalid_optional; + } else { +invalid_optional: + r = -EINVAL; + ti->error = "Invalid optional argument"; + goto bad; + } + } + + if (high_wm_percent < low_wm_percent) { + r = -EINVAL; + ti->error = "High watermark must be greater than or equal to low watermark"; + goto bad; + } + + if (!WC_MODE_PMEM(wc)) { + struct dm_io_region region; + struct dm_io_request req; + size_t n_blocks, n_metadata_blocks; + uint64_t n_bitmap_bits; + + bio_list_init(&wc->flush_list); + wc->flush_thread = kthread_create(writecache_flush_thread, wc, "dm_writecache_flush"); + if (IS_ERR(wc->flush_thread)) { + r = PTR_ERR(wc->flush_thread); + wc->flush_thread = NULL; + ti->error = "Couldn't spawn endio thread"; + goto bad; + } + wake_up_process(wc->flush_thread); + + r = calculate_memory_size(wc->memory_map_size, wc->block_size, + &n_blocks, &n_metadata_blocks); + if (r) { + ti->error = "Invalid device size"; + goto bad; + } + + n_bitmap_bits = (((uint64_t)n_metadata_blocks << wc->block_size_bits) + + BITMAP_GRANULARITY - 1) / BITMAP_GRANULARITY; + /* this is limitation of test_bit functions */ + if (n_bitmap_bits > 1U << 31) { + r = -EFBIG; + ti->error = "Invalid device size"; + goto bad; + } + + wc->memory_map = vmalloc(n_metadata_blocks << wc->block_size_bits); + if (!wc->memory_map) { + r = -ENOMEM; + ti->error = "Unable to allocate memory for metadata"; + goto bad; + } + + wc->dm_kcopyd = dm_kcopyd_client_create(&dm_kcopyd_throttle); + if (IS_ERR(wc->dm_kcopyd)) { + r = PTR_ERR(wc->dm_kcopyd); + ti->error = "Unable to allocate dm-kcopyd client"; + wc->dm_kcopyd = NULL; + goto bad; + } + + wc->metadata_sectors = n_metadata_blocks << (wc->block_size_bits - SECTOR_SHIFT); + wc->dirty_bitmap_size = (n_bitmap_bits + BITS_PER_LONG - 1) / + BITS_PER_LONG * sizeof(unsigned long); + wc->dirty_bitmap = vzalloc(wc->dirty_bitmap_size); + if (!wc->dirty_bitmap) { + r = -ENOMEM; + ti->error = "Unable to allocate dirty bitmap"; + goto bad; + } + + region.bdev = wc->ssd_dev->bdev; + region.sector = 0; + region.count = wc->metadata_sectors; + req.bi_op = REQ_OP_READ; + req.bi_op_flags = REQ_SYNC; + req.mem.type = DM_IO_VMA; + req.mem.ptr.vma = (char *)wc->memory_map; + req.client = wc->dm_io; + req.notify.fn = NULL; + + r = dm_io(&req, 1, ®ion, NULL); + if (r) { + ti->error = "Unable to read metadata"; + goto bad; + } + } + + r = memcpy_mcsafe(&s, sb(wc), sizeof(struct wc_memory_superblock)); + if (r) { + ti->error = "Hardware memory error when reading superblock"; + goto bad; + } + if (!le32_to_cpu(s.magic) && !le32_to_cpu(s.version)) { + r = init_memory(wc); + if (r) { + ti->error = "Unable to initialize device"; + goto bad; + } + r = memcpy_mcsafe(&s, sb(wc), sizeof(struct wc_memory_superblock)); + if (r) { + ti->error = "Hardware memory error when reading superblock"; + goto bad; + } + } + + if (le32_to_cpu(s.magic) != MEMORY_SUPERBLOCK_MAGIC) { + ti->error = "Invalid magic in the superblock"; + r = -EINVAL; + goto bad; + } + + if (le32_to_cpu(s.version) != MEMORY_SUPERBLOCK_VERSION) { + ti->error = "Invalid version in the superblock"; + r = -EINVAL; + goto bad; + } + + if (le32_to_cpu(s.block_size) != wc->block_size) { + ti->error = "Block size does not match superblock"; + r = -EINVAL; + goto bad; + } + + wc->n_blocks = le64_to_cpu(s.n_blocks); + + offset = wc->n_blocks * sizeof(struct wc_memory_entry); + if (offset / sizeof(struct wc_memory_entry) != le64_to_cpu(sb(wc)->n_blocks)) { +overflow: + ti->error = "Overflow in size calculation"; + r = -EINVAL; + goto bad; + } + offset += sizeof(struct wc_memory_superblock); + if (offset < sizeof(struct wc_memory_superblock)) + goto overflow; + offset = (offset + wc->block_size - 1) & ~(size_t)(wc->block_size - 1); + data_size = wc->n_blocks * (size_t)wc->block_size; + if (!offset || (data_size / wc->block_size != wc->n_blocks) || + (offset + data_size < offset)) + goto overflow; + if (offset + data_size > wc->memory_map_size) { + ti->error = "Memory area is too small"; + r = -EINVAL; + goto bad; + } + + wc->metadata_sectors = offset >> SECTOR_SHIFT; + wc->block_start = (char *)sb(wc) + offset; + + x = (uint64_t)wc->n_blocks * (100 - high_wm_percent); + x += 50; + do_div(x, 100); + wc->freelist_high_watermark = x; + x = (uint64_t)wc->n_blocks * (100 - low_wm_percent); + x += 50; + do_div(x, 100); + wc->freelist_low_watermark = x; + + r = writecache_alloc_entries(wc); + if (r) { + ti->error = "Cannot allocate memory"; + goto bad; + } + + ti->num_flush_bios = 1; + ti->flush_supported = true; + ti->num_discard_bios = 1; + + if (WC_MODE_PMEM(wc)) + persistent_memory_flush_cache(wc->memory_map, wc->memory_map_size); + + return 0; + +bad_arguments: + r = -EINVAL; + ti->error = "Bad arguments"; +bad: + writecache_dtr(ti); + return r; +} + +static void writecache_status(struct dm_target *ti, status_type_t type, + unsigned status_flags, char *result, unsigned maxlen) +{ + struct dm_writecache *wc = ti->private; + unsigned extra_args; + unsigned sz = 0; + uint64_t x; + + switch (type) { + case STATUSTYPE_INFO: + DMEMIT("%ld %llu %llu %llu", writecache_has_error(wc), + (unsigned long long)wc->n_blocks, (unsigned long long)wc->freelist_size, + (unsigned long long)wc->writeback_size); + break; + case STATUSTYPE_TABLE: + DMEMIT("%c %s %s %u ", WC_MODE_PMEM(wc) ? 'p' : 's', + wc->dev->name, wc->ssd_dev->name, wc->block_size); + extra_args = 0; + if (wc->high_wm_percent_set) + extra_args += 2; + if (wc->low_wm_percent_set) + extra_args += 2; + if (wc->max_writeback_jobs_set) + extra_args += 2; + if (wc->autocommit_blocks_set) + extra_args += 2; + if (wc->autocommit_time_set) + extra_args += 2; + if (wc->writeback_fua_set) + extra_args++; + + DMEMIT("%u", extra_args); + if (wc->high_wm_percent_set) { + x = (uint64_t)wc->freelist_high_watermark * 100; + x += wc->n_blocks / 2; + do_div(x, (size_t)wc->n_blocks); + DMEMIT(" high_watermark %u", 100 - (unsigned)x); + } + if (wc->low_wm_percent_set) { + x = (uint64_t)wc->freelist_low_watermark * 100; + x += wc->n_blocks / 2; + do_div(x, (size_t)wc->n_blocks); + DMEMIT(" low_watermark %u", 100 - (unsigned)x); + } + if (wc->max_writeback_jobs_set) + DMEMIT(" writeback_jobs %u", wc->max_writeback_jobs); + if (wc->autocommit_blocks_set) + DMEMIT(" autocommit_blocks %u", wc->autocommit_blocks); + if (wc->autocommit_time_set) + DMEMIT(" autocommit_time %u", jiffies_to_msecs(wc->autocommit_jiffies)); + if (wc->writeback_fua_set) + DMEMIT(" %sfua", wc->writeback_fua ? "" : "no"); + break; + } +} + +static struct target_type writecache_target = { + .name = "writecache", + .version = {1, 0, 0}, + .module = THIS_MODULE, + .ctr = writecache_ctr, + .dtr = writecache_dtr, + .status = writecache_status, + .postsuspend = writecache_suspend, + .resume = writecache_resume, + .message = writecache_message, + .map = writecache_map, + .end_io = writecache_end_io, + .iterate_devices = writecache_iterate_devices, + .io_hints = writecache_io_hints, +}; + +static int __init dm_writecache_init(void) +{ + int r; + + r = dm_register_target(&writecache_target); + if (r < 0) { + DMERR("register failed %d", r); + return r; + } + + return 0; +} + +static void __exit dm_writecache_exit(void) +{ + dm_unregister_target(&writecache_target); +} + +module_init(dm_writecache_init); +module_exit(dm_writecache_exit); + +MODULE_DESCRIPTION(DM_NAME " writecache target"); +MODULE_AUTHOR("Mikulas Patocka <dm-devel@redhat.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/md/dm-zoned-target.c b/drivers/md/dm-zoned-target.c index 30602d1..3c0e45f 100644 --- a/drivers/md/dm-zoned-target.c +++ b/drivers/md/dm-zoned-target.c @@ -52,9 +52,9 @@ struct dmz_target { struct dmz_reclaim *reclaim; /* For chunk work */ - struct mutex chunk_lock; struct radix_tree_root chunk_rxtree; struct workqueue_struct *chunk_wq; + struct mutex chunk_lock; /* For cloned BIOs to zones */ struct bio_set bio_set; diff --git a/drivers/media/rc/ir-rx51.c b/drivers/media/rc/ir-rx51.c index 49265f0..8a93f74 100644 --- a/drivers/media/rc/ir-rx51.c +++ b/drivers/media/rc/ir-rx51.c @@ -22,7 +22,6 @@ #include <linux/hrtimer.h> #include <media/rc-core.h> -#include <linux/platform_data/media/ir-rx51.h> #define WBUF_LEN 256 @@ -31,7 +30,6 @@ struct ir_rx51 { struct pwm_device *pwm; struct hrtimer timer; struct device *dev; - struct ir_rx51_platform_data *pdata; wait_queue_head_t wqueue; unsigned int freq; /* carrier frequency */ @@ -130,10 +128,9 @@ static int ir_rx51_tx(struct rc_dev *dev, unsigned int *buffer, ir_rx51->wbuf[count] = -1; /* Insert termination mark */ /* - * Adjust latency requirements so the device doesn't go in too - * deep sleep states + * REVISIT: Adjust latency requirements so the device doesn't go in too + * deep sleep states with pm_qos_add_request(). */ - ir_rx51->pdata->set_max_mpu_wakeup_lat(ir_rx51->dev, 50); ir_rx51_on(ir_rx51); ir_rx51->wbuf_index = 1; @@ -146,8 +143,7 @@ static int ir_rx51_tx(struct rc_dev *dev, unsigned int *buffer, */ wait_event_interruptible(ir_rx51->wqueue, ir_rx51->wbuf_index < 0); - /* We can sleep again */ - ir_rx51->pdata->set_max_mpu_wakeup_lat(ir_rx51->dev, -1); + /* REVISIT: Remove pm_qos constraint, we can sleep again */ return count; } @@ -244,13 +240,6 @@ static int ir_rx51_probe(struct platform_device *dev) struct pwm_device *pwm; struct rc_dev *rcdev; - ir_rx51.pdata = dev->dev.platform_data; - - if (!ir_rx51.pdata) { - dev_err(&dev->dev, "Platform Data is missing\n"); - return -ENXIO; - } - pwm = pwm_get(&dev->dev, NULL); if (IS_ERR(pwm)) { int err = PTR_ERR(pwm); diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig index 19a0e83..8d731d6 100644 --- a/drivers/memory/Kconfig +++ b/drivers/memory/Kconfig @@ -104,16 +104,6 @@ config MVEBU_DEVBUS Armada 370 and Armada XP. This controller allows to handle flash devices such as NOR, NAND, SRAM, and FPGA. -config TEGRA20_MC - bool "Tegra20 Memory Controller(MC) driver" - default y - depends on ARCH_TEGRA_2x_SOC - help - This driver is for the Memory Controller(MC) module available - in Tegra20 SoCs, mainly for a address translation fault - analysis, especially for IOMMU/GART(Graphics Address - Relocation Table) module. - config FSL_CORENET_CF tristate "Freescale CoreNet Error Reporting" depends on FSL_SOC_BOOKE diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile index 66f5524..a01ab3e 100644 --- a/drivers/memory/Makefile +++ b/drivers/memory/Makefile @@ -16,7 +16,6 @@ obj-$(CONFIG_OMAP_GPMC) += omap-gpmc.o obj-$(CONFIG_FSL_CORENET_CF) += fsl-corenet-cf.o obj-$(CONFIG_FSL_IFC) += fsl_ifc.o obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o -obj-$(CONFIG_TEGRA20_MC) += tegra20-mc.o obj-$(CONFIG_JZ4780_NEMC) += jz4780-nemc.o obj-$(CONFIG_MTK_SMI) += mtk-smi.o obj-$(CONFIG_DA8XX_DDRCTL) += da8xx-ddrctl.o diff --git a/drivers/memory/brcmstb_dpfe.c b/drivers/memory/brcmstb_dpfe.c index e9c1485..04599ec 100644 --- a/drivers/memory/brcmstb_dpfe.c +++ b/drivers/memory/brcmstb_dpfe.c @@ -176,7 +176,6 @@ struct private_data { void __iomem *dmem; void __iomem *imem; struct device *dev; - unsigned int index; struct mutex lock; }; @@ -674,10 +673,8 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct private_data *priv; - struct device *dpfe_dev; struct init_data init; struct resource *res; - u32 index; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -687,11 +684,6 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev) mutex_init(&priv->lock); platform_set_drvdata(pdev, priv); - /* Cell index is optional; default to 0 if not present. */ - ret = of_property_read_u32(dev->of_node, "cell-index", &index); - if (ret) - index = 0; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpfe-cpu"); priv->regs = devm_ioremap_resource(dev, res); if (IS_ERR(priv->regs)) { @@ -715,35 +707,20 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev) ret = brcmstb_dpfe_download_firmware(pdev, &init); if (ret) - goto err; - - dpfe_dev = devm_kzalloc(dev, sizeof(*dpfe_dev), GFP_KERNEL); - if (!dpfe_dev) { - ret = -ENOMEM; - goto err; - } - - priv->dev = dpfe_dev; - priv->index = index; + return ret; - dpfe_dev->parent = dev; - dpfe_dev->groups = dpfe_groups; - dpfe_dev->of_node = dev->of_node; - dev_set_drvdata(dpfe_dev, priv); - dev_set_name(dpfe_dev, "dpfe%u", index); + ret = sysfs_create_groups(&pdev->dev.kobj, dpfe_groups); + if (!ret) + dev_info(dev, "registered.\n"); - ret = device_register(dpfe_dev); - if (ret) - goto err; + return ret; +} - dev_info(dev, "registered.\n"); +static int brcmstb_dpfe_remove(struct platform_device *pdev) +{ + sysfs_remove_groups(&pdev->dev.kobj, dpfe_groups); return 0; - -err: - dev_err(dev, "failed to initialize -- error %d\n", ret); - - return ret; } static const struct of_device_id brcmstb_dpfe_of_match[] = { @@ -758,6 +735,7 @@ static struct platform_driver brcmstb_dpfe_driver = { .of_match_table = brcmstb_dpfe_of_match, }, .probe = brcmstb_dpfe_probe, + .remove = brcmstb_dpfe_remove, .resume = brcmstb_dpfe_resume, }; diff --git a/drivers/memory/omap-gpmc.c b/drivers/memory/omap-gpmc.c index 90a66b3..c215287 100644 --- a/drivers/memory/omap-gpmc.c +++ b/drivers/memory/omap-gpmc.c @@ -2060,8 +2060,8 @@ static int gpmc_probe_generic_child(struct platform_device *pdev, * timings. */ name = gpmc_cs_get_name(cs); - if (name && child->name && of_node_cmp(child->name, name) == 0) - goto no_timings; + if (name && of_node_cmp(child->name, name) == 0) + goto no_timings; ret = gpmc_cs_request(cs, resource_size(&res), &base); if (ret < 0) { diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile index ce87a94..94ab16b 100644 --- a/drivers/memory/tegra/Makefile +++ b/drivers/memory/tegra/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 tegra-mc-y := mc.o +tegra-mc-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20.o tegra-mc-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30.o tegra-mc-$(CONFIG_ARCH_TEGRA_114_SOC) += tegra114.o tegra-mc-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124.o diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c index a4803ac..bb93cc5 100644 --- a/drivers/memory/tegra/mc.c +++ b/drivers/memory/tegra/mc.c @@ -7,6 +7,7 @@ */ #include <linux/clk.h> +#include <linux/delay.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> @@ -20,14 +21,6 @@ #include "mc.h" #define MC_INTSTATUS 0x000 -#define MC_INT_DECERR_MTS (1 << 16) -#define MC_INT_SECERR_SEC (1 << 13) -#define MC_INT_DECERR_VPR (1 << 12) -#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) -#define MC_INT_INVALID_SMMU_PAGE (1 << 10) -#define MC_INT_ARBITRATION_EMEM (1 << 9) -#define MC_INT_SECURITY_VIOLATION (1 << 8) -#define MC_INT_DECERR_EMEM (1 << 6) #define MC_INTMASK 0x004 @@ -45,6 +38,9 @@ #define MC_ERR_ADR 0x0c +#define MC_DECERR_EMEM_OTHERS_STATUS 0x58 +#define MC_SECURITY_VIOLATION_STATUS 0x74 + #define MC_EMEM_ARB_CFG 0x90 #define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE(x) (((x) & 0x1ff) << 0) #define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK 0x1ff @@ -54,6 +50,9 @@ #define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0) static const struct of_device_id tegra_mc_of_match[] = { +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + { .compatible = "nvidia,tegra20-mc", .data = &tegra20_mc_soc }, +#endif #ifdef CONFIG_ARCH_TEGRA_3x_SOC { .compatible = "nvidia,tegra30-mc", .data = &tegra30_mc_soc }, #endif @@ -73,6 +72,207 @@ static const struct of_device_id tegra_mc_of_match[] = { }; MODULE_DEVICE_TABLE(of, tegra_mc_of_match); +static int terga_mc_block_dma_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) | BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static bool terga_mc_dma_idling_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return (mc_readl(mc, rst->status) & BIT(rst->bit)) != 0; +} + +static int terga_mc_unblock_dma_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) & ~BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static int terga_mc_reset_status_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return (mc_readl(mc, rst->control) & BIT(rst->bit)) != 0; +} + +const struct tegra_mc_reset_ops terga_mc_reset_ops_common = { + .block_dma = terga_mc_block_dma_common, + .dma_idling = terga_mc_dma_idling_common, + .unblock_dma = terga_mc_unblock_dma_common, + .reset_status = terga_mc_reset_status_common, +}; + +static inline struct tegra_mc *reset_to_mc(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct tegra_mc, reset); +} + +static const struct tegra_mc_reset *tegra_mc_reset_find(struct tegra_mc *mc, + unsigned long id) +{ + unsigned int i; + + for (i = 0; i < mc->soc->num_resets; i++) + if (mc->soc->resets[i].id == id) + return &mc->soc->resets[i]; + + return NULL; +} + +static int tegra_mc_hotreset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tegra_mc *mc = reset_to_mc(rcdev); + const struct tegra_mc_reset_ops *rst_ops; + const struct tegra_mc_reset *rst; + int retries = 500; + int err; + + rst = tegra_mc_reset_find(mc, id); + if (!rst) + return -ENODEV; + + rst_ops = mc->soc->reset_ops; + if (!rst_ops) + return -ENODEV; + + if (rst_ops->block_dma) { + /* block clients DMA requests */ + err = rst_ops->block_dma(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to block %s DMA: %d\n", + rst->name, err); + return err; + } + } + + if (rst_ops->dma_idling) { + /* wait for completion of the outstanding DMA requests */ + while (!rst_ops->dma_idling(mc, rst)) { + if (!retries--) { + dev_err(mc->dev, "Failed to flush %s DMA\n", + rst->name); + return -EBUSY; + } + + usleep_range(10, 100); + } + } + + if (rst_ops->hotreset_assert) { + /* clear clients DMA requests sitting before arbitration */ + err = rst_ops->hotreset_assert(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to hot reset %s: %d\n", + rst->name, err); + return err; + } + } + + return 0; +} + +static int tegra_mc_hotreset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tegra_mc *mc = reset_to_mc(rcdev); + const struct tegra_mc_reset_ops *rst_ops; + const struct tegra_mc_reset *rst; + int err; + + rst = tegra_mc_reset_find(mc, id); + if (!rst) + return -ENODEV; + + rst_ops = mc->soc->reset_ops; + if (!rst_ops) + return -ENODEV; + + if (rst_ops->hotreset_deassert) { + /* take out client from hot reset */ + err = rst_ops->hotreset_deassert(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to deassert hot reset %s: %d\n", + rst->name, err); + return err; + } + } + + if (rst_ops->unblock_dma) { + /* allow new DMA requests to proceed to arbitration */ + err = rst_ops->unblock_dma(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to unblock %s DMA : %d\n", + rst->name, err); + return err; + } + } + + return 0; +} + +static int tegra_mc_hotreset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tegra_mc *mc = reset_to_mc(rcdev); + const struct tegra_mc_reset_ops *rst_ops; + const struct tegra_mc_reset *rst; + + rst = tegra_mc_reset_find(mc, id); + if (!rst) + return -ENODEV; + + rst_ops = mc->soc->reset_ops; + if (!rst_ops) + return -ENODEV; + + return rst_ops->reset_status(mc, rst); +} + +static const struct reset_control_ops tegra_mc_reset_ops = { + .assert = tegra_mc_hotreset_assert, + .deassert = tegra_mc_hotreset_deassert, + .status = tegra_mc_hotreset_status, +}; + +static int tegra_mc_reset_setup(struct tegra_mc *mc) +{ + int err; + + mc->reset.ops = &tegra_mc_reset_ops; + mc->reset.owner = THIS_MODULE; + mc->reset.of_node = mc->dev->of_node; + mc->reset.of_reset_n_cells = 1; + mc->reset.nr_resets = mc->soc->num_resets; + + err = reset_controller_register(&mc->reset); + if (err < 0) + return err; + + return 0; +} + static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc) { unsigned long long tick; @@ -229,6 +429,7 @@ static int tegra_mc_setup_timings(struct tegra_mc *mc) static const char *const status_names[32] = { [ 1] = "External interrupt", [ 6] = "EMEM address decode error", + [ 7] = "GART page fault", [ 8] = "Security violation", [ 9] = "EMEM arbitration error", [10] = "Page fault", @@ -248,12 +449,13 @@ static const char *const error_names[8] = { static irqreturn_t tegra_mc_irq(int irq, void *data) { struct tegra_mc *mc = data; - unsigned long status, mask; + unsigned long status; unsigned int bit; /* mask all interrupts to avoid flooding */ - status = mc_readl(mc, MC_INTSTATUS); - mask = mc_readl(mc, MC_INTMASK); + status = mc_readl(mc, MC_INTSTATUS) & mc->soc->intmask; + if (!status) + return IRQ_NONE; for_each_set_bit(bit, &status, 32) { const char *error = status_names[bit] ?: "unknown"; @@ -341,12 +543,78 @@ static irqreturn_t tegra_mc_irq(int irq, void *data) return IRQ_HANDLED; } +static __maybe_unused irqreturn_t tegra20_mc_irq(int irq, void *data) +{ + struct tegra_mc *mc = data; + unsigned long status; + unsigned int bit; + + /* mask all interrupts to avoid flooding */ + status = mc_readl(mc, MC_INTSTATUS) & mc->soc->intmask; + if (!status) + return IRQ_NONE; + + for_each_set_bit(bit, &status, 32) { + const char *direction = "read", *secure = ""; + const char *error = status_names[bit]; + const char *client, *desc; + phys_addr_t addr; + u32 value, reg; + u8 id, type; + + switch (BIT(bit)) { + case MC_INT_DECERR_EMEM: + reg = MC_DECERR_EMEM_OTHERS_STATUS; + value = mc_readl(mc, reg); + + id = value & mc->soc->client_id_mask; + desc = error_names[2]; + + if (value & BIT(31)) + direction = "write"; + break; + + case MC_INT_INVALID_GART_PAGE: + dev_err_ratelimited(mc->dev, "%s\n", error); + continue; + + case MC_INT_SECURITY_VIOLATION: + reg = MC_SECURITY_VIOLATION_STATUS; + value = mc_readl(mc, reg); + + id = value & mc->soc->client_id_mask; + type = (value & BIT(30)) ? 4 : 3; + desc = error_names[type]; + secure = "secure "; + + if (value & BIT(31)) + direction = "write"; + break; + + default: + continue; + } + + client = mc->soc->clients[id].name; + addr = mc_readl(mc, reg + sizeof(u32)); + + dev_err_ratelimited(mc->dev, "%s: %s%s @%pa: %s (%s)\n", + client, secure, direction, &addr, error, + desc); + } + + /* clear interrupts */ + mc_writel(mc, status, MC_INTSTATUS); + + return IRQ_HANDLED; +} + static int tegra_mc_probe(struct platform_device *pdev) { const struct of_device_id *match; struct resource *res; struct tegra_mc *mc; - u32 value; + void *isr; int err; match = of_match_node(tegra_mc_of_match, pdev->dev.of_node); @@ -358,6 +626,7 @@ static int tegra_mc_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, mc); + spin_lock_init(&mc->lock); mc->soc = match->data; mc->dev = &pdev->dev; @@ -369,18 +638,32 @@ static int tegra_mc_probe(struct platform_device *pdev) if (IS_ERR(mc->regs)) return PTR_ERR(mc->regs); - mc->clk = devm_clk_get(&pdev->dev, "mc"); - if (IS_ERR(mc->clk)) { - dev_err(&pdev->dev, "failed to get MC clock: %ld\n", - PTR_ERR(mc->clk)); - return PTR_ERR(mc->clk); - } +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + if (mc->soc == &tegra20_mc_soc) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mc->regs2 = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mc->regs2)) + return PTR_ERR(mc->regs2); - err = tegra_mc_setup_latency_allowance(mc); - if (err < 0) { - dev_err(&pdev->dev, "failed to setup latency allowance: %d\n", - err); - return err; + isr = tegra20_mc_irq; + } else +#endif + { + mc->clk = devm_clk_get(&pdev->dev, "mc"); + if (IS_ERR(mc->clk)) { + dev_err(&pdev->dev, "failed to get MC clock: %ld\n", + PTR_ERR(mc->clk)); + return PTR_ERR(mc->clk); + } + + err = tegra_mc_setup_latency_allowance(mc); + if (err < 0) { + dev_err(&pdev->dev, "failed to setup latency allowance: %d\n", + err); + return err; + } + + isr = tegra_mc_irq; } err = tegra_mc_setup_timings(mc); @@ -389,13 +672,11 @@ static int tegra_mc_probe(struct platform_device *pdev) return err; } - if (IS_ENABLED(CONFIG_TEGRA_IOMMU_SMMU)) { - mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc); - if (IS_ERR(mc->smmu)) { - dev_err(&pdev->dev, "failed to probe SMMU: %ld\n", - PTR_ERR(mc->smmu)); - return PTR_ERR(mc->smmu); - } + err = tegra_mc_reset_setup(mc); + if (err < 0) { + dev_err(&pdev->dev, "failed to register reset controller: %d\n", + err); + return err; } mc->irq = platform_get_irq(pdev, 0); @@ -404,7 +685,11 @@ static int tegra_mc_probe(struct platform_device *pdev) return mc->irq; } - err = devm_request_irq(&pdev->dev, mc->irq, tegra_mc_irq, IRQF_SHARED, + WARN(!mc->soc->client_id_mask, "Missing client ID mask for this SoC\n"); + + mc_writel(mc, mc->soc->intmask, MC_INTMASK); + + err = devm_request_irq(&pdev->dev, mc->irq, isr, IRQF_SHARED, dev_name(&pdev->dev), mc); if (err < 0) { dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", mc->irq, @@ -412,13 +697,14 @@ static int tegra_mc_probe(struct platform_device *pdev) return err; } - WARN(!mc->soc->client_id_mask, "Missing client ID mask for this SoC\n"); - - value = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | - MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | - MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM; - - mc_writel(mc, value, MC_INTMASK); + if (IS_ENABLED(CONFIG_TEGRA_IOMMU_SMMU)) { + mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc); + if (IS_ERR(mc->smmu)) { + dev_err(&pdev->dev, "failed to probe SMMU: %ld\n", + PTR_ERR(mc->smmu)); + return PTR_ERR(mc->smmu); + } + } return 0; } diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h index ddb1667..01065f1 100644 --- a/drivers/memory/tegra/mc.h +++ b/drivers/memory/tegra/mc.h @@ -14,17 +14,39 @@ #include <soc/tegra/mc.h> +#define MC_INT_DECERR_MTS (1 << 16) +#define MC_INT_SECERR_SEC (1 << 13) +#define MC_INT_DECERR_VPR (1 << 12) +#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11) +#define MC_INT_INVALID_SMMU_PAGE (1 << 10) +#define MC_INT_ARBITRATION_EMEM (1 << 9) +#define MC_INT_SECURITY_VIOLATION (1 << 8) +#define MC_INT_INVALID_GART_PAGE (1 << 7) +#define MC_INT_DECERR_EMEM (1 << 6) + static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset) { + if (mc->regs2 && offset >= 0x24) + return readl(mc->regs2 + offset - 0x3c); + return readl(mc->regs + offset); } static inline void mc_writel(struct tegra_mc *mc, u32 value, unsigned long offset) { + if (mc->regs2 && offset >= 0x24) + return writel(value, mc->regs2 + offset - 0x3c); + writel(value, mc->regs + offset); } +extern const struct tegra_mc_reset_ops terga_mc_reset_ops_common; + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +extern const struct tegra_mc_soc tegra20_mc_soc; +#endif + #ifdef CONFIG_ARCH_TEGRA_3x_SOC extern const struct tegra_mc_soc tegra30_mc_soc; #endif diff --git a/drivers/memory/tegra/tegra114.c b/drivers/memory/tegra/tegra114.c index b20e6e3..6560a51 100644 --- a/drivers/memory/tegra/tegra114.c +++ b/drivers/memory/tegra/tegra114.c @@ -938,6 +938,34 @@ static const struct tegra_smmu_soc tegra114_smmu_soc = { .num_asids = 4, }; +#define TEGRA114_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA114_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra114_mc_resets[] = { + TEGRA114_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA114_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA114_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA114_MC_RESET(EPP, 0x200, 0x204, 4), + TEGRA114_MC_RESET(2D, 0x200, 0x204, 5), + TEGRA114_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA114_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA114_MC_RESET(ISP, 0x200, 0x204, 8), + TEGRA114_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA114_MC_RESET(MPCORELP, 0x200, 0x204, 10), + TEGRA114_MC_RESET(MPE, 0x200, 0x204, 11), + TEGRA114_MC_RESET(3D, 0x200, 0x204, 12), + TEGRA114_MC_RESET(3D2, 0x200, 0x204, 13), + TEGRA114_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA114_MC_RESET(VDE, 0x200, 0x204, 16), + TEGRA114_MC_RESET(VI, 0x200, 0x204, 17), +}; + const struct tegra_mc_soc tegra114_mc_soc = { .clients = tegra114_mc_clients, .num_clients = ARRAY_SIZE(tegra114_mc_clients), @@ -945,4 +973,9 @@ const struct tegra_mc_soc tegra114_mc_soc = { .atom_size = 32, .client_id_mask = 0x7f, .smmu = &tegra114_smmu_soc, + .intmask = MC_INT_INVALID_SMMU_PAGE | MC_INT_SECURITY_VIOLATION | + MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra114_mc_resets, + .num_resets = ARRAY_SIZE(tegra114_mc_resets), }; diff --git a/drivers/memory/tegra/tegra124.c b/drivers/memory/tegra/tegra124.c index 8b6360e..b561a1f 100644 --- a/drivers/memory/tegra/tegra124.c +++ b/drivers/memory/tegra/tegra124.c @@ -1012,6 +1012,42 @@ static const struct tegra_smmu_group_soc tegra124_groups[] = { }, }; +#define TEGRA124_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA124_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra124_mc_resets[] = { + TEGRA124_MC_RESET(AFI, 0x200, 0x204, 0), + TEGRA124_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA124_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA124_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA124_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA124_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA124_MC_RESET(ISP2, 0x200, 0x204, 8), + TEGRA124_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA124_MC_RESET(MPCORELP, 0x200, 0x204, 10), + TEGRA124_MC_RESET(MSENC, 0x200, 0x204, 11), + TEGRA124_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA124_MC_RESET(SATA, 0x200, 0x204, 15), + TEGRA124_MC_RESET(VDE, 0x200, 0x204, 16), + TEGRA124_MC_RESET(VI, 0x200, 0x204, 17), + TEGRA124_MC_RESET(VIC, 0x200, 0x204, 18), + TEGRA124_MC_RESET(XUSB_HOST, 0x200, 0x204, 19), + TEGRA124_MC_RESET(XUSB_DEV, 0x200, 0x204, 20), + TEGRA124_MC_RESET(TSEC, 0x200, 0x204, 21), + TEGRA124_MC_RESET(SDMMC1, 0x200, 0x204, 22), + TEGRA124_MC_RESET(SDMMC2, 0x200, 0x204, 23), + TEGRA124_MC_RESET(SDMMC3, 0x200, 0x204, 25), + TEGRA124_MC_RESET(SDMMC4, 0x970, 0x974, 0), + TEGRA124_MC_RESET(ISP2B, 0x970, 0x974, 1), + TEGRA124_MC_RESET(GPU, 0x970, 0x974, 2), +}; + #ifdef CONFIG_ARCH_TEGRA_124_SOC static const struct tegra_smmu_soc tegra124_smmu_soc = { .clients = tegra124_mc_clients, @@ -1035,6 +1071,12 @@ const struct tegra_mc_soc tegra124_mc_soc = { .smmu = &tegra124_smmu_soc, .emem_regs = tegra124_mc_emem_regs, .num_emem_regs = ARRAY_SIZE(tegra124_mc_emem_regs), + .intmask = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | + MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra124_mc_resets, + .num_resets = ARRAY_SIZE(tegra124_mc_resets), }; #endif /* CONFIG_ARCH_TEGRA_124_SOC */ @@ -1059,5 +1101,11 @@ const struct tegra_mc_soc tegra132_mc_soc = { .atom_size = 32, .client_id_mask = 0x7f, .smmu = &tegra132_smmu_soc, + .intmask = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | + MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra124_mc_resets, + .num_resets = ARRAY_SIZE(tegra124_mc_resets), }; #endif /* CONFIG_ARCH_TEGRA_132_SOC */ diff --git a/drivers/memory/tegra/tegra20.c b/drivers/memory/tegra/tegra20.c new file mode 100644 index 0000000..7119e53 --- /dev/null +++ b/drivers/memory/tegra/tegra20.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <dt-bindings/memory/tegra20-mc.h> + +#include "mc.h" + +static const struct tegra_mc_client tegra20_mc_clients[] = { + { + .id = 0x00, + .name = "display0a", + }, { + .id = 0x01, + .name = "display0ab", + }, { + .id = 0x02, + .name = "display0b", + }, { + .id = 0x03, + .name = "display0bb", + }, { + .id = 0x04, + .name = "display0c", + }, { + .id = 0x05, + .name = "display0cb", + }, { + .id = 0x06, + .name = "display1b", + }, { + .id = 0x07, + .name = "display1bb", + }, { + .id = 0x08, + .name = "eppup", + }, { + .id = 0x09, + .name = "g2pr", + }, { + .id = 0x0a, + .name = "g2sr", + }, { + .id = 0x0b, + .name = "mpeunifbr", + }, { + .id = 0x0c, + .name = "viruv", + }, { + .id = 0x0d, + .name = "avpcarm7r", + }, { + .id = 0x0e, + .name = "displayhc", + }, { + .id = 0x0f, + .name = "displayhcb", + }, { + .id = 0x10, + .name = "fdcdrd", + }, { + .id = 0x11, + .name = "g2dr", + }, { + .id = 0x12, + .name = "host1xdmar", + }, { + .id = 0x13, + .name = "host1xr", + }, { + .id = 0x14, + .name = "idxsrd", + }, { + .id = 0x15, + .name = "mpcorer", + }, { + .id = 0x16, + .name = "mpe_ipred", + }, { + .id = 0x17, + .name = "mpeamemrd", + }, { + .id = 0x18, + .name = "mpecsrd", + }, { + .id = 0x19, + .name = "ppcsahbdmar", + }, { + .id = 0x1a, + .name = "ppcsahbslvr", + }, { + .id = 0x1b, + .name = "texsrd", + }, { + .id = 0x1c, + .name = "vdebsevr", + }, { + .id = 0x1d, + .name = "vdember", + }, { + .id = 0x1e, + .name = "vdemcer", + }, { + .id = 0x1f, + .name = "vdetper", + }, { + .id = 0x20, + .name = "eppu", + }, { + .id = 0x21, + .name = "eppv", + }, { + .id = 0x22, + .name = "eppy", + }, { + .id = 0x23, + .name = "mpeunifbw", + }, { + .id = 0x24, + .name = "viwsb", + }, { + .id = 0x25, + .name = "viwu", + }, { + .id = 0x26, + .name = "viwv", + }, { + .id = 0x27, + .name = "viwy", + }, { + .id = 0x28, + .name = "g2dw", + }, { + .id = 0x29, + .name = "avpcarm7w", + }, { + .id = 0x2a, + .name = "fdcdwr", + }, { + .id = 0x2b, + .name = "host1xw", + }, { + .id = 0x2c, + .name = "ispw", + }, { + .id = 0x2d, + .name = "mpcorew", + }, { + .id = 0x2e, + .name = "mpecswr", + }, { + .id = 0x2f, + .name = "ppcsahbdmaw", + }, { + .id = 0x30, + .name = "ppcsahbslvw", + }, { + .id = 0x31, + .name = "vdebsevw", + }, { + .id = 0x32, + .name = "vdembew", + }, { + .id = 0x33, + .name = "vdetpmw", + }, +}; + +#define TEGRA20_MC_RESET(_name, _control, _status, _reset, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA20_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .reset = _reset, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra20_mc_resets[] = { + TEGRA20_MC_RESET(AVPC, 0x100, 0x140, 0x104, 0), + TEGRA20_MC_RESET(DC, 0x100, 0x144, 0x104, 1), + TEGRA20_MC_RESET(DCB, 0x100, 0x148, 0x104, 2), + TEGRA20_MC_RESET(EPP, 0x100, 0x14c, 0x104, 3), + TEGRA20_MC_RESET(2D, 0x100, 0x150, 0x104, 4), + TEGRA20_MC_RESET(HC, 0x100, 0x154, 0x104, 5), + TEGRA20_MC_RESET(ISP, 0x100, 0x158, 0x104, 6), + TEGRA20_MC_RESET(MPCORE, 0x100, 0x15c, 0x104, 7), + TEGRA20_MC_RESET(MPEA, 0x100, 0x160, 0x104, 8), + TEGRA20_MC_RESET(MPEB, 0x100, 0x164, 0x104, 9), + TEGRA20_MC_RESET(MPEC, 0x100, 0x168, 0x104, 10), + TEGRA20_MC_RESET(3D, 0x100, 0x16c, 0x104, 11), + TEGRA20_MC_RESET(PPCS, 0x100, 0x170, 0x104, 12), + TEGRA20_MC_RESET(VDE, 0x100, 0x174, 0x104, 13), + TEGRA20_MC_RESET(VI, 0x100, 0x178, 0x104, 14), +}; + +static int terga20_mc_hotreset_assert(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->reset); + mc_writel(mc, value & ~BIT(rst->bit), rst->reset); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static int terga20_mc_hotreset_deassert(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->reset); + mc_writel(mc, value | BIT(rst->bit), rst->reset); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static int terga20_mc_block_dma(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) & ~BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static bool terga20_mc_dma_idling(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return mc_readl(mc, rst->status) == 0; +} + +static int terga20_mc_reset_status(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return (mc_readl(mc, rst->reset) & BIT(rst->bit)) == 0; +} + +static int terga20_mc_unblock_dma(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) | BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +const struct tegra_mc_reset_ops terga20_mc_reset_ops = { + .hotreset_assert = terga20_mc_hotreset_assert, + .hotreset_deassert = terga20_mc_hotreset_deassert, + .block_dma = terga20_mc_block_dma, + .dma_idling = terga20_mc_dma_idling, + .unblock_dma = terga20_mc_unblock_dma, + .reset_status = terga20_mc_reset_status, +}; + +const struct tegra_mc_soc tegra20_mc_soc = { + .clients = tegra20_mc_clients, + .num_clients = ARRAY_SIZE(tegra20_mc_clients), + .num_address_bits = 32, + .client_id_mask = 0x3f, + .intmask = MC_INT_SECURITY_VIOLATION | MC_INT_INVALID_GART_PAGE | + MC_INT_DECERR_EMEM, + .reset_ops = &terga20_mc_reset_ops, + .resets = tegra20_mc_resets, + .num_resets = ARRAY_SIZE(tegra20_mc_resets), +}; diff --git a/drivers/memory/tegra/tegra210.c b/drivers/memory/tegra/tegra210.c index d398bcd..d00a771 100644 --- a/drivers/memory/tegra/tegra210.c +++ b/drivers/memory/tegra/tegra210.c @@ -6,11 +6,6 @@ * published by the Free Software Foundation. */ -#include <linux/of.h> -#include <linux/mm.h> - -#include <asm/cacheflush.h> - #include <dt-bindings/memory/tegra210-mc.h> #include "mc.h" @@ -1085,6 +1080,48 @@ static const struct tegra_smmu_soc tegra210_smmu_soc = { .num_asids = 128, }; +#define TEGRA210_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA210_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra210_mc_resets[] = { + TEGRA210_MC_RESET(AFI, 0x200, 0x204, 0), + TEGRA210_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA210_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA210_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA210_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA210_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA210_MC_RESET(ISP2, 0x200, 0x204, 8), + TEGRA210_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA210_MC_RESET(NVENC, 0x200, 0x204, 11), + TEGRA210_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA210_MC_RESET(SATA, 0x200, 0x204, 15), + TEGRA210_MC_RESET(VI, 0x200, 0x204, 17), + TEGRA210_MC_RESET(VIC, 0x200, 0x204, 18), + TEGRA210_MC_RESET(XUSB_HOST, 0x200, 0x204, 19), + TEGRA210_MC_RESET(XUSB_DEV, 0x200, 0x204, 20), + TEGRA210_MC_RESET(A9AVP, 0x200, 0x204, 21), + TEGRA210_MC_RESET(TSEC, 0x200, 0x204, 22), + TEGRA210_MC_RESET(SDMMC1, 0x200, 0x204, 29), + TEGRA210_MC_RESET(SDMMC2, 0x200, 0x204, 30), + TEGRA210_MC_RESET(SDMMC3, 0x200, 0x204, 31), + TEGRA210_MC_RESET(SDMMC4, 0x970, 0x974, 0), + TEGRA210_MC_RESET(ISP2B, 0x970, 0x974, 1), + TEGRA210_MC_RESET(GPU, 0x970, 0x974, 2), + TEGRA210_MC_RESET(NVDEC, 0x970, 0x974, 5), + TEGRA210_MC_RESET(APE, 0x970, 0x974, 6), + TEGRA210_MC_RESET(SE, 0x970, 0x974, 7), + TEGRA210_MC_RESET(NVJPG, 0x970, 0x974, 8), + TEGRA210_MC_RESET(AXIAP, 0x970, 0x974, 11), + TEGRA210_MC_RESET(ETR, 0x970, 0x974, 12), + TEGRA210_MC_RESET(TSECB, 0x970, 0x974, 13), +}; + const struct tegra_mc_soc tegra210_mc_soc = { .clients = tegra210_mc_clients, .num_clients = ARRAY_SIZE(tegra210_mc_clients), @@ -1092,4 +1129,10 @@ const struct tegra_mc_soc tegra210_mc_soc = { .atom_size = 64, .client_id_mask = 0xff, .smmu = &tegra210_smmu_soc, + .intmask = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | + MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra210_mc_resets, + .num_resets = ARRAY_SIZE(tegra210_mc_resets), }; diff --git a/drivers/memory/tegra/tegra30.c b/drivers/memory/tegra/tegra30.c index d756c83..bee5314 100644 --- a/drivers/memory/tegra/tegra30.c +++ b/drivers/memory/tegra/tegra30.c @@ -960,6 +960,36 @@ static const struct tegra_smmu_soc tegra30_smmu_soc = { .num_asids = 4, }; +#define TEGRA30_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA30_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra30_mc_resets[] = { + TEGRA30_MC_RESET(AFI, 0x200, 0x204, 0), + TEGRA30_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA30_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA30_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA30_MC_RESET(EPP, 0x200, 0x204, 4), + TEGRA30_MC_RESET(2D, 0x200, 0x204, 5), + TEGRA30_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA30_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA30_MC_RESET(ISP, 0x200, 0x204, 8), + TEGRA30_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA30_MC_RESET(MPCORELP, 0x200, 0x204, 10), + TEGRA30_MC_RESET(MPE, 0x200, 0x204, 11), + TEGRA30_MC_RESET(3D, 0x200, 0x204, 12), + TEGRA30_MC_RESET(3D2, 0x200, 0x204, 13), + TEGRA30_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA30_MC_RESET(SATA, 0x200, 0x204, 15), + TEGRA30_MC_RESET(VDE, 0x200, 0x204, 16), + TEGRA30_MC_RESET(VI, 0x200, 0x204, 17), +}; + const struct tegra_mc_soc tegra30_mc_soc = { .clients = tegra30_mc_clients, .num_clients = ARRAY_SIZE(tegra30_mc_clients), @@ -967,4 +997,9 @@ const struct tegra_mc_soc tegra30_mc_soc = { .atom_size = 16, .client_id_mask = 0x7f, .smmu = &tegra30_smmu_soc, + .intmask = MC_INT_INVALID_SMMU_PAGE | MC_INT_SECURITY_VIOLATION | + MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra30_mc_resets, + .num_resets = ARRAY_SIZE(tegra30_mc_resets), }; diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c deleted file mode 100644 index cc309a0..0000000 --- a/drivers/memory/tegra20-mc.c +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Tegra20 Memory Controller - * - * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include <linux/err.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/ratelimit.h> -#include <linux/platform_device.h> -#include <linux/interrupt.h> -#include <linux/io.h> - -#define DRV_NAME "tegra20-mc" - -#define MC_INTSTATUS 0x0 -#define MC_INTMASK 0x4 - -#define MC_INT_ERR_SHIFT 6 -#define MC_INT_ERR_MASK (0x1f << MC_INT_ERR_SHIFT) -#define MC_INT_DECERR_EMEM BIT(MC_INT_ERR_SHIFT) -#define MC_INT_INVALID_GART_PAGE BIT(MC_INT_ERR_SHIFT + 1) -#define MC_INT_SECURITY_VIOLATION BIT(MC_INT_ERR_SHIFT + 2) -#define MC_INT_ARBITRATION_EMEM BIT(MC_INT_ERR_SHIFT + 3) - -#define MC_GART_ERROR_REQ 0x30 -#define MC_DECERR_EMEM_OTHERS_STATUS 0x58 -#define MC_SECURITY_VIOLATION_STATUS 0x74 - -#define SECURITY_VIOLATION_TYPE BIT(30) /* 0=TRUSTZONE, 1=CARVEOUT */ - -#define MC_CLIENT_ID_MASK 0x3f - -#define NUM_MC_REG_BANKS 2 - -struct tegra20_mc { - void __iomem *regs[NUM_MC_REG_BANKS]; - struct device *dev; -}; - -static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) -{ - u32 val = 0; - - if (offs < 0x24) - val = readl(mc->regs[0] + offs); - else if (offs < 0x400) - val = readl(mc->regs[1] + offs - 0x3c); - - return val; -} - -static inline void mc_writel(struct tegra20_mc *mc, u32 val, u32 offs) -{ - if (offs < 0x24) - writel(val, mc->regs[0] + offs); - else if (offs < 0x400) - writel(val, mc->regs[1] + offs - 0x3c); -} - -static const char * const tegra20_mc_client[] = { - "cbr_display0a", - "cbr_display0ab", - "cbr_display0b", - "cbr_display0bb", - "cbr_display0c", - "cbr_display0cb", - "cbr_display1b", - "cbr_display1bb", - "cbr_eppup", - "cbr_g2pr", - "cbr_g2sr", - "cbr_mpeunifbr", - "cbr_viruv", - "csr_avpcarm7r", - "csr_displayhc", - "csr_displayhcb", - "csr_fdcdrd", - "csr_g2dr", - "csr_host1xdmar", - "csr_host1xr", - "csr_idxsrd", - "csr_mpcorer", - "csr_mpe_ipred", - "csr_mpeamemrd", - "csr_mpecsrd", - "csr_ppcsahbdmar", - "csr_ppcsahbslvr", - "csr_texsrd", - "csr_vdebsevr", - "csr_vdember", - "csr_vdemcer", - "csr_vdetper", - "cbw_eppu", - "cbw_eppv", - "cbw_eppy", - "cbw_mpeunifbw", - "cbw_viwsb", - "cbw_viwu", - "cbw_viwv", - "cbw_viwy", - "ccw_g2dw", - "csw_avpcarm7w", - "csw_fdcdwr", - "csw_host1xw", - "csw_ispw", - "csw_mpcorew", - "csw_mpecswr", - "csw_ppcsahbdmaw", - "csw_ppcsahbslvw", - "csw_vdebsevw", - "csw_vdembew", - "csw_vdetpmw", -}; - -static void tegra20_mc_decode(struct tegra20_mc *mc, int n) -{ - u32 addr, req; - const char *client = "Unknown"; - int idx, cid; - const struct reg_info { - u32 offset; - u32 write_bit; /* 0=READ, 1=WRITE */ - int cid_shift; - char *message; - } reg[] = { - { - .offset = MC_DECERR_EMEM_OTHERS_STATUS, - .write_bit = 31, - .message = "MC_DECERR", - }, - { - .offset = MC_GART_ERROR_REQ, - .cid_shift = 1, - .message = "MC_GART_ERR", - - }, - { - .offset = MC_SECURITY_VIOLATION_STATUS, - .write_bit = 31, - .message = "MC_SECURITY_ERR", - }, - }; - - idx = n - MC_INT_ERR_SHIFT; - if ((idx < 0) || (idx >= ARRAY_SIZE(reg))) { - dev_err_ratelimited(mc->dev, "Unknown interrupt status %08lx\n", - BIT(n)); - return; - } - - req = mc_readl(mc, reg[idx].offset); - cid = (req >> reg[idx].cid_shift) & MC_CLIENT_ID_MASK; - if (cid < ARRAY_SIZE(tegra20_mc_client)) - client = tegra20_mc_client[cid]; - - addr = mc_readl(mc, reg[idx].offset + sizeof(u32)); - - dev_err_ratelimited(mc->dev, "%s (0x%08x): 0x%08x %s (%s %s)\n", - reg[idx].message, req, addr, client, - (req & BIT(reg[idx].write_bit)) ? "write" : "read", - (reg[idx].offset == MC_SECURITY_VIOLATION_STATUS) ? - ((req & SECURITY_VIOLATION_TYPE) ? - "carveout" : "trustzone") : ""); -} - -static const struct of_device_id tegra20_mc_of_match[] = { - { .compatible = "nvidia,tegra20-mc", }, - {}, -}; - -static irqreturn_t tegra20_mc_isr(int irq, void *data) -{ - u32 stat, mask, bit; - struct tegra20_mc *mc = data; - - stat = mc_readl(mc, MC_INTSTATUS); - mask = mc_readl(mc, MC_INTMASK); - mask &= stat; - if (!mask) - return IRQ_NONE; - while ((bit = ffs(mask)) != 0) { - tegra20_mc_decode(mc, bit - 1); - mask &= ~BIT(bit - 1); - } - - mc_writel(mc, stat, MC_INTSTATUS); - return IRQ_HANDLED; -} - -static int tegra20_mc_probe(struct platform_device *pdev) -{ - struct resource *irq; - struct tegra20_mc *mc; - int i, err; - u32 intmask; - - mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); - if (!mc) - return -ENOMEM; - mc->dev = &pdev->dev; - - for (i = 0; i < ARRAY_SIZE(mc->regs); i++) { - struct resource *res; - - res = platform_get_resource(pdev, IORESOURCE_MEM, i); - mc->regs[i] = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(mc->regs[i])) - return PTR_ERR(mc->regs[i]); - } - - irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!irq) - return -ENODEV; - err = devm_request_irq(&pdev->dev, irq->start, tegra20_mc_isr, - IRQF_SHARED, dev_name(&pdev->dev), mc); - if (err) - return -ENODEV; - - platform_set_drvdata(pdev, mc); - - intmask = MC_INT_INVALID_GART_PAGE | - MC_INT_DECERR_EMEM | MC_INT_SECURITY_VIOLATION; - mc_writel(mc, intmask, MC_INTMASK); - return 0; -} - -static struct platform_driver tegra20_mc_driver = { - .probe = tegra20_mc_probe, - .driver = { - .name = DRV_NAME, - .of_match_table = tegra20_mc_of_match, - }, -}; -module_platform_driver(tegra20_mc_driver); - -MODULE_AUTHOR("Hiroshi DOYU <hdoyu@nvidia.com>"); -MODULE_DESCRIPTION("Tegra20 MC driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/memory/ti-aemif.c b/drivers/memory/ti-aemif.c index 2744b1b..31112f6 100644 --- a/drivers/memory/ti-aemif.c +++ b/drivers/memory/ti-aemif.c @@ -339,9 +339,6 @@ static int aemif_probe(struct platform_device *pdev) struct aemif_platform_data *pdata; struct of_dev_auxdata *dev_lookup; - if (np == NULL) - return 0; - aemif = devm_kzalloc(dev, sizeof(*aemif), GFP_KERNEL); if (!aemif) return -ENOMEM; @@ -363,8 +360,10 @@ static int aemif_probe(struct platform_device *pdev) aemif->clk_rate = clk_get_rate(aemif->clk) / MSEC_PER_SEC; - if (of_device_is_compatible(np, "ti,da850-aemif")) + if (np && of_device_is_compatible(np, "ti,da850-aemif")) aemif->cs_offset = 2; + else if (pdata) + aemif->cs_offset = pdata->cs_offset; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); aemif->base = devm_ioremap_resource(dev, res); @@ -373,15 +372,23 @@ static int aemif_probe(struct platform_device *pdev) goto error; } - /* - * For every controller device node, there is a cs device node that - * describe the bus configuration parameters. This functions iterate - * over these nodes and update the cs data array. - */ - for_each_available_child_of_node(np, child_np) { - ret = of_aemif_parse_abus_config(pdev, child_np); - if (ret < 0) - goto error; + if (np) { + /* + * For every controller device node, there is a cs device node + * that describe the bus configuration parameters. This + * functions iterate over these nodes and update the cs data + * array. + */ + for_each_available_child_of_node(np, child_np) { + ret = of_aemif_parse_abus_config(pdev, child_np); + if (ret < 0) + goto error; + } + } else if (pdata && pdata->num_abus_data > 0) { + for (i = 0; i < pdata->num_abus_data; i++, aemif->num_cs++) { + aemif->cs_data[i].cs = pdata->abus_data[i].cs; + aemif_get_hw_params(pdev, i); + } } for (i = 0; i < aemif->num_cs; i++) { @@ -394,14 +401,25 @@ static int aemif_probe(struct platform_device *pdev) } /* - * Create a child devices explicitly from here to - * guarantee that the child will be probed after the AEMIF timing - * parameters are set. + * Create a child devices explicitly from here to guarantee that the + * child will be probed after the AEMIF timing parameters are set. */ - for_each_available_child_of_node(np, child_np) { - ret = of_platform_populate(child_np, NULL, dev_lookup, dev); - if (ret < 0) - goto error; + if (np) { + for_each_available_child_of_node(np, child_np) { + ret = of_platform_populate(child_np, NULL, + dev_lookup, dev); + if (ret < 0) + goto error; + } + } else { + for (i = 0; i < pdata->num_sub_devices; i++) { + pdata->sub_devices[i].dev.parent = dev; + ret = platform_device_register(&pdata->sub_devices[i]); + if (ret) { + dev_warn(dev, "Error register sub device %s\n", + pdata->sub_devices[i].name); + } + } } return 0; @@ -422,7 +440,7 @@ static struct platform_driver aemif_driver = { .probe = aemif_probe, .remove = aemif_remove, .driver = { - .name = KBUILD_MODNAME, + .name = "ti-aemif", .of_match_table = of_match_ptr(aemif_of_match), }, }; diff --git a/drivers/mtd/nand/raw/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c index 7255a0d..cd12e5a 100644 --- a/drivers/mtd/nand/raw/davinci_nand.c +++ b/drivers/mtd/nand/raw/davinci_nand.c @@ -545,7 +545,7 @@ static struct davinci_nand_pdata return ERR_PTR(-ENOMEM); if (!of_property_read_u32(pdev->dev.of_node, "ti,davinci-chipselect", &prop)) - pdev->id = prop; + pdata->core_chipsel = prop; else return ERR_PTR(-EINVAL); @@ -627,7 +627,7 @@ static int nand_davinci_probe(struct platform_device *pdev) return -ENODEV; /* which external chipselect will we be managing? */ - if (pdev->id < 0 || pdev->id > 3) + if (pdata->core_chipsel < 0 || pdata->core_chipsel > 3) return -ENODEV; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); @@ -683,7 +683,7 @@ static int nand_davinci_probe(struct platform_device *pdev) info->ioaddr = (uint32_t __force) vaddr; info->current_cs = info->ioaddr; - info->core_chipsel = pdev->id; + info->core_chipsel = pdata->core_chipsel; info->mask_chipsel = pdata->mask_chipsel; /* use nandboot-capable ALE/CLE masks by default */ diff --git a/drivers/ntb/hw/idt/ntb_hw_idt.c b/drivers/ntb/hw/idt/ntb_hw_idt.c index 8d98872..dbe72f1 100644 --- a/drivers/ntb/hw/idt/ntb_hw_idt.c +++ b/drivers/ntb/hw/idt/ntb_hw_idt.c @@ -1401,7 +1401,7 @@ static int idt_ntb_peer_mw_clear_trans(struct ntb_dev *ntb, int pidx, * 5. Doorbell operations * * Doorbell functionality of IDT PCIe-switches is pretty unusual. First of - * all there is global doorbell register which state can by changed by any + * all there is global doorbell register which state can be changed by any * NT-function of the IDT device in accordance with global permissions. These * permissions configs are not supported by NTB API, so it must be done by * either BIOS or EEPROM settings. In the same way the state of the global diff --git a/drivers/ntb/hw/intel/Makefile b/drivers/ntb/hw/intel/Makefile index 1b43456..4ff22af 100644 --- a/drivers/ntb/hw/intel/Makefile +++ b/drivers/ntb/hw/intel/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_NTB_INTEL) += ntb_hw_intel.o +ntb_hw_intel-y := ntb_hw_gen1.o ntb_hw_gen3.o diff --git a/drivers/ntb/hw/intel/ntb_hw_intel.c b/drivers/ntb/hw/intel/ntb_hw_gen1.c index 3be3231..6aa5732 100644 --- a/drivers/ntb/hw/intel/ntb_hw_intel.c +++ b/drivers/ntb/hw/intel/ntb_hw_gen1.c @@ -45,9 +45,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Intel PCIe NTB Linux driver - * - * Contact Information: - * Jon Mason <jon.mason@intel.com> */ #include <linux/debugfs.h> @@ -61,6 +58,8 @@ #include <linux/ntb.h> #include "ntb_hw_intel.h" +#include "ntb_hw_gen1.h" +#include "ntb_hw_gen3.h" #define NTB_NAME "ntb_hw_intel" #define NTB_DESC "Intel(R) PCI-E Non-Transparent Bridge Driver" @@ -80,14 +79,7 @@ static const struct intel_ntb_alt_reg xeon_sec_reg; static const struct intel_ntb_alt_reg xeon_b2b_reg; static const struct intel_ntb_xlat_reg xeon_pri_xlat; static const struct intel_ntb_xlat_reg xeon_sec_xlat; -static struct intel_b2b_addr xeon_b2b_usd_addr; -static struct intel_b2b_addr xeon_b2b_dsd_addr; -static const struct intel_ntb_reg skx_reg; -static const struct intel_ntb_alt_reg skx_pri_reg; -static const struct intel_ntb_alt_reg skx_b2b_reg; -static const struct intel_ntb_xlat_reg skx_sec_xlat; static const struct ntb_dev_ops intel_ntb_ops; -static const struct ntb_dev_ops intel_ntb3_ops; static const struct file_operations intel_ntb_debugfs_info; static struct dentry *debugfs_dir; @@ -146,68 +138,8 @@ module_param_named(xeon_b2b_dsd_bar5_addr32, MODULE_PARM_DESC(xeon_b2b_dsd_bar5_addr32, "XEON B2B DSD split-BAR 5 32-bit address"); -static inline enum ntb_topo xeon_ppd_topo(struct intel_ntb_dev *ndev, u8 ppd); -static int xeon_init_isr(struct intel_ntb_dev *ndev); - -#ifndef ioread64 -#ifdef readq -#define ioread64 readq -#else -#define ioread64 _ioread64 -static inline u64 _ioread64(void __iomem *mmio) -{ - u64 low, high; - - low = ioread32(mmio); - high = ioread32(mmio + sizeof(u32)); - return low | (high << 32); -} -#endif -#endif - -#ifndef iowrite64 -#ifdef writeq -#define iowrite64 writeq -#else -#define iowrite64 _iowrite64 -static inline void _iowrite64(u64 val, void __iomem *mmio) -{ - iowrite32(val, mmio); - iowrite32(val >> 32, mmio + sizeof(u32)); -} -#endif -#endif - -static inline int pdev_is_xeon(struct pci_dev *pdev) -{ - switch (pdev->device) { - case PCI_DEVICE_ID_INTEL_NTB_SS_JSF: - case PCI_DEVICE_ID_INTEL_NTB_SS_SNB: - case PCI_DEVICE_ID_INTEL_NTB_SS_IVT: - case PCI_DEVICE_ID_INTEL_NTB_SS_HSX: - case PCI_DEVICE_ID_INTEL_NTB_SS_BDX: - case PCI_DEVICE_ID_INTEL_NTB_PS_JSF: - case PCI_DEVICE_ID_INTEL_NTB_PS_SNB: - case PCI_DEVICE_ID_INTEL_NTB_PS_IVT: - case PCI_DEVICE_ID_INTEL_NTB_PS_HSX: - case PCI_DEVICE_ID_INTEL_NTB_PS_BDX: - case PCI_DEVICE_ID_INTEL_NTB_B2B_JSF: - case PCI_DEVICE_ID_INTEL_NTB_B2B_SNB: - case PCI_DEVICE_ID_INTEL_NTB_B2B_IVT: - case PCI_DEVICE_ID_INTEL_NTB_B2B_HSX: - case PCI_DEVICE_ID_INTEL_NTB_B2B_BDX: - return 1; - } - return 0; -} - -static inline int pdev_is_skx_xeon(struct pci_dev *pdev) -{ - if (pdev->device == PCI_DEVICE_ID_INTEL_NTB_B2B_SKX) - return 1; - return 0; -} +static int xeon_init_isr(struct intel_ntb_dev *ndev); static inline void ndev_reset_unsafe_flags(struct intel_ntb_dev *ndev) { @@ -241,7 +173,7 @@ static inline int ndev_ignore_unsafe(struct intel_ntb_dev *ndev, return !!flag; } -static int ndev_mw_to_bar(struct intel_ntb_dev *ndev, int idx) +int ndev_mw_to_bar(struct intel_ntb_dev *ndev, int idx) { if (idx < 0 || idx >= ndev->mw_count) return -EINVAL; @@ -268,7 +200,7 @@ static inline int ndev_db_addr(struct intel_ntb_dev *ndev, return 0; } -static inline u64 ndev_db_read(struct intel_ntb_dev *ndev, +u64 ndev_db_read(struct intel_ntb_dev *ndev, void __iomem *mmio) { if (ndev_is_unsafe(ndev, NTB_UNSAFE_DB)) @@ -277,7 +209,7 @@ static inline u64 ndev_db_read(struct intel_ntb_dev *ndev, return ndev->reg->db_ioread(mmio); } -static inline int ndev_db_write(struct intel_ntb_dev *ndev, u64 db_bits, +int ndev_db_write(struct intel_ntb_dev *ndev, u64 db_bits, void __iomem *mmio) { if (ndev_is_unsafe(ndev, NTB_UNSAFE_DB)) @@ -429,7 +361,7 @@ static irqreturn_t ndev_irq_isr(int irq, void *dev) return ndev_interrupt(ndev, irq - ndev->ntb.pdev->irq); } -static int ndev_init_isr(struct intel_ntb_dev *ndev, +int ndev_init_isr(struct intel_ntb_dev *ndev, int msix_min, int msix_max, int msix_shift, int total_shift) { @@ -557,169 +489,6 @@ static void ndev_deinit_isr(struct intel_ntb_dev *ndev) } } -static ssize_t ndev_ntb3_debugfs_read(struct file *filp, char __user *ubuf, - size_t count, loff_t *offp) -{ - struct intel_ntb_dev *ndev; - void __iomem *mmio; - char *buf; - size_t buf_size; - ssize_t ret, off; - union { u64 v64; u32 v32; u16 v16; } u; - - ndev = filp->private_data; - mmio = ndev->self_mmio; - - buf_size = min(count, 0x800ul); - - buf = kmalloc(buf_size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - off = 0; - - off += scnprintf(buf + off, buf_size - off, - "NTB Device Information:\n"); - - off += scnprintf(buf + off, buf_size - off, - "Connection Topology -\t%s\n", - ntb_topo_string(ndev->ntb.topo)); - - off += scnprintf(buf + off, buf_size - off, - "NTB CTL -\t\t%#06x\n", ndev->ntb_ctl); - off += scnprintf(buf + off, buf_size - off, - "LNK STA -\t\t%#06x\n", ndev->lnk_sta); - - if (!ndev->reg->link_is_up(ndev)) - off += scnprintf(buf + off, buf_size - off, - "Link Status -\t\tDown\n"); - else { - off += scnprintf(buf + off, buf_size - off, - "Link Status -\t\tUp\n"); - off += scnprintf(buf + off, buf_size - off, - "Link Speed -\t\tPCI-E Gen %u\n", - NTB_LNK_STA_SPEED(ndev->lnk_sta)); - off += scnprintf(buf + off, buf_size - off, - "Link Width -\t\tx%u\n", - NTB_LNK_STA_WIDTH(ndev->lnk_sta)); - } - - off += scnprintf(buf + off, buf_size - off, - "Memory Window Count -\t%u\n", ndev->mw_count); - off += scnprintf(buf + off, buf_size - off, - "Scratchpad Count -\t%u\n", ndev->spad_count); - off += scnprintf(buf + off, buf_size - off, - "Doorbell Count -\t%u\n", ndev->db_count); - off += scnprintf(buf + off, buf_size - off, - "Doorbell Vector Count -\t%u\n", ndev->db_vec_count); - off += scnprintf(buf + off, buf_size - off, - "Doorbell Vector Shift -\t%u\n", ndev->db_vec_shift); - - off += scnprintf(buf + off, buf_size - off, - "Doorbell Valid Mask -\t%#llx\n", ndev->db_valid_mask); - off += scnprintf(buf + off, buf_size - off, - "Doorbell Link Mask -\t%#llx\n", ndev->db_link_mask); - off += scnprintf(buf + off, buf_size - off, - "Doorbell Mask Cached -\t%#llx\n", ndev->db_mask); - - u.v64 = ndev_db_read(ndev, mmio + ndev->self_reg->db_mask); - off += scnprintf(buf + off, buf_size - off, - "Doorbell Mask -\t\t%#llx\n", u.v64); - - u.v64 = ndev_db_read(ndev, mmio + ndev->self_reg->db_bell); - off += scnprintf(buf + off, buf_size - off, - "Doorbell Bell -\t\t%#llx\n", u.v64); - - off += scnprintf(buf + off, buf_size - off, - "\nNTB Incoming XLAT:\n"); - - u.v64 = ioread64(mmio + SKX_IMBAR1XBASE_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "IMBAR1XBASE -\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_IMBAR2XBASE_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "IMBAR2XBASE -\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_IMBAR1XLMT_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "IMBAR1XLMT -\t\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_IMBAR2XLMT_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "IMBAR2XLMT -\t\t\t%#018llx\n", u.v64); - - if (ntb_topo_is_b2b(ndev->ntb.topo)) { - off += scnprintf(buf + off, buf_size - off, - "\nNTB Outgoing B2B XLAT:\n"); - - u.v64 = ioread64(mmio + SKX_EMBAR1XBASE_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "EMBAR1XBASE -\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_EMBAR2XBASE_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "EMBAR2XBASE -\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_EMBAR1XLMT_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "EMBAR1XLMT -\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_EMBAR2XLMT_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "EMBAR2XLMT -\t\t%#018llx\n", u.v64); - - off += scnprintf(buf + off, buf_size - off, - "\nNTB Secondary BAR:\n"); - - u.v64 = ioread64(mmio + SKX_EMBAR0_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "EMBAR0 -\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_EMBAR1_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "EMBAR1 -\t\t%#018llx\n", u.v64); - - u.v64 = ioread64(mmio + SKX_EMBAR2_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "EMBAR2 -\t\t%#018llx\n", u.v64); - } - - off += scnprintf(buf + off, buf_size - off, - "\nNTB Statistics:\n"); - - u.v16 = ioread16(mmio + SKX_USMEMMISS_OFFSET); - off += scnprintf(buf + off, buf_size - off, - "Upstream Memory Miss -\t%u\n", u.v16); - - off += scnprintf(buf + off, buf_size - off, - "\nNTB Hardware Errors:\n"); - - if (!pci_read_config_word(ndev->ntb.pdev, - SKX_DEVSTS_OFFSET, &u.v16)) - off += scnprintf(buf + off, buf_size - off, - "DEVSTS -\t\t%#06x\n", u.v16); - - if (!pci_read_config_word(ndev->ntb.pdev, - SKX_LINK_STATUS_OFFSET, &u.v16)) - off += scnprintf(buf + off, buf_size - off, - "LNKSTS -\t\t%#06x\n", u.v16); - - if (!pci_read_config_dword(ndev->ntb.pdev, - SKX_UNCERRSTS_OFFSET, &u.v32)) - off += scnprintf(buf + off, buf_size - off, - "UNCERRSTS -\t\t%#06x\n", u.v32); - - if (!pci_read_config_dword(ndev->ntb.pdev, - SKX_CORERRSTS_OFFSET, &u.v32)) - off += scnprintf(buf + off, buf_size - off, - "CORERRSTS -\t\t%#06x\n", u.v32); - - ret = simple_read_from_buffer(ubuf, count, offp, buf, off); - kfree(buf); - return ret; -} - static ssize_t ndev_ntb_debugfs_read(struct file *filp, char __user *ubuf, size_t count, loff_t *offp) { @@ -879,7 +648,7 @@ static ssize_t ndev_ntb_debugfs_read(struct file *filp, char __user *ubuf, "LMT45 -\t\t\t%#018llx\n", u.v64); } - if (pdev_is_xeon(pdev)) { + if (pdev_is_gen1(pdev)) { if (ntb_topo_is_b2b(ndev->ntb.topo)) { off += scnprintf(buf + off, buf_size - off, "\nNTB Outgoing B2B XLAT:\n"); @@ -991,9 +760,9 @@ static ssize_t ndev_debugfs_read(struct file *filp, char __user *ubuf, { struct intel_ntb_dev *ndev = filp->private_data; - if (pdev_is_xeon(ndev->ntb.pdev)) + if (pdev_is_gen1(ndev->ntb.pdev)) return ndev_ntb_debugfs_read(filp, ubuf, count, offp); - else if (pdev_is_skx_xeon(ndev->ntb.pdev)) + else if (pdev_is_gen3(ndev->ntb.pdev)) return ndev_ntb3_debugfs_read(filp, ubuf, count, offp); return -ENXIO; @@ -1023,7 +792,7 @@ static void ndev_deinit_debugfs(struct intel_ntb_dev *ndev) debugfs_remove_recursive(ndev->debugfs_dir); } -static int intel_ntb_mw_count(struct ntb_dev *ntb, int pidx) +int intel_ntb_mw_count(struct ntb_dev *ntb, int pidx) { if (pidx != NTB_DEF_PEER_IDX) return -EINVAL; @@ -1031,10 +800,10 @@ static int intel_ntb_mw_count(struct ntb_dev *ntb, int pidx) return ntb_ndev(ntb)->mw_count; } -static int intel_ntb_mw_get_align(struct ntb_dev *ntb, int pidx, int idx, - resource_size_t *addr_align, - resource_size_t *size_align, - resource_size_t *size_max) +int intel_ntb_mw_get_align(struct ntb_dev *ntb, int pidx, int idx, + resource_size_t *addr_align, + resource_size_t *size_align, + resource_size_t *size_max) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); resource_size_t bar_size, mw_size; @@ -1170,9 +939,8 @@ static int intel_ntb_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx, return 0; } -static u64 intel_ntb_link_is_up(struct ntb_dev *ntb, - enum ntb_speed *speed, - enum ntb_width *width) +u64 intel_ntb_link_is_up(struct ntb_dev *ntb, enum ntb_speed *speed, + enum ntb_width *width) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1224,7 +992,7 @@ static int intel_ntb_link_enable(struct ntb_dev *ntb, return 0; } -static int intel_ntb_link_disable(struct ntb_dev *ntb) +int intel_ntb_link_disable(struct ntb_dev *ntb) { struct intel_ntb_dev *ndev; u32 ntb_cntl; @@ -1248,14 +1016,14 @@ static int intel_ntb_link_disable(struct ntb_dev *ntb) return 0; } -static int intel_ntb_peer_mw_count(struct ntb_dev *ntb) +int intel_ntb_peer_mw_count(struct ntb_dev *ntb) { /* Numbers of inbound and outbound memory windows match */ return ntb_ndev(ntb)->mw_count; } -static int intel_ntb_peer_mw_get_addr(struct ntb_dev *ntb, int idx, - phys_addr_t *base, resource_size_t *size) +int intel_ntb_peer_mw_get_addr(struct ntb_dev *ntb, int idx, + phys_addr_t *base, resource_size_t *size) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); int bar; @@ -1283,12 +1051,12 @@ static int intel_ntb_db_is_unsafe(struct ntb_dev *ntb) return ndev_ignore_unsafe(ntb_ndev(ntb), NTB_UNSAFE_DB); } -static u64 intel_ntb_db_valid_mask(struct ntb_dev *ntb) +u64 intel_ntb_db_valid_mask(struct ntb_dev *ntb) { return ntb_ndev(ntb)->db_valid_mask; } -static int intel_ntb_db_vector_count(struct ntb_dev *ntb) +int intel_ntb_db_vector_count(struct ntb_dev *ntb) { struct intel_ntb_dev *ndev; @@ -1297,7 +1065,7 @@ static int intel_ntb_db_vector_count(struct ntb_dev *ntb) return ndev->db_vec_count; } -static u64 intel_ntb_db_vector_mask(struct ntb_dev *ntb, int db_vector) +u64 intel_ntb_db_vector_mask(struct ntb_dev *ntb, int db_vector) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1325,7 +1093,7 @@ static int intel_ntb_db_clear(struct ntb_dev *ntb, u64 db_bits) ndev->self_reg->db_bell); } -static int intel_ntb_db_set_mask(struct ntb_dev *ntb, u64 db_bits) +int intel_ntb_db_set_mask(struct ntb_dev *ntb, u64 db_bits) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1334,7 +1102,7 @@ static int intel_ntb_db_set_mask(struct ntb_dev *ntb, u64 db_bits) ndev->self_reg->db_mask); } -static int intel_ntb_db_clear_mask(struct ntb_dev *ntb, u64 db_bits) +int intel_ntb_db_clear_mask(struct ntb_dev *ntb, u64 db_bits) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1343,9 +1111,8 @@ static int intel_ntb_db_clear_mask(struct ntb_dev *ntb, u64 db_bits) ndev->self_reg->db_mask); } -static int intel_ntb_peer_db_addr(struct ntb_dev *ntb, - phys_addr_t *db_addr, - resource_size_t *db_size) +int intel_ntb_peer_db_addr(struct ntb_dev *ntb, phys_addr_t *db_addr, + resource_size_t *db_size) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1362,12 +1129,12 @@ static int intel_ntb_peer_db_set(struct ntb_dev *ntb, u64 db_bits) ndev->peer_reg->db_bell); } -static int intel_ntb_spad_is_unsafe(struct ntb_dev *ntb) +int intel_ntb_spad_is_unsafe(struct ntb_dev *ntb) { return ndev_ignore_unsafe(ntb_ndev(ntb), NTB_UNSAFE_SPAD); } -static int intel_ntb_spad_count(struct ntb_dev *ntb) +int intel_ntb_spad_count(struct ntb_dev *ntb) { struct intel_ntb_dev *ndev; @@ -1376,7 +1143,7 @@ static int intel_ntb_spad_count(struct ntb_dev *ntb) return ndev->spad_count; } -static u32 intel_ntb_spad_read(struct ntb_dev *ntb, int idx) +u32 intel_ntb_spad_read(struct ntb_dev *ntb, int idx) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1385,8 +1152,7 @@ static u32 intel_ntb_spad_read(struct ntb_dev *ntb, int idx) ndev->self_reg->spad); } -static int intel_ntb_spad_write(struct ntb_dev *ntb, - int idx, u32 val) +int intel_ntb_spad_write(struct ntb_dev *ntb, int idx, u32 val) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1395,8 +1161,8 @@ static int intel_ntb_spad_write(struct ntb_dev *ntb, ndev->self_reg->spad); } -static int intel_ntb_peer_spad_addr(struct ntb_dev *ntb, int pidx, int sidx, - phys_addr_t *spad_addr) +int intel_ntb_peer_spad_addr(struct ntb_dev *ntb, int pidx, int sidx, + phys_addr_t *spad_addr) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1404,7 +1170,7 @@ static int intel_ntb_peer_spad_addr(struct ntb_dev *ntb, int pidx, int sidx, ndev->peer_reg->spad); } -static u32 intel_ntb_peer_spad_read(struct ntb_dev *ntb, int pidx, int sidx) +u32 intel_ntb_peer_spad_read(struct ntb_dev *ntb, int pidx, int sidx) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1413,8 +1179,8 @@ static u32 intel_ntb_peer_spad_read(struct ntb_dev *ntb, int pidx, int sidx) ndev->peer_reg->spad); } -static int intel_ntb_peer_spad_write(struct ntb_dev *ntb, int pidx, - int sidx, u32 val) +int intel_ntb_peer_spad_write(struct ntb_dev *ntb, int pidx, int sidx, + u32 val) { struct intel_ntb_dev *ndev = ntb_ndev(ntb); @@ -1423,336 +1189,6 @@ static int intel_ntb_peer_spad_write(struct ntb_dev *ntb, int pidx, ndev->peer_reg->spad); } -/* Skylake Xeon NTB */ - -static int skx_poll_link(struct intel_ntb_dev *ndev) -{ - u16 reg_val; - int rc; - - ndev->reg->db_iowrite(ndev->db_link_mask, - ndev->self_mmio + - ndev->self_reg->db_clear); - - rc = pci_read_config_word(ndev->ntb.pdev, - SKX_LINK_STATUS_OFFSET, ®_val); - if (rc) - return 0; - - if (reg_val == ndev->lnk_sta) - return 0; - - ndev->lnk_sta = reg_val; - - return 1; -} - -static u64 skx_db_ioread(void __iomem *mmio) -{ - return ioread64(mmio); -} - -static void skx_db_iowrite(u64 bits, void __iomem *mmio) -{ - iowrite64(bits, mmio); -} - -static int skx_init_isr(struct intel_ntb_dev *ndev) -{ - int i; - - /* - * The MSIX vectors and the interrupt status bits are not lined up - * on Skylake. By default the link status bit is bit 32, however it - * is by default MSIX vector0. We need to fixup to line them up. - * The vectors at reset is 1-32,0. We need to reprogram to 0-32. - */ - - for (i = 0; i < SKX_DB_MSIX_VECTOR_COUNT; i++) - iowrite8(i, ndev->self_mmio + SKX_INTVEC_OFFSET + i); - - /* move link status down one as workaround */ - if (ndev->hwerr_flags & NTB_HWERR_MSIX_VECTOR32_BAD) { - iowrite8(SKX_DB_MSIX_VECTOR_COUNT - 2, - ndev->self_mmio + SKX_INTVEC_OFFSET + - (SKX_DB_MSIX_VECTOR_COUNT - 1)); - } - - return ndev_init_isr(ndev, SKX_DB_MSIX_VECTOR_COUNT, - SKX_DB_MSIX_VECTOR_COUNT, - SKX_DB_MSIX_VECTOR_SHIFT, - SKX_DB_TOTAL_SHIFT); -} - -static int skx_setup_b2b_mw(struct intel_ntb_dev *ndev, - const struct intel_b2b_addr *addr, - const struct intel_b2b_addr *peer_addr) -{ - struct pci_dev *pdev; - void __iomem *mmio; - phys_addr_t bar_addr; - - pdev = ndev->ntb.pdev; - mmio = ndev->self_mmio; - - /* setup incoming bar limits == base addrs (zero length windows) */ - bar_addr = addr->bar2_addr64; - iowrite64(bar_addr, mmio + SKX_IMBAR1XLMT_OFFSET); - bar_addr = ioread64(mmio + SKX_IMBAR1XLMT_OFFSET); - dev_dbg(&pdev->dev, "IMBAR1XLMT %#018llx\n", bar_addr); - - bar_addr = addr->bar4_addr64; - iowrite64(bar_addr, mmio + SKX_IMBAR2XLMT_OFFSET); - bar_addr = ioread64(mmio + SKX_IMBAR2XLMT_OFFSET); - dev_dbg(&pdev->dev, "IMBAR2XLMT %#018llx\n", bar_addr); - - /* zero incoming translation addrs */ - iowrite64(0, mmio + SKX_IMBAR1XBASE_OFFSET); - iowrite64(0, mmio + SKX_IMBAR2XBASE_OFFSET); - - ndev->peer_mmio = ndev->self_mmio; - - return 0; -} - -static int skx_init_ntb(struct intel_ntb_dev *ndev) -{ - int rc; - - - ndev->mw_count = XEON_MW_COUNT; - ndev->spad_count = SKX_SPAD_COUNT; - ndev->db_count = SKX_DB_COUNT; - ndev->db_link_mask = SKX_DB_LINK_BIT; - - /* DB fixup for using 31 right now */ - if (ndev->hwerr_flags & NTB_HWERR_MSIX_VECTOR32_BAD) - ndev->db_link_mask |= BIT_ULL(31); - - switch (ndev->ntb.topo) { - case NTB_TOPO_B2B_USD: - case NTB_TOPO_B2B_DSD: - ndev->self_reg = &skx_pri_reg; - ndev->peer_reg = &skx_b2b_reg; - ndev->xlat_reg = &skx_sec_xlat; - - if (ndev->ntb.topo == NTB_TOPO_B2B_USD) { - rc = skx_setup_b2b_mw(ndev, - &xeon_b2b_dsd_addr, - &xeon_b2b_usd_addr); - } else { - rc = skx_setup_b2b_mw(ndev, - &xeon_b2b_usd_addr, - &xeon_b2b_dsd_addr); - } - - if (rc) - return rc; - - /* Enable Bus Master and Memory Space on the secondary side */ - iowrite16(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER, - ndev->self_mmio + SKX_SPCICMD_OFFSET); - - break; - - default: - return -EINVAL; - } - - ndev->db_valid_mask = BIT_ULL(ndev->db_count) - 1; - - ndev->reg->db_iowrite(ndev->db_valid_mask, - ndev->self_mmio + - ndev->self_reg->db_mask); - - return 0; -} - -static int skx_init_dev(struct intel_ntb_dev *ndev) -{ - struct pci_dev *pdev; - u8 ppd; - int rc; - - pdev = ndev->ntb.pdev; - - ndev->reg = &skx_reg; - - rc = pci_read_config_byte(pdev, XEON_PPD_OFFSET, &ppd); - if (rc) - return -EIO; - - ndev->ntb.topo = xeon_ppd_topo(ndev, ppd); - dev_dbg(&pdev->dev, "ppd %#x topo %s\n", ppd, - ntb_topo_string(ndev->ntb.topo)); - if (ndev->ntb.topo == NTB_TOPO_NONE) - return -EINVAL; - - if (pdev_is_skx_xeon(pdev)) - ndev->hwerr_flags |= NTB_HWERR_MSIX_VECTOR32_BAD; - - rc = skx_init_ntb(ndev); - if (rc) - return rc; - - return skx_init_isr(ndev); -} - -static int intel_ntb3_link_enable(struct ntb_dev *ntb, - enum ntb_speed max_speed, - enum ntb_width max_width) -{ - struct intel_ntb_dev *ndev; - u32 ntb_ctl; - - ndev = container_of(ntb, struct intel_ntb_dev, ntb); - - dev_dbg(&ntb->pdev->dev, - "Enabling link with max_speed %d max_width %d\n", - max_speed, max_width); - - if (max_speed != NTB_SPEED_AUTO) - dev_dbg(&ntb->pdev->dev, "ignoring max_speed %d\n", max_speed); - if (max_width != NTB_WIDTH_AUTO) - dev_dbg(&ntb->pdev->dev, "ignoring max_width %d\n", max_width); - - ntb_ctl = ioread32(ndev->self_mmio + ndev->reg->ntb_ctl); - ntb_ctl &= ~(NTB_CTL_DISABLE | NTB_CTL_CFG_LOCK); - ntb_ctl |= NTB_CTL_P2S_BAR2_SNOOP | NTB_CTL_S2P_BAR2_SNOOP; - ntb_ctl |= NTB_CTL_P2S_BAR4_SNOOP | NTB_CTL_S2P_BAR4_SNOOP; - iowrite32(ntb_ctl, ndev->self_mmio + ndev->reg->ntb_ctl); - - return 0; -} -static int intel_ntb3_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx, - dma_addr_t addr, resource_size_t size) -{ - struct intel_ntb_dev *ndev = ntb_ndev(ntb); - unsigned long xlat_reg, limit_reg; - resource_size_t bar_size, mw_size; - void __iomem *mmio; - u64 base, limit, reg_val; - int bar; - - if (pidx != NTB_DEF_PEER_IDX) - return -EINVAL; - - if (idx >= ndev->b2b_idx && !ndev->b2b_off) - idx += 1; - - bar = ndev_mw_to_bar(ndev, idx); - if (bar < 0) - return bar; - - bar_size = pci_resource_len(ndev->ntb.pdev, bar); - - if (idx == ndev->b2b_idx) - mw_size = bar_size - ndev->b2b_off; - else - mw_size = bar_size; - - /* hardware requires that addr is aligned to bar size */ - if (addr & (bar_size - 1)) - return -EINVAL; - - /* make sure the range fits in the usable mw size */ - if (size > mw_size) - return -EINVAL; - - mmio = ndev->self_mmio; - xlat_reg = ndev->xlat_reg->bar2_xlat + (idx * 0x10); - limit_reg = ndev->xlat_reg->bar2_limit + (idx * 0x10); - base = pci_resource_start(ndev->ntb.pdev, bar); - - /* Set the limit if supported, if size is not mw_size */ - if (limit_reg && size != mw_size) - limit = base + size; - else - limit = base + mw_size; - - /* set and verify setting the translation address */ - iowrite64(addr, mmio + xlat_reg); - reg_val = ioread64(mmio + xlat_reg); - if (reg_val != addr) { - iowrite64(0, mmio + xlat_reg); - return -EIO; - } - - dev_dbg(&ntb->pdev->dev, "BAR %d IMBARXBASE: %#Lx\n", bar, reg_val); - - /* set and verify setting the limit */ - iowrite64(limit, mmio + limit_reg); - reg_val = ioread64(mmio + limit_reg); - if (reg_val != limit) { - iowrite64(base, mmio + limit_reg); - iowrite64(0, mmio + xlat_reg); - return -EIO; - } - - dev_dbg(&ntb->pdev->dev, "BAR %d IMBARXLMT: %#Lx\n", bar, reg_val); - - /* setup the EP */ - limit_reg = ndev->xlat_reg->bar2_limit + (idx * 0x10) + 0x4000; - base = ioread64(mmio + SKX_EMBAR1_OFFSET + (8 * idx)); - base &= ~0xf; - - if (limit_reg && size != mw_size) - limit = base + size; - else - limit = base + mw_size; - - /* set and verify setting the limit */ - iowrite64(limit, mmio + limit_reg); - reg_val = ioread64(mmio + limit_reg); - if (reg_val != limit) { - iowrite64(base, mmio + limit_reg); - iowrite64(0, mmio + xlat_reg); - return -EIO; - } - - dev_dbg(&ntb->pdev->dev, "BAR %d EMBARXLMT: %#Lx\n", bar, reg_val); - - return 0; -} - -static int intel_ntb3_peer_db_set(struct ntb_dev *ntb, u64 db_bits) -{ - struct intel_ntb_dev *ndev = ntb_ndev(ntb); - int bit; - - if (db_bits & ~ndev->db_valid_mask) - return -EINVAL; - - while (db_bits) { - bit = __ffs(db_bits); - iowrite32(1, ndev->peer_mmio + - ndev->peer_reg->db_bell + (bit * 4)); - db_bits &= db_bits - 1; - } - - return 0; -} - -static u64 intel_ntb3_db_read(struct ntb_dev *ntb) -{ - struct intel_ntb_dev *ndev = ntb_ndev(ntb); - - return ndev_db_read(ndev, - ndev->self_mmio + - ndev->self_reg->db_clear); -} - -static int intel_ntb3_db_clear(struct ntb_dev *ntb, u64 db_bits) -{ - struct intel_ntb_dev *ndev = ntb_ndev(ntb); - - return ndev_db_write(ndev, db_bits, - ndev->self_mmio + - ndev->self_reg->db_clear); -} - -/* XEON */ - static u64 xeon_db_ioread(void __iomem *mmio) { return (u64)ioread16(mmio); @@ -1785,7 +1221,7 @@ static int xeon_poll_link(struct intel_ntb_dev *ndev) return 1; } -static int xeon_link_is_up(struct intel_ntb_dev *ndev) +int xeon_link_is_up(struct intel_ntb_dev *ndev) { if (ndev->ntb.topo == NTB_TOPO_SEC) return 1; @@ -1793,7 +1229,7 @@ static int xeon_link_is_up(struct intel_ntb_dev *ndev) return NTB_LNK_STA_ACTIVE(ndev->lnk_sta); } -static inline enum ntb_topo xeon_ppd_topo(struct intel_ntb_dev *ndev, u8 ppd) +enum ntb_topo xeon_ppd_topo(struct intel_ntb_dev *ndev, u8 ppd) { switch (ppd & XEON_PPD_TOPO_MASK) { case XEON_PPD_TOPO_B2B_USD: @@ -2410,7 +1846,7 @@ static int intel_ntb_pci_probe(struct pci_dev *pdev, node = dev_to_node(&pdev->dev); - if (pdev_is_xeon(pdev)) { + if (pdev_is_gen1(pdev)) { ndev = kzalloc_node(sizeof(*ndev), GFP_KERNEL, node); if (!ndev) { rc = -ENOMEM; @@ -2427,7 +1863,7 @@ static int intel_ntb_pci_probe(struct pci_dev *pdev, if (rc) goto err_init_dev; - } else if (pdev_is_skx_xeon(pdev)) { + } else if (pdev_is_gen3(pdev)) { ndev = kzalloc_node(sizeof(*ndev), GFP_KERNEL, node); if (!ndev) { rc = -ENOMEM; @@ -2441,7 +1877,7 @@ static int intel_ntb_pci_probe(struct pci_dev *pdev, if (rc) goto err_init_pci; - rc = skx_init_dev(ndev); + rc = gen3_init_dev(ndev); if (rc) goto err_init_dev; @@ -2466,7 +1902,7 @@ static int intel_ntb_pci_probe(struct pci_dev *pdev, err_register: ndev_deinit_debugfs(ndev); - if (pdev_is_xeon(pdev) || pdev_is_skx_xeon(pdev)) + if (pdev_is_gen1(pdev) || pdev_is_gen3(pdev)) xeon_deinit_dev(ndev); err_init_dev: intel_ntb_deinit_pci(ndev); @@ -2482,7 +1918,7 @@ static void intel_ntb_pci_remove(struct pci_dev *pdev) ntb_unregister_device(&ndev->ntb); ndev_deinit_debugfs(ndev); - if (pdev_is_xeon(pdev) || pdev_is_skx_xeon(pdev)) + if (pdev_is_gen1(pdev) || pdev_is_gen3(pdev)) xeon_deinit_dev(ndev); intel_ntb_deinit_pci(ndev); kfree(ndev); @@ -2537,50 +1973,20 @@ static const struct intel_ntb_xlat_reg xeon_sec_xlat = { .bar2_xlat = XEON_SBAR23XLAT_OFFSET, }; -static struct intel_b2b_addr xeon_b2b_usd_addr = { +struct intel_b2b_addr xeon_b2b_usd_addr = { .bar2_addr64 = XEON_B2B_BAR2_ADDR64, .bar4_addr64 = XEON_B2B_BAR4_ADDR64, .bar4_addr32 = XEON_B2B_BAR4_ADDR32, .bar5_addr32 = XEON_B2B_BAR5_ADDR32, }; -static struct intel_b2b_addr xeon_b2b_dsd_addr = { +struct intel_b2b_addr xeon_b2b_dsd_addr = { .bar2_addr64 = XEON_B2B_BAR2_ADDR64, .bar4_addr64 = XEON_B2B_BAR4_ADDR64, .bar4_addr32 = XEON_B2B_BAR4_ADDR32, .bar5_addr32 = XEON_B2B_BAR5_ADDR32, }; -static const struct intel_ntb_reg skx_reg = { - .poll_link = skx_poll_link, - .link_is_up = xeon_link_is_up, - .db_ioread = skx_db_ioread, - .db_iowrite = skx_db_iowrite, - .db_size = sizeof(u32), - .ntb_ctl = SKX_NTBCNTL_OFFSET, - .mw_bar = {2, 4}, -}; - -static const struct intel_ntb_alt_reg skx_pri_reg = { - .db_bell = SKX_EM_DOORBELL_OFFSET, - .db_clear = SKX_IM_INT_STATUS_OFFSET, - .db_mask = SKX_IM_INT_DISABLE_OFFSET, - .spad = SKX_IM_SPAD_OFFSET, -}; - -static const struct intel_ntb_alt_reg skx_b2b_reg = { - .db_bell = SKX_IM_DOORBELL_OFFSET, - .db_clear = SKX_EM_INT_STATUS_OFFSET, - .db_mask = SKX_EM_INT_DISABLE_OFFSET, - .spad = SKX_B2B_SPAD_OFFSET, -}; - -static const struct intel_ntb_xlat_reg skx_sec_xlat = { -/* .bar0_base = SKX_EMBAR0_OFFSET, */ - .bar2_limit = SKX_IMBAR1XLMT_OFFSET, - .bar2_xlat = SKX_IMBAR1XBASE_OFFSET, -}; - /* operations for primary side of local ntb */ static const struct ntb_dev_ops intel_ntb_ops = { .mw_count = intel_ntb_mw_count, @@ -2610,33 +2016,6 @@ static const struct ntb_dev_ops intel_ntb_ops = { .peer_spad_write = intel_ntb_peer_spad_write, }; -static const struct ntb_dev_ops intel_ntb3_ops = { - .mw_count = intel_ntb_mw_count, - .mw_get_align = intel_ntb_mw_get_align, - .mw_set_trans = intel_ntb3_mw_set_trans, - .peer_mw_count = intel_ntb_peer_mw_count, - .peer_mw_get_addr = intel_ntb_peer_mw_get_addr, - .link_is_up = intel_ntb_link_is_up, - .link_enable = intel_ntb3_link_enable, - .link_disable = intel_ntb_link_disable, - .db_valid_mask = intel_ntb_db_valid_mask, - .db_vector_count = intel_ntb_db_vector_count, - .db_vector_mask = intel_ntb_db_vector_mask, - .db_read = intel_ntb3_db_read, - .db_clear = intel_ntb3_db_clear, - .db_set_mask = intel_ntb_db_set_mask, - .db_clear_mask = intel_ntb_db_clear_mask, - .peer_db_addr = intel_ntb_peer_db_addr, - .peer_db_set = intel_ntb3_peer_db_set, - .spad_is_unsafe = intel_ntb_spad_is_unsafe, - .spad_count = intel_ntb_spad_count, - .spad_read = intel_ntb_spad_read, - .spad_write = intel_ntb_spad_write, - .peer_spad_addr = intel_ntb_peer_spad_addr, - .peer_spad_read = intel_ntb_peer_spad_read, - .peer_spad_write = intel_ntb_peer_spad_write, -}; - static const struct file_operations intel_ntb_debugfs_info = { .owner = THIS_MODULE, .open = simple_open, diff --git a/drivers/ntb/hw/intel/ntb_hw_gen1.h b/drivers/ntb/hw/intel/ntb_hw_gen1.h new file mode 100644 index 0000000..ad8ec14 --- /dev/null +++ b/drivers/ntb/hw/intel/ntb_hw_gen1.h @@ -0,0 +1,182 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012-2017 Intel Corporation. All rights reserved. + * + * 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. + * + * BSD LICENSE + * + * Copyright(c) 2012-2017 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _NTB_INTEL_GEN1_H_ +#define _NTB_INTEL_GEN1_H_ + +#include "ntb_hw_intel.h" + +/* Intel Gen1 Xeon hardware */ +#define XEON_PBAR23LMT_OFFSET 0x0000 +#define XEON_PBAR45LMT_OFFSET 0x0008 +#define XEON_PBAR4LMT_OFFSET 0x0008 +#define XEON_PBAR5LMT_OFFSET 0x000c +#define XEON_PBAR23XLAT_OFFSET 0x0010 +#define XEON_PBAR45XLAT_OFFSET 0x0018 +#define XEON_PBAR4XLAT_OFFSET 0x0018 +#define XEON_PBAR5XLAT_OFFSET 0x001c +#define XEON_SBAR23LMT_OFFSET 0x0020 +#define XEON_SBAR45LMT_OFFSET 0x0028 +#define XEON_SBAR4LMT_OFFSET 0x0028 +#define XEON_SBAR5LMT_OFFSET 0x002c +#define XEON_SBAR23XLAT_OFFSET 0x0030 +#define XEON_SBAR45XLAT_OFFSET 0x0038 +#define XEON_SBAR4XLAT_OFFSET 0x0038 +#define XEON_SBAR5XLAT_OFFSET 0x003c +#define XEON_SBAR0BASE_OFFSET 0x0040 +#define XEON_SBAR23BASE_OFFSET 0x0048 +#define XEON_SBAR45BASE_OFFSET 0x0050 +#define XEON_SBAR4BASE_OFFSET 0x0050 +#define XEON_SBAR5BASE_OFFSET 0x0054 +#define XEON_SBDF_OFFSET 0x005c +#define XEON_NTBCNTL_OFFSET 0x0058 +#define XEON_PDOORBELL_OFFSET 0x0060 +#define XEON_PDBMSK_OFFSET 0x0062 +#define XEON_SDOORBELL_OFFSET 0x0064 +#define XEON_SDBMSK_OFFSET 0x0066 +#define XEON_USMEMMISS_OFFSET 0x0070 +#define XEON_SPAD_OFFSET 0x0080 +#define XEON_PBAR23SZ_OFFSET 0x00d0 +#define XEON_PBAR45SZ_OFFSET 0x00d1 +#define XEON_PBAR4SZ_OFFSET 0x00d1 +#define XEON_SBAR23SZ_OFFSET 0x00d2 +#define XEON_SBAR45SZ_OFFSET 0x00d3 +#define XEON_SBAR4SZ_OFFSET 0x00d3 +#define XEON_PPD_OFFSET 0x00d4 +#define XEON_PBAR5SZ_OFFSET 0x00d5 +#define XEON_SBAR5SZ_OFFSET 0x00d6 +#define XEON_WCCNTRL_OFFSET 0x00e0 +#define XEON_UNCERRSTS_OFFSET 0x014c +#define XEON_CORERRSTS_OFFSET 0x0158 +#define XEON_LINK_STATUS_OFFSET 0x01a2 +#define XEON_SPCICMD_OFFSET 0x0504 +#define XEON_DEVCTRL_OFFSET 0x0598 +#define XEON_DEVSTS_OFFSET 0x059a +#define XEON_SLINK_STATUS_OFFSET 0x05a2 +#define XEON_B2B_SPAD_OFFSET 0x0100 +#define XEON_B2B_DOORBELL_OFFSET 0x0140 +#define XEON_B2B_XLAT_OFFSETL 0x0144 +#define XEON_B2B_XLAT_OFFSETU 0x0148 +#define XEON_PPD_CONN_MASK 0x03 +#define XEON_PPD_CONN_TRANSPARENT 0x00 +#define XEON_PPD_CONN_B2B 0x01 +#define XEON_PPD_CONN_RP 0x02 +#define XEON_PPD_DEV_MASK 0x10 +#define XEON_PPD_DEV_USD 0x00 +#define XEON_PPD_DEV_DSD 0x10 +#define XEON_PPD_SPLIT_BAR_MASK 0x40 + +#define XEON_PPD_TOPO_MASK (XEON_PPD_CONN_MASK | XEON_PPD_DEV_MASK) +#define XEON_PPD_TOPO_PRI_USD (XEON_PPD_CONN_RP | XEON_PPD_DEV_USD) +#define XEON_PPD_TOPO_PRI_DSD (XEON_PPD_CONN_RP | XEON_PPD_DEV_DSD) +#define XEON_PPD_TOPO_SEC_USD (XEON_PPD_CONN_TRANSPARENT | XEON_PPD_DEV_USD) +#define XEON_PPD_TOPO_SEC_DSD (XEON_PPD_CONN_TRANSPARENT | XEON_PPD_DEV_DSD) +#define XEON_PPD_TOPO_B2B_USD (XEON_PPD_CONN_B2B | XEON_PPD_DEV_USD) +#define XEON_PPD_TOPO_B2B_DSD (XEON_PPD_CONN_B2B | XEON_PPD_DEV_DSD) + +#define XEON_MW_COUNT 2 +#define HSX_SPLIT_BAR_MW_COUNT 3 +#define XEON_DB_COUNT 15 +#define XEON_DB_LINK 15 +#define XEON_DB_LINK_BIT BIT_ULL(XEON_DB_LINK) +#define XEON_DB_MSIX_VECTOR_COUNT 4 +#define XEON_DB_MSIX_VECTOR_SHIFT 5 +#define XEON_DB_TOTAL_SHIFT 16 +#define XEON_SPAD_COUNT 16 + +/* Use the following addresses for translation between b2b ntb devices in case + * the hardware default values are not reliable. */ +#define XEON_B2B_BAR0_ADDR 0x1000000000000000ull +#define XEON_B2B_BAR2_ADDR64 0x2000000000000000ull +#define XEON_B2B_BAR4_ADDR64 0x4000000000000000ull +#define XEON_B2B_BAR4_ADDR32 0x20000000u +#define XEON_B2B_BAR5_ADDR32 0x40000000u + +/* The peer ntb secondary config space is 32KB fixed size */ +#define XEON_B2B_MIN_SIZE 0x8000 + +/* flags to indicate hardware errata */ +#define NTB_HWERR_SDOORBELL_LOCKUP BIT_ULL(0) +#define NTB_HWERR_SB01BASE_LOCKUP BIT_ULL(1) +#define NTB_HWERR_B2BDOORBELL_BIT14 BIT_ULL(2) +#define NTB_HWERR_MSIX_VECTOR32_BAD BIT_ULL(3) + +extern struct intel_b2b_addr xeon_b2b_usd_addr; +extern struct intel_b2b_addr xeon_b2b_dsd_addr; + +int ndev_init_isr(struct intel_ntb_dev *ndev, int msix_min, int msix_max, + int msix_shift, int total_shift); +enum ntb_topo xeon_ppd_topo(struct intel_ntb_dev *ndev, u8 ppd); +u64 ndev_db_read(struct intel_ntb_dev *ndev, void __iomem *mmio); +int ndev_db_write(struct intel_ntb_dev *ndev, u64 db_bits, + void __iomem *mmio); +int ndev_mw_to_bar(struct intel_ntb_dev *ndev, int idx); +int intel_ntb_mw_count(struct ntb_dev *ntb, int pidx); +int intel_ntb_mw_get_align(struct ntb_dev *ntb, int pidx, int idx, + resource_size_t *addr_align, resource_size_t *size_align, + resource_size_t *size_max); +int intel_ntb_peer_mw_count(struct ntb_dev *ntb); +int intel_ntb_peer_mw_get_addr(struct ntb_dev *ntb, int idx, + phys_addr_t *base, resource_size_t *size); +u64 intel_ntb_link_is_up(struct ntb_dev *ntb, enum ntb_speed *speed, + enum ntb_width *width); +int intel_ntb_link_disable(struct ntb_dev *ntb); +u64 intel_ntb_db_valid_mask(struct ntb_dev *ntb); +int intel_ntb_db_vector_count(struct ntb_dev *ntb); +u64 intel_ntb_db_vector_mask(struct ntb_dev *ntb, int db_vector); +int intel_ntb_db_set_mask(struct ntb_dev *ntb, u64 db_bits); +int intel_ntb_db_clear_mask(struct ntb_dev *ntb, u64 db_bits); +int intel_ntb_peer_db_addr(struct ntb_dev *ntb, phys_addr_t *db_addr, + resource_size_t *db_size); +int intel_ntb_spad_is_unsafe(struct ntb_dev *ntb); +int intel_ntb_spad_count(struct ntb_dev *ntb); +u32 intel_ntb_spad_read(struct ntb_dev *ntb, int idx); +int intel_ntb_spad_write(struct ntb_dev *ntb, int idx, u32 val); +u32 intel_ntb_peer_spad_read(struct ntb_dev *ntb, int pidx, int sidx); +int intel_ntb_peer_spad_write(struct ntb_dev *ntb, int pidx, int sidx, + u32 val); +int intel_ntb_peer_spad_addr(struct ntb_dev *ntb, int pidx, int sidx, + phys_addr_t *spad_addr); +int xeon_link_is_up(struct intel_ntb_dev *ndev); + +#endif diff --git a/drivers/ntb/hw/intel/ntb_hw_gen3.c b/drivers/ntb/hw/intel/ntb_hw_gen3.c new file mode 100644 index 0000000..b3fa247 --- /dev/null +++ b/drivers/ntb/hw/intel/ntb_hw_gen3.c @@ -0,0 +1,597 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2017 Intel Corporation. All rights reserved. + * + * 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. + * + * BSD LICENSE + * + * Copyright(c) 2017 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe GEN3 NTB Linux driver + * + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/ntb.h> + +#include "ntb_hw_intel.h" +#include "ntb_hw_gen1.h" +#include "ntb_hw_gen3.h" + +static int gen3_poll_link(struct intel_ntb_dev *ndev); + +static const struct intel_ntb_reg gen3_reg = { + .poll_link = gen3_poll_link, + .link_is_up = xeon_link_is_up, + .db_ioread = gen3_db_ioread, + .db_iowrite = gen3_db_iowrite, + .db_size = sizeof(u32), + .ntb_ctl = GEN3_NTBCNTL_OFFSET, + .mw_bar = {2, 4}, +}; + +static const struct intel_ntb_alt_reg gen3_pri_reg = { + .db_bell = GEN3_EM_DOORBELL_OFFSET, + .db_clear = GEN3_IM_INT_STATUS_OFFSET, + .db_mask = GEN3_IM_INT_DISABLE_OFFSET, + .spad = GEN3_IM_SPAD_OFFSET, +}; + +static const struct intel_ntb_alt_reg gen3_b2b_reg = { + .db_bell = GEN3_IM_DOORBELL_OFFSET, + .db_clear = GEN3_EM_INT_STATUS_OFFSET, + .db_mask = GEN3_EM_INT_DISABLE_OFFSET, + .spad = GEN3_B2B_SPAD_OFFSET, +}; + +static const struct intel_ntb_xlat_reg gen3_sec_xlat = { +/* .bar0_base = GEN3_EMBAR0_OFFSET, */ + .bar2_limit = GEN3_IMBAR1XLMT_OFFSET, + .bar2_xlat = GEN3_IMBAR1XBASE_OFFSET, +}; + +static int gen3_poll_link(struct intel_ntb_dev *ndev) +{ + u16 reg_val; + int rc; + + ndev->reg->db_iowrite(ndev->db_link_mask, + ndev->self_mmio + + ndev->self_reg->db_clear); + + rc = pci_read_config_word(ndev->ntb.pdev, + GEN3_LINK_STATUS_OFFSET, ®_val); + if (rc) + return 0; + + if (reg_val == ndev->lnk_sta) + return 0; + + ndev->lnk_sta = reg_val; + + return 1; +} + +static int gen3_init_isr(struct intel_ntb_dev *ndev) +{ + int i; + + /* + * The MSIX vectors and the interrupt status bits are not lined up + * on Skylake. By default the link status bit is bit 32, however it + * is by default MSIX vector0. We need to fixup to line them up. + * The vectors at reset is 1-32,0. We need to reprogram to 0-32. + */ + + for (i = 0; i < GEN3_DB_MSIX_VECTOR_COUNT; i++) + iowrite8(i, ndev->self_mmio + GEN3_INTVEC_OFFSET + i); + + /* move link status down one as workaround */ + if (ndev->hwerr_flags & NTB_HWERR_MSIX_VECTOR32_BAD) { + iowrite8(GEN3_DB_MSIX_VECTOR_COUNT - 2, + ndev->self_mmio + GEN3_INTVEC_OFFSET + + (GEN3_DB_MSIX_VECTOR_COUNT - 1)); + } + + return ndev_init_isr(ndev, GEN3_DB_MSIX_VECTOR_COUNT, + GEN3_DB_MSIX_VECTOR_COUNT, + GEN3_DB_MSIX_VECTOR_SHIFT, + GEN3_DB_TOTAL_SHIFT); +} + +static int gen3_setup_b2b_mw(struct intel_ntb_dev *ndev, + const struct intel_b2b_addr *addr, + const struct intel_b2b_addr *peer_addr) +{ + struct pci_dev *pdev; + void __iomem *mmio; + phys_addr_t bar_addr; + + pdev = ndev->ntb.pdev; + mmio = ndev->self_mmio; + + /* setup incoming bar limits == base addrs (zero length windows) */ + bar_addr = addr->bar2_addr64; + iowrite64(bar_addr, mmio + GEN3_IMBAR1XLMT_OFFSET); + bar_addr = ioread64(mmio + GEN3_IMBAR1XLMT_OFFSET); + dev_dbg(&pdev->dev, "IMBAR1XLMT %#018llx\n", bar_addr); + + bar_addr = addr->bar4_addr64; + iowrite64(bar_addr, mmio + GEN3_IMBAR2XLMT_OFFSET); + bar_addr = ioread64(mmio + GEN3_IMBAR2XLMT_OFFSET); + dev_dbg(&pdev->dev, "IMBAR2XLMT %#018llx\n", bar_addr); + + /* zero incoming translation addrs */ + iowrite64(0, mmio + GEN3_IMBAR1XBASE_OFFSET); + iowrite64(0, mmio + GEN3_IMBAR2XBASE_OFFSET); + + ndev->peer_mmio = ndev->self_mmio; + + return 0; +} + +static int gen3_init_ntb(struct intel_ntb_dev *ndev) +{ + int rc; + + + ndev->mw_count = XEON_MW_COUNT; + ndev->spad_count = GEN3_SPAD_COUNT; + ndev->db_count = GEN3_DB_COUNT; + ndev->db_link_mask = GEN3_DB_LINK_BIT; + + /* DB fixup for using 31 right now */ + if (ndev->hwerr_flags & NTB_HWERR_MSIX_VECTOR32_BAD) + ndev->db_link_mask |= BIT_ULL(31); + + switch (ndev->ntb.topo) { + case NTB_TOPO_B2B_USD: + case NTB_TOPO_B2B_DSD: + ndev->self_reg = &gen3_pri_reg; + ndev->peer_reg = &gen3_b2b_reg; + ndev->xlat_reg = &gen3_sec_xlat; + + if (ndev->ntb.topo == NTB_TOPO_B2B_USD) { + rc = gen3_setup_b2b_mw(ndev, + &xeon_b2b_dsd_addr, + &xeon_b2b_usd_addr); + } else { + rc = gen3_setup_b2b_mw(ndev, + &xeon_b2b_usd_addr, + &xeon_b2b_dsd_addr); + } + + if (rc) + return rc; + + /* Enable Bus Master and Memory Space on the secondary side */ + iowrite16(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER, + ndev->self_mmio + GEN3_SPCICMD_OFFSET); + + break; + + default: + return -EINVAL; + } + + ndev->db_valid_mask = BIT_ULL(ndev->db_count) - 1; + + ndev->reg->db_iowrite(ndev->db_valid_mask, + ndev->self_mmio + + ndev->self_reg->db_mask); + + return 0; +} + +int gen3_init_dev(struct intel_ntb_dev *ndev) +{ + struct pci_dev *pdev; + u8 ppd; + int rc; + + pdev = ndev->ntb.pdev; + + ndev->reg = &gen3_reg; + + rc = pci_read_config_byte(pdev, XEON_PPD_OFFSET, &ppd); + if (rc) + return -EIO; + + ndev->ntb.topo = xeon_ppd_topo(ndev, ppd); + dev_dbg(&pdev->dev, "ppd %#x topo %s\n", ppd, + ntb_topo_string(ndev->ntb.topo)); + if (ndev->ntb.topo == NTB_TOPO_NONE) + return -EINVAL; + + ndev->hwerr_flags |= NTB_HWERR_MSIX_VECTOR32_BAD; + + rc = gen3_init_ntb(ndev); + if (rc) + return rc; + + return gen3_init_isr(ndev); +} + +ssize_t ndev_ntb3_debugfs_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *offp) +{ + struct intel_ntb_dev *ndev; + void __iomem *mmio; + char *buf; + size_t buf_size; + ssize_t ret, off; + union { u64 v64; u32 v32; u16 v16; } u; + + ndev = filp->private_data; + mmio = ndev->self_mmio; + + buf_size = min(count, 0x800ul); + + buf = kmalloc(buf_size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + off = 0; + + off += scnprintf(buf + off, buf_size - off, + "NTB Device Information:\n"); + + off += scnprintf(buf + off, buf_size - off, + "Connection Topology -\t%s\n", + ntb_topo_string(ndev->ntb.topo)); + + off += scnprintf(buf + off, buf_size - off, + "NTB CTL -\t\t%#06x\n", ndev->ntb_ctl); + off += scnprintf(buf + off, buf_size - off, + "LNK STA -\t\t%#06x\n", ndev->lnk_sta); + + if (!ndev->reg->link_is_up(ndev)) + off += scnprintf(buf + off, buf_size - off, + "Link Status -\t\tDown\n"); + else { + off += scnprintf(buf + off, buf_size - off, + "Link Status -\t\tUp\n"); + off += scnprintf(buf + off, buf_size - off, + "Link Speed -\t\tPCI-E Gen %u\n", + NTB_LNK_STA_SPEED(ndev->lnk_sta)); + off += scnprintf(buf + off, buf_size - off, + "Link Width -\t\tx%u\n", + NTB_LNK_STA_WIDTH(ndev->lnk_sta)); + } + + off += scnprintf(buf + off, buf_size - off, + "Memory Window Count -\t%u\n", ndev->mw_count); + off += scnprintf(buf + off, buf_size - off, + "Scratchpad Count -\t%u\n", ndev->spad_count); + off += scnprintf(buf + off, buf_size - off, + "Doorbell Count -\t%u\n", ndev->db_count); + off += scnprintf(buf + off, buf_size - off, + "Doorbell Vector Count -\t%u\n", ndev->db_vec_count); + off += scnprintf(buf + off, buf_size - off, + "Doorbell Vector Shift -\t%u\n", ndev->db_vec_shift); + + off += scnprintf(buf + off, buf_size - off, + "Doorbell Valid Mask -\t%#llx\n", ndev->db_valid_mask); + off += scnprintf(buf + off, buf_size - off, + "Doorbell Link Mask -\t%#llx\n", ndev->db_link_mask); + off += scnprintf(buf + off, buf_size - off, + "Doorbell Mask Cached -\t%#llx\n", ndev->db_mask); + + u.v64 = ndev_db_read(ndev, mmio + ndev->self_reg->db_mask); + off += scnprintf(buf + off, buf_size - off, + "Doorbell Mask -\t\t%#llx\n", u.v64); + + u.v64 = ndev_db_read(ndev, mmio + ndev->self_reg->db_bell); + off += scnprintf(buf + off, buf_size - off, + "Doorbell Bell -\t\t%#llx\n", u.v64); + + off += scnprintf(buf + off, buf_size - off, + "\nNTB Incoming XLAT:\n"); + + u.v64 = ioread64(mmio + GEN3_IMBAR1XBASE_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "IMBAR1XBASE -\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_IMBAR2XBASE_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "IMBAR2XBASE -\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_IMBAR1XLMT_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "IMBAR1XLMT -\t\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_IMBAR2XLMT_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "IMBAR2XLMT -\t\t\t%#018llx\n", u.v64); + + if (ntb_topo_is_b2b(ndev->ntb.topo)) { + off += scnprintf(buf + off, buf_size - off, + "\nNTB Outgoing B2B XLAT:\n"); + + u.v64 = ioread64(mmio + GEN3_EMBAR1XBASE_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "EMBAR1XBASE -\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_EMBAR2XBASE_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "EMBAR2XBASE -\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_EMBAR1XLMT_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "EMBAR1XLMT -\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_EMBAR2XLMT_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "EMBAR2XLMT -\t\t%#018llx\n", u.v64); + + off += scnprintf(buf + off, buf_size - off, + "\nNTB Secondary BAR:\n"); + + u.v64 = ioread64(mmio + GEN3_EMBAR0_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "EMBAR0 -\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_EMBAR1_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "EMBAR1 -\t\t%#018llx\n", u.v64); + + u.v64 = ioread64(mmio + GEN3_EMBAR2_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "EMBAR2 -\t\t%#018llx\n", u.v64); + } + + off += scnprintf(buf + off, buf_size - off, + "\nNTB Statistics:\n"); + + u.v16 = ioread16(mmio + GEN3_USMEMMISS_OFFSET); + off += scnprintf(buf + off, buf_size - off, + "Upstream Memory Miss -\t%u\n", u.v16); + + off += scnprintf(buf + off, buf_size - off, + "\nNTB Hardware Errors:\n"); + + if (!pci_read_config_word(ndev->ntb.pdev, + GEN3_DEVSTS_OFFSET, &u.v16)) + off += scnprintf(buf + off, buf_size - off, + "DEVSTS -\t\t%#06x\n", u.v16); + + if (!pci_read_config_word(ndev->ntb.pdev, + GEN3_LINK_STATUS_OFFSET, &u.v16)) + off += scnprintf(buf + off, buf_size - off, + "LNKSTS -\t\t%#06x\n", u.v16); + + if (!pci_read_config_dword(ndev->ntb.pdev, + GEN3_UNCERRSTS_OFFSET, &u.v32)) + off += scnprintf(buf + off, buf_size - off, + "UNCERRSTS -\t\t%#06x\n", u.v32); + + if (!pci_read_config_dword(ndev->ntb.pdev, + GEN3_CORERRSTS_OFFSET, &u.v32)) + off += scnprintf(buf + off, buf_size - off, + "CORERRSTS -\t\t%#06x\n", u.v32); + + ret = simple_read_from_buffer(ubuf, count, offp, buf, off); + kfree(buf); + return ret; +} + +static int intel_ntb3_link_enable(struct ntb_dev *ntb, + enum ntb_speed max_speed, + enum ntb_width max_width) +{ + struct intel_ntb_dev *ndev; + u32 ntb_ctl; + + ndev = container_of(ntb, struct intel_ntb_dev, ntb); + + dev_dbg(&ntb->pdev->dev, + "Enabling link with max_speed %d max_width %d\n", + max_speed, max_width); + + if (max_speed != NTB_SPEED_AUTO) + dev_dbg(&ntb->pdev->dev, "ignoring max_speed %d\n", max_speed); + if (max_width != NTB_WIDTH_AUTO) + dev_dbg(&ntb->pdev->dev, "ignoring max_width %d\n", max_width); + + ntb_ctl = ioread32(ndev->self_mmio + ndev->reg->ntb_ctl); + ntb_ctl &= ~(NTB_CTL_DISABLE | NTB_CTL_CFG_LOCK); + ntb_ctl |= NTB_CTL_P2S_BAR2_SNOOP | NTB_CTL_S2P_BAR2_SNOOP; + ntb_ctl |= NTB_CTL_P2S_BAR4_SNOOP | NTB_CTL_S2P_BAR4_SNOOP; + iowrite32(ntb_ctl, ndev->self_mmio + ndev->reg->ntb_ctl); + + return 0; +} +static int intel_ntb3_mw_set_trans(struct ntb_dev *ntb, int pidx, int idx, + dma_addr_t addr, resource_size_t size) +{ + struct intel_ntb_dev *ndev = ntb_ndev(ntb); + unsigned long xlat_reg, limit_reg; + resource_size_t bar_size, mw_size; + void __iomem *mmio; + u64 base, limit, reg_val; + int bar; + + if (pidx != NTB_DEF_PEER_IDX) + return -EINVAL; + + if (idx >= ndev->b2b_idx && !ndev->b2b_off) + idx += 1; + + bar = ndev_mw_to_bar(ndev, idx); + if (bar < 0) + return bar; + + bar_size = pci_resource_len(ndev->ntb.pdev, bar); + + if (idx == ndev->b2b_idx) + mw_size = bar_size - ndev->b2b_off; + else + mw_size = bar_size; + + /* hardware requires that addr is aligned to bar size */ + if (addr & (bar_size - 1)) + return -EINVAL; + + /* make sure the range fits in the usable mw size */ + if (size > mw_size) + return -EINVAL; + + mmio = ndev->self_mmio; + xlat_reg = ndev->xlat_reg->bar2_xlat + (idx * 0x10); + limit_reg = ndev->xlat_reg->bar2_limit + (idx * 0x10); + base = pci_resource_start(ndev->ntb.pdev, bar); + + /* Set the limit if supported, if size is not mw_size */ + if (limit_reg && size != mw_size) + limit = base + size; + else + limit = base + mw_size; + + /* set and verify setting the translation address */ + iowrite64(addr, mmio + xlat_reg); + reg_val = ioread64(mmio + xlat_reg); + if (reg_val != addr) { + iowrite64(0, mmio + xlat_reg); + return -EIO; + } + + dev_dbg(&ntb->pdev->dev, "BAR %d IMBARXBASE: %#Lx\n", bar, reg_val); + + /* set and verify setting the limit */ + iowrite64(limit, mmio + limit_reg); + reg_val = ioread64(mmio + limit_reg); + if (reg_val != limit) { + iowrite64(base, mmio + limit_reg); + iowrite64(0, mmio + xlat_reg); + return -EIO; + } + + dev_dbg(&ntb->pdev->dev, "BAR %d IMBARXLMT: %#Lx\n", bar, reg_val); + + /* setup the EP */ + limit_reg = ndev->xlat_reg->bar2_limit + (idx * 0x10) + 0x4000; + base = ioread64(mmio + GEN3_EMBAR1_OFFSET + (8 * idx)); + base &= ~0xf; + + if (limit_reg && size != mw_size) + limit = base + size; + else + limit = base + mw_size; + + /* set and verify setting the limit */ + iowrite64(limit, mmio + limit_reg); + reg_val = ioread64(mmio + limit_reg); + if (reg_val != limit) { + iowrite64(base, mmio + limit_reg); + iowrite64(0, mmio + xlat_reg); + return -EIO; + } + + dev_dbg(&ntb->pdev->dev, "BAR %d EMBARXLMT: %#Lx\n", bar, reg_val); + + return 0; +} + +static int intel_ntb3_peer_db_set(struct ntb_dev *ntb, u64 db_bits) +{ + struct intel_ntb_dev *ndev = ntb_ndev(ntb); + int bit; + + if (db_bits & ~ndev->db_valid_mask) + return -EINVAL; + + while (db_bits) { + bit = __ffs(db_bits); + iowrite32(1, ndev->peer_mmio + + ndev->peer_reg->db_bell + (bit * 4)); + db_bits &= db_bits - 1; + } + + return 0; +} + +static u64 intel_ntb3_db_read(struct ntb_dev *ntb) +{ + struct intel_ntb_dev *ndev = ntb_ndev(ntb); + + return ndev_db_read(ndev, + ndev->self_mmio + + ndev->self_reg->db_clear); +} + +static int intel_ntb3_db_clear(struct ntb_dev *ntb, u64 db_bits) +{ + struct intel_ntb_dev *ndev = ntb_ndev(ntb); + + return ndev_db_write(ndev, db_bits, + ndev->self_mmio + + ndev->self_reg->db_clear); +} + +const struct ntb_dev_ops intel_ntb3_ops = { + .mw_count = intel_ntb_mw_count, + .mw_get_align = intel_ntb_mw_get_align, + .mw_set_trans = intel_ntb3_mw_set_trans, + .peer_mw_count = intel_ntb_peer_mw_count, + .peer_mw_get_addr = intel_ntb_peer_mw_get_addr, + .link_is_up = intel_ntb_link_is_up, + .link_enable = intel_ntb3_link_enable, + .link_disable = intel_ntb_link_disable, + .db_valid_mask = intel_ntb_db_valid_mask, + .db_vector_count = intel_ntb_db_vector_count, + .db_vector_mask = intel_ntb_db_vector_mask, + .db_read = intel_ntb3_db_read, + .db_clear = intel_ntb3_db_clear, + .db_set_mask = intel_ntb_db_set_mask, + .db_clear_mask = intel_ntb_db_clear_mask, + .peer_db_addr = intel_ntb_peer_db_addr, + .peer_db_set = intel_ntb3_peer_db_set, + .spad_is_unsafe = intel_ntb_spad_is_unsafe, + .spad_count = intel_ntb_spad_count, + .spad_read = intel_ntb_spad_read, + .spad_write = intel_ntb_spad_write, + .peer_spad_addr = intel_ntb_peer_spad_addr, + .peer_spad_read = intel_ntb_peer_spad_read, + .peer_spad_write = intel_ntb_peer_spad_write, +}; + diff --git a/drivers/ntb/hw/intel/ntb_hw_gen3.h b/drivers/ntb/hw/intel/ntb_hw_gen3.h new file mode 100644 index 0000000..75fb86c --- /dev/null +++ b/drivers/ntb/hw/intel/ntb_hw_gen3.h @@ -0,0 +1,110 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012-2017 Intel Corporation. All rights reserved. + * + * 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. + * + * BSD LICENSE + * + * Copyright(c) 2012-2017 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _NTB_INTEL_GEN3_H_ +#define _NTB_INTEL_GEN3_H_ + +#include "ntb_hw_intel.h" + +/* Intel Skylake Xeon hardware */ +#define GEN3_IMBAR1SZ_OFFSET 0x00d0 +#define GEN3_IMBAR2SZ_OFFSET 0x00d1 +#define GEN3_EMBAR1SZ_OFFSET 0x00d2 +#define GEN3_EMBAR2SZ_OFFSET 0x00d3 +#define GEN3_DEVCTRL_OFFSET 0x0098 +#define GEN3_DEVSTS_OFFSET 0x009a +#define GEN3_UNCERRSTS_OFFSET 0x014c +#define GEN3_CORERRSTS_OFFSET 0x0158 +#define GEN3_LINK_STATUS_OFFSET 0x01a2 + +#define GEN3_NTBCNTL_OFFSET 0x0000 +#define GEN3_IMBAR1XBASE_OFFSET 0x0010 /* SBAR2XLAT */ +#define GEN3_IMBAR1XLMT_OFFSET 0x0018 /* SBAR2LMT */ +#define GEN3_IMBAR2XBASE_OFFSET 0x0020 /* SBAR4XLAT */ +#define GEN3_IMBAR2XLMT_OFFSET 0x0028 /* SBAR4LMT */ +#define GEN3_IM_INT_STATUS_OFFSET 0x0040 +#define GEN3_IM_INT_DISABLE_OFFSET 0x0048 +#define GEN3_IM_SPAD_OFFSET 0x0080 /* SPAD */ +#define GEN3_USMEMMISS_OFFSET 0x0070 +#define GEN3_INTVEC_OFFSET 0x00d0 +#define GEN3_IM_DOORBELL_OFFSET 0x0100 /* SDOORBELL0 */ +#define GEN3_B2B_SPAD_OFFSET 0x0180 /* B2B SPAD */ +#define GEN3_EMBAR0XBASE_OFFSET 0x4008 /* B2B_XLAT */ +#define GEN3_EMBAR1XBASE_OFFSET 0x4010 /* PBAR2XLAT */ +#define GEN3_EMBAR1XLMT_OFFSET 0x4018 /* PBAR2LMT */ +#define GEN3_EMBAR2XBASE_OFFSET 0x4020 /* PBAR4XLAT */ +#define GEN3_EMBAR2XLMT_OFFSET 0x4028 /* PBAR4LMT */ +#define GEN3_EM_INT_STATUS_OFFSET 0x4040 +#define GEN3_EM_INT_DISABLE_OFFSET 0x4048 +#define GEN3_EM_SPAD_OFFSET 0x4080 /* remote SPAD */ +#define GEN3_EM_DOORBELL_OFFSET 0x4100 /* PDOORBELL0 */ +#define GEN3_SPCICMD_OFFSET 0x4504 /* SPCICMD */ +#define GEN3_EMBAR0_OFFSET 0x4510 /* SBAR0BASE */ +#define GEN3_EMBAR1_OFFSET 0x4518 /* SBAR23BASE */ +#define GEN3_EMBAR2_OFFSET 0x4520 /* SBAR45BASE */ + +#define GEN3_DB_COUNT 32 +#define GEN3_DB_LINK 32 +#define GEN3_DB_LINK_BIT BIT_ULL(GEN3_DB_LINK) +#define GEN3_DB_MSIX_VECTOR_COUNT 33 +#define GEN3_DB_MSIX_VECTOR_SHIFT 1 +#define GEN3_DB_TOTAL_SHIFT 33 +#define GEN3_SPAD_COUNT 16 + +static inline u64 gen3_db_ioread(void __iomem *mmio) +{ + return ioread64(mmio); +} + +static inline void gen3_db_iowrite(u64 bits, void __iomem *mmio) +{ + iowrite64(bits, mmio); +} + +ssize_t ndev_ntb3_debugfs_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *offp); +int gen3_init_dev(struct intel_ntb_dev *ndev); + +extern const struct ntb_dev_ops intel_ntb3_ops; + +#endif diff --git a/drivers/ntb/hw/intel/ntb_hw_intel.h b/drivers/ntb/hw/intel/ntb_hw_intel.h index 4415aa7..c49ff89 100644 --- a/drivers/ntb/hw/intel/ntb_hw_intel.h +++ b/drivers/ntb/hw/intel/ntb_hw_intel.h @@ -54,6 +54,7 @@ #include <linux/ntb.h> #include <linux/pci.h> +/* PCI device IDs */ #define PCI_DEVICE_ID_INTEL_NTB_B2B_JSF 0x3725 #define PCI_DEVICE_ID_INTEL_NTB_PS_JSF 0x3726 #define PCI_DEVICE_ID_INTEL_NTB_SS_JSF 0x3727 @@ -71,132 +72,7 @@ #define PCI_DEVICE_ID_INTEL_NTB_SS_BDX 0x6F0F #define PCI_DEVICE_ID_INTEL_NTB_B2B_SKX 0x201C -/* Intel Xeon hardware */ - -#define XEON_PBAR23LMT_OFFSET 0x0000 -#define XEON_PBAR45LMT_OFFSET 0x0008 -#define XEON_PBAR4LMT_OFFSET 0x0008 -#define XEON_PBAR5LMT_OFFSET 0x000c -#define XEON_PBAR23XLAT_OFFSET 0x0010 -#define XEON_PBAR45XLAT_OFFSET 0x0018 -#define XEON_PBAR4XLAT_OFFSET 0x0018 -#define XEON_PBAR5XLAT_OFFSET 0x001c -#define XEON_SBAR23LMT_OFFSET 0x0020 -#define XEON_SBAR45LMT_OFFSET 0x0028 -#define XEON_SBAR4LMT_OFFSET 0x0028 -#define XEON_SBAR5LMT_OFFSET 0x002c -#define XEON_SBAR23XLAT_OFFSET 0x0030 -#define XEON_SBAR45XLAT_OFFSET 0x0038 -#define XEON_SBAR4XLAT_OFFSET 0x0038 -#define XEON_SBAR5XLAT_OFFSET 0x003c -#define XEON_SBAR0BASE_OFFSET 0x0040 -#define XEON_SBAR23BASE_OFFSET 0x0048 -#define XEON_SBAR45BASE_OFFSET 0x0050 -#define XEON_SBAR4BASE_OFFSET 0x0050 -#define XEON_SBAR5BASE_OFFSET 0x0054 -#define XEON_SBDF_OFFSET 0x005c -#define XEON_NTBCNTL_OFFSET 0x0058 -#define XEON_PDOORBELL_OFFSET 0x0060 -#define XEON_PDBMSK_OFFSET 0x0062 -#define XEON_SDOORBELL_OFFSET 0x0064 -#define XEON_SDBMSK_OFFSET 0x0066 -#define XEON_USMEMMISS_OFFSET 0x0070 -#define XEON_SPAD_OFFSET 0x0080 -#define XEON_PBAR23SZ_OFFSET 0x00d0 -#define XEON_PBAR45SZ_OFFSET 0x00d1 -#define XEON_PBAR4SZ_OFFSET 0x00d1 -#define XEON_SBAR23SZ_OFFSET 0x00d2 -#define XEON_SBAR45SZ_OFFSET 0x00d3 -#define XEON_SBAR4SZ_OFFSET 0x00d3 -#define XEON_PPD_OFFSET 0x00d4 -#define XEON_PBAR5SZ_OFFSET 0x00d5 -#define XEON_SBAR5SZ_OFFSET 0x00d6 -#define XEON_WCCNTRL_OFFSET 0x00e0 -#define XEON_UNCERRSTS_OFFSET 0x014c -#define XEON_CORERRSTS_OFFSET 0x0158 -#define XEON_LINK_STATUS_OFFSET 0x01a2 -#define XEON_SPCICMD_OFFSET 0x0504 -#define XEON_DEVCTRL_OFFSET 0x0598 -#define XEON_DEVSTS_OFFSET 0x059a -#define XEON_SLINK_STATUS_OFFSET 0x05a2 -#define XEON_B2B_SPAD_OFFSET 0x0100 -#define XEON_B2B_DOORBELL_OFFSET 0x0140 -#define XEON_B2B_XLAT_OFFSETL 0x0144 -#define XEON_B2B_XLAT_OFFSETU 0x0148 -#define XEON_PPD_CONN_MASK 0x03 -#define XEON_PPD_CONN_TRANSPARENT 0x00 -#define XEON_PPD_CONN_B2B 0x01 -#define XEON_PPD_CONN_RP 0x02 -#define XEON_PPD_DEV_MASK 0x10 -#define XEON_PPD_DEV_USD 0x00 -#define XEON_PPD_DEV_DSD 0x10 -#define XEON_PPD_SPLIT_BAR_MASK 0x40 - -#define XEON_PPD_TOPO_MASK (XEON_PPD_CONN_MASK | XEON_PPD_DEV_MASK) -#define XEON_PPD_TOPO_PRI_USD (XEON_PPD_CONN_RP | XEON_PPD_DEV_USD) -#define XEON_PPD_TOPO_PRI_DSD (XEON_PPD_CONN_RP | XEON_PPD_DEV_DSD) -#define XEON_PPD_TOPO_SEC_USD (XEON_PPD_CONN_TRANSPARENT | XEON_PPD_DEV_USD) -#define XEON_PPD_TOPO_SEC_DSD (XEON_PPD_CONN_TRANSPARENT | XEON_PPD_DEV_DSD) -#define XEON_PPD_TOPO_B2B_USD (XEON_PPD_CONN_B2B | XEON_PPD_DEV_USD) -#define XEON_PPD_TOPO_B2B_DSD (XEON_PPD_CONN_B2B | XEON_PPD_DEV_DSD) - -#define XEON_MW_COUNT 2 -#define HSX_SPLIT_BAR_MW_COUNT 3 -#define XEON_DB_COUNT 15 -#define XEON_DB_LINK 15 -#define XEON_DB_LINK_BIT BIT_ULL(XEON_DB_LINK) -#define XEON_DB_MSIX_VECTOR_COUNT 4 -#define XEON_DB_MSIX_VECTOR_SHIFT 5 -#define XEON_DB_TOTAL_SHIFT 16 -#define XEON_SPAD_COUNT 16 - -/* Intel Skylake Xeon hardware */ -#define SKX_IMBAR1SZ_OFFSET 0x00d0 -#define SKX_IMBAR2SZ_OFFSET 0x00d1 -#define SKX_EMBAR1SZ_OFFSET 0x00d2 -#define SKX_EMBAR2SZ_OFFSET 0x00d3 -#define SKX_DEVCTRL_OFFSET 0x0098 -#define SKX_DEVSTS_OFFSET 0x009a -#define SKX_UNCERRSTS_OFFSET 0x014c -#define SKX_CORERRSTS_OFFSET 0x0158 -#define SKX_LINK_STATUS_OFFSET 0x01a2 - -#define SKX_NTBCNTL_OFFSET 0x0000 -#define SKX_IMBAR1XBASE_OFFSET 0x0010 /* SBAR2XLAT */ -#define SKX_IMBAR1XLMT_OFFSET 0x0018 /* SBAR2LMT */ -#define SKX_IMBAR2XBASE_OFFSET 0x0020 /* SBAR4XLAT */ -#define SKX_IMBAR2XLMT_OFFSET 0x0028 /* SBAR4LMT */ -#define SKX_IM_INT_STATUS_OFFSET 0x0040 -#define SKX_IM_INT_DISABLE_OFFSET 0x0048 -#define SKX_IM_SPAD_OFFSET 0x0080 /* SPAD */ -#define SKX_USMEMMISS_OFFSET 0x0070 -#define SKX_INTVEC_OFFSET 0x00d0 -#define SKX_IM_DOORBELL_OFFSET 0x0100 /* SDOORBELL0 */ -#define SKX_B2B_SPAD_OFFSET 0x0180 /* B2B SPAD */ -#define SKX_EMBAR0XBASE_OFFSET 0x4008 /* B2B_XLAT */ -#define SKX_EMBAR1XBASE_OFFSET 0x4010 /* PBAR2XLAT */ -#define SKX_EMBAR1XLMT_OFFSET 0x4018 /* PBAR2LMT */ -#define SKX_EMBAR2XBASE_OFFSET 0x4020 /* PBAR4XLAT */ -#define SKX_EMBAR2XLMT_OFFSET 0x4028 /* PBAR4LMT */ -#define SKX_EM_INT_STATUS_OFFSET 0x4040 -#define SKX_EM_INT_DISABLE_OFFSET 0x4048 -#define SKX_EM_SPAD_OFFSET 0x4080 /* remote SPAD */ -#define SKX_EM_DOORBELL_OFFSET 0x4100 /* PDOORBELL0 */ -#define SKX_SPCICMD_OFFSET 0x4504 /* SPCICMD */ -#define SKX_EMBAR0_OFFSET 0x4510 /* SBAR0BASE */ -#define SKX_EMBAR1_OFFSET 0x4518 /* SBAR23BASE */ -#define SKX_EMBAR2_OFFSET 0x4520 /* SBAR45BASE */ - -#define SKX_DB_COUNT 32 -#define SKX_DB_LINK 32 -#define SKX_DB_LINK_BIT BIT_ULL(SKX_DB_LINK) -#define SKX_DB_MSIX_VECTOR_COUNT 33 -#define SKX_DB_MSIX_VECTOR_SHIFT 1 -#define SKX_DB_TOTAL_SHIFT 33 -#define SKX_SPAD_COUNT 16 - /* Ntb control and link status */ - #define NTB_CTL_CFG_LOCK BIT(0) #define NTB_CTL_DISABLE BIT(1) #define NTB_CTL_S2P_BAR2_SNOOP BIT(2) @@ -213,23 +89,6 @@ #define NTB_LNK_STA_SPEED(x) ((x) & NTB_LNK_STA_SPEED_MASK) #define NTB_LNK_STA_WIDTH(x) (((x) & NTB_LNK_STA_WIDTH_MASK) >> 4) -/* Use the following addresses for translation between b2b ntb devices in case - * the hardware default values are not reliable. */ -#define XEON_B2B_BAR0_ADDR 0x1000000000000000ull -#define XEON_B2B_BAR2_ADDR64 0x2000000000000000ull -#define XEON_B2B_BAR4_ADDR64 0x4000000000000000ull -#define XEON_B2B_BAR4_ADDR32 0x20000000u -#define XEON_B2B_BAR5_ADDR32 0x40000000u - -/* The peer ntb secondary config space is 32KB fixed size */ -#define XEON_B2B_MIN_SIZE 0x8000 - -/* flags to indicate hardware errata */ -#define NTB_HWERR_SDOORBELL_LOCKUP BIT_ULL(0) -#define NTB_HWERR_SB01BASE_LOCKUP BIT_ULL(1) -#define NTB_HWERR_B2BDOORBELL_BIT14 BIT_ULL(2) -#define NTB_HWERR_MSIX_VECTOR32_BAD BIT_ULL(3) - /* flags to indicate unsafe api */ #define NTB_UNSAFE_DB BIT_ULL(0) #define NTB_UNSAFE_SPAD BIT_ULL(1) @@ -328,4 +187,64 @@ struct intel_ntb_dev { #define hb_ndev(__work) container_of(__work, struct intel_ntb_dev, \ hb_timer.work) +static inline int pdev_is_gen1(struct pci_dev *pdev) +{ + switch (pdev->device) { + case PCI_DEVICE_ID_INTEL_NTB_SS_JSF: + case PCI_DEVICE_ID_INTEL_NTB_SS_SNB: + case PCI_DEVICE_ID_INTEL_NTB_SS_IVT: + case PCI_DEVICE_ID_INTEL_NTB_SS_HSX: + case PCI_DEVICE_ID_INTEL_NTB_SS_BDX: + case PCI_DEVICE_ID_INTEL_NTB_PS_JSF: + case PCI_DEVICE_ID_INTEL_NTB_PS_SNB: + case PCI_DEVICE_ID_INTEL_NTB_PS_IVT: + case PCI_DEVICE_ID_INTEL_NTB_PS_HSX: + case PCI_DEVICE_ID_INTEL_NTB_PS_BDX: + case PCI_DEVICE_ID_INTEL_NTB_B2B_JSF: + case PCI_DEVICE_ID_INTEL_NTB_B2B_SNB: + case PCI_DEVICE_ID_INTEL_NTB_B2B_IVT: + case PCI_DEVICE_ID_INTEL_NTB_B2B_HSX: + case PCI_DEVICE_ID_INTEL_NTB_B2B_BDX: + return 1; + } + return 0; +} + +static inline int pdev_is_gen3(struct pci_dev *pdev) +{ + if (pdev->device == PCI_DEVICE_ID_INTEL_NTB_B2B_SKX) + return 1; + + return 0; +} + +#ifndef ioread64 +#ifdef readq +#define ioread64 readq +#else +#define ioread64 _ioread64 +static inline u64 _ioread64(void __iomem *mmio) +{ + u64 low, high; + + low = ioread32(mmio); + high = ioread32(mmio + sizeof(u32)); + return low | (high << 32); +} +#endif +#endif + +#ifndef iowrite64 +#ifdef writeq +#define iowrite64 writeq +#else +#define iowrite64 _iowrite64 +static inline void _iowrite64(u64 val, void __iomem *mmio) +{ + iowrite32(val, mmio); + iowrite32(val >> 32, mmio + sizeof(u32)); +} +#endif +#endif + #endif diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 504bdcc..9398959 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -637,7 +637,7 @@ static int ntb_transport_setup_qp_mw(struct ntb_transport_ctx *nt, */ node = dev_to_node(&ndev->dev); for (i = qp->rx_alloc_entry; i < qp->rx_max_entry; i++) { - entry = kzalloc_node(sizeof(*entry), GFP_ATOMIC, node); + entry = kzalloc_node(sizeof(*entry), GFP_KERNEL, node); if (!entry) return -ENOMEM; @@ -1828,7 +1828,7 @@ ntb_transport_create_queue(void *data, struct device *client_dev, qp->rx_dma_chan ? "DMA" : "CPU"); for (i = 0; i < NTB_QP_DEF_NUM_ENTRIES; i++) { - entry = kzalloc_node(sizeof(*entry), GFP_ATOMIC, node); + entry = kzalloc_node(sizeof(*entry), GFP_KERNEL, node); if (!entry) goto err1; @@ -1839,7 +1839,7 @@ ntb_transport_create_queue(void *data, struct device *client_dev, qp->rx_alloc_entry = NTB_QP_DEF_NUM_ENTRIES; for (i = 0; i < qp->tx_max_entry; i++) { - entry = kzalloc_node(sizeof(*entry), GFP_ATOMIC, node); + entry = kzalloc_node(sizeof(*entry), GFP_KERNEL, node); if (!entry) goto err2; diff --git a/drivers/of/platform.c b/drivers/of/platform.c index 14cc962..6925d99 100644 --- a/drivers/of/platform.c +++ b/drivers/of/platform.c @@ -505,6 +505,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_populate); #ifndef CONFIG_PPC static const struct of_device_id reserved_mem_matches[] = { { .compatible = "qcom,rmtfs-mem" }, + { .compatible = "qcom,cmd-db" }, { .compatible = "ramoops" }, {} }; diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index b2f0763..56ff8f6 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -145,8 +145,6 @@ config PCI_HYPERV PCI devices from a PCI backend to support PCI driver domains. source "drivers/pci/hotplug/Kconfig" -source "drivers/pci/cadence/Kconfig" -source "drivers/pci/dwc/Kconfig" -source "drivers/pci/host/Kconfig" +source "drivers/pci/controller/Kconfig" source "drivers/pci/endpoint/Kconfig" source "drivers/pci/switch/Kconfig" diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 84c9eef..5352019 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -28,14 +28,10 @@ obj-$(CONFIG_PCI_PF_STUB) += pci-pf-stub.o obj-$(CONFIG_PCI_ECAM) += ecam.o obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o -obj-y += host/ +obj-y += controller/ obj-y += switch/ # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/ -obj-$(CONFIG_PCIE_CADENCE) += cadence/ -# pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW -obj-y += dwc/ - ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG diff --git a/drivers/pci/cadence/Kconfig b/drivers/pci/cadence/Kconfig deleted file mode 100644 index e6824cb..0000000 --- a/drivers/pci/cadence/Kconfig +++ /dev/null @@ -1,27 +0,0 @@ -menu "Cadence PCIe controllers support" - -config PCIE_CADENCE - bool - -config PCIE_CADENCE_HOST - bool "Cadence PCIe host controller" - depends on OF - depends on PCI - select IRQ_DOMAIN - select PCIE_CADENCE - help - Say Y here if you want to support the Cadence PCIe controller in host - mode. This PCIe controller may be embedded into many different vendors - SoCs. - -config PCIE_CADENCE_EP - bool "Cadence PCIe endpoint controller" - depends on OF - depends on PCI_ENDPOINT - select PCIE_CADENCE - help - Say Y here if you want to support the Cadence PCIe controller in - endpoint mode. This PCIe controller may be embedded into many - different vendors SoCs. - -endmenu diff --git a/drivers/pci/cadence/Makefile b/drivers/pci/cadence/Makefile deleted file mode 100644 index 719392b..0000000 --- a/drivers/pci/cadence/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_PCIE_CADENCE) += pcie-cadence.o -obj-$(CONFIG_PCIE_CADENCE_HOST) += pcie-cadence-host.o -obj-$(CONFIG_PCIE_CADENCE_EP) += pcie-cadence-ep.o diff --git a/drivers/pci/host/Kconfig b/drivers/pci/controller/Kconfig index a96e23b..18fa09b 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -menu "PCI host controller drivers" +menu "PCI controller drivers" depends on PCI config PCI_MVEBU @@ -20,6 +20,34 @@ config PCI_AARDVARK controller is part of the South Bridge of the Marvel Armada 3700 SoC. +menu "Cadence PCIe controllers support" + +config PCIE_CADENCE + bool + +config PCIE_CADENCE_HOST + bool "Cadence PCIe host controller" + depends on OF + depends on PCI + select IRQ_DOMAIN + select PCIE_CADENCE + help + Say Y here if you want to support the Cadence PCIe controller in host + mode. This PCIe controller may be embedded into many different vendors + SoCs. + +config PCIE_CADENCE_EP + bool "Cadence PCIe endpoint controller" + depends on OF + depends on PCI_ENDPOINT + select PCIE_CADENCE + help + Say Y here if you want to support the Cadence PCIe controller in + endpoint mode. This PCIe controller may be embedded into many + different vendors SoCs. + +endmenu + config PCIE_XILINX_NWL bool "NWL PCIe Core" depends on ARCH_ZYNQMP || COMPILE_TEST @@ -243,4 +271,5 @@ config VMD To compile this driver as a module, choose M here: the module will be called vmd. +source "drivers/pci/controller/dwc/Kconfig" endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/controller/Makefile index 11d21b0..24322b9 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/controller/Makefile @@ -1,4 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_PCIE_CADENCE) += pcie-cadence.o +obj-$(CONFIG_PCIE_CADENCE_HOST) += pcie-cadence-host.o +obj-$(CONFIG_PCIE_CADENCE_EP) += pcie-cadence-ep.o obj-$(CONFIG_PCI_FTPCI100) += pci-ftpci100.o obj-$(CONFIG_PCI_HYPERV) += pci-hyperv.o obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o @@ -25,6 +28,9 @@ obj-$(CONFIG_PCIE_ROCKCHIP_HOST) += pcie-rockchip-host.o obj-$(CONFIG_PCIE_MEDIATEK) += pcie-mediatek.o obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o obj-$(CONFIG_VMD) += vmd.o +# pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW +obj-y += dwc/ + # The following drivers are for devices that use the generic ACPI # pci_root.c driver but don't support standard ECAM config access. diff --git a/drivers/pci/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index 16f52c6..16f52c6 100644 --- a/drivers/pci/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig diff --git a/drivers/pci/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index 5d2ce72..5d2ce72 100644 --- a/drivers/pci/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile diff --git a/drivers/pci/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c index 2810c6a..345aab5 100644 --- a/drivers/pci/dwc/pci-dra7xx.c +++ b/drivers/pci/controller/dwc/pci-dra7xx.c @@ -27,7 +27,7 @@ #include <linux/mfd/syscon.h> #include <linux/regmap.h> -#include "../pci.h" +#include "../../pci.h" #include "pcie-designware.h" /* PCIe controller wrapper DRA7XX configuration registers */ diff --git a/drivers/pci/dwc/pci-exynos.c b/drivers/pci/controller/dwc/pci-exynos.c index 4cc1e5d..4cc1e5d 100644 --- a/drivers/pci/dwc/pci-exynos.c +++ b/drivers/pci/controller/dwc/pci-exynos.c diff --git a/drivers/pci/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 80f6046..80f6046 100644 --- a/drivers/pci/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c diff --git a/drivers/pci/dwc/pci-keystone-dw.c b/drivers/pci/controller/dwc/pci-keystone-dw.c index 0682213..0682213 100644 --- a/drivers/pci/dwc/pci-keystone-dw.c +++ b/drivers/pci/controller/dwc/pci-keystone-dw.c diff --git a/drivers/pci/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index 3722a5f..3722a5f 100644 --- a/drivers/pci/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c diff --git a/drivers/pci/dwc/pci-keystone.h b/drivers/pci/controller/dwc/pci-keystone.h index 8a13da3..8a13da3 100644 --- a/drivers/pci/dwc/pci-keystone.h +++ b/drivers/pci/controller/dwc/pci-keystone.h diff --git a/drivers/pci/dwc/pci-layerscape.c b/drivers/pci/controller/dwc/pci-layerscape.c index 3724d3e..3724d3e 100644 --- a/drivers/pci/dwc/pci-layerscape.c +++ b/drivers/pci/controller/dwc/pci-layerscape.c diff --git a/drivers/pci/dwc/pcie-armada8k.c b/drivers/pci/controller/dwc/pcie-armada8k.c index 072fd7e..072fd7e 100644 --- a/drivers/pci/dwc/pcie-armada8k.c +++ b/drivers/pci/controller/dwc/pcie-armada8k.c diff --git a/drivers/pci/dwc/pcie-artpec6.c b/drivers/pci/controller/dwc/pcie-artpec6.c index 321b56c..321b56c 100644 --- a/drivers/pci/dwc/pcie-artpec6.c +++ b/drivers/pci/controller/dwc/pcie-artpec6.c diff --git a/drivers/pci/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 8650416..8650416 100644 --- a/drivers/pci/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c diff --git a/drivers/pci/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index cba1432..781aa03 100644 --- a/drivers/pci/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -15,7 +15,7 @@ #include <linux/pci_regs.h> #include <linux/platform_device.h> -#include "../pci.h" +#include "../../pci.h" #include "pcie-designware.h" static struct pci_ops dw_pcie_ops; diff --git a/drivers/pci/dwc/pcie-designware-plat.c b/drivers/pci/controller/dwc/pcie-designware-plat.c index 5937fed..5937fed 100644 --- a/drivers/pci/dwc/pcie-designware-plat.c +++ b/drivers/pci/controller/dwc/pcie-designware-plat.c diff --git a/drivers/pci/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 778c4f7..778c4f7 100644 --- a/drivers/pci/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c diff --git a/drivers/pci/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index bee4e25..bee4e25 100644 --- a/drivers/pci/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h diff --git a/drivers/pci/dwc/pcie-hisi.c b/drivers/pci/controller/dwc/pcie-hisi.c index 2658aae..6d9e1b2 100644 --- a/drivers/pci/dwc/pcie-hisi.c +++ b/drivers/pci/controller/dwc/pcie-hisi.c @@ -19,7 +19,7 @@ #include <linux/pci-acpi.h> #include <linux/pci-ecam.h> #include <linux/regmap.h> -#include "../pci.h" +#include "../../pci.h" #if defined(CONFIG_PCI_HISI) || (defined(CONFIG_ACPI) && defined(CONFIG_PCI_QUIRKS)) diff --git a/drivers/pci/dwc/pcie-histb.c b/drivers/pci/controller/dwc/pcie-histb.c index 3611d6c..3611d6c 100644 --- a/drivers/pci/dwc/pcie-histb.c +++ b/drivers/pci/controller/dwc/pcie-histb.c diff --git a/drivers/pci/dwc/pcie-kirin.c b/drivers/pci/controller/dwc/pcie-kirin.c index d2970a0..d2970a0 100644 --- a/drivers/pci/dwc/pcie-kirin.c +++ b/drivers/pci/controller/dwc/pcie-kirin.c diff --git a/drivers/pci/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index a1d0198..a1d0198 100644 --- a/drivers/pci/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c diff --git a/drivers/pci/dwc/pcie-spear13xx.c b/drivers/pci/controller/dwc/pcie-spear13xx.c index ecb58f7..ecb58f7 100644 --- a/drivers/pci/dwc/pcie-spear13xx.c +++ b/drivers/pci/controller/dwc/pcie-spear13xx.c diff --git a/drivers/pci/host/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c index d3172d5..d3172d5 100644 --- a/drivers/pci/host/pci-aardvark.c +++ b/drivers/pci/controller/pci-aardvark.c diff --git a/drivers/pci/host/pci-ftpci100.c b/drivers/pci/controller/pci-ftpci100.c index a1ebe9e..a1ebe9e 100644 --- a/drivers/pci/host/pci-ftpci100.c +++ b/drivers/pci/controller/pci-ftpci100.c diff --git a/drivers/pci/host/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index d8f1045..d8f1045 100644 --- a/drivers/pci/host/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/controller/pci-host-generic.c index dea3ec7..dea3ec7 100644 --- a/drivers/pci/host/pci-host-generic.c +++ b/drivers/pci/controller/pci-host-generic.c diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index 6cc5036..6cc5036 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c index 23e2708..23e2708 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/controller/pci-mvebu.c diff --git a/drivers/pci/host/pci-rcar-gen2.c b/drivers/pci/controller/pci-rcar-gen2.c index 326171c..326171c 100644 --- a/drivers/pci/host/pci-rcar-gen2.c +++ b/drivers/pci/controller/pci-rcar-gen2.c diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/controller/pci-tegra.c index f4f53d0..f4f53d0 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/controller/pci-tegra.c diff --git a/drivers/pci/host/pci-thunder-ecam.c b/drivers/pci/controller/pci-thunder-ecam.c index 32d1d7b..32d1d7b 100644 --- a/drivers/pci/host/pci-thunder-ecam.c +++ b/drivers/pci/controller/pci-thunder-ecam.c diff --git a/drivers/pci/host/pci-thunder-pem.c b/drivers/pci/controller/pci-thunder-pem.c index f127ce8..f127ce8 100644 --- a/drivers/pci/host/pci-thunder-pem.c +++ b/drivers/pci/controller/pci-thunder-pem.c diff --git a/drivers/pci/host/pci-v3-semi.c b/drivers/pci/controller/pci-v3-semi.c index 68b8bfb..68b8bfb 100644 --- a/drivers/pci/host/pci-v3-semi.c +++ b/drivers/pci/controller/pci-v3-semi.c diff --git a/drivers/pci/host/pci-versatile.c b/drivers/pci/controller/pci-versatile.c index 994f320..994f320 100644 --- a/drivers/pci/host/pci-versatile.c +++ b/drivers/pci/controller/pci-versatile.c diff --git a/drivers/pci/host/pci-xgene-msi.c b/drivers/pci/controller/pci-xgene-msi.c index f4c02da..f4c02da 100644 --- a/drivers/pci/host/pci-xgene-msi.c +++ b/drivers/pci/controller/pci-xgene-msi.c diff --git a/drivers/pci/host/pci-xgene.c b/drivers/pci/controller/pci-xgene.c index d854d67..d854d67 100644 --- a/drivers/pci/host/pci-xgene.c +++ b/drivers/pci/controller/pci-xgene.c diff --git a/drivers/pci/host/pcie-altera-msi.c b/drivers/pci/controller/pcie-altera-msi.c index 025ef7d..025ef7d 100644 --- a/drivers/pci/host/pcie-altera-msi.c +++ b/drivers/pci/controller/pcie-altera-msi.c diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/controller/pcie-altera.c index 7d05e51..7d05e51 100644 --- a/drivers/pci/host/pcie-altera.c +++ b/drivers/pci/controller/pcie-altera.c diff --git a/drivers/pci/cadence/pcie-cadence-ep.c b/drivers/pci/controller/pcie-cadence-ep.c index e3fe412..e3fe412 100644 --- a/drivers/pci/cadence/pcie-cadence-ep.c +++ b/drivers/pci/controller/pcie-cadence-ep.c diff --git a/drivers/pci/cadence/pcie-cadence-host.c b/drivers/pci/controller/pcie-cadence-host.c index a4ebbd3..a4ebbd3 100644 --- a/drivers/pci/cadence/pcie-cadence-host.c +++ b/drivers/pci/controller/pcie-cadence-host.c diff --git a/drivers/pci/cadence/pcie-cadence.c b/drivers/pci/controller/pcie-cadence.c index 138d113..138d113 100644 --- a/drivers/pci/cadence/pcie-cadence.c +++ b/drivers/pci/controller/pcie-cadence.c diff --git a/drivers/pci/cadence/pcie-cadence.h b/drivers/pci/controller/pcie-cadence.h index 4bb2733..4bb2733 100644 --- a/drivers/pci/cadence/pcie-cadence.h +++ b/drivers/pci/controller/pcie-cadence.h diff --git a/drivers/pci/host/pcie-iproc-bcma.c b/drivers/pci/controller/pcie-iproc-bcma.c index aa55b06..aa55b06 100644 --- a/drivers/pci/host/pcie-iproc-bcma.c +++ b/drivers/pci/controller/pcie-iproc-bcma.c diff --git a/drivers/pci/host/pcie-iproc-msi.c b/drivers/pci/controller/pcie-iproc-msi.c index 9deb56989..9deb56989 100644 --- a/drivers/pci/host/pcie-iproc-msi.c +++ b/drivers/pci/controller/pcie-iproc-msi.c diff --git a/drivers/pci/host/pcie-iproc-platform.c b/drivers/pci/controller/pcie-iproc-platform.c index f30f5f3..f30f5f3 100644 --- a/drivers/pci/host/pcie-iproc-platform.c +++ b/drivers/pci/controller/pcie-iproc-platform.c diff --git a/drivers/pci/host/pcie-iproc.c b/drivers/pci/controller/pcie-iproc.c index 3c76c5f..3c76c5f 100644 --- a/drivers/pci/host/pcie-iproc.c +++ b/drivers/pci/controller/pcie-iproc.c diff --git a/drivers/pci/host/pcie-iproc.h b/drivers/pci/controller/pcie-iproc.h index 814b600..814b600 100644 --- a/drivers/pci/host/pcie-iproc.h +++ b/drivers/pci/controller/pcie-iproc.h diff --git a/drivers/pci/host/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index 0baabe3..0baabe3 100644 --- a/drivers/pci/host/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c diff --git a/drivers/pci/host/pcie-mobiveil.c b/drivers/pci/controller/pcie-mobiveil.c index 4d6c20e..4d6c20e 100644 --- a/drivers/pci/host/pcie-mobiveil.c +++ b/drivers/pci/controller/pcie-mobiveil.c diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/controller/pcie-rcar.c index 874d75c..874d75c 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/controller/pcie-rcar.c diff --git a/drivers/pci/host/pcie-rockchip-ep.c b/drivers/pci/controller/pcie-rockchip-ep.c index 6beba8e..6beba8e 100644 --- a/drivers/pci/host/pcie-rockchip-ep.c +++ b/drivers/pci/controller/pcie-rockchip-ep.c diff --git a/drivers/pci/host/pcie-rockchip-host.c b/drivers/pci/controller/pcie-rockchip-host.c index 1372d27..1372d27 100644 --- a/drivers/pci/host/pcie-rockchip-host.c +++ b/drivers/pci/controller/pcie-rockchip-host.c diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/controller/pcie-rockchip.c index c53d132..c53d132 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/controller/pcie-rockchip.c diff --git a/drivers/pci/host/pcie-rockchip.h b/drivers/pci/controller/pcie-rockchip.h index 8e87a05..8e87a05 100644 --- a/drivers/pci/host/pcie-rockchip.h +++ b/drivers/pci/controller/pcie-rockchip.h diff --git a/drivers/pci/host/pcie-tango.c b/drivers/pci/controller/pcie-tango.c index 21a208d..21a208d 100644 --- a/drivers/pci/host/pcie-tango.c +++ b/drivers/pci/controller/pcie-tango.c diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/controller/pcie-xilinx-nwl.c index 6a4bbb5..6a4bbb5 100644 --- a/drivers/pci/host/pcie-xilinx-nwl.c +++ b/drivers/pci/controller/pcie-xilinx-nwl.c diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/controller/pcie-xilinx.c index b110a3a..b110a3a 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/controller/pcie-xilinx.c diff --git a/drivers/pci/host/vmd.c b/drivers/pci/controller/vmd.c index 942b64f..942b64f 100644 --- a/drivers/pci/host/vmd.c +++ b/drivers/pci/controller/vmd.c diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index b12e28b..0a1e9d3 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -23,7 +23,42 @@ config HOTPLUG_PCI_PCIE When in doubt, say N. -source "drivers/pci/pcie/aer/Kconfig" +config PCIEAER + bool "PCI Express Advanced Error Reporting support" + depends on PCIEPORTBUS + select RAS + default y + help + This enables PCI Express Root Port Advanced Error Reporting + (AER) driver support. Error reporting messages sent to Root + Port will be handled by PCI Express AER driver. + +config PCIEAER_INJECT + tristate "PCI Express error injection support" + depends on PCIEAER + default n + help + This enables PCI Express Root Port Advanced Error Reporting + (AER) software error injector. + + Debugging AER code is quite difficult because it is hard + to trigger various real hardware errors. Software-based + error injection can fake almost all kinds of errors with the + help of a user space helper tool aer-inject, which can be + gotten from: + http://www.kernel.org/pub/linux/utils/pci/aer-inject/ + +# +# PCI Express ECRC +# +config PCIE_ECRC + bool "PCI Express ECRC settings control" + depends on PCIEAER + help + Used to override firmware/bios settings for PCI Express ECRC + (transaction layer end-to-end CRC checking). + + When in doubt, say N. # # PCI Express ASPM @@ -92,7 +127,7 @@ config PCIE_PME depends on PCIEPORTBUS && PM config PCIE_DPC - bool "PCIe Downstream Port Containment support" + bool "PCI Express Downstream Port Containment support" depends on PCIEPORTBUS && PCIEAER default n help @@ -103,7 +138,7 @@ config PCIE_DPC it is safe to answer N. config PCIE_PTM - bool "PCIe Precision Time Measurement support" + bool "PCI Express Precision Time Measurement support" default n depends on PCIEPORTBUS help diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index 03f4e0b..ab51408 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -7,7 +7,8 @@ pcieportdrv-y := portdrv_core.o portdrv_pci.o err.o obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o obj-$(CONFIG_PCIEASPM) += aspm.o -obj-$(CONFIG_PCIEAER) += aer/ +obj-$(CONFIG_PCIEAER) += aer.o +obj-$(CONFIG_PCIEAER_INJECT) += aer_inject.o obj-$(CONFIG_PCIE_PME) += pme.o obj-$(CONFIG_PCIE_DPC) += dpc.o obj-$(CONFIG_PCIE_PTM) += ptm.o diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c new file mode 100644 index 0000000..a2e8838 --- /dev/null +++ b/drivers/pci/pcie/aer.c @@ -0,0 +1,1377 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implement the AER root port service driver. The driver registers an IRQ + * handler. When a root port triggers an AER interrupt, the IRQ handler + * collects root port status and schedules work. + * + * Copyright (C) 2006 Intel Corp. + * Tom Long Nguyen (tom.l.nguyen@intel.com) + * Zhang Yanmin (yanmin.zhang@intel.com) + * + * (C) Copyright 2009 Hewlett-Packard Development Company, L.P. + * Andrew Patterson <andrew.patterson@hp.com> + */ + +#include <linux/cper.h> +#include <linux/pci.h> +#include <linux/pci-acpi.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/kfifo.h> +#include <linux/slab.h> +#include <acpi/apei.h> +#include <ras/ras_event.h> + +#include "../pci.h" +#include "portdrv.h" + +#define AER_ERROR_SOURCES_MAX 100 +#define AER_MAX_MULTI_ERR_DEVICES 5 /* Not likely to have more */ + +struct aer_err_info { + struct pci_dev *dev[AER_MAX_MULTI_ERR_DEVICES]; + int error_dev_num; + + unsigned int id:16; + + unsigned int severity:2; /* 0:NONFATAL | 1:FATAL | 2:COR */ + unsigned int __pad1:5; + unsigned int multi_error_valid:1; + + unsigned int first_error:5; + unsigned int __pad2:2; + unsigned int tlp_header_valid:1; + + unsigned int status; /* COR/UNCOR Error Status */ + unsigned int mask; /* COR/UNCOR Error Mask */ + struct aer_header_log_regs tlp; /* TLP Header */ +}; + +struct aer_err_source { + unsigned int status; + unsigned int id; +}; + +struct aer_rpc { + struct pci_dev *rpd; /* Root Port device */ + struct work_struct dpc_handler; + struct aer_err_source e_sources[AER_ERROR_SOURCES_MAX]; + struct aer_err_info e_info; + unsigned short prod_idx; /* Error Producer Index */ + unsigned short cons_idx; /* Error Consumer Index */ + int isr; + spinlock_t e_lock; /* + * Lock access to Error Status/ID Regs + * and error producer/consumer index + */ + struct mutex rpc_mutex; /* + * only one thread could do + * recovery on the same + * root port hierarchy + */ +}; + +#define AER_LOG_TLP_MASKS (PCI_ERR_UNC_POISON_TLP| \ + PCI_ERR_UNC_ECRC| \ + PCI_ERR_UNC_UNSUP| \ + PCI_ERR_UNC_COMP_ABORT| \ + PCI_ERR_UNC_UNX_COMP| \ + PCI_ERR_UNC_MALF_TLP) + +#define SYSTEM_ERROR_INTR_ON_MESG_MASK (PCI_EXP_RTCTL_SECEE| \ + PCI_EXP_RTCTL_SENFEE| \ + PCI_EXP_RTCTL_SEFEE) +#define ROOT_PORT_INTR_ON_MESG_MASK (PCI_ERR_ROOT_CMD_COR_EN| \ + PCI_ERR_ROOT_CMD_NONFATAL_EN| \ + PCI_ERR_ROOT_CMD_FATAL_EN) +#define ERR_COR_ID(d) (d & 0xffff) +#define ERR_UNCOR_ID(d) (d >> 16) + +static int pcie_aer_disable; + +void pci_no_aer(void) +{ + pcie_aer_disable = 1; +} + +bool pci_aer_available(void) +{ + return !pcie_aer_disable && pci_msi_enabled(); +} + +#ifdef CONFIG_PCIE_ECRC + +#define ECRC_POLICY_DEFAULT 0 /* ECRC set by BIOS */ +#define ECRC_POLICY_OFF 1 /* ECRC off for performance */ +#define ECRC_POLICY_ON 2 /* ECRC on for data integrity */ + +static int ecrc_policy = ECRC_POLICY_DEFAULT; + +static const char *ecrc_policy_str[] = { + [ECRC_POLICY_DEFAULT] = "bios", + [ECRC_POLICY_OFF] = "off", + [ECRC_POLICY_ON] = "on" +}; + +/** + * enable_ercr_checking - enable PCIe ECRC checking for a device + * @dev: the PCI device + * + * Returns 0 on success, or negative on failure. + */ +static int enable_ecrc_checking(struct pci_dev *dev) +{ + int pos; + u32 reg32; + + if (!pci_is_pcie(dev)) + return -ENODEV; + + pos = dev->aer_cap; + if (!pos) + return -ENODEV; + + pci_read_config_dword(dev, pos + PCI_ERR_CAP, ®32); + if (reg32 & PCI_ERR_CAP_ECRC_GENC) + reg32 |= PCI_ERR_CAP_ECRC_GENE; + if (reg32 & PCI_ERR_CAP_ECRC_CHKC) + reg32 |= PCI_ERR_CAP_ECRC_CHKE; + pci_write_config_dword(dev, pos + PCI_ERR_CAP, reg32); + + return 0; +} + +/** + * disable_ercr_checking - disables PCIe ECRC checking for a device + * @dev: the PCI device + * + * Returns 0 on success, or negative on failure. + */ +static int disable_ecrc_checking(struct pci_dev *dev) +{ + int pos; + u32 reg32; + + if (!pci_is_pcie(dev)) + return -ENODEV; + + pos = dev->aer_cap; + if (!pos) + return -ENODEV; + + pci_read_config_dword(dev, pos + PCI_ERR_CAP, ®32); + reg32 &= ~(PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE); + pci_write_config_dword(dev, pos + PCI_ERR_CAP, reg32); + + return 0; +} + +/** + * pcie_set_ecrc_checking - set/unset PCIe ECRC checking for a device based on global policy + * @dev: the PCI device + */ +void pcie_set_ecrc_checking(struct pci_dev *dev) +{ + switch (ecrc_policy) { + case ECRC_POLICY_DEFAULT: + return; + case ECRC_POLICY_OFF: + disable_ecrc_checking(dev); + break; + case ECRC_POLICY_ON: + enable_ecrc_checking(dev); + break; + default: + return; + } +} + +/** + * pcie_ecrc_get_policy - parse kernel command-line ecrc option + */ +void pcie_ecrc_get_policy(char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ecrc_policy_str); i++) + if (!strncmp(str, ecrc_policy_str[i], + strlen(ecrc_policy_str[i]))) + break; + if (i >= ARRAY_SIZE(ecrc_policy_str)) + return; + + ecrc_policy = i; +} +#endif /* CONFIG_PCIE_ECRC */ + +#ifdef CONFIG_ACPI_APEI +static inline int hest_match_pci(struct acpi_hest_aer_common *p, + struct pci_dev *pci) +{ + return ACPI_HEST_SEGMENT(p->bus) == pci_domain_nr(pci->bus) && + ACPI_HEST_BUS(p->bus) == pci->bus->number && + p->device == PCI_SLOT(pci->devfn) && + p->function == PCI_FUNC(pci->devfn); +} + +static inline bool hest_match_type(struct acpi_hest_header *hest_hdr, + struct pci_dev *dev) +{ + u16 hest_type = hest_hdr->type; + u8 pcie_type = pci_pcie_type(dev); + + if ((hest_type == ACPI_HEST_TYPE_AER_ROOT_PORT && + pcie_type == PCI_EXP_TYPE_ROOT_PORT) || + (hest_type == ACPI_HEST_TYPE_AER_ENDPOINT && + pcie_type == PCI_EXP_TYPE_ENDPOINT) || + (hest_type == ACPI_HEST_TYPE_AER_BRIDGE && + (dev->class >> 16) == PCI_BASE_CLASS_BRIDGE)) + return true; + return false; +} + +struct aer_hest_parse_info { + struct pci_dev *pci_dev; + int firmware_first; +}; + +static int hest_source_is_pcie_aer(struct acpi_hest_header *hest_hdr) +{ + if (hest_hdr->type == ACPI_HEST_TYPE_AER_ROOT_PORT || + hest_hdr->type == ACPI_HEST_TYPE_AER_ENDPOINT || + hest_hdr->type == ACPI_HEST_TYPE_AER_BRIDGE) + return 1; + return 0; +} + +static int aer_hest_parse(struct acpi_hest_header *hest_hdr, void *data) +{ + struct aer_hest_parse_info *info = data; + struct acpi_hest_aer_common *p; + int ff; + + if (!hest_source_is_pcie_aer(hest_hdr)) + return 0; + + p = (struct acpi_hest_aer_common *)(hest_hdr + 1); + ff = !!(p->flags & ACPI_HEST_FIRMWARE_FIRST); + + /* + * If no specific device is supplied, determine whether + * FIRMWARE_FIRST is set for *any* PCIe device. + */ + if (!info->pci_dev) { + info->firmware_first |= ff; + return 0; + } + + /* Otherwise, check the specific device */ + if (p->flags & ACPI_HEST_GLOBAL) { + if (hest_match_type(hest_hdr, info->pci_dev)) + info->firmware_first = ff; + } else + if (hest_match_pci(p, info->pci_dev)) + info->firmware_first = ff; + + return 0; +} + +static void aer_set_firmware_first(struct pci_dev *pci_dev) +{ + int rc; + struct aer_hest_parse_info info = { + .pci_dev = pci_dev, + .firmware_first = 0, + }; + + rc = apei_hest_parse(aer_hest_parse, &info); + + if (rc) + pci_dev->__aer_firmware_first = 0; + else + pci_dev->__aer_firmware_first = info.firmware_first; + pci_dev->__aer_firmware_first_valid = 1; +} + +int pcie_aer_get_firmware_first(struct pci_dev *dev) +{ + if (!pci_is_pcie(dev)) + return 0; + + if (!dev->__aer_firmware_first_valid) + aer_set_firmware_first(dev); + return dev->__aer_firmware_first; +} +#define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) + +static bool aer_firmware_first; + +/** + * aer_acpi_firmware_first - Check if APEI should control AER. + */ +bool aer_acpi_firmware_first(void) +{ + static bool parsed = false; + struct aer_hest_parse_info info = { + .pci_dev = NULL, /* Check all PCIe devices */ + .firmware_first = 0, + }; + + if (!parsed) { + apei_hest_parse(aer_hest_parse, &info); + aer_firmware_first = info.firmware_first; + parsed = true; + } + return aer_firmware_first; +} +#endif + +#define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) + +int pci_enable_pcie_error_reporting(struct pci_dev *dev) +{ + if (pcie_aer_get_firmware_first(dev)) + return -EIO; + + if (!dev->aer_cap) + return -EIO; + + return pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_AER_FLAGS); +} +EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting); + +int pci_disable_pcie_error_reporting(struct pci_dev *dev) +{ + if (pcie_aer_get_firmware_first(dev)) + return -EIO; + + return pcie_capability_clear_word(dev, PCI_EXP_DEVCTL, + PCI_EXP_AER_FLAGS); +} +EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); + +int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) +{ + int pos; + u32 status; + + pos = dev->aer_cap; + if (!pos) + return -EIO; + + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + if (status) + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); + + return 0; +} +EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status); + +int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) +{ + int pos; + u32 status; + int port_type; + + if (!pci_is_pcie(dev)) + return -ENODEV; + + pos = dev->aer_cap; + if (!pos) + return -EIO; + + port_type = pci_pcie_type(dev); + if (port_type == PCI_EXP_TYPE_ROOT_PORT) { + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &status); + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, status); + } + + pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, &status); + pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, status); + + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); + + return 0; +} + +int pci_aer_init(struct pci_dev *dev) +{ + dev->aer_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + return pci_cleanup_aer_error_status_regs(dev); +} + +#define AER_AGENT_RECEIVER 0 +#define AER_AGENT_REQUESTER 1 +#define AER_AGENT_COMPLETER 2 +#define AER_AGENT_TRANSMITTER 3 + +#define AER_AGENT_REQUESTER_MASK(t) ((t == AER_CORRECTABLE) ? \ + 0 : (PCI_ERR_UNC_COMP_TIME|PCI_ERR_UNC_UNSUP)) +#define AER_AGENT_COMPLETER_MASK(t) ((t == AER_CORRECTABLE) ? \ + 0 : PCI_ERR_UNC_COMP_ABORT) +#define AER_AGENT_TRANSMITTER_MASK(t) ((t == AER_CORRECTABLE) ? \ + (PCI_ERR_COR_REP_ROLL|PCI_ERR_COR_REP_TIMER) : 0) + +#define AER_GET_AGENT(t, e) \ + ((e & AER_AGENT_COMPLETER_MASK(t)) ? AER_AGENT_COMPLETER : \ + (e & AER_AGENT_REQUESTER_MASK(t)) ? AER_AGENT_REQUESTER : \ + (e & AER_AGENT_TRANSMITTER_MASK(t)) ? AER_AGENT_TRANSMITTER : \ + AER_AGENT_RECEIVER) + +#define AER_PHYSICAL_LAYER_ERROR 0 +#define AER_DATA_LINK_LAYER_ERROR 1 +#define AER_TRANSACTION_LAYER_ERROR 2 + +#define AER_PHYSICAL_LAYER_ERROR_MASK(t) ((t == AER_CORRECTABLE) ? \ + PCI_ERR_COR_RCVR : 0) +#define AER_DATA_LINK_LAYER_ERROR_MASK(t) ((t == AER_CORRECTABLE) ? \ + (PCI_ERR_COR_BAD_TLP| \ + PCI_ERR_COR_BAD_DLLP| \ + PCI_ERR_COR_REP_ROLL| \ + PCI_ERR_COR_REP_TIMER) : PCI_ERR_UNC_DLP) + +#define AER_GET_LAYER_ERROR(t, e) \ + ((e & AER_PHYSICAL_LAYER_ERROR_MASK(t)) ? AER_PHYSICAL_LAYER_ERROR : \ + (e & AER_DATA_LINK_LAYER_ERROR_MASK(t)) ? AER_DATA_LINK_LAYER_ERROR : \ + AER_TRANSACTION_LAYER_ERROR) + +/* + * AER error strings + */ +static const char *aer_error_severity_string[] = { + "Uncorrected (Non-Fatal)", + "Uncorrected (Fatal)", + "Corrected" +}; + +static const char *aer_error_layer[] = { + "Physical Layer", + "Data Link Layer", + "Transaction Layer" +}; + +static const char *aer_correctable_error_string[] = { + "Receiver Error", /* Bit Position 0 */ + NULL, + NULL, + NULL, + NULL, + NULL, + "Bad TLP", /* Bit Position 6 */ + "Bad DLLP", /* Bit Position 7 */ + "RELAY_NUM Rollover", /* Bit Position 8 */ + NULL, + NULL, + NULL, + "Replay Timer Timeout", /* Bit Position 12 */ + "Advisory Non-Fatal", /* Bit Position 13 */ + "Corrected Internal Error", /* Bit Position 14 */ + "Header Log Overflow", /* Bit Position 15 */ +}; + +static const char *aer_uncorrectable_error_string[] = { + "Undefined", /* Bit Position 0 */ + NULL, + NULL, + NULL, + "Data Link Protocol", /* Bit Position 4 */ + "Surprise Down Error", /* Bit Position 5 */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "Poisoned TLP", /* Bit Position 12 */ + "Flow Control Protocol", /* Bit Position 13 */ + "Completion Timeout", /* Bit Position 14 */ + "Completer Abort", /* Bit Position 15 */ + "Unexpected Completion", /* Bit Position 16 */ + "Receiver Overflow", /* Bit Position 17 */ + "Malformed TLP", /* Bit Position 18 */ + "ECRC", /* Bit Position 19 */ + "Unsupported Request", /* Bit Position 20 */ + "ACS Violation", /* Bit Position 21 */ + "Uncorrectable Internal Error", /* Bit Position 22 */ + "MC Blocked TLP", /* Bit Position 23 */ + "AtomicOp Egress Blocked", /* Bit Position 24 */ + "TLP Prefix Blocked Error", /* Bit Position 25 */ +}; + +static const char *aer_agent_string[] = { + "Receiver ID", + "Requester ID", + "Completer ID", + "Transmitter ID" +}; + +static void __print_tlp_header(struct pci_dev *dev, + struct aer_header_log_regs *t) +{ + pci_err(dev, " TLP Header: %08x %08x %08x %08x\n", + t->dw0, t->dw1, t->dw2, t->dw3); +} + +static void __aer_print_error(struct pci_dev *dev, + struct aer_err_info *info) +{ + int i, status; + const char *errmsg = NULL; + status = (info->status & ~info->mask); + + for (i = 0; i < 32; i++) { + if (!(status & (1 << i))) + continue; + + if (info->severity == AER_CORRECTABLE) + errmsg = i < ARRAY_SIZE(aer_correctable_error_string) ? + aer_correctable_error_string[i] : NULL; + else + errmsg = i < ARRAY_SIZE(aer_uncorrectable_error_string) ? + aer_uncorrectable_error_string[i] : NULL; + + if (errmsg) + pci_err(dev, " [%2d] %-22s%s\n", i, errmsg, + info->first_error == i ? " (First)" : ""); + else + pci_err(dev, " [%2d] Unknown Error Bit%s\n", + i, info->first_error == i ? " (First)" : ""); + } +} + +static void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) +{ + int layer, agent; + int id = ((dev->bus->number << 8) | dev->devfn); + + if (!info->status) { + pci_err(dev, "PCIe Bus Error: severity=%s, type=Inaccessible, (Unregistered Agent ID)\n", + aer_error_severity_string[info->severity]); + goto out; + } + + layer = AER_GET_LAYER_ERROR(info->severity, info->status); + agent = AER_GET_AGENT(info->severity, info->status); + + pci_err(dev, "PCIe Bus Error: severity=%s, type=%s, (%s)\n", + aer_error_severity_string[info->severity], + aer_error_layer[layer], aer_agent_string[agent]); + + pci_err(dev, " device [%04x:%04x] error status/mask=%08x/%08x\n", + dev->vendor, dev->device, + info->status, info->mask); + + __aer_print_error(dev, info); + + if (info->tlp_header_valid) + __print_tlp_header(dev, &info->tlp); + +out: + if (info->id && info->error_dev_num > 1 && info->id == id) + pci_err(dev, " Error of this Agent is reported first\n"); + + trace_aer_event(dev_name(&dev->dev), (info->status & ~info->mask), + info->severity, info->tlp_header_valid, &info->tlp); +} + +static void aer_print_port_info(struct pci_dev *dev, struct aer_err_info *info) +{ + u8 bus = info->id >> 8; + u8 devfn = info->id & 0xff; + + pci_info(dev, "AER: %s%s error received: %04x:%02x:%02x.%d\n", + info->multi_error_valid ? "Multiple " : "", + aer_error_severity_string[info->severity], + pci_domain_nr(dev->bus), bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); +} + +#ifdef CONFIG_ACPI_APEI_PCIEAER +int cper_severity_to_aer(int cper_severity) +{ + switch (cper_severity) { + case CPER_SEV_RECOVERABLE: + return AER_NONFATAL; + case CPER_SEV_FATAL: + return AER_FATAL; + default: + return AER_CORRECTABLE; + } +} +EXPORT_SYMBOL_GPL(cper_severity_to_aer); + +void cper_print_aer(struct pci_dev *dev, int aer_severity, + struct aer_capability_regs *aer) +{ + int layer, agent, tlp_header_valid = 0; + u32 status, mask; + struct aer_err_info info; + + if (aer_severity == AER_CORRECTABLE) { + status = aer->cor_status; + mask = aer->cor_mask; + } else { + status = aer->uncor_status; + mask = aer->uncor_mask; + tlp_header_valid = status & AER_LOG_TLP_MASKS; + } + + layer = AER_GET_LAYER_ERROR(aer_severity, status); + agent = AER_GET_AGENT(aer_severity, status); + + memset(&info, 0, sizeof(info)); + info.severity = aer_severity; + info.status = status; + info.mask = mask; + info.first_error = PCI_ERR_CAP_FEP(aer->cap_control); + + pci_err(dev, "aer_status: 0x%08x, aer_mask: 0x%08x\n", status, mask); + __aer_print_error(dev, &info); + pci_err(dev, "aer_layer=%s, aer_agent=%s\n", + aer_error_layer[layer], aer_agent_string[agent]); + + if (aer_severity != AER_CORRECTABLE) + pci_err(dev, "aer_uncor_severity: 0x%08x\n", + aer->uncor_severity); + + if (tlp_header_valid) + __print_tlp_header(dev, &aer->header_log); + + trace_aer_event(dev_name(&dev->dev), (status & ~mask), + aer_severity, tlp_header_valid, &aer->header_log); +} +#endif + +/** + * add_error_device - list device to be handled + * @e_info: pointer to error info + * @dev: pointer to pci_dev to be added + */ +static int add_error_device(struct aer_err_info *e_info, struct pci_dev *dev) +{ + if (e_info->error_dev_num < AER_MAX_MULTI_ERR_DEVICES) { + e_info->dev[e_info->error_dev_num] = dev; + e_info->error_dev_num++; + return 0; + } + return -ENOSPC; +} + +/** + * is_error_source - check whether the device is source of reported error + * @dev: pointer to pci_dev to be checked + * @e_info: pointer to reported error info + */ +static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) +{ + int pos; + u32 status, mask; + u16 reg16; + + /* + * When bus id is equal to 0, it might be a bad id + * reported by root port. + */ + if ((PCI_BUS_NUM(e_info->id) != 0) && + !(dev->bus->bus_flags & PCI_BUS_FLAGS_NO_AERSID)) { + /* Device ID match? */ + if (e_info->id == ((dev->bus->number << 8) | dev->devfn)) + return true; + + /* Continue id comparing if there is no multiple error */ + if (!e_info->multi_error_valid) + return false; + } + + /* + * When either + * 1) bus id is equal to 0. Some ports might lose the bus + * id of error source id; + * 2) bus flag PCI_BUS_FLAGS_NO_AERSID is set + * 3) There are multiple errors and prior ID comparing fails; + * We check AER status registers to find possible reporter. + */ + if (atomic_read(&dev->enable_cnt) == 0) + return false; + + /* Check if AER is enabled */ + pcie_capability_read_word(dev, PCI_EXP_DEVCTL, ®16); + if (!(reg16 & PCI_EXP_AER_FLAGS)) + return false; + + pos = dev->aer_cap; + if (!pos) + return false; + + /* Check if error is recorded */ + if (e_info->severity == AER_CORRECTABLE) { + pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, &mask); + } else { + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, &mask); + } + if (status & ~mask) + return true; + + return false; +} + +static int find_device_iter(struct pci_dev *dev, void *data) +{ + struct aer_err_info *e_info = (struct aer_err_info *)data; + + if (is_error_source(dev, e_info)) { + /* List this device */ + if (add_error_device(e_info, dev)) { + /* We cannot handle more... Stop iteration */ + /* TODO: Should print error message here? */ + return 1; + } + + /* If there is only a single error, stop iteration */ + if (!e_info->multi_error_valid) + return 1; + } + return 0; +} + +/** + * find_source_device - search through device hierarchy for source device + * @parent: pointer to Root Port pci_dev data structure + * @e_info: including detailed error information such like id + * + * Return true if found. + * + * Invoked by DPC when error is detected at the Root Port. + * Caller of this function must set id, severity, and multi_error_valid of + * struct aer_err_info pointed by @e_info properly. This function must fill + * e_info->error_dev_num and e_info->dev[], based on the given information. + */ +static bool find_source_device(struct pci_dev *parent, + struct aer_err_info *e_info) +{ + struct pci_dev *dev = parent; + int result; + + /* Must reset in this function */ + e_info->error_dev_num = 0; + + /* Is Root Port an agent that sends error message? */ + result = find_device_iter(dev, e_info); + if (result) + return true; + + pci_walk_bus(parent->subordinate, find_device_iter, e_info); + + if (!e_info->error_dev_num) { + pci_printk(KERN_DEBUG, parent, "can't find device of ID%04x\n", + e_info->id); + return false; + } + return true; +} + +/** + * handle_error_source - handle logging error into an event log + * @dev: pointer to pci_dev data structure of error source device + * @info: comprehensive error information + * + * Invoked when an error being detected by Root Port. + */ +static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info) +{ + int pos; + + if (info->severity == AER_CORRECTABLE) { + /* + * Correctable error does not need software intervention. + * No need to go through error recovery process. + */ + pos = dev->aer_cap; + if (pos) + pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, + info->status); + } else if (info->severity == AER_NONFATAL) + pcie_do_nonfatal_recovery(dev); + else if (info->severity == AER_FATAL) + pcie_do_fatal_recovery(dev, PCIE_PORT_SERVICE_AER); +} + +#ifdef CONFIG_ACPI_APEI_PCIEAER + +#define AER_RECOVER_RING_ORDER 4 +#define AER_RECOVER_RING_SIZE (1 << AER_RECOVER_RING_ORDER) + +struct aer_recover_entry { + u8 bus; + u8 devfn; + u16 domain; + int severity; + struct aer_capability_regs *regs; +}; + +static DEFINE_KFIFO(aer_recover_ring, struct aer_recover_entry, + AER_RECOVER_RING_SIZE); + +static void aer_recover_work_func(struct work_struct *work) +{ + struct aer_recover_entry entry; + struct pci_dev *pdev; + + while (kfifo_get(&aer_recover_ring, &entry)) { + pdev = pci_get_domain_bus_and_slot(entry.domain, entry.bus, + entry.devfn); + if (!pdev) { + pr_err("AER recover: Can not find pci_dev for %04x:%02x:%02x:%x\n", + entry.domain, entry.bus, + PCI_SLOT(entry.devfn), PCI_FUNC(entry.devfn)); + continue; + } + cper_print_aer(pdev, entry.severity, entry.regs); + if (entry.severity == AER_NONFATAL) + pcie_do_nonfatal_recovery(pdev); + else if (entry.severity == AER_FATAL) + pcie_do_fatal_recovery(pdev, PCIE_PORT_SERVICE_AER); + pci_dev_put(pdev); + } +} + +/* + * Mutual exclusion for writers of aer_recover_ring, reader side don't + * need lock, because there is only one reader and lock is not needed + * between reader and writer. + */ +static DEFINE_SPINLOCK(aer_recover_ring_lock); +static DECLARE_WORK(aer_recover_work, aer_recover_work_func); + +void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, + int severity, struct aer_capability_regs *aer_regs) +{ + unsigned long flags; + struct aer_recover_entry entry = { + .bus = bus, + .devfn = devfn, + .domain = domain, + .severity = severity, + .regs = aer_regs, + }; + + spin_lock_irqsave(&aer_recover_ring_lock, flags); + if (kfifo_put(&aer_recover_ring, entry)) + schedule_work(&aer_recover_work); + else + pr_err("AER recover: Buffer overflow when recovering AER for %04x:%02x:%02x:%x\n", + domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); + spin_unlock_irqrestore(&aer_recover_ring_lock, flags); +} +EXPORT_SYMBOL_GPL(aer_recover_queue); +#endif + +/** + * get_device_error_info - read error status from dev and store it to info + * @dev: pointer to the device expected to have a error record + * @info: pointer to structure to store the error record + * + * Return 1 on success, 0 on error. + * + * Note that @info is reused among all error devices. Clear fields properly. + */ +static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) +{ + int pos, temp; + + /* Must reset in this function */ + info->status = 0; + info->tlp_header_valid = 0; + + pos = dev->aer_cap; + + /* The device might not support AER */ + if (!pos) + return 0; + + if (info->severity == AER_CORRECTABLE) { + pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, + &info->status); + pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, + &info->mask); + if (!(info->status & ~info->mask)) + return 0; + } else if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + info->severity == AER_NONFATAL) { + + /* Link is still healthy for IO reads */ + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, + &info->status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, + &info->mask); + if (!(info->status & ~info->mask)) + return 0; + + /* Get First Error Pointer */ + pci_read_config_dword(dev, pos + PCI_ERR_CAP, &temp); + info->first_error = PCI_ERR_CAP_FEP(temp); + + if (info->status & AER_LOG_TLP_MASKS) { + info->tlp_header_valid = 1; + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG, &info->tlp.dw0); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2); + pci_read_config_dword(dev, + pos + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3); + } + } + + return 1; +} + +static inline void aer_process_err_devices(struct aer_err_info *e_info) +{ + int i; + + /* Report all before handle them, not to lost records by reset etc. */ + for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { + if (get_device_error_info(e_info->dev[i], e_info)) + aer_print_error(e_info->dev[i], e_info); + } + for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { + if (get_device_error_info(e_info->dev[i], e_info)) + handle_error_source(e_info->dev[i], e_info); + } +} + +/** + * aer_isr_one_error - consume an error detected by root port + * @rpc: pointer to the root port which holds an error + * @e_src: pointer to an error source + */ +static void aer_isr_one_error(struct aer_rpc *rpc, + struct aer_err_source *e_src) +{ + struct pci_dev *pdev = rpc->rpd; + struct aer_err_info *e_info = &rpc->e_info; + + /* + * There is a possibility that both correctable error and + * uncorrectable error being logged. Report correctable error first. + */ + if (e_src->status & PCI_ERR_ROOT_COR_RCV) { + e_info->id = ERR_COR_ID(e_src->id); + e_info->severity = AER_CORRECTABLE; + + if (e_src->status & PCI_ERR_ROOT_MULTI_COR_RCV) + e_info->multi_error_valid = 1; + else + e_info->multi_error_valid = 0; + aer_print_port_info(pdev, e_info); + + if (find_source_device(pdev, e_info)) + aer_process_err_devices(e_info); + } + + if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) { + e_info->id = ERR_UNCOR_ID(e_src->id); + + if (e_src->status & PCI_ERR_ROOT_FATAL_RCV) + e_info->severity = AER_FATAL; + else + e_info->severity = AER_NONFATAL; + + if (e_src->status & PCI_ERR_ROOT_MULTI_UNCOR_RCV) + e_info->multi_error_valid = 1; + else + e_info->multi_error_valid = 0; + + aer_print_port_info(pdev, e_info); + + if (find_source_device(pdev, e_info)) + aer_process_err_devices(e_info); + } +} + +/** + * get_e_source - retrieve an error source + * @rpc: pointer to the root port which holds an error + * @e_src: pointer to store retrieved error source + * + * Return 1 if an error source is retrieved, otherwise 0. + * + * Invoked by DPC handler to consume an error. + */ +static int get_e_source(struct aer_rpc *rpc, struct aer_err_source *e_src) +{ + unsigned long flags; + + /* Lock access to Root error producer/consumer index */ + spin_lock_irqsave(&rpc->e_lock, flags); + if (rpc->prod_idx == rpc->cons_idx) { + spin_unlock_irqrestore(&rpc->e_lock, flags); + return 0; + } + + *e_src = rpc->e_sources[rpc->cons_idx]; + rpc->cons_idx++; + if (rpc->cons_idx == AER_ERROR_SOURCES_MAX) + rpc->cons_idx = 0; + spin_unlock_irqrestore(&rpc->e_lock, flags); + + return 1; +} + +/** + * aer_isr - consume errors detected by root port + * @work: definition of this work item + * + * Invoked, as DPC, when root port records new detected error + */ +static void aer_isr(struct work_struct *work) +{ + struct aer_rpc *rpc = container_of(work, struct aer_rpc, dpc_handler); + struct aer_err_source uninitialized_var(e_src); + + mutex_lock(&rpc->rpc_mutex); + while (get_e_source(rpc, &e_src)) + aer_isr_one_error(rpc, &e_src); + mutex_unlock(&rpc->rpc_mutex); +} + +/** + * aer_irq - Root Port's ISR + * @irq: IRQ assigned to Root Port + * @context: pointer to Root Port data structure + * + * Invoked when Root Port detects AER messages. + */ +irqreturn_t aer_irq(int irq, void *context) +{ + unsigned int status, id; + struct pcie_device *pdev = (struct pcie_device *)context; + struct aer_rpc *rpc = get_service_data(pdev); + int next_prod_idx; + unsigned long flags; + int pos; + + pos = pdev->port->aer_cap; + /* + * Must lock access to Root Error Status Reg, Root Error ID Reg, + * and Root error producer/consumer index + */ + spin_lock_irqsave(&rpc->e_lock, flags); + + /* Read error status */ + pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, &status); + if (!(status & (PCI_ERR_ROOT_UNCOR_RCV|PCI_ERR_ROOT_COR_RCV))) { + spin_unlock_irqrestore(&rpc->e_lock, flags); + return IRQ_NONE; + } + + /* Read error source and clear error status */ + pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_ERR_SRC, &id); + pci_write_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, status); + + /* Store error source for later DPC handler */ + next_prod_idx = rpc->prod_idx + 1; + if (next_prod_idx == AER_ERROR_SOURCES_MAX) + next_prod_idx = 0; + if (next_prod_idx == rpc->cons_idx) { + /* + * Error Storm Condition - possibly the same error occurred. + * Drop the error. + */ + spin_unlock_irqrestore(&rpc->e_lock, flags); + return IRQ_HANDLED; + } + rpc->e_sources[rpc->prod_idx].status = status; + rpc->e_sources[rpc->prod_idx].id = id; + rpc->prod_idx = next_prod_idx; + spin_unlock_irqrestore(&rpc->e_lock, flags); + + /* Invoke DPC handler */ + schedule_work(&rpc->dpc_handler); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(aer_irq); + +static int set_device_error_reporting(struct pci_dev *dev, void *data) +{ + bool enable = *((bool *)data); + int type = pci_pcie_type(dev); + + if ((type == PCI_EXP_TYPE_ROOT_PORT) || + (type == PCI_EXP_TYPE_UPSTREAM) || + (type == PCI_EXP_TYPE_DOWNSTREAM)) { + if (enable) + pci_enable_pcie_error_reporting(dev); + else + pci_disable_pcie_error_reporting(dev); + } + + if (enable) + pcie_set_ecrc_checking(dev); + + return 0; +} + +/** + * set_downstream_devices_error_reporting - enable/disable the error reporting bits on the root port and its downstream ports. + * @dev: pointer to root port's pci_dev data structure + * @enable: true = enable error reporting, false = disable error reporting. + */ +static void set_downstream_devices_error_reporting(struct pci_dev *dev, + bool enable) +{ + set_device_error_reporting(dev, &enable); + + if (!dev->subordinate) + return; + pci_walk_bus(dev->subordinate, set_device_error_reporting, &enable); +} + +/** + * aer_enable_rootport - enable Root Port's interrupts when receiving messages + * @rpc: pointer to a Root Port data structure + * + * Invoked when PCIe bus loads AER service driver. + */ +static void aer_enable_rootport(struct aer_rpc *rpc) +{ + struct pci_dev *pdev = rpc->rpd; + int aer_pos; + u16 reg16; + u32 reg32; + + /* Clear PCIe Capability's Device Status */ + pcie_capability_read_word(pdev, PCI_EXP_DEVSTA, ®16); + pcie_capability_write_word(pdev, PCI_EXP_DEVSTA, reg16); + + /* Disable system error generation in response to error messages */ + pcie_capability_clear_word(pdev, PCI_EXP_RTCTL, + SYSTEM_ERROR_INTR_ON_MESG_MASK); + + aer_pos = pdev->aer_cap; + /* Clear error status */ + pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, reg32); + pci_read_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, reg32); + pci_read_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, ®32); + pci_write_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, reg32); + + /* + * Enable error reporting for the root port device and downstream port + * devices. + */ + set_downstream_devices_error_reporting(pdev, true); + + /* Enable Root Port's interrupt in response to error messages */ + pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_COMMAND, ®32); + reg32 |= ROOT_PORT_INTR_ON_MESG_MASK; + pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_COMMAND, reg32); +} + +/** + * aer_disable_rootport - disable Root Port's interrupts when receiving messages + * @rpc: pointer to a Root Port data structure + * + * Invoked when PCIe bus unloads AER service driver. + */ +static void aer_disable_rootport(struct aer_rpc *rpc) +{ + struct pci_dev *pdev = rpc->rpd; + u32 reg32; + int pos; + + /* + * Disable error reporting for the root port device and downstream port + * devices. + */ + set_downstream_devices_error_reporting(pdev, false); + + pos = pdev->aer_cap; + /* Disable Root's interrupt in response to error messages */ + pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, ®32); + reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; + pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, reg32); + + /* Clear Root's error status reg */ + pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, ®32); + pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, reg32); +} + +/** + * aer_alloc_rpc - allocate Root Port data structure + * @dev: pointer to the pcie_dev data structure + * + * Invoked when Root Port's AER service is loaded. + */ +static struct aer_rpc *aer_alloc_rpc(struct pcie_device *dev) +{ + struct aer_rpc *rpc; + + rpc = kzalloc(sizeof(struct aer_rpc), GFP_KERNEL); + if (!rpc) + return NULL; + + /* Initialize Root lock access, e_lock, to Root Error Status Reg */ + spin_lock_init(&rpc->e_lock); + + rpc->rpd = dev->port; + INIT_WORK(&rpc->dpc_handler, aer_isr); + mutex_init(&rpc->rpc_mutex); + + /* Use PCIe bus function to store rpc into PCIe device */ + set_service_data(dev, rpc); + + return rpc; +} + +/** + * aer_remove - clean up resources + * @dev: pointer to the pcie_dev data structure + * + * Invoked when PCI Express bus unloads or AER probe fails. + */ +static void aer_remove(struct pcie_device *dev) +{ + struct aer_rpc *rpc = get_service_data(dev); + + if (rpc) { + /* If register interrupt service, it must be free. */ + if (rpc->isr) + free_irq(dev->irq, dev); + + flush_work(&rpc->dpc_handler); + aer_disable_rootport(rpc); + kfree(rpc); + set_service_data(dev, NULL); + } +} + +/** + * aer_probe - initialize resources + * @dev: pointer to the pcie_dev data structure + * + * Invoked when PCI Express bus loads AER service driver. + */ +static int aer_probe(struct pcie_device *dev) +{ + int status; + struct aer_rpc *rpc; + struct device *device = &dev->port->dev; + + /* Alloc rpc data structure */ + rpc = aer_alloc_rpc(dev); + if (!rpc) { + dev_printk(KERN_DEBUG, device, "alloc AER rpc failed\n"); + aer_remove(dev); + return -ENOMEM; + } + + /* Request IRQ ISR */ + status = request_irq(dev->irq, aer_irq, IRQF_SHARED, "aerdrv", dev); + if (status) { + dev_printk(KERN_DEBUG, device, "request AER IRQ %d failed\n", + dev->irq); + aer_remove(dev); + return status; + } + + rpc->isr = 1; + + aer_enable_rootport(rpc); + dev_info(device, "AER enabled with IRQ %d\n", dev->irq); + return 0; +} + +/** + * aer_root_reset - reset link on Root Port + * @dev: pointer to Root Port's pci_dev data structure + * + * Invoked by Port Bus driver when performing link reset at Root Port. + */ +static pci_ers_result_t aer_root_reset(struct pci_dev *dev) +{ + u32 reg32; + int pos; + + pos = dev->aer_cap; + + /* Disable Root's interrupt in response to error messages */ + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, ®32); + reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32); + + pci_reset_bridge_secondary_bus(dev); + pci_printk(KERN_DEBUG, dev, "Root Port link has been reset\n"); + + /* Clear Root Error Status */ + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, ®32); + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, reg32); + + /* Enable Root Port's interrupt in response to error messages */ + pci_read_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, ®32); + reg32 |= ROOT_PORT_INTR_ON_MESG_MASK; + pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32); + + return PCI_ERS_RESULT_RECOVERED; +} + +/** + * aer_error_resume - clean up corresponding error status bits + * @dev: pointer to Root Port's pci_dev data structure + * + * Invoked by Port Bus driver during nonfatal recovery. + */ +static void aer_error_resume(struct pci_dev *dev) +{ + int pos; + u32 status, mask; + u16 reg16; + + /* Clean up Root device status */ + pcie_capability_read_word(dev, PCI_EXP_DEVSTA, ®16); + pcie_capability_write_word(dev, PCI_EXP_DEVSTA, reg16); + + /* Clean AER Root Error Status */ + pos = dev->aer_cap; + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); + pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); + status &= ~mask; /* Clear corresponding nonfatal bits */ + pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); +} + +static struct pcie_port_service_driver aerdriver = { + .name = "aer", + .port_type = PCI_EXP_TYPE_ROOT_PORT, + .service = PCIE_PORT_SERVICE_AER, + + .probe = aer_probe, + .remove = aer_remove, + .error_resume = aer_error_resume, + .reset_link = aer_root_reset, +}; + +/** + * aer_service_init - register AER root service driver + * + * Invoked when AER root service driver is loaded. + */ +static int __init aer_service_init(void) +{ + if (!pci_aer_available() || aer_acpi_firmware_first()) + return -ENXIO; + return pcie_port_service_register(&aerdriver); +} +device_initcall(aer_service_init); diff --git a/drivers/pci/pcie/aer/Kconfig b/drivers/pci/pcie/aer/Kconfig deleted file mode 100644 index 5a64eb3..0000000 --- a/drivers/pci/pcie/aer/Kconfig +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# PCI Express Root Port Device AER Configuration -# - -config PCIEAER - bool "Root Port Advanced Error Reporting support" - depends on PCIEPORTBUS - select RAS - default y - help - This enables PCI Express Root Port Advanced Error Reporting - (AER) driver support. Error reporting messages sent to Root - Port will be handled by PCI Express AER driver. - - -# -# PCI Express ECRC -# -config PCIE_ECRC - bool "PCI Express ECRC settings control" - depends on PCIEAER - help - Used to override firmware/bios settings for PCI Express ECRC - (transaction layer end-to-end CRC checking). - - When in doubt, say N. - -source "drivers/pci/pcie/aer/Kconfig.debug" diff --git a/drivers/pci/pcie/aer/Kconfig.debug b/drivers/pci/pcie/aer/Kconfig.debug deleted file mode 100644 index 67e0217..0000000 --- a/drivers/pci/pcie/aer/Kconfig.debug +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# PCI Express Root Port Device AER Debug Configuration -# - -config PCIEAER_INJECT - tristate "PCIe AER error injector support" - depends on PCIEAER - default n - help - This enables PCI Express Root Port Advanced Error Reporting - (AER) software error injector. - - Debugging PCIe AER code is quite difficult because it is hard - to trigger various real hardware errors. Software based - error injection can fake almost all kinds of errors with the - help of a user space helper tool aer-inject, which can be - gotten from: - http://www.kernel.org/pub/linux/utils/pci/aer-inject/ diff --git a/drivers/pci/pcie/aer/Makefile b/drivers/pci/pcie/aer/Makefile deleted file mode 100644 index 09bd890..0000000 --- a/drivers/pci/pcie/aer/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Makefile for PCI-Express Root Port Advanced Error Reporting Driver -# - -obj-$(CONFIG_PCIEAER) += aerdriver.o - -obj-$(CONFIG_PCIE_ECRC) += ecrc.o - -aerdriver-objs := aerdrv_errprint.o aerdrv_core.o aerdrv.o -aerdriver-$(CONFIG_ACPI) += aerdrv_acpi.o - -obj-$(CONFIG_PCIEAER_INJECT) += aer_inject.o diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c deleted file mode 100644 index 9735c19..0000000 --- a/drivers/pci/pcie/aer/aerdrv.c +++ /dev/null @@ -1,371 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Implement the AER root port service driver. The driver registers an IRQ - * handler. When a root port triggers an AER interrupt, the IRQ handler - * collects root port status and schedules work. - * - * Copyright (C) 2006 Intel Corp. - * Tom Long Nguyen (tom.l.nguyen@intel.com) - * Zhang Yanmin (yanmin.zhang@intel.com) - */ - -#include <linux/pci.h> -#include <linux/pci-acpi.h> -#include <linux/sched.h> -#include <linux/kernel.h> -#include <linux/errno.h> -#include <linux/pm.h> -#include <linux/init.h> -#include <linux/interrupt.h> -#include <linux/delay.h> -#include <linux/slab.h> - -#include "aerdrv.h" -#include "../../pci.h" - -static int aer_probe(struct pcie_device *dev); -static void aer_remove(struct pcie_device *dev); -static void aer_error_resume(struct pci_dev *dev); -static pci_ers_result_t aer_root_reset(struct pci_dev *dev); - -static struct pcie_port_service_driver aerdriver = { - .name = "aer", - .port_type = PCI_EXP_TYPE_ROOT_PORT, - .service = PCIE_PORT_SERVICE_AER, - - .probe = aer_probe, - .remove = aer_remove, - .error_resume = aer_error_resume, - .reset_link = aer_root_reset, -}; - -static int pcie_aer_disable; - -void pci_no_aer(void) -{ - pcie_aer_disable = 1; -} - -bool pci_aer_available(void) -{ - return !pcie_aer_disable && pci_msi_enabled(); -} - -static int set_device_error_reporting(struct pci_dev *dev, void *data) -{ - bool enable = *((bool *)data); - int type = pci_pcie_type(dev); - - if ((type == PCI_EXP_TYPE_ROOT_PORT) || - (type == PCI_EXP_TYPE_UPSTREAM) || - (type == PCI_EXP_TYPE_DOWNSTREAM)) { - if (enable) - pci_enable_pcie_error_reporting(dev); - else - pci_disable_pcie_error_reporting(dev); - } - - if (enable) - pcie_set_ecrc_checking(dev); - - return 0; -} - -/** - * set_downstream_devices_error_reporting - enable/disable the error reporting bits on the root port and its downstream ports. - * @dev: pointer to root port's pci_dev data structure - * @enable: true = enable error reporting, false = disable error reporting. - */ -static void set_downstream_devices_error_reporting(struct pci_dev *dev, - bool enable) -{ - set_device_error_reporting(dev, &enable); - - if (!dev->subordinate) - return; - pci_walk_bus(dev->subordinate, set_device_error_reporting, &enable); -} - -/** - * aer_enable_rootport - enable Root Port's interrupts when receiving messages - * @rpc: pointer to a Root Port data structure - * - * Invoked when PCIe bus loads AER service driver. - */ -static void aer_enable_rootport(struct aer_rpc *rpc) -{ - struct pci_dev *pdev = rpc->rpd; - int aer_pos; - u16 reg16; - u32 reg32; - - /* Clear PCIe Capability's Device Status */ - pcie_capability_read_word(pdev, PCI_EXP_DEVSTA, ®16); - pcie_capability_write_word(pdev, PCI_EXP_DEVSTA, reg16); - - /* Disable system error generation in response to error messages */ - pcie_capability_clear_word(pdev, PCI_EXP_RTCTL, - SYSTEM_ERROR_INTR_ON_MESG_MASK); - - aer_pos = pdev->aer_cap; - /* Clear error status */ - pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, ®32); - pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, reg32); - pci_read_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, ®32); - pci_write_config_dword(pdev, aer_pos + PCI_ERR_COR_STATUS, reg32); - pci_read_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, ®32); - pci_write_config_dword(pdev, aer_pos + PCI_ERR_UNCOR_STATUS, reg32); - - /* - * Enable error reporting for the root port device and downstream port - * devices. - */ - set_downstream_devices_error_reporting(pdev, true); - - /* Enable Root Port's interrupt in response to error messages */ - pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_COMMAND, ®32); - reg32 |= ROOT_PORT_INTR_ON_MESG_MASK; - pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_COMMAND, reg32); -} - -/** - * aer_disable_rootport - disable Root Port's interrupts when receiving messages - * @rpc: pointer to a Root Port data structure - * - * Invoked when PCIe bus unloads AER service driver. - */ -static void aer_disable_rootport(struct aer_rpc *rpc) -{ - struct pci_dev *pdev = rpc->rpd; - u32 reg32; - int pos; - - /* - * Disable error reporting for the root port device and downstream port - * devices. - */ - set_downstream_devices_error_reporting(pdev, false); - - pos = pdev->aer_cap; - /* Disable Root's interrupt in response to error messages */ - pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, ®32); - reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; - pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, reg32); - - /* Clear Root's error status reg */ - pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, ®32); - pci_write_config_dword(pdev, pos + PCI_ERR_ROOT_STATUS, reg32); -} - -/** - * aer_irq - Root Port's ISR - * @irq: IRQ assigned to Root Port - * @context: pointer to Root Port data structure - * - * Invoked when Root Port detects AER messages. - */ -irqreturn_t aer_irq(int irq, void *context) -{ - unsigned int status, id; - struct pcie_device *pdev = (struct pcie_device *)context; - struct aer_rpc *rpc = get_service_data(pdev); - int next_prod_idx; - unsigned long flags; - int pos; - - pos = pdev->port->aer_cap; - /* - * Must lock access to Root Error Status Reg, Root Error ID Reg, - * and Root error producer/consumer index - */ - spin_lock_irqsave(&rpc->e_lock, flags); - - /* Read error status */ - pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, &status); - if (!(status & (PCI_ERR_ROOT_UNCOR_RCV|PCI_ERR_ROOT_COR_RCV))) { - spin_unlock_irqrestore(&rpc->e_lock, flags); - return IRQ_NONE; - } - - /* Read error source and clear error status */ - pci_read_config_dword(pdev->port, pos + PCI_ERR_ROOT_ERR_SRC, &id); - pci_write_config_dword(pdev->port, pos + PCI_ERR_ROOT_STATUS, status); - - /* Store error source for later DPC handler */ - next_prod_idx = rpc->prod_idx + 1; - if (next_prod_idx == AER_ERROR_SOURCES_MAX) - next_prod_idx = 0; - if (next_prod_idx == rpc->cons_idx) { - /* - * Error Storm Condition - possibly the same error occurred. - * Drop the error. - */ - spin_unlock_irqrestore(&rpc->e_lock, flags); - return IRQ_HANDLED; - } - rpc->e_sources[rpc->prod_idx].status = status; - rpc->e_sources[rpc->prod_idx].id = id; - rpc->prod_idx = next_prod_idx; - spin_unlock_irqrestore(&rpc->e_lock, flags); - - /* Invoke DPC handler */ - schedule_work(&rpc->dpc_handler); - - return IRQ_HANDLED; -} -EXPORT_SYMBOL_GPL(aer_irq); - -/** - * aer_alloc_rpc - allocate Root Port data structure - * @dev: pointer to the pcie_dev data structure - * - * Invoked when Root Port's AER service is loaded. - */ -static struct aer_rpc *aer_alloc_rpc(struct pcie_device *dev) -{ - struct aer_rpc *rpc; - - rpc = kzalloc(sizeof(struct aer_rpc), GFP_KERNEL); - if (!rpc) - return NULL; - - /* Initialize Root lock access, e_lock, to Root Error Status Reg */ - spin_lock_init(&rpc->e_lock); - - rpc->rpd = dev->port; - INIT_WORK(&rpc->dpc_handler, aer_isr); - mutex_init(&rpc->rpc_mutex); - - /* Use PCIe bus function to store rpc into PCIe device */ - set_service_data(dev, rpc); - - return rpc; -} - -/** - * aer_remove - clean up resources - * @dev: pointer to the pcie_dev data structure - * - * Invoked when PCI Express bus unloads or AER probe fails. - */ -static void aer_remove(struct pcie_device *dev) -{ - struct aer_rpc *rpc = get_service_data(dev); - - if (rpc) { - /* If register interrupt service, it must be free. */ - if (rpc->isr) - free_irq(dev->irq, dev); - - flush_work(&rpc->dpc_handler); - aer_disable_rootport(rpc); - kfree(rpc); - set_service_data(dev, NULL); - } -} - -/** - * aer_probe - initialize resources - * @dev: pointer to the pcie_dev data structure - * - * Invoked when PCI Express bus loads AER service driver. - */ -static int aer_probe(struct pcie_device *dev) -{ - int status; - struct aer_rpc *rpc; - struct device *device = &dev->port->dev; - - /* Alloc rpc data structure */ - rpc = aer_alloc_rpc(dev); - if (!rpc) { - dev_printk(KERN_DEBUG, device, "alloc AER rpc failed\n"); - aer_remove(dev); - return -ENOMEM; - } - - /* Request IRQ ISR */ - status = request_irq(dev->irq, aer_irq, IRQF_SHARED, "aerdrv", dev); - if (status) { - dev_printk(KERN_DEBUG, device, "request AER IRQ %d failed\n", - dev->irq); - aer_remove(dev); - return status; - } - - rpc->isr = 1; - - aer_enable_rootport(rpc); - dev_info(device, "AER enabled with IRQ %d\n", dev->irq); - return 0; -} - -/** - * aer_root_reset - reset link on Root Port - * @dev: pointer to Root Port's pci_dev data structure - * - * Invoked by Port Bus driver when performing link reset at Root Port. - */ -static pci_ers_result_t aer_root_reset(struct pci_dev *dev) -{ - u32 reg32; - int pos; - - pos = dev->aer_cap; - - /* Disable Root's interrupt in response to error messages */ - pci_read_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, ®32); - reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; - pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32); - - pci_reset_bridge_secondary_bus(dev); - pci_printk(KERN_DEBUG, dev, "Root Port link has been reset\n"); - - /* Clear Root Error Status */ - pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, ®32); - pci_write_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, reg32); - - /* Enable Root Port's interrupt in response to error messages */ - pci_read_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, ®32); - reg32 |= ROOT_PORT_INTR_ON_MESG_MASK; - pci_write_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, reg32); - - return PCI_ERS_RESULT_RECOVERED; -} - -/** - * aer_error_resume - clean up corresponding error status bits - * @dev: pointer to Root Port's pci_dev data structure - * - * Invoked by Port Bus driver during nonfatal recovery. - */ -static void aer_error_resume(struct pci_dev *dev) -{ - int pos; - u32 status, mask; - u16 reg16; - - /* Clean up Root device status */ - pcie_capability_read_word(dev, PCI_EXP_DEVSTA, ®16); - pcie_capability_write_word(dev, PCI_EXP_DEVSTA, reg16); - - /* Clean AER Root Error Status */ - pos = dev->aer_cap; - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); - status &= ~mask; /* Clear corresponding nonfatal bits */ - pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); -} - -/** - * aer_service_init - register AER root service driver - * - * Invoked when AER root service driver is loaded. - */ -static int __init aer_service_init(void) -{ - if (!pci_aer_available() || aer_acpi_firmware_first()) - return -ENXIO; - return pcie_port_service_register(&aerdriver); -} -device_initcall(aer_service_init); diff --git a/drivers/pci/pcie/aer/aerdrv.h b/drivers/pci/pcie/aer/aerdrv.h deleted file mode 100644 index 6e0ad9a..0000000 --- a/drivers/pci/pcie/aer/aerdrv.h +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (C) 2006 Intel Corp. - * Tom Long Nguyen (tom.l.nguyen@intel.com) - * Zhang Yanmin (yanmin.zhang@intel.com) - */ - -#ifndef _AERDRV_H_ -#define _AERDRV_H_ - -#include <linux/workqueue.h> -#include <linux/aer.h> -#include <linux/interrupt.h> - -#include "../portdrv.h" - -#define SYSTEM_ERROR_INTR_ON_MESG_MASK (PCI_EXP_RTCTL_SECEE| \ - PCI_EXP_RTCTL_SENFEE| \ - PCI_EXP_RTCTL_SEFEE) -#define ROOT_PORT_INTR_ON_MESG_MASK (PCI_ERR_ROOT_CMD_COR_EN| \ - PCI_ERR_ROOT_CMD_NONFATAL_EN| \ - PCI_ERR_ROOT_CMD_FATAL_EN) -#define ERR_COR_ID(d) (d & 0xffff) -#define ERR_UNCOR_ID(d) (d >> 16) - -#define AER_ERROR_SOURCES_MAX 100 - -#define AER_LOG_TLP_MASKS (PCI_ERR_UNC_POISON_TLP| \ - PCI_ERR_UNC_ECRC| \ - PCI_ERR_UNC_UNSUP| \ - PCI_ERR_UNC_COMP_ABORT| \ - PCI_ERR_UNC_UNX_COMP| \ - PCI_ERR_UNC_MALF_TLP) - -#define AER_MAX_MULTI_ERR_DEVICES 5 /* Not likely to have more */ -struct aer_err_info { - struct pci_dev *dev[AER_MAX_MULTI_ERR_DEVICES]; - int error_dev_num; - - unsigned int id:16; - - unsigned int severity:2; /* 0:NONFATAL | 1:FATAL | 2:COR */ - unsigned int __pad1:5; - unsigned int multi_error_valid:1; - - unsigned int first_error:5; - unsigned int __pad2:2; - unsigned int tlp_header_valid:1; - - unsigned int status; /* COR/UNCOR Error Status */ - unsigned int mask; /* COR/UNCOR Error Mask */ - struct aer_header_log_regs tlp; /* TLP Header */ -}; - -struct aer_err_source { - unsigned int status; - unsigned int id; -}; - -struct aer_rpc { - struct pci_dev *rpd; /* Root Port device */ - struct work_struct dpc_handler; - struct aer_err_source e_sources[AER_ERROR_SOURCES_MAX]; - struct aer_err_info e_info; - unsigned short prod_idx; /* Error Producer Index */ - unsigned short cons_idx; /* Error Consumer Index */ - int isr; - spinlock_t e_lock; /* - * Lock access to Error Status/ID Regs - * and error producer/consumer index - */ - struct mutex rpc_mutex; /* - * only one thread could do - * recovery on the same - * root port hierarchy - */ -}; - -extern struct bus_type pcie_port_bus_type; -void aer_isr(struct work_struct *work); -void aer_print_error(struct pci_dev *dev, struct aer_err_info *info); -void aer_print_port_info(struct pci_dev *dev, struct aer_err_info *info); -irqreturn_t aer_irq(int irq, void *context); - -#ifdef CONFIG_ACPI_APEI -int pcie_aer_get_firmware_first(struct pci_dev *pci_dev); -#else -static inline int pcie_aer_get_firmware_first(struct pci_dev *pci_dev) -{ - if (pci_dev->__aer_firmware_first_valid) - return pci_dev->__aer_firmware_first; - return 0; -} -#endif -#endif /* _AERDRV_H_ */ diff --git a/drivers/pci/pcie/aer/aerdrv_acpi.c b/drivers/pci/pcie/aer/aerdrv_acpi.c deleted file mode 100644 index 08c87de..0000000 --- a/drivers/pci/pcie/aer/aerdrv_acpi.c +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Access ACPI _OSC method - * - * Copyright (C) 2006 Intel Corp. - * Tom Long Nguyen (tom.l.nguyen@intel.com) - * Zhang Yanmin (yanmin.zhang@intel.com) - */ - -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/kernel.h> -#include <linux/errno.h> -#include <linux/pm.h> -#include <linux/suspend.h> -#include <linux/acpi.h> -#include <linux/pci-acpi.h> -#include <linux/delay.h> -#include <acpi/apei.h> -#include "aerdrv.h" - -#ifdef CONFIG_ACPI_APEI -static inline int hest_match_pci(struct acpi_hest_aer_common *p, - struct pci_dev *pci) -{ - return ACPI_HEST_SEGMENT(p->bus) == pci_domain_nr(pci->bus) && - ACPI_HEST_BUS(p->bus) == pci->bus->number && - p->device == PCI_SLOT(pci->devfn) && - p->function == PCI_FUNC(pci->devfn); -} - -static inline bool hest_match_type(struct acpi_hest_header *hest_hdr, - struct pci_dev *dev) -{ - u16 hest_type = hest_hdr->type; - u8 pcie_type = pci_pcie_type(dev); - - if ((hest_type == ACPI_HEST_TYPE_AER_ROOT_PORT && - pcie_type == PCI_EXP_TYPE_ROOT_PORT) || - (hest_type == ACPI_HEST_TYPE_AER_ENDPOINT && - pcie_type == PCI_EXP_TYPE_ENDPOINT) || - (hest_type == ACPI_HEST_TYPE_AER_BRIDGE && - (dev->class >> 16) == PCI_BASE_CLASS_BRIDGE)) - return true; - return false; -} - -struct aer_hest_parse_info { - struct pci_dev *pci_dev; - int firmware_first; -}; - -static int hest_source_is_pcie_aer(struct acpi_hest_header *hest_hdr) -{ - if (hest_hdr->type == ACPI_HEST_TYPE_AER_ROOT_PORT || - hest_hdr->type == ACPI_HEST_TYPE_AER_ENDPOINT || - hest_hdr->type == ACPI_HEST_TYPE_AER_BRIDGE) - return 1; - return 0; -} - -static int aer_hest_parse(struct acpi_hest_header *hest_hdr, void *data) -{ - struct aer_hest_parse_info *info = data; - struct acpi_hest_aer_common *p; - int ff; - - if (!hest_source_is_pcie_aer(hest_hdr)) - return 0; - - p = (struct acpi_hest_aer_common *)(hest_hdr + 1); - ff = !!(p->flags & ACPI_HEST_FIRMWARE_FIRST); - - /* - * If no specific device is supplied, determine whether - * FIRMWARE_FIRST is set for *any* PCIe device. - */ - if (!info->pci_dev) { - info->firmware_first |= ff; - return 0; - } - - /* Otherwise, check the specific device */ - if (p->flags & ACPI_HEST_GLOBAL) { - if (hest_match_type(hest_hdr, info->pci_dev)) - info->firmware_first = ff; - } else - if (hest_match_pci(p, info->pci_dev)) - info->firmware_first = ff; - - return 0; -} - -static void aer_set_firmware_first(struct pci_dev *pci_dev) -{ - int rc; - struct aer_hest_parse_info info = { - .pci_dev = pci_dev, - .firmware_first = 0, - }; - - rc = apei_hest_parse(aer_hest_parse, &info); - - if (rc) - pci_dev->__aer_firmware_first = 0; - else - pci_dev->__aer_firmware_first = info.firmware_first; - pci_dev->__aer_firmware_first_valid = 1; -} - -int pcie_aer_get_firmware_first(struct pci_dev *dev) -{ - if (!pci_is_pcie(dev)) - return 0; - - if (!dev->__aer_firmware_first_valid) - aer_set_firmware_first(dev); - return dev->__aer_firmware_first; -} - -static bool aer_firmware_first; - -/** - * aer_acpi_firmware_first - Check if APEI should control AER. - */ -bool aer_acpi_firmware_first(void) -{ - static bool parsed = false; - struct aer_hest_parse_info info = { - .pci_dev = NULL, /* Check all PCIe devices */ - .firmware_first = 0, - }; - - if (!parsed) { - apei_hest_parse(aer_hest_parse, &info); - aer_firmware_first = info.firmware_first; - parsed = true; - } - return aer_firmware_first; -} -#endif diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c deleted file mode 100644 index 42d4f3f..0000000 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ /dev/null @@ -1,496 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Implement the core part of PCIe AER. When a PCIe error is delivered, an - * error message will be collected and printed to console, then an error - * recovery procedure will be executed by following the PCI error recovery - * rules. - * - * Copyright (C) 2006 Intel Corp. - * Tom Long Nguyen (tom.l.nguyen@intel.com) - * Zhang Yanmin (yanmin.zhang@intel.com) - */ - -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/kernel.h> -#include <linux/errno.h> -#include <linux/pm.h> -#include <linux/suspend.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/kfifo.h> -#include "aerdrv.h" -#include "../../pci.h" - -#define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ - PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) - -int pci_enable_pcie_error_reporting(struct pci_dev *dev) -{ - if (pcie_aer_get_firmware_first(dev)) - return -EIO; - - if (!dev->aer_cap) - return -EIO; - - return pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_AER_FLAGS); -} -EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting); - -int pci_disable_pcie_error_reporting(struct pci_dev *dev) -{ - if (pcie_aer_get_firmware_first(dev)) - return -EIO; - - return pcie_capability_clear_word(dev, PCI_EXP_DEVCTL, - PCI_EXP_AER_FLAGS); -} -EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); - -int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) -{ - int pos; - u32 status; - - pos = dev->aer_cap; - if (!pos) - return -EIO; - - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); - if (status) - pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); - - return 0; -} -EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status); - -int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) -{ - int pos; - u32 status; - int port_type; - - if (!pci_is_pcie(dev)) - return -ENODEV; - - pos = dev->aer_cap; - if (!pos) - return -EIO; - - port_type = pci_pcie_type(dev); - if (port_type == PCI_EXP_TYPE_ROOT_PORT) { - pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &status); - pci_write_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, status); - } - - pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, &status); - pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, status); - - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); - pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); - - return 0; -} - -int pci_aer_init(struct pci_dev *dev) -{ - dev->aer_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); - return pci_cleanup_aer_error_status_regs(dev); -} - -/** - * add_error_device - list device to be handled - * @e_info: pointer to error info - * @dev: pointer to pci_dev to be added - */ -static int add_error_device(struct aer_err_info *e_info, struct pci_dev *dev) -{ - if (e_info->error_dev_num < AER_MAX_MULTI_ERR_DEVICES) { - e_info->dev[e_info->error_dev_num] = dev; - e_info->error_dev_num++; - return 0; - } - return -ENOSPC; -} - -/** - * is_error_source - check whether the device is source of reported error - * @dev: pointer to pci_dev to be checked - * @e_info: pointer to reported error info - */ -static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) -{ - int pos; - u32 status, mask; - u16 reg16; - - /* - * When bus id is equal to 0, it might be a bad id - * reported by root port. - */ - if ((PCI_BUS_NUM(e_info->id) != 0) && - !(dev->bus->bus_flags & PCI_BUS_FLAGS_NO_AERSID)) { - /* Device ID match? */ - if (e_info->id == ((dev->bus->number << 8) | dev->devfn)) - return true; - - /* Continue id comparing if there is no multiple error */ - if (!e_info->multi_error_valid) - return false; - } - - /* - * When either - * 1) bus id is equal to 0. Some ports might lose the bus - * id of error source id; - * 2) bus flag PCI_BUS_FLAGS_NO_AERSID is set - * 3) There are multiple errors and prior ID comparing fails; - * We check AER status registers to find possible reporter. - */ - if (atomic_read(&dev->enable_cnt) == 0) - return false; - - /* Check if AER is enabled */ - pcie_capability_read_word(dev, PCI_EXP_DEVCTL, ®16); - if (!(reg16 & PCI_EXP_AER_FLAGS)) - return false; - - pos = dev->aer_cap; - if (!pos) - return false; - - /* Check if error is recorded */ - if (e_info->severity == AER_CORRECTABLE) { - pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, &status); - pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, &mask); - } else { - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, &mask); - } - if (status & ~mask) - return true; - - return false; -} - -static int find_device_iter(struct pci_dev *dev, void *data) -{ - struct aer_err_info *e_info = (struct aer_err_info *)data; - - if (is_error_source(dev, e_info)) { - /* List this device */ - if (add_error_device(e_info, dev)) { - /* We cannot handle more... Stop iteration */ - /* TODO: Should print error message here? */ - return 1; - } - - /* If there is only a single error, stop iteration */ - if (!e_info->multi_error_valid) - return 1; - } - return 0; -} - -/** - * find_source_device - search through device hierarchy for source device - * @parent: pointer to Root Port pci_dev data structure - * @e_info: including detailed error information such like id - * - * Return true if found. - * - * Invoked by DPC when error is detected at the Root Port. - * Caller of this function must set id, severity, and multi_error_valid of - * struct aer_err_info pointed by @e_info properly. This function must fill - * e_info->error_dev_num and e_info->dev[], based on the given information. - */ -static bool find_source_device(struct pci_dev *parent, - struct aer_err_info *e_info) -{ - struct pci_dev *dev = parent; - int result; - - /* Must reset in this function */ - e_info->error_dev_num = 0; - - /* Is Root Port an agent that sends error message? */ - result = find_device_iter(dev, e_info); - if (result) - return true; - - pci_walk_bus(parent->subordinate, find_device_iter, e_info); - - if (!e_info->error_dev_num) { - pci_printk(KERN_DEBUG, parent, "can't find device of ID%04x\n", - e_info->id); - return false; - } - return true; -} - -/** - * handle_error_source - handle logging error into an event log - * @dev: pointer to pci_dev data structure of error source device - * @info: comprehensive error information - * - * Invoked when an error being detected by Root Port. - */ -static void handle_error_source(struct pci_dev *dev, struct aer_err_info *info) -{ - int pos; - - if (info->severity == AER_CORRECTABLE) { - /* - * Correctable error does not need software intervention. - * No need to go through error recovery process. - */ - pos = dev->aer_cap; - if (pos) - pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, - info->status); - } else if (info->severity == AER_NONFATAL) - pcie_do_nonfatal_recovery(dev); - else if (info->severity == AER_FATAL) - pcie_do_fatal_recovery(dev, PCIE_PORT_SERVICE_AER); -} - -#ifdef CONFIG_ACPI_APEI_PCIEAER - -#define AER_RECOVER_RING_ORDER 4 -#define AER_RECOVER_RING_SIZE (1 << AER_RECOVER_RING_ORDER) - -struct aer_recover_entry { - u8 bus; - u8 devfn; - u16 domain; - int severity; - struct aer_capability_regs *regs; -}; - -static DEFINE_KFIFO(aer_recover_ring, struct aer_recover_entry, - AER_RECOVER_RING_SIZE); - -static void aer_recover_work_func(struct work_struct *work) -{ - struct aer_recover_entry entry; - struct pci_dev *pdev; - - while (kfifo_get(&aer_recover_ring, &entry)) { - pdev = pci_get_domain_bus_and_slot(entry.domain, entry.bus, - entry.devfn); - if (!pdev) { - pr_err("AER recover: Can not find pci_dev for %04x:%02x:%02x:%x\n", - entry.domain, entry.bus, - PCI_SLOT(entry.devfn), PCI_FUNC(entry.devfn)); - continue; - } - cper_print_aer(pdev, entry.severity, entry.regs); - if (entry.severity == AER_NONFATAL) - pcie_do_nonfatal_recovery(pdev); - else if (entry.severity == AER_FATAL) - pcie_do_fatal_recovery(pdev, PCIE_PORT_SERVICE_AER); - pci_dev_put(pdev); - } -} - -/* - * Mutual exclusion for writers of aer_recover_ring, reader side don't - * need lock, because there is only one reader and lock is not needed - * between reader and writer. - */ -static DEFINE_SPINLOCK(aer_recover_ring_lock); -static DECLARE_WORK(aer_recover_work, aer_recover_work_func); - -void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, - int severity, struct aer_capability_regs *aer_regs) -{ - unsigned long flags; - struct aer_recover_entry entry = { - .bus = bus, - .devfn = devfn, - .domain = domain, - .severity = severity, - .regs = aer_regs, - }; - - spin_lock_irqsave(&aer_recover_ring_lock, flags); - if (kfifo_put(&aer_recover_ring, entry)) - schedule_work(&aer_recover_work); - else - pr_err("AER recover: Buffer overflow when recovering AER for %04x:%02x:%02x:%x\n", - domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); - spin_unlock_irqrestore(&aer_recover_ring_lock, flags); -} -EXPORT_SYMBOL_GPL(aer_recover_queue); -#endif - -/** - * get_device_error_info - read error status from dev and store it to info - * @dev: pointer to the device expected to have a error record - * @info: pointer to structure to store the error record - * - * Return 1 on success, 0 on error. - * - * Note that @info is reused among all error devices. Clear fields properly. - */ -static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) -{ - int pos, temp; - - /* Must reset in this function */ - info->status = 0; - info->tlp_header_valid = 0; - - pos = dev->aer_cap; - - /* The device might not support AER */ - if (!pos) - return 0; - - if (info->severity == AER_CORRECTABLE) { - pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, - &info->status); - pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, - &info->mask); - if (!(info->status & ~info->mask)) - return 0; - } else if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || - info->severity == AER_NONFATAL) { - - /* Link is still healthy for IO reads */ - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, - &info->status); - pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, - &info->mask); - if (!(info->status & ~info->mask)) - return 0; - - /* Get First Error Pointer */ - pci_read_config_dword(dev, pos + PCI_ERR_CAP, &temp); - info->first_error = PCI_ERR_CAP_FEP(temp); - - if (info->status & AER_LOG_TLP_MASKS) { - info->tlp_header_valid = 1; - pci_read_config_dword(dev, - pos + PCI_ERR_HEADER_LOG, &info->tlp.dw0); - pci_read_config_dword(dev, - pos + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1); - pci_read_config_dword(dev, - pos + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2); - pci_read_config_dword(dev, - pos + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3); - } - } - - return 1; -} - -static inline void aer_process_err_devices(struct aer_err_info *e_info) -{ - int i; - - /* Report all before handle them, not to lost records by reset etc. */ - for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { - if (get_device_error_info(e_info->dev[i], e_info)) - aer_print_error(e_info->dev[i], e_info); - } - for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { - if (get_device_error_info(e_info->dev[i], e_info)) - handle_error_source(e_info->dev[i], e_info); - } -} - -/** - * aer_isr_one_error - consume an error detected by root port - * @rpc: pointer to the root port which holds an error - * @e_src: pointer to an error source - */ -static void aer_isr_one_error(struct aer_rpc *rpc, - struct aer_err_source *e_src) -{ - struct pci_dev *pdev = rpc->rpd; - struct aer_err_info *e_info = &rpc->e_info; - - /* - * There is a possibility that both correctable error and - * uncorrectable error being logged. Report correctable error first. - */ - if (e_src->status & PCI_ERR_ROOT_COR_RCV) { - e_info->id = ERR_COR_ID(e_src->id); - e_info->severity = AER_CORRECTABLE; - - if (e_src->status & PCI_ERR_ROOT_MULTI_COR_RCV) - e_info->multi_error_valid = 1; - else - e_info->multi_error_valid = 0; - aer_print_port_info(pdev, e_info); - - if (find_source_device(pdev, e_info)) - aer_process_err_devices(e_info); - } - - if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) { - e_info->id = ERR_UNCOR_ID(e_src->id); - - if (e_src->status & PCI_ERR_ROOT_FATAL_RCV) - e_info->severity = AER_FATAL; - else - e_info->severity = AER_NONFATAL; - - if (e_src->status & PCI_ERR_ROOT_MULTI_UNCOR_RCV) - e_info->multi_error_valid = 1; - else - e_info->multi_error_valid = 0; - - aer_print_port_info(pdev, e_info); - - if (find_source_device(pdev, e_info)) - aer_process_err_devices(e_info); - } -} - -/** - * get_e_source - retrieve an error source - * @rpc: pointer to the root port which holds an error - * @e_src: pointer to store retrieved error source - * - * Return 1 if an error source is retrieved, otherwise 0. - * - * Invoked by DPC handler to consume an error. - */ -static int get_e_source(struct aer_rpc *rpc, struct aer_err_source *e_src) -{ - unsigned long flags; - - /* Lock access to Root error producer/consumer index */ - spin_lock_irqsave(&rpc->e_lock, flags); - if (rpc->prod_idx == rpc->cons_idx) { - spin_unlock_irqrestore(&rpc->e_lock, flags); - return 0; - } - - *e_src = rpc->e_sources[rpc->cons_idx]; - rpc->cons_idx++; - if (rpc->cons_idx == AER_ERROR_SOURCES_MAX) - rpc->cons_idx = 0; - spin_unlock_irqrestore(&rpc->e_lock, flags); - - return 1; -} - -/** - * aer_isr - consume errors detected by root port - * @work: definition of this work item - * - * Invoked, as DPC, when root port records new detected error - */ -void aer_isr(struct work_struct *work) -{ - struct aer_rpc *rpc = container_of(work, struct aer_rpc, dpc_handler); - struct aer_err_source uninitialized_var(e_src); - - mutex_lock(&rpc->rpc_mutex); - while (get_e_source(rpc, &e_src)) - aer_isr_one_error(rpc, &e_src); - mutex_unlock(&rpc->rpc_mutex); -} diff --git a/drivers/pci/pcie/aer/aerdrv_errprint.c b/drivers/pci/pcie/aer/aerdrv_errprint.c deleted file mode 100644 index 4985bdf..0000000 --- a/drivers/pci/pcie/aer/aerdrv_errprint.c +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Format error messages and print them to console. - * - * Copyright (C) 2006 Intel Corp. - * Tom Long Nguyen (tom.l.nguyen@intel.com) - * Zhang Yanmin (yanmin.zhang@intel.com) - */ - -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/kernel.h> -#include <linux/errno.h> -#include <linux/pm.h> -#include <linux/suspend.h> -#include <linux/cper.h> - -#include "aerdrv.h" -#include <ras/ras_event.h> - -#define AER_AGENT_RECEIVER 0 -#define AER_AGENT_REQUESTER 1 -#define AER_AGENT_COMPLETER 2 -#define AER_AGENT_TRANSMITTER 3 - -#define AER_AGENT_REQUESTER_MASK(t) ((t == AER_CORRECTABLE) ? \ - 0 : (PCI_ERR_UNC_COMP_TIME|PCI_ERR_UNC_UNSUP)) -#define AER_AGENT_COMPLETER_MASK(t) ((t == AER_CORRECTABLE) ? \ - 0 : PCI_ERR_UNC_COMP_ABORT) -#define AER_AGENT_TRANSMITTER_MASK(t) ((t == AER_CORRECTABLE) ? \ - (PCI_ERR_COR_REP_ROLL|PCI_ERR_COR_REP_TIMER) : 0) - -#define AER_GET_AGENT(t, e) \ - ((e & AER_AGENT_COMPLETER_MASK(t)) ? AER_AGENT_COMPLETER : \ - (e & AER_AGENT_REQUESTER_MASK(t)) ? AER_AGENT_REQUESTER : \ - (e & AER_AGENT_TRANSMITTER_MASK(t)) ? AER_AGENT_TRANSMITTER : \ - AER_AGENT_RECEIVER) - -#define AER_PHYSICAL_LAYER_ERROR 0 -#define AER_DATA_LINK_LAYER_ERROR 1 -#define AER_TRANSACTION_LAYER_ERROR 2 - -#define AER_PHYSICAL_LAYER_ERROR_MASK(t) ((t == AER_CORRECTABLE) ? \ - PCI_ERR_COR_RCVR : 0) -#define AER_DATA_LINK_LAYER_ERROR_MASK(t) ((t == AER_CORRECTABLE) ? \ - (PCI_ERR_COR_BAD_TLP| \ - PCI_ERR_COR_BAD_DLLP| \ - PCI_ERR_COR_REP_ROLL| \ - PCI_ERR_COR_REP_TIMER) : PCI_ERR_UNC_DLP) - -#define AER_GET_LAYER_ERROR(t, e) \ - ((e & AER_PHYSICAL_LAYER_ERROR_MASK(t)) ? AER_PHYSICAL_LAYER_ERROR : \ - (e & AER_DATA_LINK_LAYER_ERROR_MASK(t)) ? AER_DATA_LINK_LAYER_ERROR : \ - AER_TRANSACTION_LAYER_ERROR) - -/* - * AER error strings - */ -static const char *aer_error_severity_string[] = { - "Uncorrected (Non-Fatal)", - "Uncorrected (Fatal)", - "Corrected" -}; - -static const char *aer_error_layer[] = { - "Physical Layer", - "Data Link Layer", - "Transaction Layer" -}; - -static const char *aer_correctable_error_string[] = { - "Receiver Error", /* Bit Position 0 */ - NULL, - NULL, - NULL, - NULL, - NULL, - "Bad TLP", /* Bit Position 6 */ - "Bad DLLP", /* Bit Position 7 */ - "RELAY_NUM Rollover", /* Bit Position 8 */ - NULL, - NULL, - NULL, - "Replay Timer Timeout", /* Bit Position 12 */ - "Advisory Non-Fatal", /* Bit Position 13 */ - "Corrected Internal Error", /* Bit Position 14 */ - "Header Log Overflow", /* Bit Position 15 */ -}; - -static const char *aer_uncorrectable_error_string[] = { - "Undefined", /* Bit Position 0 */ - NULL, - NULL, - NULL, - "Data Link Protocol", /* Bit Position 4 */ - "Surprise Down Error", /* Bit Position 5 */ - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "Poisoned TLP", /* Bit Position 12 */ - "Flow Control Protocol", /* Bit Position 13 */ - "Completion Timeout", /* Bit Position 14 */ - "Completer Abort", /* Bit Position 15 */ - "Unexpected Completion", /* Bit Position 16 */ - "Receiver Overflow", /* Bit Position 17 */ - "Malformed TLP", /* Bit Position 18 */ - "ECRC", /* Bit Position 19 */ - "Unsupported Request", /* Bit Position 20 */ - "ACS Violation", /* Bit Position 21 */ - "Uncorrectable Internal Error", /* Bit Position 22 */ - "MC Blocked TLP", /* Bit Position 23 */ - "AtomicOp Egress Blocked", /* Bit Position 24 */ - "TLP Prefix Blocked Error", /* Bit Position 25 */ -}; - -static const char *aer_agent_string[] = { - "Receiver ID", - "Requester ID", - "Completer ID", - "Transmitter ID" -}; - -static void __print_tlp_header(struct pci_dev *dev, - struct aer_header_log_regs *t) -{ - pci_err(dev, " TLP Header: %08x %08x %08x %08x\n", - t->dw0, t->dw1, t->dw2, t->dw3); -} - -static void __aer_print_error(struct pci_dev *dev, - struct aer_err_info *info) -{ - int i, status; - const char *errmsg = NULL; - status = (info->status & ~info->mask); - - for (i = 0; i < 32; i++) { - if (!(status & (1 << i))) - continue; - - if (info->severity == AER_CORRECTABLE) - errmsg = i < ARRAY_SIZE(aer_correctable_error_string) ? - aer_correctable_error_string[i] : NULL; - else - errmsg = i < ARRAY_SIZE(aer_uncorrectable_error_string) ? - aer_uncorrectable_error_string[i] : NULL; - - if (errmsg) - pci_err(dev, " [%2d] %-22s%s\n", i, errmsg, - info->first_error == i ? " (First)" : ""); - else - pci_err(dev, " [%2d] Unknown Error Bit%s\n", - i, info->first_error == i ? " (First)" : ""); - } -} - -void aer_print_error(struct pci_dev *dev, struct aer_err_info *info) -{ - int layer, agent; - int id = ((dev->bus->number << 8) | dev->devfn); - - if (!info->status) { - pci_err(dev, "PCIe Bus Error: severity=%s, type=Inaccessible, (Unregistered Agent ID)\n", - aer_error_severity_string[info->severity]); - goto out; - } - - layer = AER_GET_LAYER_ERROR(info->severity, info->status); - agent = AER_GET_AGENT(info->severity, info->status); - - pci_err(dev, "PCIe Bus Error: severity=%s, type=%s, (%s)\n", - aer_error_severity_string[info->severity], - aer_error_layer[layer], aer_agent_string[agent]); - - pci_err(dev, " device [%04x:%04x] error status/mask=%08x/%08x\n", - dev->vendor, dev->device, - info->status, info->mask); - - __aer_print_error(dev, info); - - if (info->tlp_header_valid) - __print_tlp_header(dev, &info->tlp); - -out: - if (info->id && info->error_dev_num > 1 && info->id == id) - pci_err(dev, " Error of this Agent is reported first\n"); - - trace_aer_event(dev_name(&dev->dev), (info->status & ~info->mask), - info->severity, info->tlp_header_valid, &info->tlp); -} - -void aer_print_port_info(struct pci_dev *dev, struct aer_err_info *info) -{ - u8 bus = info->id >> 8; - u8 devfn = info->id & 0xff; - - pci_info(dev, "AER: %s%s error received: %04x:%02x:%02x.%d\n", - info->multi_error_valid ? "Multiple " : "", - aer_error_severity_string[info->severity], - pci_domain_nr(dev->bus), bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); -} - -#ifdef CONFIG_ACPI_APEI_PCIEAER -int cper_severity_to_aer(int cper_severity) -{ - switch (cper_severity) { - case CPER_SEV_RECOVERABLE: - return AER_NONFATAL; - case CPER_SEV_FATAL: - return AER_FATAL; - default: - return AER_CORRECTABLE; - } -} -EXPORT_SYMBOL_GPL(cper_severity_to_aer); - -void cper_print_aer(struct pci_dev *dev, int aer_severity, - struct aer_capability_regs *aer) -{ - int layer, agent, tlp_header_valid = 0; - u32 status, mask; - struct aer_err_info info; - - if (aer_severity == AER_CORRECTABLE) { - status = aer->cor_status; - mask = aer->cor_mask; - } else { - status = aer->uncor_status; - mask = aer->uncor_mask; - tlp_header_valid = status & AER_LOG_TLP_MASKS; - } - - layer = AER_GET_LAYER_ERROR(aer_severity, status); - agent = AER_GET_AGENT(aer_severity, status); - - memset(&info, 0, sizeof(info)); - info.severity = aer_severity; - info.status = status; - info.mask = mask; - info.first_error = PCI_ERR_CAP_FEP(aer->cap_control); - - pci_err(dev, "aer_status: 0x%08x, aer_mask: 0x%08x\n", status, mask); - __aer_print_error(dev, &info); - pci_err(dev, "aer_layer=%s, aer_agent=%s\n", - aer_error_layer[layer], aer_agent_string[agent]); - - if (aer_severity != AER_CORRECTABLE) - pci_err(dev, "aer_uncor_severity: 0x%08x\n", - aer->uncor_severity); - - if (tlp_header_valid) - __print_tlp_header(dev, &aer->header_log); - - trace_aer_event(dev_name(&dev->dev), (status & ~mask), - aer_severity, tlp_header_valid, &aer->header_log); -} -#endif diff --git a/drivers/pci/pcie/aer/ecrc.c b/drivers/pci/pcie/aer/ecrc.c deleted file mode 100644 index 039efb60..0000000 --- a/drivers/pci/pcie/aer/ecrc.c +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Enable/disable PCIe ECRC checking - * - * (C) Copyright 2009 Hewlett-Packard Development Company, L.P. - * Andrew Patterson <andrew.patterson@hp.com> - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/pci.h> -#include <linux/pci_regs.h> -#include <linux/errno.h> -#include "../../pci.h" - -#define ECRC_POLICY_DEFAULT 0 /* ECRC set by BIOS */ -#define ECRC_POLICY_OFF 1 /* ECRC off for performance */ -#define ECRC_POLICY_ON 2 /* ECRC on for data integrity */ - -static int ecrc_policy = ECRC_POLICY_DEFAULT; - -static const char *ecrc_policy_str[] = { - [ECRC_POLICY_DEFAULT] = "bios", - [ECRC_POLICY_OFF] = "off", - [ECRC_POLICY_ON] = "on" -}; - -/** - * enable_ercr_checking - enable PCIe ECRC checking for a device - * @dev: the PCI device - * - * Returns 0 on success, or negative on failure. - */ -static int enable_ecrc_checking(struct pci_dev *dev) -{ - int pos; - u32 reg32; - - if (!pci_is_pcie(dev)) - return -ENODEV; - - pos = dev->aer_cap; - if (!pos) - return -ENODEV; - - pci_read_config_dword(dev, pos + PCI_ERR_CAP, ®32); - if (reg32 & PCI_ERR_CAP_ECRC_GENC) - reg32 |= PCI_ERR_CAP_ECRC_GENE; - if (reg32 & PCI_ERR_CAP_ECRC_CHKC) - reg32 |= PCI_ERR_CAP_ECRC_CHKE; - pci_write_config_dword(dev, pos + PCI_ERR_CAP, reg32); - - return 0; -} - -/** - * disable_ercr_checking - disables PCIe ECRC checking for a device - * @dev: the PCI device - * - * Returns 0 on success, or negative on failure. - */ -static int disable_ecrc_checking(struct pci_dev *dev) -{ - int pos; - u32 reg32; - - if (!pci_is_pcie(dev)) - return -ENODEV; - - pos = dev->aer_cap; - if (!pos) - return -ENODEV; - - pci_read_config_dword(dev, pos + PCI_ERR_CAP, ®32); - reg32 &= ~(PCI_ERR_CAP_ECRC_GENE | PCI_ERR_CAP_ECRC_CHKE); - pci_write_config_dword(dev, pos + PCI_ERR_CAP, reg32); - - return 0; -} - -/** - * pcie_set_ecrc_checking - set/unset PCIe ECRC checking for a device based on global policy - * @dev: the PCI device - */ -void pcie_set_ecrc_checking(struct pci_dev *dev) -{ - switch (ecrc_policy) { - case ECRC_POLICY_DEFAULT: - return; - case ECRC_POLICY_OFF: - disable_ecrc_checking(dev); - break; - case ECRC_POLICY_ON: - enable_ecrc_checking(dev); - break; - default: - return; - } -} - -/** - * pcie_ecrc_get_policy - parse kernel command-line ecrc option - */ -void pcie_ecrc_get_policy(char *str) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(ecrc_policy_str); i++) - if (!strncmp(str, ecrc_policy_str[i], - strlen(ecrc_policy_str[i]))) - break; - if (i >= ARRAY_SIZE(ecrc_policy_str)) - return; - - ecrc_policy = i; -} diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer_inject.c index a490909..0eb2434 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer_inject.c @@ -21,7 +21,8 @@ #include <linux/uaccess.h> #include <linux/stddef.h> #include <linux/device.h> -#include "aerdrv.h" + +#include "portdrv.h" /* Override the existing corrected and uncorrected error masks */ static bool aer_mask_override; diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c index d643668..921ed97 100644 --- a/drivers/pci/pcie/dpc.c +++ b/drivers/pci/pcie/dpc.c @@ -13,7 +13,6 @@ #include "portdrv.h" #include "../pci.h" -#include "aer/aerdrv.h" struct dpc_dev { struct pcie_device *dev; diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index 2bb5db7..6ffc797 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -110,6 +110,21 @@ static inline bool pcie_pme_no_msi(void) { return false; } static inline void pcie_pme_interrupt_enable(struct pci_dev *dev, bool en) {} #endif /* !CONFIG_PCIE_PME */ +#ifdef CONFIG_ACPI_APEI +int pcie_aer_get_firmware_first(struct pci_dev *pci_dev); +#else +static inline int pcie_aer_get_firmware_first(struct pci_dev *pci_dev) +{ + if (pci_dev->__aer_firmware_first_valid) + return pci_dev->__aer_firmware_first; + return 0; +} +#endif + +#ifdef CONFIG_PCIEAER +irqreturn_t aer_irq(int irq, void *context); +#endif + struct pcie_port_service_driver *pcie_port_find_service(struct pci_dev *dev, u32 service); struct device *pcie_port_find_device(struct pci_dev *dev, u32 service); diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 0272740..cd1c168 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -24,7 +24,6 @@ config IMX_REMOTEPROC config OMAP_REMOTEPROC tristate "OMAP remoteproc support" - depends on HAS_DMA depends on ARCH_OMAP4 || SOC_OMAP5 depends on OMAP_IOMMU select MAILBOX diff --git a/drivers/remoteproc/da8xx_remoteproc.c b/drivers/remoteproc/da8xx_remoteproc.c index bf3b903..b668e32 100644 --- a/drivers/remoteproc/da8xx_remoteproc.c +++ b/drivers/remoteproc/da8xx_remoteproc.c @@ -25,7 +25,7 @@ #include "remoteproc_internal.h" static char *da8xx_fw_name; -module_param(da8xx_fw_name, charp, S_IRUGO); +module_param(da8xx_fw_name, charp, 0444); MODULE_PARM_DESC(da8xx_fw_name, "Name of DSP firmware file in /lib/firmware (if not specified defaults to 'rproc-dsp-fw')"); @@ -138,6 +138,7 @@ static int da8xx_rproc_start(struct rproc *rproc) struct device *dev = rproc->dev.parent; struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv; struct clk *dsp_clk = drproc->dsp_clk; + int ret; /* hw requires the start (boot) address be on 1KB boundary */ if (rproc->bootaddr & 0x3ff) { @@ -148,7 +149,12 @@ static int da8xx_rproc_start(struct rproc *rproc) writel(rproc->bootaddr, drproc->bootreg); - clk_enable(dsp_clk); + ret = clk_prepare_enable(dsp_clk); + if (ret) { + dev_err(dev, "clk_prepare_enable() failed: %d\n", ret); + return ret; + } + davinci_clk_reset_deassert(dsp_clk); return 0; @@ -159,7 +165,7 @@ static int da8xx_rproc_stop(struct rproc *rproc) struct da8xx_rproc *drproc = rproc->priv; davinci_clk_reset_assert(drproc->dsp_clk); - clk_disable(drproc->dsp_clk); + clk_disable_unprepare(drproc->dsp_clk); return 0; } diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c index cbbafdc..2190deb 100644 --- a/drivers/remoteproc/qcom_q6v5_pil.c +++ b/drivers/remoteproc/qcom_q6v5_pil.c @@ -57,6 +57,8 @@ #define RMB_PMI_META_DATA_REG 0x10 #define RMB_PMI_CODE_START_REG 0x14 #define RMB_PMI_CODE_LENGTH_REG 0x18 +#define RMB_MBA_MSS_STATUS 0x40 +#define RMB_MBA_ALT_RESET 0x44 #define RMB_CMD_META_DATA_READY 0x1 #define RMB_CMD_LOAD_READY 0x2 @@ -104,6 +106,13 @@ #define QDSP6SS_XO_CBCR 0x0038 #define QDSP6SS_ACC_OVERRIDE_VAL 0x20 +/* QDSP6v65 parameters */ +#define QDSP6SS_SLEEP 0x3C +#define QDSP6SS_BOOT_CORE_START 0x400 +#define QDSP6SS_BOOT_CMD 0x404 +#define SLEEP_CHECK_MAX_LOOPS 200 +#define BOOT_FSM_TIMEOUT 10000 + struct reg_info { struct regulator *reg; int uV; @@ -121,9 +130,11 @@ struct rproc_hexagon_res { struct qcom_mss_reg_res *proxy_supply; struct qcom_mss_reg_res *active_supply; char **proxy_clk_names; + char **reset_clk_names; char **active_clk_names; int version; bool need_mem_protection; + bool has_alt_reset; }; struct q6v5 { @@ -143,9 +154,15 @@ struct q6v5 { struct qcom_smem_state *state; unsigned stop_bit; + int handover_irq; + + bool proxy_unvoted; + struct clk *active_clks[8]; + struct clk *reset_clks[4]; struct clk *proxy_clks[4]; int active_clk_count; + int reset_clk_count; int proxy_clk_count; struct reg_info active_regs[1]; @@ -166,10 +183,12 @@ struct q6v5 { void *mpss_region; size_t mpss_size; + struct qcom_rproc_glink glink_subdev; struct qcom_rproc_subdev smd_subdev; struct qcom_rproc_ssr ssr_subdev; struct qcom_sysmon *sysmon; bool need_mem_protection; + bool has_alt_reset; int mpss_perm; int mba_perm; int version; @@ -179,6 +198,7 @@ enum { MSS_MSM8916, MSS_MSM8974, MSS_MSM8996, + MSS_SDM845, }; static int q6v5_regulator_init(struct device *dev, struct reg_info *regs, @@ -333,6 +353,29 @@ static int q6v5_load(struct rproc *rproc, const struct firmware *fw) return 0; } +static int q6v5_reset_assert(struct q6v5 *qproc) +{ + if (qproc->has_alt_reset) + return reset_control_reset(qproc->mss_restart); + else + return reset_control_assert(qproc->mss_restart); +} + +static int q6v5_reset_deassert(struct q6v5 *qproc) +{ + int ret; + + if (qproc->has_alt_reset) { + writel(1, qproc->rmb_base + RMB_MBA_ALT_RESET); + ret = reset_control_reset(qproc->mss_restart); + writel(0, qproc->rmb_base + RMB_MBA_ALT_RESET); + } else { + ret = reset_control_deassert(qproc->mss_restart); + } + + return ret; +} + static int q6v5_rmb_pbl_wait(struct q6v5 *qproc, int ms) { unsigned long timeout; @@ -385,8 +428,35 @@ static int q6v5proc_reset(struct q6v5 *qproc) int ret; int i; + if (qproc->version == MSS_SDM845) { + val = readl(qproc->reg_base + QDSP6SS_SLEEP); + val |= 0x1; + writel(val, qproc->reg_base + QDSP6SS_SLEEP); - if (qproc->version == MSS_MSM8996) { + ret = readl_poll_timeout(qproc->reg_base + QDSP6SS_SLEEP, + val, !(val & BIT(31)), 1, + SLEEP_CHECK_MAX_LOOPS); + if (ret) { + dev_err(qproc->dev, "QDSP6SS Sleep clock timed out\n"); + return -ETIMEDOUT; + } + + /* De-assert QDSP6 stop core */ + writel(1, qproc->reg_base + QDSP6SS_BOOT_CORE_START); + /* Trigger boot FSM */ + writel(1, qproc->reg_base + QDSP6SS_BOOT_CMD); + + ret = readl_poll_timeout(qproc->rmb_base + RMB_MBA_MSS_STATUS, + val, (val & BIT(0)) != 0, 10, BOOT_FSM_TIMEOUT); + if (ret) { + dev_err(qproc->dev, "Boot FSM failed to complete.\n"); + /* Reset the modem so that boot FSM is in reset state */ + q6v5_reset_deassert(qproc); + return ret; + } + + goto pbl_wait; + } else if (qproc->version == MSS_MSM8996) { /* Override the ACC value if required */ writel(QDSP6SS_ACC_OVERRIDE_VAL, qproc->reg_base + QDSP6SS_STRAP_ACC); @@ -494,6 +564,7 @@ static int q6v5proc_reset(struct q6v5 *qproc) val &= ~Q6SS_STOP_CORE; writel(val, qproc->reg_base + QDSP6SS_RESET_REG); +pbl_wait: /* Wait for PBL status */ ret = q6v5_rmb_pbl_wait(qproc, 1000); if (ret == -ETIMEDOUT) { @@ -727,11 +798,15 @@ static int q6v5_start(struct rproc *rproc) int xfermemop_ret; int ret; + qproc->proxy_unvoted = false; + + enable_irq(qproc->handover_irq); + ret = q6v5_regulator_enable(qproc, qproc->proxy_regs, qproc->proxy_reg_count); if (ret) { dev_err(qproc->dev, "failed to enable proxy supplies\n"); - return ret; + goto disable_irqs; } ret = q6v5_clk_enable(qproc->dev, qproc->proxy_clks, @@ -747,12 +822,20 @@ static int q6v5_start(struct rproc *rproc) dev_err(qproc->dev, "failed to enable supplies\n"); goto disable_proxy_clk; } - ret = reset_control_deassert(qproc->mss_restart); + + ret = q6v5_clk_enable(qproc->dev, qproc->reset_clks, + qproc->reset_clk_count); if (ret) { - dev_err(qproc->dev, "failed to deassert mss restart\n"); + dev_err(qproc->dev, "failed to enable reset clocks\n"); goto disable_vdd; } + ret = q6v5_reset_deassert(qproc); + if (ret) { + dev_err(qproc->dev, "failed to deassert mss restart\n"); + goto disable_reset_clks; + } + ret = q6v5_clk_enable(qproc->dev, qproc->active_clks, qproc->active_clk_count); if (ret) { @@ -761,13 +844,11 @@ static int q6v5_start(struct rproc *rproc) } /* Assign MBA image access in DDR to q6 */ - xfermemop_ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, - qproc->mba_phys, - qproc->mba_size); - if (xfermemop_ret) { + ret = q6v5_xfer_mem_ownership(qproc, &qproc->mba_perm, true, + qproc->mba_phys, qproc->mba_size); + if (ret) { dev_err(qproc->dev, - "assigning Q6 access to mba memory failed: %d\n", - xfermemop_ret); + "assigning Q6 access to mba memory failed: %d\n", ret); goto disable_active_clks; } @@ -810,11 +891,6 @@ static int q6v5_start(struct rproc *rproc) "Failed to reclaim mba buffer system may become unstable\n"); qproc->running = true; - q6v5_clk_disable(qproc->dev, qproc->proxy_clks, - qproc->proxy_clk_count); - q6v5_regulator_disable(qproc, qproc->proxy_regs, - qproc->proxy_reg_count); - return 0; reclaim_mpss: @@ -842,7 +918,10 @@ disable_active_clks: qproc->active_clk_count); assert_reset: - reset_control_assert(qproc->mss_restart); + q6v5_reset_assert(qproc); +disable_reset_clks: + q6v5_clk_disable(qproc->dev, qproc->reset_clks, + qproc->reset_clk_count); disable_vdd: q6v5_regulator_disable(qproc, qproc->active_regs, qproc->active_reg_count); @@ -853,6 +932,9 @@ disable_proxy_reg: q6v5_regulator_disable(qproc, qproc->proxy_regs, qproc->proxy_reg_count); +disable_irqs: + disable_irq(qproc->handover_irq); + return ret; } @@ -892,7 +974,19 @@ static int q6v5_stop(struct rproc *rproc) qproc->mpss_phys, qproc->mpss_size); WARN_ON(ret); - reset_control_assert(qproc->mss_restart); + q6v5_reset_assert(qproc); + + disable_irq(qproc->handover_irq); + + if (!qproc->proxy_unvoted) { + q6v5_clk_disable(qproc->dev, qproc->proxy_clks, + qproc->proxy_clk_count); + q6v5_regulator_disable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); + } + + q6v5_clk_disable(qproc->dev, qproc->reset_clks, + qproc->reset_clk_count); q6v5_clk_disable(qproc->dev, qproc->active_clks, qproc->active_clk_count); q6v5_regulator_disable(qproc, qproc->active_regs, @@ -960,7 +1054,7 @@ static irqreturn_t q6v5_fatal_interrupt(int irq, void *dev) return IRQ_HANDLED; } -static irqreturn_t q6v5_handover_interrupt(int irq, void *dev) +static irqreturn_t q6v5_ready_interrupt(int irq, void *dev) { struct q6v5 *qproc = dev; @@ -968,6 +1062,20 @@ static irqreturn_t q6v5_handover_interrupt(int irq, void *dev) return IRQ_HANDLED; } +static irqreturn_t q6v5_handover_interrupt(int irq, void *dev) +{ + struct q6v5 *qproc = dev; + + q6v5_clk_disable(qproc->dev, qproc->proxy_clks, + qproc->proxy_clk_count); + q6v5_regulator_disable(qproc, qproc->proxy_regs, + qproc->proxy_reg_count); + + qproc->proxy_unvoted = true; + + return IRQ_HANDLED; +} + static irqreturn_t q6v5_stop_ack_interrupt(int irq, void *dev) { struct q6v5 *qproc = dev; @@ -1051,22 +1159,23 @@ static int q6v5_request_irq(struct q6v5 *qproc, const char *name, irq_handler_t thread_fn) { + int irq; int ret; - ret = platform_get_irq_byname(pdev, name); - if (ret < 0) { + irq = platform_get_irq_byname(pdev, name); + if (irq < 0) { dev_err(&pdev->dev, "no %s IRQ defined\n", name); - return ret; + return irq; } - ret = devm_request_threaded_irq(&pdev->dev, ret, + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, thread_fn, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "q6v5", qproc); if (ret) dev_err(&pdev->dev, "request %s IRQ failed\n", name); - return ret; + return ret ? : irq; } static int q6v5_alloc_memory_region(struct q6v5 *qproc) @@ -1157,6 +1266,14 @@ static int q6v5_probe(struct platform_device *pdev) } qproc->proxy_clk_count = ret; + ret = q6v5_init_clocks(&pdev->dev, qproc->reset_clks, + desc->reset_clk_names); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get reset clocks.\n"); + goto free_rproc; + } + qproc->reset_clk_count = ret; + ret = q6v5_init_clocks(&pdev->dev, qproc->active_clks, desc->active_clk_names); if (ret < 0) { @@ -1186,6 +1303,7 @@ static int q6v5_probe(struct platform_device *pdev) goto free_rproc; qproc->version = desc->version; + qproc->has_alt_reset = desc->has_alt_reset; qproc->need_mem_protection = desc->need_mem_protection; ret = q6v5_request_irq(qproc, pdev, "wdog", q6v5_wdog_interrupt); if (ret < 0) @@ -1195,9 +1313,15 @@ static int q6v5_probe(struct platform_device *pdev) if (ret < 0) goto free_rproc; + ret = q6v5_request_irq(qproc, pdev, "ready", q6v5_ready_interrupt); + if (ret < 0) + goto free_rproc; + ret = q6v5_request_irq(qproc, pdev, "handover", q6v5_handover_interrupt); if (ret < 0) goto free_rproc; + qproc->handover_irq = ret; + disable_irq(qproc->handover_irq); ret = q6v5_request_irq(qproc, pdev, "stop-ack", q6v5_stop_ack_interrupt); if (ret < 0) @@ -1210,6 +1334,7 @@ static int q6v5_probe(struct platform_device *pdev) } qproc->mpss_perm = BIT(QCOM_SCM_VMID_HLOS); qproc->mba_perm = BIT(QCOM_SCM_VMID_HLOS); + qcom_add_glink_subdev(rproc, &qproc->glink_subdev); qcom_add_smd_subdev(rproc, &qproc->smd_subdev); qcom_add_ssr_subdev(rproc, &qproc->ssr_subdev, "mpss"); qproc->sysmon = qcom_add_sysmon_subdev(rproc, "modem", 0x12); @@ -1233,6 +1358,7 @@ static int q6v5_remove(struct platform_device *pdev) rproc_del(qproc->rproc); qcom_remove_sysmon_subdev(qproc->sysmon); + qcom_remove_glink_subdev(qproc->rproc, &qproc->glink_subdev); qcom_remove_smd_subdev(qproc->rproc, &qproc->smd_subdev); qcom_remove_ssr_subdev(qproc->rproc, &qproc->ssr_subdev); rproc_free(qproc->rproc); @@ -1240,6 +1366,31 @@ static int q6v5_remove(struct platform_device *pdev) return 0; } +static const struct rproc_hexagon_res sdm845_mss = { + .hexagon_mba_image = "mba.mbn", + .proxy_clk_names = (char*[]){ + "xo", + "axis2", + "prng", + NULL + }, + .reset_clk_names = (char*[]){ + "iface", + "snoc_axi", + NULL + }, + .active_clk_names = (char*[]){ + "bus", + "mem", + "gpll0_mss", + "mnoc_axi", + NULL + }, + .need_mem_protection = true, + .has_alt_reset = true, + .version = MSS_SDM845, +}; + static const struct rproc_hexagon_res msm8996_mss = { .hexagon_mba_image = "mba.mbn", .proxy_clk_names = (char*[]){ @@ -1255,6 +1406,7 @@ static const struct rproc_hexagon_res msm8996_mss = { NULL }, .need_mem_protection = true, + .has_alt_reset = false, .version = MSS_MSM8996, }; @@ -1286,6 +1438,7 @@ static const struct rproc_hexagon_res msm8916_mss = { NULL }, .need_mem_protection = false, + .has_alt_reset = false, .version = MSS_MSM8916, }; @@ -1325,6 +1478,7 @@ static const struct rproc_hexagon_res msm8974_mss = { NULL }, .need_mem_protection = false, + .has_alt_reset = false, .version = MSS_MSM8974, }; @@ -1333,6 +1487,7 @@ static const struct of_device_id q6v5_of_match[] = { { .compatible = "qcom,msm8916-mss-pil", .data = &msm8916_mss}, { .compatible = "qcom,msm8974-mss-pil", .data = &msm8974_mss}, { .compatible = "qcom,msm8996-mss-pil", .data = &msm8996_mss}, + { .compatible = "qcom,sdm845-mss-pil", .data = &sdm845_mss}, { }, }; MODULE_DEVICE_TABLE(of, q6v5_of_match); diff --git a/drivers/reset/reset-uniphier.c b/drivers/reset/reset-uniphier.c index ac18f2f..e9030ff 100644 --- a/drivers/reset/reset-uniphier.c +++ b/drivers/reset/reset-uniphier.c @@ -63,6 +63,9 @@ static const struct uniphier_reset_data uniphier_pro4_sys_reset_data[] = { UNIPHIER_RESETX(12, 0x2000, 6), /* GIO (Ether, SATA, USB3) */ UNIPHIER_RESETX(14, 0x2000, 17), /* USB30 */ UNIPHIER_RESETX(15, 0x2004, 17), /* USB31 */ + UNIPHIER_RESETX(28, 0x2000, 18), /* SATA0 */ + UNIPHIER_RESETX(29, 0x2004, 18), /* SATA1 */ + UNIPHIER_RESETX(30, 0x2000, 19), /* SATA-PHY */ UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */ UNIPHIER_RESET_END, }; @@ -73,6 +76,7 @@ static const struct uniphier_reset_data uniphier_pro5_sys_reset_data[] = { UNIPHIER_RESETX(12, 0x2000, 6), /* GIO (PCIe, USB3) */ UNIPHIER_RESETX(14, 0x2000, 17), /* USB30 */ UNIPHIER_RESETX(15, 0x2004, 17), /* USB31 */ + UNIPHIER_RESETX(24, 0x2008, 2), /* PCIe */ UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */ UNIPHIER_RESET_END, }; @@ -89,7 +93,7 @@ static const struct uniphier_reset_data uniphier_pxs2_sys_reset_data[] = { UNIPHIER_RESETX(20, 0x2014, 5), /* USB31-PHY0 */ UNIPHIER_RESETX(21, 0x2014, 1), /* USB31-PHY1 */ UNIPHIER_RESETX(28, 0x2014, 12), /* SATA */ - UNIPHIER_RESET(29, 0x2014, 8), /* SATA-PHY (active high) */ + UNIPHIER_RESET(30, 0x2014, 8), /* SATA-PHY (active high) */ UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */ UNIPHIER_RESET_END, }; @@ -99,6 +103,7 @@ static const struct uniphier_reset_data uniphier_ld11_sys_reset_data[] = { UNIPHIER_RESETX(4, 0x200c, 2), /* eMMC */ UNIPHIER_RESETX(6, 0x200c, 6), /* Ether */ UNIPHIER_RESETX(8, 0x200c, 8), /* STDMAC (HSC, MIO) */ + UNIPHIER_RESETX(9, 0x200c, 9), /* HSC */ UNIPHIER_RESETX(40, 0x2008, 0), /* AIO */ UNIPHIER_RESETX(41, 0x2008, 1), /* EVEA */ UNIPHIER_RESETX(42, 0x2010, 2), /* EXIV */ @@ -110,11 +115,13 @@ static const struct uniphier_reset_data uniphier_ld20_sys_reset_data[] = { UNIPHIER_RESETX(4, 0x200c, 2), /* eMMC */ UNIPHIER_RESETX(6, 0x200c, 6), /* Ether */ UNIPHIER_RESETX(8, 0x200c, 8), /* STDMAC (HSC) */ + UNIPHIER_RESETX(9, 0x200c, 9), /* HSC */ UNIPHIER_RESETX(14, 0x200c, 5), /* USB30 */ UNIPHIER_RESETX(16, 0x200c, 12), /* USB30-PHY0 */ UNIPHIER_RESETX(17, 0x200c, 13), /* USB30-PHY1 */ UNIPHIER_RESETX(18, 0x200c, 14), /* USB30-PHY2 */ UNIPHIER_RESETX(19, 0x200c, 15), /* USB30-PHY3 */ + UNIPHIER_RESETX(24, 0x200c, 4), /* PCIe */ UNIPHIER_RESETX(40, 0x2008, 0), /* AIO */ UNIPHIER_RESETX(41, 0x2008, 1), /* EVEA */ UNIPHIER_RESETX(42, 0x2010, 2), /* EXIV */ @@ -134,6 +141,10 @@ static const struct uniphier_reset_data uniphier_pxs3_sys_reset_data[] = { UNIPHIER_RESETX(18, 0x200c, 20), /* USB30-PHY2 */ UNIPHIER_RESETX(20, 0x200c, 17), /* USB31-PHY0 */ UNIPHIER_RESETX(21, 0x200c, 19), /* USB31-PHY1 */ + UNIPHIER_RESETX(24, 0x200c, 3), /* PCIe */ + UNIPHIER_RESETX(28, 0x200c, 7), /* SATA0 */ + UNIPHIER_RESETX(29, 0x200c, 8), /* SATA1 */ + UNIPHIER_RESETX(30, 0x200c, 21), /* SATA-PHY */ UNIPHIER_RESET_END, }; diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig index 65a9f6b..d0322b4 100644 --- a/drivers/rpmsg/Kconfig +++ b/drivers/rpmsg/Kconfig @@ -1,3 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + menu "Rpmsg drivers" # RPMSG always gets selected by whoever wants it @@ -39,6 +41,7 @@ config RPMSG_QCOM_GLINK_SMEM config RPMSG_QCOM_SMD tristate "Qualcomm Shared Memory Driver (SMD)" + depends on MAILBOX depends on QCOM_SMEM select RPMSG help diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c index 768ef54..f505f58 100644 --- a/drivers/rpmsg/qcom_glink_native.c +++ b/drivers/rpmsg/qcom_glink_native.c @@ -1,14 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2016-2017, Linaro Ltd - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/idr.h> diff --git a/drivers/rpmsg/qcom_glink_native.h b/drivers/rpmsg/qcom_glink_native.h index 0cae8a8..624184f 100644 --- a/drivers/rpmsg/qcom_glink_native.h +++ b/drivers/rpmsg/qcom_glink_native.h @@ -1,14 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2016-2017, Linaro Ltd - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #ifndef __QCOM_GLINK_NATIVE_H__ diff --git a/drivers/rpmsg/qcom_glink_rpm.c b/drivers/rpmsg/qcom_glink_rpm.c index 69b25d1..f64f45d 100644 --- a/drivers/rpmsg/qcom_glink_rpm.c +++ b/drivers/rpmsg/qcom_glink_rpm.c @@ -1,14 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2016-2017, Linaro Ltd - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/idr.h> diff --git a/drivers/rpmsg/qcom_glink_smem.c b/drivers/rpmsg/qcom_glink_smem.c index 3fa9d43..2b5cf27 100644 --- a/drivers/rpmsg/qcom_glink_smem.c +++ b/drivers/rpmsg/qcom_glink_smem.c @@ -1,14 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2016, Linaro Ltd - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/io.h> diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c index 5ce9bf7..6437bbe 100644 --- a/drivers/rpmsg/qcom_smd.c +++ b/drivers/rpmsg/qcom_smd.c @@ -1,19 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2015, Sony Mobile Communications AB. * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/interrupt.h> #include <linux/io.h> +#include <linux/mailbox_client.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/of_irq.h> @@ -107,6 +100,8 @@ static const struct { * @ipc_regmap: regmap handle holding the outgoing ipc register * @ipc_offset: offset within @ipc_regmap of the register for ipc * @ipc_bit: bit in the register at @ipc_offset of @ipc_regmap + * @mbox_client: mailbox client handle + * @mbox_chan: apcs ipc mailbox channel handle * @channels: list of all channels detected on this edge * @channels_lock: guard for modifications of @channels * @allocated: array of bitmaps representing already allocated channels @@ -129,6 +124,9 @@ struct qcom_smd_edge { int ipc_offset; int ipc_bit; + struct mbox_client mbox_client; + struct mbox_chan *mbox_chan; + struct list_head channels; spinlock_t channels_lock; @@ -366,7 +364,17 @@ static void qcom_smd_signal_channel(struct qcom_smd_channel *channel) { struct qcom_smd_edge *edge = channel->edge; - regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit)); + if (edge->mbox_chan) { + /* + * We can ignore a failing mbox_send_message() as the only + * possible cause is that the FIFO in the framework is full of + * other writes to the same bit. + */ + mbox_send_message(edge->mbox_chan, NULL); + mbox_client_txdone(edge->mbox_chan, 0); + } else { + regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit)); + } } /* @@ -1100,12 +1108,12 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed void *info; int ret; - channel = devm_kzalloc(&edge->dev, sizeof(*channel), GFP_KERNEL); + channel = kzalloc(sizeof(*channel), GFP_KERNEL); if (!channel) return ERR_PTR(-ENOMEM); channel->edge = edge; - channel->name = devm_kstrdup(&edge->dev, name, GFP_KERNEL); + channel->name = kstrdup(name, GFP_KERNEL); if (!channel->name) return ERR_PTR(-ENOMEM); @@ -1156,8 +1164,8 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed return channel; free_name_and_channel: - devm_kfree(&edge->dev, channel->name); - devm_kfree(&edge->dev, channel); + kfree(channel->name); + kfree(channel); return ERR_PTR(ret); } @@ -1326,27 +1334,37 @@ static int qcom_smd_parse_edge(struct device *dev, key = "qcom,remote-pid"; of_property_read_u32(node, key, &edge->remote_pid); - syscon_np = of_parse_phandle(node, "qcom,ipc", 0); - if (!syscon_np) { - dev_err(dev, "no qcom,ipc node\n"); - return -ENODEV; - } + edge->mbox_client.dev = dev; + edge->mbox_client.knows_txdone = true; + edge->mbox_chan = mbox_request_channel(&edge->mbox_client, 0); + if (IS_ERR(edge->mbox_chan)) { + if (PTR_ERR(edge->mbox_chan) != -ENODEV) + return PTR_ERR(edge->mbox_chan); - edge->ipc_regmap = syscon_node_to_regmap(syscon_np); - if (IS_ERR(edge->ipc_regmap)) - return PTR_ERR(edge->ipc_regmap); + edge->mbox_chan = NULL; - key = "qcom,ipc"; - ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset); - if (ret < 0) { - dev_err(dev, "no offset in %s\n", key); - return -EINVAL; - } + syscon_np = of_parse_phandle(node, "qcom,ipc", 0); + if (!syscon_np) { + dev_err(dev, "no qcom,ipc node\n"); + return -ENODEV; + } - ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit); - if (ret < 0) { - dev_err(dev, "no bit in %s\n", key); - return -EINVAL; + edge->ipc_regmap = syscon_node_to_regmap(syscon_np); + if (IS_ERR(edge->ipc_regmap)) + return PTR_ERR(edge->ipc_regmap); + + key = "qcom,ipc"; + ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset); + if (ret < 0) { + dev_err(dev, "no offset in %s\n", key); + return -EINVAL; + } + + ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit); + if (ret < 0) { + dev_err(dev, "no bit in %s\n", key); + return -EINVAL; + } } ret = of_property_read_string(node, "label", &edge->name); @@ -1378,13 +1396,13 @@ static int qcom_smd_parse_edge(struct device *dev, */ static void qcom_smd_edge_release(struct device *dev) { - struct qcom_smd_channel *channel; + struct qcom_smd_channel *channel, *tmp; struct qcom_smd_edge *edge = to_smd_edge(dev); - list_for_each_entry(channel, &edge->channels, list) { - SET_RX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); - SET_RX_CHANNEL_INFO(channel, head, 0); - SET_RX_CHANNEL_INFO(channel, tail, 0); + list_for_each_entry_safe(channel, tmp, &edge->channels, list) { + list_del(&channel->list); + kfree(channel->name); + kfree(channel); } kfree(edge); @@ -1453,6 +1471,9 @@ struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, return edge; unregister_dev: + if (!IS_ERR_OR_NULL(edge->mbox_chan)) + mbox_free_channel(edge->mbox_chan); + device_unregister(&edge->dev); return ERR_PTR(ret); } @@ -1481,6 +1502,7 @@ int qcom_smd_unregister_edge(struct qcom_smd_edge *edge) if (ret) dev_warn(&edge->dev, "can't remove smd device: %d\n", ret); + mbox_free_channel(edge->mbox_chan); device_unregister(&edge->dev); return 0; diff --git a/drivers/rpmsg/rpmsg_char.c b/drivers/rpmsg/rpmsg_char.c index 1efdf9f..76a4477 100644 --- a/drivers/rpmsg/rpmsg_char.c +++ b/drivers/rpmsg/rpmsg_char.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2016, Linaro Ltd. * Copyright (c) 2012, Michal Simek <monstr@monstr.eu> @@ -7,15 +8,6 @@ * * Based on rpmsg performance statistics driver by Michal Simek, which in turn * was based on TI & Google OMX rpmsg driver. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/cdev.h> #include <linux/device.h> diff --git a/drivers/rpmsg/rpmsg_core.c b/drivers/rpmsg/rpmsg_core.c index 920a02f..b714a54 100644 --- a/drivers/rpmsg/rpmsg_core.c +++ b/drivers/rpmsg/rpmsg_core.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * remote processor messaging bus * @@ -6,15 +7,6 @@ * * Ohad Ben-Cohen <ohad@wizery.com> * Brian Swetland <swetland@google.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) "%s: " fmt, __func__ @@ -333,11 +325,49 @@ field##_show(struct device *dev, \ } \ static DEVICE_ATTR_RO(field); +#define rpmsg_string_attr(field, member) \ +static ssize_t \ +field##_store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t sz) \ +{ \ + struct rpmsg_device *rpdev = to_rpmsg_device(dev); \ + char *new, *old; \ + \ + new = kstrndup(buf, sz, GFP_KERNEL); \ + if (!new) \ + return -ENOMEM; \ + new[strcspn(new, "\n")] = '\0'; \ + \ + device_lock(dev); \ + old = rpdev->member; \ + if (strlen(new)) { \ + rpdev->member = new; \ + } else { \ + kfree(new); \ + rpdev->member = NULL; \ + } \ + device_unlock(dev); \ + \ + kfree(old); \ + \ + return sz; \ +} \ +static ssize_t \ +field##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct rpmsg_device *rpdev = to_rpmsg_device(dev); \ + \ + return sprintf(buf, "%s\n", rpdev->member); \ +} \ +static DEVICE_ATTR_RW(field) + /* for more info, see Documentation/ABI/testing/sysfs-bus-rpmsg */ rpmsg_show_attr(name, id.name, "%s\n"); rpmsg_show_attr(src, src, "0x%x\n"); rpmsg_show_attr(dst, dst, "0x%x\n"); rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n"); +rpmsg_string_attr(driver_override, driver_override); static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -359,6 +389,7 @@ static struct attribute *rpmsg_dev_attrs[] = { &dev_attr_dst.attr, &dev_attr_src.attr, &dev_attr_announce.attr, + &dev_attr_driver_override.attr, NULL, }; ATTRIBUTE_GROUPS(rpmsg_dev); diff --git a/drivers/rpmsg/rpmsg_internal.h b/drivers/rpmsg/rpmsg_internal.h index 685aa70..0d791c3 100644 --- a/drivers/rpmsg/rpmsg_internal.h +++ b/drivers/rpmsg/rpmsg_internal.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * remote processor messaging bus internals * @@ -6,15 +7,6 @@ * * Ohad Ben-Cohen <ohad@wizery.com> * Brian Swetland <swetland@google.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #ifndef __RPMSG_INTERNAL_H__ diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c index 82b8300..664f957 100644 --- a/drivers/rpmsg/virtio_rpmsg_bus.c +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Virtio-based remote processor messaging bus * @@ -6,15 +7,6 @@ * * Ohad Ben-Cohen <ohad@wizery.com> * Brian Swetland <swetland@google.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) "%s: " fmt, __func__ diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 4052357..113e884 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -14,7 +14,7 @@ obj-$(CONFIG_ARCH_MXC) += imx/ obj-$(CONFIG_SOC_XWAY) += lantiq/ obj-y += mediatek/ obj-$(CONFIG_ARCH_MESON) += amlogic/ -obj-$(CONFIG_ARCH_QCOM) += qcom/ +obj-y += qcom/ obj-y += renesas/ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_SOC_SAMSUNG) += samsung/ diff --git a/drivers/soc/imx/gpc.c b/drivers/soc/imx/gpc.c index c4d35f3..32f0748 100644 --- a/drivers/soc/imx/gpc.c +++ b/drivers/soc/imx/gpc.c @@ -443,17 +443,25 @@ static int imx_gpc_probe(struct platform_device *pdev) if (domain_index >= of_id_data->num_domains) continue; - domain = &imx_gpc_domains[domain_index]; - domain->regmap = regmap; - domain->ipg_rate_mhz = ipg_rate_mhz; - pd_pdev = platform_device_alloc("imx-pgc-power-domain", domain_index); if (!pd_pdev) { of_node_put(np); return -ENOMEM; } - pd_pdev->dev.platform_data = domain; + + ret = platform_device_add_data(pd_pdev, + &imx_gpc_domains[domain_index], + sizeof(imx_gpc_domains[domain_index])); + if (ret) { + platform_device_put(pd_pdev); + of_node_put(np); + return ret; + } + domain = pd_pdev->dev.platform_data; + domain->regmap = regmap; + domain->ipg_rate_mhz = ipg_rate_mhz; + pd_pdev->dev.parent = &pdev->dev; pd_pdev->dev.of_node = np; diff --git a/drivers/soc/imx/gpcv2.c b/drivers/soc/imx/gpcv2.c index afc7ecc..f4e3bd4 100644 --- a/drivers/soc/imx/gpcv2.c +++ b/drivers/soc/imx/gpcv2.c @@ -155,7 +155,7 @@ static int imx7_gpc_pu_pgc_sw_pdn_req(struct generic_pm_domain *genpd) return imx7_gpc_pu_pgc_sw_pxx_req(genpd, false); } -static struct imx7_pgc_domain imx7_pgc_domains[] = { +static const struct imx7_pgc_domain imx7_pgc_domains[] = { [IMX7_POWER_DOMAIN_MIPI_PHY] = { .genpd = { .name = "mipi-phy", @@ -321,11 +321,6 @@ static int imx_gpcv2_probe(struct platform_device *pdev) continue; } - domain = &imx7_pgc_domains[domain_index]; - domain->regmap = regmap; - domain->genpd.power_on = imx7_gpc_pu_pgc_sw_pup_req; - domain->genpd.power_off = imx7_gpc_pu_pgc_sw_pdn_req; - pd_pdev = platform_device_alloc("imx7-pgc-domain", domain_index); if (!pd_pdev) { @@ -334,7 +329,20 @@ static int imx_gpcv2_probe(struct platform_device *pdev) return -ENOMEM; } - pd_pdev->dev.platform_data = domain; + ret = platform_device_add_data(pd_pdev, + &imx7_pgc_domains[domain_index], + sizeof(imx7_pgc_domains[domain_index])); + if (ret) { + platform_device_put(pd_pdev); + of_node_put(np); + return ret; + } + + domain = pd_pdev->dev.platform_data; + domain->regmap = regmap; + domain->genpd.power_on = imx7_gpc_pu_pgc_sw_pup_req; + domain->genpd.power_off = imx7_gpc_pu_pgc_sw_pdn_req; + pd_pdev->dev.parent = dev; pd_pdev->dev.of_node = np; diff --git a/drivers/soc/mediatek/mtk-infracfg.c b/drivers/soc/mediatek/mtk-infracfg.c index 8c310de..958861c 100644 --- a/drivers/soc/mediatek/mtk-infracfg.c +++ b/drivers/soc/mediatek/mtk-infracfg.c @@ -17,6 +17,9 @@ #include <linux/soc/mediatek/infracfg.h> #include <asm/processor.h> +#define MTK_POLL_DELAY_US 10 +#define MTK_POLL_TIMEOUT (jiffies_to_usecs(HZ)) + #define INFRA_TOPAXI_PROTECTEN 0x0220 #define INFRA_TOPAXI_PROTECTSTA1 0x0228 #define INFRA_TOPAXI_PROTECTEN_SET 0x0260 @@ -37,7 +40,6 @@ int mtk_infracfg_set_bus_protection(struct regmap *infracfg, u32 mask, bool reg_update) { - unsigned long expired; u32 val; int ret; @@ -47,22 +49,11 @@ int mtk_infracfg_set_bus_protection(struct regmap *infracfg, u32 mask, else regmap_write(infracfg, INFRA_TOPAXI_PROTECTEN_SET, mask); - expired = jiffies + HZ; - - while (1) { - ret = regmap_read(infracfg, INFRA_TOPAXI_PROTECTSTA1, &val); - if (ret) - return ret; - - if ((val & mask) == mask) - break; + ret = regmap_read_poll_timeout(infracfg, INFRA_TOPAXI_PROTECTSTA1, + val, (val & mask) == mask, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); - cpu_relax(); - if (time_after(jiffies, expired)) - return -EIO; - } - - return 0; + return ret; } /** @@ -80,30 +71,17 @@ int mtk_infracfg_set_bus_protection(struct regmap *infracfg, u32 mask, int mtk_infracfg_clear_bus_protection(struct regmap *infracfg, u32 mask, bool reg_update) { - unsigned long expired; int ret; + u32 val; if (reg_update) regmap_update_bits(infracfg, INFRA_TOPAXI_PROTECTEN, mask, 0); else regmap_write(infracfg, INFRA_TOPAXI_PROTECTEN_CLR, mask); - expired = jiffies + HZ; - - while (1) { - u32 val; - - ret = regmap_read(infracfg, INFRA_TOPAXI_PROTECTSTA1, &val); - if (ret) - return ret; - - if (!(val & mask)) - break; - - cpu_relax(); - if (time_after(jiffies, expired)) - return -EIO; - } + ret = regmap_read_poll_timeout(infracfg, INFRA_TOPAXI_PROTECTSTA1, + val, !(val & mask), + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); - return 0; + return ret; } diff --git a/drivers/soc/mediatek/mtk-pmic-wrap.c b/drivers/soc/mediatek/mtk-pmic-wrap.c index e9e054a..2afae64 100644 --- a/drivers/soc/mediatek/mtk-pmic-wrap.c +++ b/drivers/soc/mediatek/mtk-pmic-wrap.c @@ -1458,19 +1458,12 @@ static int pwrap_probe(struct platform_device *pdev) int ret, irq; struct pmic_wrapper *wrp; struct device_node *np = pdev->dev.of_node; - const struct of_device_id *of_id = - of_match_device(of_pwrap_match_tbl, &pdev->dev); const struct of_device_id *of_slave_id = NULL; struct resource *res; - if (!of_id) { - dev_err(&pdev->dev, "Error: No device match found\n"); - return -ENODEV; - } + if (np->child) + of_slave_id = of_match_node(of_slave_match_tbl, np->child); - if (pdev->dev.of_node->child) - of_slave_id = of_match_node(of_slave_match_tbl, - pdev->dev.of_node->child); if (!of_slave_id) { dev_dbg(&pdev->dev, "slave pmic should be defined in dts\n"); return -EINVAL; @@ -1482,7 +1475,7 @@ static int pwrap_probe(struct platform_device *pdev) platform_set_drvdata(pdev, wrp); - wrp->master = of_id->data; + wrp->master = of_device_get_match_data(&pdev->dev); wrp->slave = of_slave_id->data; wrp->dev = &pdev->dev; diff --git a/drivers/soc/mediatek/mtk-scpsys.c b/drivers/soc/mediatek/mtk-scpsys.c index d041d98..5b24bb4 100644 --- a/drivers/soc/mediatek/mtk-scpsys.c +++ b/drivers/soc/mediatek/mtk-scpsys.c @@ -13,6 +13,7 @@ #include <linux/clk.h> #include <linux/init.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/mfd/syscon.h> #include <linux/of_device.h> #include <linux/platform_device.h> @@ -27,6 +28,13 @@ #include <dt-bindings/power/mt7623a-power.h> #include <dt-bindings/power/mt8173-power.h> +#define MTK_POLL_DELAY_US 10 +#define MTK_POLL_TIMEOUT (jiffies_to_usecs(HZ)) + +#define MTK_SCPD_ACTIVE_WAKEUP BIT(0) +#define MTK_SCPD_FWAIT_SRAM BIT(1) +#define MTK_SCPD_CAPS(_scpd, _x) ((_scpd)->data->caps & (_x)) + #define SPM_VDE_PWR_CON 0x0210 #define SPM_MFG_PWR_CON 0x0214 #define SPM_VEN_PWR_CON 0x0230 @@ -116,7 +124,7 @@ struct scp_domain_data { u32 sram_pdn_ack_bits; u32 bus_prot_mask; enum clk_id clk_id[MAX_CLKS]; - bool active_wakeup; + u8 caps; }; struct scp; @@ -184,12 +192,10 @@ static int scpsys_power_on(struct generic_pm_domain *genpd) { struct scp_domain *scpd = container_of(genpd, struct scp_domain, genpd); struct scp *scp = scpd->scp; - unsigned long timeout; - bool expired; void __iomem *ctl_addr = scp->base + scpd->data->ctl_offs; - u32 sram_pdn_ack = scpd->data->sram_pdn_ack_bits; + u32 pdn_ack = scpd->data->sram_pdn_ack_bits; u32 val; - int ret; + int ret, tmp; int i; if (scpd->supply) { @@ -215,23 +221,10 @@ static int scpsys_power_on(struct generic_pm_domain *genpd) writel(val, ctl_addr); /* wait until PWR_ACK = 1 */ - timeout = jiffies + HZ; - expired = false; - while (1) { - ret = scpsys_domain_is_on(scpd); - if (ret > 0) - break; - - if (expired) { - ret = -ETIMEDOUT; - goto err_pwr_ack; - } - - cpu_relax(); - - if (time_after(jiffies, timeout)) - expired = true; - } + ret = readx_poll_timeout(scpsys_domain_is_on, scpd, tmp, tmp > 0, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret < 0) + goto err_pwr_ack; val &= ~PWR_CLK_DIS_BIT; writel(val, ctl_addr); @@ -245,20 +238,20 @@ static int scpsys_power_on(struct generic_pm_domain *genpd) val &= ~scpd->data->sram_pdn_bits; writel(val, ctl_addr); - /* wait until SRAM_PDN_ACK all 0 */ - timeout = jiffies + HZ; - expired = false; - while (sram_pdn_ack && (readl(ctl_addr) & sram_pdn_ack)) { + /* Either wait until SRAM_PDN_ACK all 0 or have a force wait */ + if (MTK_SCPD_CAPS(scpd, MTK_SCPD_FWAIT_SRAM)) { + /* + * Currently, MTK_SCPD_FWAIT_SRAM is necessary only for + * MT7622_POWER_DOMAIN_WB and thus just a trivial setup is + * applied here. + */ + usleep_range(12000, 12100); - if (expired) { - ret = -ETIMEDOUT; + } else { + ret = readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == 0, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret < 0) goto err_pwr_ack; - } - - cpu_relax(); - - if (time_after(jiffies, timeout)) - expired = true; } if (scpd->data->bus_prot_mask) { @@ -289,12 +282,10 @@ static int scpsys_power_off(struct generic_pm_domain *genpd) { struct scp_domain *scpd = container_of(genpd, struct scp_domain, genpd); struct scp *scp = scpd->scp; - unsigned long timeout; - bool expired; void __iomem *ctl_addr = scp->base + scpd->data->ctl_offs; u32 pdn_ack = scpd->data->sram_pdn_ack_bits; u32 val; - int ret; + int ret, tmp; int i; if (scpd->data->bus_prot_mask) { @@ -310,19 +301,10 @@ static int scpsys_power_off(struct generic_pm_domain *genpd) writel(val, ctl_addr); /* wait until SRAM_PDN_ACK all 1 */ - timeout = jiffies + HZ; - expired = false; - while (pdn_ack && (readl(ctl_addr) & pdn_ack) != pdn_ack) { - if (expired) { - ret = -ETIMEDOUT; - goto out; - } - - cpu_relax(); - - if (time_after(jiffies, timeout)) - expired = true; - } + ret = readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == pdn_ack, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret < 0) + goto out; val |= PWR_ISO_BIT; writel(val, ctl_addr); @@ -340,23 +322,10 @@ static int scpsys_power_off(struct generic_pm_domain *genpd) writel(val, ctl_addr); /* wait until PWR_ACK = 0 */ - timeout = jiffies + HZ; - expired = false; - while (1) { - ret = scpsys_domain_is_on(scpd); - if (ret == 0) - break; - - if (expired) { - ret = -ETIMEDOUT; - goto out; - } - - cpu_relax(); - - if (time_after(jiffies, timeout)) - expired = true; - } + ret = readx_poll_timeout(scpsys_domain_is_on, scpd, tmp, tmp == 0, + MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT); + if (ret < 0) + goto out; for (i = 0; i < MAX_CLKS && scpd->clk[i]; i++) clk_disable_unprepare(scpd->clk[i]); @@ -469,7 +438,7 @@ static struct scp *init_scp(struct platform_device *pdev, genpd->name = data->name; genpd->power_off = scpsys_power_off; genpd->power_on = scpsys_power_on; - if (scpd->data->active_wakeup) + if (MTK_SCPD_CAPS(scpd, MTK_SCPD_ACTIVE_WAKEUP)) genpd->flags |= GENPD_FLAG_ACTIVE_WAKEUP; } @@ -522,7 +491,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_CONN_M | MT2701_TOP_AXI_PROT_EN_CONN_S, .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_DISP] = { .name = "disp", @@ -531,7 +500,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .sram_pdn_bits = GENMASK(11, 8), .clk_id = {CLK_MM}, .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_MM_M0, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_MFG] = { .name = "mfg", @@ -540,7 +509,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(12, 12), .clk_id = {CLK_MFG}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_VDEC] = { .name = "vdec", @@ -549,7 +518,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(12, 12), .clk_id = {CLK_MM}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_ISP] = { .name = "isp", @@ -558,7 +527,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(13, 12), .clk_id = {CLK_MM}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_BDP] = { .name = "bdp", @@ -566,7 +535,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .ctl_offs = SPM_BDP_PWR_CON, .sram_pdn_bits = GENMASK(11, 8), .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_ETH] = { .name = "eth", @@ -575,7 +544,7 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_ETHIF}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_HIF] = { .name = "hif", @@ -584,14 +553,14 @@ static const struct scp_domain_data scp_domain_data_mt2701[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_ETHIF}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2701_POWER_DOMAIN_IFR_MSC] = { .name = "ifr_msc", .sta_mask = PWR_STATUS_IFR_MSC, .ctl_offs = SPM_IFR_MSC_PWR_CON, .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, }; @@ -606,7 +575,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(8, 8), .sram_pdn_ack_bits = GENMASK(12, 12), .clk_id = {CLK_MM}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_VDEC] = { .name = "vdec", @@ -615,7 +584,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(8, 8), .sram_pdn_ack_bits = GENMASK(12, 12), .clk_id = {CLK_MM, CLK_VDEC}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_VENC] = { .name = "venc", @@ -624,7 +593,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_MM, CLK_VENC, CLK_JPGDEC}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_ISP] = { .name = "isp", @@ -633,7 +602,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(13, 12), .clk_id = {CLK_MM}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_AUDIO] = { .name = "audio", @@ -642,7 +611,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_AUDIO}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_USB] = { .name = "usb", @@ -651,7 +620,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(10, 8), .sram_pdn_ack_bits = GENMASK(14, 12), .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_USB2] = { .name = "usb2", @@ -660,7 +629,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(10, 8), .sram_pdn_ack_bits = GENMASK(14, 12), .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_MFG] = { .name = "mfg", @@ -670,7 +639,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_ack_bits = GENMASK(16, 16), .clk_id = {CLK_MFG}, .bus_prot_mask = BIT(14) | BIT(21) | BIT(23), - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_MFG_SC1] = { .name = "mfg_sc1", @@ -679,7 +648,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(8, 8), .sram_pdn_ack_bits = GENMASK(16, 16), .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_MFG_SC2] = { .name = "mfg_sc2", @@ -688,7 +657,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(8, 8), .sram_pdn_ack_bits = GENMASK(16, 16), .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT2712_POWER_DOMAIN_MFG_SC3] = { .name = "mfg_sc3", @@ -697,7 +666,7 @@ static const struct scp_domain_data scp_domain_data_mt2712[] = { .sram_pdn_bits = GENMASK(8, 8), .sram_pdn_ack_bits = GENMASK(16, 16), .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, }; @@ -797,7 +766,7 @@ static const struct scp_domain_data scp_domain_data_mt7622[] = { .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_NONE}, .bus_prot_mask = MT7622_TOP_AXI_PROT_EN_ETHSYS, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT7622_POWER_DOMAIN_HIF0] = { .name = "hif0", @@ -807,7 +776,7 @@ static const struct scp_domain_data scp_domain_data_mt7622[] = { .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_HIFSEL}, .bus_prot_mask = MT7622_TOP_AXI_PROT_EN_HIF0, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT7622_POWER_DOMAIN_HIF1] = { .name = "hif1", @@ -817,7 +786,7 @@ static const struct scp_domain_data scp_domain_data_mt7622[] = { .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_HIFSEL}, .bus_prot_mask = MT7622_TOP_AXI_PROT_EN_HIF1, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT7622_POWER_DOMAIN_WB] = { .name = "wb", @@ -827,7 +796,7 @@ static const struct scp_domain_data scp_domain_data_mt7622[] = { .sram_pdn_ack_bits = 0, .clk_id = {CLK_NONE}, .bus_prot_mask = MT7622_TOP_AXI_PROT_EN_WB, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP | MTK_SCPD_FWAIT_SRAM, }, }; @@ -843,7 +812,7 @@ static const struct scp_domain_data scp_domain_data_mt7623a[] = { .bus_prot_mask = MT2701_TOP_AXI_PROT_EN_CONN_M | MT2701_TOP_AXI_PROT_EN_CONN_S, .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT7623A_POWER_DOMAIN_ETH] = { .name = "eth", @@ -852,7 +821,7 @@ static const struct scp_domain_data scp_domain_data_mt7623a[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_ETHIF}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT7623A_POWER_DOMAIN_HIF] = { .name = "hif", @@ -861,14 +830,14 @@ static const struct scp_domain_data scp_domain_data_mt7623a[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_ETHIF}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT7623A_POWER_DOMAIN_IFR_MSC] = { .name = "ifr_msc", .sta_mask = PWR_STATUS_IFR_MSC, .ctl_offs = SPM_IFR_MSC_PWR_CON, .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, }; @@ -934,7 +903,7 @@ static const struct scp_domain_data scp_domain_data_mt8173[] = { .sram_pdn_bits = GENMASK(11, 8), .sram_pdn_ack_bits = GENMASK(15, 12), .clk_id = {CLK_NONE}, - .active_wakeup = true, + .caps = MTK_SCPD_ACTIVE_WAKEUP, }, [MT8173_POWER_DOMAIN_MFG_ASYNC] = { .name = "mfg_async", @@ -1067,15 +1036,13 @@ static const struct of_device_id of_scpsys_match_tbl[] = { static int scpsys_probe(struct platform_device *pdev) { - const struct of_device_id *match; const struct scp_subdomain *sd; const struct scp_soc_data *soc; struct scp *scp; struct genpd_onecell_data *pd_data; int i, ret; - match = of_match_device(of_scpsys_match_tbl, &pdev->dev); - soc = (const struct scp_soc_data *)match->data; + soc = of_device_get_match_data(&pdev->dev); scp = init_scp(pdev, soc->domains, soc->num_domains, &soc->regs, soc->bus_prot_reg_update); diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index d053f26..9dc02f3 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -3,6 +3,24 @@ # menu "Qualcomm SoC drivers" +config QCOM_COMMAND_DB + bool "Qualcomm Command DB" + depends on (ARCH_QCOM && OF) || COMPILE_TEST + help + Command DB queries shared memory by key string for shared system + resources. Platform drivers that require to set state of a shared + resource on a RPM-hardened platform must use this database to get + SoC specific identifier and information for the shared resources. + +config QCOM_GENI_SE + tristate "QCOM GENI Serial Engine Driver" + depends on ARCH_QCOM || COMPILE_TEST + help + This driver is used to manage Generic Interface (GENI) firmware based + Qualcomm Technologies, Inc. Universal Peripheral (QUP) Wrapper. This + driver is also used to manage the common aspects of multiple Serial + Engines present in the QUP. + config QCOM_GLINK_SSR tristate "Qualcomm Glink SSR driver" depends on RPMSG diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 39de5de..19dcf95 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_QCOM_GENI_SE) += qcom-geni-se.o +obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c new file mode 100644 index 0000000..a6f6462 --- /dev/null +++ b/drivers/soc/qcom/cmd-db.c @@ -0,0 +1,317 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. */ + +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/of_reserved_mem.h> +#include <linux/platform_device.h> +#include <linux/types.h> + +#include <soc/qcom/cmd-db.h> + +#define NUM_PRIORITY 2 +#define MAX_SLV_ID 8 +#define SLAVE_ID_MASK 0x7 +#define SLAVE_ID_SHIFT 16 + +/** + * struct entry_header: header for each entry in cmddb + * + * @id: resource's identifier + * @priority: unused + * @addr: the address of the resource + * @len: length of the data + * @offset: offset from :@data_offset, start of the data + */ +struct entry_header { + u8 id[8]; + __le32 priority[NUM_PRIORITY]; + __le32 addr; + __le16 len; + __le16 offset; +}; + +/** + * struct rsc_hdr: resource header information + * + * @slv_id: id for the resource + * @header_offset: entry's header at offset from the end of the cmd_db_header + * @data_offset: entry's data at offset from the end of the cmd_db_header + * @cnt: number of entries for HW type + * @version: MSB is major, LSB is minor + * @reserved: reserved for future use. + */ +struct rsc_hdr { + __le16 slv_id; + __le16 header_offset; + __le16 data_offset; + __le16 cnt; + __le16 version; + __le16 reserved[3]; +}; + +/** + * struct cmd_db_header: The DB header information + * + * @version: The cmd db version + * @magic: constant expected in the database + * @header: array of resources + * @checksum: checksum for the header. Unused. + * @reserved: reserved memory + * @data: driver specific data + */ +struct cmd_db_header { + __le32 version; + u8 magic[4]; + struct rsc_hdr header[MAX_SLV_ID]; + __le32 checksum; + __le32 reserved; + u8 data[]; +}; + +/** + * DOC: Description of the Command DB database. + * + * At the start of the command DB memory is the cmd_db_header structure. + * The cmd_db_header holds the version, checksum, magic key as well as an + * array for header for each slave (depicted by the rsc_header). Each h/w + * based accelerator is a 'slave' (shared resource) and has slave id indicating + * the type of accelerator. The rsc_header is the header for such individual + * slaves of a given type. The entries for each of these slaves begin at the + * rsc_hdr.header_offset. In addition each slave could have auxiliary data + * that may be needed by the driver. The data for the slave starts at the + * entry_header.offset to the location pointed to by the rsc_hdr.data_offset. + * + * Drivers have a stringified key to a slave/resource. They can query the slave + * information and get the slave id and the auxiliary data and the length of the + * data. Using this information, they can format the request to be sent to the + * h/w accelerator and request a resource state. + */ + +static const u8 CMD_DB_MAGIC[] = { 0xdb, 0x30, 0x03, 0x0c }; + +static bool cmd_db_magic_matches(const struct cmd_db_header *header) +{ + const u8 *magic = header->magic; + + return memcmp(magic, CMD_DB_MAGIC, ARRAY_SIZE(CMD_DB_MAGIC)) == 0; +} + +static struct cmd_db_header *cmd_db_header; + + +static inline void *rsc_to_entry_header(struct rsc_hdr *hdr) +{ + u16 offset = le16_to_cpu(hdr->header_offset); + + return cmd_db_header->data + offset; +} + +static inline void * +rsc_offset(struct rsc_hdr *hdr, struct entry_header *ent) +{ + u16 offset = le16_to_cpu(hdr->data_offset); + u16 loffset = le16_to_cpu(ent->offset); + + return cmd_db_header->data + offset + loffset; +} + +/** + * cmd_db_ready - Indicates if command DB is available + * + * Return: 0 on success, errno otherwise + */ +int cmd_db_ready(void) +{ + if (cmd_db_header == NULL) + return -EPROBE_DEFER; + else if (!cmd_db_magic_matches(cmd_db_header)) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(cmd_db_ready); + +static int cmd_db_get_header(const char *id, struct entry_header *eh, + struct rsc_hdr *rh) +{ + struct rsc_hdr *rsc_hdr; + struct entry_header *ent; + int ret, i, j; + u8 query[8]; + + ret = cmd_db_ready(); + if (ret) + return ret; + + if (!eh || !rh) + return -EINVAL; + + /* Pad out query string to same length as in DB */ + strncpy(query, id, sizeof(query)); + + for (i = 0; i < MAX_SLV_ID; i++) { + rsc_hdr = &cmd_db_header->header[i]; + if (!rsc_hdr->slv_id) + break; + + ent = rsc_to_entry_header(rsc_hdr); + for (j = 0; j < le16_to_cpu(rsc_hdr->cnt); j++, ent++) { + if (memcmp(ent->id, query, sizeof(ent->id)) == 0) + break; + } + + if (j < le16_to_cpu(rsc_hdr->cnt)) { + memcpy(eh, ent, sizeof(*ent)); + memcpy(rh, rsc_hdr, sizeof(*rh)); + return 0; + } + } + + return -ENODEV; +} + +/** + * cmd_db_read_addr() - Query command db for resource id address. + * + * @id: resource id to query for address + * + * Return: resource address on success, 0 on error + * + * This is used to retrieve resource address based on resource + * id. + */ +u32 cmd_db_read_addr(const char *id) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + + ret = cmd_db_get_header(id, &ent, &rsc_hdr); + + return ret < 0 ? 0 : le32_to_cpu(ent.addr); +} +EXPORT_SYMBOL(cmd_db_read_addr); + +/** + * cmd_db_read_aux_data() - Query command db for aux data. + * + * @id: Resource to retrieve AUX Data on. + * @data: Data buffer to copy returned aux data to. Returns size on NULL + * @len: Caller provides size of data buffer passed in. + * + * Return: size of data on success, errno otherwise + */ +int cmd_db_read_aux_data(const char *id, u8 *data, size_t len) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + u16 ent_len; + + if (!data) + return -EINVAL; + + ret = cmd_db_get_header(id, &ent, &rsc_hdr); + if (ret) + return ret; + + ent_len = le16_to_cpu(ent.len); + if (len < ent_len) + return -EINVAL; + + len = min_t(u16, ent_len, len); + memcpy(data, rsc_offset(&rsc_hdr, &ent), len); + + return len; +} +EXPORT_SYMBOL(cmd_db_read_aux_data); + +/** + * cmd_db_read_aux_data_len - Get the length of the auxiliary data stored in DB. + * + * @id: Resource to retrieve AUX Data. + * + * Return: size on success, 0 on error + */ +size_t cmd_db_read_aux_data_len(const char *id) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + + ret = cmd_db_get_header(id, &ent, &rsc_hdr); + + return ret < 0 ? 0 : le16_to_cpu(ent.len); +} +EXPORT_SYMBOL(cmd_db_read_aux_data_len); + +/** + * cmd_db_read_slave_id - Get the slave ID for a given resource address + * + * @id: Resource id to query the DB for version + * + * Return: cmd_db_hw_type enum on success, CMD_DB_HW_INVALID on error + */ +enum cmd_db_hw_type cmd_db_read_slave_id(const char *id) +{ + int ret; + struct entry_header ent; + struct rsc_hdr rsc_hdr; + u32 addr; + + ret = cmd_db_get_header(id, &ent, &rsc_hdr); + if (ret < 0) + return CMD_DB_HW_INVALID; + + addr = le32_to_cpu(ent.addr); + return (addr >> SLAVE_ID_SHIFT) & SLAVE_ID_MASK; +} +EXPORT_SYMBOL(cmd_db_read_slave_id); + +static int cmd_db_dev_probe(struct platform_device *pdev) +{ + struct reserved_mem *rmem; + int ret = 0; + + rmem = of_reserved_mem_lookup(pdev->dev.of_node); + if (!rmem) { + dev_err(&pdev->dev, "failed to acquire memory region\n"); + return -EINVAL; + } + + cmd_db_header = memremap(rmem->base, rmem->size, MEMREMAP_WB); + if (IS_ERR_OR_NULL(cmd_db_header)) { + ret = PTR_ERR(cmd_db_header); + cmd_db_header = NULL; + return ret; + } + + if (!cmd_db_magic_matches(cmd_db_header)) { + dev_err(&pdev->dev, "Invalid Command DB Magic\n"); + return -EINVAL; + } + + return 0; +} + +static const struct of_device_id cmd_db_match_table[] = { + { .compatible = "qcom,cmd-db" }, + { }, +}; + +static struct platform_driver cmd_db_dev_driver = { + .probe = cmd_db_dev_probe, + .driver = { + .name = "cmd-db", + .of_match_table = cmd_db_match_table, + }, +}; + +static int __init cmd_db_device_init(void) +{ + return platform_driver_register(&cmd_db_dev_driver); +} +arch_initcall(cmd_db_device_init); diff --git a/drivers/soc/qcom/qcom-geni-se.c b/drivers/soc/qcom/qcom-geni-se.c new file mode 100644 index 0000000..feed3db2 --- /dev/null +++ b/drivers/soc/qcom/qcom-geni-se.c @@ -0,0 +1,748 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/qcom-geni-se.h> + +/** + * DOC: Overview + * + * Generic Interface (GENI) Serial Engine (SE) Wrapper driver is introduced + * to manage GENI firmware based Qualcomm Universal Peripheral (QUP) Wrapper + * controller. QUP Wrapper is designed to support various serial bus protocols + * like UART, SPI, I2C, I3C, etc. + */ + +/** + * DOC: Hardware description + * + * GENI based QUP is a highly-flexible and programmable module for supporting + * a wide range of serial interfaces like UART, SPI, I2C, I3C, etc. A single + * QUP module can provide upto 8 serial interfaces, using its internal + * serial engines. The actual configuration is determined by the target + * platform configuration. The protocol supported by each interface is + * determined by the firmware loaded to the serial engine. Each SE consists + * of a DMA Engine and GENI sub modules which enable serial engines to + * support FIFO and DMA modes of operation. + * + * + * +-----------------------------------------+ + * |QUP Wrapper | + * | +----------------------------+ | + * --QUP & SE Clocks--> | Serial Engine N | +-IO------> + * | | ... | | Interface + * <---Clock Perf.----+ +----+-----------------------+ | | + * State Interface | | Serial Engine 1 | | | + * | | | | | + * | | | | | + * <--------AHB-------> | | | | + * | | +----+ | + * | | | | + * | | | | + * <------SE IRQ------+ +----------------------------+ | + * | | + * +-----------------------------------------+ + * + * Figure 1: GENI based QUP Wrapper + * + * The GENI submodules include primary and secondary sequencers which are + * used to drive TX & RX operations. On serial interfaces that operate using + * master-slave model, primary sequencer drives both TX & RX operations. On + * serial interfaces that operate using peer-to-peer model, primary sequencer + * drives TX operation and secondary sequencer drives RX operation. + */ + +/** + * DOC: Software description + * + * GENI SE Wrapper driver is structured into 2 parts: + * + * geni_wrapper represents QUP Wrapper controller. This part of the driver + * manages QUP Wrapper information such as hardware version, clock + * performance table that is common to all the internal serial engines. + * + * geni_se represents serial engine. This part of the driver manages serial + * engine information such as clocks, containing QUP Wrapper, etc. This part + * of driver also supports operations (eg. initialize the concerned serial + * engine, select between FIFO and DMA mode of operation etc.) that are + * common to all the serial engines and are independent of serial interfaces. + */ + +#define MAX_CLK_PERF_LEVEL 32 +#define NUM_AHB_CLKS 2 + +/** + * @struct geni_wrapper - Data structure to represent the QUP Wrapper Core + * @dev: Device pointer of the QUP wrapper core + * @base: Base address of this instance of QUP wrapper core + * @ahb_clks: Handle to the primary & secondary AHB clocks + */ +struct geni_wrapper { + struct device *dev; + void __iomem *base; + struct clk_bulk_data ahb_clks[NUM_AHB_CLKS]; +}; + +#define QUP_HW_VER_REG 0x4 + +/* Common SE registers */ +#define GENI_INIT_CFG_REVISION 0x0 +#define GENI_S_INIT_CFG_REVISION 0x4 +#define GENI_OUTPUT_CTRL 0x24 +#define GENI_CGC_CTRL 0x28 +#define GENI_CLK_CTRL_RO 0x60 +#define GENI_IF_DISABLE_RO 0x64 +#define GENI_FW_S_REVISION_RO 0x6c +#define SE_GENI_BYTE_GRAN 0x254 +#define SE_GENI_TX_PACKING_CFG0 0x260 +#define SE_GENI_TX_PACKING_CFG1 0x264 +#define SE_GENI_RX_PACKING_CFG0 0x284 +#define SE_GENI_RX_PACKING_CFG1 0x288 +#define SE_GENI_M_GP_LENGTH 0x910 +#define SE_GENI_S_GP_LENGTH 0x914 +#define SE_DMA_TX_PTR_L 0xc30 +#define SE_DMA_TX_PTR_H 0xc34 +#define SE_DMA_TX_ATTR 0xc38 +#define SE_DMA_TX_LEN 0xc3c +#define SE_DMA_TX_IRQ_EN 0xc48 +#define SE_DMA_TX_IRQ_EN_SET 0xc4c +#define SE_DMA_TX_IRQ_EN_CLR 0xc50 +#define SE_DMA_TX_LEN_IN 0xc54 +#define SE_DMA_TX_MAX_BURST 0xc5c +#define SE_DMA_RX_PTR_L 0xd30 +#define SE_DMA_RX_PTR_H 0xd34 +#define SE_DMA_RX_ATTR 0xd38 +#define SE_DMA_RX_LEN 0xd3c +#define SE_DMA_RX_IRQ_EN 0xd48 +#define SE_DMA_RX_IRQ_EN_SET 0xd4c +#define SE_DMA_RX_IRQ_EN_CLR 0xd50 +#define SE_DMA_RX_LEN_IN 0xd54 +#define SE_DMA_RX_MAX_BURST 0xd5c +#define SE_DMA_RX_FLUSH 0xd60 +#define SE_GSI_EVENT_EN 0xe18 +#define SE_IRQ_EN 0xe1c +#define SE_DMA_GENERAL_CFG 0xe30 + +/* GENI_OUTPUT_CTRL fields */ +#define DEFAULT_IO_OUTPUT_CTRL_MSK GENMASK(6, 0) + +/* GENI_CGC_CTRL fields */ +#define CFG_AHB_CLK_CGC_ON BIT(0) +#define CFG_AHB_WR_ACLK_CGC_ON BIT(1) +#define DATA_AHB_CLK_CGC_ON BIT(2) +#define SCLK_CGC_ON BIT(3) +#define TX_CLK_CGC_ON BIT(4) +#define RX_CLK_CGC_ON BIT(5) +#define EXT_CLK_CGC_ON BIT(6) +#define PROG_RAM_HCLK_OFF BIT(8) +#define PROG_RAM_SCLK_OFF BIT(9) +#define DEFAULT_CGC_EN GENMASK(6, 0) + +/* SE_GSI_EVENT_EN fields */ +#define DMA_RX_EVENT_EN BIT(0) +#define DMA_TX_EVENT_EN BIT(1) +#define GENI_M_EVENT_EN BIT(2) +#define GENI_S_EVENT_EN BIT(3) + +/* SE_IRQ_EN fields */ +#define DMA_RX_IRQ_EN BIT(0) +#define DMA_TX_IRQ_EN BIT(1) +#define GENI_M_IRQ_EN BIT(2) +#define GENI_S_IRQ_EN BIT(3) + +/* SE_DMA_GENERAL_CFG */ +#define DMA_RX_CLK_CGC_ON BIT(0) +#define DMA_TX_CLK_CGC_ON BIT(1) +#define DMA_AHB_SLV_CFG_ON BIT(2) +#define AHB_SEC_SLV_CLK_CGC_ON BIT(3) +#define DUMMY_RX_NON_BUFFERABLE BIT(4) +#define RX_DMA_ZERO_PADDING_EN BIT(5) +#define RX_DMA_IRQ_DELAY_MSK GENMASK(8, 6) +#define RX_DMA_IRQ_DELAY_SHFT 6 + +/** + * geni_se_get_qup_hw_version() - Read the QUP wrapper Hardware version + * @se: Pointer to the corresponding serial engine. + * + * Return: Hardware Version of the wrapper. + */ +u32 geni_se_get_qup_hw_version(struct geni_se *se) +{ + struct geni_wrapper *wrapper = se->wrapper; + + return readl_relaxed(wrapper->base + QUP_HW_VER_REG); +} +EXPORT_SYMBOL(geni_se_get_qup_hw_version); + +static void geni_se_io_set_mode(void __iomem *base) +{ + u32 val; + + val = readl_relaxed(base + SE_IRQ_EN); + val |= GENI_M_IRQ_EN | GENI_S_IRQ_EN; + val |= DMA_TX_IRQ_EN | DMA_RX_IRQ_EN; + writel_relaxed(val, base + SE_IRQ_EN); + + val = readl_relaxed(base + SE_GENI_DMA_MODE_EN); + val &= ~GENI_DMA_MODE_EN; + writel_relaxed(val, base + SE_GENI_DMA_MODE_EN); + + writel_relaxed(0, base + SE_GSI_EVENT_EN); +} + +static void geni_se_io_init(void __iomem *base) +{ + u32 val; + + val = readl_relaxed(base + GENI_CGC_CTRL); + val |= DEFAULT_CGC_EN; + writel_relaxed(val, base + GENI_CGC_CTRL); + + val = readl_relaxed(base + SE_DMA_GENERAL_CFG); + val |= AHB_SEC_SLV_CLK_CGC_ON | DMA_AHB_SLV_CFG_ON; + val |= DMA_TX_CLK_CGC_ON | DMA_RX_CLK_CGC_ON; + writel_relaxed(val, base + SE_DMA_GENERAL_CFG); + + writel_relaxed(DEFAULT_IO_OUTPUT_CTRL_MSK, base + GENI_OUTPUT_CTRL); + writel_relaxed(FORCE_DEFAULT, base + GENI_FORCE_DEFAULT_REG); +} + +/** + * geni_se_init() - Initialize the GENI serial engine + * @se: Pointer to the concerned serial engine. + * @rx_wm: Receive watermark, in units of FIFO words. + * @rx_rfr_wm: Ready-for-receive watermark, in units of FIFO words. + * + * This function is used to initialize the GENI serial engine, configure + * receive watermark and ready-for-receive watermarks. + */ +void geni_se_init(struct geni_se *se, u32 rx_wm, u32 rx_rfr) +{ + u32 val; + + geni_se_io_init(se->base); + geni_se_io_set_mode(se->base); + + writel_relaxed(rx_wm, se->base + SE_GENI_RX_WATERMARK_REG); + writel_relaxed(rx_rfr, se->base + SE_GENI_RX_RFR_WATERMARK_REG); + + val = readl_relaxed(se->base + SE_GENI_M_IRQ_EN); + val |= M_COMMON_GENI_M_IRQ_EN; + writel_relaxed(val, se->base + SE_GENI_M_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_S_IRQ_EN); + val |= S_COMMON_GENI_S_IRQ_EN; + writel_relaxed(val, se->base + SE_GENI_S_IRQ_EN); +} +EXPORT_SYMBOL(geni_se_init); + +static void geni_se_select_fifo_mode(struct geni_se *se) +{ + u32 proto = geni_se_read_proto(se); + u32 val; + + writel_relaxed(0, se->base + SE_GSI_EVENT_EN); + writel_relaxed(0xffffffff, se->base + SE_GENI_M_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_GENI_S_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_DMA_TX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_DMA_RX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_M_IRQ_EN); + if (proto != GENI_SE_UART) { + val |= M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN; + val |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN; + } + writel_relaxed(val, se->base + SE_GENI_M_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_S_IRQ_EN); + if (proto != GENI_SE_UART) + val |= S_CMD_DONE_EN; + writel_relaxed(val, se->base + SE_GENI_S_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_DMA_MODE_EN); + val &= ~GENI_DMA_MODE_EN; + writel_relaxed(val, se->base + SE_GENI_DMA_MODE_EN); +} + +static void geni_se_select_dma_mode(struct geni_se *se) +{ + u32 val; + + writel_relaxed(0, se->base + SE_GSI_EVENT_EN); + writel_relaxed(0xffffffff, se->base + SE_GENI_M_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_GENI_S_IRQ_CLEAR); + writel_relaxed(0xffffffff, se->base + SE_DMA_TX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_DMA_RX_IRQ_CLR); + writel_relaxed(0xffffffff, se->base + SE_IRQ_EN); + + val = readl_relaxed(se->base + SE_GENI_DMA_MODE_EN); + val |= GENI_DMA_MODE_EN; + writel_relaxed(val, se->base + SE_GENI_DMA_MODE_EN); +} + +/** + * geni_se_select_mode() - Select the serial engine transfer mode + * @se: Pointer to the concerned serial engine. + * @mode: Transfer mode to be selected. + */ +void geni_se_select_mode(struct geni_se *se, enum geni_se_xfer_mode mode) +{ + WARN_ON(mode != GENI_SE_FIFO && mode != GENI_SE_DMA); + + switch (mode) { + case GENI_SE_FIFO: + geni_se_select_fifo_mode(se); + break; + case GENI_SE_DMA: + geni_se_select_dma_mode(se); + break; + case GENI_SE_INVALID: + default: + break; + } +} +EXPORT_SYMBOL(geni_se_select_mode); + +/** + * DOC: Overview + * + * GENI FIFO packing is highly configurable. TX/RX packing/unpacking consist + * of up to 4 operations, each operation represented by 4 configuration vectors + * of 10 bits programmed in GENI_TX_PACKING_CFG0 and GENI_TX_PACKING_CFG1 for + * TX FIFO and in GENI_RX_PACKING_CFG0 and GENI_RX_PACKING_CFG1 for RX FIFO. + * Refer to below examples for detailed bit-field description. + * + * Example 1: word_size = 7, packing_mode = 4 x 8, msb_to_lsb = 1 + * + * +-----------+-------+-------+-------+-------+ + * | | vec_0 | vec_1 | vec_2 | vec_3 | + * +-----------+-------+-------+-------+-------+ + * | start | 0x6 | 0xe | 0x16 | 0x1e | + * | direction | 1 | 1 | 1 | 1 | + * | length | 6 | 6 | 6 | 6 | + * | stop | 0 | 0 | 0 | 1 | + * +-----------+-------+-------+-------+-------+ + * + * Example 2: word_size = 15, packing_mode = 2 x 16, msb_to_lsb = 0 + * + * +-----------+-------+-------+-------+-------+ + * | | vec_0 | vec_1 | vec_2 | vec_3 | + * +-----------+-------+-------+-------+-------+ + * | start | 0x0 | 0x8 | 0x10 | 0x18 | + * | direction | 0 | 0 | 0 | 0 | + * | length | 7 | 6 | 7 | 6 | + * | stop | 0 | 0 | 0 | 1 | + * +-----------+-------+-------+-------+-------+ + * + * Example 3: word_size = 23, packing_mode = 1 x 32, msb_to_lsb = 1 + * + * +-----------+-------+-------+-------+-------+ + * | | vec_0 | vec_1 | vec_2 | vec_3 | + * +-----------+-------+-------+-------+-------+ + * | start | 0x16 | 0xe | 0x6 | 0x0 | + * | direction | 1 | 1 | 1 | 1 | + * | length | 7 | 7 | 6 | 0 | + * | stop | 0 | 0 | 1 | 0 | + * +-----------+-------+-------+-------+-------+ + * + */ + +#define NUM_PACKING_VECTORS 4 +#define PACKING_START_SHIFT 5 +#define PACKING_DIR_SHIFT 4 +#define PACKING_LEN_SHIFT 1 +#define PACKING_STOP_BIT BIT(0) +#define PACKING_VECTOR_SHIFT 10 +/** + * geni_se_config_packing() - Packing configuration of the serial engine + * @se: Pointer to the concerned serial engine + * @bpw: Bits of data per transfer word. + * @pack_words: Number of words per fifo element. + * @msb_to_lsb: Transfer from MSB to LSB or vice-versa. + * @tx_cfg: Flag to configure the TX Packing. + * @rx_cfg: Flag to configure the RX Packing. + * + * This function is used to configure the packing rules for the current + * transfer. + */ +void geni_se_config_packing(struct geni_se *se, int bpw, int pack_words, + bool msb_to_lsb, bool tx_cfg, bool rx_cfg) +{ + u32 cfg0, cfg1, cfg[NUM_PACKING_VECTORS] = {0}; + int len; + int temp_bpw = bpw; + int idx_start = msb_to_lsb ? bpw - 1 : 0; + int idx = idx_start; + int idx_delta = msb_to_lsb ? -BITS_PER_BYTE : BITS_PER_BYTE; + int ceil_bpw = ALIGN(bpw, BITS_PER_BYTE); + int iter = (ceil_bpw * pack_words) / BITS_PER_BYTE; + int i; + + if (iter <= 0 || iter > NUM_PACKING_VECTORS) + return; + + for (i = 0; i < iter; i++) { + len = min_t(int, temp_bpw, BITS_PER_BYTE) - 1; + cfg[i] = idx << PACKING_START_SHIFT; + cfg[i] |= msb_to_lsb << PACKING_DIR_SHIFT; + cfg[i] |= len << PACKING_LEN_SHIFT; + + if (temp_bpw <= BITS_PER_BYTE) { + idx = ((i + 1) * BITS_PER_BYTE) + idx_start; + temp_bpw = bpw; + } else { + idx = idx + idx_delta; + temp_bpw = temp_bpw - BITS_PER_BYTE; + } + } + cfg[iter - 1] |= PACKING_STOP_BIT; + cfg0 = cfg[0] | (cfg[1] << PACKING_VECTOR_SHIFT); + cfg1 = cfg[2] | (cfg[3] << PACKING_VECTOR_SHIFT); + + if (tx_cfg) { + writel_relaxed(cfg0, se->base + SE_GENI_TX_PACKING_CFG0); + writel_relaxed(cfg1, se->base + SE_GENI_TX_PACKING_CFG1); + } + if (rx_cfg) { + writel_relaxed(cfg0, se->base + SE_GENI_RX_PACKING_CFG0); + writel_relaxed(cfg1, se->base + SE_GENI_RX_PACKING_CFG1); + } + + /* + * Number of protocol words in each FIFO entry + * 0 - 4x8, four words in each entry, max word size of 8 bits + * 1 - 2x16, two words in each entry, max word size of 16 bits + * 2 - 1x32, one word in each entry, max word size of 32 bits + * 3 - undefined + */ + if (pack_words || bpw == 32) + writel_relaxed(bpw / 16, se->base + SE_GENI_BYTE_GRAN); +} +EXPORT_SYMBOL(geni_se_config_packing); + +static void geni_se_clks_off(struct geni_se *se) +{ + struct geni_wrapper *wrapper = se->wrapper; + + clk_disable_unprepare(se->clk); + clk_bulk_disable_unprepare(ARRAY_SIZE(wrapper->ahb_clks), + wrapper->ahb_clks); +} + +/** + * geni_se_resources_off() - Turn off resources associated with the serial + * engine + * @se: Pointer to the concerned serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_resources_off(struct geni_se *se) +{ + int ret; + + ret = pinctrl_pm_select_sleep_state(se->dev); + if (ret) + return ret; + + geni_se_clks_off(se); + return 0; +} +EXPORT_SYMBOL(geni_se_resources_off); + +static int geni_se_clks_on(struct geni_se *se) +{ + int ret; + struct geni_wrapper *wrapper = se->wrapper; + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(wrapper->ahb_clks), + wrapper->ahb_clks); + if (ret) + return ret; + + ret = clk_prepare_enable(se->clk); + if (ret) + clk_bulk_disable_unprepare(ARRAY_SIZE(wrapper->ahb_clks), + wrapper->ahb_clks); + return ret; +} + +/** + * geni_se_resources_on() - Turn on resources associated with the serial + * engine + * @se: Pointer to the concerned serial engine. + * + * Return: 0 on success, standard Linux error codes on failure/error. + */ +int geni_se_resources_on(struct geni_se *se) +{ + int ret; + + ret = geni_se_clks_on(se); + if (ret) + return ret; + + ret = pinctrl_pm_select_default_state(se->dev); + if (ret) + geni_se_clks_off(se); + + return ret; +} +EXPORT_SYMBOL(geni_se_resources_on); + +/** + * geni_se_clk_tbl_get() - Get the clock table to program DFS + * @se: Pointer to the concerned serial engine. + * @tbl: Table in which the output is returned. + * + * This function is called by the protocol drivers to determine the different + * clock frequencies supported by serial engine core clock. The protocol + * drivers use the output to determine the clock frequency index to be + * programmed into DFS. + * + * Return: number of valid performance levels in the table on success, + * standard Linux error codes on failure. + */ +int geni_se_clk_tbl_get(struct geni_se *se, unsigned long **tbl) +{ + unsigned long freq = 0; + int i; + + if (se->clk_perf_tbl) { + *tbl = se->clk_perf_tbl; + return se->num_clk_levels; + } + + se->clk_perf_tbl = devm_kcalloc(se->dev, MAX_CLK_PERF_LEVEL, + sizeof(*se->clk_perf_tbl), + GFP_KERNEL); + if (!se->clk_perf_tbl) + return -ENOMEM; + + for (i = 0; i < MAX_CLK_PERF_LEVEL; i++) { + freq = clk_round_rate(se->clk, freq + 1); + if (!freq || freq == se->clk_perf_tbl[i - 1]) + break; + se->clk_perf_tbl[i] = freq; + } + se->num_clk_levels = i; + *tbl = se->clk_perf_tbl; + return se->num_clk_levels; +} +EXPORT_SYMBOL(geni_se_clk_tbl_get); + +/** + * geni_se_clk_freq_match() - Get the matching or closest SE clock frequency + * @se: Pointer to the concerned serial engine. + * @req_freq: Requested clock frequency. + * @index: Index of the resultant frequency in the table. + * @res_freq: Resultant frequency which matches or is closer to the + * requested frequency. + * @exact: Flag to indicate exact multiple requirement of the requested + * frequency. + * + * This function is called by the protocol drivers to determine the matching + * or exact multiple of the requested frequency, as provided by the serial + * engine clock in order to meet the performance requirements. If there is + * no matching or exact multiple of the requested frequency found, then it + * selects the closest floor frequency, if exact flag is not set. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_clk_freq_match(struct geni_se *se, unsigned long req_freq, + unsigned int *index, unsigned long *res_freq, + bool exact) +{ + unsigned long *tbl; + int num_clk_levels; + int i; + + num_clk_levels = geni_se_clk_tbl_get(se, &tbl); + if (num_clk_levels < 0) + return num_clk_levels; + + if (num_clk_levels == 0) + return -EINVAL; + + *res_freq = 0; + for (i = 0; i < num_clk_levels; i++) { + if (!(tbl[i] % req_freq)) { + *index = i; + *res_freq = tbl[i]; + return 0; + } + + if (!(*res_freq) || ((tbl[i] > *res_freq) && + (tbl[i] < req_freq))) { + *index = i; + *res_freq = tbl[i]; + } + } + + if (exact) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(geni_se_clk_freq_match); + +#define GENI_SE_DMA_DONE_EN BIT(0) +#define GENI_SE_DMA_EOT_EN BIT(1) +#define GENI_SE_DMA_AHB_ERR_EN BIT(2) +#define GENI_SE_DMA_EOT_BUF BIT(0) +/** + * geni_se_tx_dma_prep() - Prepare the serial engine for TX DMA transfer + * @se: Pointer to the concerned serial engine. + * @buf: Pointer to the TX buffer. + * @len: Length of the TX buffer. + * @iova: Pointer to store the mapped DMA address. + * + * This function is used to prepare the buffers for DMA TX. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_tx_dma_prep(struct geni_se *se, void *buf, size_t len, + dma_addr_t *iova) +{ + struct geni_wrapper *wrapper = se->wrapper; + u32 val; + + *iova = dma_map_single(wrapper->dev, buf, len, DMA_TO_DEVICE); + if (dma_mapping_error(wrapper->dev, *iova)) + return -EIO; + + val = GENI_SE_DMA_DONE_EN; + val |= GENI_SE_DMA_EOT_EN; + val |= GENI_SE_DMA_AHB_ERR_EN; + writel_relaxed(val, se->base + SE_DMA_TX_IRQ_EN_SET); + writel_relaxed(lower_32_bits(*iova), se->base + SE_DMA_TX_PTR_L); + writel_relaxed(upper_32_bits(*iova), se->base + SE_DMA_TX_PTR_H); + writel_relaxed(GENI_SE_DMA_EOT_BUF, se->base + SE_DMA_TX_ATTR); + writel_relaxed(len, se->base + SE_DMA_TX_LEN); + return 0; +} +EXPORT_SYMBOL(geni_se_tx_dma_prep); + +/** + * geni_se_rx_dma_prep() - Prepare the serial engine for RX DMA transfer + * @se: Pointer to the concerned serial engine. + * @buf: Pointer to the RX buffer. + * @len: Length of the RX buffer. + * @iova: Pointer to store the mapped DMA address. + * + * This function is used to prepare the buffers for DMA RX. + * + * Return: 0 on success, standard Linux error codes on failure. + */ +int geni_se_rx_dma_prep(struct geni_se *se, void *buf, size_t len, + dma_addr_t *iova) +{ + struct geni_wrapper *wrapper = se->wrapper; + u32 val; + + *iova = dma_map_single(wrapper->dev, buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(wrapper->dev, *iova)) + return -EIO; + + val = GENI_SE_DMA_DONE_EN; + val |= GENI_SE_DMA_EOT_EN; + val |= GENI_SE_DMA_AHB_ERR_EN; + writel_relaxed(val, se->base + SE_DMA_RX_IRQ_EN_SET); + writel_relaxed(lower_32_bits(*iova), se->base + SE_DMA_RX_PTR_L); + writel_relaxed(upper_32_bits(*iova), se->base + SE_DMA_RX_PTR_H); + /* RX does not have EOT buffer type bit. So just reset RX_ATTR */ + writel_relaxed(0, se->base + SE_DMA_RX_ATTR); + writel_relaxed(len, se->base + SE_DMA_RX_LEN); + return 0; +} +EXPORT_SYMBOL(geni_se_rx_dma_prep); + +/** + * geni_se_tx_dma_unprep() - Unprepare the serial engine after TX DMA transfer + * @se: Pointer to the concerned serial engine. + * @iova: DMA address of the TX buffer. + * @len: Length of the TX buffer. + * + * This function is used to unprepare the DMA buffers after DMA TX. + */ +void geni_se_tx_dma_unprep(struct geni_se *se, dma_addr_t iova, size_t len) +{ + struct geni_wrapper *wrapper = se->wrapper; + + if (iova && !dma_mapping_error(wrapper->dev, iova)) + dma_unmap_single(wrapper->dev, iova, len, DMA_TO_DEVICE); +} +EXPORT_SYMBOL(geni_se_tx_dma_unprep); + +/** + * geni_se_rx_dma_unprep() - Unprepare the serial engine after RX DMA transfer + * @se: Pointer to the concerned serial engine. + * @iova: DMA address of the RX buffer. + * @len: Length of the RX buffer. + * + * This function is used to unprepare the DMA buffers after DMA RX. + */ +void geni_se_rx_dma_unprep(struct geni_se *se, dma_addr_t iova, size_t len) +{ + struct geni_wrapper *wrapper = se->wrapper; + + if (iova && !dma_mapping_error(wrapper->dev, iova)) + dma_unmap_single(wrapper->dev, iova, len, DMA_FROM_DEVICE); +} +EXPORT_SYMBOL(geni_se_rx_dma_unprep); + +static int geni_se_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct geni_wrapper *wrapper; + int ret; + + wrapper = devm_kzalloc(dev, sizeof(*wrapper), GFP_KERNEL); + if (!wrapper) + return -ENOMEM; + + wrapper->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wrapper->base = devm_ioremap_resource(dev, res); + if (IS_ERR(wrapper->base)) + return PTR_ERR(wrapper->base); + + wrapper->ahb_clks[0].id = "m-ahb"; + wrapper->ahb_clks[1].id = "s-ahb"; + ret = devm_clk_bulk_get(dev, NUM_AHB_CLKS, wrapper->ahb_clks); + if (ret) { + dev_err(dev, "Err getting AHB clks %d\n", ret); + return ret; + } + + dev_set_drvdata(dev, wrapper); + dev_dbg(dev, "GENI SE Driver probed\n"); + return devm_of_platform_populate(dev); +} + +static const struct of_device_id geni_se_dt_match[] = { + { .compatible = "qcom,geni-se-qup", }, + {} +}; +MODULE_DEVICE_TABLE(of, geni_se_dt_match); + +static struct platform_driver geni_se_driver = { + .driver = { + .name = "geni_se_qup", + .of_match_table = geni_se_dt_match, + }, + .probe = geni_se_probe, +}; +module_platform_driver(geni_se_driver); + +MODULE_DESCRIPTION("GENI Serial Engine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/qmi_interface.c b/drivers/soc/qcom/qmi_interface.c index 3219822..938ca41c 100644 --- a/drivers/soc/qcom/qmi_interface.c +++ b/drivers/soc/qcom/qmi_interface.c @@ -639,10 +639,11 @@ int qmi_handle_init(struct qmi_handle *qmi, size_t recv_buf_size, if (ops) qmi->ops = *ops; + /* Make room for the header */ + recv_buf_size += sizeof(struct qmi_header); + /* Must also be sufficient to hold a control packet */ if (recv_buf_size < sizeof(struct qrtr_ctrl_pkt)) recv_buf_size = sizeof(struct qrtr_ctrl_pkt); - else - recv_buf_size += sizeof(struct qmi_header); qmi->recv_buf_size = recv_buf_size; qmi->recv_buf = kzalloc(recv_buf_size, GFP_KERNEL); diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index c234675..93517ed 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -226,6 +226,7 @@ static const struct of_device_id qcom_smd_rpm_of_match[] = { { .compatible = "qcom,rpm-msm8916" }, { .compatible = "qcom,rpm-msm8974" }, { .compatible = "qcom,rpm-msm8996" }, + { .compatible = "qcom,rpm-msm8998" }, {} }; MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 0b94d62..70b2ee8 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -280,7 +280,7 @@ struct qcom_smem { struct smem_region regions[0]; }; -static struct smem_private_entry * +static void * phdr_to_last_uncached_entry(struct smem_partition_header *phdr) { void *p = phdr; @@ -288,15 +288,18 @@ phdr_to_last_uncached_entry(struct smem_partition_header *phdr) return p + le32_to_cpu(phdr->offset_free_uncached); } -static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr, +static struct smem_private_entry * +phdr_to_first_cached_entry(struct smem_partition_header *phdr, size_t cacheline) { void *p = phdr; + struct smem_private_entry *e; - return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*phdr), cacheline); + return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); } -static void *phdr_to_last_cached_entry(struct smem_partition_header *phdr) +static void * +phdr_to_last_cached_entry(struct smem_partition_header *phdr) { void *p = phdr; @@ -361,14 +364,14 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, end = phdr_to_last_uncached_entry(phdr); cached = phdr_to_last_cached_entry(phdr); - while (hdr < end) { - if (hdr->canary != SMEM_PRIVATE_CANARY) { - dev_err(smem->dev, - "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); - return -EINVAL; - } + if (smem->global_partition) { + dev_err(smem->dev, "Already found the global partition\n"); + return -EINVAL; + } + while (hdr < end) { + if (hdr->canary != SMEM_PRIVATE_CANARY) + goto bad_canary; if (le16_to_cpu(hdr->item) == item) return -EEXIST; @@ -377,7 +380,7 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, /* Check that we don't grow into the cached region */ alloc_size = sizeof(*hdr) + ALIGN(size, 8); - if ((void *)hdr + alloc_size >= cached) { + if ((void *)hdr + alloc_size > cached) { dev_err(smem->dev, "Out of memory\n"); return -ENOSPC; } @@ -397,6 +400,11 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, le32_add_cpu(&phdr->offset_free_uncached, alloc_size); return 0; +bad_canary: + dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); + + return -EINVAL; } static int qcom_smem_alloc_global(struct qcom_smem *smem, @@ -560,8 +568,8 @@ static void *qcom_smem_get_private(struct qcom_smem *smem, return ERR_PTR(-ENOENT); invalid_canary: - dev_err(smem->dev, "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); + dev_err(smem->dev, "Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); return ERR_PTR(-EINVAL); } @@ -647,6 +655,33 @@ int qcom_smem_get_free_space(unsigned host) } EXPORT_SYMBOL(qcom_smem_get_free_space); +/** + * qcom_smem_virt_to_phys() - return the physical address associated + * with an smem item pointer (previously returned by qcom_smem_get() + * @p: the virtual address to convert + * + * Returns 0 if the pointer provided is not within any smem region. + */ +phys_addr_t qcom_smem_virt_to_phys(void *p) +{ + unsigned i; + + for (i = 0; i < __smem->num_regions; i++) { + struct smem_region *region = &__smem->regions[i]; + + if (p < region->virt_base) + continue; + if (p < region->virt_base + region->size) { + u64 offset = p - region->virt_base; + + return (phys_addr_t)region->aux_base + offset; + } + } + + return 0; +} +EXPORT_SYMBOL(qcom_smem_virt_to_phys); + static int qcom_smem_get_sbl_version(struct qcom_smem *smem) { struct smem_header *header; @@ -695,9 +730,10 @@ static u32 qcom_smem_get_item_count(struct qcom_smem *smem) static int qcom_smem_set_global_partition(struct qcom_smem *smem) { struct smem_partition_header *header; - struct smem_ptable_entry *entry = NULL; + struct smem_ptable_entry *entry; struct smem_ptable *ptable; u32 host0, host1, size; + bool found = false; int i; ptable = qcom_smem_get_ptable(smem); @@ -709,11 +745,13 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem) host0 = le16_to_cpu(entry->host0); host1 = le16_to_cpu(entry->host1); - if (host0 == SMEM_GLOBAL_HOST && host0 == host1) + if (host0 == SMEM_GLOBAL_HOST && host0 == host1) { + found = true; break; + } } - if (!entry) { + if (!found) { dev_err(smem->dev, "Missing entry for global partition\n"); return -EINVAL; } @@ -723,11 +761,6 @@ static int qcom_smem_set_global_partition(struct qcom_smem *smem) return -EINVAL; } - if (smem->global_partition) { - dev_err(smem->dev, "Already found the global partition\n"); - return -EINVAL; - } - header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); host0 = le16_to_cpu(header->host0); host1 = le16_to_cpu(header->host1); diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig index 3bbe611..1d824cb 100644 --- a/drivers/soc/renesas/Kconfig +++ b/drivers/soc/renesas/Kconfig @@ -4,9 +4,11 @@ config SOC_RENESAS select SOC_BUS select RST_RCAR if ARCH_RCAR_GEN1 || ARCH_RCAR_GEN2 || \ ARCH_R8A7795 || ARCH_R8A7796 || ARCH_R8A77965 || \ - ARCH_R8A77970 || ARCH_R8A77980 || ARCH_R8A77995 + ARCH_R8A77970 || ARCH_R8A77980 || ARCH_R8A77990 || \ + ARCH_R8A77995 select SYSC_R8A7743 if ARCH_R8A7743 select SYSC_R8A7745 if ARCH_R8A7745 + select SYSC_R8A77470 if ARCH_R8A77470 select SYSC_R8A7779 if ARCH_R8A7779 select SYSC_R8A7790 if ARCH_R8A7790 select SYSC_R8A7791 if ARCH_R8A7791 || ARCH_R8A7793 @@ -17,6 +19,7 @@ config SOC_RENESAS select SYSC_R8A77965 if ARCH_R8A77965 select SYSC_R8A77970 if ARCH_R8A77970 select SYSC_R8A77980 if ARCH_R8A77980 + select SYSC_R8A77990 if ARCH_R8A77990 select SYSC_R8A77995 if ARCH_R8A77995 if SOC_RENESAS @@ -30,6 +33,10 @@ config SYSC_R8A7745 bool "RZ/G1E System Controller support" if COMPILE_TEST select SYSC_RCAR +config SYSC_R8A77470 + bool "RZ/G1C System Controller support" if COMPILE_TEST + select SYSC_RCAR + config SYSC_R8A7779 bool "R-Car H1 System Controller support" if COMPILE_TEST select SYSC_RCAR @@ -70,6 +77,10 @@ config SYSC_R8A77980 bool "R-Car V3H System Controller support" if COMPILE_TEST select SYSC_RCAR +config SYSC_R8A77990 + bool "R-Car E3 System Controller support" if COMPILE_TEST + select SYSC_RCAR + config SYSC_R8A77995 bool "R-Car D3 System Controller support" if COMPILE_TEST select SYSC_RCAR diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile index ccb5ec5..7dc0f20 100644 --- a/drivers/soc/renesas/Makefile +++ b/drivers/soc/renesas/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SOC_RENESAS) += renesas-soc.o # SoC obj-$(CONFIG_SYSC_R8A7743) += r8a7743-sysc.o obj-$(CONFIG_SYSC_R8A7745) += r8a7745-sysc.o +obj-$(CONFIG_SYSC_R8A77470) += r8a77470-sysc.o obj-$(CONFIG_SYSC_R8A7779) += r8a7779-sysc.o obj-$(CONFIG_SYSC_R8A7790) += r8a7790-sysc.o obj-$(CONFIG_SYSC_R8A7791) += r8a7791-sysc.o @@ -15,6 +16,7 @@ obj-$(CONFIG_SYSC_R8A7796) += r8a7796-sysc.o obj-$(CONFIG_SYSC_R8A77965) += r8a77965-sysc.o obj-$(CONFIG_SYSC_R8A77970) += r8a77970-sysc.o obj-$(CONFIG_SYSC_R8A77980) += r8a77980-sysc.o +obj-$(CONFIG_SYSC_R8A77990) += r8a77990-sysc.o obj-$(CONFIG_SYSC_R8A77995) += r8a77995-sysc.o # Family diff --git a/drivers/soc/renesas/r8a77470-sysc.c b/drivers/soc/renesas/r8a77470-sysc.c new file mode 100644 index 0000000..cfa015e --- /dev/null +++ b/drivers/soc/renesas/r8a77470-sysc.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G1C System Controller + * + * Copyright (C) 2018 Renesas Electronics Corp. + */ + +#include <linux/bug.h> +#include <linux/kernel.h> + +#include <dt-bindings/power/r8a77470-sysc.h> + +#include "rcar-sysc.h" + +static const struct rcar_sysc_area r8a77470_areas[] __initconst = { + { "always-on", 0, 0, R8A77470_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, + { "ca7-scu", 0x100, 0, R8A77470_PD_CA7_SCU, R8A77470_PD_ALWAYS_ON, + PD_SCU }, + { "ca7-cpu0", 0x1c0, 0, R8A77470_PD_CA7_CPU0, R8A77470_PD_CA7_SCU, + PD_CPU_NOCR }, + { "ca7-cpu1", 0x1c0, 1, R8A77470_PD_CA7_CPU1, R8A77470_PD_CA7_SCU, + PD_CPU_NOCR }, + { "sgx", 0xc0, 0, R8A77470_PD_SGX, R8A77470_PD_ALWAYS_ON }, +}; + +const struct rcar_sysc_info r8a77470_sysc_info __initconst = { + .areas = r8a77470_areas, + .num_areas = ARRAY_SIZE(r8a77470_areas), +}; diff --git a/drivers/soc/renesas/r8a77990-sysc.c b/drivers/soc/renesas/r8a77990-sysc.c new file mode 100644 index 0000000..15579eb --- /dev/null +++ b/drivers/soc/renesas/r8a77990-sysc.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car E3 System Controller + * + * Copyright (C) 2018 Renesas Electronics Corp. + */ + +#include <linux/bug.h> +#include <linux/kernel.h> +#include <linux/sys_soc.h> + +#include <dt-bindings/power/r8a77990-sysc.h> + +#include "rcar-sysc.h" + +static struct rcar_sysc_area r8a77990_areas[] __initdata = { + { "always-on", 0, 0, R8A77990_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, + { "ca53-scu", 0x140, 0, R8A77990_PD_CA53_SCU, R8A77990_PD_ALWAYS_ON, + PD_SCU }, + { "ca53-cpu0", 0x200, 0, R8A77990_PD_CA53_CPU0, R8A77990_PD_CA53_SCU, + PD_CPU_NOCR }, + { "ca53-cpu1", 0x200, 1, R8A77990_PD_CA53_CPU1, R8A77990_PD_CA53_SCU, + PD_CPU_NOCR }, + { "cr7", 0x240, 0, R8A77990_PD_CR7, R8A77990_PD_ALWAYS_ON }, + { "a3vc", 0x380, 0, R8A77990_PD_A3VC, R8A77990_PD_ALWAYS_ON }, + { "a2vc1", 0x3c0, 1, R8A77990_PD_A2VC1, R8A77990_PD_A3VC }, + { "3dg-a", 0x100, 0, R8A77990_PD_3DG_A, R8A77990_PD_ALWAYS_ON }, + { "3dg-b", 0x100, 1, R8A77990_PD_3DG_B, R8A77990_PD_3DG_A }, +}; + +static void __init rcar_sysc_fix_parent(struct rcar_sysc_area *areas, + unsigned int num_areas, u8 id, + int new_parent) +{ + unsigned int i; + + for (i = 0; i < num_areas; i++) + if (areas[i].isr_bit == id) { + areas[i].parent = new_parent; + return; + } +} + +/* Fixups for R-Car E3 ES1.0 revision */ +static const struct soc_device_attribute r8a77990[] __initconst = { + { .soc_id = "r8a77990", .revision = "ES1.0" }, + { /* sentinel */ } +}; + +static int __init r8a77990_sysc_init(void) +{ + if (soc_device_match(r8a77990)) { + rcar_sysc_fix_parent(r8a77990_areas, + ARRAY_SIZE(r8a77990_areas), + R8A77990_PD_3DG_A, R8A77990_PD_3DG_B); + rcar_sysc_fix_parent(r8a77990_areas, + ARRAY_SIZE(r8a77990_areas), + R8A77990_PD_3DG_B, R8A77990_PD_ALWAYS_ON); + } + + return 0; +} + +const struct rcar_sysc_info r8a77990_sysc_info __initconst = { + .init = r8a77990_sysc_init, + .areas = r8a77990_areas, + .num_areas = ARRAY_SIZE(r8a77990_areas), +}; diff --git a/drivers/soc/renesas/r8a77995-sysc.c b/drivers/soc/renesas/r8a77995-sysc.c index f718429..1b2ef41 100644 --- a/drivers/soc/renesas/r8a77995-sysc.c +++ b/drivers/soc/renesas/r8a77995-sysc.c @@ -10,13 +10,12 @@ #include <linux/bug.h> #include <linux/kernel.h> -#include <linux/sys_soc.h> #include <dt-bindings/power/r8a77995-sysc.h> #include "rcar-sysc.h" -static struct rcar_sysc_area r8a77995_areas[] __initdata = { +static const struct rcar_sysc_area r8a77995_areas[] __initconst = { { "always-on", 0, 0, R8A77995_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, { "ca53-scu", 0x140, 0, R8A77995_PD_CA53_SCU, R8A77995_PD_ALWAYS_ON, PD_SCU }, diff --git a/drivers/soc/renesas/rcar-rst.c b/drivers/soc/renesas/rcar-rst.c index 8e9cb79..d9c1034 100644 --- a/drivers/soc/renesas/rcar-rst.c +++ b/drivers/soc/renesas/rcar-rst.c @@ -44,6 +44,7 @@ static const struct of_device_id rcar_rst_matches[] __initconst = { /* RZ/G is handled like R-Car Gen2 */ { .compatible = "renesas,r8a7743-rst", .data = &rcar_rst_gen2 }, { .compatible = "renesas,r8a7745-rst", .data = &rcar_rst_gen2 }, + { .compatible = "renesas,r8a77470-rst", .data = &rcar_rst_gen2 }, /* R-Car Gen1 */ { .compatible = "renesas,r8a7778-reset-wdt", .data = &rcar_rst_gen1 }, { .compatible = "renesas,r8a7779-reset-wdt", .data = &rcar_rst_gen1 }, @@ -59,6 +60,7 @@ static const struct of_device_id rcar_rst_matches[] __initconst = { { .compatible = "renesas,r8a77965-rst", .data = &rcar_rst_gen3 }, { .compatible = "renesas,r8a77970-rst", .data = &rcar_rst_gen3 }, { .compatible = "renesas,r8a77980-rst", .data = &rcar_rst_gen3 }, + { .compatible = "renesas,r8a77990-rst", .data = &rcar_rst_gen3 }, { .compatible = "renesas,r8a77995-rst", .data = &rcar_rst_gen3 }, { /* sentinel */ } }; diff --git a/drivers/soc/renesas/rcar-sysc.c b/drivers/soc/renesas/rcar-sysc.c index faf20e7..95120ac 100644 --- a/drivers/soc/renesas/rcar-sysc.c +++ b/drivers/soc/renesas/rcar-sysc.c @@ -261,6 +261,9 @@ static const struct of_device_id rcar_sysc_matches[] __initconst = { #ifdef CONFIG_SYSC_R8A7745 { .compatible = "renesas,r8a7745-sysc", .data = &r8a7745_sysc_info }, #endif +#ifdef CONFIG_SYSC_R8A77470 + { .compatible = "renesas,r8a77470-sysc", .data = &r8a77470_sysc_info }, +#endif #ifdef CONFIG_SYSC_R8A7779 { .compatible = "renesas,r8a7779-sysc", .data = &r8a7779_sysc_info }, #endif @@ -293,6 +296,9 @@ static const struct of_device_id rcar_sysc_matches[] __initconst = { #ifdef CONFIG_SYSC_R8A77980 { .compatible = "renesas,r8a77980-sysc", .data = &r8a77980_sysc_info }, #endif +#ifdef CONFIG_SYSC_R8A77990 + { .compatible = "renesas,r8a77990-sysc", .data = &r8a77990_sysc_info }, +#endif #ifdef CONFIG_SYSC_R8A77995 { .compatible = "renesas,r8a77995-sysc", .data = &r8a77995_sysc_info }, #endif diff --git a/drivers/soc/renesas/rcar-sysc.h b/drivers/soc/renesas/rcar-sysc.h index dcdc9ec..a22e7cf 100644 --- a/drivers/soc/renesas/rcar-sysc.h +++ b/drivers/soc/renesas/rcar-sysc.h @@ -51,6 +51,7 @@ struct rcar_sysc_info { extern const struct rcar_sysc_info r8a7743_sysc_info; extern const struct rcar_sysc_info r8a7745_sysc_info; +extern const struct rcar_sysc_info r8a77470_sysc_info; extern const struct rcar_sysc_info r8a7779_sysc_info; extern const struct rcar_sysc_info r8a7790_sysc_info; extern const struct rcar_sysc_info r8a7791_sysc_info; @@ -61,6 +62,7 @@ extern const struct rcar_sysc_info r8a7796_sysc_info; extern const struct rcar_sysc_info r8a77965_sysc_info; extern const struct rcar_sysc_info r8a77970_sysc_info; extern const struct rcar_sysc_info r8a77980_sysc_info; +extern const struct rcar_sysc_info r8a77990_sysc_info; extern const struct rcar_sysc_info r8a77995_sysc_info; diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c index ea71c41..d44d0e6 100644 --- a/drivers/soc/renesas/renesas-soc.c +++ b/drivers/soc/renesas/renesas-soc.c @@ -100,6 +100,11 @@ static const struct renesas_soc soc_rz_g1e __initconst __maybe_unused = { .id = 0x4c, }; +static const struct renesas_soc soc_rz_g1c __initconst __maybe_unused = { + .family = &fam_rzg, + .id = 0x53, +}; + static const struct renesas_soc soc_rcar_m1a __initconst __maybe_unused = { .family = &fam_rcar_gen1, }; @@ -159,6 +164,11 @@ static const struct renesas_soc soc_rcar_v3h __initconst __maybe_unused = { .id = 0x56, }; +static const struct renesas_soc soc_rcar_e3 __initconst __maybe_unused = { + .family = &fam_rcar_gen3, + .id = 0x57, +}; + static const struct renesas_soc soc_rcar_d3 __initconst __maybe_unused = { .family = &fam_rcar_gen3, .id = 0x58, @@ -192,6 +202,9 @@ static const struct of_device_id renesas_socs[] __initconst = { #ifdef CONFIG_ARCH_R8A7745 { .compatible = "renesas,r8a7745", .data = &soc_rz_g1e }, #endif +#ifdef CONFIG_ARCH_R8A77470 + { .compatible = "renesas,r8a77470", .data = &soc_rz_g1c }, +#endif #ifdef CONFIG_ARCH_R8A7778 { .compatible = "renesas,r8a7778", .data = &soc_rcar_m1a }, #endif @@ -228,6 +241,9 @@ static const struct of_device_id renesas_socs[] __initconst = { #ifdef CONFIG_ARCH_R8A77980 { .compatible = "renesas,r8a77980", .data = &soc_rcar_v3h }, #endif +#ifdef CONFIG_ARCH_R8A77990 + { .compatible = "renesas,r8a77990", .data = &soc_rcar_e3 }, +#endif #ifdef CONFIG_ARCH_R8A77995 { .compatible = "renesas,r8a77995", .data = &soc_rcar_d3 }, #endif diff --git a/drivers/soc/rockchip/pm_domains.c b/drivers/soc/rockchip/pm_domains.c index f874baa..6dff868 100644 --- a/drivers/soc/rockchip/pm_domains.c +++ b/drivers/soc/rockchip/pm_domains.c @@ -19,6 +19,10 @@ #include <linux/clk.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> +#include <dt-bindings/power/px30-power.h> +#include <dt-bindings/power/rk3036-power.h> +#include <dt-bindings/power/rk3128-power.h> +#include <dt-bindings/power/rk3228-power.h> #include <dt-bindings/power/rk3288-power.h> #include <dt-bindings/power/rk3328-power.h> #include <dt-bindings/power/rk3366-power.h> @@ -104,6 +108,18 @@ struct rockchip_pmu { .active_wakeup = wakeup, \ } +#define DOMAIN_RK3036(req, ack, idle, wakeup) \ +{ \ + .req_mask = (req >= 0) ? BIT(req) : 0, \ + .req_w_mask = (req >= 0) ? BIT(req + 16) : 0, \ + .ack_mask = (ack >= 0) ? BIT(ack) : 0, \ + .idle_mask = (idle >= 0) ? BIT(idle) : 0, \ + .active_wakeup = wakeup, \ +} + +#define DOMAIN_PX30(pwr, status, req, wakeup) \ + DOMAIN_M(pwr, status, req, (req) + 16, req, wakeup) + #define DOMAIN_RK3288(pwr, status, req, wakeup) \ DOMAIN(pwr, status, req, req, (req) + 16, wakeup) @@ -256,7 +272,7 @@ static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd, return; else if (pd->info->pwr_w_mask) regmap_write(pmu->regmap, pmu->info->pwr_offset, - on ? pd->info->pwr_mask : + on ? pd->info->pwr_w_mask : (pd->info->pwr_mask | pd->info->pwr_w_mask)); else regmap_update_bits(pmu->regmap, pmu->info->pwr_offset, @@ -700,6 +716,49 @@ err_out: return error; } +static const struct rockchip_domain_info px30_pm_domains[] = { + [PX30_PD_USB] = DOMAIN_PX30(5, 5, 10, false), + [PX30_PD_SDCARD] = DOMAIN_PX30(8, 8, 9, false), + [PX30_PD_GMAC] = DOMAIN_PX30(10, 10, 6, false), + [PX30_PD_MMC_NAND] = DOMAIN_PX30(11, 11, 5, false), + [PX30_PD_VPU] = DOMAIN_PX30(12, 12, 14, false), + [PX30_PD_VO] = DOMAIN_PX30(13, 13, 7, false), + [PX30_PD_VI] = DOMAIN_PX30(14, 14, 8, false), + [PX30_PD_GPU] = DOMAIN_PX30(15, 15, 2, false), +}; + +static const struct rockchip_domain_info rk3036_pm_domains[] = { + [RK3036_PD_MSCH] = DOMAIN_RK3036(14, 23, 30, true), + [RK3036_PD_CORE] = DOMAIN_RK3036(13, 17, 24, false), + [RK3036_PD_PERI] = DOMAIN_RK3036(12, 18, 25, false), + [RK3036_PD_VIO] = DOMAIN_RK3036(11, 19, 26, false), + [RK3036_PD_VPU] = DOMAIN_RK3036(10, 20, 27, false), + [RK3036_PD_GPU] = DOMAIN_RK3036(9, 21, 28, false), + [RK3036_PD_SYS] = DOMAIN_RK3036(8, 22, 29, false), +}; + +static const struct rockchip_domain_info rk3128_pm_domains[] = { + [RK3128_PD_CORE] = DOMAIN_RK3288(0, 0, 4, false), + [RK3128_PD_MSCH] = DOMAIN_RK3288(-1, -1, 6, true), + [RK3128_PD_VIO] = DOMAIN_RK3288(3, 3, 2, false), + [RK3128_PD_VIDEO] = DOMAIN_RK3288(2, 2, 1, false), + [RK3128_PD_GPU] = DOMAIN_RK3288(1, 1, 3, false), +}; + +static const struct rockchip_domain_info rk3228_pm_domains[] = { + [RK3228_PD_CORE] = DOMAIN_RK3036(0, 0, 16, true), + [RK3228_PD_MSCH] = DOMAIN_RK3036(1, 1, 17, true), + [RK3228_PD_BUS] = DOMAIN_RK3036(2, 2, 18, true), + [RK3228_PD_SYS] = DOMAIN_RK3036(3, 3, 19, true), + [RK3228_PD_VIO] = DOMAIN_RK3036(4, 4, 20, false), + [RK3228_PD_VOP] = DOMAIN_RK3036(5, 5, 21, false), + [RK3228_PD_VPU] = DOMAIN_RK3036(6, 6, 22, false), + [RK3228_PD_RKVDEC] = DOMAIN_RK3036(7, 7, 23, false), + [RK3228_PD_GPU] = DOMAIN_RK3036(8, 8, 24, false), + [RK3228_PD_PERI] = DOMAIN_RK3036(9, 9, 25, true), + [RK3228_PD_GMAC] = DOMAIN_RK3036(10, 10, 26, false), +}; + static const struct rockchip_domain_info rk3288_pm_domains[] = { [RK3288_PD_VIO] = DOMAIN_RK3288(7, 7, 4, false), [RK3288_PD_HEVC] = DOMAIN_RK3288(14, 10, 9, false), @@ -767,6 +826,46 @@ static const struct rockchip_domain_info rk3399_pm_domains[] = { [RK3399_PD_SDIOAUDIO] = DOMAIN_RK3399(31, 31, 29, true), }; +static const struct rockchip_pmu_info px30_pmu = { + .pwr_offset = 0x18, + .status_offset = 0x20, + .req_offset = 0x64, + .idle_offset = 0x6c, + .ack_offset = 0x6c, + + .num_domains = ARRAY_SIZE(px30_pm_domains), + .domain_info = px30_pm_domains, +}; + +static const struct rockchip_pmu_info rk3036_pmu = { + .req_offset = 0x148, + .idle_offset = 0x14c, + .ack_offset = 0x14c, + + .num_domains = ARRAY_SIZE(rk3036_pm_domains), + .domain_info = rk3036_pm_domains, +}; + +static const struct rockchip_pmu_info rk3128_pmu = { + .pwr_offset = 0x04, + .status_offset = 0x08, + .req_offset = 0x0c, + .idle_offset = 0x10, + .ack_offset = 0x10, + + .num_domains = ARRAY_SIZE(rk3128_pm_domains), + .domain_info = rk3128_pm_domains, +}; + +static const struct rockchip_pmu_info rk3228_pmu = { + .req_offset = 0x40c, + .idle_offset = 0x488, + .ack_offset = 0x488, + + .num_domains = ARRAY_SIZE(rk3228_pm_domains), + .domain_info = rk3228_pm_domains, +}; + static const struct rockchip_pmu_info rk3288_pmu = { .pwr_offset = 0x08, .status_offset = 0x0c, @@ -842,6 +941,22 @@ static const struct rockchip_pmu_info rk3399_pmu = { static const struct of_device_id rockchip_pm_domain_dt_match[] = { { + .compatible = "rockchip,px30-power-controller", + .data = (void *)&px30_pmu, + }, + { + .compatible = "rockchip,rk3036-power-controller", + .data = (void *)&rk3036_pmu, + }, + { + .compatible = "rockchip,rk3128-power-controller", + .data = (void *)&rk3128_pmu, + }, + { + .compatible = "rockchip,rk3228-power-controller", + .data = (void *)&rk3228_pmu, + }, + { .compatible = "rockchip,rk3288-power-controller", .data = (void *)&rk3288_pmu, }, diff --git a/drivers/soc/samsung/pm_domains.c b/drivers/soc/samsung/pm_domains.c index caf45cf..ab85829 100644 --- a/drivers/soc/samsung/pm_domains.c +++ b/drivers/soc/samsung/pm_domains.c @@ -13,14 +13,11 @@ #include <linux/err.h> #include <linux/slab.h> #include <linux/pm_domain.h> -#include <linux/clk.h> #include <linux/delay.h> #include <linux/of_address.h> #include <linux/of_platform.h> #include <linux/sched.h> -#define MAX_CLK_PER_DOMAIN 4 - struct exynos_pm_domain_config { /* Value for LOCAL_PWR_CFG and STATUS fields for each domain */ u32 local_pwr_cfg; @@ -33,10 +30,6 @@ struct exynos_pm_domain { void __iomem *base; bool is_off; struct generic_pm_domain pd; - struct clk *oscclk; - struct clk *clk[MAX_CLK_PER_DOMAIN]; - struct clk *pclk[MAX_CLK_PER_DOMAIN]; - struct clk *asb_clk[MAX_CLK_PER_DOMAIN]; u32 local_pwr_cfg; }; @@ -46,29 +39,10 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on) void __iomem *base; u32 timeout, pwr; char *op; - int i; pd = container_of(domain, struct exynos_pm_domain, pd); base = pd->base; - for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { - if (IS_ERR(pd->asb_clk[i])) - break; - clk_prepare_enable(pd->asb_clk[i]); - } - - /* Set oscclk before powering off a domain*/ - if (!power_on) { - for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { - if (IS_ERR(pd->clk[i])) - break; - pd->pclk[i] = clk_get_parent(pd->clk[i]); - if (clk_set_parent(pd->clk[i], pd->oscclk)) - pr_err("%s: error setting oscclk as parent to clock %d\n", - domain->name, i); - } - } - pwr = power_on ? pd->local_pwr_cfg : 0; writel_relaxed(pwr, base); @@ -86,26 +60,6 @@ static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on) usleep_range(80, 100); } - /* Restore clocks after powering on a domain*/ - if (power_on) { - for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { - if (IS_ERR(pd->clk[i])) - break; - - if (IS_ERR(pd->pclk[i])) - continue; /* Skip on first power up */ - if (clk_set_parent(pd->clk[i], pd->pclk[i])) - pr_err("%s: error setting parent to clock%d\n", - domain->name, i); - } - } - - for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { - if (IS_ERR(pd->asb_clk[i])) - break; - clk_disable_unprepare(pd->asb_clk[i]); - } - return 0; } @@ -147,12 +101,6 @@ static __init const char *exynos_get_domain_name(struct device_node *node) return kstrdup_const(name, GFP_KERNEL); } -static const char *soc_force_no_clk[] = { - "samsung,exynos5250-clock", - "samsung,exynos5420-clock", - "samsung,exynos5800-clock", -}; - static __init int exynos4_pm_init_power_domain(void) { struct device_node *np; @@ -161,7 +109,7 @@ static __init int exynos4_pm_init_power_domain(void) for_each_matching_node_and_match(np, exynos_pm_domain_of_match, &match) { const struct exynos_pm_domain_config *pm_domain_cfg; struct exynos_pm_domain *pd; - int on, i; + int on; pm_domain_cfg = match->data; @@ -189,42 +137,6 @@ static __init int exynos4_pm_init_power_domain(void) pd->pd.power_on = exynos_pd_power_on; pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg; - for (i = 0; i < ARRAY_SIZE(soc_force_no_clk); i++) - if (of_find_compatible_node(NULL, NULL, - soc_force_no_clk[i])) - goto no_clk; - - for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { - char clk_name[8]; - - snprintf(clk_name, sizeof(clk_name), "asb%d", i); - pd->asb_clk[i] = of_clk_get_by_name(np, clk_name); - if (IS_ERR(pd->asb_clk[i])) - break; - } - - pd->oscclk = of_clk_get_by_name(np, "oscclk"); - if (IS_ERR(pd->oscclk)) - goto no_clk; - - for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) { - char clk_name[8]; - - snprintf(clk_name, sizeof(clk_name), "clk%d", i); - pd->clk[i] = of_clk_get_by_name(np, clk_name); - if (IS_ERR(pd->clk[i])) - break; - /* - * Skip setting parent on first power up. - * The parent at this time may not be useful at all. - */ - pd->pclk[i] = ERR_PTR(-EINVAL); - } - - if (IS_ERR(pd->clk[0])) - clk_put(pd->oscclk); - -no_clk: on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg; pm_genpd_init(&pd->pd, NULL, !on); diff --git a/drivers/soc/ti/knav_qmss.h b/drivers/soc/ti/knav_qmss.h index 56866ba4..3efc47e 100644 --- a/drivers/soc/ti/knav_qmss.h +++ b/drivers/soc/ti/knav_qmss.h @@ -19,6 +19,8 @@ #ifndef __KNAV_QMSS_H__ #define __KNAV_QMSS_H__ +#include <linux/percpu.h> + #define THRESH_GTE BIT(7) #define THRESH_LT 0 @@ -162,11 +164,11 @@ struct knav_qmgr_info { * notifies: notifier counts */ struct knav_queue_stats { - atomic_t pushes; - atomic_t pops; - atomic_t push_errors; - atomic_t pop_errors; - atomic_t notifies; + unsigned int pushes; + unsigned int pops; + unsigned int push_errors; + unsigned int pop_errors; + unsigned int notifies; }; /** @@ -283,7 +285,7 @@ struct knav_queue_inst { struct knav_queue { struct knav_reg_queue __iomem *reg_push, *reg_pop, *reg_peek; struct knav_queue_inst *inst; - struct knav_queue_stats stats; + struct knav_queue_stats __percpu *stats; knav_queue_notify_fn notifier_fn; void *notifier_fn_arg; atomic_t notifier_enabled; diff --git a/drivers/soc/ti/knav_qmss_queue.c b/drivers/soc/ti/knav_qmss_queue.c index 419365a..6755f2a 100644 --- a/drivers/soc/ti/knav_qmss_queue.c +++ b/drivers/soc/ti/knav_qmss_queue.c @@ -99,7 +99,7 @@ void knav_queue_notify(struct knav_queue_inst *inst) continue; if (WARN_ON(!qh->notifier_fn)) continue; - atomic_inc(&qh->stats.notifies); + this_cpu_inc(qh->stats->notifies); qh->notifier_fn(qh->notifier_fn_arg); } rcu_read_unlock(); @@ -230,6 +230,12 @@ static struct knav_queue *__knav_queue_open(struct knav_queue_inst *inst, if (!qh) return ERR_PTR(-ENOMEM); + qh->stats = alloc_percpu(struct knav_queue_stats); + if (!qh->stats) { + ret = -ENOMEM; + goto err; + } + qh->flags = flags; qh->inst = inst; id = inst->id - inst->qmgr->start_queue; @@ -245,13 +251,17 @@ static struct knav_queue *__knav_queue_open(struct knav_queue_inst *inst, if (range->ops && range->ops->open_queue) ret = range->ops->open_queue(range, inst, flags); - if (ret) { - devm_kfree(inst->kdev->dev, qh); - return ERR_PTR(ret); - } + if (ret) + goto err; } list_add_tail_rcu(&qh->list, &inst->handles); return qh; + +err: + if (qh->stats) + free_percpu(qh->stats); + devm_kfree(inst->kdev->dev, qh); + return ERR_PTR(ret); } static struct knav_queue * @@ -427,6 +437,12 @@ static void knav_queue_debug_show_instance(struct seq_file *s, { struct knav_device *kdev = inst->kdev; struct knav_queue *qh; + int cpu = 0; + int pushes = 0; + int pops = 0; + int push_errors = 0; + int pop_errors = 0; + int notifies = 0; if (!knav_queue_is_busy(inst)) return; @@ -434,19 +450,22 @@ static void knav_queue_debug_show_instance(struct seq_file *s, seq_printf(s, "\tqueue id %d (%s)\n", kdev->base_id + inst->id, inst->name); for_each_handle_rcu(qh, inst) { - seq_printf(s, "\t\thandle %p: ", qh); - seq_printf(s, "pushes %8d, ", - atomic_read(&qh->stats.pushes)); - seq_printf(s, "pops %8d, ", - atomic_read(&qh->stats.pops)); - seq_printf(s, "count %8d, ", - knav_queue_get_count(qh)); - seq_printf(s, "notifies %8d, ", - atomic_read(&qh->stats.notifies)); - seq_printf(s, "push errors %8d, ", - atomic_read(&qh->stats.push_errors)); - seq_printf(s, "pop errors %8d\n", - atomic_read(&qh->stats.pop_errors)); + for_each_possible_cpu(cpu) { + pushes += per_cpu_ptr(qh->stats, cpu)->pushes; + pops += per_cpu_ptr(qh->stats, cpu)->pops; + push_errors += per_cpu_ptr(qh->stats, cpu)->push_errors; + pop_errors += per_cpu_ptr(qh->stats, cpu)->pop_errors; + notifies += per_cpu_ptr(qh->stats, cpu)->notifies; + } + + seq_printf(s, "\t\thandle %p: pushes %8d, pops %8d, count %8d, notifies %8d, push errors %8d, pop errors %8d\n", + qh, + pushes, + pops, + knav_queue_get_count(qh), + notifies, + push_errors, + pop_errors); } } @@ -563,6 +582,7 @@ void knav_queue_close(void *qhandle) if (range->ops && range->ops->close_queue) range->ops->close_queue(range, inst); } + free_percpu(qh->stats); devm_kfree(inst->kdev->dev, qh); } EXPORT_SYMBOL_GPL(knav_queue_close); @@ -636,7 +656,7 @@ int knav_queue_push(void *qhandle, dma_addr_t dma, val = (u32)dma | ((size / 16) - 1); writel_relaxed(val, &qh->reg_push[0].ptr_size_thresh); - atomic_inc(&qh->stats.pushes); + this_cpu_inc(qh->stats->pushes); return 0; } EXPORT_SYMBOL_GPL(knav_queue_push); @@ -674,7 +694,7 @@ dma_addr_t knav_queue_pop(void *qhandle, unsigned *size) if (size) *size = ((val & DESC_SIZE_MASK) + 1) * 16; - atomic_inc(&qh->stats.pops); + this_cpu_inc(qh->stats->pops); return dma; } EXPORT_SYMBOL_GPL(knav_queue_pop); diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c index ee3a215..334d98b 100644 --- a/drivers/thermal/imx_thermal.c +++ b/drivers/thermal/imx_thermal.c @@ -1,11 +1,6 @@ -/* - * Copyright 2013 Freescale Semiconductor, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright 2013 Freescale Semiconductor, Inc. #include <linux/clk.h> #include <linux/cpufreq.h> @@ -31,35 +26,57 @@ #define REG_CLR 0x8 #define REG_TOG 0xc -#define MISC0 0x0150 -#define MISC0_REFTOP_SELBIASOFF (1 << 3) -#define MISC1 0x0160 -#define MISC1_IRQ_TEMPHIGH (1 << 29) +/* i.MX6 specific */ +#define IMX6_MISC0 0x0150 +#define IMX6_MISC0_REFTOP_SELBIASOFF (1 << 3) +#define IMX6_MISC1 0x0160 +#define IMX6_MISC1_IRQ_TEMPHIGH (1 << 29) /* Below LOW and PANIC bits are only for TEMPMON_IMX6SX */ -#define MISC1_IRQ_TEMPLOW (1 << 28) -#define MISC1_IRQ_TEMPPANIC (1 << 27) - -#define TEMPSENSE0 0x0180 -#define TEMPSENSE0_ALARM_VALUE_SHIFT 20 -#define TEMPSENSE0_ALARM_VALUE_MASK (0xfff << TEMPSENSE0_ALARM_VALUE_SHIFT) -#define TEMPSENSE0_TEMP_CNT_SHIFT 8 -#define TEMPSENSE0_TEMP_CNT_MASK (0xfff << TEMPSENSE0_TEMP_CNT_SHIFT) -#define TEMPSENSE0_FINISHED (1 << 2) -#define TEMPSENSE0_MEASURE_TEMP (1 << 1) -#define TEMPSENSE0_POWER_DOWN (1 << 0) - -#define TEMPSENSE1 0x0190 -#define TEMPSENSE1_MEASURE_FREQ 0xffff -/* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */ -#define TEMPSENSE2 0x0290 -#define TEMPSENSE2_LOW_VALUE_SHIFT 0 -#define TEMPSENSE2_LOW_VALUE_MASK 0xfff -#define TEMPSENSE2_PANIC_VALUE_SHIFT 16 -#define TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000 +#define IMX6_MISC1_IRQ_TEMPLOW (1 << 28) +#define IMX6_MISC1_IRQ_TEMPPANIC (1 << 27) + +#define IMX6_TEMPSENSE0 0x0180 +#define IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT 20 +#define IMX6_TEMPSENSE0_ALARM_VALUE_MASK (0xfff << 20) +#define IMX6_TEMPSENSE0_TEMP_CNT_SHIFT 8 +#define IMX6_TEMPSENSE0_TEMP_CNT_MASK (0xfff << 8) +#define IMX6_TEMPSENSE0_FINISHED (1 << 2) +#define IMX6_TEMPSENSE0_MEASURE_TEMP (1 << 1) +#define IMX6_TEMPSENSE0_POWER_DOWN (1 << 0) + +#define IMX6_TEMPSENSE1 0x0190 +#define IMX6_TEMPSENSE1_MEASURE_FREQ 0xffff +#define IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT 0 #define OCOTP_MEM0 0x0480 #define OCOTP_ANA1 0x04e0 +/* Below TEMPSENSE2 is only for TEMPMON_IMX6SX */ +#define IMX6_TEMPSENSE2 0x0290 +#define IMX6_TEMPSENSE2_LOW_VALUE_SHIFT 0 +#define IMX6_TEMPSENSE2_LOW_VALUE_MASK 0xfff +#define IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT 16 +#define IMX6_TEMPSENSE2_PANIC_VALUE_MASK 0xfff0000 + +/* i.MX7 specific */ +#define IMX7_ANADIG_DIGPROG 0x800 +#define IMX7_TEMPSENSE0 0x300 +#define IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT 18 +#define IMX7_TEMPSENSE0_PANIC_ALARM_MASK (0x1ff << 18) +#define IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT 9 +#define IMX7_TEMPSENSE0_HIGH_ALARM_MASK (0x1ff << 9) +#define IMX7_TEMPSENSE0_LOW_ALARM_SHIFT 0 +#define IMX7_TEMPSENSE0_LOW_ALARM_MASK 0x1ff + +#define IMX7_TEMPSENSE1 0x310 +#define IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT 16 +#define IMX7_TEMPSENSE1_MEASURE_FREQ_MASK (0xffff << 16) +#define IMX7_TEMPSENSE1_FINISHED (1 << 11) +#define IMX7_TEMPSENSE1_MEASURE_TEMP (1 << 10) +#define IMX7_TEMPSENSE1_POWER_DOWN (1 << 9) +#define IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT 0 +#define IMX7_TEMPSENSE1_TEMP_VALUE_MASK 0x1ff + /* The driver supports 1 passive trip point and 1 critical trip point */ enum imx_thermal_trip { IMX_TRIP_PASSIVE, @@ -72,17 +89,114 @@ enum imx_thermal_trip { #define TEMPMON_IMX6Q 1 #define TEMPMON_IMX6SX 2 +#define TEMPMON_IMX7D 3 struct thermal_soc_data { u32 version; + + u32 sensor_ctrl; + u32 power_down_mask; + u32 measure_temp_mask; + + u32 measure_freq_ctrl; + u32 measure_freq_mask; + u32 measure_freq_shift; + + u32 temp_data; + u32 temp_value_mask; + u32 temp_value_shift; + u32 temp_valid_mask; + + u32 panic_alarm_ctrl; + u32 panic_alarm_mask; + u32 panic_alarm_shift; + + u32 high_alarm_ctrl; + u32 high_alarm_mask; + u32 high_alarm_shift; + + u32 low_alarm_ctrl; + u32 low_alarm_mask; + u32 low_alarm_shift; }; static struct thermal_soc_data thermal_imx6q_data = { .version = TEMPMON_IMX6Q, + + .sensor_ctrl = IMX6_TEMPSENSE0, + .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, + .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, + + .measure_freq_ctrl = IMX6_TEMPSENSE1, + .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, + .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, + + .temp_data = IMX6_TEMPSENSE0, + .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, + .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, + .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, + + .high_alarm_ctrl = IMX6_TEMPSENSE0, + .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, + .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, }; static struct thermal_soc_data thermal_imx6sx_data = { .version = TEMPMON_IMX6SX, + + .sensor_ctrl = IMX6_TEMPSENSE0, + .power_down_mask = IMX6_TEMPSENSE0_POWER_DOWN, + .measure_temp_mask = IMX6_TEMPSENSE0_MEASURE_TEMP, + + .measure_freq_ctrl = IMX6_TEMPSENSE1, + .measure_freq_shift = IMX6_TEMPSENSE1_MEASURE_FREQ_SHIFT, + .measure_freq_mask = IMX6_TEMPSENSE1_MEASURE_FREQ, + + .temp_data = IMX6_TEMPSENSE0, + .temp_value_mask = IMX6_TEMPSENSE0_TEMP_CNT_MASK, + .temp_value_shift = IMX6_TEMPSENSE0_TEMP_CNT_SHIFT, + .temp_valid_mask = IMX6_TEMPSENSE0_FINISHED, + + .high_alarm_ctrl = IMX6_TEMPSENSE0, + .high_alarm_mask = IMX6_TEMPSENSE0_ALARM_VALUE_MASK, + .high_alarm_shift = IMX6_TEMPSENSE0_ALARM_VALUE_SHIFT, + + .panic_alarm_ctrl = IMX6_TEMPSENSE2, + .panic_alarm_mask = IMX6_TEMPSENSE2_PANIC_VALUE_MASK, + .panic_alarm_shift = IMX6_TEMPSENSE2_PANIC_VALUE_SHIFT, + + .low_alarm_ctrl = IMX6_TEMPSENSE2, + .low_alarm_mask = IMX6_TEMPSENSE2_LOW_VALUE_MASK, + .low_alarm_shift = IMX6_TEMPSENSE2_LOW_VALUE_SHIFT, +}; + +static struct thermal_soc_data thermal_imx7d_data = { + .version = TEMPMON_IMX7D, + + .sensor_ctrl = IMX7_TEMPSENSE1, + .power_down_mask = IMX7_TEMPSENSE1_POWER_DOWN, + .measure_temp_mask = IMX7_TEMPSENSE1_MEASURE_TEMP, + + .measure_freq_ctrl = IMX7_TEMPSENSE1, + .measure_freq_shift = IMX7_TEMPSENSE1_MEASURE_FREQ_SHIFT, + .measure_freq_mask = IMX7_TEMPSENSE1_MEASURE_FREQ_MASK, + + .temp_data = IMX7_TEMPSENSE1, + .temp_value_mask = IMX7_TEMPSENSE1_TEMP_VALUE_MASK, + .temp_value_shift = IMX7_TEMPSENSE1_TEMP_VALUE_SHIFT, + .temp_valid_mask = IMX7_TEMPSENSE1_FINISHED, + + .panic_alarm_ctrl = IMX7_TEMPSENSE1, + .panic_alarm_mask = IMX7_TEMPSENSE0_PANIC_ALARM_MASK, + .panic_alarm_shift = IMX7_TEMPSENSE0_PANIC_ALARM_SHIFT, + + .high_alarm_ctrl = IMX7_TEMPSENSE0, + .high_alarm_mask = IMX7_TEMPSENSE0_HIGH_ALARM_MASK, + .high_alarm_shift = IMX7_TEMPSENSE0_HIGH_ALARM_SHIFT, + + .low_alarm_ctrl = IMX7_TEMPSENSE0, + .low_alarm_mask = IMX7_TEMPSENSE0_LOW_ALARM_MASK, + .low_alarm_shift = IMX7_TEMPSENSE0_LOW_ALARM_SHIFT, }; struct imx_thermal_data { @@ -107,31 +221,42 @@ struct imx_thermal_data { static void imx_set_panic_temp(struct imx_thermal_data *data, int panic_temp) { + const struct thermal_soc_data *soc_data = data->socdata; struct regmap *map = data->tempmon; int critical_value; critical_value = (data->c2 - panic_temp) / data->c1; - regmap_write(map, TEMPSENSE2 + REG_CLR, TEMPSENSE2_PANIC_VALUE_MASK); - regmap_write(map, TEMPSENSE2 + REG_SET, critical_value << - TEMPSENSE2_PANIC_VALUE_SHIFT); + + regmap_write(map, soc_data->panic_alarm_ctrl + REG_CLR, + soc_data->panic_alarm_mask); + regmap_write(map, soc_data->panic_alarm_ctrl + REG_SET, + critical_value << soc_data->panic_alarm_shift); } static void imx_set_alarm_temp(struct imx_thermal_data *data, int alarm_temp) { struct regmap *map = data->tempmon; + const struct thermal_soc_data *soc_data = data->socdata; int alarm_value; data->alarm_temp = alarm_temp; - alarm_value = (data->c2 - alarm_temp) / data->c1; - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_ALARM_VALUE_MASK); - regmap_write(map, TEMPSENSE0 + REG_SET, alarm_value << - TEMPSENSE0_ALARM_VALUE_SHIFT); + + if (data->socdata->version == TEMPMON_IMX7D) + alarm_value = alarm_temp / 1000 + data->c1 - 25; + else + alarm_value = (data->c2 - alarm_temp) / data->c1; + + regmap_write(map, soc_data->high_alarm_ctrl + REG_CLR, + soc_data->high_alarm_mask); + regmap_write(map, soc_data->high_alarm_ctrl + REG_SET, + alarm_value << soc_data->high_alarm_shift); } static int imx_get_temp(struct thermal_zone_device *tz, int *temp) { struct imx_thermal_data *data = tz->devdata; + const struct thermal_soc_data *soc_data = data->socdata; struct regmap *map = data->tempmon; unsigned int n_meas; bool wait; @@ -139,16 +264,18 @@ static int imx_get_temp(struct thermal_zone_device *tz, int *temp) if (data->mode == THERMAL_DEVICE_ENABLED) { /* Check if a measurement is currently in progress */ - regmap_read(map, TEMPSENSE0, &val); - wait = !(val & TEMPSENSE0_FINISHED); + regmap_read(map, soc_data->temp_data, &val); + wait = !(val & soc_data->temp_valid_mask); } else { /* * Every time we measure the temperature, we will power on the * temperature sensor, enable measurements, take a reading, * disable measurements, power off the temperature sensor. */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->power_down_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->measure_temp_mask); wait = true; } @@ -160,22 +287,28 @@ static int imx_get_temp(struct thermal_zone_device *tz, int *temp) if (wait) usleep_range(20, 50); - regmap_read(map, TEMPSENSE0, &val); + regmap_read(map, soc_data->temp_data, &val); if (data->mode != THERMAL_DEVICE_ENABLED) { - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->measure_temp_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->power_down_mask); } - if ((val & TEMPSENSE0_FINISHED) == 0) { + if ((val & soc_data->temp_valid_mask) == 0) { dev_dbg(&tz->device, "temp measurement never finished\n"); return -EAGAIN; } - n_meas = (val & TEMPSENSE0_TEMP_CNT_MASK) >> TEMPSENSE0_TEMP_CNT_SHIFT; + n_meas = (val & soc_data->temp_value_mask) + >> soc_data->temp_value_shift; /* See imx_init_calib() for formula derivation */ - *temp = data->c2 - n_meas * data->c1; + if (data->socdata->version == TEMPMON_IMX7D) + *temp = (n_meas - data->c1 + 25) * 1000; + else + *temp = data->c2 - n_meas * data->c1; /* Update alarm value to next higher trip point for TEMPMON_IMX6Q */ if (data->socdata->version == TEMPMON_IMX6Q) { @@ -219,21 +352,26 @@ static int imx_set_mode(struct thermal_zone_device *tz, { struct imx_thermal_data *data = tz->devdata; struct regmap *map = data->tempmon; + const struct thermal_soc_data *soc_data = data->socdata; if (mode == THERMAL_DEVICE_ENABLED) { tz->polling_delay = IMX_POLLING_DELAY; tz->passive_delay = IMX_PASSIVE_DELAY; - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->power_down_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->measure_temp_mask); if (!data->irq_enabled) { data->irq_enabled = true; enable_irq(data->irq); } } else { - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, soc_data->sensor_ctrl + REG_CLR, + soc_data->measure_temp_mask); + regmap_write(map, soc_data->sensor_ctrl + REG_SET, + soc_data->power_down_mask); tz->polling_delay = 0; tz->passive_delay = 0; @@ -355,6 +493,15 @@ static int imx_init_calib(struct platform_device *pdev, u32 ocotp_ana1) } /* + * On i.MX7D, we only use the calibration data at 25C to get the temp, + * Tmeas = ( Nmeas - n1) + 25; n1 is the fuse value for 25C. + */ + if (data->socdata->version == TEMPMON_IMX7D) { + data->c1 = (ocotp_ana1 >> 9) & 0x1ff; + return 0; + } + + /* * The sensor is calibrated at 25 °C (aka T1) and the value measured * (aka N1) at this temperature is provided in bits [31:20] in the * i.MX's OCOTP value ANA1. @@ -492,6 +639,7 @@ static irqreturn_t imx_thermal_alarm_irq_thread(int irq, void *dev) static const struct of_device_id of_imx_thermal_match[] = { { .compatible = "fsl,imx6q-tempmon", .data = &thermal_imx6q_data, }, { .compatible = "fsl,imx6sx-tempmon", .data = &thermal_imx6sx_data, }, + { .compatible = "fsl,imx7d-tempmon", .data = &thermal_imx7d_data, }, { /* end */ } }; MODULE_DEVICE_TABLE(of, of_imx_thermal_match); @@ -523,14 +671,15 @@ static int imx_thermal_probe(struct platform_device *pdev) /* make sure the IRQ flag is clear before enabling irq on i.MX6SX */ if (data->socdata->version == TEMPMON_IMX6SX) { - regmap_write(map, MISC1 + REG_CLR, MISC1_IRQ_TEMPHIGH | - MISC1_IRQ_TEMPLOW | MISC1_IRQ_TEMPPANIC); + regmap_write(map, IMX6_MISC1 + REG_CLR, + IMX6_MISC1_IRQ_TEMPHIGH | IMX6_MISC1_IRQ_TEMPLOW + | IMX6_MISC1_IRQ_TEMPPANIC); /* * reset value of LOW ALARM is incorrect, set it to lowest * value to avoid false trigger of low alarm. */ - regmap_write(map, TEMPSENSE2 + REG_SET, - TEMPSENSE2_LOW_VALUE_MASK); + regmap_write(map, data->socdata->low_alarm_ctrl + REG_SET, + data->socdata->low_alarm_mask); } data->irq = platform_get_irq(pdev, 0); @@ -557,11 +706,17 @@ static int imx_thermal_probe(struct platform_device *pdev) } /* Make sure sensor is in known good state for measurements */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); - regmap_write(map, MISC0 + REG_SET, MISC0_REFTOP_SELBIASOFF); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->power_down_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->measure_temp_mask); + regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, + data->socdata->measure_freq_mask); + if (data->socdata->version != TEMPMON_IMX7D) + regmap_write(map, IMX6_MISC0 + REG_SET, + IMX6_MISC0_REFTOP_SELBIASOFF); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->power_down_mask); data->policy = cpufreq_cpu_get(0); if (!data->policy) { @@ -626,16 +781,20 @@ static int imx_thermal_probe(struct platform_device *pdev) data->temp_passive / 1000); /* Enable measurements at ~ 10 Hz */ - regmap_write(map, TEMPSENSE1 + REG_CLR, TEMPSENSE1_MEASURE_FREQ); + regmap_write(map, data->socdata->measure_freq_ctrl + REG_CLR, + data->socdata->measure_freq_mask); measure_freq = DIV_ROUND_UP(32768, 10); /* 10 Hz */ - regmap_write(map, TEMPSENSE1 + REG_SET, measure_freq); + regmap_write(map, data->socdata->measure_freq_ctrl + REG_SET, + measure_freq << data->socdata->measure_freq_shift); imx_set_alarm_temp(data, data->temp_passive); if (data->socdata->version == TEMPMON_IMX6SX) imx_set_panic_temp(data, data->temp_critical); - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->power_down_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->measure_temp_mask); data->irq_enabled = true; data->mode = THERMAL_DEVICE_ENABLED; @@ -661,7 +820,8 @@ static int imx_thermal_remove(struct platform_device *pdev) struct regmap *map = data->tempmon; /* Disable measurements */ - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->power_down_mask); if (!IS_ERR(data->thermal_clk)) clk_disable_unprepare(data->thermal_clk); @@ -684,8 +844,10 @@ static int imx_thermal_suspend(struct device *dev) * temperature will be read as the thermal sensor is powered * down. */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_MEASURE_TEMP); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_POWER_DOWN); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->measure_temp_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->power_down_mask); data->mode = THERMAL_DEVICE_DISABLED; clk_disable_unprepare(data->thermal_clk); @@ -702,8 +864,10 @@ static int imx_thermal_resume(struct device *dev) if (ret) return ret; /* Enabled thermal sensor after resume */ - regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN); - regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP); + regmap_write(map, data->socdata->sensor_ctrl + REG_CLR, + data->socdata->power_down_mask); + regmap_write(map, data->socdata->sensor_ctrl + REG_SET, + data->socdata->measure_temp_mask); data->mode = THERMAL_DEVICE_ENABLED; return 0; diff --git a/drivers/thermal/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/int340x_thermal/int340x_thermal_zone.c index 953c839..9ec27ac 100644 --- a/drivers/thermal/int340x_thermal/int340x_thermal_zone.c +++ b/drivers/thermal/int340x_thermal/int340x_thermal_zone.c @@ -147,9 +147,9 @@ static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone, status = acpi_evaluate_integer(d->adev->handle, "GTSH", NULL, &hyst); if (ACPI_FAILURE(status)) - return -EIO; - - *temp = hyst * 100; + *temp = 0; + else + *temp = hyst * 100; return 0; } diff --git a/drivers/thermal/int340x_thermal/processor_thermal_device.c b/drivers/thermal/int340x_thermal/processor_thermal_device.c index 80bbf9c..284cf2c 100644 --- a/drivers/thermal/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/int340x_thermal/processor_thermal_device.c @@ -43,6 +43,9 @@ #define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C #define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C +/* GeminiLake thermal reporting device */ +#define PCI_DEVICE_ID_PROC_GLK_THERMAL 0x318C + struct power_config { u32 index; u32 min_uw; @@ -467,6 +470,7 @@ static const struct pci_device_id proc_thermal_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)}, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CNL_THERMAL)}, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CFL_THERMAL)}, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)}, { 0, }, }; diff --git a/drivers/thermal/mtk_thermal.c b/drivers/thermal/mtk_thermal.c index c75661a..0691f26 100644 --- a/drivers/thermal/mtk_thermal.c +++ b/drivers/thermal/mtk_thermal.c @@ -153,6 +153,12 @@ /* The number of sensing points per bank */ #define MT2712_NUM_SENSORS_PER_ZONE 4 +#define MT7622_TEMP_AUXADC_CHANNEL 11 +#define MT7622_NUM_SENSORS 1 +#define MT7622_NUM_ZONES 1 +#define MT7622_NUM_SENSORS_PER_ZONE 1 +#define MT7622_TS1 0 + struct mtk_thermal; struct thermal_bank_cfg { @@ -242,6 +248,12 @@ static const int mt2712_adcpnp[MT2712_NUM_SENSORS_PER_ZONE] = { static const int mt2712_mux_values[MT2712_NUM_SENSORS] = { 0, 1, 2, 3 }; +/* MT7622 thermal sensor data */ +static const int mt7622_bank_data[MT7622_NUM_SENSORS] = { MT7622_TS1, }; +static const int mt7622_msr[MT7622_NUM_SENSORS_PER_ZONE] = { TEMP_MSR0, }; +static const int mt7622_adcpnp[MT7622_NUM_SENSORS_PER_ZONE] = { TEMP_ADCPNP0, }; +static const int mt7622_mux_values[MT7622_NUM_SENSORS] = { 0, }; + /** * The MT8173 thermal controller has four banks. Each bank can read up to * four temperature sensors simultaneously. The MT8173 has a total of 5 @@ -329,6 +341,25 @@ static const struct mtk_thermal_data mt2712_thermal_data = { .sensor_mux_values = mt2712_mux_values, }; +/* + * MT7622 have only one sensing point which uses AUXADC Channel 11 for raw data + * access. + */ +static const struct mtk_thermal_data mt7622_thermal_data = { + .auxadc_channel = MT7622_TEMP_AUXADC_CHANNEL, + .num_banks = MT7622_NUM_ZONES, + .num_sensors = MT7622_NUM_SENSORS, + .bank_data = { + { + .num_sensors = 1, + .sensors = mt7622_bank_data, + }, + }, + .msr = mt7622_msr, + .adcpnp = mt7622_adcpnp, + .sensor_mux_values = mt7622_mux_values, +}; + /** * raw_to_mcelsius - convert a raw ADC value to mcelsius * @mt: The thermal controller @@ -631,6 +662,10 @@ static const struct of_device_id mtk_thermal_of_match[] = { { .compatible = "mediatek,mt2712-thermal", .data = (void *)&mt2712_thermal_data, + }, + { + .compatible = "mediatek,mt7622-thermal", + .data = (void *)&mt7622_thermal_data, }, { }, }; @@ -642,7 +677,6 @@ static int mtk_thermal_probe(struct platform_device *pdev) struct device_node *auxadc, *apmixedsys, *np = pdev->dev.of_node; struct mtk_thermal *mt; struct resource *res; - const struct of_device_id *of_id; u64 auxadc_phys_base, apmixed_phys_base; struct thermal_zone_device *tzdev; @@ -650,9 +684,7 @@ static int mtk_thermal_probe(struct platform_device *pdev) if (!mt) return -ENOMEM; - of_id = of_match_device(mtk_thermal_of_match, &pdev->dev); - if (of_id) - mt->conf = (const struct mtk_thermal_data *)of_id->data; + mt->conf = of_device_get_match_data(&pdev->dev); mt->clk_peri_therm = devm_clk_get(&pdev->dev, "therm"); if (IS_ERR(mt->clk_peri_therm)) diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 5798420..977a830 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -1,26 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * of-thermal.c - Generic Thermal Management device tree support. * * Copyright (C) 2013 Texas Instruments * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> - * - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * 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; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #include <linux/thermal.h> #include <linux/slab.h> diff --git a/drivers/thermal/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom-spmi-temp-alarm.c index 95f987d..ad4f3a8 100644 --- a/drivers/thermal/qcom-spmi-temp-alarm.c +++ b/drivers/thermal/qcom-spmi-temp-alarm.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2011-2015, 2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -11,6 +11,7 @@ * GNU General Public License for more details. */ +#include <linux/bitops.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/iio/consumer.h> @@ -29,13 +30,17 @@ #define QPNP_TM_REG_ALARM_CTRL 0x46 #define QPNP_TM_TYPE 0x09 -#define QPNP_TM_SUBTYPE 0x08 +#define QPNP_TM_SUBTYPE_GEN1 0x08 +#define QPNP_TM_SUBTYPE_GEN2 0x09 -#define STATUS_STAGE_MASK 0x03 +#define STATUS_GEN1_STAGE_MASK GENMASK(1, 0) +#define STATUS_GEN2_STATE_MASK GENMASK(6, 4) +#define STATUS_GEN2_STATE_SHIFT 4 -#define SHUTDOWN_CTRL1_THRESHOLD_MASK 0x03 +#define SHUTDOWN_CTRL1_OVERRIDE_MASK GENMASK(7, 6) +#define SHUTDOWN_CTRL1_THRESHOLD_MASK GENMASK(1, 0) -#define ALARM_CTRL_FORCE_ENABLE 0x80 +#define ALARM_CTRL_FORCE_ENABLE BIT(7) /* * Trip point values based on threshold control @@ -58,6 +63,7 @@ struct qpnp_tm_chip { struct regmap *map; struct thermal_zone_device *tz_dev; + unsigned int subtype; long temp; unsigned int thresh; unsigned int stage; @@ -66,6 +72,9 @@ struct qpnp_tm_chip { struct iio_channel *adc; }; +/* This array maps from GEN2 alarm state to GEN1 alarm stage */ +static const unsigned int alarm_state_map[8] = {0, 1, 1, 2, 2, 3, 3, 3}; + static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data) { unsigned int val; @@ -84,30 +93,59 @@ static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data) return regmap_write(chip->map, chip->base + addr, data); } +/** + * qpnp_tm_get_temp_stage() - return over-temperature stage + * @chip: Pointer to the qpnp_tm chip + * + * Return: stage (GEN1) or state (GEN2) on success, or errno on failure. + */ +static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip) +{ + int ret; + u8 reg = 0; + + ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®); + if (ret < 0) + return ret; + + if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) + ret = reg & STATUS_GEN1_STAGE_MASK; + else + ret = (reg & STATUS_GEN2_STATE_MASK) >> STATUS_GEN2_STATE_SHIFT; + + return ret; +} + /* * This function updates the internal temp value based on the * current thermal stage and threshold as well as the previous stage */ static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip) { - unsigned int stage; + unsigned int stage, stage_new, stage_old; int ret; - u8 reg = 0; - ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®); + ret = qpnp_tm_get_temp_stage(chip); if (ret < 0) return ret; + stage = ret; - stage = reg & STATUS_STAGE_MASK; + if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) { + stage_new = stage; + stage_old = chip->stage; + } else { + stage_new = alarm_state_map[stage]; + stage_old = alarm_state_map[chip->stage]; + } - if (stage > chip->stage) { + if (stage_new > stage_old) { /* increasing stage, use lower bound */ - chip->temp = (stage - 1) * TEMP_STAGE_STEP + + chip->temp = (stage_new - 1) * TEMP_STAGE_STEP + chip->thresh * TEMP_THRESH_STEP + TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; - } else if (stage < chip->stage) { + } else if (stage_new < stage_old) { /* decreasing stage, use upper bound */ - chip->temp = stage * TEMP_STAGE_STEP + + chip->temp = stage_new * TEMP_STAGE_STEP + chip->thresh * TEMP_THRESH_STEP - TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; } @@ -162,28 +200,37 @@ static irqreturn_t qpnp_tm_isr(int irq, void *data) */ static int qpnp_tm_init(struct qpnp_tm_chip *chip) { + unsigned int stage; int ret; - u8 reg; + u8 reg = 0; - chip->thresh = THRESH_MIN; + ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, ®); + if (ret < 0) + return ret; + + chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK; chip->temp = DEFAULT_TEMP; - ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®); + ret = qpnp_tm_get_temp_stage(chip); if (ret < 0) return ret; + chip->stage = ret; - chip->stage = reg & STATUS_STAGE_MASK; + stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1 + ? chip->stage : alarm_state_map[chip->stage]; - if (chip->stage) + if (stage) chip->temp = chip->thresh * TEMP_THRESH_STEP + - (chip->stage - 1) * TEMP_STAGE_STEP + + (stage - 1) * TEMP_STAGE_STEP + TEMP_THRESH_MIN; /* * Set threshold and disable software override of stage 2 and 3 * shutdowns. */ - reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK; + chip->thresh = THRESH_MIN; + reg &= ~(SHUTDOWN_CTRL1_OVERRIDE_MASK | SHUTDOWN_CTRL1_THRESHOLD_MASK); + reg |= chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK; ret = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg); if (ret < 0) return ret; @@ -246,12 +293,15 @@ static int qpnp_tm_probe(struct platform_device *pdev) return ret; } - if (type != QPNP_TM_TYPE || subtype != QPNP_TM_SUBTYPE) { + if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1 + && subtype != QPNP_TM_SUBTYPE_GEN2)) { dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n", type, subtype); return -ENODEV; } + chip->subtype = subtype; + ret = qpnp_tm_init(chip); if (ret < 0) { dev_err(&pdev->dev, "init failed\n"); diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index c2c3442..3440166 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -115,6 +115,7 @@ static int tsens_probe(struct platform_device *pdev) struct tsens_device *tmdev; const struct tsens_data *data; const struct of_device_id *id; + u32 num_sensors; if (pdev->dev.of_node) dev = &pdev->dev; @@ -129,19 +130,24 @@ static int tsens_probe(struct platform_device *pdev) else data = &data_8960; - if (data->num_sensors <= 0) { + num_sensors = data->num_sensors; + + if (np) + of_property_read_u32(np, "#qcom,sensors", &num_sensors); + + if (num_sensors <= 0) { dev_err(dev, "invalid number of sensors\n"); return -EINVAL; } tmdev = devm_kzalloc(dev, - struct_size(tmdev, sensor, data->num_sensors), + struct_size(tmdev, sensor, num_sensors), GFP_KERNEL); if (!tmdev) return -ENOMEM; tmdev->dev = dev; - tmdev->num_sensors = data->num_sensors; + tmdev->num_sensors = num_sensors; tmdev->ops = data->ops; for (i = 0; i < tmdev->num_sensors; i++) { if (data->hw_ids) diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c index 561a0a3..766521e 100644 --- a/drivers/thermal/rcar_gen3_thermal.c +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -132,7 +132,7 @@ static inline void rcar_gen3_thermal_write(struct rcar_gen3_thermal_tsc *tsc, #define RCAR3_THERMAL_GRAN 500 /* mili Celsius */ /* no idea where these constants come from */ -#define TJ_1 96 +#define TJ_1 116 #define TJ_3 -41 static void rcar_gen3_thermal_calc_coefs(struct equation_coefs *coef, @@ -146,7 +146,7 @@ static void rcar_gen3_thermal_calc_coefs(struct equation_coefs *coef, * Division is not scaled in BSP and if scaled it might overflow * the dividend (4095 * 4095 << 14 > INT_MAX) so keep it unscaled */ - tj_2 = (FIXPT_INT((ptat[1] - ptat[2]) * 137) + tj_2 = (FIXPT_INT((ptat[1] - ptat[2]) * 157) / (ptat[0] - ptat[2])) - FIXPT_INT(41); coef->a1 = FIXPT_DIV(FIXPT_INT(thcode[1] - thcode[2]), @@ -207,8 +207,8 @@ static int rcar_gen3_thermal_set_trips(void *devdata, int low, int high) { struct rcar_gen3_thermal_tsc *tsc = devdata; - low = clamp_val(low, -40000, 125000); - high = clamp_val(high, -40000, 125000); + low = clamp_val(low, -40000, 120000); + high = clamp_val(high, -40000, 120000); rcar_gen3_thermal_write(tsc, REG_GEN3_IRQTEMP1, rcar_gen3_thermal_mcelsius_to_temp(tsc, low)); @@ -329,6 +329,7 @@ static void rcar_gen3_thermal_init(struct rcar_gen3_thermal_tsc *tsc) static const struct of_device_id rcar_gen3_thermal_dt_ids[] = { { .compatible = "renesas,r8a7795-thermal", }, { .compatible = "renesas,r8a7796-thermal", }, + { .compatible = "renesas,r8a77965-thermal", }, {}, }; MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids); @@ -354,11 +355,11 @@ static int rcar_gen3_thermal_probe(struct platform_device *pdev) /* default values if FUSEs are missing */ /* TODO: Read values from hardware on supported platforms */ - int ptat[3] = { 2351, 1509, 435 }; + int ptat[3] = { 2631, 1509, 435 }; int thcode[TSC_MAX_NUM][3] = { - { 3248, 2800, 2221 }, - { 3245, 2795, 2216 }, - { 3250, 2805, 2237 }, + { 3397, 2800, 2221 }, + { 3393, 2795, 2216 }, + { 3389, 2805, 2237 }, }; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index 73e5fee..45fb284 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -58,10 +58,47 @@ struct rcar_thermal_common { spinlock_t lock; }; +struct rcar_thermal_chip { + unsigned int use_of_thermal : 1; + unsigned int has_filonoff : 1; + unsigned int irq_per_ch : 1; + unsigned int needs_suspend_resume : 1; + unsigned int nirqs; +}; + +static const struct rcar_thermal_chip rcar_thermal = { + .use_of_thermal = 0, + .has_filonoff = 1, + .irq_per_ch = 0, + .needs_suspend_resume = 0, + .nirqs = 1, +}; + +static const struct rcar_thermal_chip rcar_gen2_thermal = { + .use_of_thermal = 1, + .has_filonoff = 1, + .irq_per_ch = 0, + .needs_suspend_resume = 0, + .nirqs = 1, +}; + +static const struct rcar_thermal_chip rcar_gen3_thermal = { + .use_of_thermal = 1, + .has_filonoff = 0, + .irq_per_ch = 1, + .needs_suspend_resume = 1, + /* + * The Gen3 chip has 3 interrupts, but this driver uses only 2 + * interrupts to detect a temperature change, rise or fall. + */ + .nirqs = 2, +}; + struct rcar_thermal_priv { void __iomem *base; struct rcar_thermal_common *common; struct thermal_zone_device *zone; + const struct rcar_thermal_chip *chip; struct delayed_work work; struct mutex lock; struct list_head list; @@ -77,13 +114,20 @@ struct rcar_thermal_priv { #define rcar_priv_to_dev(priv) ((priv)->common->dev) #define rcar_has_irq_support(priv) ((priv)->common->base) #define rcar_id_to_shift(priv) ((priv)->id * 8) -#define rcar_of_data(dev) ((unsigned long)of_device_get_match_data(dev)) -#define rcar_use_of_thermal(dev) (rcar_of_data(dev) == USE_OF_THERMAL) -#define USE_OF_THERMAL 1 static const struct of_device_id rcar_thermal_dt_ids[] = { - { .compatible = "renesas,rcar-thermal", }, - { .compatible = "renesas,rcar-gen2-thermal", .data = (void *)USE_OF_THERMAL }, + { + .compatible = "renesas,rcar-thermal", + .data = &rcar_thermal, + }, + { + .compatible = "renesas,rcar-gen2-thermal", + .data = &rcar_gen2_thermal, + }, + { + .compatible = "renesas,thermal-r8a77995", + .data = &rcar_gen3_thermal, + }, {}, }; MODULE_DEVICE_TABLE(of, rcar_thermal_dt_ids); @@ -190,7 +234,8 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv) * enable IRQ */ if (rcar_has_irq_support(priv)) { - rcar_thermal_write(priv, FILONOFF, 0); + if (priv->chip->has_filonoff) + rcar_thermal_write(priv, FILONOFF, 0); /* enable Rising/Falling edge interrupt */ rcar_thermal_write(priv, POSNEG, 0x1); @@ -420,7 +465,7 @@ static int rcar_thermal_remove(struct platform_device *pdev) rcar_thermal_for_each_priv(priv, common) { rcar_thermal_irq_disable(priv); - if (rcar_use_of_thermal(dev)) + if (priv->chip->use_of_thermal) thermal_remove_hwmon_sysfs(priv->zone); else thermal_zone_device_unregister(priv->zone); @@ -438,6 +483,7 @@ static int rcar_thermal_probe(struct platform_device *pdev) struct rcar_thermal_priv *priv; struct device *dev = &pdev->dev; struct resource *res, *irq; + const struct rcar_thermal_chip *chip = of_device_get_match_data(dev); int mres = 0; int i; int ret = -ENODEV; @@ -457,19 +503,35 @@ static int rcar_thermal_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_get_sync(dev); - irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (irq) { - /* - * platform has IRQ support. - * Then, driver uses common registers - * rcar_has_irq_support() will be enabled - */ - res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); - common->base = devm_ioremap_resource(dev, res); - if (IS_ERR(common->base)) - return PTR_ERR(common->base); + for (i = 0; i < chip->nirqs; i++) { + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) + continue; + if (!common->base) { + /* + * platform has IRQ support. + * Then, driver uses common registers + * rcar_has_irq_support() will be enabled + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, + mres++); + common->base = devm_ioremap_resource(dev, res); + if (IS_ERR(common->base)) + return PTR_ERR(common->base); + + idle = 0; /* polling delay is not needed */ + } - idle = 0; /* polling delay is not needed */ + ret = devm_request_irq(dev, irq->start, rcar_thermal_irq, + IRQF_SHARED, dev_name(dev), common); + if (ret) { + dev_err(dev, "irq request failed\n "); + goto error_unregister; + } + + /* update ENR bits */ + if (chip->irq_per_ch) + enr_bits |= 1 << i; } for (i = 0;; i++) { @@ -491,6 +553,7 @@ static int rcar_thermal_probe(struct platform_device *pdev) priv->common = common; priv->id = i; + priv->chip = chip; mutex_init(&priv->lock); INIT_LIST_HEAD(&priv->list); INIT_DELAYED_WORK(&priv->work, rcar_thermal_work); @@ -498,7 +561,7 @@ static int rcar_thermal_probe(struct platform_device *pdev) if (ret < 0) goto error_unregister; - if (rcar_use_of_thermal(dev)) + if (chip->use_of_thermal) priv->zone = devm_thermal_zone_of_sensor_register( dev, i, priv, &rcar_thermal_zone_of_ops); @@ -515,7 +578,7 @@ static int rcar_thermal_probe(struct platform_device *pdev) goto error_unregister; } - if (rcar_use_of_thermal(dev)) { + if (chip->use_of_thermal) { /* * thermal_zone doesn't enable hwmon as default, * but, enable it here to keep compatible @@ -531,20 +594,12 @@ static int rcar_thermal_probe(struct platform_device *pdev) list_move_tail(&priv->list, &common->head); /* update ENR bits */ - enr_bits |= 3 << (i * 8); + if (!chip->irq_per_ch) + enr_bits |= 3 << (i * 8); } - /* enable temperature comparation */ - if (irq) { - ret = devm_request_irq(dev, irq->start, rcar_thermal_irq, 0, - dev_name(dev), common); - if (ret) { - dev_err(dev, "irq request failed\n "); - goto error_unregister; - } - + if (enr_bits) rcar_thermal_common_write(common, ENR, enr_bits); - } dev_info(dev, "%d sensor probed\n", i); @@ -556,9 +611,48 @@ error_unregister: return ret; } +#ifdef CONFIG_PM_SLEEP +static int rcar_thermal_suspend(struct device *dev) +{ + struct rcar_thermal_common *common = dev_get_drvdata(dev); + struct rcar_thermal_priv *priv = list_first_entry(&common->head, + typeof(*priv), list); + + if (priv->chip->needs_suspend_resume) { + rcar_thermal_common_write(common, ENR, 0); + rcar_thermal_irq_disable(priv); + rcar_thermal_bset(priv, THSCR, CPCTL, 0); + } + + return 0; +} + +static int rcar_thermal_resume(struct device *dev) +{ + struct rcar_thermal_common *common = dev_get_drvdata(dev); + struct rcar_thermal_priv *priv = list_first_entry(&common->head, + typeof(*priv), list); + int ret; + + if (priv->chip->needs_suspend_resume) { + ret = rcar_thermal_update_temp(priv); + if (ret < 0) + return ret; + rcar_thermal_irq_enable(priv); + rcar_thermal_common_write(common, ENR, 0x03); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(rcar_thermal_pm_ops, rcar_thermal_suspend, + rcar_thermal_resume); + static struct platform_driver rcar_thermal_driver = { .driver = { .name = "rcar_thermal", + .pm = &rcar_thermal_pm_ops, .of_match_table = rcar_thermal_dt_ids, }, .probe = rcar_thermal_probe, diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index ac83f72..a992e51 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -29,13 +29,14 @@ #include <linux/io.h> #include <linux/interrupt.h> #include <linux/module.h> -#include <linux/of.h> +#include <linux/of_device.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/platform_device.h> #include <linux/regulator/consumer.h> -#include "exynos_tmu.h" +#include <dt-bindings/thermal/thermal_exynos.h> + #include "../thermal_core.h" /* Exynos generic registers */ @@ -75,9 +76,6 @@ #define EXYNOS_TMU_THERM_TRIP_EN_SHIFT 12 #define EXYNOS_TMU_INTEN_RISE0_SHIFT 0 -#define EXYNOS_TMU_INTEN_RISE1_SHIFT 4 -#define EXYNOS_TMU_INTEN_RISE2_SHIFT 8 -#define EXYNOS_TMU_INTEN_RISE3_SHIFT 12 #define EXYNOS_TMU_INTEN_FALL0_SHIFT 16 #define EXYNOS_EMUL_TIME 0x57F0 @@ -98,11 +96,6 @@ #define EXYNOS4412_MUX_ADDR_SHIFT 20 /* Exynos5433 specific registers */ -#define EXYNOS5433_TMU_REG_CONTROL1 0x024 -#define EXYNOS5433_TMU_SAMPLING_INTERVAL 0x02c -#define EXYNOS5433_TMU_COUNTER_VALUE0 0x030 -#define EXYNOS5433_TMU_COUNTER_VALUE1 0x034 -#define EXYNOS5433_TMU_REG_CURRENT_TEMP1 0x044 #define EXYNOS5433_THD_TEMP_RISE3_0 0x050 #define EXYNOS5433_THD_TEMP_RISE7_4 0x054 #define EXYNOS5433_THD_TEMP_FALL3_0 0x060 @@ -123,27 +116,7 @@ #define EXYNOS5433_PD_DET_EN 1 -/*exynos5440 specific registers*/ -#define EXYNOS5440_TMU_S0_7_TRIM 0x000 -#define EXYNOS5440_TMU_S0_7_CTRL 0x020 -#define EXYNOS5440_TMU_S0_7_DEBUG 0x040 -#define EXYNOS5440_TMU_S0_7_TEMP 0x0f0 -#define EXYNOS5440_TMU_S0_7_TH0 0x110 -#define EXYNOS5440_TMU_S0_7_TH1 0x130 -#define EXYNOS5440_TMU_S0_7_TH2 0x150 -#define EXYNOS5440_TMU_S0_7_IRQEN 0x210 -#define EXYNOS5440_TMU_S0_7_IRQ 0x230 -/* exynos5440 common registers */ -#define EXYNOS5440_TMU_IRQ_STATUS 0x000 -#define EXYNOS5440_TMU_PMIN 0x004 - -#define EXYNOS5440_TMU_INTEN_RISE0_SHIFT 0 -#define EXYNOS5440_TMU_INTEN_RISE1_SHIFT 1 -#define EXYNOS5440_TMU_INTEN_RISE2_SHIFT 2 -#define EXYNOS5440_TMU_INTEN_RISE3_SHIFT 3 -#define EXYNOS5440_TMU_INTEN_FALL0_SHIFT 4 -#define EXYNOS5440_TMU_TH_RISE4_SHIFT 24 -#define EXYNOS5440_EFUSE_SWAP_OFFSET 8 +#define EXYNOS5433_G3D_BASE 0x10070000 /* Exynos7 specific registers */ #define EXYNOS7_THD_TEMP_RISE7_6 0x50 @@ -155,22 +128,32 @@ #define EXYNOS7_TMU_TEMP_MASK 0x1ff #define EXYNOS7_PD_DET_EN_SHIFT 23 #define EXYNOS7_TMU_INTEN_RISE0_SHIFT 0 -#define EXYNOS7_TMU_INTEN_RISE1_SHIFT 1 -#define EXYNOS7_TMU_INTEN_RISE2_SHIFT 2 -#define EXYNOS7_TMU_INTEN_RISE3_SHIFT 3 -#define EXYNOS7_TMU_INTEN_RISE4_SHIFT 4 -#define EXYNOS7_TMU_INTEN_RISE5_SHIFT 5 -#define EXYNOS7_TMU_INTEN_RISE6_SHIFT 6 -#define EXYNOS7_TMU_INTEN_RISE7_SHIFT 7 #define EXYNOS7_EMUL_DATA_SHIFT 7 #define EXYNOS7_EMUL_DATA_MASK 0x1ff +#define EXYNOS_FIRST_POINT_TRIM 25 +#define EXYNOS_SECOND_POINT_TRIM 85 + +#define EXYNOS_NOISE_CANCEL_MODE 4 + #define MCELSIUS 1000 + +enum soc_type { + SOC_ARCH_EXYNOS3250 = 1, + SOC_ARCH_EXYNOS4210, + SOC_ARCH_EXYNOS4412, + SOC_ARCH_EXYNOS5250, + SOC_ARCH_EXYNOS5260, + SOC_ARCH_EXYNOS5420, + SOC_ARCH_EXYNOS5420_TRIMINFO, + SOC_ARCH_EXYNOS5433, + SOC_ARCH_EXYNOS7, +}; + /** * struct exynos_tmu_data : A structure to hold the private data of the TMU driver * @id: identifier of the one instance of the TMU controller. - * @pdata: pointer to the tmu platform/configuration data * @base: base address of the single instance of the TMU controller. * @base_second: base address of the common registers of the TMU controller. * @irq: irq number of the TMU controller. @@ -180,8 +163,17 @@ * @clk: pointer to the clock structure. * @clk_sec: pointer to the clock structure for accessing the base_second. * @sclk: pointer to the clock structure for accessing the tmu special clk. + * @cal_type: calibration type for temperature + * @efuse_value: SoC defined fuse value + * @min_efuse_value: minimum valid trimming data + * @max_efuse_value: maximum valid trimming data * @temp_error1: fused value of the first point trim. * @temp_error2: fused value of the second point trim. + * @gain: gain of amplifier in the positive-TC generator block + * 0 < gain <= 15 + * @reference_voltage: reference voltage of amplifier + * in the positive-TC generator block + * 0 < reference_voltage <= 31 * @regulator: pointer to the TMU regulator structure. * @reg_conf: pointer to structure to register with core thermal. * @ntrip: number of supported trip points. @@ -194,7 +186,6 @@ */ struct exynos_tmu_data { int id; - struct exynos_tmu_platform_data *pdata; void __iomem *base; void __iomem *base_second; int irq; @@ -202,71 +193,42 @@ struct exynos_tmu_data { struct work_struct irq_work; struct mutex lock; struct clk *clk, *clk_sec, *sclk; + u32 cal_type; + u32 efuse_value; + u32 min_efuse_value; + u32 max_efuse_value; u16 temp_error1, temp_error2; + u8 gain; + u8 reference_voltage; struct regulator *regulator; struct thermal_zone_device *tzd; unsigned int ntrip; bool enabled; - int (*tmu_initialize)(struct platform_device *pdev); + void (*tmu_set_trip_temp)(struct exynos_tmu_data *data, int trip, + u8 temp); + void (*tmu_set_trip_hyst)(struct exynos_tmu_data *data, int trip, + u8 temp, u8 hyst); + void (*tmu_initialize)(struct platform_device *pdev); void (*tmu_control)(struct platform_device *pdev, bool on); int (*tmu_read)(struct exynos_tmu_data *data); void (*tmu_set_emulation)(struct exynos_tmu_data *data, int temp); void (*tmu_clear_irqs)(struct exynos_tmu_data *data); }; -static void exynos_report_trigger(struct exynos_tmu_data *p) -{ - char data[10], *envp[] = { data, NULL }; - struct thermal_zone_device *tz = p->tzd; - int temp; - unsigned int i; - - if (!tz) { - pr_err("No thermal zone device defined\n"); - return; - } - - thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); - - mutex_lock(&tz->lock); - /* Find the level for which trip happened */ - for (i = 0; i < of_thermal_get_ntrips(tz); i++) { - tz->ops->get_trip_temp(tz, i, &temp); - if (tz->last_temperature < temp) - break; - } - - snprintf(data, sizeof(data), "%u", i); - kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, envp); - mutex_unlock(&tz->lock); -} - /* * TMU treats temperature as a mapped temperature code. * The temperature is converted differently depending on the calibration type. */ static int temp_to_code(struct exynos_tmu_data *data, u8 temp) { - struct exynos_tmu_platform_data *pdata = data->pdata; - int temp_code; - - switch (pdata->cal_type) { - case TYPE_TWO_POINT_TRIMMING: - temp_code = (temp - pdata->first_point_trim) * - (data->temp_error2 - data->temp_error1) / - (pdata->second_point_trim - pdata->first_point_trim) + - data->temp_error1; - break; - case TYPE_ONE_POINT_TRIMMING: - temp_code = temp + data->temp_error1 - pdata->first_point_trim; - break; - default: - temp_code = temp + pdata->default_temp_offset; - break; - } + if (data->cal_type == TYPE_ONE_POINT_TRIMMING) + return temp + data->temp_error1 - EXYNOS_FIRST_POINT_TRIM; - return temp_code; + return (temp - EXYNOS_FIRST_POINT_TRIM) * + (data->temp_error2 - data->temp_error1) / + (EXYNOS_SECOND_POINT_TRIM - EXYNOS_FIRST_POINT_TRIM) + + data->temp_error1; } /* @@ -275,120 +237,123 @@ static int temp_to_code(struct exynos_tmu_data *data, u8 temp) */ static int code_to_temp(struct exynos_tmu_data *data, u16 temp_code) { - struct exynos_tmu_platform_data *pdata = data->pdata; - int temp; - - switch (pdata->cal_type) { - case TYPE_TWO_POINT_TRIMMING: - temp = (temp_code - data->temp_error1) * - (pdata->second_point_trim - pdata->first_point_trim) / - (data->temp_error2 - data->temp_error1) + - pdata->first_point_trim; - break; - case TYPE_ONE_POINT_TRIMMING: - temp = temp_code - data->temp_error1 + pdata->first_point_trim; - break; - default: - temp = temp_code - pdata->default_temp_offset; - break; - } + if (data->cal_type == TYPE_ONE_POINT_TRIMMING) + return temp_code - data->temp_error1 + EXYNOS_FIRST_POINT_TRIM; - return temp; + return (temp_code - data->temp_error1) * + (EXYNOS_SECOND_POINT_TRIM - EXYNOS_FIRST_POINT_TRIM) / + (data->temp_error2 - data->temp_error1) + + EXYNOS_FIRST_POINT_TRIM; } static void sanitize_temp_error(struct exynos_tmu_data *data, u32 trim_info) { - struct exynos_tmu_platform_data *pdata = data->pdata; + u16 tmu_temp_mask = + (data->soc == SOC_ARCH_EXYNOS7) ? EXYNOS7_TMU_TEMP_MASK + : EXYNOS_TMU_TEMP_MASK; - data->temp_error1 = trim_info & EXYNOS_TMU_TEMP_MASK; + data->temp_error1 = trim_info & tmu_temp_mask; data->temp_error2 = ((trim_info >> EXYNOS_TRIMINFO_85_SHIFT) & EXYNOS_TMU_TEMP_MASK); if (!data->temp_error1 || - (pdata->min_efuse_value > data->temp_error1) || - (data->temp_error1 > pdata->max_efuse_value)) - data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; + (data->min_efuse_value > data->temp_error1) || + (data->temp_error1 > data->max_efuse_value)) + data->temp_error1 = data->efuse_value & EXYNOS_TMU_TEMP_MASK; if (!data->temp_error2) data->temp_error2 = - (pdata->efuse_value >> EXYNOS_TRIMINFO_85_SHIFT) & + (data->efuse_value >> EXYNOS_TRIMINFO_85_SHIFT) & EXYNOS_TMU_TEMP_MASK; } -static u32 get_th_reg(struct exynos_tmu_data *data, u32 threshold, bool falling) +static int exynos_tmu_initialize(struct platform_device *pdev) { - struct thermal_zone_device *tz = data->tzd; + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + struct thermal_zone_device *tzd = data->tzd; const struct thermal_trip * const trips = - of_thermal_get_trip_points(tz); - unsigned long temp; - int i; + of_thermal_get_trip_points(tzd); + unsigned int status; + int ret = 0, temp, hyst; if (!trips) { - pr_err("%s: Cannot get trip points from of-thermal.c!\n", - __func__); - return 0; + dev_err(&pdev->dev, + "Cannot get trip points from device tree!\n"); + return -ENODEV; } - for (i = 0; i < of_thermal_get_ntrips(tz); i++) { - if (trips[i].type == THERMAL_TRIP_CRITICAL) - continue; - - temp = trips[i].temperature / MCELSIUS; - if (falling) - temp -= (trips[i].hysteresis / MCELSIUS); - else - threshold &= ~(0xff << 8 * i); - - threshold |= temp_to_code(data, temp) << 8 * i; + if (data->soc != SOC_ARCH_EXYNOS5433) /* FIXME */ + ret = tzd->ops->get_crit_temp(tzd, &temp); + if (ret) { + dev_err(&pdev->dev, + "No CRITICAL trip point defined in device tree!\n"); + goto out; } - return threshold; -} - -static int exynos_tmu_initialize(struct platform_device *pdev) -{ - struct exynos_tmu_data *data = platform_get_drvdata(pdev); - int ret; - - if (of_thermal_get_ntrips(data->tzd) > data->ntrip) { + if (of_thermal_get_ntrips(tzd) > data->ntrip) { dev_info(&pdev->dev, "More trip points than supported by this TMU.\n"); dev_info(&pdev->dev, "%d trip points should be configured in polling mode.\n", - (of_thermal_get_ntrips(data->tzd) - data->ntrip)); + (of_thermal_get_ntrips(tzd) - data->ntrip)); } mutex_lock(&data->lock); clk_enable(data->clk); if (!IS_ERR(data->clk_sec)) clk_enable(data->clk_sec); - ret = data->tmu_initialize(pdev); + + status = readb(data->base + EXYNOS_TMU_REG_STATUS); + if (!status) { + ret = -EBUSY; + } else { + int i, ntrips = + min_t(int, of_thermal_get_ntrips(tzd), data->ntrip); + + data->tmu_initialize(pdev); + + /* Write temperature code for rising and falling threshold */ + for (i = 0; i < ntrips; i++) { + /* Write temperature code for rising threshold */ + ret = tzd->ops->get_trip_temp(tzd, i, &temp); + if (ret) + goto err; + temp /= MCELSIUS; + data->tmu_set_trip_temp(data, i, temp); + + /* Write temperature code for falling threshold */ + ret = tzd->ops->get_trip_hyst(tzd, i, &hyst); + if (ret) + goto err; + hyst /= MCELSIUS; + data->tmu_set_trip_hyst(data, i, temp, hyst); + } + + data->tmu_clear_irqs(data); + } +err: clk_disable(data->clk); mutex_unlock(&data->lock); if (!IS_ERR(data->clk_sec)) clk_disable(data->clk_sec); - +out: return ret; } static u32 get_con_reg(struct exynos_tmu_data *data, u32 con) { - struct exynos_tmu_platform_data *pdata = data->pdata; - if (data->soc == SOC_ARCH_EXYNOS4412 || data->soc == SOC_ARCH_EXYNOS3250) con |= (EXYNOS4412_MUX_ADDR_VALUE << EXYNOS4412_MUX_ADDR_SHIFT); con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK << EXYNOS_TMU_REF_VOLTAGE_SHIFT); - con |= pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT; + con |= data->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT; con &= ~(EXYNOS_TMU_BUF_SLOPE_SEL_MASK << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); - con |= (pdata->gain << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); + con |= (data->gain << EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT); - if (pdata->noise_cancel_mode) { - con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << EXYNOS_TMU_TRIP_MODE_SHIFT); - con |= (pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT); - } + con &= ~(EXYNOS_TMU_TRIP_MODE_MASK << EXYNOS_TMU_TRIP_MODE_SHIFT); + con |= (EXYNOS_NOISE_CANCEL_MODE << EXYNOS_TMU_TRIP_MODE_SHIFT); return con; } @@ -405,65 +370,70 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on) mutex_unlock(&data->lock); } -static int exynos4210_tmu_initialize(struct platform_device *pdev) +static void exynos4210_tmu_set_trip_temp(struct exynos_tmu_data *data, + int trip, u8 temp) { - struct exynos_tmu_data *data = platform_get_drvdata(pdev); - struct thermal_zone_device *tz = data->tzd; const struct thermal_trip * const trips = - of_thermal_get_trip_points(tz); - int ret = 0, threshold_code, i; - unsigned long reference, temp; - unsigned int status; + of_thermal_get_trip_points(data->tzd); + u8 ref, th_code; - if (!trips) { - pr_err("%s: Cannot get trip points from of-thermal.c!\n", - __func__); - ret = -ENODEV; - goto out; - } + ref = trips[0].temperature / MCELSIUS; - status = readb(data->base + EXYNOS_TMU_REG_STATUS); - if (!status) { - ret = -EBUSY; - goto out; + if (trip == 0) { + th_code = temp_to_code(data, ref); + writeb(th_code, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); } + temp -= ref; + writeb(temp, data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + trip * 4); +} + +/* failing thresholds are not supported on Exynos4210 */ +static void exynos4210_tmu_set_trip_hyst(struct exynos_tmu_data *data, + int trip, u8 temp, u8 hyst) +{ +} + +static void exynos4210_tmu_initialize(struct platform_device *pdev) +{ + struct exynos_tmu_data *data = platform_get_drvdata(pdev); + sanitize_temp_error(data, readl(data->base + EXYNOS_TMU_REG_TRIMINFO)); +} - /* Write temperature code for threshold */ - reference = trips[0].temperature / MCELSIUS; - threshold_code = temp_to_code(data, reference); - if (threshold_code < 0) { - ret = threshold_code; - goto out; - } - writeb(threshold_code, data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP); +static void exynos4412_tmu_set_trip_temp(struct exynos_tmu_data *data, + int trip, u8 temp) +{ + u32 th, con; - for (i = 0; i < of_thermal_get_ntrips(tz); i++) { - temp = trips[i].temperature / MCELSIUS; - writeb(temp - reference, data->base + - EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4); + th = readl(data->base + EXYNOS_THD_TEMP_RISE); + th &= ~(0xff << 8 * trip); + th |= temp_to_code(data, temp) << 8 * trip; + writel(th, data->base + EXYNOS_THD_TEMP_RISE); + + if (trip == 3) { + con = readl(data->base + EXYNOS_TMU_REG_CONTROL); + con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); + writel(con, data->base + EXYNOS_TMU_REG_CONTROL); } +} - data->tmu_clear_irqs(data); -out: - return ret; +static void exynos4412_tmu_set_trip_hyst(struct exynos_tmu_data *data, + int trip, u8 temp, u8 hyst) +{ + u32 th; + + th = readl(data->base + EXYNOS_THD_TEMP_FALL); + th &= ~(0xff << 8 * trip); + if (hyst) + th |= temp_to_code(data, temp - hyst) << 8 * trip; + writel(th, data->base + EXYNOS_THD_TEMP_FALL); } -static int exynos4412_tmu_initialize(struct platform_device *pdev) +static void exynos4412_tmu_initialize(struct platform_device *pdev) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); - const struct thermal_trip * const trips = - of_thermal_get_trip_points(data->tzd); - unsigned int status, trim_info, con, ctrl, rising_threshold; - int ret = 0, threshold_code, i; - unsigned long crit_temp = 0; - - status = readb(data->base + EXYNOS_TMU_REG_STATUS); - if (!status) { - ret = -EBUSY; - goto out; - } + unsigned int trim_info, ctrl; if (data->soc == SOC_ARCH_EXYNOS3250 || data->soc == SOC_ARCH_EXYNOS4412 || @@ -485,58 +455,53 @@ static int exynos4412_tmu_initialize(struct platform_device *pdev) trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); sanitize_temp_error(data, trim_info); +} - /* Write temperature code for rising and falling threshold */ - rising_threshold = readl(data->base + EXYNOS_THD_TEMP_RISE); - rising_threshold = get_th_reg(data, rising_threshold, false); - writel(rising_threshold, data->base + EXYNOS_THD_TEMP_RISE); - writel(get_th_reg(data, 0, true), data->base + EXYNOS_THD_TEMP_FALL); - - data->tmu_clear_irqs(data); +static void exynos5433_tmu_set_trip_temp(struct exynos_tmu_data *data, + int trip, u8 temp) +{ + unsigned int reg_off, j; + u32 th; - /* if last threshold limit is also present */ - for (i = 0; i < of_thermal_get_ntrips(data->tzd); i++) { - if (trips[i].type == THERMAL_TRIP_CRITICAL) { - crit_temp = trips[i].temperature; - break; - } + if (trip > 3) { + reg_off = EXYNOS5433_THD_TEMP_RISE7_4; + j = trip - 4; + } else { + reg_off = EXYNOS5433_THD_TEMP_RISE3_0; + j = trip; } - if (i == of_thermal_get_ntrips(data->tzd)) { - pr_err("%s: No CRITICAL trip point defined at of-thermal.c!\n", - __func__); - ret = -EINVAL; - goto out; - } + th = readl(data->base + reg_off); + th &= ~(0xff << j * 8); + th |= (temp_to_code(data, temp) << j * 8); + writel(th, data->base + reg_off); +} - threshold_code = temp_to_code(data, crit_temp / MCELSIUS); - /* 1-4 level to be assigned in th0 reg */ - rising_threshold &= ~(0xff << 8 * i); - rising_threshold |= threshold_code << 8 * i; - writel(rising_threshold, data->base + EXYNOS_THD_TEMP_RISE); - con = readl(data->base + EXYNOS_TMU_REG_CONTROL); - con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); - writel(con, data->base + EXYNOS_TMU_REG_CONTROL); +static void exynos5433_tmu_set_trip_hyst(struct exynos_tmu_data *data, + int trip, u8 temp, u8 hyst) +{ + unsigned int reg_off, j; + u32 th; -out: - return ret; + if (trip > 3) { + reg_off = EXYNOS5433_THD_TEMP_FALL7_4; + j = trip - 4; + } else { + reg_off = EXYNOS5433_THD_TEMP_FALL3_0; + j = trip; + } + + th = readl(data->base + reg_off); + th &= ~(0xff << j * 8); + th |= (temp_to_code(data, temp - hyst) << j * 8); + writel(th, data->base + reg_off); } -static int exynos5433_tmu_initialize(struct platform_device *pdev) +static void exynos5433_tmu_initialize(struct platform_device *pdev) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); - struct exynos_tmu_platform_data *pdata = data->pdata; - struct thermal_zone_device *tz = data->tzd; - unsigned int status, trim_info; - unsigned int rising_threshold = 0, falling_threshold = 0; - int temp, temp_hist; - int ret = 0, threshold_code, i, sensor_id, cal_type; - - status = readb(data->base + EXYNOS_TMU_REG_STATUS); - if (!status) { - ret = -EBUSY; - goto out; - } + unsigned int trim_info; + int sensor_id, cal_type; trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); sanitize_temp_error(data, trim_info); @@ -552,227 +517,84 @@ static int exynos5433_tmu_initialize(struct platform_device *pdev) >> EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT; switch (cal_type) { - case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING: - pdata->cal_type = TYPE_ONE_POINT_TRIMMING; - break; case EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING: - pdata->cal_type = TYPE_TWO_POINT_TRIMMING; + data->cal_type = TYPE_TWO_POINT_TRIMMING; break; + case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING: default: - pdata->cal_type = TYPE_ONE_POINT_TRIMMING; + data->cal_type = TYPE_ONE_POINT_TRIMMING; break; } dev_info(&pdev->dev, "Calibration type is %d-point calibration\n", cal_type ? 2 : 1); - - /* Write temperature code for rising and falling threshold */ - for (i = 0; i < of_thermal_get_ntrips(tz); i++) { - int rising_reg_offset, falling_reg_offset; - int j = 0; - - switch (i) { - case 0: - case 1: - case 2: - case 3: - rising_reg_offset = EXYNOS5433_THD_TEMP_RISE3_0; - falling_reg_offset = EXYNOS5433_THD_TEMP_FALL3_0; - j = i; - break; - case 4: - case 5: - case 6: - case 7: - rising_reg_offset = EXYNOS5433_THD_TEMP_RISE7_4; - falling_reg_offset = EXYNOS5433_THD_TEMP_FALL7_4; - j = i - 4; - break; - default: - continue; - } - - /* Write temperature code for rising threshold */ - tz->ops->get_trip_temp(tz, i, &temp); - temp /= MCELSIUS; - threshold_code = temp_to_code(data, temp); - - rising_threshold = readl(data->base + rising_reg_offset); - rising_threshold |= (threshold_code << j * 8); - writel(rising_threshold, data->base + rising_reg_offset); - - /* Write temperature code for falling threshold */ - tz->ops->get_trip_hyst(tz, i, &temp_hist); - temp_hist = temp - (temp_hist / MCELSIUS); - threshold_code = temp_to_code(data, temp_hist); - - falling_threshold = readl(data->base + falling_reg_offset); - falling_threshold &= ~(0xff << j * 8); - falling_threshold |= (threshold_code << j * 8); - writel(falling_threshold, data->base + falling_reg_offset); - } - - data->tmu_clear_irqs(data); -out: - return ret; } -static int exynos5440_tmu_initialize(struct platform_device *pdev) +static void exynos7_tmu_set_trip_temp(struct exynos_tmu_data *data, + int trip, u8 temp) { - struct exynos_tmu_data *data = platform_get_drvdata(pdev); - unsigned int trim_info = 0, con, rising_threshold; - int threshold_code; - int crit_temp = 0; + unsigned int reg_off, bit_off; + u32 th; - /* - * For exynos5440 soc triminfo value is swapped between TMU0 and - * TMU2, so the below logic is needed. - */ - switch (data->id) { - case 0: - trim_info = readl(data->base + EXYNOS5440_EFUSE_SWAP_OFFSET + - EXYNOS5440_TMU_S0_7_TRIM); - break; - case 1: - trim_info = readl(data->base + EXYNOS5440_TMU_S0_7_TRIM); - break; - case 2: - trim_info = readl(data->base - EXYNOS5440_EFUSE_SWAP_OFFSET + - EXYNOS5440_TMU_S0_7_TRIM); - } - sanitize_temp_error(data, trim_info); + reg_off = ((7 - trip) / 2) * 4; + bit_off = ((8 - trip) % 2); - /* Write temperature code for rising and falling threshold */ - rising_threshold = readl(data->base + EXYNOS5440_TMU_S0_7_TH0); - rising_threshold = get_th_reg(data, rising_threshold, false); - writel(rising_threshold, data->base + EXYNOS5440_TMU_S0_7_TH0); - writel(0, data->base + EXYNOS5440_TMU_S0_7_TH1); + th = readl(data->base + EXYNOS7_THD_TEMP_RISE7_6 + reg_off); + th &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off)); + th |= temp_to_code(data, temp) << (16 * bit_off); + writel(th, data->base + EXYNOS7_THD_TEMP_RISE7_6 + reg_off); +} - data->tmu_clear_irqs(data); +static void exynos7_tmu_set_trip_hyst(struct exynos_tmu_data *data, + int trip, u8 temp, u8 hyst) +{ + unsigned int reg_off, bit_off; + u32 th; - /* if last threshold limit is also present */ - if (!data->tzd->ops->get_crit_temp(data->tzd, &crit_temp)) { - threshold_code = temp_to_code(data, crit_temp / MCELSIUS); - /* 5th level to be assigned in th2 reg */ - rising_threshold = - threshold_code << EXYNOS5440_TMU_TH_RISE4_SHIFT; - writel(rising_threshold, data->base + EXYNOS5440_TMU_S0_7_TH2); - con = readl(data->base + EXYNOS5440_TMU_S0_7_CTRL); - con |= (1 << EXYNOS_TMU_THERM_TRIP_EN_SHIFT); - writel(con, data->base + EXYNOS5440_TMU_S0_7_CTRL); - } - /* Clear the PMIN in the common TMU register */ - if (!data->id) - writel(0, data->base_second + EXYNOS5440_TMU_PMIN); + reg_off = ((7 - trip) / 2) * 4; + bit_off = ((8 - trip) % 2); - return 0; + th = readl(data->base + EXYNOS7_THD_TEMP_FALL7_6 + reg_off); + th &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off)); + th |= temp_to_code(data, temp - hyst) << (16 * bit_off); + writel(th, data->base + EXYNOS7_THD_TEMP_FALL7_6 + reg_off); } -static int exynos7_tmu_initialize(struct platform_device *pdev) +static void exynos7_tmu_initialize(struct platform_device *pdev) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); - struct thermal_zone_device *tz = data->tzd; - struct exynos_tmu_platform_data *pdata = data->pdata; - unsigned int status, trim_info; - unsigned int rising_threshold = 0, falling_threshold = 0; - int ret = 0, threshold_code, i; - int temp, temp_hist; - unsigned int reg_off, bit_off; - - status = readb(data->base + EXYNOS_TMU_REG_STATUS); - if (!status) { - ret = -EBUSY; - goto out; - } + unsigned int trim_info; trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO); - - data->temp_error1 = trim_info & EXYNOS7_TMU_TEMP_MASK; - if (!data->temp_error1 || - (pdata->min_efuse_value > data->temp_error1) || - (data->temp_error1 > pdata->max_efuse_value)) - data->temp_error1 = pdata->efuse_value & EXYNOS_TMU_TEMP_MASK; - - /* Write temperature code for rising and falling threshold */ - for (i = (of_thermal_get_ntrips(tz) - 1); i >= 0; i--) { - /* - * On exynos7 there are 4 rising and 4 falling threshold - * registers (0x50-0x5c and 0x60-0x6c respectively). Each - * register holds the value of two threshold levels (at bit - * offsets 0 and 16). Based on the fact that there are atmost - * eight possible trigger levels, calculate the register and - * bit offsets where the threshold levels are to be written. - * - * e.g. EXYNOS7_THD_TEMP_RISE7_6 (0x50) - * [24:16] - Threshold level 7 - * [8:0] - Threshold level 6 - * e.g. EXYNOS7_THD_TEMP_RISE5_4 (0x54) - * [24:16] - Threshold level 5 - * [8:0] - Threshold level 4 - * - * and similarly for falling thresholds. - * - * Based on the above, calculate the register and bit offsets - * for rising/falling threshold levels and populate them. - */ - reg_off = ((7 - i) / 2) * 4; - bit_off = ((8 - i) % 2); - - tz->ops->get_trip_temp(tz, i, &temp); - temp /= MCELSIUS; - - tz->ops->get_trip_hyst(tz, i, &temp_hist); - temp_hist = temp - (temp_hist / MCELSIUS); - - /* Set 9-bit temperature code for rising threshold levels */ - threshold_code = temp_to_code(data, temp); - rising_threshold = readl(data->base + - EXYNOS7_THD_TEMP_RISE7_6 + reg_off); - rising_threshold &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off)); - rising_threshold |= threshold_code << (16 * bit_off); - writel(rising_threshold, - data->base + EXYNOS7_THD_TEMP_RISE7_6 + reg_off); - - /* Set 9-bit temperature code for falling threshold levels */ - threshold_code = temp_to_code(data, temp_hist); - falling_threshold &= ~(EXYNOS7_TMU_TEMP_MASK << (16 * bit_off)); - falling_threshold |= threshold_code << (16 * bit_off); - writel(falling_threshold, - data->base + EXYNOS7_THD_TEMP_FALL7_6 + reg_off); - } - - data->tmu_clear_irqs(data); -out: - return ret; + sanitize_temp_error(data, trim_info); } static void exynos4210_tmu_control(struct platform_device *pdev, bool on) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); struct thermal_zone_device *tz = data->tzd; - unsigned int con, interrupt_en; + unsigned int con, interrupt_en = 0, i; con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL)); if (on) { - con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); - interrupt_en = - (of_thermal_is_trip_valid(tz, 3) - << EXYNOS_TMU_INTEN_RISE3_SHIFT) | - (of_thermal_is_trip_valid(tz, 2) - << EXYNOS_TMU_INTEN_RISE2_SHIFT) | - (of_thermal_is_trip_valid(tz, 1) - << EXYNOS_TMU_INTEN_RISE1_SHIFT) | - (of_thermal_is_trip_valid(tz, 0) - << EXYNOS_TMU_INTEN_RISE0_SHIFT); + for (i = 0; i < data->ntrip; i++) { + if (!of_thermal_is_trip_valid(tz, i)) + continue; + + interrupt_en |= + (1 << (EXYNOS_TMU_INTEN_RISE0_SHIFT + i * 4)); + } if (data->soc != SOC_ARCH_EXYNOS4210) interrupt_en |= interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; + + con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); } else { con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); - interrupt_en = 0; /* Disable all interrupts */ } + writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN); writel(con, data->base + EXYNOS_TMU_REG_CONTROL); } @@ -781,36 +603,25 @@ static void exynos5433_tmu_control(struct platform_device *pdev, bool on) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); struct thermal_zone_device *tz = data->tzd; - unsigned int con, interrupt_en, pd_det_en; + unsigned int con, interrupt_en = 0, pd_det_en, i; con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL)); if (on) { - con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); - interrupt_en = - (of_thermal_is_trip_valid(tz, 7) - << EXYNOS7_TMU_INTEN_RISE7_SHIFT) | - (of_thermal_is_trip_valid(tz, 6) - << EXYNOS7_TMU_INTEN_RISE6_SHIFT) | - (of_thermal_is_trip_valid(tz, 5) - << EXYNOS7_TMU_INTEN_RISE5_SHIFT) | - (of_thermal_is_trip_valid(tz, 4) - << EXYNOS7_TMU_INTEN_RISE4_SHIFT) | - (of_thermal_is_trip_valid(tz, 3) - << EXYNOS7_TMU_INTEN_RISE3_SHIFT) | - (of_thermal_is_trip_valid(tz, 2) - << EXYNOS7_TMU_INTEN_RISE2_SHIFT) | - (of_thermal_is_trip_valid(tz, 1) - << EXYNOS7_TMU_INTEN_RISE1_SHIFT) | - (of_thermal_is_trip_valid(tz, 0) - << EXYNOS7_TMU_INTEN_RISE0_SHIFT); + for (i = 0; i < data->ntrip; i++) { + if (!of_thermal_is_trip_valid(tz, i)) + continue; + + interrupt_en |= + (1 << (EXYNOS7_TMU_INTEN_RISE0_SHIFT + i)); + } interrupt_en |= interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; - } else { + + con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); + } else con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); - interrupt_en = 0; /* Disable all interrupts */ - } pd_det_en = on ? EXYNOS5433_PD_DET_EN : 0; @@ -819,70 +630,31 @@ static void exynos5433_tmu_control(struct platform_device *pdev, bool on) writel(con, data->base + EXYNOS_TMU_REG_CONTROL); } -static void exynos5440_tmu_control(struct platform_device *pdev, bool on) -{ - struct exynos_tmu_data *data = platform_get_drvdata(pdev); - struct thermal_zone_device *tz = data->tzd; - unsigned int con, interrupt_en; - - con = get_con_reg(data, readl(data->base + EXYNOS5440_TMU_S0_7_CTRL)); - - if (on) { - con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); - interrupt_en = - (of_thermal_is_trip_valid(tz, 3) - << EXYNOS5440_TMU_INTEN_RISE3_SHIFT) | - (of_thermal_is_trip_valid(tz, 2) - << EXYNOS5440_TMU_INTEN_RISE2_SHIFT) | - (of_thermal_is_trip_valid(tz, 1) - << EXYNOS5440_TMU_INTEN_RISE1_SHIFT) | - (of_thermal_is_trip_valid(tz, 0) - << EXYNOS5440_TMU_INTEN_RISE0_SHIFT); - interrupt_en |= - interrupt_en << EXYNOS5440_TMU_INTEN_FALL0_SHIFT; - } else { - con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); - interrupt_en = 0; /* Disable all interrupts */ - } - writel(interrupt_en, data->base + EXYNOS5440_TMU_S0_7_IRQEN); - writel(con, data->base + EXYNOS5440_TMU_S0_7_CTRL); -} - static void exynos7_tmu_control(struct platform_device *pdev, bool on) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); struct thermal_zone_device *tz = data->tzd; - unsigned int con, interrupt_en; + unsigned int con, interrupt_en = 0, i; con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL)); if (on) { - con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); - con |= (1 << EXYNOS7_PD_DET_EN_SHIFT); - interrupt_en = - (of_thermal_is_trip_valid(tz, 7) - << EXYNOS7_TMU_INTEN_RISE7_SHIFT) | - (of_thermal_is_trip_valid(tz, 6) - << EXYNOS7_TMU_INTEN_RISE6_SHIFT) | - (of_thermal_is_trip_valid(tz, 5) - << EXYNOS7_TMU_INTEN_RISE5_SHIFT) | - (of_thermal_is_trip_valid(tz, 4) - << EXYNOS7_TMU_INTEN_RISE4_SHIFT) | - (of_thermal_is_trip_valid(tz, 3) - << EXYNOS7_TMU_INTEN_RISE3_SHIFT) | - (of_thermal_is_trip_valid(tz, 2) - << EXYNOS7_TMU_INTEN_RISE2_SHIFT) | - (of_thermal_is_trip_valid(tz, 1) - << EXYNOS7_TMU_INTEN_RISE1_SHIFT) | - (of_thermal_is_trip_valid(tz, 0) - << EXYNOS7_TMU_INTEN_RISE0_SHIFT); + for (i = 0; i < data->ntrip; i++) { + if (!of_thermal_is_trip_valid(tz, i)) + continue; + + interrupt_en |= + (1 << (EXYNOS7_TMU_INTEN_RISE0_SHIFT + i)); + } interrupt_en |= interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT; + + con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT); + con |= (1 << EXYNOS7_PD_DET_EN_SHIFT); } else { con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT); con &= ~(1 << EXYNOS7_PD_DET_EN_SHIFT); - interrupt_en = 0; /* Disable all interrupts */ } writel(interrupt_en, data->base + EXYNOS7_TMU_REG_INTEN); @@ -896,6 +668,12 @@ static int exynos_get_temp(void *p, int *temp) if (!data || !data->tmu_read || !data->enabled) return -EINVAL; + else if (!data->enabled) + /* + * Called too early, probably + * from thermal_zone_of_sensor_register(). + */ + return -EAGAIN; mutex_lock(&data->lock); clk_enable(data->clk); @@ -919,10 +697,8 @@ static u32 get_emul_con_reg(struct exynos_tmu_data *data, unsigned int val, if (temp) { temp /= MCELSIUS; - if (data->soc != SOC_ARCH_EXYNOS5440) { - val &= ~(EXYNOS_EMUL_TIME_MASK << EXYNOS_EMUL_TIME_SHIFT); - val |= (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT); - } + val &= ~(EXYNOS_EMUL_TIME_MASK << EXYNOS_EMUL_TIME_SHIFT); + val |= (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT); if (data->soc == SOC_ARCH_EXYNOS7) { val &= ~(EXYNOS7_EMUL_DATA_MASK << EXYNOS7_EMUL_DATA_SHIFT); @@ -963,16 +739,6 @@ static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data, writel(val, data->base + emul_con); } -static void exynos5440_tmu_set_emulation(struct exynos_tmu_data *data, - int temp) -{ - unsigned int val; - - val = readl(data->base + EXYNOS5440_TMU_S0_7_DEBUG); - val = get_emul_con_reg(data, val, temp); - writel(val, data->base + EXYNOS5440_TMU_S0_7_DEBUG); -} - static int exynos_tmu_set_emulation(void *drv_data, int temp) { struct exynos_tmu_data *data = drv_data; @@ -995,7 +761,6 @@ out: } #else #define exynos4412_tmu_set_emulation NULL -#define exynos5440_tmu_set_emulation NULL static int exynos_tmu_set_emulation(void *drv_data, int temp) { return -EINVAL; } #endif /* CONFIG_THERMAL_EMULATION */ @@ -1013,11 +778,6 @@ static int exynos4412_tmu_read(struct exynos_tmu_data *data) return readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP); } -static int exynos5440_tmu_read(struct exynos_tmu_data *data) -{ - return readb(data->base + EXYNOS5440_TMU_S0_7_TEMP); -} - static int exynos7_tmu_read(struct exynos_tmu_data *data) { return readw(data->base + EXYNOS_TMU_REG_CURRENT_TEMP) & @@ -1028,20 +788,14 @@ static void exynos_tmu_work(struct work_struct *work) { struct exynos_tmu_data *data = container_of(work, struct exynos_tmu_data, irq_work); - unsigned int val_type; if (!IS_ERR(data->clk_sec)) clk_enable(data->clk_sec); - /* Find which sensor generated this interrupt */ - if (data->soc == SOC_ARCH_EXYNOS5440) { - val_type = readl(data->base_second + EXYNOS5440_TMU_IRQ_STATUS); - if (!((val_type >> data->id) & 0x1)) - goto out; - } if (!IS_ERR(data->clk_sec)) clk_disable(data->clk_sec); - exynos_report_trigger(data); + thermal_zone_device_update(data->tzd, THERMAL_EVENT_UNSPECIFIED); + mutex_lock(&data->lock); clk_enable(data->clk); @@ -1050,7 +804,6 @@ static void exynos_tmu_work(struct work_struct *work) clk_disable(data->clk); mutex_unlock(&data->lock); -out: enable_irq(data->irq); } @@ -1085,15 +838,6 @@ static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data) writel(val_irq, data->base + tmu_intclear); } -static void exynos5440_tmu_clear_irqs(struct exynos_tmu_data *data) -{ - unsigned int val_irq; - - val_irq = readl(data->base + EXYNOS5440_TMU_S0_7_IRQ); - /* clear the interrupts */ - writel(val_irq, data->base + EXYNOS5440_TMU_S0_7_IRQ); -} - static irqreturn_t exynos_tmu_irq(int irq, void *id) { struct exynos_tmu_data *data = id; @@ -1105,86 +849,41 @@ static irqreturn_t exynos_tmu_irq(int irq, void *id) } static const struct of_device_id exynos_tmu_match[] = { - { .compatible = "samsung,exynos3250-tmu", }, - { .compatible = "samsung,exynos4210-tmu", }, - { .compatible = "samsung,exynos4412-tmu", }, - { .compatible = "samsung,exynos5250-tmu", }, - { .compatible = "samsung,exynos5260-tmu", }, - { .compatible = "samsung,exynos5420-tmu", }, - { .compatible = "samsung,exynos5420-tmu-ext-triminfo", }, - { .compatible = "samsung,exynos5433-tmu", }, - { .compatible = "samsung,exynos5440-tmu", }, - { .compatible = "samsung,exynos7-tmu", }, - { /* sentinel */ }, + { + .compatible = "samsung,exynos3250-tmu", + .data = (const void *)SOC_ARCH_EXYNOS3250, + }, { + .compatible = "samsung,exynos4210-tmu", + .data = (const void *)SOC_ARCH_EXYNOS4210, + }, { + .compatible = "samsung,exynos4412-tmu", + .data = (const void *)SOC_ARCH_EXYNOS4412, + }, { + .compatible = "samsung,exynos5250-tmu", + .data = (const void *)SOC_ARCH_EXYNOS5250, + }, { + .compatible = "samsung,exynos5260-tmu", + .data = (const void *)SOC_ARCH_EXYNOS5260, + }, { + .compatible = "samsung,exynos5420-tmu", + .data = (const void *)SOC_ARCH_EXYNOS5420, + }, { + .compatible = "samsung,exynos5420-tmu-ext-triminfo", + .data = (const void *)SOC_ARCH_EXYNOS5420_TRIMINFO, + }, { + .compatible = "samsung,exynos5433-tmu", + .data = (const void *)SOC_ARCH_EXYNOS5433, + }, { + .compatible = "samsung,exynos7-tmu", + .data = (const void *)SOC_ARCH_EXYNOS7, + }, + { }, }; MODULE_DEVICE_TABLE(of, exynos_tmu_match); -static int exynos_of_get_soc_type(struct device_node *np) -{ - if (of_device_is_compatible(np, "samsung,exynos3250-tmu")) - return SOC_ARCH_EXYNOS3250; - else if (of_device_is_compatible(np, "samsung,exynos4210-tmu")) - return SOC_ARCH_EXYNOS4210; - else if (of_device_is_compatible(np, "samsung,exynos4412-tmu")) - return SOC_ARCH_EXYNOS4412; - else if (of_device_is_compatible(np, "samsung,exynos5250-tmu")) - return SOC_ARCH_EXYNOS5250; - else if (of_device_is_compatible(np, "samsung,exynos5260-tmu")) - return SOC_ARCH_EXYNOS5260; - else if (of_device_is_compatible(np, "samsung,exynos5420-tmu")) - return SOC_ARCH_EXYNOS5420; - else if (of_device_is_compatible(np, - "samsung,exynos5420-tmu-ext-triminfo")) - return SOC_ARCH_EXYNOS5420_TRIMINFO; - else if (of_device_is_compatible(np, "samsung,exynos5433-tmu")) - return SOC_ARCH_EXYNOS5433; - else if (of_device_is_compatible(np, "samsung,exynos5440-tmu")) - return SOC_ARCH_EXYNOS5440; - else if (of_device_is_compatible(np, "samsung,exynos7-tmu")) - return SOC_ARCH_EXYNOS7; - - return -EINVAL; -} - -static int exynos_of_sensor_conf(struct device_node *np, - struct exynos_tmu_platform_data *pdata) -{ - u32 value; - int ret; - - of_node_get(np); - - ret = of_property_read_u32(np, "samsung,tmu_gain", &value); - pdata->gain = (u8)value; - of_property_read_u32(np, "samsung,tmu_reference_voltage", &value); - pdata->reference_voltage = (u8)value; - of_property_read_u32(np, "samsung,tmu_noise_cancel_mode", &value); - pdata->noise_cancel_mode = (u8)value; - - of_property_read_u32(np, "samsung,tmu_efuse_value", - &pdata->efuse_value); - of_property_read_u32(np, "samsung,tmu_min_efuse_value", - &pdata->min_efuse_value); - of_property_read_u32(np, "samsung,tmu_max_efuse_value", - &pdata->max_efuse_value); - - of_property_read_u32(np, "samsung,tmu_first_point_trim", &value); - pdata->first_point_trim = (u8)value; - of_property_read_u32(np, "samsung,tmu_second_point_trim", &value); - pdata->second_point_trim = (u8)value; - of_property_read_u32(np, "samsung,tmu_default_temp_offset", &value); - pdata->default_temp_offset = (u8)value; - - of_property_read_u32(np, "samsung,tmu_cal_type", &pdata->cal_type); - - of_node_put(np); - return 0; -} - static int exynos_map_dt_data(struct platform_device *pdev) { struct exynos_tmu_data *data = platform_get_drvdata(pdev); - struct exynos_tmu_platform_data *pdata; struct resource res; if (!data || !pdev->dev.of_node) @@ -1211,23 +910,22 @@ static int exynos_map_dt_data(struct platform_device *pdev) return -EADDRNOTAVAIL; } - pdata = devm_kzalloc(&pdev->dev, - sizeof(struct exynos_tmu_platform_data), - GFP_KERNEL); - if (!pdata) - return -ENOMEM; - - exynos_of_sensor_conf(pdev->dev.of_node, pdata); - data->pdata = pdata; - data->soc = exynos_of_get_soc_type(pdev->dev.of_node); + data->soc = (enum soc_type)of_device_get_match_data(&pdev->dev); switch (data->soc) { case SOC_ARCH_EXYNOS4210: + data->tmu_set_trip_temp = exynos4210_tmu_set_trip_temp; + data->tmu_set_trip_hyst = exynos4210_tmu_set_trip_hyst; data->tmu_initialize = exynos4210_tmu_initialize; data->tmu_control = exynos4210_tmu_control; data->tmu_read = exynos4210_tmu_read; data->tmu_clear_irqs = exynos4210_tmu_clear_irqs; data->ntrip = 4; + data->gain = 15; + data->reference_voltage = 7; + data->efuse_value = 55; + data->min_efuse_value = 40; + data->max_efuse_value = 100; break; case SOC_ARCH_EXYNOS3250: case SOC_ARCH_EXYNOS4412: @@ -1235,48 +933,69 @@ static int exynos_map_dt_data(struct platform_device *pdev) case SOC_ARCH_EXYNOS5260: case SOC_ARCH_EXYNOS5420: case SOC_ARCH_EXYNOS5420_TRIMINFO: + data->tmu_set_trip_temp = exynos4412_tmu_set_trip_temp; + data->tmu_set_trip_hyst = exynos4412_tmu_set_trip_hyst; data->tmu_initialize = exynos4412_tmu_initialize; data->tmu_control = exynos4210_tmu_control; data->tmu_read = exynos4412_tmu_read; data->tmu_set_emulation = exynos4412_tmu_set_emulation; data->tmu_clear_irqs = exynos4210_tmu_clear_irqs; data->ntrip = 4; + data->gain = 8; + data->reference_voltage = 16; + data->efuse_value = 55; + if (data->soc != SOC_ARCH_EXYNOS5420 && + data->soc != SOC_ARCH_EXYNOS5420_TRIMINFO) + data->min_efuse_value = 40; + else + data->min_efuse_value = 0; + data->max_efuse_value = 100; break; case SOC_ARCH_EXYNOS5433: + data->tmu_set_trip_temp = exynos5433_tmu_set_trip_temp; + data->tmu_set_trip_hyst = exynos5433_tmu_set_trip_hyst; data->tmu_initialize = exynos5433_tmu_initialize; data->tmu_control = exynos5433_tmu_control; data->tmu_read = exynos4412_tmu_read; data->tmu_set_emulation = exynos4412_tmu_set_emulation; data->tmu_clear_irqs = exynos4210_tmu_clear_irqs; data->ntrip = 8; - break; - case SOC_ARCH_EXYNOS5440: - data->tmu_initialize = exynos5440_tmu_initialize; - data->tmu_control = exynos5440_tmu_control; - data->tmu_read = exynos5440_tmu_read; - data->tmu_set_emulation = exynos5440_tmu_set_emulation; - data->tmu_clear_irqs = exynos5440_tmu_clear_irqs; - data->ntrip = 4; + data->gain = 8; + if (res.start == EXYNOS5433_G3D_BASE) + data->reference_voltage = 23; + else + data->reference_voltage = 16; + data->efuse_value = 75; + data->min_efuse_value = 40; + data->max_efuse_value = 150; break; case SOC_ARCH_EXYNOS7: + data->tmu_set_trip_temp = exynos7_tmu_set_trip_temp; + data->tmu_set_trip_hyst = exynos7_tmu_set_trip_hyst; data->tmu_initialize = exynos7_tmu_initialize; data->tmu_control = exynos7_tmu_control; data->tmu_read = exynos7_tmu_read; data->tmu_set_emulation = exynos4412_tmu_set_emulation; data->tmu_clear_irqs = exynos4210_tmu_clear_irqs; data->ntrip = 8; + data->gain = 9; + data->reference_voltage = 17; + data->efuse_value = 75; + data->min_efuse_value = 15; + data->max_efuse_value = 100; break; default: dev_err(&pdev->dev, "Platform not supported\n"); return -EINVAL; } + data->cal_type = TYPE_ONE_POINT_TRIMMING; + /* * Check if the TMU shares some registers and then try to map the * memory of common registers. */ - if (data->soc != SOC_ARCH_EXYNOS5420_TRIMINFO && - data->soc != SOC_ARCH_EXYNOS5440) + if (data->soc != SOC_ARCH_EXYNOS5420_TRIMINFO) return 0; if (of_address_to_resource(pdev->dev.of_node, 1, &res)) { diff --git a/drivers/thermal/samsung/exynos_tmu.h b/drivers/thermal/samsung/exynos_tmu.h deleted file mode 100644 index 5149c2a..0000000 --- a/drivers/thermal/samsung/exynos_tmu.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * exynos_tmu.h - Samsung EXYNOS TMU (Thermal Management Unit) - * - * Copyright (C) 2011 Samsung Electronics - * Donggeun Kim <dg77.kim@samsung.com> - * Amit Daniel Kachhap <amit.daniel@samsung.com> - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _EXYNOS_TMU_H -#define _EXYNOS_TMU_H -#include <linux/cpu_cooling.h> -#include <dt-bindings/thermal/thermal_exynos.h> - -enum soc_type { - SOC_ARCH_EXYNOS3250 = 1, - SOC_ARCH_EXYNOS4210, - SOC_ARCH_EXYNOS4412, - SOC_ARCH_EXYNOS5250, - SOC_ARCH_EXYNOS5260, - SOC_ARCH_EXYNOS5420, - SOC_ARCH_EXYNOS5420_TRIMINFO, - SOC_ARCH_EXYNOS5433, - SOC_ARCH_EXYNOS5440, - SOC_ARCH_EXYNOS7, -}; - -/** - * struct exynos_tmu_platform_data - * @gain: gain of amplifier in the positive-TC generator block - * 0 < gain <= 15 - * @reference_voltage: reference voltage of amplifier - * in the positive-TC generator block - * 0 < reference_voltage <= 31 - * @noise_cancel_mode: noise cancellation mode - * 000, 100, 101, 110 and 111 can be different modes - * @type: determines the type of SOC - * @efuse_value: platform defined fuse value - * @min_efuse_value: minimum valid trimming data - * @max_efuse_value: maximum valid trimming data - * @default_temp_offset: default temperature offset in case of no trimming - * @cal_type: calibration type for temperature - * - * This structure is required for configuration of exynos_tmu driver. - */ -struct exynos_tmu_platform_data { - u8 gain; - u8 reference_voltage; - u8 noise_cancel_mode; - - u32 efuse_value; - u32 min_efuse_value; - u32 max_efuse_value; - u8 first_point_trim; - u8 second_point_trim; - u8 default_temp_offset; - - enum soc_type type; - u32 cal_type; -}; - -#endif /* _EXYNOS_TMU_H */ diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index e1fc2b0..ed28110 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -241,31 +241,6 @@ struct tegra_soctherm { }; /** - * clk_writel() - writes a value to a CAR register - * @ts: pointer to a struct tegra_soctherm - * @v: the value to write - * @reg: the register offset - * - * Writes @v to @reg. No return value. - */ -static inline void clk_writel(struct tegra_soctherm *ts, u32 value, u32 reg) -{ - writel(value, (ts->clk_regs + reg)); -} - -/** - * clk_readl() - reads specified register from CAR IP block - * @ts: pointer to a struct tegra_soctherm - * @reg: register address to be read - * - * Return: the value of the register - */ -static inline u32 clk_readl(struct tegra_soctherm *ts, u32 reg) -{ - return readl(ts->clk_regs + reg); -} - -/** * ccroc_writel() - writes a value to a CCROC register * @ts: pointer to a struct tegra_soctherm * @v: the value to write @@ -926,7 +901,7 @@ static int throt_set_cdev_state(struct thermal_cooling_device *cdev, return 0; } -static struct thermal_cooling_device_ops throt_cooling_ops = { +static const struct thermal_cooling_device_ops throt_cooling_ops = { .get_max_state = throt_get_cdev_max_state, .get_cur_state = throt_get_cdev_cur_state, .set_cur_state = throt_set_cdev_state, @@ -1207,9 +1182,9 @@ static void tegra_soctherm_throttle(struct device *dev) } else { writel(v, ts->regs + THROT_GLOBAL_CFG); - v = clk_readl(ts, CAR_SUPER_CCLKG_DIVIDER); + v = readl(ts->clk_regs + CAR_SUPER_CCLKG_DIVIDER); v = REG_SET_MASK(v, CDIVG_USE_THERM_CONTROLS_MASK, 1); - clk_writel(ts, v, CAR_SUPER_CCLKG_DIVIDER); + writel(v, ts->clk_regs + CAR_SUPER_CCLKG_DIVIDER); } /* initialize stats collection */ diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index d64325e..6ab9823 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * thermal.c - Generic Thermal Management Sysfs support. * * Copyright (C) 2008 Intel Corp * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> - * - * 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; version 2 of the License. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -736,7 +733,7 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, sysfs_attr_init(&dev->attr.attr); dev->attr.attr.name = dev->attr_name; dev->attr.attr.mode = 0444; - dev->attr.show = thermal_cooling_device_trip_point_show; + dev->attr.show = trip_point_show; result = device_create_file(&tz->device, &dev->attr); if (result) goto remove_symbol_link; @@ -745,8 +742,8 @@ int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, sysfs_attr_init(&dev->weight_attr.attr); dev->weight_attr.attr.name = dev->weight_attr_name; dev->weight_attr.attr.mode = S_IWUSR | S_IRUGO; - dev->weight_attr.show = thermal_cooling_device_weight_show; - dev->weight_attr.store = thermal_cooling_device_weight_store; + dev->weight_attr.show = weight_show; + dev->weight_attr.store = weight_store; result = device_create_file(&tz->device, &dev->weight_attr); if (result) goto remove_trip_file; diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 5e41502..0df190e 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -1,24 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * thermal_core.h * * Copyright (C) 2012 Intel Corp * Author: Durgadoss R <durgadoss.r@intel.com> - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * 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; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #ifndef __THERMAL_CORE_H__ @@ -75,15 +60,10 @@ void thermal_zone_destroy_device_groups(struct thermal_zone_device *); void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *); void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev); /* used only at binding time */ -ssize_t -thermal_cooling_device_trip_point_show(struct device *, - struct device_attribute *, char *); -ssize_t thermal_cooling_device_weight_show(struct device *, - struct device_attribute *, char *); - -ssize_t thermal_cooling_device_weight_store(struct device *, - struct device_attribute *, - const char *, size_t); +ssize_t trip_point_show(struct device *, struct device_attribute *, char *); +ssize_t weight_show(struct device *, struct device_attribute *, char *); +ssize_t weight_store(struct device *, struct device_attribute *, const char *, + size_t); #ifdef CONFIG_THERMAL_STATISTICS void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c index eb03d7e..2ba756a 100644 --- a/drivers/thermal/thermal_helpers.c +++ b/drivers/thermal/thermal_helpers.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * thermal_helpers.c - helper functions to handle thermal devices * @@ -7,10 +8,6 @@ * Copyright (C) 2008 Intel Corp * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> - * - * 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; version 2 of the License. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index c4a508a..1127883 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * thermal_hwmon.c - Generic Thermal Management hwmon support. * @@ -8,22 +9,6 @@ * * Copyright (C) 2013 Texas Instruments * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * 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; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #include <linux/hwmon.h> #include <linux/thermal.h> diff --git a/drivers/thermal/thermal_hwmon.h b/drivers/thermal/thermal_hwmon.h index c798fdb..019f6f8 100644 --- a/drivers/thermal/thermal_hwmon.h +++ b/drivers/thermal/thermal_hwmon.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * thermal_hwmon.h - Generic Thermal Management hwmon support. * @@ -8,22 +9,6 @@ * * Copyright (C) 2013 Texas Instruments * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * 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; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #ifndef __THERMAL_HWMON_H__ #define __THERMAL_HWMON_H__ diff --git a/drivers/thermal/thermal_sysfs.c b/drivers/thermal/thermal_sysfs.c index 23b5e0a..2241cea 100644 --- a/drivers/thermal/thermal_sysfs.c +++ b/drivers/thermal/thermal_sysfs.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * thermal.c - sysfs interface of thermal devices * @@ -7,10 +8,6 @@ * Copyright (C) 2008 Intel Corp * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> - * - * 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; version 2 of the License. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -381,7 +378,7 @@ sustainable_power_store(struct device *dev, struct device_attribute *devattr, \ return count; \ } \ - static DEVICE_ATTR(name, S_IWUSR | S_IRUGO, name##_show, name##_store) + static DEVICE_ATTR_RW(name) create_s32_tzp_attr(k_po); create_s32_tzp_attr(k_pu); @@ -668,17 +665,15 @@ void thermal_zone_destroy_device_groups(struct thermal_zone_device *tz) /* sys I/F for cooling device */ static ssize_t -thermal_cooling_device_type_show(struct device *dev, - struct device_attribute *attr, char *buf) +cdev_type_show(struct device *dev, struct device_attribute *attr, char *buf) { struct thermal_cooling_device *cdev = to_cooling_device(dev); return sprintf(buf, "%s\n", cdev->type); } -static ssize_t -thermal_cooling_device_max_state_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t max_state_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct thermal_cooling_device *cdev = to_cooling_device(dev); unsigned long state; @@ -690,9 +685,8 @@ thermal_cooling_device_max_state_show(struct device *dev, return sprintf(buf, "%ld\n", state); } -static ssize_t -thermal_cooling_device_cur_state_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t cur_state_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct thermal_cooling_device *cdev = to_cooling_device(dev); unsigned long state; @@ -705,9 +699,8 @@ thermal_cooling_device_cur_state_show(struct device *dev, } static ssize_t -thermal_cooling_device_cur_state_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +cur_state_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct thermal_cooling_device *cdev = to_cooling_device(dev); unsigned long state; @@ -726,13 +719,10 @@ thermal_cooling_device_cur_state_store(struct device *dev, return count; } -static struct device_attribute dev_attr_cdev_type = -__ATTR(type, 0444, thermal_cooling_device_type_show, NULL); -static DEVICE_ATTR(max_state, 0444, - thermal_cooling_device_max_state_show, NULL); -static DEVICE_ATTR(cur_state, 0644, - thermal_cooling_device_cur_state_show, - thermal_cooling_device_cur_state_store); +static struct device_attribute +dev_attr_cdev_type = __ATTR(type, 0444, cdev_type_show, NULL); +static DEVICE_ATTR_RO(max_state); +static DEVICE_ATTR_RW(cur_state); static struct attribute *cooling_device_attrs[] = { &dev_attr_cdev_type.attr, @@ -791,10 +781,8 @@ unlock: spin_unlock(&stats->lock); } -static ssize_t -thermal_cooling_device_total_trans_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t total_trans_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct thermal_cooling_device *cdev = to_cooling_device(dev); struct cooling_dev_stats *stats = cdev->stats; @@ -808,9 +796,8 @@ thermal_cooling_device_total_trans_show(struct device *dev, } static ssize_t -thermal_cooling_device_time_in_state_show(struct device *dev, - struct device_attribute *attr, - char *buf) +time_in_state_ms_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct thermal_cooling_device *cdev = to_cooling_device(dev); struct cooling_dev_stats *stats = cdev->stats; @@ -830,9 +817,8 @@ thermal_cooling_device_time_in_state_show(struct device *dev, } static ssize_t -thermal_cooling_device_reset_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +reset_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) { struct thermal_cooling_device *cdev = to_cooling_device(dev); struct cooling_dev_stats *stats = cdev->stats; @@ -853,10 +839,8 @@ thermal_cooling_device_reset_store(struct device *dev, return count; } -static ssize_t -thermal_cooling_device_trans_table_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t trans_table_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct thermal_cooling_device *cdev = to_cooling_device(dev); struct cooling_dev_stats *stats = cdev->stats; @@ -899,13 +883,10 @@ thermal_cooling_device_trans_table_show(struct device *dev, return len; } -static DEVICE_ATTR(total_trans, 0444, thermal_cooling_device_total_trans_show, - NULL); -static DEVICE_ATTR(time_in_state_ms, 0444, - thermal_cooling_device_time_in_state_show, NULL); -static DEVICE_ATTR(reset, 0200, NULL, thermal_cooling_device_reset_store); -static DEVICE_ATTR(trans_table, 0444, - thermal_cooling_device_trans_table_show, NULL); +static DEVICE_ATTR_RO(total_trans); +static DEVICE_ATTR_RO(time_in_state_ms); +static DEVICE_ATTR_WO(reset); +static DEVICE_ATTR_RO(trans_table); static struct attribute *cooling_device_stats_attrs[] = { &dev_attr_total_trans.attr, @@ -980,8 +961,7 @@ void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev) /* these helper will be used only at the time of bindig */ ssize_t -thermal_cooling_device_trip_point_show(struct device *dev, - struct device_attribute *attr, char *buf) +trip_point_show(struct device *dev, struct device_attribute *attr, char *buf) { struct thermal_instance *instance; @@ -995,8 +975,7 @@ thermal_cooling_device_trip_point_show(struct device *dev, } ssize_t -thermal_cooling_device_weight_show(struct device *dev, - struct device_attribute *attr, char *buf) +weight_show(struct device *dev, struct device_attribute *attr, char *buf) { struct thermal_instance *instance; @@ -1005,10 +984,8 @@ thermal_cooling_device_weight_show(struct device *dev, return sprintf(buf, "%d\n", instance->weight); } -ssize_t -thermal_cooling_device_weight_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +ssize_t weight_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct thermal_instance *instance; int ret, weight; diff --git a/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c index cd9a304..6ac0370 100644 --- a/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c +++ b/drivers/thermal/ti-soc-thermal/omap5-thermal-data.c @@ -310,7 +310,7 @@ omap5430_adc_to_temp[ 119800, 120200, 120600, 121000, 121400, 121800, 122400, 122600, 123000, 123400, /* Index 940 - 945 */ - 123800, 1242000, 124600, 124900, 125000, 125000, + 123800, 124200, 124600, 124900, 125000, 125000, }; /* OMAP54xx ES2.0 data */ diff --git a/drivers/thermal/uniphier_thermal.c b/drivers/thermal/uniphier_thermal.c index 9570473..55477d7 100644 --- a/drivers/thermal/uniphier_thermal.c +++ b/drivers/thermal/uniphier_thermal.c @@ -365,6 +365,10 @@ static const struct of_device_id uniphier_tm_dt_ids[] = { .compatible = "socionext,uniphier-ld20-thermal", .data = &uniphier_ld20_tm_data, }, + { + .compatible = "socionext,uniphier-pxs3-thermal", + .data = &uniphier_ld20_tm_data, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, uniphier_tm_dt_ids); diff --git a/drivers/vfio/mdev/mdev_core.c b/drivers/vfio/mdev/mdev_core.c index 1269910..0212f0e 100644 --- a/drivers/vfio/mdev/mdev_core.c +++ b/drivers/vfio/mdev/mdev_core.c @@ -66,34 +66,6 @@ uuid_le mdev_uuid(struct mdev_device *mdev) } EXPORT_SYMBOL(mdev_uuid); -static int _find_mdev_device(struct device *dev, void *data) -{ - struct mdev_device *mdev; - - if (!dev_is_mdev(dev)) - return 0; - - mdev = to_mdev_device(dev); - - if (uuid_le_cmp(mdev->uuid, *(uuid_le *)data) == 0) - return 1; - - return 0; -} - -static bool mdev_device_exist(struct mdev_parent *parent, uuid_le uuid) -{ - struct device *dev; - - dev = device_find_child(parent->dev, &uuid, _find_mdev_device); - if (dev) { - put_device(dev); - return true; - } - - return false; -} - /* Should be called holding parent_list_lock */ static struct mdev_parent *__find_parent_device(struct device *dev) { @@ -221,7 +193,6 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops) } kref_init(&parent->ref); - mutex_init(&parent->lock); parent->dev = dev; parent->ops = ops; @@ -297,6 +268,10 @@ static void mdev_device_release(struct device *dev) { struct mdev_device *mdev = to_mdev_device(dev); + mutex_lock(&mdev_list_lock); + list_del(&mdev->next); + mutex_unlock(&mdev_list_lock); + dev_dbg(&mdev->dev, "MDEV: destroying\n"); kfree(mdev); } @@ -304,7 +279,7 @@ static void mdev_device_release(struct device *dev) int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) { int ret; - struct mdev_device *mdev; + struct mdev_device *mdev, *tmp; struct mdev_parent *parent; struct mdev_type *type = to_mdev_type(kobj); @@ -312,21 +287,28 @@ int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) if (!parent) return -EINVAL; - mutex_lock(&parent->lock); + mutex_lock(&mdev_list_lock); /* Check for duplicate */ - if (mdev_device_exist(parent, uuid)) { - ret = -EEXIST; - goto create_err; + list_for_each_entry(tmp, &mdev_list, next) { + if (!uuid_le_cmp(tmp->uuid, uuid)) { + mutex_unlock(&mdev_list_lock); + ret = -EEXIST; + goto mdev_fail; + } } mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); if (!mdev) { + mutex_unlock(&mdev_list_lock); ret = -ENOMEM; - goto create_err; + goto mdev_fail; } memcpy(&mdev->uuid, &uuid, sizeof(uuid_le)); + list_add(&mdev->next, &mdev_list); + mutex_unlock(&mdev_list_lock); + mdev->parent = parent; kref_init(&mdev->ref); @@ -338,35 +320,28 @@ int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) ret = device_register(&mdev->dev); if (ret) { put_device(&mdev->dev); - goto create_err; + goto mdev_fail; } ret = mdev_device_create_ops(kobj, mdev); if (ret) - goto create_failed; + goto create_fail; ret = mdev_create_sysfs_files(&mdev->dev, type); if (ret) { mdev_device_remove_ops(mdev, true); - goto create_failed; + goto create_fail; } mdev->type_kobj = kobj; + mdev->active = true; dev_dbg(&mdev->dev, "MDEV: created\n"); - mutex_unlock(&parent->lock); - - mutex_lock(&mdev_list_lock); - list_add(&mdev->next, &mdev_list); - mutex_unlock(&mdev_list_lock); - - return ret; + return 0; -create_failed: +create_fail: device_unregister(&mdev->dev); - -create_err: - mutex_unlock(&parent->lock); +mdev_fail: mdev_put_parent(parent); return ret; } @@ -377,44 +352,39 @@ int mdev_device_remove(struct device *dev, bool force_remove) struct mdev_parent *parent; struct mdev_type *type; int ret; - bool found = false; mdev = to_mdev_device(dev); mutex_lock(&mdev_list_lock); list_for_each_entry(tmp, &mdev_list, next) { - if (tmp == mdev) { - found = true; + if (tmp == mdev) break; - } } - if (found) - list_del(&mdev->next); + if (tmp != mdev) { + mutex_unlock(&mdev_list_lock); + return -ENODEV; + } - mutex_unlock(&mdev_list_lock); + if (!mdev->active) { + mutex_unlock(&mdev_list_lock); + return -EAGAIN; + } - if (!found) - return -ENODEV; + mdev->active = false; + mutex_unlock(&mdev_list_lock); type = to_mdev_type(mdev->type_kobj); parent = mdev->parent; - mutex_lock(&parent->lock); ret = mdev_device_remove_ops(mdev, force_remove); if (ret) { - mutex_unlock(&parent->lock); - - mutex_lock(&mdev_list_lock); - list_add(&mdev->next, &mdev_list); - mutex_unlock(&mdev_list_lock); - + mdev->active = true; return ret; } mdev_remove_sysfs_files(dev, type); device_unregister(dev); - mutex_unlock(&parent->lock); mdev_put_parent(parent); return 0; diff --git a/drivers/vfio/mdev/mdev_private.h b/drivers/vfio/mdev/mdev_private.h index a9cefd7..b5819b7 100644 --- a/drivers/vfio/mdev/mdev_private.h +++ b/drivers/vfio/mdev/mdev_private.h @@ -20,7 +20,6 @@ struct mdev_parent { struct device *dev; const struct mdev_parent_ops *ops; struct kref ref; - struct mutex lock; struct list_head next; struct kset *mdev_types_kset; struct list_head type_list; @@ -34,6 +33,7 @@ struct mdev_device { struct kref ref; struct list_head next; struct kobject *type_kobj; + bool active; }; #define to_mdev_device(dev) container_of(dev, struct mdev_device, dev) diff --git a/drivers/vfio/mdev/mdev_sysfs.c b/drivers/vfio/mdev/mdev_sysfs.c index 802df21..249472f 100644 --- a/drivers/vfio/mdev/mdev_sysfs.c +++ b/drivers/vfio/mdev/mdev_sysfs.c @@ -257,24 +257,24 @@ int mdev_create_sysfs_files(struct device *dev, struct mdev_type *type) { int ret; - ret = sysfs_create_files(&dev->kobj, mdev_device_attrs); - if (ret) - return ret; - ret = sysfs_create_link(type->devices_kobj, &dev->kobj, dev_name(dev)); if (ret) - goto device_link_failed; + return ret; ret = sysfs_create_link(&dev->kobj, &type->kobj, "mdev_type"); if (ret) goto type_link_failed; + ret = sysfs_create_files(&dev->kobj, mdev_device_attrs); + if (ret) + goto create_files_failed; + return ret; +create_files_failed: + sysfs_remove_link(&dev->kobj, "mdev_type"); type_link_failed: sysfs_remove_link(type->devices_kobj, dev_name(dev)); -device_link_failed: - sysfs_remove_files(&dev->kobj, mdev_device_attrs); return ret; } diff --git a/drivers/vfio/platform/vfio_platform_common.c b/drivers/vfio/platform/vfio_platform_common.c index 4c27f4b..c0cd824 100644 --- a/drivers/vfio/platform/vfio_platform_common.c +++ b/drivers/vfio/platform/vfio_platform_common.c @@ -17,6 +17,7 @@ #include <linux/iommu.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/uaccess.h> @@ -239,6 +240,7 @@ static void vfio_platform_release(void *device_data) ret, extra_dbg ? extra_dbg : ""); WARN_ON(1); } + pm_runtime_put(vdev->device); vfio_platform_regions_cleanup(vdev); vfio_platform_irq_cleanup(vdev); } @@ -269,6 +271,10 @@ static int vfio_platform_open(void *device_data) if (ret) goto err_irq; + ret = pm_runtime_get_sync(vdev->device); + if (ret < 0) + goto err_pm; + ret = vfio_platform_call_reset(vdev, &extra_dbg); if (ret && vdev->reset_required) { dev_warn(vdev->device, "reset driver is required and reset call failed in open (%d) %s\n", @@ -283,6 +289,8 @@ static int vfio_platform_open(void *device_data) return 0; err_rst: + pm_runtime_put(vdev->device); +err_pm: vfio_platform_irq_cleanup(vdev); err_irq: vfio_platform_regions_cleanup(vdev); @@ -630,8 +638,7 @@ static int vfio_platform_of_probe(struct vfio_platform_device *vdev, ret = device_property_read_string(dev, "compatible", &vdev->compat); if (ret) - pr_err("VFIO: cannot retrieve compat for %s\n", - vdev->name); + pr_err("VFIO: Cannot retrieve compat for %s\n", vdev->name); return ret; } @@ -673,7 +680,7 @@ int vfio_platform_probe_common(struct vfio_platform_device *vdev, ret = vfio_platform_get_reset(vdev); if (ret && vdev->reset_required) { - pr_err("vfio: no reset function found for device %s\n", + pr_err("VFIO: No reset function found for device %s\n", vdev->name); return ret; } @@ -681,18 +688,24 @@ int vfio_platform_probe_common(struct vfio_platform_device *vdev, group = vfio_iommu_group_get(dev); if (!group) { pr_err("VFIO: No IOMMU group for device %s\n", vdev->name); - return -EINVAL; + ret = -EINVAL; + goto put_reset; } ret = vfio_add_group_dev(dev, &vfio_platform_ops, vdev); - if (ret) { - vfio_iommu_group_put(group, dev); - return ret; - } + if (ret) + goto put_iommu; mutex_init(&vdev->igate); + pm_runtime_enable(vdev->device); return 0; + +put_iommu: + vfio_iommu_group_put(group, dev); +put_reset: + vfio_platform_put_reset(vdev); + return ret; } EXPORT_SYMBOL_GPL(vfio_platform_probe_common); @@ -703,6 +716,7 @@ struct vfio_platform_device *vfio_platform_remove_common(struct device *dev) vdev = vfio_del_group_dev(dev); if (vdev) { + pm_runtime_disable(vdev->device); vfio_platform_put_reset(vdev); vfio_iommu_group_put(dev->iommu_group, dev); } diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c index 721f97f..6483387 100644 --- a/drivers/vfio/vfio.c +++ b/drivers/vfio/vfio.c @@ -630,8 +630,6 @@ static const char * const vfio_driver_whitelist[] = { "pci-stub" }; static bool vfio_dev_whitelisted(struct device *dev, struct device_driver *drv) { - int i; - if (dev_is_pci(dev)) { struct pci_dev *pdev = to_pci_dev(dev); @@ -639,12 +637,9 @@ static bool vfio_dev_whitelisted(struct device *dev, struct device_driver *drv) return true; } - for (i = 0; i < ARRAY_SIZE(vfio_driver_whitelist); i++) { - if (!strcmp(drv->name, vfio_driver_whitelist[i])) - return true; - } - - return false; + return match_string(vfio_driver_whitelist, + ARRAY_SIZE(vfio_driver_whitelist), + drv->name) >= 0; } /* diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 3c082451..2c75b33 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -83,6 +83,7 @@ struct vfio_dma { size_t size; /* Map size (bytes) */ int prot; /* IOMMU_READ/WRITE */ bool iommu_mapped; + bool lock_cap; /* capable(CAP_IPC_LOCK) */ struct task_struct *task; struct rb_root pfn_list; /* Ex-user pinned pfn list */ }; @@ -253,29 +254,25 @@ static int vfio_iova_put_vfio_pfn(struct vfio_dma *dma, struct vfio_pfn *vpfn) return ret; } -static int vfio_lock_acct(struct task_struct *task, long npage, bool *lock_cap) +static int vfio_lock_acct(struct vfio_dma *dma, long npage, bool async) { struct mm_struct *mm; - bool is_current; int ret; if (!npage) return 0; - is_current = (task->mm == current->mm); - - mm = is_current ? task->mm : get_task_mm(task); + mm = async ? get_task_mm(dma->task) : dma->task->mm; if (!mm) return -ESRCH; /* process exited */ ret = down_write_killable(&mm->mmap_sem); if (!ret) { if (npage > 0) { - if (lock_cap ? !*lock_cap : - !has_capability(task, CAP_IPC_LOCK)) { + if (!dma->lock_cap) { unsigned long limit; - limit = task_rlimit(task, + limit = task_rlimit(dma->task, RLIMIT_MEMLOCK) >> PAGE_SHIFT; if (mm->locked_vm + npage > limit) @@ -289,7 +286,7 @@ static int vfio_lock_acct(struct task_struct *task, long npage, bool *lock_cap) up_write(&mm->mmap_sem); } - if (!is_current) + if (async) mmput(mm); return ret; @@ -400,7 +397,7 @@ static int vaddr_get_pfn(struct mm_struct *mm, unsigned long vaddr, */ static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, long npage, unsigned long *pfn_base, - bool lock_cap, unsigned long limit) + unsigned long limit) { unsigned long pfn = 0; long ret, pinned = 0, lock_acct = 0; @@ -423,7 +420,7 @@ static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, * pages are already counted against the user. */ if (!rsvd && !vfio_find_vpfn(dma, iova)) { - if (!lock_cap && current->mm->locked_vm + 1 > limit) { + if (!dma->lock_cap && current->mm->locked_vm + 1 > limit) { put_pfn(*pfn_base, dma->prot); pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n", __func__, limit << PAGE_SHIFT); @@ -449,7 +446,7 @@ static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, } if (!rsvd && !vfio_find_vpfn(dma, iova)) { - if (!lock_cap && + if (!dma->lock_cap && current->mm->locked_vm + lock_acct + 1 > limit) { put_pfn(pfn, dma->prot); pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n", @@ -462,7 +459,7 @@ static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, } out: - ret = vfio_lock_acct(current, lock_acct, &lock_cap); + ret = vfio_lock_acct(dma, lock_acct, false); unpin_out: if (ret) { @@ -493,7 +490,7 @@ static long vfio_unpin_pages_remote(struct vfio_dma *dma, dma_addr_t iova, } if (do_accounting) - vfio_lock_acct(dma->task, locked - unlocked, NULL); + vfio_lock_acct(dma, locked - unlocked, true); return unlocked; } @@ -510,7 +507,7 @@ static int vfio_pin_page_external(struct vfio_dma *dma, unsigned long vaddr, ret = vaddr_get_pfn(mm, vaddr, dma->prot, pfn_base); if (!ret && do_accounting && !is_invalid_reserved_pfn(*pfn_base)) { - ret = vfio_lock_acct(dma->task, 1, NULL); + ret = vfio_lock_acct(dma, 1, true); if (ret) { put_pfn(*pfn_base, dma->prot); if (ret == -ENOMEM) @@ -537,7 +534,7 @@ static int vfio_unpin_page_external(struct vfio_dma *dma, dma_addr_t iova, unlocked = vfio_iova_put_vfio_pfn(dma, vpfn); if (do_accounting) - vfio_lock_acct(dma->task, -unlocked, NULL); + vfio_lock_acct(dma, -unlocked, true); return unlocked; } @@ -829,7 +826,7 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma, unlocked += vfio_sync_unpin(dma, domain, &unmapped_region_list); if (do_accounting) { - vfio_lock_acct(dma->task, -unlocked, NULL); + vfio_lock_acct(dma, -unlocked, true); return 0; } return unlocked; @@ -1044,14 +1041,12 @@ static int vfio_pin_map_dma(struct vfio_iommu *iommu, struct vfio_dma *dma, size_t size = map_size; long npage; unsigned long pfn, limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; - bool lock_cap = capable(CAP_IPC_LOCK); int ret = 0; while (size) { /* Pin a contiguous chunk of memory */ npage = vfio_pin_pages_remote(dma, vaddr + dma->size, - size >> PAGE_SHIFT, &pfn, - lock_cap, limit); + size >> PAGE_SHIFT, &pfn, limit); if (npage <= 0) { WARN_ON(!npage); ret = (int)npage; @@ -1126,8 +1121,36 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu, dma->iova = iova; dma->vaddr = vaddr; dma->prot = prot; - get_task_struct(current); - dma->task = current; + + /* + * We need to be able to both add to a task's locked memory and test + * against the locked memory limit and we need to be able to do both + * outside of this call path as pinning can be asynchronous via the + * external interfaces for mdev devices. RLIMIT_MEMLOCK requires a + * task_struct and VM locked pages requires an mm_struct, however + * holding an indefinite mm reference is not recommended, therefore we + * only hold a reference to a task. We could hold a reference to + * current, however QEMU uses this call path through vCPU threads, + * which can be killed resulting in a NULL mm and failure in the unmap + * path when called via a different thread. Avoid this problem by + * using the group_leader as threads within the same group require + * both CLONE_THREAD and CLONE_VM and will therefore use the same + * mm_struct. + * + * Previously we also used the task for testing CAP_IPC_LOCK at the + * time of pinning and accounting, however has_capability() makes use + * of real_cred, a copy-on-write field, so we can't guarantee that it + * matches group_leader, or in fact that it might not change by the + * time it's evaluated. If a process were to call MAP_DMA with + * CAP_IPC_LOCK but later drop it, it doesn't make sense that they + * possibly see different results for an iommu_mapped vfio_dma vs + * externally mapped. Therefore track CAP_IPC_LOCK in vfio_dma at the + * time of calling MAP_DMA. + */ + get_task_struct(current->group_leader); + dma->task = current->group_leader; + dma->lock_cap = capable(CAP_IPC_LOCK); + dma->pfn_list = RB_ROOT; /* Insert zero-sized and grow as we map chunks of it */ @@ -1162,7 +1185,6 @@ static int vfio_iommu_replay(struct vfio_iommu *iommu, struct vfio_domain *d; struct rb_node *n; unsigned long limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; - bool lock_cap = capable(CAP_IPC_LOCK); int ret; /* Arbitrarily pick the first domain in the list for lookups */ @@ -1209,8 +1231,7 @@ static int vfio_iommu_replay(struct vfio_iommu *iommu, npage = vfio_pin_pages_remote(dma, vaddr, n >> PAGE_SHIFT, - &pfn, lock_cap, - limit); + &pfn, limit); if (npage <= 0) { WARN_ON(!npage); ret = (int)npage; @@ -1487,7 +1508,7 @@ static void vfio_iommu_unmap_unpin_reaccount(struct vfio_iommu *iommu) if (!is_invalid_reserved_pfn(vpfn->pfn)) locked++; } - vfio_lock_acct(dma->task, locked - unlocked, NULL); + vfio_lock_acct(dma, locked - unlocked, true); } } diff --git a/drivers/watchdog/cadence_wdt.c b/drivers/watchdog/cadence_wdt.c index 3ec1f41..c392435 100644 --- a/drivers/watchdog/cadence_wdt.c +++ b/drivers/watchdog/cadence_wdt.c @@ -418,8 +418,7 @@ static void cdns_wdt_shutdown(struct platform_device *pdev) */ static int __maybe_unused cdns_wdt_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct cdns_wdt *wdt = platform_get_drvdata(pdev); + struct cdns_wdt *wdt = dev_get_drvdata(dev); if (watchdog_active(&wdt->cdns_wdt_device)) { cdns_wdt_stop(&wdt->cdns_wdt_device); @@ -438,8 +437,7 @@ static int __maybe_unused cdns_wdt_suspend(struct device *dev) static int __maybe_unused cdns_wdt_resume(struct device *dev) { int ret; - struct platform_device *pdev = to_platform_device(dev); - struct cdns_wdt *wdt = platform_get_drvdata(pdev); + struct cdns_wdt *wdt = dev_get_drvdata(dev); if (watchdog_active(&wdt->cdns_wdt_device)) { ret = clk_prepare_enable(wdt->clk); diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index a001782..fe169d8 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -30,14 +30,8 @@ static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 }; struct da9062_watchdog { struct da9062 *hw; struct watchdog_device wdtdev; - unsigned long j_time_stamp; }; -static void da9062_set_window_start(struct da9062_watchdog *wdt) -{ - wdt->j_time_stamp = jiffies; -} - static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs) { unsigned int i; @@ -59,8 +53,6 @@ static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt) DA9062AA_WATCHDOG_MASK, DA9062AA_WATCHDOG_MASK); - da9062_set_window_start(wdt); - return ret; } @@ -232,8 +224,6 @@ static int da9062_wdt_probe(struct platform_device *pdev) return ret; } - da9062_set_window_start(wdt); - return da9062_wdt_ping(&wdt->wdtdev); } diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c index b17ac1b..384dca1 100644 --- a/drivers/watchdog/da9063_wdt.c +++ b/drivers/watchdog/da9063_wdt.c @@ -45,20 +45,56 @@ static unsigned int da9063_wdt_timeout_to_sel(unsigned int secs) return DA9063_TWDSCALE_MAX; } -static int _da9063_wdt_set_timeout(struct da9063 *da9063, unsigned int regval) +/* + * Return 0 if watchdog is disabled, else non zero. + */ +static unsigned int da9063_wdt_is_running(struct da9063 *da9063) +{ + unsigned int val; + + regmap_read(da9063->regmap, DA9063_REG_CONTROL_D, &val); + + return val & DA9063_TWDSCALE_MASK; +} + +static int da9063_wdt_disable_timer(struct da9063 *da9063) { return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D, + DA9063_TWDSCALE_MASK, + DA9063_TWDSCALE_DISABLE); +} + +static int +da9063_wdt_update_timeout(struct da9063 *da9063, unsigned int timeout) +{ + unsigned int regval; + int ret; + + /* + * The watchdog triggers a reboot if a timeout value is already + * programmed because the timeout value combines two functions + * in one: indicating the counter limit and starting the watchdog. + * The watchdog must be disabled to be able to change the timeout + * value if the watchdog is already running. Then we can set the + * new timeout value which enables the watchdog again. + */ + ret = da9063_wdt_disable_timer(da9063); + if (ret) + return ret; + + usleep_range(150, 300); + regval = da9063_wdt_timeout_to_sel(timeout); + + return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D, DA9063_TWDSCALE_MASK, regval); } static int da9063_wdt_start(struct watchdog_device *wdd) { struct da9063 *da9063 = watchdog_get_drvdata(wdd); - unsigned int selector; int ret; - selector = da9063_wdt_timeout_to_sel(wdd->timeout); - ret = _da9063_wdt_set_timeout(da9063, selector); + ret = da9063_wdt_update_timeout(da9063, wdd->timeout); if (ret) dev_err(da9063->dev, "Watchdog failed to start (err = %d)\n", ret); @@ -71,8 +107,7 @@ static int da9063_wdt_stop(struct watchdog_device *wdd) struct da9063 *da9063 = watchdog_get_drvdata(wdd); int ret; - ret = regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D, - DA9063_TWDSCALE_MASK, DA9063_TWDSCALE_DISABLE); + ret = da9063_wdt_disable_timer(da9063); if (ret) dev_alert(da9063->dev, "Watchdog failed to stop (err = %d)\n", ret); @@ -98,16 +133,26 @@ static int da9063_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { struct da9063 *da9063 = watchdog_get_drvdata(wdd); - unsigned int selector; - int ret; + int ret = 0; + + /* + * There are two cases when a set_timeout() will be called: + * 1. The watchdog is off and someone wants to set the timeout for the + * further use. + * 2. The watchdog is already running and a new timeout value should be + * set. + * + * The watchdog can't store a timeout value not equal zero without + * enabling the watchdog, so the timeout must be buffered by the driver. + */ + if (watchdog_active(wdd)) + ret = da9063_wdt_update_timeout(da9063, timeout); - selector = da9063_wdt_timeout_to_sel(timeout); - ret = _da9063_wdt_set_timeout(da9063, selector); if (ret) dev_err(da9063->dev, "Failed to set watchdog timeout (err = %d)\n", ret); else - wdd->timeout = wdt_timeout[selector]; + wdd->timeout = wdt_timeout[da9063_wdt_timeout_to_sel(timeout)]; return ret; } @@ -171,6 +216,12 @@ static int da9063_wdt_probe(struct platform_device *pdev) watchdog_set_drvdata(wdd, da9063); + /* Change the timeout to the default value if the watchdog is running */ + if (da9063_wdt_is_running(da9063)) { + da9063_wdt_update_timeout(da9063, DA9063_WDG_TIMEOUT); + set_bit(WDOG_HW_RUNNING, &wdd->status); + } + return devm_watchdog_register_device(&pdev->dev, wdd); } diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index a43ab2c..9dc62a4 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -159,7 +159,7 @@ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) "3. OA Forward Progress Log\n" "4. iLO Event Log"; - if (ilo5 && ulReason == NMI_UNKNOWN && mynmi) + if (ilo5 && ulReason == NMI_UNKNOWN && !mynmi) return NMI_DONE; if (ilo5 && !pretimeout) diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c index aafbeb9..ec4d99a 100644 --- a/drivers/watchdog/jz4740_wdt.c +++ b/drivers/watchdog/jz4740_wdt.c @@ -124,12 +124,20 @@ static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) { struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); - jz4740_timer_disable_watchdog(); writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); + jz4740_timer_disable_watchdog(); return 0; } +static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) +{ + wdt_dev->timeout = 0; + jz4740_wdt_start(wdt_dev); + return 0; +} + static const struct watchdog_info jz4740_wdt_info = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .identity = "jz4740 Watchdog", @@ -141,6 +149,7 @@ static const struct watchdog_ops jz4740_wdt_ops = { .stop = jz4740_wdt_stop, .ping = jz4740_wdt_ping, .set_timeout = jz4740_wdt_set_timeout, + .restart = jz4740_wdt_restart, }; #ifdef CONFIG_OF @@ -179,45 +188,26 @@ static int jz4740_wdt_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); drvdata->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(drvdata->base)) { - ret = PTR_ERR(drvdata->base); - goto err_out; - } + if (IS_ERR(drvdata->base)) + return PTR_ERR(drvdata->base); - drvdata->rtc_clk = clk_get(&pdev->dev, "rtc"); + drvdata->rtc_clk = devm_clk_get(&pdev->dev, "rtc"); if (IS_ERR(drvdata->rtc_clk)) { dev_err(&pdev->dev, "cannot find RTC clock\n"); - ret = PTR_ERR(drvdata->rtc_clk); - goto err_out; + return PTR_ERR(drvdata->rtc_clk); } - ret = watchdog_register_device(&drvdata->wdt); + ret = devm_watchdog_register_device(&pdev->dev, &drvdata->wdt); if (ret < 0) - goto err_disable_clk; + return ret; platform_set_drvdata(pdev, drvdata); - return 0; - -err_disable_clk: - clk_put(drvdata->rtc_clk); -err_out: - return ret; -} - -static int jz4740_wdt_remove(struct platform_device *pdev) -{ - struct jz4740_wdt_drvdata *drvdata = platform_get_drvdata(pdev); - - jz4740_wdt_stop(&drvdata->wdt); - watchdog_unregister_device(&drvdata->wdt); - clk_put(drvdata->rtc_clk); return 0; } static struct platform_driver jz4740_wdt_driver = { .probe = jz4740_wdt_probe, - .remove = jz4740_wdt_remove, .driver = { .name = "jz4740-wdt", .of_match_table = of_match_ptr(jz4740_wdt_of_matches), diff --git a/drivers/watchdog/mena21_wdt.c b/drivers/watchdog/mena21_wdt.c index 25d5d2b..0be7f50 100644 --- a/drivers/watchdog/mena21_wdt.c +++ b/drivers/watchdog/mena21_wdt.c @@ -31,7 +31,6 @@ enum a21_wdt_gpios { struct a21_wdt_drv { struct watchdog_device wdt; - struct mutex lock; unsigned gpios[NUM_GPIOS]; }; @@ -55,12 +54,8 @@ static int a21_wdt_start(struct watchdog_device *wdt) { struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); - mutex_lock(&drv->lock); - gpio_set_value(drv->gpios[GPIO_WD_ENAB], 1); - mutex_unlock(&drv->lock); - return 0; } @@ -68,12 +63,8 @@ static int a21_wdt_stop(struct watchdog_device *wdt) { struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); - mutex_lock(&drv->lock); - gpio_set_value(drv->gpios[GPIO_WD_ENAB], 0); - mutex_unlock(&drv->lock); - return 0; } @@ -81,14 +72,10 @@ static int a21_wdt_ping(struct watchdog_device *wdt) { struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); - mutex_lock(&drv->lock); - gpio_set_value(drv->gpios[GPIO_WD_TRIG], 0); ndelay(10); gpio_set_value(drv->gpios[GPIO_WD_TRIG], 1); - mutex_unlock(&drv->lock); - return 0; } @@ -108,8 +95,6 @@ static int a21_wdt_set_timeout(struct watchdog_device *wdt, return -EINVAL; } - mutex_lock(&drv->lock); - if (timeout == 1) gpio_set_value(drv->gpios[GPIO_WD_FAST], 1); else @@ -117,8 +102,6 @@ static int a21_wdt_set_timeout(struct watchdog_device *wdt, wdt->timeout = timeout; - mutex_unlock(&drv->lock); - return 0; } @@ -191,7 +174,6 @@ static int a21_wdt_probe(struct platform_device *pdev) return ret; } - mutex_init(&drv->lock); watchdog_init_timeout(&a21_wdt, 30, &pdev->dev); watchdog_set_nowayout(&a21_wdt, nowayout); watchdog_set_drvdata(&a21_wdt, drv); diff --git a/drivers/watchdog/of_xilinx_wdt.c b/drivers/watchdog/of_xilinx_wdt.c index 4acbe05..d3f7eb0 100644 --- a/drivers/watchdog/of_xilinx_wdt.c +++ b/drivers/watchdog/of_xilinx_wdt.c @@ -268,8 +268,7 @@ static int xwdt_remove(struct platform_device *pdev) */ static int __maybe_unused xwdt_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct xwdt_device *xdev = platform_get_drvdata(pdev); + struct xwdt_device *xdev = dev_get_drvdata(dev); if (watchdog_active(&xdev->xilinx_wdt_wdd)) xilinx_wdt_stop(&xdev->xilinx_wdt_wdd); @@ -285,8 +284,7 @@ static int __maybe_unused xwdt_suspend(struct device *dev) */ static int __maybe_unused xwdt_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct xwdt_device *xdev = platform_get_drvdata(pdev); + struct xwdt_device *xdev = dev_get_drvdata(dev); int ret = 0; if (watchdog_active(&xdev->xilinx_wdt_wdd)) diff --git a/drivers/watchdog/renesas_wdt.c b/drivers/watchdog/renesas_wdt.c index 514db5c..88d81fe 100644 --- a/drivers/watchdog/renesas_wdt.c +++ b/drivers/watchdog/renesas_wdt.c @@ -146,7 +146,7 @@ static const struct soc_device_attribute rwdt_quirks_match[] = { .data = (void *)1, /* needs single CPU */ }, { .soc_id = "r8a7791", - .revision = "ES[12].*", + .revision = "ES1.*", .data = (void *)1, /* needs single CPU */ }, { .soc_id = "r8a7792", diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index 03805bc..9849db0 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -121,6 +121,18 @@ static unsigned int wdt_timeleft(struct watchdog_device *wdd) return div_u64(load, rate); } +static int +wdt_restart(struct watchdog_device *wdd, unsigned long mode, void *cmd) +{ + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); + + writel_relaxed(0, wdt->base + WDTCONTROL); + writel_relaxed(0, wdt->base + WDTLOAD); + writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); + + return 0; +} + static int wdt_config(struct watchdog_device *wdd, bool ping) { struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); @@ -197,6 +209,7 @@ static const struct watchdog_ops wdt_ops = { .ping = wdt_ping, .set_timeout = wdt_setload, .get_timeleft = wdt_timeleft, + .restart = wdt_restart, }; static int @@ -230,6 +243,7 @@ sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) spin_lock_init(&wdt->lock); watchdog_set_nowayout(&wdt->wdd, nowayout); watchdog_set_drvdata(&wdt->wdd, wdt); + watchdog_set_restart_priority(&wdt->wdd, 128); wdt_setload(&wdt->wdd, DEFAULT_TIMEOUT); ret = watchdog_register_device(&wdt->wdd); diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c index 0da9943..56ad196 100644 --- a/drivers/watchdog/wdat_wdt.c +++ b/drivers/watchdog/wdat_wdt.c @@ -447,8 +447,7 @@ static int wdat_wdt_probe(struct platform_device *pdev) #ifdef CONFIG_PM_SLEEP static int wdat_wdt_suspend_noirq(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct wdat_wdt *wdat = platform_get_drvdata(pdev); + struct wdat_wdt *wdat = dev_get_drvdata(dev); int ret; if (!watchdog_active(&wdat->wdd)) @@ -475,8 +474,7 @@ static int wdat_wdt_suspend_noirq(struct device *dev) static int wdat_wdt_resume_noirq(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct wdat_wdt *wdat = platform_get_drvdata(pdev); + struct wdat_wdt *wdat = dev_get_drvdata(dev); int ret; if (!watchdog_active(&wdat->wdd)) |