From 7c696693a4f54d12714738b45aee3e4302884ade Mon Sep 17 00:00:00 2001 From: Caesar Wang Date: Tue, 8 Sep 2015 14:18:22 +0800 Subject: soc: rockchip: power-domain: Add power domain driver This driver is found on RK3288 SoCs. In order to meet high performance and low power requirements, a power management unit is designed or saving power when RK3288 in low power mode. The RK3288 PMU is dedicated for managing the power of the whole chip. PMU can work in the Low Power Mode by setting bit[0] of PMU_PWRMODE_CON register. After setting the register, PMU would enter the Low Power mode. In the low power mode, pmu will auto power on/off the specified power domain, send idle req to specified power domain, shut down/up pll and so on. All of above are configurable by setting corresponding registers. Signed-off-by: Caesar Wang Reviewed-by: Kevin Hilman [replace dsb() with dsb(sy) for arm64 buildability; sy is the default, so no functional change; adapt to per-user clocks in genpd] Signed-off-by: Heiko Stuebner --- drivers/soc/Kconfig | 1 + drivers/soc/Makefile | 1 + drivers/soc/rockchip/Kconfig | 14 ++ drivers/soc/rockchip/Makefile | 4 + drivers/soc/rockchip/pm_domains.c | 490 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 510 insertions(+) create mode 100644 drivers/soc/rockchip/Kconfig create mode 100644 drivers/soc/rockchip/Makefile create mode 100644 drivers/soc/rockchip/pm_domains.c (limited to 'drivers/soc') diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index 96ddecb92254..ecb1a6c7f0d7 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -2,6 +2,7 @@ menu "SOC (System On Chip) specific Drivers" source "drivers/soc/mediatek/Kconfig" source "drivers/soc/qcom/Kconfig" +source "drivers/soc/rockchip/Kconfig" source "drivers/soc/sunxi/Kconfig" source "drivers/soc/ti/Kconfig" source "drivers/soc/versatile/Kconfig" diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 0b12d777d3c4..c2b485f82415 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_MACH_DOVE) += dove/ obj-$(CONFIG_ARCH_MEDIATEK) += mediatek/ obj-$(CONFIG_ARCH_QCOM) += qcom/ +obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_SOC_TI) += ti/ diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig new file mode 100644 index 000000000000..6ee03994801c --- /dev/null +++ b/drivers/soc/rockchip/Kconfig @@ -0,0 +1,14 @@ +# +# Rockchip Soc drivers +# +config ROCKCHIP_PM_DOMAINS + bool "Rockchip generic power domain" + depends on PM + select PM_GENERIC_DOMAINS + help + Say y here to enable power domain support. + In order to meet high performance and low power requirements, a power + management unit is designed or saving power when RK3288 in low power + mode. The RK3288 PMU is dedicated for managing the power of the whole chip. + + If unsure, say N. diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile new file mode 100644 index 000000000000..3d73d0672d22 --- /dev/null +++ b/drivers/soc/rockchip/Makefile @@ -0,0 +1,4 @@ +# +# Rockchip Soc drivers +# +obj-$(CONFIG_ROCKCHIP_PM_DOMAINS) += pm_domains.o diff --git a/drivers/soc/rockchip/pm_domains.c b/drivers/soc/rockchip/pm_domains.c new file mode 100644 index 000000000000..8268d5d2b852 --- /dev/null +++ b/drivers/soc/rockchip/pm_domains.c @@ -0,0 +1,490 @@ +/* + * Rockchip Generic power domain support. + * + * Copyright (c) 2015 ROCKCHIP, Co. 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 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct rockchip_domain_info { + int pwr_mask; + int status_mask; + int req_mask; + int idle_mask; + int ack_mask; +}; + +struct rockchip_pmu_info { + u32 pwr_offset; + u32 status_offset; + u32 req_offset; + u32 idle_offset; + u32 ack_offset; + + u32 core_pwrcnt_offset; + u32 gpu_pwrcnt_offset; + + unsigned int core_power_transition_time; + unsigned int gpu_power_transition_time; + + int num_domains; + const struct rockchip_domain_info *domain_info; +}; + +struct rockchip_pm_domain { + struct generic_pm_domain genpd; + const struct rockchip_domain_info *info; + struct rockchip_pmu *pmu; + int num_clks; + struct clk *clks[]; +}; + +struct rockchip_pmu { + struct device *dev; + struct regmap *regmap; + const struct rockchip_pmu_info *info; + struct mutex mutex; /* mutex lock for pmu */ + struct genpd_onecell_data genpd_data; + struct generic_pm_domain *domains[]; +}; + +#define to_rockchip_pd(gpd) container_of(gpd, struct rockchip_pm_domain, genpd) + +#define DOMAIN(pwr, status, req, idle, ack) \ +{ \ + .pwr_mask = BIT(pwr), \ + .status_mask = BIT(status), \ + .req_mask = BIT(req), \ + .idle_mask = BIT(idle), \ + .ack_mask = BIT(ack), \ +} + +#define DOMAIN_RK3288(pwr, status, req) \ + DOMAIN(pwr, status, req, req, (req) + 16) + +static bool rockchip_pmu_domain_is_idle(struct rockchip_pm_domain *pd) +{ + struct rockchip_pmu *pmu = pd->pmu; + const struct rockchip_domain_info *pd_info = pd->info; + unsigned int val; + + regmap_read(pmu->regmap, pmu->info->idle_offset, &val); + return (val & pd_info->idle_mask) == pd_info->idle_mask; +} + +static int rockchip_pmu_set_idle_request(struct rockchip_pm_domain *pd, + bool idle) +{ + const struct rockchip_domain_info *pd_info = pd->info; + struct rockchip_pmu *pmu = pd->pmu; + unsigned int val; + + regmap_update_bits(pmu->regmap, pmu->info->req_offset, + pd_info->req_mask, idle ? -1U : 0); + + dsb(sy); + + do { + regmap_read(pmu->regmap, pmu->info->ack_offset, &val); + } while ((val & pd_info->ack_mask) != (idle ? pd_info->ack_mask : 0)); + + while (rockchip_pmu_domain_is_idle(pd) != idle) + cpu_relax(); + + return 0; +} + +static bool rockchip_pmu_domain_is_on(struct rockchip_pm_domain *pd) +{ + struct rockchip_pmu *pmu = pd->pmu; + unsigned int val; + + regmap_read(pmu->regmap, pmu->info->status_offset, &val); + + /* 1'b0: power on, 1'b1: power off */ + return !(val & pd->info->status_mask); +} + +static void rockchip_do_pmu_set_power_domain(struct rockchip_pm_domain *pd, + bool on) +{ + struct rockchip_pmu *pmu = pd->pmu; + + regmap_update_bits(pmu->regmap, pmu->info->pwr_offset, + pd->info->pwr_mask, on ? 0 : -1U); + + dsb(sy); + + while (rockchip_pmu_domain_is_on(pd) != on) + cpu_relax(); +} + +static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on) +{ + int i; + + mutex_lock(&pd->pmu->mutex); + + if (rockchip_pmu_domain_is_on(pd) != power_on) { + for (i = 0; i < pd->num_clks; i++) + clk_enable(pd->clks[i]); + + if (!power_on) { + /* FIXME: add code to save AXI_QOS */ + + /* if powering down, idle request to NIU first */ + rockchip_pmu_set_idle_request(pd, true); + } + + rockchip_do_pmu_set_power_domain(pd, power_on); + + if (power_on) { + /* if powering up, leave idle mode */ + rockchip_pmu_set_idle_request(pd, false); + + /* FIXME: add code to restore AXI_QOS */ + } + + for (i = pd->num_clks - 1; i >= 0; i--) + clk_disable(pd->clks[i]); + } + + mutex_unlock(&pd->pmu->mutex); + return 0; +} + +static int rockchip_pd_power_on(struct generic_pm_domain *domain) +{ + struct rockchip_pm_domain *pd = to_rockchip_pd(domain); + + return rockchip_pd_power(pd, true); +} + +static int rockchip_pd_power_off(struct generic_pm_domain *domain) +{ + struct rockchip_pm_domain *pd = to_rockchip_pd(domain); + + return rockchip_pd_power(pd, false); +} + +static int rockchip_pd_attach_dev(struct generic_pm_domain *genpd, + struct device *dev) +{ + struct clk *clk; + int i; + int error; + + dev_dbg(dev, "attaching to power domain '%s'\n", genpd->name); + + error = pm_clk_create(dev); + if (error) { + dev_err(dev, "pm_clk_create failed %d\n", error); + return error; + } + + i = 0; + while ((clk = of_clk_get(dev->of_node, i++)) && !IS_ERR(clk)) { + dev_dbg(dev, "adding clock '%pC' to list of PM clocks\n", clk); + error = pm_clk_add_clk(dev, clk); + if (error) { + dev_err(dev, "pm_clk_add_clk failed %d\n", error); + clk_put(clk); + pm_clk_destroy(dev); + return error; + } + } + + return 0; +} + +static void rockchip_pd_detach_dev(struct generic_pm_domain *genpd, + struct device *dev) +{ + dev_dbg(dev, "detaching from power domain '%s'\n", genpd->name); + + pm_clk_destroy(dev); +} + +static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, + struct device_node *node) +{ + const struct rockchip_domain_info *pd_info; + struct rockchip_pm_domain *pd; + struct clk *clk; + int clk_cnt; + int i; + u32 id; + int error; + + error = of_property_read_u32(node, "reg", &id); + if (error) { + dev_err(pmu->dev, + "%s: failed to retrieve domain id (reg): %d\n", + node->name, error); + return -EINVAL; + } + + if (id >= pmu->info->num_domains) { + dev_err(pmu->dev, "%s: invalid domain id %d\n", + node->name, id); + return -EINVAL; + } + + pd_info = &pmu->info->domain_info[id]; + if (!pd_info) { + dev_err(pmu->dev, "%s: undefined domain id %d\n", + node->name, id); + return -EINVAL; + } + + clk_cnt = of_count_phandle_with_args(node, "clocks", "#clock-cells"); + pd = devm_kzalloc(pmu->dev, + sizeof(*pd) + clk_cnt * sizeof(pd->clks[0]), + GFP_KERNEL); + if (!pd) + return -ENOMEM; + + pd->info = pd_info; + pd->pmu = pmu; + + for (i = 0; i < clk_cnt; i++) { + clk = of_clk_get(node, i); + if (IS_ERR(clk)) { + error = PTR_ERR(clk); + dev_err(pmu->dev, + "%s: failed to get clk %pC (index %d): %d\n", + node->name, clk, i, error); + goto err_out; + } + + error = clk_prepare(clk); + if (error) { + dev_err(pmu->dev, + "%s: failed to prepare clk %pC (index %d): %d\n", + node->name, clk, i, error); + clk_put(clk); + goto err_out; + } + + pd->clks[pd->num_clks++] = clk; + + dev_dbg(pmu->dev, "added clock '%pC' to domain '%s'\n", + clk, node->name); + } + + error = rockchip_pd_power(pd, true); + if (error) { + dev_err(pmu->dev, + "failed to power on domain '%s': %d\n", + node->name, error); + goto err_out; + } + + pd->genpd.name = node->name; + pd->genpd.power_off = rockchip_pd_power_off; + pd->genpd.power_on = rockchip_pd_power_on; + pd->genpd.attach_dev = rockchip_pd_attach_dev; + pd->genpd.detach_dev = rockchip_pd_detach_dev; + pd->genpd.flags = GENPD_FLAG_PM_CLK; + pm_genpd_init(&pd->genpd, NULL, false); + + pmu->genpd_data.domains[id] = &pd->genpd; + return 0; + +err_out: + while (--i >= 0) { + clk_unprepare(pd->clks[i]); + clk_put(pd->clks[i]); + } + return error; +} + +static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd) +{ + int i; + + for (i = 0; i < pd->num_clks; i++) { + clk_unprepare(pd->clks[i]); + clk_put(pd->clks[i]); + } + + /* protect the zeroing of pm->num_clks */ + mutex_lock(&pd->pmu->mutex); + pd->num_clks = 0; + mutex_unlock(&pd->pmu->mutex); + + /* devm will free our memory */ +} + +static void rockchip_pm_domain_cleanup(struct rockchip_pmu *pmu) +{ + struct generic_pm_domain *genpd; + struct rockchip_pm_domain *pd; + int i; + + for (i = 0; i < pmu->genpd_data.num_domains; i++) { + genpd = pmu->genpd_data.domains[i]; + if (genpd) { + pd = to_rockchip_pd(genpd); + rockchip_pm_remove_one_domain(pd); + } + } + + /* devm will free our memory */ +} + +static void rockchip_configure_pd_cnt(struct rockchip_pmu *pmu, + u32 domain_reg_offset, + unsigned int count) +{ + /* First configure domain power down transition count ... */ + regmap_write(pmu->regmap, domain_reg_offset, count); + /* ... and then power up count. */ + regmap_write(pmu->regmap, domain_reg_offset + 4, count); +} + +static int rockchip_pm_domain_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *node; + struct device *parent; + struct rockchip_pmu *pmu; + const struct of_device_id *match; + const struct rockchip_pmu_info *pmu_info; + int error; + + if (!np) { + dev_err(dev, "device tree node not found\n"); + return -ENODEV; + } + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "missing pmu data\n"); + return -EINVAL; + } + + pmu_info = match->data; + + pmu = devm_kzalloc(dev, + sizeof(*pmu) + + pmu_info->num_domains * sizeof(pmu->domains[0]), + GFP_KERNEL); + if (!pmu) + return -ENOMEM; + + pmu->dev = &pdev->dev; + mutex_init(&pmu->mutex); + + pmu->info = pmu_info; + + pmu->genpd_data.domains = pmu->domains; + pmu->genpd_data.num_domains = pmu_info->num_domains; + + parent = dev->parent; + if (!parent) { + dev_err(dev, "no parent for syscon devices\n"); + return -ENODEV; + } + + pmu->regmap = syscon_node_to_regmap(parent->of_node); + + /* + * Configure power up and down transition delays for CORE + * and GPU domains. + */ + rockchip_configure_pd_cnt(pmu, pmu_info->core_pwrcnt_offset, + pmu_info->core_power_transition_time); + rockchip_configure_pd_cnt(pmu, pmu_info->gpu_pwrcnt_offset, + pmu_info->gpu_power_transition_time); + + error = -ENODEV; + + for_each_available_child_of_node(np, node) { + error = rockchip_pm_add_one_domain(pmu, node); + if (error) { + dev_err(dev, "failed to handle node %s: %d\n", + node->name, error); + goto err_out; + } + } + + if (error) { + dev_dbg(dev, "no power domains defined\n"); + goto err_out; + } + + of_genpd_add_provider_onecell(np, &pmu->genpd_data); + + return 0; + +err_out: + rockchip_pm_domain_cleanup(pmu); + return error; +} + +static const struct rockchip_domain_info rk3288_pm_domains[] = { + [RK3288_PD_VIO] = DOMAIN_RK3288(7, 7, 4), + [RK3288_PD_HEVC] = DOMAIN_RK3288(14, 10, 9), + [RK3288_PD_VIDEO] = DOMAIN_RK3288(8, 8, 3), + [RK3288_PD_GPU] = DOMAIN_RK3288(9, 9, 2), +}; + +static const struct rockchip_pmu_info rk3288_pmu = { + .pwr_offset = 0x08, + .status_offset = 0x0c, + .req_offset = 0x10, + .idle_offset = 0x14, + .ack_offset = 0x14, + + .core_pwrcnt_offset = 0x34, + .gpu_pwrcnt_offset = 0x3c, + + .core_power_transition_time = 24, /* 1us */ + .gpu_power_transition_time = 24, /* 1us */ + + .num_domains = ARRAY_SIZE(rk3288_pm_domains), + .domain_info = rk3288_pm_domains, +}; + +static const struct of_device_id rockchip_pm_domain_dt_match[] = { + { + .compatible = "rockchip,rk3288-power-controller", + .data = (void *)&rk3288_pmu, + }, + { /* sentinel */ }, +}; + +static struct platform_driver rockchip_pm_domain_driver = { + .probe = rockchip_pm_domain_probe, + .driver = { + .name = "rockchip-pm-domain", + .of_match_table = rockchip_pm_domain_dt_match, + /* + * We can't forcibly eject devices form power domain, + * so we can't really remove power domains once they + * were added. + */ + .suppress_bind_attrs = true, + }, +}; + +static int __init rockchip_pm_domain_drv_register(void) +{ + return platform_driver_register(&rockchip_pm_domain_driver); +} +postcore_initcall(rockchip_pm_domain_drv_register); -- cgit v1.2.3 From 0cc598915fc787193ddb7401a11f80c17ccbabe4 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 9 Oct 2015 15:46:18 +0200 Subject: soc: rockchip: Restrict to ARCH_ROCKCHIP By definition this directory contains drivers that are specific to the Rockchip architecture. All Kconfig options should therefore depend on ARCH_ROCKCHIP to avoid exposing these symbols on other architectures. For example, this options currently shows up as new when doing an incremental build on PowerPC. Signed-off-by: Thierry Reding [add COMPILE_TEST alternative condition] Signed-off-by: Heiko Stuebner --- drivers/soc/rockchip/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig index 6ee03994801c..7140ff825598 100644 --- a/drivers/soc/rockchip/Kconfig +++ b/drivers/soc/rockchip/Kconfig @@ -1,3 +1,5 @@ +if ARCH_ROCKCHIP || COMPILE_TEST + # # Rockchip Soc drivers # @@ -12,3 +14,5 @@ config ROCKCHIP_PM_DOMAINS mode. The RK3288 PMU is dedicated for managing the power of the whole chip. If unsure, say N. + +endif -- cgit v1.2.3 From 1a7caca20ed56a80cea045327deaeb4e4379cbd1 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 28 Aug 2015 10:39:20 -0700 Subject: soc: qcom: smd: Implement id_table driver matching Implement a id_table based driver maching mechanism for drivers that binds to fixed channels and doesn't need any additional configuration, e.g. IPCRTR and DIAG. Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 25 ++++++++++++++++++------- include/linux/soc/qcom/smd.h | 11 +++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index a6155c917d52..d883c16d1775 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -727,6 +727,19 @@ static struct qcom_smd_driver *to_smd_driver(struct device *dev) static int qcom_smd_dev_match(struct device *dev, struct device_driver *drv) { + struct qcom_smd_device *qsdev = to_smd_device(dev); + struct qcom_smd_driver *qsdrv = container_of(drv, struct qcom_smd_driver, driver); + const struct qcom_smd_id *match = qsdrv->smd_match_table; + const char *name = qsdev->channel->name; + + if (match) { + while (match->name[0]) { + if (!strcmp(match->name, name)) + return 1; + match++; + } + } + return of_driver_match_device(dev, drv); } @@ -880,19 +893,17 @@ static int qcom_smd_create_device(struct qcom_smd_channel *channel) if (channel->qsdev) return -EEXIST; - node = qcom_smd_match_channel(edge->of_node, channel->name); - if (!node) { - dev_dbg(smd->dev, "no match for '%s'\n", channel->name); - return -ENXIO; - } - dev_dbg(smd->dev, "registering '%s'\n", channel->name); qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); if (!qsdev) return -ENOMEM; - dev_set_name(&qsdev->dev, "%s.%s", edge->of_node->name, node->name); + node = qcom_smd_match_channel(edge->of_node, channel->name); + dev_set_name(&qsdev->dev, "%s.%s", + edge->of_node->name, + node ? node->name : channel->name); + qsdev->dev.parent = smd->dev; qsdev->dev.bus = &qcom_smd_bus; qsdev->dev.release = qcom_smd_release_device; diff --git a/include/linux/soc/qcom/smd.h b/include/linux/soc/qcom/smd.h index d7e50aa6a4ac..d0cb6d189a0a 100644 --- a/include/linux/soc/qcom/smd.h +++ b/include/linux/soc/qcom/smd.h @@ -8,6 +8,14 @@ struct qcom_smd; struct qcom_smd_channel; struct qcom_smd_lookup; +/** + * struct qcom_smd_id - struct used for matching a smd device + * @name: name of the channel + */ +struct qcom_smd_id { + char name[20]; +}; + /** * struct qcom_smd_device - smd device struct * @dev: the device struct @@ -21,6 +29,7 @@ struct qcom_smd_device { /** * struct qcom_smd_driver - smd driver struct * @driver: underlying device driver + * @smd_match_table: static channel match table * @probe: invoked when the smd channel is found * @remove: invoked when the smd channel is closed * @callback: invoked when an inbound message is received on the channel, @@ -29,6 +38,8 @@ struct qcom_smd_device { */ struct qcom_smd_driver { struct device_driver driver; + const struct qcom_smd_id *smd_match_table; + int (*probe)(struct qcom_smd_device *dev); void (*remove)(struct qcom_smd_device *dev); int (*callback)(struct qcom_smd_device *, const void *, size_t); -- cgit v1.2.3 From f8c67df71bcdd863d623985c9e8dc78368c862db Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Fri, 28 Aug 2015 11:18:49 -0700 Subject: soc: qcom: smem: Avoid NULL pointer exception on remove Don't set a pointer to NULL and then dereference it in the next line. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 52365188a1c2..f402a606eb7d 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -730,8 +730,8 @@ static int qcom_smem_probe(struct platform_device *pdev) static int qcom_smem_remove(struct platform_device *pdev) { - __smem = NULL; hwspin_lock_free(__smem->hwlock); + __smem = NULL; return 0; } -- cgit v1.2.3 From 7d0c8beea6b8d158d9a628b798dfc1638a7a8ce0 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Fri, 28 Aug 2015 11:23:33 -0700 Subject: soc: qcom: Reorder SMEM/SMD configs When I make nconfig, having the SMEM option after the SMD option causes the configurator to get confused when I'm enabling and disabling these options. Let's move SMEM before SMD so there's a clear indented dependency chain. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/Kconfig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index ba47b70f4d85..3e4d2133c3d2 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -19,6 +19,14 @@ config QCOM_PM modes. It interface with various system drivers to put the cores in low power modes. +config QCOM_SMEM + tristate "Qualcomm Shared Memory Manager (SMEM)" + depends on ARCH_QCOM + help + Say y here to enable support for the Qualcomm Shared Memory Manager. + The driver provides an interface to items in a heap shared among all + processors in a Qualcomm platform. + config QCOM_SMD tristate "Qualcomm Shared Memory Driver (SMD)" depends on QCOM_SMEM @@ -40,11 +48,3 @@ config QCOM_SMD_RPM Say M here if you want to include support for the Qualcomm RPM as a module. This will build a module called "qcom-smd-rpm". - -config QCOM_SMEM - tristate "Qualcomm Shared Memory Manager (SMEM)" - depends on ARCH_QCOM - help - Say y here to enable support for the Qualcomm Shared Memory Manager. - The driver provides an interface to items in a heap shared among all - processors in a Qualcomm platform. -- cgit v1.2.3 From 1a03964dec3cecb6382d172b9dfe318735c2cad7 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 2 Sep 2015 15:46:44 -0700 Subject: soc: qcom: Make qcom_smem_get() return a pointer Passing a void ** almost always requires a cast at the call site. Instead of littering the code with casts every time this function is called, have qcom_smem_get() return a void pointer to the location of the smem item. This frees the caller from having to cast the pointer. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 30 +++++++++--------- drivers/soc/qcom/smem.c | 72 +++++++++++++++++++------------------------ include/linux/soc/qcom/smem.h | 2 +- 3 files changed, 48 insertions(+), 56 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index d883c16d1775..beaea1d5169f 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -989,10 +989,11 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed spin_lock_init(&channel->recv_lock); init_waitqueue_head(&channel->fblockread_event); - ret = qcom_smem_get(edge->remote_pid, smem_info_item, (void **)&info, - &info_size); - if (ret) + info = qcom_smem_get(edge->remote_pid, smem_info_item, &info_size); + if (IS_ERR(info)) { + ret = PTR_ERR(info); goto free_name_and_channel; + } /* * Use the size of the item to figure out which channel info struct to @@ -1011,10 +1012,11 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed goto free_name_and_channel; } - ret = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_base, - &fifo_size); - if (ret) + fifo_base = qcom_smem_get(edge->remote_pid, smem_fifo_item, &fifo_size); + if (IS_ERR(fifo_base)) { + ret = PTR_ERR(fifo_base); goto free_name_and_channel; + } /* The channel consist of a rx and tx fifo of equal size */ fifo_size /= 2; @@ -1051,16 +1053,13 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge) unsigned long flags; unsigned fifo_id; unsigned info_id; - int ret; int tbl; int i; for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) { - ret = qcom_smem_get(edge->remote_pid, - smem_items[tbl].alloc_tbl_id, - (void **)&alloc_tbl, - NULL); - if (ret < 0) + alloc_tbl = qcom_smem_get(edge->remote_pid, + smem_items[tbl].alloc_tbl_id, NULL); + if (IS_ERR(alloc_tbl)) continue; for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) { @@ -1238,11 +1237,12 @@ static int qcom_smd_probe(struct platform_device *pdev) int num_edges; int ret; int i = 0; + void *p; /* Wait for smem */ - ret = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, NULL, NULL); - if (ret == -EPROBE_DEFER) - return ret; + p = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, NULL); + if (PTR_ERR(p) == -EPROBE_DEFER) + return PTR_ERR(p); num_edges = of_get_available_child_count(pdev->dev.of_node); array_size = sizeof(*smd) + num_edges * sizeof(struct qcom_smd_edge); diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index f402a606eb7d..e6d0dae63845 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -378,10 +378,9 @@ int qcom_smem_alloc(unsigned host, unsigned item, size_t size) } EXPORT_SYMBOL(qcom_smem_alloc); -static int qcom_smem_get_global(struct qcom_smem *smem, - unsigned item, - void **ptr, - size_t *size) +static void *qcom_smem_get_global(struct qcom_smem *smem, + unsigned item, + size_t *size) { struct smem_header *header; struct smem_region *area; @@ -390,36 +389,32 @@ static int qcom_smem_get_global(struct qcom_smem *smem, unsigned i; if (WARN_ON(item >= SMEM_ITEM_COUNT)) - return -EINVAL; + return ERR_PTR(-EINVAL); header = smem->regions[0].virt_base; entry = &header->toc[item]; if (!entry->allocated) - return -ENXIO; + return ERR_PTR(-ENXIO); - if (ptr != NULL) { - aux_base = entry->aux_base & AUX_BASE_MASK; + aux_base = entry->aux_base & AUX_BASE_MASK; - for (i = 0; i < smem->num_regions; i++) { - area = &smem->regions[i]; + for (i = 0; i < smem->num_regions; i++) { + area = &smem->regions[i]; - if (area->aux_base == aux_base || !aux_base) { - *ptr = area->virt_base + entry->offset; - break; - } + if (area->aux_base == aux_base || !aux_base) { + if (size != NULL) + *size = entry->size; + return area->virt_base + entry->offset; } } - if (size != NULL) - *size = entry->size; - return 0; + return ERR_PTR(-ENOENT); } -static int qcom_smem_get_private(struct qcom_smem *smem, - unsigned host, - unsigned item, - void **ptr, - size_t *size) +static void *qcom_smem_get_private(struct qcom_smem *smem, + unsigned host, + unsigned item, + size_t *size) { struct smem_partition_header *phdr; struct smem_private_entry *hdr; @@ -435,55 +430,54 @@ static int qcom_smem_get_private(struct qcom_smem *smem, dev_err(smem->dev, "Found invalid canary in host %d partition\n", host); - return -EINVAL; + return ERR_PTR(-EINVAL); } if (hdr->item == item) { - if (ptr != NULL) - *ptr = p + sizeof(*hdr) + hdr->padding_hdr; - if (size != NULL) *size = hdr->size - hdr->padding_data; - return 0; + return p + sizeof(*hdr) + hdr->padding_hdr; } p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; } - return -ENOENT; + return ERR_PTR(-ENOENT); } /** * qcom_smem_get() - resolve ptr of size of a smem item * @host: the remote processor, or -1 * @item: smem item handle - * @ptr: pointer to be filled out with address of the item * @size: pointer to be filled out with size of the item * - * Looks up pointer and size of a smem item. + * Looks up smem item and returns pointer to it. Size of smem + * item is returned in @size. */ -int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size) +void *qcom_smem_get(unsigned host, unsigned item, size_t *size) { unsigned long flags; int ret; + void *ptr = ERR_PTR(-EPROBE_DEFER); if (!__smem) - return -EPROBE_DEFER; + return ptr; ret = hwspin_lock_timeout_irqsave(__smem->hwlock, HWSPINLOCK_TIMEOUT, &flags); if (ret) - return ret; + return ERR_PTR(ret); if (host < SMEM_HOST_COUNT && __smem->partitions[host]) - ret = qcom_smem_get_private(__smem, host, item, ptr, size); + ptr = qcom_smem_get_private(__smem, host, item, size); else - ret = qcom_smem_get_global(__smem, item, ptr, size); + ptr = qcom_smem_get_global(__smem, item, size); hwspin_unlock_irqrestore(__smem->hwlock, &flags); - return ret; + + return ptr; } EXPORT_SYMBOL(qcom_smem_get); @@ -520,11 +514,9 @@ static int qcom_smem_get_sbl_version(struct qcom_smem *smem) { unsigned *versions; size_t size; - int ret; - ret = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, - (void **)&versions, &size); - if (ret < 0) { + versions = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, &size); + if (IS_ERR(versions)) { dev_err(smem->dev, "Unable to read the version item\n"); return -ENOENT; } diff --git a/include/linux/soc/qcom/smem.h b/include/linux/soc/qcom/smem.h index bc9630d3aced..785e196ee2ca 100644 --- a/include/linux/soc/qcom/smem.h +++ b/include/linux/soc/qcom/smem.h @@ -4,7 +4,7 @@ #define QCOM_SMEM_HOST_ANY -1 int qcom_smem_alloc(unsigned host, unsigned item, size_t size); -int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size); +void *qcom_smem_get(unsigned host, unsigned item, size_t *size); int qcom_smem_get_free_space(unsigned host); -- cgit v1.2.3 From 9806884d8cd552e6926c162a022cc4b948f4abc8 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 2 Sep 2015 15:46:45 -0700 Subject: soc: qcom: smem: Handle big endian CPUs The contents of smem are always in little endian, but the smem driver is not capable of being used on big endian CPUs. Annotate the little endian data members and update the code to do the proper byte swapping. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smem.c | 229 +++++++++++++++++++++++++++++------------------- 1 file changed, 138 insertions(+), 91 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index e6d0dae63845..74017114ce6e 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -92,9 +92,9 @@ * @params: parameters to the command */ struct smem_proc_comm { - u32 command; - u32 status; - u32 params[2]; + __le32 command; + __le32 status; + __le32 params[2]; }; /** @@ -106,10 +106,10 @@ struct smem_proc_comm { * the default region. bits 0,1 are reserved */ struct smem_global_entry { - u32 allocated; - u32 offset; - u32 size; - u32 aux_base; /* bits 1:0 reserved */ + __le32 allocated; + __le32 offset; + __le32 size; + __le32 aux_base; /* bits 1:0 reserved */ }; #define AUX_BASE_MASK 0xfffffffc @@ -125,11 +125,11 @@ struct smem_global_entry { */ struct smem_header { struct smem_proc_comm proc_comm[4]; - u32 version[32]; - u32 initialized; - u32 free_offset; - u32 available; - u32 reserved; + __le32 version[32]; + __le32 initialized; + __le32 free_offset; + __le32 available; + __le32 reserved; struct smem_global_entry toc[SMEM_ITEM_COUNT]; }; @@ -143,12 +143,12 @@ struct smem_header { * @reserved: reserved entries for later use */ struct smem_ptable_entry { - u32 offset; - u32 size; - u32 flags; - u16 host0; - u16 host1; - u32 reserved[8]; + __le32 offset; + __le32 size; + __le32 flags; + __le16 host0; + __le16 host1; + __le32 reserved[8]; }; /** @@ -160,13 +160,14 @@ struct smem_ptable_entry { * @entry: list of @smem_ptable_entry for the @num_entries partitions */ struct smem_ptable { - u32 magic; - u32 version; - u32 num_entries; - u32 reserved[5]; + u8 magic[4]; + __le32 version; + __le32 num_entries; + __le32 reserved[5]; struct smem_ptable_entry entry[]; }; -#define SMEM_PTABLE_MAGIC 0x434f5424 /* "$TOC" */ + +static const u8 SMEM_PTABLE_MAGIC[] = { 0x24, 0x54, 0x4f, 0x43 }; /* "$TOC" */ /** * struct smem_partition_header - header of the partitions @@ -181,15 +182,16 @@ struct smem_ptable { * @reserved: for now reserved entries */ struct smem_partition_header { - u32 magic; - u16 host0; - u16 host1; - u32 size; - u32 offset_free_uncached; - u32 offset_free_cached; - u32 reserved[3]; + u8 magic[4]; + __le16 host0; + __le16 host1; + __le32 size; + __le32 offset_free_uncached; + __le32 offset_free_cached; + __le32 reserved[3]; }; -#define SMEM_PART_MAGIC 0x54525024 /* "$PRT" */ + +static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; /** * struct smem_private_entry - header of each item in the private partition @@ -201,12 +203,12 @@ struct smem_partition_header { * @reserved: for now reserved entry */ struct smem_private_entry { - u16 canary; - u16 item; - u32 size; /* includes padding bytes */ - u16 padding_data; - u16 padding_hdr; - u32 reserved; + u16 canary; /* bytes are the same so no swapping needed */ + __le16 item; + __le32 size; /* includes padding bytes */ + __le16 padding_data; + __le16 padding_hdr; + __le32 reserved; }; #define SMEM_PRIVATE_CANARY 0xa5a5 @@ -242,6 +244,45 @@ struct qcom_smem { struct smem_region regions[0]; }; +static struct smem_private_entry * +phdr_to_last_private_entry(struct smem_partition_header *phdr) +{ + void *p = phdr; + + return p + le32_to_cpu(phdr->offset_free_uncached); +} + +static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr) +{ + void *p = phdr; + + return p + le32_to_cpu(phdr->offset_free_cached); +} + +static struct smem_private_entry * +phdr_to_first_private_entry(struct smem_partition_header *phdr) +{ + void *p = phdr; + + return p + sizeof(*phdr); +} + +static struct smem_private_entry * +private_entry_next(struct smem_private_entry *e) +{ + void *p = e; + + return p + sizeof(*e) + le16_to_cpu(e->padding_hdr) + + le32_to_cpu(e->size); +} + +static void *entry_to_item(struct smem_private_entry *e) +{ + void *p = e; + + return p + sizeof(*e) + le16_to_cpu(e->padding_hdr); +} + /* Pointer to the one and only smem handle */ static struct qcom_smem *__smem; @@ -254,16 +295,16 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, size_t size) { struct smem_partition_header *phdr; - struct smem_private_entry *hdr; + struct smem_private_entry *hdr, *end; size_t alloc_size; - void *p; + void *cached; phdr = smem->partitions[host]; + hdr = phdr_to_first_private_entry(phdr); + end = phdr_to_last_private_entry(phdr); + cached = phdr_to_first_cached_entry(phdr); - p = (void *)phdr + sizeof(*phdr); - while (p < (void *)phdr + phdr->offset_free_uncached) { - hdr = p; - + while (hdr < end) { if (hdr->canary != SMEM_PRIVATE_CANARY) { dev_err(smem->dev, "Found invalid canary in host %d partition\n", @@ -271,24 +312,23 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, return -EINVAL; } - if (hdr->item == item) + if (le16_to_cpu(hdr->item) == item) return -EEXIST; - p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; + hdr = private_entry_next(hdr); } /* Check that we don't grow into the cached region */ alloc_size = sizeof(*hdr) + ALIGN(size, 8); - if (p + alloc_size >= (void *)phdr + phdr->offset_free_cached) { + if ((void *)hdr + alloc_size >= cached) { dev_err(smem->dev, "Out of memory\n"); return -ENOSPC; } - hdr = p; hdr->canary = SMEM_PRIVATE_CANARY; - hdr->item = item; - hdr->size = ALIGN(size, 8); - hdr->padding_data = hdr->size - size; + hdr->item = cpu_to_le16(item); + hdr->size = cpu_to_le32(ALIGN(size, 8)); + hdr->padding_data = cpu_to_le16(le32_to_cpu(hdr->size) - size); hdr->padding_hdr = 0; /* @@ -297,7 +337,7 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, * gets a consistent view of the linked list. */ wmb(); - phdr->offset_free_uncached += alloc_size; + le32_add_cpu(&phdr->offset_free_uncached, alloc_size); return 0; } @@ -318,11 +358,11 @@ static int qcom_smem_alloc_global(struct qcom_smem *smem, return -EEXIST; size = ALIGN(size, 8); - if (WARN_ON(size > header->available)) + if (WARN_ON(size > le32_to_cpu(header->available))) return -ENOMEM; entry->offset = header->free_offset; - entry->size = size; + entry->size = cpu_to_le32(size); /* * Ensure the header is consistent before we mark the item allocated, @@ -330,10 +370,10 @@ static int qcom_smem_alloc_global(struct qcom_smem *smem, * even though they do not take the spinlock on read. */ wmb(); - entry->allocated = 1; + entry->allocated = cpu_to_le32(1); - header->free_offset += size; - header->available -= size; + le32_add_cpu(&header->free_offset, size); + le32_add_cpu(&header->available, -size); return 0; } @@ -396,15 +436,15 @@ static void *qcom_smem_get_global(struct qcom_smem *smem, if (!entry->allocated) return ERR_PTR(-ENXIO); - aux_base = entry->aux_base & AUX_BASE_MASK; + aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; for (i = 0; i < smem->num_regions; i++) { area = &smem->regions[i]; if (area->aux_base == aux_base || !aux_base) { if (size != NULL) - *size = entry->size; - return area->virt_base + entry->offset; + *size = le32_to_cpu(entry->size); + return area->virt_base + le32_to_cpu(entry->offset); } } @@ -417,30 +457,29 @@ static void *qcom_smem_get_private(struct qcom_smem *smem, size_t *size) { struct smem_partition_header *phdr; - struct smem_private_entry *hdr; - void *p; + struct smem_private_entry *e, *end; phdr = smem->partitions[host]; + e = phdr_to_first_private_entry(phdr); + end = phdr_to_last_private_entry(phdr); - p = (void *)phdr + sizeof(*phdr); - while (p < (void *)phdr + phdr->offset_free_uncached) { - hdr = p; - - if (hdr->canary != SMEM_PRIVATE_CANARY) { + while (e < end) { + if (e->canary != SMEM_PRIVATE_CANARY) { dev_err(smem->dev, "Found invalid canary in host %d partition\n", host); return ERR_PTR(-EINVAL); } - if (hdr->item == item) { + if (le16_to_cpu(e->item) == item) { if (size != NULL) - *size = hdr->size - hdr->padding_data; + *size = le32_to_cpu(e->size) - + le16_to_cpu(e->padding_data); - return p + sizeof(*hdr) + hdr->padding_hdr; + return entry_to_item(e); } - p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; + e = private_entry_next(e); } return ERR_PTR(-ENOENT); @@ -500,10 +539,11 @@ int qcom_smem_get_free_space(unsigned host) if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { phdr = __smem->partitions[host]; - ret = phdr->offset_free_cached - phdr->offset_free_uncached; + ret = le32_to_cpu(phdr->offset_free_cached) - + le32_to_cpu(phdr->offset_free_uncached); } else { header = __smem->regions[0].virt_base; - ret = header->available; + ret = le32_to_cpu(header->available); } return ret; @@ -512,7 +552,7 @@ EXPORT_SYMBOL(qcom_smem_get_free_space); static int qcom_smem_get_sbl_version(struct qcom_smem *smem) { - unsigned *versions; + __le32 *versions; size_t size; versions = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, &size); @@ -526,7 +566,7 @@ static int qcom_smem_get_sbl_version(struct qcom_smem *smem) return -EINVAL; } - return versions[SMEM_MASTER_SBL_VERSION_INDEX]; + return le32_to_cpu(versions[SMEM_MASTER_SBL_VERSION_INDEX]); } static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, @@ -536,35 +576,38 @@ static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, struct smem_ptable_entry *entry; struct smem_ptable *ptable; unsigned remote_host; + u32 version, host0, host1; int i; ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; - if (ptable->magic != SMEM_PTABLE_MAGIC) + if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) return 0; - if (ptable->version != 1) { + version = le32_to_cpu(ptable->version); + if (version != 1) { dev_err(smem->dev, - "Unsupported partition header version %d\n", - ptable->version); + "Unsupported partition header version %d\n", version); return -EINVAL; } - for (i = 0; i < ptable->num_entries; i++) { + for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { entry = &ptable->entry[i]; + host0 = le16_to_cpu(entry->host0); + host1 = le16_to_cpu(entry->host1); - if (entry->host0 != local_host && entry->host1 != local_host) + if (host0 != local_host && host1 != local_host) continue; - if (!entry->offset) + if (!le32_to_cpu(entry->offset)) continue; - if (!entry->size) + if (!le32_to_cpu(entry->size)) continue; - if (entry->host0 == local_host) - remote_host = entry->host1; + if (host0 == local_host) + remote_host = host1; else - remote_host = entry->host0; + remote_host = host0; if (remote_host >= SMEM_HOST_COUNT) { dev_err(smem->dev, @@ -580,21 +623,24 @@ static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, return -EINVAL; } - header = smem->regions[0].virt_base + entry->offset; + header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); + host0 = le16_to_cpu(header->host0); + host1 = le16_to_cpu(header->host1); - if (header->magic != SMEM_PART_MAGIC) { + if (memcmp(header->magic, SMEM_PART_MAGIC, + sizeof(header->magic))) { dev_err(smem->dev, "Partition %d has invalid magic\n", i); return -EINVAL; } - if (header->host0 != local_host && header->host1 != local_host) { + if (host0 != local_host && host1 != local_host) { dev_err(smem->dev, "Partition %d hosts are invalid\n", i); return -EINVAL; } - if (header->host0 != remote_host && header->host1 != remote_host) { + if (host0 != remote_host && host1 != remote_host) { dev_err(smem->dev, "Partition %d hosts are invalid\n", i); return -EINVAL; @@ -606,7 +652,7 @@ static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, return -EINVAL; } - if (header->offset_free_uncached > header->size) { + if (le32_to_cpu(header->offset_free_uncached) > le32_to_cpu(header->size)) { dev_err(smem->dev, "Partition %d has invalid free pointer\n", i); return -EINVAL; @@ -690,7 +736,8 @@ static int qcom_smem_probe(struct platform_device *pdev) } header = smem->regions[0].virt_base; - if (header->initialized != 1 || header->reserved) { + if (le32_to_cpu(header->initialized) != 1 || + le32_to_cpu(header->reserved)) { dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); return -EINVAL; } -- cgit v1.2.3 From f02dc82523a72619a10e24355c7b2f12c6814d52 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 2 Sep 2015 15:46:46 -0700 Subject: soc: qcom: smd: Represent channel layout in structures The rx and tx channel info are laid out in memory next to each other, and there are two types of channel info structures, byte based and word based. We have 4 pointers to these info structures, when we really only need two to point to the different types of structures. Encapsulate the byte based and word based tx/rx structures in a "channel pair" structure that describes the layout of memory and reduces the number of pointers in the smd channel structure by two. Reviewed-by: Bjorn Andersson Signed-off-by: Stephen Boyd Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 61 +++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 28 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index beaea1d5169f..3efa3670ce18 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -65,7 +65,9 @@ */ struct smd_channel_info; +struct smd_channel_info_pair; struct smd_channel_info_word; +struct smd_channel_info_word_pair; #define SMD_ALLOC_TBL_COUNT 2 #define SMD_ALLOC_TBL_SIZE 64 @@ -151,10 +153,8 @@ enum smd_channel_state { * @name: name of the channel * @state: local state of the channel * @remote_state: remote state of the channel - * @tx_info: byte aligned outgoing channel info - * @rx_info: byte aligned incoming channel info - * @tx_info_word: word aligned outgoing channel info - * @rx_info_word: word aligned incoming channel info + * @info: byte aligned outgoing/incoming channel info + * @info_word: word aligned outgoing/incoming channel info * @tx_lock: lock to make writes to the channel mutually exclusive * @fblockread_event: wakeup event tied to tx fBLOCKREADINTR * @tx_fifo: pointer to the outgoing ring buffer @@ -175,11 +175,8 @@ struct qcom_smd_channel { enum smd_channel_state state; enum smd_channel_state remote_state; - struct smd_channel_info *tx_info; - struct smd_channel_info *rx_info; - - struct smd_channel_info_word *tx_info_word; - struct smd_channel_info_word *rx_info_word; + struct smd_channel_info_pair *info; + struct smd_channel_info_word_pair *info_word; struct mutex tx_lock; wait_queue_head_t fblockread_event; @@ -228,6 +225,11 @@ struct smd_channel_info { u32 head; }; +struct smd_channel_info_pair { + struct smd_channel_info tx; + struct smd_channel_info rx; +}; + /* * Format of the smd_info smem items, for word aligned channels. */ @@ -245,25 +247,30 @@ struct smd_channel_info_word { u32 head; }; +struct smd_channel_info_word_pair { + struct smd_channel_info_word tx; + struct smd_channel_info_word rx; +}; + #define GET_RX_CHANNEL_INFO(channel, param) \ - (channel->rx_info_word ? \ - channel->rx_info_word->param : \ - channel->rx_info->param) + (channel->info_word ? \ + channel->info_word->rx.param : \ + channel->info->rx.param) #define SET_RX_CHANNEL_INFO(channel, param, value) \ - (channel->rx_info_word ? \ - (channel->rx_info_word->param = value) : \ - (channel->rx_info->param = value)) + (channel->info_word ? \ + (channel->info_word->rx.param = value) : \ + (channel->info->rx.param = value)) #define GET_TX_CHANNEL_INFO(channel, param) \ - (channel->tx_info_word ? \ - channel->tx_info_word->param : \ - channel->tx_info->param) + (channel->info_word ? \ + channel->info_word->tx.param : \ + channel->info->tx.param) #define SET_TX_CHANNEL_INFO(channel, param, value) \ - (channel->tx_info_word ? \ - (channel->tx_info_word->param = value) : \ - (channel->tx_info->param = value)) + (channel->info_word ? \ + (channel->info_word->tx.param = value) : \ + (channel->info->tx.param = value)) /** * struct qcom_smd_alloc_entry - channel allocation entry @@ -412,7 +419,7 @@ static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel, unsigned tail; size_t len; - word_aligned = channel->rx_info_word != NULL; + word_aligned = channel->info_word; tail = GET_RX_CHANNEL_INFO(channel, tail); len = min_t(size_t, count, channel->fifo_size - tail); @@ -627,7 +634,7 @@ static int qcom_smd_write_fifo(struct qcom_smd_channel *channel, unsigned head; size_t len; - word_aligned = channel->tx_info_word != NULL; + word_aligned = channel->info_word; head = GET_TX_CHANNEL_INFO(channel, head); len = min_t(size_t, count, channel->fifo_size - head); @@ -670,7 +677,7 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len) int ret; /* Word aligned channels only accept word size aligned data */ - if (channel->rx_info_word != NULL && len % 4) + if (channel->info_word && len % 4) return -EINVAL; ret = mutex_lock_interruptible(&channel->tx_lock); @@ -1000,11 +1007,9 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed * use. */ if (info_size == 2 * sizeof(struct smd_channel_info_word)) { - channel->tx_info_word = info; - channel->rx_info_word = info + sizeof(struct smd_channel_info_word); + channel->info_word = info; } else if (info_size == 2 * sizeof(struct smd_channel_info)) { - channel->tx_info = info; - channel->rx_info = info + sizeof(struct smd_channel_info); + channel->info = info; } else { dev_err(smd->dev, "channel info of size %zu not supported\n", info_size); -- cgit v1.2.3 From 3b781e55c6cb1afa5da7b2b1b2bb1b0c9ee7bf97 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 2 Sep 2015 15:46:47 -0700 Subject: soc: qcom: smd: Use __iowrite32_copy() instead of open-coding it We already have a function to do this and it silences some sparse warnings along the way. Reviewed-by: Bjorn Andersson Signed-off-by: Stephen Boyd Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index 3efa3670ce18..f9768bff8051 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -371,20 +371,15 @@ static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel, /* * Copy count bytes of data using 32bit accesses, if that's required. */ -static void smd_copy_to_fifo(void __iomem *_dst, - const void *_src, +static void smd_copy_to_fifo(void __iomem *dst, + const void *src, size_t count, bool word_aligned) { - u32 *dst = (u32 *)_dst; - u32 *src = (u32 *)_src; - if (word_aligned) { - count /= sizeof(u32); - while (count--) - writel_relaxed(*src++, dst++); + __iowrite32_copy(dst, src, count / sizeof(u32)); } else { - memcpy_toio(_dst, _src, count); + memcpy_toio(dst, src, count); } } -- cgit v1.2.3 From 50e1b29b4438bdb0be61ff41e6925cab6f8a9284 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 2 Sep 2015 15:46:48 -0700 Subject: soc: qcom: smd: Remove use of VLAIS Usage of VLAIS prevents clang from compiling this file, and it also opens us to the possibility of allocating a large structure on the stack to the point that we blow past the limit of the kernel stack. Remove the VLAIS and allocate a structure on the heap with kmalloc so that we're safer and more clang friendly. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smd-rpm.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index 1392ccf14a20..7709579d63d0 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -104,30 +105,34 @@ int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, static unsigned msg_id = 1; int left; int ret; - struct { struct qcom_rpm_header hdr; struct qcom_rpm_request req; - u8 payload[count]; - } pkt; + u8 payload[]; + } *pkt; + size_t size = sizeof(*pkt) + count; /* SMD packets to the RPM may not exceed 256 bytes */ - if (WARN_ON(sizeof(pkt) >= 256)) + if (WARN_ON(size >= 256)) return -EINVAL; + pkt = kmalloc(size, GFP_KERNEL); + if (!pkt) + return -ENOMEM; + mutex_lock(&rpm->lock); - pkt.hdr.service_type = RPM_SERVICE_TYPE_REQUEST; - pkt.hdr.length = sizeof(struct qcom_rpm_request) + count; + pkt->hdr.service_type = RPM_SERVICE_TYPE_REQUEST; + pkt->hdr.length = sizeof(struct qcom_rpm_request) + count; - pkt.req.msg_id = msg_id++; - pkt.req.flags = BIT(state); - pkt.req.type = type; - pkt.req.id = id; - pkt.req.data_len = count; - memcpy(pkt.payload, buf, count); + pkt->req.msg_id = msg_id++; + pkt->req.flags = BIT(state); + pkt->req.type = type; + pkt->req.id = id; + pkt->req.data_len = count; + memcpy(pkt->payload, buf, count); - ret = qcom_smd_send(rpm->rpm_channel, &pkt, sizeof(pkt)); + ret = qcom_smd_send(rpm->rpm_channel, pkt, sizeof(*pkt)); if (ret) goto out; @@ -138,6 +143,7 @@ int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, ret = rpm->ack_status; out: + kfree(pkt); mutex_unlock(&rpm->lock); return ret; } -- cgit v1.2.3 From 30b7ea5eda0afb13164b2d7049a56645cfc1d47b Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 2 Sep 2015 15:46:50 -0700 Subject: soc: qcom: smd_rpm: Handle big endian CPUs The smd rpm structures are always in little endian, but this driver is not capable of being used on big endian CPUs. Annotate the little endian data members and update the code to do the proper byte swapping. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Reviewed-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smd-rpm.c | 50 ++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 24 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index 7709579d63d0..d4cbaa3d4629 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -45,8 +45,8 @@ struct qcom_smd_rpm { * @length: length of the payload */ struct qcom_rpm_header { - u32 service_type; - u32 length; + __le32 service_type; + __le32 length; }; /** @@ -58,11 +58,11 @@ struct qcom_rpm_header { * @data_len: length of the payload following this header */ struct qcom_rpm_request { - u32 msg_id; - u32 flags; - u32 type; - u32 id; - u32 data_len; + __le32 msg_id; + __le32 flags; + __le32 type; + __le32 id; + __le32 data_len; }; /** @@ -75,10 +75,10 @@ struct qcom_rpm_request { * Multiple of these messages can be stacked in an rpm message. */ struct qcom_rpm_message { - u32 msg_type; - u32 length; + __le32 msg_type; + __le32 length; union { - u32 msg_id; + __le32 msg_id; u8 message[0]; }; }; @@ -122,14 +122,14 @@ int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, mutex_lock(&rpm->lock); - pkt->hdr.service_type = RPM_SERVICE_TYPE_REQUEST; - pkt->hdr.length = sizeof(struct qcom_rpm_request) + count; + pkt->hdr.service_type = cpu_to_le32(RPM_SERVICE_TYPE_REQUEST); + pkt->hdr.length = cpu_to_le32(sizeof(struct qcom_rpm_request) + count); - pkt->req.msg_id = msg_id++; - pkt->req.flags = BIT(state); - pkt->req.type = type; - pkt->req.id = id; - pkt->req.data_len = count; + pkt->req.msg_id = cpu_to_le32(msg_id++); + pkt->req.flags = cpu_to_le32(BIT(state)); + pkt->req.type = cpu_to_le32(type); + pkt->req.id = cpu_to_le32(id); + pkt->req.data_len = cpu_to_le32(count); memcpy(pkt->payload, buf, count); ret = qcom_smd_send(rpm->rpm_channel, pkt, sizeof(*pkt)); @@ -154,27 +154,29 @@ static int qcom_smd_rpm_callback(struct qcom_smd_device *qsdev, size_t count) { const struct qcom_rpm_header *hdr = data; + size_t hdr_length = le32_to_cpu(hdr->length); const struct qcom_rpm_message *msg; struct qcom_smd_rpm *rpm = dev_get_drvdata(&qsdev->dev); const u8 *buf = data + sizeof(struct qcom_rpm_header); - const u8 *end = buf + hdr->length; + const u8 *end = buf + hdr_length; char msgbuf[32]; int status = 0; - u32 len; + u32 len, msg_length; - if (hdr->service_type != RPM_SERVICE_TYPE_REQUEST || - hdr->length < sizeof(struct qcom_rpm_message)) { + if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST || + hdr_length < sizeof(struct qcom_rpm_message)) { dev_err(&qsdev->dev, "invalid request\n"); return 0; } while (buf < end) { msg = (struct qcom_rpm_message *)buf; - switch (msg->msg_type) { + msg_length = le32_to_cpu(msg->length); + switch (le32_to_cpu(msg->msg_type)) { case RPM_MSG_TYPE_MSG_ID: break; case RPM_MSG_TYPE_ERR: - len = min_t(u32, ALIGN(msg->length, 4), sizeof(msgbuf)); + len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf)); memcpy_fromio(msgbuf, msg->message, len); msgbuf[len - 1] = 0; @@ -185,7 +187,7 @@ static int qcom_smd_rpm_callback(struct qcom_smd_device *qsdev, break; } - buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg->length, 4); + buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4); } rpm->ack_status = status; -- cgit v1.2.3 From 24f60e3776e340136f8d822aff75fe06b78a315f Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 17 Sep 2015 14:50:53 -0700 Subject: soc: qcom: smd: Handle big endian CPUs The smd structures are always in little endian, but the smd driver is not capable of being used on big endian CPUs. Annotate the little endian data members and update the code to do the proper byte swapping. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 181 +++++++++++++++++++++++++++++++------------------ 1 file changed, 116 insertions(+), 65 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index f9768bff8051..18964f154383 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -212,7 +212,7 @@ struct qcom_smd { * Format of the smd_info smem items, for byte aligned channels. */ struct smd_channel_info { - u32 state; + __le32 state; u8 fDSR; u8 fCTS; u8 fCD; @@ -221,8 +221,8 @@ struct smd_channel_info { u8 fTAIL; u8 fSTATE; u8 fBLOCKREADINTR; - u32 tail; - u32 head; + __le32 tail; + __le32 head; }; struct smd_channel_info_pair { @@ -234,17 +234,17 @@ struct smd_channel_info_pair { * Format of the smd_info smem items, for word aligned channels. */ struct smd_channel_info_word { - u32 state; - u32 fDSR; - u32 fCTS; - u32 fCD; - u32 fRI; - u32 fHEAD; - u32 fTAIL; - u32 fSTATE; - u32 fBLOCKREADINTR; - u32 tail; - u32 head; + __le32 state; + __le32 fDSR; + __le32 fCTS; + __le32 fCD; + __le32 fRI; + __le32 fHEAD; + __le32 fTAIL; + __le32 fSTATE; + __le32 fBLOCKREADINTR; + __le32 tail; + __le32 head; }; struct smd_channel_info_word_pair { @@ -252,25 +252,73 @@ struct smd_channel_info_word_pair { struct smd_channel_info_word rx; }; -#define GET_RX_CHANNEL_INFO(channel, param) \ - (channel->info_word ? \ - channel->info_word->rx.param : \ - channel->info->rx.param) - -#define SET_RX_CHANNEL_INFO(channel, param, value) \ - (channel->info_word ? \ - (channel->info_word->rx.param = value) : \ - (channel->info->rx.param = value)) - -#define GET_TX_CHANNEL_INFO(channel, param) \ - (channel->info_word ? \ - channel->info_word->tx.param : \ - channel->info->tx.param) - -#define SET_TX_CHANNEL_INFO(channel, param, value) \ - (channel->info_word ? \ - (channel->info_word->tx.param = value) : \ - (channel->info->tx.param = value)) +#define GET_RX_CHANNEL_FLAG(channel, param) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ + channel->info_word ? \ + le32_to_cpu(channel->info_word->rx.param) : \ + channel->info->rx.param; \ + }) + +#define GET_RX_CHANNEL_INFO(channel, param) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ + le32_to_cpu(channel->info_word ? \ + channel->info_word->rx.param : \ + channel->info->rx.param); \ + }) + +#define SET_RX_CHANNEL_FLAG(channel, param, value) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u8)); \ + if (channel->info_word) \ + channel->info_word->rx.param = cpu_to_le32(value); \ + else \ + channel->info->rx.param = value; \ + }) + +#define SET_RX_CHANNEL_INFO(channel, param, value) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->rx.param) != sizeof(u32)); \ + if (channel->info_word) \ + channel->info_word->rx.param = cpu_to_le32(value); \ + else \ + channel->info->rx.param = cpu_to_le32(value); \ + }) + +#define GET_TX_CHANNEL_FLAG(channel, param) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ + channel->info_word ? \ + le32_to_cpu(channel->info_word->tx.param) : \ + channel->info->tx.param; \ + }) + +#define GET_TX_CHANNEL_INFO(channel, param) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ + le32_to_cpu(channel->info_word ? \ + channel->info_word->tx.param : \ + channel->info->tx.param); \ + }) + +#define SET_TX_CHANNEL_FLAG(channel, param, value) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u8)); \ + if (channel->info_word) \ + channel->info_word->tx.param = cpu_to_le32(value); \ + else \ + channel->info->tx.param = value; \ + }) + +#define SET_TX_CHANNEL_INFO(channel, param, value) \ + ({ \ + BUILD_BUG_ON(sizeof(channel->info->tx.param) != sizeof(u32)); \ + if (channel->info_word) \ + channel->info_word->tx.param = cpu_to_le32(value); \ + else \ + channel->info->tx.param = cpu_to_le32(value); \ + }) /** * struct qcom_smd_alloc_entry - channel allocation entry @@ -281,9 +329,9 @@ struct smd_channel_info_word_pair { */ struct qcom_smd_alloc_entry { u8 name[20]; - u32 cid; - u32 flags; - u32 ref_count; + __le32 cid; + __le32 flags; + __le32 ref_count; } __packed; #define SMD_CHANNEL_FLAGS_EDGE_MASK 0xff @@ -312,14 +360,14 @@ static void qcom_smd_signal_channel(struct qcom_smd_channel *channel) static void qcom_smd_channel_reset(struct qcom_smd_channel *channel) { SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); - SET_TX_CHANNEL_INFO(channel, fDSR, 0); - SET_TX_CHANNEL_INFO(channel, fCTS, 0); - SET_TX_CHANNEL_INFO(channel, fCD, 0); - SET_TX_CHANNEL_INFO(channel, fRI, 0); - SET_TX_CHANNEL_INFO(channel, fHEAD, 0); - SET_TX_CHANNEL_INFO(channel, fTAIL, 0); - SET_TX_CHANNEL_INFO(channel, fSTATE, 1); - SET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR, 1); + SET_TX_CHANNEL_FLAG(channel, fDSR, 0); + SET_TX_CHANNEL_FLAG(channel, fCTS, 0); + SET_TX_CHANNEL_FLAG(channel, fCD, 0); + SET_TX_CHANNEL_FLAG(channel, fRI, 0); + SET_TX_CHANNEL_FLAG(channel, fHEAD, 0); + SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); + SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); + SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); SET_TX_CHANNEL_INFO(channel, head, 0); SET_TX_CHANNEL_INFO(channel, tail, 0); @@ -357,12 +405,12 @@ static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel, dev_dbg(edge->smd->dev, "set_state(%s, %d)\n", channel->name, state); - SET_TX_CHANNEL_INFO(channel, fDSR, is_open); - SET_TX_CHANNEL_INFO(channel, fCTS, is_open); - SET_TX_CHANNEL_INFO(channel, fCD, is_open); + SET_TX_CHANNEL_FLAG(channel, fDSR, is_open); + SET_TX_CHANNEL_FLAG(channel, fCTS, is_open); + SET_TX_CHANNEL_FLAG(channel, fCD, is_open); SET_TX_CHANNEL_INFO(channel, state, state); - SET_TX_CHANNEL_INFO(channel, fSTATE, 1); + SET_TX_CHANNEL_FLAG(channel, fSTATE, 1); channel->state = state; qcom_smd_signal_channel(channel); @@ -397,7 +445,7 @@ static void smd_copy_from_fifo(void *_dst, if (word_aligned) { count /= sizeof(u32); while (count--) - *dst++ = readl_relaxed(src++); + *dst++ = __raw_readl(src++); } else { memcpy_fromio(_dst, _src, count); } @@ -493,7 +541,7 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) { bool need_state_scan = false; int remote_state; - u32 pktlen; + __le32 pktlen; int avail; int ret; @@ -504,10 +552,10 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) need_state_scan = true; } /* Indicate that we have seen any state change */ - SET_RX_CHANNEL_INFO(channel, fSTATE, 0); + SET_RX_CHANNEL_FLAG(channel, fSTATE, 0); /* Signal waiting qcom_smd_send() about the interrupt */ - if (!GET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR)) + if (!GET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) wake_up_interruptible(&channel->fblockread_event); /* Don't consume any data until we've opened the channel */ @@ -515,7 +563,7 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) goto out; /* Indicate that we've seen the new data */ - SET_RX_CHANNEL_INFO(channel, fHEAD, 0); + SET_RX_CHANNEL_FLAG(channel, fHEAD, 0); /* Consume data */ for (;;) { @@ -524,7 +572,7 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) { qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen)); qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN); - channel->pkt_size = pktlen; + channel->pkt_size = le32_to_cpu(pktlen); } else if (channel->pkt_size && avail >= channel->pkt_size) { ret = qcom_smd_channel_recv_single(channel); if (ret) @@ -535,10 +583,10 @@ static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) } /* Indicate that we have seen and updated tail */ - SET_RX_CHANNEL_INFO(channel, fTAIL, 1); + SET_RX_CHANNEL_FLAG(channel, fTAIL, 1); /* Signal the remote that we've consumed the data (if requested) */ - if (!GET_RX_CHANNEL_INFO(channel, fBLOCKREADINTR)) { + if (!GET_RX_CHANNEL_FLAG(channel, fBLOCKREADINTR)) { /* Ensure ordering of channel info updates */ wmb(); @@ -667,7 +715,7 @@ static int qcom_smd_write_fifo(struct qcom_smd_channel *channel, */ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len) { - u32 hdr[5] = {len,}; + __le32 hdr[5] = { cpu_to_le32(len), }; int tlen = sizeof(hdr) + len; int ret; @@ -685,7 +733,7 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len) goto out; } - SET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR, 0); + SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 0); ret = wait_event_interruptible(channel->fblockread_event, qcom_smd_get_tx_avail(channel) >= tlen || @@ -693,15 +741,15 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len) if (ret) goto out; - SET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR, 1); + SET_TX_CHANNEL_FLAG(channel, fBLOCKREADINTR, 1); } - SET_TX_CHANNEL_INFO(channel, fTAIL, 0); + SET_TX_CHANNEL_FLAG(channel, fTAIL, 0); qcom_smd_write_fifo(channel, hdr, sizeof(hdr)); qcom_smd_write_fifo(channel, data, len); - SET_TX_CHANNEL_INFO(channel, fHEAD, 1); + SET_TX_CHANNEL_FLAG(channel, fHEAD, 1); /* Ensure ordering of channel info updates */ wmb(); @@ -1055,6 +1103,7 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge) unsigned info_id; int tbl; int i; + u32 eflags, cid; for (tbl = 0; tbl < SMD_ALLOC_TBL_COUNT; tbl++) { alloc_tbl = qcom_smem_get(edge->remote_pid, @@ -1064,6 +1113,7 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge) for (i = 0; i < SMD_ALLOC_TBL_SIZE; i++) { entry = &alloc_tbl[i]; + eflags = le32_to_cpu(entry->flags); if (test_bit(i, edge->allocated[tbl])) continue; @@ -1073,14 +1123,15 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge) if (!entry->name[0]) continue; - if (!(entry->flags & SMD_CHANNEL_FLAGS_PACKET)) + if (!(eflags & SMD_CHANNEL_FLAGS_PACKET)) continue; - if ((entry->flags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id) + if ((eflags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id) continue; - info_id = smem_items[tbl].info_base_id + entry->cid; - fifo_id = smem_items[tbl].fifo_base_id + entry->cid; + cid = le32_to_cpu(entry->cid); + info_id = smem_items[tbl].info_base_id + cid; + fifo_id = smem_items[tbl].fifo_base_id + cid; channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name); if (IS_ERR(channel)) -- cgit v1.2.3 From a208ca98ff63e5c94d03810bc80fee9b02fe9972 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 24 Sep 2015 18:37:18 -0700 Subject: soc: qcom: smd: Reject send of too big packets Attempting to find room for a packet that's bigger than the fifo will never succeed and the calling process will be sleeping forever in the loop, waiting for enough room. So fail early instead. Reported-by: Courtney Cavin Signed-off-by: Bjorn Andersson Reviewed-by: Andy Gross Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index 18964f154383..88353bda1ea4 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -723,6 +723,10 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len) if (channel->info_word && len % 4) return -EINVAL; + /* Reject packets that are too big */ + if (tlen >= channel->fifo_size) + return -EINVAL; + ret = mutex_lock_interruptible(&channel->tx_lock); if (ret) return ret; -- cgit v1.2.3 From ea4683e0b6c38ed54e5a66160c99d65f5022ecb2 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 9 Oct 2015 22:06:44 -0700 Subject: soc: qcom: smd: Correct SMEM items for upper channels Update the SMEM items for the second set of SMD channels, as these where incorrect. Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index 88353bda1ea4..ef5fed6ceb21 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -87,8 +87,8 @@ static const struct { .fifo_base_id = 338 }, { - .alloc_tbl_id = 14, - .info_base_id = 266, + .alloc_tbl_id = 266, + .info_base_id = 138, .fifo_base_id = 202, }, }; -- cgit v1.2.3 From 6083096d961f3cf754c4da282179053f7b85ac16 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Mon, 12 Oct 2015 22:43:15 +0200 Subject: soc: qcom: smd: delete unneeded of_node_put Device node iterators perform an of_node_put on each iteration, so putting an of_node_put before a continue results in a double put. A simplified version of the semantic match that finds this problem is as follows (http://coccinelle.lip6.fr): // @@ expression root,e; local idexpression child; iterator i; @@ i(..., child, ...) { ... when != of_node_get(child) * of_node_put(child); ... * continue; } // Signed-off-by: Julia Lawall Signed-off-by: Andy Gross --- drivers/soc/qcom/smd.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index ef5fed6ceb21..86b598cff91a 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -921,10 +921,8 @@ static struct device_node *qcom_smd_match_channel(struct device_node *edge_node, for_each_available_child_of_node(edge_node, child) { key = "qcom,smd-channels"; ret = of_property_read_string(child, key, &name); - if (ret) { - of_node_put(child); + if (ret) continue; - } if (strcmp(name, channel) == 0) return child; -- cgit v1.2.3 From a8ddd1b998bdad9489876a3068cc45917bee5441 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Tue, 13 Oct 2015 13:57:43 -0700 Subject: soc: qcom: smd-rpm: Correct the active vs sleep state flagging The BIT() was incorrectly inherited from family A and should not be used on family B where the state is denoted by an enum. Reported-by: Georgi Djakov Signed-off-by: Bjorn Andersson Tested-by: Georgi Djakov Signed-off-by: Andy Gross --- drivers/soc/qcom/smd-rpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index d4cbaa3d4629..1ee02d2587b2 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -126,7 +126,7 @@ int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, pkt->hdr.length = cpu_to_le32(sizeof(struct qcom_rpm_request) + count); pkt->req.msg_id = cpu_to_le32(msg_id++); - pkt->req.flags = cpu_to_le32(BIT(state)); + pkt->req.flags = cpu_to_le32(state); pkt->req.type = cpu_to_le32(type); pkt->req.id = cpu_to_le32(id); pkt->req.data_len = cpu_to_le32(count); -- cgit v1.2.3 From d0bfd7c9b162612de55ca2d204403b90dc278db6 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Thu, 8 Oct 2015 13:34:09 -0500 Subject: soc: qcom: smem: Move RPM message ram out of smem DT node SMEM is a software construct built on top of a DDR reserved region and sometimes a device memory region called RPM message ram. Having the RPM message ram in the smem DT node's reg property leads to the smem node being located in different places depending on if the message ram is being used or not. Let's add a qcom specific property, qcom,rpm-msg-ram, and point to the device memory from the SMEM node via a phandle. As SMEM is a software construct, it really needs to reside at the root of the DT regardless of whether it's using the message ram or not. Cc: Bjorn Andersson Signed-off-by: Stephen Boyd Signed-off-by: Andy Gross --- arch/arm/boot/dts/qcom-msm8974.dtsi | 17 ++++++--- drivers/soc/qcom/smem.c | 73 +++++++++++++++---------------------- 2 files changed, 41 insertions(+), 49 deletions(-) (limited to 'drivers/soc') diff --git a/arch/arm/boot/dts/qcom-msm8974.dtsi b/arch/arm/boot/dts/qcom-msm8974.dtsi index ab8e57250468..93e315053bdd 100644 --- a/arch/arm/boot/dts/qcom-msm8974.dtsi +++ b/arch/arm/boot/dts/qcom-msm8974.dtsi @@ -100,6 +100,15 @@ clock-frequency = <19200000>; }; + smem { + compatible = "qcom,smem"; + + memory-region = <&smem_region>; + qcom,rpm-msg-ram = <&rpm_msg_ram>; + + hwlocks = <&tcsr_mutex 3>; + }; + soc: soc { #address-cells = <1>; #size-cells = <1>; @@ -250,13 +259,9 @@ #hwlock-cells = <1>; }; - smem@fa00000 { - compatible = "qcom,smem"; - - memory-region = <&smem_region>; + rpm_msg_ram: memory@fc428000 { + compatible = "qcom,rpm-msg-ram"; reg = <0xfc428000 0x4000>; - - hwlocks = <&tcsr_mutex 3>; }; blsp1_uart2: serial@f991e000 { diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c index 74017114ce6e..19019aa092e8 100644 --- a/drivers/soc/qcom/smem.c +++ b/drivers/soc/qcom/smem.c @@ -664,37 +664,47 @@ static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, return 0; } -static int qcom_smem_count_mem_regions(struct platform_device *pdev) +static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, + const char *name, int i) { - struct resource *res; - int num_regions = 0; - int i; - - for (i = 0; i < pdev->num_resources; i++) { - res = &pdev->resource[i]; + struct device_node *np; + struct resource r; + int ret; - if (resource_type(res) == IORESOURCE_MEM) - num_regions++; + np = of_parse_phandle(dev->of_node, name, 0); + if (!np) { + dev_err(dev, "No %s specified\n", name); + return -EINVAL; } - return num_regions; + ret = of_address_to_resource(np, 0, &r); + of_node_put(np); + if (ret) + return ret; + + smem->regions[i].aux_base = (u32)r.start; + smem->regions[i].size = resource_size(&r); + smem->regions[i].virt_base = devm_ioremap_nocache(dev, r.start, + resource_size(&r)); + if (!smem->regions[i].virt_base) + return -ENOMEM; + + return 0; } static int qcom_smem_probe(struct platform_device *pdev) { struct smem_header *header; - struct device_node *np; struct qcom_smem *smem; - struct resource *res; - struct resource r; size_t array_size; - int num_regions = 0; + int num_regions; int hwlock_id; u32 version; int ret; - int i; - num_regions = qcom_smem_count_mem_regions(pdev) + 1; + num_regions = 1; + if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL)) + num_regions++; array_size = num_regions * sizeof(struct smem_region); smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); @@ -704,36 +714,13 @@ static int qcom_smem_probe(struct platform_device *pdev) smem->dev = &pdev->dev; smem->num_regions = num_regions; - np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); - if (!np) { - dev_err(&pdev->dev, "No memory-region specified\n"); - return -EINVAL; - } - - ret = of_address_to_resource(np, 0, &r); - of_node_put(np); + ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); if (ret) return ret; - smem->regions[0].aux_base = (u32)r.start; - smem->regions[0].size = resource_size(&r); - smem->regions[0].virt_base = devm_ioremap_nocache(&pdev->dev, - r.start, - resource_size(&r)); - if (!smem->regions[0].virt_base) - return -ENOMEM; - - for (i = 1; i < num_regions; i++) { - res = platform_get_resource(pdev, IORESOURCE_MEM, i - 1); - - smem->regions[i].aux_base = (u32)res->start; - smem->regions[i].size = resource_size(res); - smem->regions[i].virt_base = devm_ioremap_nocache(&pdev->dev, - res->start, - resource_size(res)); - if (!smem->regions[i].virt_base) - return -ENOMEM; - } + if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev, + "qcom,rpm-msg-ram", 1))) + return ret; header = smem->regions[0].virt_base; if (le32_to_cpu(header->initialized) != 1 || -- cgit v1.2.3 From 73ebb85444b0472d90bb70a1a9e6b5df3f92c14c Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 13 Oct 2015 17:05:39 +0200 Subject: soc: qcom/smem: add HWSPINLOCK dependency This fixes a build error when smem is enabled without hwspinlock: drivers/built-in.o: In function `qcom_smem_alloc': rockchip-efuse.c:(.text+0x7a3e4): undefined reference to `__hwspin_lock_timeout' rockchip-efuse.c:(.text+0x7a568): undefined reference to `__hwspin_unlock' drivers/built-in.o: In function `qcom_smem_remove': rockchip-efuse.c:(.text+0x7a5cc): undefined reference to `hwspin_lock_free' drivers/built-in.o: In function `qcom_smem_probe': rockchip-efuse.c:(.text+0x7a960): undefined reference to `hwspin_lock_request_specific' rockchip-efuse.c:(.text+0x7a988): undefined reference to `of_hwspin_lock_get_id' drivers/built-in.o: In function `qcom_smem_get': rockchip-efuse.c:(.text+0x7aa24): undefined reference to `__hwspin_lock_timeout' rockchip-efuse.c:(.text+0x7aafc): undefined reference to `__hwspin_unlock' Signed-off-by: Arnd Bergmann --- drivers/soc/qcom/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 3e4d2133c3d2..eec76141d9b9 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -22,6 +22,7 @@ config QCOM_PM config QCOM_SMEM tristate "Qualcomm Shared Memory Manager (SMEM)" depends on ARCH_QCOM + depends on HWSPINLOCK help Say y here to enable support for the Qualcomm Shared Memory Manager. The driver provides an interface to items in a heap shared among all -- cgit v1.2.3 From c3ce5b3768b17120dca852513af211c4acd3d2b0 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 15 Oct 2015 14:35:01 +0200 Subject: soc: rockchip: power-domain: don't try to print the clock name in error case When we never got the the clock-reference, i.e. when IS_ERR(clk) is true, don't try to print the clock name via %pC as this of course produces a null-pointer-dereference in __clk_get_name(). Signed-off-by: Heiko Stuebner Reviewed-by: Caesar Wang --- drivers/soc/rockchip/pm_domains.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/soc') diff --git a/drivers/soc/rockchip/pm_domains.c b/drivers/soc/rockchip/pm_domains.c index 8268d5d2b852..534c58937a56 100644 --- a/drivers/soc/rockchip/pm_domains.c +++ b/drivers/soc/rockchip/pm_domains.c @@ -265,8 +265,8 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu, if (IS_ERR(clk)) { error = PTR_ERR(clk); dev_err(pmu->dev, - "%s: failed to get clk %pC (index %d): %d\n", - node->name, clk, i, error); + "%s: failed to get clk at index %d: %d\n", + node->name, i, error); goto err_out; } -- cgit v1.2.3 From d13a5c8c4c3dbe299659bcff805f79a2c83e2bbc Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 29 Oct 2015 15:09:54 -0700 Subject: soc: qcom: smd-rpm: Correct size of outgoing message With the removal of VLAIS the size was incorrectly changed to only cover the headers of the packet, resulting in "empty" requests being sent to the RPM. Correct this so the entire message is transfered. Fixes: 50e1b29b4438 ("soc: qcom: smd: Remove use of VLAIS") Signed-off-by: Bjorn Andersson Reviewed-by: Stephen Boyd Signed-off-by: Andy Gross Signed-off-by: Olof Johansson --- drivers/soc/qcom/smd-rpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/soc') diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index 1ee02d2587b2..2969321e1b09 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -132,7 +132,7 @@ int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, pkt->req.data_len = cpu_to_le32(count); memcpy(pkt->payload, buf, count); - ret = qcom_smd_send(rpm->rpm_channel, pkt, sizeof(*pkt)); + ret = qcom_smd_send(rpm->rpm_channel, pkt, size); if (ret) goto out; -- cgit v1.2.3