diff options
Diffstat (limited to 'drivers')
34 files changed, 3751 insertions, 369 deletions
diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index fd0973b922a7..60ee5591ee8f 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -93,7 +93,7 @@ static int __pm_clk_add(struct device *dev, const char *con_id, return -ENOMEM; } } else { - if (IS_ERR(clk) || !__clk_get(clk)) { + if (IS_ERR(clk)) { kfree(ce); return -ENOENT; } @@ -127,7 +127,9 @@ int pm_clk_add(struct device *dev, const char *con_id) * @clk: Clock pointer * * Add the clock to the list of clocks used for the power management of @dev. - * It will increment refcount on clock pointer, use clk_put() on it when done. + * The power-management code will take control of the clock reference, so + * callers should not call clk_put() on @clk after this function sucessfully + * returned. */ int pm_clk_add_clk(struct device *dev, struct clk *clk) { diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index 0ebca8ba7bc4..116b363b7987 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -120,6 +120,17 @@ config SIMPLE_PM_BUS Controller (BSC, sometimes called "LBSC within Bus Bridge", or "External Bus Interface") as found on several Renesas ARM SoCs. +config SUNXI_RSB + tristate "Allwinner sunXi Reduced Serial Bus Driver" + default MACH_SUN8I || MACH_SUN9I + depends on ARCH_SUNXI + select REGMAP + help + Say y here to enable support for Allwinner's Reduced Serial Bus + (RSB) support. This controller is responsible for communicating + with various RSB based devices, such as AXP223, AXP8XX PMICs, + and AC100/AC200 ICs. + config VEXPRESS_CONFIG bool "Versatile Express configuration bus" default y if ARCH_VEXPRESS diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 790e7b933fb2..fcb9f9794a1f 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -15,5 +15,6 @@ obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o obj-$(CONFIG_OMAP_INTERCONNECT) += omap_l3_smx.o omap_l3_noc.o obj-$(CONFIG_OMAP_OCP2SCP) += omap-ocp2scp.o +obj-$(CONFIG_SUNXI_RSB) += sunxi-rsb.o obj-$(CONFIG_SIMPLE_PM_BUS) += simple-pm-bus.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o diff --git a/drivers/bus/sunxi-rsb.c b/drivers/bus/sunxi-rsb.c new file mode 100644 index 000000000000..846bc29c157d --- /dev/null +++ b/drivers/bus/sunxi-rsb.c @@ -0,0 +1,783 @@ +/* + * RSB (Reduced Serial Bus) driver. + * + * Author: Chen-Yu Tsai <wens@csie.org> + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * The RSB controller looks like an SMBus controller which only supports + * byte and word data transfers. But, it differs from standard SMBus + * protocol on several aspects: + * - it uses addresses set at runtime to address slaves. Runtime addresses + * are sent to slaves using their 12bit hardware addresses. Up to 15 + * runtime addresses are available. + * - it adds a parity bit every 8bits of data and address for read and + * write accesses; this replaces the ack bit + * - only one read access is required to read a byte (instead of a write + * followed by a read access in standard SMBus protocol) + * - there's no Ack bit after each read access + * + * This means this bus cannot be used to interface with standard SMBus + * devices. Devices known to support this interface include the AXP223, + * AXP809, and AXP806 PMICs, and the AC100 audio codec, all from X-Powers. + * + * A description of the operation and wire protocol can be found in the + * RSB section of Allwinner's A80 user manual, which can be found at + * + * https://github.com/allwinner-zh/documents/tree/master/A80 + * + * This document is officially released by Allwinner. + * + * This driver is based on i2c-sun6i-p2wi.c, the P2WI bus driver. + * + */ + +#include <linux/clk.h> +#include <linux/clk/clk-conf.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/sunxi-rsb.h> +#include <linux/types.h> + +/* RSB registers */ +#define RSB_CTRL 0x0 /* Global control */ +#define RSB_CCR 0x4 /* Clock control */ +#define RSB_INTE 0x8 /* Interrupt controls */ +#define RSB_INTS 0xc /* Interrupt status */ +#define RSB_ADDR 0x10 /* Address to send with read/write command */ +#define RSB_DATA 0x1c /* Data to read/write */ +#define RSB_LCR 0x24 /* Line control */ +#define RSB_DMCR 0x28 /* Device mode (init) control */ +#define RSB_CMD 0x2c /* RSB Command */ +#define RSB_DAR 0x30 /* Device address / runtime address */ + +/* CTRL fields */ +#define RSB_CTRL_START_TRANS BIT(7) +#define RSB_CTRL_ABORT_TRANS BIT(6) +#define RSB_CTRL_GLOBAL_INT_ENB BIT(1) +#define RSB_CTRL_SOFT_RST BIT(0) + +/* CLK CTRL fields */ +#define RSB_CCR_SDA_OUT_DELAY(v) (((v) & 0x7) << 8) +#define RSB_CCR_MAX_CLK_DIV 0xff +#define RSB_CCR_CLK_DIV(v) ((v) & RSB_CCR_MAX_CLK_DIV) + +/* STATUS fields */ +#define RSB_INTS_TRANS_ERR_ACK BIT(16) +#define RSB_INTS_TRANS_ERR_DATA_BIT(v) (((v) >> 8) & 0xf) +#define RSB_INTS_TRANS_ERR_DATA GENMASK(11, 8) +#define RSB_INTS_LOAD_BSY BIT(2) +#define RSB_INTS_TRANS_ERR BIT(1) +#define RSB_INTS_TRANS_OVER BIT(0) + +/* LINE CTRL fields*/ +#define RSB_LCR_SCL_STATE BIT(5) +#define RSB_LCR_SDA_STATE BIT(4) +#define RSB_LCR_SCL_CTL BIT(3) +#define RSB_LCR_SCL_CTL_EN BIT(2) +#define RSB_LCR_SDA_CTL BIT(1) +#define RSB_LCR_SDA_CTL_EN BIT(0) + +/* DEVICE MODE CTRL field values */ +#define RSB_DMCR_DEVICE_START BIT(31) +#define RSB_DMCR_MODE_DATA (0x7c << 16) +#define RSB_DMCR_MODE_REG (0x3e << 8) +#define RSB_DMCR_DEV_ADDR 0x00 + +/* CMD values */ +#define RSB_CMD_RD8 0x8b +#define RSB_CMD_RD16 0x9c +#define RSB_CMD_RD32 0xa6 +#define RSB_CMD_WR8 0x4e +#define RSB_CMD_WR16 0x59 +#define RSB_CMD_WR32 0x63 +#define RSB_CMD_STRA 0xe8 + +/* DAR fields */ +#define RSB_DAR_RTA(v) (((v) & 0xff) << 16) +#define RSB_DAR_DA(v) ((v) & 0xffff) + +#define RSB_MAX_FREQ 20000000 + +#define RSB_CTRL_NAME "sunxi-rsb" + +struct sunxi_rsb_addr_map { + u16 hwaddr; + u8 rtaddr; +}; + +struct sunxi_rsb { + struct device *dev; + void __iomem *regs; + struct clk *clk; + struct reset_control *rstc; + struct completion complete; + struct mutex lock; + unsigned int status; +}; + +/* bus / slave device related functions */ +static struct bus_type sunxi_rsb_bus; + +static int sunxi_rsb_device_match(struct device *dev, struct device_driver *drv) +{ + return of_driver_match_device(dev, drv); +} + +static int sunxi_rsb_device_probe(struct device *dev) +{ + const struct sunxi_rsb_driver *drv = to_sunxi_rsb_driver(dev->driver); + struct sunxi_rsb_device *rdev = to_sunxi_rsb_device(dev); + int ret; + + if (!drv->probe) + return -ENODEV; + + if (!rdev->irq) { + int irq = -ENOENT; + + if (dev->of_node) + irq = of_irq_get(dev->of_node, 0); + + if (irq == -EPROBE_DEFER) + return irq; + if (irq < 0) + irq = 0; + + rdev->irq = irq; + } + + ret = of_clk_set_defaults(dev->of_node, false); + if (ret < 0) + return ret; + + return drv->probe(rdev); +} + +static int sunxi_rsb_device_remove(struct device *dev) +{ + const struct sunxi_rsb_driver *drv = to_sunxi_rsb_driver(dev->driver); + + return drv->remove(to_sunxi_rsb_device(dev)); +} + +static struct bus_type sunxi_rsb_bus = { + .name = RSB_CTRL_NAME, + .match = sunxi_rsb_device_match, + .probe = sunxi_rsb_device_probe, + .remove = sunxi_rsb_device_remove, +}; + +static void sunxi_rsb_dev_release(struct device *dev) +{ + struct sunxi_rsb_device *rdev = to_sunxi_rsb_device(dev); + + kfree(rdev); +} + +/** + * sunxi_rsb_device_create() - allocate and add an RSB device + * @rsb: RSB controller + * @node: RSB slave device node + * @hwaddr: RSB slave hardware address + * @rtaddr: RSB slave runtime address + */ +static struct sunxi_rsb_device *sunxi_rsb_device_create(struct sunxi_rsb *rsb, + struct device_node *node, u16 hwaddr, u8 rtaddr) +{ + int err; + struct sunxi_rsb_device *rdev; + + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); + if (!rdev) + return ERR_PTR(-ENOMEM); + + rdev->rsb = rsb; + rdev->hwaddr = hwaddr; + rdev->rtaddr = rtaddr; + rdev->dev.bus = &sunxi_rsb_bus; + rdev->dev.parent = rsb->dev; + rdev->dev.of_node = node; + rdev->dev.release = sunxi_rsb_dev_release; + + dev_set_name(&rdev->dev, "%s-%x", RSB_CTRL_NAME, hwaddr); + + err = device_register(&rdev->dev); + if (err < 0) { + dev_err(&rdev->dev, "Can't add %s, status %d\n", + dev_name(&rdev->dev), err); + goto err_device_add; + } + + dev_dbg(&rdev->dev, "device %s registered\n", dev_name(&rdev->dev)); + +err_device_add: + put_device(&rdev->dev); + + return ERR_PTR(err); +} + +/** + * sunxi_rsb_device_unregister(): unregister an RSB device + * @rdev: rsb_device to be removed + */ +static void sunxi_rsb_device_unregister(struct sunxi_rsb_device *rdev) +{ + device_unregister(&rdev->dev); +} + +static int sunxi_rsb_remove_devices(struct device *dev, void *data) +{ + struct sunxi_rsb_device *rdev = to_sunxi_rsb_device(dev); + + if (dev->bus == &sunxi_rsb_bus) + sunxi_rsb_device_unregister(rdev); + + return 0; +} + +/** + * sunxi_rsb_driver_register() - Register device driver with RSB core + * @rdrv: device driver to be associated with slave-device. + * + * This API will register the client driver with the RSB framework. + * It is typically called from the driver's module-init function. + */ +int sunxi_rsb_driver_register(struct sunxi_rsb_driver *rdrv) +{ + rdrv->driver.bus = &sunxi_rsb_bus; + return driver_register(&rdrv->driver); +} +EXPORT_SYMBOL_GPL(sunxi_rsb_driver_register); + +/* common code that starts a transfer */ +static int _sunxi_rsb_run_xfer(struct sunxi_rsb *rsb) +{ + if (readl(rsb->regs + RSB_CTRL) & RSB_CTRL_START_TRANS) { + dev_dbg(rsb->dev, "RSB transfer still in progress\n"); + return -EBUSY; + } + + reinit_completion(&rsb->complete); + + writel(RSB_INTS_LOAD_BSY | RSB_INTS_TRANS_ERR | RSB_INTS_TRANS_OVER, + rsb->regs + RSB_INTE); + writel(RSB_CTRL_START_TRANS | RSB_CTRL_GLOBAL_INT_ENB, + rsb->regs + RSB_CTRL); + + if (!wait_for_completion_io_timeout(&rsb->complete, + msecs_to_jiffies(100))) { + dev_dbg(rsb->dev, "RSB timeout\n"); + + /* abort the transfer */ + writel(RSB_CTRL_ABORT_TRANS, rsb->regs + RSB_CTRL); + + /* clear any interrupt flags */ + writel(readl(rsb->regs + RSB_INTS), rsb->regs + RSB_INTS); + + return -ETIMEDOUT; + } + + if (rsb->status & RSB_INTS_LOAD_BSY) { + dev_dbg(rsb->dev, "RSB busy\n"); + return -EBUSY; + } + + if (rsb->status & RSB_INTS_TRANS_ERR) { + if (rsb->status & RSB_INTS_TRANS_ERR_ACK) { + dev_dbg(rsb->dev, "RSB slave nack\n"); + return -EINVAL; + } + + if (rsb->status & RSB_INTS_TRANS_ERR_DATA) { + dev_dbg(rsb->dev, "RSB transfer data error\n"); + return -EIO; + } + } + + return 0; +} + +static int sunxi_rsb_read(struct sunxi_rsb *rsb, u8 rtaddr, u8 addr, + u32 *buf, size_t len) +{ + u32 cmd; + int ret; + + if (!buf) + return -EINVAL; + + switch (len) { + case 1: + cmd = RSB_CMD_RD8; + break; + case 2: + cmd = RSB_CMD_RD16; + break; + case 4: + cmd = RSB_CMD_RD32; + break; + default: + dev_err(rsb->dev, "Invalid access width: %d\n", len); + return -EINVAL; + } + + mutex_lock(&rsb->lock); + + writel(addr, rsb->regs + RSB_ADDR); + writel(RSB_DAR_RTA(rtaddr), rsb->regs + RSB_DAR); + writel(cmd, rsb->regs + RSB_CMD); + + ret = _sunxi_rsb_run_xfer(rsb); + if (ret) + goto out; + + *buf = readl(rsb->regs + RSB_DATA); + + mutex_unlock(&rsb->lock); + +out: + return ret; +} + +static int sunxi_rsb_write(struct sunxi_rsb *rsb, u8 rtaddr, u8 addr, + const u32 *buf, size_t len) +{ + u32 cmd; + int ret; + + if (!buf) + return -EINVAL; + + switch (len) { + case 1: + cmd = RSB_CMD_WR8; + break; + case 2: + cmd = RSB_CMD_WR16; + break; + case 4: + cmd = RSB_CMD_WR32; + break; + default: + dev_err(rsb->dev, "Invalid access width: %d\n", len); + return -EINVAL; + } + + mutex_lock(&rsb->lock); + + writel(addr, rsb->regs + RSB_ADDR); + writel(RSB_DAR_RTA(rtaddr), rsb->regs + RSB_DAR); + writel(*buf, rsb->regs + RSB_DATA); + writel(cmd, rsb->regs + RSB_CMD); + ret = _sunxi_rsb_run_xfer(rsb); + + mutex_unlock(&rsb->lock); + + return ret; +} + +/* RSB regmap functions */ +struct sunxi_rsb_ctx { + struct sunxi_rsb_device *rdev; + int size; +}; + +static int regmap_sunxi_rsb_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct sunxi_rsb_ctx *ctx = context; + struct sunxi_rsb_device *rdev = ctx->rdev; + + if (reg > 0xff) + return -EINVAL; + + return sunxi_rsb_read(rdev->rsb, rdev->rtaddr, reg, val, ctx->size); +} + +static int regmap_sunxi_rsb_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct sunxi_rsb_ctx *ctx = context; + struct sunxi_rsb_device *rdev = ctx->rdev; + + return sunxi_rsb_write(rdev->rsb, rdev->rtaddr, reg, &val, ctx->size); +} + +static void regmap_sunxi_rsb_free_ctx(void *context) +{ + struct sunxi_rsb_ctx *ctx = context; + + kfree(ctx); +} + +static struct regmap_bus regmap_sunxi_rsb = { + .reg_write = regmap_sunxi_rsb_reg_write, + .reg_read = regmap_sunxi_rsb_reg_read, + .free_context = regmap_sunxi_rsb_free_ctx, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static struct sunxi_rsb_ctx *regmap_sunxi_rsb_init_ctx(struct sunxi_rsb_device *rdev, + const struct regmap_config *config) +{ + struct sunxi_rsb_ctx *ctx; + + switch (config->val_bits) { + case 8: + case 16: + case 32: + break; + default: + return ERR_PTR(-EINVAL); + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + ctx->rdev = rdev; + ctx->size = config->val_bits / 8; + + return ctx; +} + +struct regmap *__devm_regmap_init_sunxi_rsb(struct sunxi_rsb_device *rdev, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + struct sunxi_rsb_ctx *ctx = regmap_sunxi_rsb_init_ctx(rdev, config); + + if (IS_ERR(ctx)) + return ERR_CAST(ctx); + + return __devm_regmap_init(&rdev->dev, ®map_sunxi_rsb, ctx, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__devm_regmap_init_sunxi_rsb); + +/* RSB controller driver functions */ +static irqreturn_t sunxi_rsb_irq(int irq, void *dev_id) +{ + struct sunxi_rsb *rsb = dev_id; + u32 status; + + status = readl(rsb->regs + RSB_INTS); + rsb->status = status; + + /* Clear interrupts */ + status &= (RSB_INTS_LOAD_BSY | RSB_INTS_TRANS_ERR | + RSB_INTS_TRANS_OVER); + writel(status, rsb->regs + RSB_INTS); + + complete(&rsb->complete); + + return IRQ_HANDLED; +} + +static int sunxi_rsb_init_device_mode(struct sunxi_rsb *rsb) +{ + int ret = 0; + u32 reg; + + /* send init sequence */ + writel(RSB_DMCR_DEVICE_START | RSB_DMCR_MODE_DATA | + RSB_DMCR_MODE_REG | RSB_DMCR_DEV_ADDR, rsb->regs + RSB_DMCR); + + readl_poll_timeout(rsb->regs + RSB_DMCR, reg, + !(reg & RSB_DMCR_DEVICE_START), 100, 250000); + if (reg & RSB_DMCR_DEVICE_START) + ret = -ETIMEDOUT; + + /* clear interrupt status bits */ + writel(readl(rsb->regs + RSB_INTS), rsb->regs + RSB_INTS); + + return ret; +} + +/* + * There are 15 valid runtime addresses, though Allwinner typically + * skips the first, for unknown reasons, and uses the following three. + * + * 0x17, 0x2d, 0x3a, 0x4e, 0x59, 0x63, 0x74, 0x8b, + * 0x9c, 0xa6, 0xb1, 0xc5, 0xd2, 0xe8, 0xff + * + * No designs with 2 RSB slave devices sharing identical hardware + * addresses on the same bus have been seen in the wild. All designs + * use 0x2d for the primary PMIC, 0x3a for the secondary PMIC if + * there is one, and 0x45 for peripheral ICs. + * + * The hardware does not seem to support re-setting runtime addresses. + * Attempts to do so result in the slave devices returning a NACK. + * Hence we just hardcode the mapping here, like Allwinner does. + */ + +static const struct sunxi_rsb_addr_map sunxi_rsb_addr_maps[] = { + { 0x3e3, 0x2d }, /* Primary PMIC: AXP223, AXP809, AXP81X, ... */ + { 0x745, 0x3a }, /* Secondary PMIC: AXP806, ... */ + { 0xe89, 0x45 }, /* Peripheral IC: AC100, ... */ +}; + +static u8 sunxi_rsb_get_rtaddr(u16 hwaddr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sunxi_rsb_addr_maps); i++) + if (hwaddr == sunxi_rsb_addr_maps[i].hwaddr) + return sunxi_rsb_addr_maps[i].rtaddr; + + return 0; /* 0 is an invalid runtime address */ +} + +static int of_rsb_register_devices(struct sunxi_rsb *rsb) +{ + struct device *dev = rsb->dev; + struct device_node *child, *np = dev->of_node; + u32 hwaddr; + u8 rtaddr; + int ret; + + if (!np) + return -EINVAL; + + /* Runtime addresses for all slaves should be set first */ + for_each_available_child_of_node(np, child) { + dev_dbg(dev, "setting child %s runtime address\n", + child->full_name); + + ret = of_property_read_u32(child, "reg", &hwaddr); + if (ret) { + dev_err(dev, "%s: invalid 'reg' property: %d\n", + child->full_name, ret); + continue; + } + + rtaddr = sunxi_rsb_get_rtaddr(hwaddr); + if (!rtaddr) { + dev_err(dev, "%s: unknown hardware device address\n", + child->full_name); + continue; + } + + /* + * Since no devices have been registered yet, we are the + * only ones using the bus, we can skip locking the bus. + */ + + /* setup command parameters */ + writel(RSB_CMD_STRA, rsb->regs + RSB_CMD); + writel(RSB_DAR_RTA(rtaddr) | RSB_DAR_DA(hwaddr), + rsb->regs + RSB_DAR); + + /* send command */ + ret = _sunxi_rsb_run_xfer(rsb); + if (ret) + dev_warn(dev, "%s: set runtime address failed: %d\n", + child->full_name, ret); + } + + /* Then we start adding devices and probing them */ + for_each_available_child_of_node(np, child) { + struct sunxi_rsb_device *rdev; + + dev_dbg(dev, "adding child %s\n", child->full_name); + + ret = of_property_read_u32(child, "reg", &hwaddr); + if (ret) + continue; + + rtaddr = sunxi_rsb_get_rtaddr(hwaddr); + if (!rtaddr) + continue; + + rdev = sunxi_rsb_device_create(rsb, child, hwaddr, rtaddr); + if (IS_ERR(rdev)) + dev_err(dev, "failed to add child device %s: %ld\n", + child->full_name, PTR_ERR(rdev)); + } + + return 0; +} + +static const struct of_device_id sunxi_rsb_of_match_table[] = { + { .compatible = "allwinner,sun8i-a23-rsb" }, + {} +}; +MODULE_DEVICE_TABLE(of, sunxi_rsb_of_match_table); + +static int sunxi_rsb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct resource *r; + struct sunxi_rsb *rsb; + unsigned long p_clk_freq; + u32 clk_delay, clk_freq = 3000000; + int clk_div, irq, ret; + u32 reg; + + of_property_read_u32(np, "clock-frequency", &clk_freq); + if (clk_freq > RSB_MAX_FREQ) { + dev_err(dev, + "clock-frequency (%u Hz) is too high (max = 20MHz)\n", + clk_freq); + return -EINVAL; + } + + rsb = devm_kzalloc(dev, sizeof(*rsb), GFP_KERNEL); + if (!rsb) + return -ENOMEM; + + rsb->dev = dev; + platform_set_drvdata(pdev, rsb); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rsb->regs = devm_ioremap_resource(dev, r); + if (IS_ERR(rsb->regs)) + return PTR_ERR(rsb->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "failed to retrieve irq: %d\n", irq); + return irq; + } + + rsb->clk = devm_clk_get(dev, NULL); + if (IS_ERR(rsb->clk)) { + ret = PTR_ERR(rsb->clk); + dev_err(dev, "failed to retrieve clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(rsb->clk); + if (ret) { + dev_err(dev, "failed to enable clk: %d\n", ret); + return ret; + } + + p_clk_freq = clk_get_rate(rsb->clk); + + rsb->rstc = devm_reset_control_get(dev, NULL); + if (IS_ERR(rsb->rstc)) { + ret = PTR_ERR(rsb->rstc); + dev_err(dev, "failed to retrieve reset controller: %d\n", ret); + goto err_clk_disable; + } + + ret = reset_control_deassert(rsb->rstc); + if (ret) { + dev_err(dev, "failed to deassert reset line: %d\n", ret); + goto err_clk_disable; + } + + init_completion(&rsb->complete); + mutex_init(&rsb->lock); + + /* reset the controller */ + writel(RSB_CTRL_SOFT_RST, rsb->regs + RSB_CTRL); + readl_poll_timeout(rsb->regs + RSB_CTRL, reg, + !(reg & RSB_CTRL_SOFT_RST), 1000, 100000); + + /* + * Clock frequency and delay calculation code is from + * Allwinner U-boot sources. + * + * From A83 user manual: + * bus clock frequency = parent clock frequency / (2 * (divider + 1)) + */ + clk_div = p_clk_freq / clk_freq / 2; + if (!clk_div) + clk_div = 1; + else if (clk_div > RSB_CCR_MAX_CLK_DIV + 1) + clk_div = RSB_CCR_MAX_CLK_DIV + 1; + + clk_delay = clk_div >> 1; + if (!clk_delay) + clk_delay = 1; + + dev_info(dev, "RSB running at %lu Hz\n", p_clk_freq / clk_div / 2); + writel(RSB_CCR_SDA_OUT_DELAY(clk_delay) | RSB_CCR_CLK_DIV(clk_div - 1), + rsb->regs + RSB_CCR); + + ret = devm_request_irq(dev, irq, sunxi_rsb_irq, 0, RSB_CTRL_NAME, rsb); + if (ret) { + dev_err(dev, "can't register interrupt handler irq %d: %d\n", + irq, ret); + goto err_reset_assert; + } + + /* initialize all devices on the bus into RSB mode */ + ret = sunxi_rsb_init_device_mode(rsb); + if (ret) + dev_warn(dev, "Initialize device mode failed: %d\n", ret); + + of_rsb_register_devices(rsb); + + return 0; + +err_reset_assert: + reset_control_assert(rsb->rstc); + +err_clk_disable: + clk_disable_unprepare(rsb->clk); + + return ret; +} + +static int sunxi_rsb_remove(struct platform_device *pdev) +{ + struct sunxi_rsb *rsb = platform_get_drvdata(pdev); + + device_for_each_child(rsb->dev, NULL, sunxi_rsb_remove_devices); + reset_control_assert(rsb->rstc); + clk_disable_unprepare(rsb->clk); + + return 0; +} + +static struct platform_driver sunxi_rsb_driver = { + .probe = sunxi_rsb_probe, + .remove = sunxi_rsb_remove, + .driver = { + .name = RSB_CTRL_NAME, + .of_match_table = sunxi_rsb_of_match_table, + }, +}; + +static int __init sunxi_rsb_init(void) +{ + int ret; + + ret = bus_register(&sunxi_rsb_bus); + if (ret) { + pr_err("failed to register sunxi sunxi_rsb bus: %d\n", ret); + return ret; + } + + return platform_driver_register(&sunxi_rsb_driver); +} +module_init(sunxi_rsb_init); + +static void __exit sunxi_rsb_exit(void) +{ + platform_driver_unregister(&sunxi_rsb_driver); + bus_unregister(&sunxi_rsb_bus); +} +module_exit(sunxi_rsb_exit); + +MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); +MODULE_DESCRIPTION("Allwinner sunXi Reduced Serial Bus controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 7a1ab24052b8..c3e3a02f7f1f 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -60,6 +60,16 @@ config COMMON_CLK_RK808 clocked at 32KHz each. Clkout1 is always on, Clkout2 can off by control register. +config COMMON_CLK_SCPI + tristate "Clock driver controlled via SCPI interface" + depends on ARM_SCPI_PROTOCOL || COMPILE_TEST + ---help--- + This driver provides support for clocks that are controlled + by firmware that implements the SCPI interface. + + This driver uses SCPI Message Protocol to interact with the + firmware providing all the clock controls. + config COMMON_CLK_SI5351 tristate "Clock driver for SiLabs 5351A/B/C" depends on I2C diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d3e1910eebab..820714c72d36 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_COMMON_CLK_PALMAS) += clk-palmas.o obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o +obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o diff --git a/drivers/clk/berlin/bg2q.c b/drivers/clk/berlin/bg2q.c index 243f421abcb4..f144547cf76c 100644 --- a/drivers/clk/berlin/bg2q.c +++ b/drivers/clk/berlin/bg2q.c @@ -45,7 +45,7 @@ #define REG_SDIO0XIN_CLKCTL 0x0158 #define REG_SDIO1XIN_CLKCTL 0x015c -#define MAX_CLKS 27 +#define MAX_CLKS 28 static struct clk *clks[MAX_CLKS]; static struct clk_onecell_data clk_data; static DEFINE_SPINLOCK(lock); @@ -356,13 +356,13 @@ static void __init berlin2q_clock_setup(struct device_node *np) gd->bit_idx, 0, &lock); } - /* - * twdclk is derived from cpu/3 - * TODO: use cpupll until cpuclk is not available - */ + /* cpuclk divider is fixed to 1 */ + clks[CLKID_CPU] = + clk_register_fixed_factor(NULL, "cpu", clk_names[CPUPLL], + 0, 1, 1); + /* twdclk is derived from cpu/3 */ clks[CLKID_TWD] = - clk_register_fixed_factor(NULL, "twd", clk_names[CPUPLL], - 0, 1, 3); + clk_register_fixed_factor(NULL, "twd", "cpu", 0, 1, 3); /* check for errors on leaf clocks */ for (n = 0; n < MAX_CLKS; n++) { diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c new file mode 100644 index 000000000000..0b501a9fef92 --- /dev/null +++ b/drivers/clk/clk-scpi.c @@ -0,0 +1,325 @@ +/* + * System Control and Power Interface (SCPI) Protocol based clock driver + * + * Copyright (C) 2015 ARM Ltd. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk-provider.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/scpi_protocol.h> + +struct scpi_clk { + u32 id; + struct clk_hw hw; + struct scpi_dvfs_info *info; + struct scpi_ops *scpi_ops; +}; + +#define to_scpi_clk(clk) container_of(clk, struct scpi_clk, hw) + +static struct platform_device *cpufreq_dev; + +static unsigned long scpi_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct scpi_clk *clk = to_scpi_clk(hw); + + return clk->scpi_ops->clk_get_val(clk->id); +} + +static long scpi_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + /* + * We can't figure out what rate it will be, so just return the + * rate back to the caller. scpi_clk_recalc_rate() will be called + * after the rate is set and we'll know what rate the clock is + * running at then. + */ + return rate; +} + +static int scpi_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct scpi_clk *clk = to_scpi_clk(hw); + + return clk->scpi_ops->clk_set_val(clk->id, rate); +} + +static const struct clk_ops scpi_clk_ops = { + .recalc_rate = scpi_clk_recalc_rate, + .round_rate = scpi_clk_round_rate, + .set_rate = scpi_clk_set_rate, +}; + +/* find closest match to given frequency in OPP table */ +static int __scpi_dvfs_round_rate(struct scpi_clk *clk, unsigned long rate) +{ + int idx; + u32 fmin = 0, fmax = ~0, ftmp; + const struct scpi_opp *opp = clk->info->opps; + + for (idx = 0; idx < clk->info->count; idx++, opp++) { + ftmp = opp->freq; + if (ftmp >= (u32)rate) { + if (ftmp <= fmax) + fmax = ftmp; + break; + } else if (ftmp >= fmin) { + fmin = ftmp; + } + } + return fmax != ~0 ? fmax : fmin; +} + +static unsigned long scpi_dvfs_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct scpi_clk *clk = to_scpi_clk(hw); + int idx = clk->scpi_ops->dvfs_get_idx(clk->id); + const struct scpi_opp *opp; + + if (idx < 0) + return 0; + + opp = clk->info->opps + idx; + return opp->freq; +} + +static long scpi_dvfs_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct scpi_clk *clk = to_scpi_clk(hw); + + return __scpi_dvfs_round_rate(clk, rate); +} + +static int __scpi_find_dvfs_index(struct scpi_clk *clk, unsigned long rate) +{ + int idx, max_opp = clk->info->count; + const struct scpi_opp *opp = clk->info->opps; + + for (idx = 0; idx < max_opp; idx++, opp++) + if (opp->freq == rate) + return idx; + return -EINVAL; +} + +static int scpi_dvfs_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct scpi_clk *clk = to_scpi_clk(hw); + int ret = __scpi_find_dvfs_index(clk, rate); + + if (ret < 0) + return ret; + return clk->scpi_ops->dvfs_set_idx(clk->id, (u8)ret); +} + +static const struct clk_ops scpi_dvfs_ops = { + .recalc_rate = scpi_dvfs_recalc_rate, + .round_rate = scpi_dvfs_round_rate, + .set_rate = scpi_dvfs_set_rate, +}; + +static const struct of_device_id scpi_clk_match[] = { + { .compatible = "arm,scpi-dvfs-clocks", .data = &scpi_dvfs_ops, }, + { .compatible = "arm,scpi-variable-clocks", .data = &scpi_clk_ops, }, + {} +}; + +static struct clk * +scpi_clk_ops_init(struct device *dev, const struct of_device_id *match, + struct scpi_clk *sclk, const char *name) +{ + struct clk_init_data init; + struct clk *clk; + unsigned long min = 0, max = 0; + + init.name = name; + init.flags = CLK_IS_ROOT; + init.num_parents = 0; + init.ops = match->data; + sclk->hw.init = &init; + sclk->scpi_ops = get_scpi_ops(); + + if (init.ops == &scpi_dvfs_ops) { + sclk->info = sclk->scpi_ops->dvfs_get_info(sclk->id); + if (IS_ERR(sclk->info)) + return NULL; + } else if (init.ops == &scpi_clk_ops) { + if (sclk->scpi_ops->clk_get_range(sclk->id, &min, &max) || !max) + return NULL; + } else { + return NULL; + } + + clk = devm_clk_register(dev, &sclk->hw); + if (!IS_ERR(clk) && max) + clk_hw_set_rate_range(&sclk->hw, min, max); + return clk; +} + +struct scpi_clk_data { + struct scpi_clk **clk; + unsigned int clk_num; +}; + +static struct clk * +scpi_of_clk_src_get(struct of_phandle_args *clkspec, void *data) +{ + struct scpi_clk *sclk; + struct scpi_clk_data *clk_data = data; + unsigned int idx = clkspec->args[0], count; + + for (count = 0; count < clk_data->clk_num; count++) { + sclk = clk_data->clk[count]; + if (idx == sclk->id) + return sclk->hw.clk; + } + + return ERR_PTR(-EINVAL); +} + +static int scpi_clk_add(struct device *dev, struct device_node *np, + const struct of_device_id *match) +{ + struct clk **clks; + int idx, count; + struct scpi_clk_data *clk_data; + + count = of_property_count_strings(np, "clock-output-names"); + if (count < 0) { + dev_err(dev, "%s: invalid clock output count\n", np->name); + return -EINVAL; + } + + clk_data = devm_kmalloc(dev, sizeof(*clk_data), GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clk_num = count; + clk_data->clk = devm_kcalloc(dev, count, sizeof(*clk_data->clk), + GFP_KERNEL); + if (!clk_data->clk) + return -ENOMEM; + + clks = devm_kcalloc(dev, count, sizeof(*clks), GFP_KERNEL); + if (!clks) + return -ENOMEM; + + for (idx = 0; idx < count; idx++) { + struct scpi_clk *sclk; + const char *name; + u32 val; + + sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL); + if (!sclk) + return -ENOMEM; + + if (of_property_read_string_index(np, "clock-output-names", + idx, &name)) { + dev_err(dev, "invalid clock name @ %s\n", np->name); + return -EINVAL; + } + + if (of_property_read_u32_index(np, "clock-indices", + idx, &val)) { + dev_err(dev, "invalid clock index @ %s\n", np->name); + return -EINVAL; + } + + sclk->id = val; + + clks[idx] = scpi_clk_ops_init(dev, match, sclk, name); + if (IS_ERR_OR_NULL(clks[idx])) + dev_err(dev, "failed to register clock '%s'\n", name); + else + dev_dbg(dev, "Registered clock '%s'\n", name); + clk_data->clk[idx] = sclk; + } + + return of_clk_add_provider(np, scpi_of_clk_src_get, clk_data); +} + +static int scpi_clocks_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child, *np = dev->of_node; + + if (cpufreq_dev) { + platform_device_unregister(cpufreq_dev); + cpufreq_dev = NULL; + } + + for_each_available_child_of_node(np, child) + of_clk_del_provider(np); + return 0; +} + +static int scpi_clocks_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct device_node *child, *np = dev->of_node; + const struct of_device_id *match; + + if (!get_scpi_ops()) + return -ENXIO; + + for_each_available_child_of_node(np, child) { + match = of_match_node(scpi_clk_match, child); + if (!match) + continue; + ret = scpi_clk_add(dev, child, match); + if (ret) { + scpi_clocks_remove(pdev); + return ret; + } + } + /* Add the virtual cpufreq device */ + cpufreq_dev = platform_device_register_simple("scpi-cpufreq", + -1, NULL, 0); + if (!cpufreq_dev) + pr_warn("unable to register cpufreq device"); + + return 0; +} + +static const struct of_device_id scpi_clocks_ids[] = { + { .compatible = "arm,scpi-clocks", }, + {} +}; +MODULE_DEVICE_TABLE(of, scpi_clocks_ids); + +static struct platform_driver scpi_clocks_driver = { + .driver = { + .name = "scpi_clocks", + .of_match_table = scpi_clocks_ids, + }, + .probe = scpi_clocks_probe, + .remove = scpi_clocks_remove, +}; +module_platform_driver(scpi_clocks_driver); + +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); +MODULE_DESCRIPTION("ARM SCPI clock driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/tcb_clksrc.c b/drivers/clocksource/tcb_clksrc.c index d28d2fe798d5..6ee91401918e 100644 --- a/drivers/clocksource/tcb_clksrc.c +++ b/drivers/clocksource/tcb_clksrc.c @@ -193,10 +193,17 @@ static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) struct clk *t2_clk = tc->clk[2]; int irq = tc->irq[2]; + ret = clk_prepare_enable(tc->slow_clk); + if (ret) + return ret; + /* try to enable t2 clk to avoid future errors in mode change */ ret = clk_prepare_enable(t2_clk); - if (ret) + if (ret) { + clk_disable_unprepare(tc->slow_clk); return ret; + } + clk_disable(t2_clk); clkevt.regs = tc->regs; @@ -208,7 +215,8 @@ static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx) ret = request_irq(irq, ch2_irq, IRQF_TIMER, "tc_clkevt", &clkevt); if (ret) { - clk_disable_unprepare(t2_clk); + clk_unprepare(t2_clk); + clk_disable_unprepare(tc->slow_clk); return ret; } diff --git a/drivers/clocksource/timer-atmel-st.c b/drivers/clocksource/timer-atmel-st.c index 41b7b6dc1d0d..29d21d68df5a 100644 --- a/drivers/clocksource/timer-atmel-st.c +++ b/drivers/clocksource/timer-atmel-st.c @@ -22,6 +22,7 @@ #include <linux/kernel.h> #include <linux/interrupt.h> #include <linux/irq.h> +#include <linux/clk.h> #include <linux/clockchips.h> #include <linux/export.h> #include <linux/mfd/syscon.h> @@ -33,9 +34,7 @@ static unsigned long last_crtr; static u32 irqmask; static struct clock_event_device clkevt; static struct regmap *regmap_st; - -#define AT91_SLOW_CLOCK 32768 -#define RM9200_TIMER_LATCH ((AT91_SLOW_CLOCK + HZ/2) / HZ) +static int timer_latch; /* * The ST_CRTR is updated asynchronously to the master clock ... but @@ -82,8 +81,8 @@ static irqreturn_t at91rm9200_timer_interrupt(int irq, void *dev_id) if (sr & AT91_ST_PITS) { u32 crtr = read_CRTR(); - while (((crtr - last_crtr) & AT91_ST_CRTV) >= RM9200_TIMER_LATCH) { - last_crtr += RM9200_TIMER_LATCH; + while (((crtr - last_crtr) & AT91_ST_CRTV) >= timer_latch) { + last_crtr += timer_latch; clkevt.event_handler(&clkevt); } return IRQ_HANDLED; @@ -144,7 +143,7 @@ static int clkevt32k_set_periodic(struct clock_event_device *dev) /* PIT for periodic irqs; fixed rate of 1/HZ */ irqmask = AT91_ST_PITS; - regmap_write(regmap_st, AT91_ST_PIMR, RM9200_TIMER_LATCH); + regmap_write(regmap_st, AT91_ST_PIMR, timer_latch); regmap_write(regmap_st, AT91_ST_IER, irqmask); return 0; } @@ -197,7 +196,8 @@ static struct clock_event_device clkevt = { */ static void __init atmel_st_timer_init(struct device_node *node) { - unsigned int val; + struct clk *sclk; + unsigned int sclk_rate, val; int irq, ret; regmap_st = syscon_node_to_regmap(node); @@ -221,6 +221,19 @@ static void __init atmel_st_timer_init(struct device_node *node) if (ret) panic(pr_fmt("Unable to setup IRQ\n")); + sclk = of_clk_get(node, 0); + if (IS_ERR(sclk)) + panic(pr_fmt("Unable to get slow clock\n")); + + clk_prepare_enable(sclk); + if (ret) + panic(pr_fmt("Could not enable slow clock\n")); + + sclk_rate = clk_get_rate(sclk); + if (!sclk_rate) + panic(pr_fmt("Invalid slow clock rate\n")); + timer_latch = (sclk_rate + HZ / 2) / HZ; + /* The 32KiHz "Slow Clock" (tick every 30517.58 nanoseconds) is used * directly for the clocksource and all clockevents, after adjusting * its prescaler from the 1 Hz default. @@ -229,11 +242,11 @@ static void __init atmel_st_timer_init(struct device_node *node) /* Setup timer clockevent, with minimum of two ticks (important!!) */ clkevt.cpumask = cpumask_of(0); - clockevents_config_and_register(&clkevt, AT91_SLOW_CLOCK, + clockevents_config_and_register(&clkevt, sclk_rate, 2, AT91_ST_ALMV); /* register clocksource */ - clocksource_register_hz(&clk32k, AT91_SLOW_CLOCK); + clocksource_register_hz(&clk32k, sclk_rate); } CLOCKSOURCE_OF_DECLARE(atmel_st_timer, "atmel,at91rm9200-st", atmel_st_timer_init); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 642fd49793b0..1582c1c016b0 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -199,6 +199,16 @@ config ARM_SA1100_CPUFREQ config ARM_SA1110_CPUFREQ bool +config ARM_SCPI_CPUFREQ + tristate "SCPI based CPUfreq driver" + depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL + help + This adds the CPUfreq driver support for ARM big.LITTLE platforms + using SCPI protocol for CPU power management. + + This driver uses SCPI Message Protocol driver to interact with the + firmware providing the CPU DVFS functionality. + config ARM_SPEAR_CPUFREQ bool "SPEAr CPUFreq support" depends on PLAT_SPEAR diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index d11309c487d0..c0af1a1281c8 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o +obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o obj-$(CONFIG_ARM_TEGRA20_CPUFREQ) += tegra20-cpufreq.o obj-$(CONFIG_ARM_TEGRA124_CPUFREQ) += tegra124-cpufreq.o diff --git a/drivers/cpufreq/scpi-cpufreq.c b/drivers/cpufreq/scpi-cpufreq.c new file mode 100644 index 000000000000..2c3b16fd3a01 --- /dev/null +++ b/drivers/cpufreq/scpi-cpufreq.c @@ -0,0 +1,124 @@ +/* + * System Control and Power Interface (SCPI) based CPUFreq Interface driver + * + * It provides necessary ops to arm_big_little cpufreq driver. + * + * Copyright (C) 2015 ARM Ltd. + * Sudeep Holla <sudeep.holla@arm.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 "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) KBUILD_MODNAME ": " fmt + +#include <linux/cpufreq.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/scpi_protocol.h> +#include <linux/types.h> + +#include "arm_big_little.h" + +static struct scpi_ops *scpi_ops; + +static struct scpi_dvfs_info *scpi_get_dvfs_info(struct device *cpu_dev) +{ + u8 domain = topology_physical_package_id(cpu_dev->id); + + if (domain < 0) + return ERR_PTR(-EINVAL); + return scpi_ops->dvfs_get_info(domain); +} + +static int scpi_opp_table_ops(struct device *cpu_dev, bool remove) +{ + int idx, ret = 0; + struct scpi_opp *opp; + struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev); + + if (IS_ERR(info)) + return PTR_ERR(info); + + if (!info->opps) + return -EIO; + + for (opp = info->opps, idx = 0; idx < info->count; idx++, opp++) { + if (remove) + dev_pm_opp_remove(cpu_dev, opp->freq); + else + ret = dev_pm_opp_add(cpu_dev, opp->freq, + opp->m_volt * 1000); + if (ret) { + dev_warn(cpu_dev, "failed to add opp %uHz %umV\n", + opp->freq, opp->m_volt); + while (idx-- > 0) + dev_pm_opp_remove(cpu_dev, (--opp)->freq); + return ret; + } + } + return ret; +} + +static int scpi_get_transition_latency(struct device *cpu_dev) +{ + struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev); + + if (IS_ERR(info)) + return PTR_ERR(info); + return info->latency; +} + +static int scpi_init_opp_table(struct device *cpu_dev) +{ + return scpi_opp_table_ops(cpu_dev, false); +} + +static void scpi_free_opp_table(struct device *cpu_dev) +{ + scpi_opp_table_ops(cpu_dev, true); +} + +static struct cpufreq_arm_bL_ops scpi_cpufreq_ops = { + .name = "scpi", + .get_transition_latency = scpi_get_transition_latency, + .init_opp_table = scpi_init_opp_table, + .free_opp_table = scpi_free_opp_table, +}; + +static int scpi_cpufreq_probe(struct platform_device *pdev) +{ + scpi_ops = get_scpi_ops(); + if (!scpi_ops) + return -EIO; + + return bL_cpufreq_register(&scpi_cpufreq_ops); +} + +static int scpi_cpufreq_remove(struct platform_device *pdev) +{ + bL_cpufreq_unregister(&scpi_cpufreq_ops); + scpi_ops = NULL; + return 0; +} + +static struct platform_driver scpi_cpufreq_platdrv = { + .driver = { + .name = "scpi-cpufreq", + .owner = THIS_MODULE, + }, + .probe = scpi_cpufreq_probe, + .remove = scpi_cpufreq_remove, +}; +module_platform_driver(scpi_cpufreq_platdrv); + +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); +MODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 665efca59487..cf478fe6b335 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -8,6 +8,25 @@ menu "Firmware Drivers" config ARM_PSCI_FW bool +config ARM_SCPI_PROTOCOL + tristate "ARM System Control and Power Interface (SCPI) Message Protocol" + depends on ARM_MHU + help + System Control and Power Interface (SCPI) Message Protocol is + defined for the purpose of communication between the Application + Cores(AP) and the System Control Processor(SCP). The MHU peripheral + provides a mechanism for inter-processor communication between SCP + and AP. + + SCP controls most of the power managament on the Application + Processors. It offers control and management of: the core/cluster + power states, various power domain DVFS including the core/cluster, + certain system clocks configuration, thermal sensors and many + others. + + This protocol library provides interface for all the client drivers + making use of the features offered by the SCP. + config EDD tristate "BIOS Enhanced Disk Drive calls determine boot disk" depends on X86 @@ -135,6 +154,13 @@ config ISCSI_IBFT detect iSCSI boot parameters dynamically during system boot, say Y. Otherwise, say N. +config RASPBERRYPI_FIRMWARE + tristate "Raspberry Pi Firmware Driver" + depends on BCM2835_MBOX + help + This option enables support for communicating with the firmware on the + Raspberry Pi. + config QCOM_SCM bool depends on ARM || ARM64 diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 2ee83474a3c1..48dd4175297e 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -2,6 +2,7 @@ # Makefile for the linux kernel. # obj-$(CONFIG_ARM_PSCI_FW) += psci.o +obj-$(CONFIG_ARM_SCPI_PROTOCOL) += arm_scpi.o obj-$(CONFIG_DMI) += dmi_scan.o obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o obj-$(CONFIG_EDD) += edd.o @@ -12,10 +13,11 @@ obj-$(CONFIG_DMIID) += dmi-id.o obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o +obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o obj-$(CONFIG_QCOM_SCM) += qcom_scm.o obj-$(CONFIG_QCOM_SCM_64) += qcom_scm-64.o obj-$(CONFIG_QCOM_SCM_32) += qcom_scm-32.o -CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) +CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch armv7-a\n.arch_extension sec,-DREQUIRES_SEC=1) -march=armv7-a obj-y += broadcom/ obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c new file mode 100644 index 000000000000..6174db80c663 --- /dev/null +++ b/drivers/firmware/arm_scpi.c @@ -0,0 +1,771 @@ +/* + * System Control and Power Interface (SCPI) Message Protocol driver + * + * SCPI Message Protocol is used between the System Control Processor(SCP) + * and the Application Processors(AP). The Message Handling Unit(MHU) + * provides a mechanism for inter-processor communication between SCP's + * Cortex M3 and AP. + * + * SCP offers control and management of the core/cluster power states, + * various power domain DVFS including the core/cluster, certain system + * clocks configuration, thermal sensors and many others. + * + * Copyright (C) 2015 ARM Ltd. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bitmap.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/printk.h> +#include <linux/scpi_protocol.h> +#include <linux/slab.h> +#include <linux/sort.h> +#include <linux/spinlock.h> + +#define CMD_ID_SHIFT 0 +#define CMD_ID_MASK 0x7f +#define CMD_TOKEN_ID_SHIFT 8 +#define CMD_TOKEN_ID_MASK 0xff +#define CMD_DATA_SIZE_SHIFT 16 +#define CMD_DATA_SIZE_MASK 0x1ff +#define PACK_SCPI_CMD(cmd_id, tx_sz) \ + ((((cmd_id) & CMD_ID_MASK) << CMD_ID_SHIFT) | \ + (((tx_sz) & CMD_DATA_SIZE_MASK) << CMD_DATA_SIZE_SHIFT)) +#define ADD_SCPI_TOKEN(cmd, token) \ + ((cmd) |= (((token) & CMD_TOKEN_ID_MASK) << CMD_TOKEN_ID_SHIFT)) + +#define CMD_SIZE(cmd) (((cmd) >> CMD_DATA_SIZE_SHIFT) & CMD_DATA_SIZE_MASK) +#define CMD_UNIQ_MASK (CMD_TOKEN_ID_MASK << CMD_TOKEN_ID_SHIFT | CMD_ID_MASK) +#define CMD_XTRACT_UNIQ(cmd) ((cmd) & CMD_UNIQ_MASK) + +#define SCPI_SLOT 0 + +#define MAX_DVFS_DOMAINS 8 +#define MAX_DVFS_OPPS 8 +#define DVFS_LATENCY(hdr) (le32_to_cpu(hdr) >> 16) +#define DVFS_OPP_COUNT(hdr) ((le32_to_cpu(hdr) >> 8) & 0xff) + +#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 FW_REV_MAJOR_BITS 24 +#define FW_REV_MINOR_BITS 16 +#define FW_REV_PATCH_MASK ((1U << FW_REV_MINOR_BITS) - 1) +#define FW_REV_MINOR_MASK ((1U << FW_REV_MAJOR_BITS) - 1) +#define FW_REV_MAJOR(x) ((x) >> FW_REV_MAJOR_BITS) +#define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS) +#define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK) + +#define MAX_RX_TIMEOUT (msecs_to_jiffies(20)) + +enum scpi_error_codes { + SCPI_SUCCESS = 0, /* Success */ + SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */ + SCPI_ERR_ALIGN = 2, /* Invalid alignment */ + SCPI_ERR_SIZE = 3, /* Invalid size */ + SCPI_ERR_HANDLER = 4, /* Invalid handler/callback */ + SCPI_ERR_ACCESS = 5, /* Invalid access/permission denied */ + SCPI_ERR_RANGE = 6, /* Value out of range */ + SCPI_ERR_TIMEOUT = 7, /* Timeout has occurred */ + SCPI_ERR_NOMEM = 8, /* Invalid memory area or pointer */ + SCPI_ERR_PWRSTATE = 9, /* Invalid power state */ + SCPI_ERR_SUPPORT = 10, /* Not supported or disabled */ + SCPI_ERR_DEVICE = 11, /* Device error */ + SCPI_ERR_BUSY = 12, /* Device busy */ + SCPI_ERR_MAX +}; + +enum scpi_std_cmd { + SCPI_CMD_INVALID = 0x00, + SCPI_CMD_SCPI_READY = 0x01, + SCPI_CMD_SCPI_CAPABILITIES = 0x02, + SCPI_CMD_SET_CSS_PWR_STATE = 0x03, + SCPI_CMD_GET_CSS_PWR_STATE = 0x04, + SCPI_CMD_SET_SYS_PWR_STATE = 0x05, + SCPI_CMD_SET_CPU_TIMER = 0x06, + SCPI_CMD_CANCEL_CPU_TIMER = 0x07, + SCPI_CMD_DVFS_CAPABILITIES = 0x08, + SCPI_CMD_GET_DVFS_INFO = 0x09, + SCPI_CMD_SET_DVFS = 0x0a, + SCPI_CMD_GET_DVFS = 0x0b, + SCPI_CMD_GET_DVFS_STAT = 0x0c, + SCPI_CMD_CLOCK_CAPABILITIES = 0x0d, + SCPI_CMD_GET_CLOCK_INFO = 0x0e, + SCPI_CMD_SET_CLOCK_VALUE = 0x0f, + SCPI_CMD_GET_CLOCK_VALUE = 0x10, + SCPI_CMD_PSU_CAPABILITIES = 0x11, + SCPI_CMD_GET_PSU_INFO = 0x12, + SCPI_CMD_SET_PSU = 0x13, + SCPI_CMD_GET_PSU = 0x14, + SCPI_CMD_SENSOR_CAPABILITIES = 0x15, + SCPI_CMD_SENSOR_INFO = 0x16, + SCPI_CMD_SENSOR_VALUE = 0x17, + SCPI_CMD_SENSOR_CFG_PERIODIC = 0x18, + SCPI_CMD_SENSOR_CFG_BOUNDS = 0x19, + SCPI_CMD_SENSOR_ASYNC_VALUE = 0x1a, + SCPI_CMD_SET_DEVICE_PWR_STATE = 0x1b, + SCPI_CMD_GET_DEVICE_PWR_STATE = 0x1c, + SCPI_CMD_COUNT +}; + +struct scpi_xfer { + u32 slot; /* has to be first element */ + u32 cmd; + u32 status; + const void *tx_buf; + void *rx_buf; + unsigned int tx_len; + unsigned int rx_len; + struct list_head node; + struct completion done; +}; + +struct scpi_chan { + struct mbox_client cl; + struct mbox_chan *chan; + void __iomem *tx_payload; + void __iomem *rx_payload; + struct list_head rx_pending; + struct list_head xfers_list; + struct scpi_xfer *xfers; + spinlock_t rx_lock; /* locking for the rx pending list */ + struct mutex xfers_lock; + u8 token; +}; + +struct scpi_drvinfo { + u32 protocol_version; + u32 firmware_version; + int num_chans; + atomic_t next_chan; + struct scpi_ops *scpi_ops; + struct scpi_chan *channels; + struct scpi_dvfs_info *dvfs[MAX_DVFS_DOMAINS]; +}; + +/* + * The SCP firmware only executes in little-endian mode, so any buffers + * shared through SCPI should have their contents converted to little-endian + */ +struct scpi_shared_mem { + __le32 command; + __le32 status; + u8 payload[0]; +} __packed; + +struct scp_capabilities { + __le32 protocol_version; + __le32 event_version; + __le32 platform_version; + __le32 commands[4]; +} __packed; + +struct clk_get_info { + __le16 id; + __le16 flags; + __le32 min_rate; + __le32 max_rate; + u8 name[20]; +} __packed; + +struct clk_get_value { + __le32 rate; +} __packed; + +struct clk_set_value { + __le16 id; + __le16 reserved; + __le32 rate; +} __packed; + +struct dvfs_info { + __le32 header; + struct { + __le32 freq; + __le32 m_volt; + } opps[MAX_DVFS_OPPS]; +} __packed; + +struct dvfs_get { + u8 index; +} __packed; + +struct dvfs_set { + u8 domain; + u8 index; +} __packed; + +struct sensor_capabilities { + __le16 sensors; +} __packed; + +struct _scpi_sensor_info { + __le16 sensor_id; + u8 class; + u8 trigger_type; + char name[20]; +}; + +struct sensor_value { + __le32 val; +} __packed; + +static struct scpi_drvinfo *scpi_info; + +static int scpi_linux_errmap[SCPI_ERR_MAX] = { + /* better than switch case as long as return value is continuous */ + 0, /* SCPI_SUCCESS */ + -EINVAL, /* SCPI_ERR_PARAM */ + -ENOEXEC, /* SCPI_ERR_ALIGN */ + -EMSGSIZE, /* SCPI_ERR_SIZE */ + -EINVAL, /* SCPI_ERR_HANDLER */ + -EACCES, /* SCPI_ERR_ACCESS */ + -ERANGE, /* SCPI_ERR_RANGE */ + -ETIMEDOUT, /* SCPI_ERR_TIMEOUT */ + -ENOMEM, /* SCPI_ERR_NOMEM */ + -EINVAL, /* SCPI_ERR_PWRSTATE */ + -EOPNOTSUPP, /* SCPI_ERR_SUPPORT */ + -EIO, /* SCPI_ERR_DEVICE */ + -EBUSY, /* SCPI_ERR_BUSY */ +}; + +static inline int scpi_to_linux_errno(int errno) +{ + if (errno >= SCPI_SUCCESS && errno < SCPI_ERR_MAX) + return scpi_linux_errmap[errno]; + return -EIO; +} + +static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd) +{ + unsigned long flags; + struct scpi_xfer *t, *match = NULL; + + spin_lock_irqsave(&ch->rx_lock, flags); + if (list_empty(&ch->rx_pending)) { + spin_unlock_irqrestore(&ch->rx_lock, flags); + return; + } + + list_for_each_entry(t, &ch->rx_pending, node) + if (CMD_XTRACT_UNIQ(t->cmd) == CMD_XTRACT_UNIQ(cmd)) { + list_del(&t->node); + match = t; + break; + } + /* check if wait_for_completion is in progress or timed-out */ + if (match && !completion_done(&match->done)) { + struct scpi_shared_mem *mem = ch->rx_payload; + unsigned int len = min(match->rx_len, CMD_SIZE(cmd)); + + match->status = le32_to_cpu(mem->status); + memcpy_fromio(match->rx_buf, mem->payload, len); + if (match->rx_len > len) + memset(match->rx_buf + len, 0, match->rx_len - len); + complete(&match->done); + } + spin_unlock_irqrestore(&ch->rx_lock, flags); +} + +static void scpi_handle_remote_msg(struct mbox_client *c, void *msg) +{ + struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); + struct scpi_shared_mem *mem = ch->rx_payload; + u32 cmd = le32_to_cpu(mem->command); + + scpi_process_cmd(ch, cmd); +} + +static void scpi_tx_prepare(struct mbox_client *c, void *msg) +{ + unsigned long flags; + struct scpi_xfer *t = msg; + struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); + struct scpi_shared_mem *mem = (struct scpi_shared_mem *)ch->tx_payload; + + if (t->tx_buf) + memcpy_toio(mem->payload, t->tx_buf, t->tx_len); + if (t->rx_buf) { + if (!(++ch->token)) + ++ch->token; + ADD_SCPI_TOKEN(t->cmd, ch->token); + spin_lock_irqsave(&ch->rx_lock, flags); + list_add_tail(&t->node, &ch->rx_pending); + spin_unlock_irqrestore(&ch->rx_lock, flags); + } + mem->command = cpu_to_le32(t->cmd); +} + +static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch) +{ + struct scpi_xfer *t; + + mutex_lock(&ch->xfers_lock); + if (list_empty(&ch->xfers_list)) { + mutex_unlock(&ch->xfers_lock); + return NULL; + } + t = list_first_entry(&ch->xfers_list, struct scpi_xfer, node); + list_del(&t->node); + mutex_unlock(&ch->xfers_lock); + return t; +} + +static void put_scpi_xfer(struct scpi_xfer *t, struct scpi_chan *ch) +{ + mutex_lock(&ch->xfers_lock); + list_add_tail(&t->node, &ch->xfers_list); + mutex_unlock(&ch->xfers_lock); +} + +static int scpi_send_message(u8 cmd, void *tx_buf, unsigned int tx_len, + void *rx_buf, unsigned int rx_len) +{ + int ret; + u8 chan; + struct scpi_xfer *msg; + struct scpi_chan *scpi_chan; + + chan = atomic_inc_return(&scpi_info->next_chan) % scpi_info->num_chans; + scpi_chan = scpi_info->channels + chan; + + msg = get_scpi_xfer(scpi_chan); + if (!msg) + return -ENOMEM; + + msg->slot = BIT(SCPI_SLOT); + msg->cmd = PACK_SCPI_CMD(cmd, tx_len); + msg->tx_buf = tx_buf; + msg->tx_len = tx_len; + msg->rx_buf = rx_buf; + msg->rx_len = rx_len; + init_completion(&msg->done); + + ret = mbox_send_message(scpi_chan->chan, msg); + if (ret < 0 || !rx_buf) + goto out; + + if (!wait_for_completion_timeout(&msg->done, MAX_RX_TIMEOUT)) + ret = -ETIMEDOUT; + else + /* first status word */ + ret = le32_to_cpu(msg->status); +out: + if (ret < 0 && rx_buf) /* remove entry from the list if timed-out */ + scpi_process_cmd(scpi_chan, msg->cmd); + + put_scpi_xfer(msg, scpi_chan); + /* SCPI error codes > 0, translate them to Linux scale*/ + return ret > 0 ? scpi_to_linux_errno(ret) : ret; +} + +static u32 scpi_get_version(void) +{ + return scpi_info->protocol_version; +} + +static int +scpi_clk_get_range(u16 clk_id, unsigned long *min, unsigned long *max) +{ + int ret; + struct clk_get_info clk; + __le16 le_clk_id = cpu_to_le16(clk_id); + + ret = scpi_send_message(SCPI_CMD_GET_CLOCK_INFO, &le_clk_id, + sizeof(le_clk_id), &clk, sizeof(clk)); + if (!ret) { + *min = le32_to_cpu(clk.min_rate); + *max = le32_to_cpu(clk.max_rate); + } + return ret; +} + +static unsigned long scpi_clk_get_val(u16 clk_id) +{ + int ret; + struct clk_get_value clk; + __le16 le_clk_id = cpu_to_le16(clk_id); + + ret = scpi_send_message(SCPI_CMD_GET_CLOCK_VALUE, &le_clk_id, + sizeof(le_clk_id), &clk, sizeof(clk)); + return ret ? ret : le32_to_cpu(clk.rate); +} + +static int scpi_clk_set_val(u16 clk_id, unsigned long rate) +{ + int stat; + struct clk_set_value clk = { + .id = cpu_to_le16(clk_id), + .rate = cpu_to_le32(rate) + }; + + return scpi_send_message(SCPI_CMD_SET_CLOCK_VALUE, &clk, sizeof(clk), + &stat, sizeof(stat)); +} + +static int scpi_dvfs_get_idx(u8 domain) +{ + int ret; + struct dvfs_get dvfs; + + ret = scpi_send_message(SCPI_CMD_GET_DVFS, &domain, sizeof(domain), + &dvfs, sizeof(dvfs)); + return ret ? ret : dvfs.index; +} + +static int scpi_dvfs_set_idx(u8 domain, u8 index) +{ + int stat; + struct dvfs_set dvfs = {domain, index}; + + return scpi_send_message(SCPI_CMD_SET_DVFS, &dvfs, sizeof(dvfs), + &stat, sizeof(stat)); +} + +static int opp_cmp_func(const void *opp1, const void *opp2) +{ + const struct scpi_opp *t1 = opp1, *t2 = opp2; + + return t1->freq - t2->freq; +} + +static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) +{ + struct scpi_dvfs_info *info; + struct scpi_opp *opp; + struct dvfs_info buf; + int ret, i; + + if (domain >= MAX_DVFS_DOMAINS) + return ERR_PTR(-EINVAL); + + if (scpi_info->dvfs[domain]) /* data already populated */ + return scpi_info->dvfs[domain]; + + ret = scpi_send_message(SCPI_CMD_GET_DVFS_INFO, &domain, sizeof(domain), + &buf, sizeof(buf)); + + if (ret) + return ERR_PTR(ret); + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return ERR_PTR(-ENOMEM); + + info->count = DVFS_OPP_COUNT(buf.header); + info->latency = DVFS_LATENCY(buf.header) * 1000; /* uS to nS */ + + info->opps = kcalloc(info->count, sizeof(*opp), GFP_KERNEL); + if (!info->opps) { + kfree(info); + return ERR_PTR(-ENOMEM); + } + + for (i = 0, opp = info->opps; i < info->count; i++, opp++) { + opp->freq = le32_to_cpu(buf.opps[i].freq); + opp->m_volt = le32_to_cpu(buf.opps[i].m_volt); + } + + sort(info->opps, info->count, sizeof(*opp), opp_cmp_func, NULL); + + scpi_info->dvfs[domain] = info; + return info; +} + +static int scpi_sensor_get_capability(u16 *sensors) +{ + struct sensor_capabilities cap_buf; + int ret; + + ret = scpi_send_message(SCPI_CMD_SENSOR_CAPABILITIES, NULL, 0, &cap_buf, + sizeof(cap_buf)); + if (!ret) + *sensors = le16_to_cpu(cap_buf.sensors); + + return ret; +} + +static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info) +{ + __le16 id = cpu_to_le16(sensor_id); + struct _scpi_sensor_info _info; + int ret; + + ret = scpi_send_message(SCPI_CMD_SENSOR_INFO, &id, sizeof(id), + &_info, sizeof(_info)); + if (!ret) { + memcpy(info, &_info, sizeof(*info)); + info->sensor_id = le16_to_cpu(_info.sensor_id); + } + + return ret; +} + +int scpi_sensor_get_value(u16 sensor, u32 *val) +{ + struct sensor_value buf; + int ret; + + ret = scpi_send_message(SCPI_CMD_SENSOR_VALUE, &sensor, sizeof(sensor), + &buf, sizeof(buf)); + if (!ret) + *val = le32_to_cpu(buf.val); + + return ret; +} + +static struct scpi_ops scpi_ops = { + .get_version = scpi_get_version, + .clk_get_range = scpi_clk_get_range, + .clk_get_val = scpi_clk_get_val, + .clk_set_val = scpi_clk_set_val, + .dvfs_get_idx = scpi_dvfs_get_idx, + .dvfs_set_idx = scpi_dvfs_set_idx, + .dvfs_get_info = scpi_dvfs_get_info, + .sensor_get_capability = scpi_sensor_get_capability, + .sensor_get_info = scpi_sensor_get_info, + .sensor_get_value = scpi_sensor_get_value, +}; + +struct scpi_ops *get_scpi_ops(void) +{ + return scpi_info ? scpi_info->scpi_ops : NULL; +} +EXPORT_SYMBOL_GPL(get_scpi_ops); + +static int scpi_init_versions(struct scpi_drvinfo *info) +{ + int ret; + struct scp_capabilities caps; + + ret = scpi_send_message(SCPI_CMD_SCPI_CAPABILITIES, NULL, 0, + &caps, sizeof(caps)); + if (!ret) { + info->protocol_version = le32_to_cpu(caps.protocol_version); + info->firmware_version = le32_to_cpu(caps.platform_version); + } + return ret; +} + +static ssize_t protocol_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); + + return sprintf(buf, "%d.%d\n", + PROTOCOL_REV_MAJOR(scpi_info->protocol_version), + PROTOCOL_REV_MINOR(scpi_info->protocol_version)); +} +static DEVICE_ATTR_RO(protocol_version); + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); + + return sprintf(buf, "%d.%d.%d\n", + FW_REV_MAJOR(scpi_info->firmware_version), + FW_REV_MINOR(scpi_info->firmware_version), + FW_REV_PATCH(scpi_info->firmware_version)); +} +static DEVICE_ATTR_RO(firmware_version); + +static struct attribute *versions_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_protocol_version.attr, + NULL, +}; +ATTRIBUTE_GROUPS(versions); + +static void +scpi_free_channels(struct device *dev, struct scpi_chan *pchan, int count) +{ + int i; + + for (i = 0; i < count && pchan->chan; i++, pchan++) { + mbox_free_channel(pchan->chan); + devm_kfree(dev, pchan->xfers); + devm_iounmap(dev, pchan->rx_payload); + } +} + +static int scpi_remove(struct platform_device *pdev) +{ + int i; + struct device *dev = &pdev->dev; + struct scpi_drvinfo *info = platform_get_drvdata(pdev); + + scpi_info = NULL; /* stop exporting SCPI ops through get_scpi_ops */ + + of_platform_depopulate(dev); + sysfs_remove_groups(&dev->kobj, versions_groups); + scpi_free_channels(dev, info->channels, info->num_chans); + platform_set_drvdata(pdev, NULL); + + for (i = 0; i < MAX_DVFS_DOMAINS && info->dvfs[i]; i++) { + kfree(info->dvfs[i]->opps); + kfree(info->dvfs[i]); + } + devm_kfree(dev, info->channels); + devm_kfree(dev, info); + + return 0; +} + +#define MAX_SCPI_XFERS 10 +static int scpi_alloc_xfer_list(struct device *dev, struct scpi_chan *ch) +{ + int i; + struct scpi_xfer *xfers; + + xfers = devm_kzalloc(dev, MAX_SCPI_XFERS * sizeof(*xfers), GFP_KERNEL); + if (!xfers) + return -ENOMEM; + + ch->xfers = xfers; + for (i = 0; i < MAX_SCPI_XFERS; i++, xfers++) + list_add_tail(&xfers->node, &ch->xfers_list); + return 0; +} + +static int scpi_probe(struct platform_device *pdev) +{ + int count, idx, ret; + struct resource res; + struct scpi_chan *scpi_chan; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + scpi_info = devm_kzalloc(dev, sizeof(*scpi_info), GFP_KERNEL); + if (!scpi_info) + return -ENOMEM; + + count = of_count_phandle_with_args(np, "mboxes", "#mbox-cells"); + if (count < 0) { + dev_err(dev, "no mboxes property in '%s'\n", np->full_name); + return -ENODEV; + } + + scpi_chan = devm_kcalloc(dev, count, sizeof(*scpi_chan), GFP_KERNEL); + if (!scpi_chan) + return -ENOMEM; + + for (idx = 0; idx < count; idx++) { + resource_size_t size; + struct scpi_chan *pchan = scpi_chan + idx; + struct mbox_client *cl = &pchan->cl; + struct device_node *shmem = of_parse_phandle(np, "shmem", idx); + + if (of_address_to_resource(shmem, 0, &res)) { + dev_err(dev, "failed to get SCPI payload mem resource\n"); + ret = -EINVAL; + goto err; + } + + size = resource_size(&res); + pchan->rx_payload = devm_ioremap(dev, res.start, size); + if (!pchan->rx_payload) { + dev_err(dev, "failed to ioremap SCPI payload\n"); + ret = -EADDRNOTAVAIL; + goto err; + } + pchan->tx_payload = pchan->rx_payload + (size >> 1); + + cl->dev = dev; + cl->rx_callback = scpi_handle_remote_msg; + cl->tx_prepare = scpi_tx_prepare; + cl->tx_block = true; + cl->tx_tout = 50; + cl->knows_txdone = false; /* controller can't ack */ + + INIT_LIST_HEAD(&pchan->rx_pending); + INIT_LIST_HEAD(&pchan->xfers_list); + spin_lock_init(&pchan->rx_lock); + mutex_init(&pchan->xfers_lock); + + ret = scpi_alloc_xfer_list(dev, pchan); + if (!ret) { + pchan->chan = mbox_request_channel(cl, idx); + if (!IS_ERR(pchan->chan)) + continue; + ret = PTR_ERR(pchan->chan); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get channel%d err %d\n", + idx, ret); + } +err: + scpi_free_channels(dev, scpi_chan, idx); + scpi_info = NULL; + return ret; + } + + scpi_info->channels = scpi_chan; + scpi_info->num_chans = count; + platform_set_drvdata(pdev, scpi_info); + + ret = scpi_init_versions(scpi_info); + if (ret) { + dev_err(dev, "incorrect or no SCP firmware found\n"); + scpi_remove(pdev); + return ret; + } + + _dev_info(dev, "SCP Protocol %d.%d Firmware %d.%d.%d version\n", + PROTOCOL_REV_MAJOR(scpi_info->protocol_version), + PROTOCOL_REV_MINOR(scpi_info->protocol_version), + FW_REV_MAJOR(scpi_info->firmware_version), + FW_REV_MINOR(scpi_info->firmware_version), + FW_REV_PATCH(scpi_info->firmware_version)); + scpi_info->scpi_ops = &scpi_ops; + + ret = sysfs_create_groups(&dev->kobj, versions_groups); + if (ret) + dev_err(dev, "unable to create sysfs version group\n"); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static const struct of_device_id scpi_of_match[] = { + {.compatible = "arm,scpi"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, scpi_of_match); + +static struct platform_driver scpi_driver = { + .driver = { + .name = "scpi_protocol", + .of_match_table = scpi_of_match, + }, + .probe = scpi_probe, + .remove = scpi_remove, +}; +module_platform_driver(scpi_driver); + +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); +MODULE_DESCRIPTION("ARM SCPI mailbox protocol driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index 42700f09a8c5..d24f35d74b27 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -20,23 +20,25 @@ #include <linux/printk.h> #include <linux/psci.h> #include <linux/reboot.h> +#include <linux/suspend.h> #include <uapi/linux/psci.h> #include <asm/cputype.h> #include <asm/system_misc.h> #include <asm/smp_plat.h> +#include <asm/suspend.h> /* * While a 64-bit OS can make calls with SMC32 calling conventions, for some - * calls it is necessary to use SMC64 to pass or return 64-bit values. For such - * calls PSCI_0_2_FN_NATIVE(x) will choose the appropriate (native-width) - * function ID. + * calls it is necessary to use SMC64 to pass or return 64-bit values. + * For such calls PSCI_FN_NATIVE(version, name) will choose the appropriate + * (native-width) function ID. */ #ifdef CONFIG_64BIT -#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN64_##name +#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN64_##name #else -#define PSCI_0_2_FN_NATIVE(name) PSCI_0_2_FN_##name +#define PSCI_FN_NATIVE(version, name) PSCI_##version##_FN_##name #endif /* @@ -70,6 +72,41 @@ enum psci_function { static u32 psci_function_id[PSCI_FN_MAX]; +#define PSCI_0_2_POWER_STATE_MASK \ + (PSCI_0_2_POWER_STATE_ID_MASK | \ + PSCI_0_2_POWER_STATE_TYPE_MASK | \ + PSCI_0_2_POWER_STATE_AFFL_MASK) + +#define PSCI_1_0_EXT_POWER_STATE_MASK \ + (PSCI_1_0_EXT_POWER_STATE_ID_MASK | \ + PSCI_1_0_EXT_POWER_STATE_TYPE_MASK) + +static u32 psci_cpu_suspend_feature; + +static inline bool psci_has_ext_power_state(void) +{ + return psci_cpu_suspend_feature & + PSCI_1_0_FEATURES_CPU_SUSPEND_PF_MASK; +} + +bool psci_power_state_loses_context(u32 state) +{ + const u32 mask = psci_has_ext_power_state() ? + PSCI_1_0_EXT_POWER_STATE_TYPE_MASK : + PSCI_0_2_POWER_STATE_TYPE_MASK; + + return state & mask; +} + +bool psci_power_state_is_valid(u32 state) +{ + const u32 valid_mask = psci_has_ext_power_state() ? + PSCI_1_0_EXT_POWER_STATE_MASK : + PSCI_0_2_POWER_STATE_MASK; + + return !(state & ~valid_mask); +} + static int psci_to_linux_errno(int errno) { switch (errno) { @@ -78,6 +115,7 @@ static int psci_to_linux_errno(int errno) case PSCI_RET_NOT_SUPPORTED: return -EOPNOTSUPP; case PSCI_RET_INVALID_PARAMS: + case PSCI_RET_INVALID_ADDRESS: return -EINVAL; case PSCI_RET_DENIED: return -EPERM; @@ -134,7 +172,7 @@ static int psci_migrate(unsigned long cpuid) static int psci_affinity_info(unsigned long target_affinity, unsigned long lowest_affinity_level) { - return invoke_psci_fn(PSCI_0_2_FN_NATIVE(AFFINITY_INFO), + return invoke_psci_fn(PSCI_FN_NATIVE(0_2, AFFINITY_INFO), target_affinity, lowest_affinity_level, 0); } @@ -145,7 +183,7 @@ static int psci_migrate_info_type(void) static unsigned long psci_migrate_info_up_cpu(void) { - return invoke_psci_fn(PSCI_0_2_FN_NATIVE(MIGRATE_INFO_UP_CPU), + return invoke_psci_fn(PSCI_FN_NATIVE(0_2, MIGRATE_INFO_UP_CPU), 0, 0, 0); } @@ -181,6 +219,49 @@ static void psci_sys_poweroff(void) invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0); } +static int __init psci_features(u32 psci_func_id) +{ + return invoke_psci_fn(PSCI_1_0_FN_PSCI_FEATURES, + psci_func_id, 0, 0); +} + +static int psci_system_suspend(unsigned long unused) +{ + return invoke_psci_fn(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), + virt_to_phys(cpu_resume), 0, 0); +} + +static int psci_system_suspend_enter(suspend_state_t state) +{ + return cpu_suspend(0, psci_system_suspend); +} + +static const struct platform_suspend_ops psci_suspend_ops = { + .valid = suspend_valid_only_mem, + .enter = psci_system_suspend_enter, +}; + +static void __init psci_init_system_suspend(void) +{ + int ret; + + if (!IS_ENABLED(CONFIG_SUSPEND)) + return; + + ret = psci_features(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND)); + + if (ret != PSCI_RET_NOT_SUPPORTED) + suspend_set_ops(&psci_suspend_ops); +} + +static void __init psci_init_cpu_suspend(void) +{ + int feature = psci_features(psci_function_id[PSCI_FN_CPU_SUSPEND]); + + if (feature != PSCI_RET_NOT_SUPPORTED) + psci_cpu_suspend_feature = feature; +} + /* * Detect the presence of a resident Trusted OS which may cause CPU_OFF to * return DENIED (which would be fatal). @@ -224,16 +305,17 @@ static void __init psci_init_migrate(void) static void __init psci_0_2_set_functions(void) { pr_info("Using standard PSCI v0.2 function IDs\n"); - psci_function_id[PSCI_FN_CPU_SUSPEND] = PSCI_0_2_FN_NATIVE(CPU_SUSPEND); + psci_function_id[PSCI_FN_CPU_SUSPEND] = + PSCI_FN_NATIVE(0_2, CPU_SUSPEND); psci_ops.cpu_suspend = psci_cpu_suspend; psci_function_id[PSCI_FN_CPU_OFF] = PSCI_0_2_FN_CPU_OFF; psci_ops.cpu_off = psci_cpu_off; - psci_function_id[PSCI_FN_CPU_ON] = PSCI_0_2_FN_NATIVE(CPU_ON); + psci_function_id[PSCI_FN_CPU_ON] = PSCI_FN_NATIVE(0_2, CPU_ON); psci_ops.cpu_on = psci_cpu_on; - psci_function_id[PSCI_FN_MIGRATE] = PSCI_0_2_FN_NATIVE(MIGRATE); + psci_function_id[PSCI_FN_MIGRATE] = PSCI_FN_NATIVE(0_2, MIGRATE); psci_ops.migrate = psci_migrate; psci_ops.affinity_info = psci_affinity_info; @@ -265,6 +347,11 @@ static int __init psci_probe(void) psci_init_migrate(); + if (PSCI_VERSION_MAJOR(ver) >= 1) { + psci_init_cpu_suspend(); + psci_init_system_suspend(); + } + return 0; } @@ -340,6 +427,7 @@ out_put_node: static const struct of_device_id const psci_of_match[] __initconst = { { .compatible = "arm,psci", .data = psci_0_1_init}, { .compatible = "arm,psci-0.2", .data = psci_0_2_init}, + { .compatible = "arm,psci-1.0", .data = psci_0_2_init}, {}, }; diff --git a/drivers/firmware/qcom_scm-32.c b/drivers/firmware/qcom_scm-32.c index 29e6850665eb..0883292f640f 100644 --- a/drivers/firmware/qcom_scm-32.c +++ b/drivers/firmware/qcom_scm-32.c @@ -480,15 +480,15 @@ void __qcom_scm_cpu_power_down(u32 flags) int __qcom_scm_is_call_available(u32 svc_id, u32 cmd_id) { int ret; - u32 svc_cmd = (svc_id << 10) | cmd_id; - u32 ret_val = 0; + __le32 svc_cmd = cpu_to_le32((svc_id << 10) | cmd_id); + __le32 ret_val = 0; ret = qcom_scm_call(QCOM_SCM_SVC_INFO, QCOM_IS_CALL_AVAIL_CMD, &svc_cmd, sizeof(svc_cmd), &ret_val, sizeof(ret_val)); if (ret) return ret; - return ret_val; + return le32_to_cpu(ret_val); } int __qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp) diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c new file mode 100644 index 000000000000..dd506cd3a5b8 --- /dev/null +++ b/drivers/firmware/raspberrypi.c @@ -0,0 +1,260 @@ +/* + * Defines interfaces for interacting wtih the Raspberry Pi firmware's + * property channel. + * + * Copyright © 2015 Broadcom + * + * 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 <linux/dma-mapping.h> +#include <linux/mailbox_client.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <soc/bcm2835/raspberrypi-firmware.h> + +#define MBOX_MSG(chan, data28) (((data28) & ~0xf) | ((chan) & 0xf)) +#define MBOX_CHAN(msg) ((msg) & 0xf) +#define MBOX_DATA28(msg) ((msg) & ~0xf) +#define MBOX_CHAN_PROPERTY 8 + +struct rpi_firmware { + struct mbox_client cl; + struct mbox_chan *chan; /* The property channel. */ + struct completion c; + u32 enabled; +}; + +static DEFINE_MUTEX(transaction_lock); + +static void response_callback(struct mbox_client *cl, void *msg) +{ + struct rpi_firmware *fw = container_of(cl, struct rpi_firmware, cl); + complete(&fw->c); +} + +/* + * Sends a request to the firmware through the BCM2835 mailbox driver, + * and synchronously waits for the reply. + */ +static int +rpi_firmware_transaction(struct rpi_firmware *fw, u32 chan, u32 data) +{ + u32 message = MBOX_MSG(chan, data); + int ret; + + WARN_ON(data & 0xf); + + mutex_lock(&transaction_lock); + reinit_completion(&fw->c); + ret = mbox_send_message(fw->chan, &message); + if (ret >= 0) { + wait_for_completion(&fw->c); + ret = 0; + } else { + dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret); + } + mutex_unlock(&transaction_lock); + + return ret; +} + +/** + * rpi_firmware_property_list - Submit firmware property list + * @fw: Pointer to firmware structure from rpi_firmware_get(). + * @data: Buffer holding tags. + * @tag_size: Size of tags buffer. + * + * Submits a set of concatenated tags to the VPU firmware through the + * mailbox property interface. + * + * The buffer header and the ending tag are added by this function and + * don't need to be supplied, just the actual tags for your operation. + * See struct rpi_firmware_property_tag_header for the per-tag + * structure. + */ +int rpi_firmware_property_list(struct rpi_firmware *fw, + void *data, size_t tag_size) +{ + size_t size = tag_size + 12; + u32 *buf; + dma_addr_t bus_addr; + int ret; + + /* Packets are processed a dword at a time. */ + if (size & 3) + return -EINVAL; + + buf = dma_alloc_coherent(fw->cl.dev, PAGE_ALIGN(size), &bus_addr, + GFP_ATOMIC); + if (!buf) + return -ENOMEM; + + /* The firmware will error out without parsing in this case. */ + WARN_ON(size >= 1024 * 1024); + + buf[0] = size; + buf[1] = RPI_FIRMWARE_STATUS_REQUEST; + memcpy(&buf[2], data, tag_size); + buf[size / 4 - 1] = RPI_FIRMWARE_PROPERTY_END; + wmb(); + + ret = rpi_firmware_transaction(fw, MBOX_CHAN_PROPERTY, bus_addr); + + rmb(); + memcpy(data, &buf[2], tag_size); + if (ret == 0 && buf[1] != RPI_FIRMWARE_STATUS_SUCCESS) { + /* + * The tag name here might not be the one causing the + * error, if there were multiple tags in the request. + * But single-tag is the most common, so go with it. + */ + dev_err(fw->cl.dev, "Request 0x%08x returned status 0x%08x\n", + buf[2], buf[1]); + ret = -EINVAL; + } + + dma_free_coherent(fw->cl.dev, PAGE_ALIGN(size), buf, bus_addr); + + return ret; +} +EXPORT_SYMBOL_GPL(rpi_firmware_property_list); + +/** + * rpi_firmware_property - Submit single firmware property + * @fw: Pointer to firmware structure from rpi_firmware_get(). + * @tag: One of enum_mbox_property_tag. + * @tag_data: Tag data buffer. + * @buf_size: Buffer size. + * + * Submits a single tag to the VPU firmware through the mailbox + * property interface. + * + * This is a convenience wrapper around + * rpi_firmware_property_list() to avoid some of the + * boilerplate in property calls. + */ +int rpi_firmware_property(struct rpi_firmware *fw, + u32 tag, void *tag_data, size_t buf_size) +{ + /* Single tags are very small (generally 8 bytes), so the + * stack should be safe. + */ + u8 data[buf_size + sizeof(struct rpi_firmware_property_tag_header)]; + struct rpi_firmware_property_tag_header *header = + (struct rpi_firmware_property_tag_header *)data; + int ret; + + header->tag = tag; + header->buf_size = buf_size; + header->req_resp_size = 0; + memcpy(data + sizeof(struct rpi_firmware_property_tag_header), + tag_data, buf_size); + + ret = rpi_firmware_property_list(fw, &data, sizeof(data)); + memcpy(tag_data, + data + sizeof(struct rpi_firmware_property_tag_header), + buf_size); + + return ret; +} +EXPORT_SYMBOL_GPL(rpi_firmware_property); + +static void +rpi_firmware_print_firmware_revision(struct rpi_firmware *fw) +{ + u32 packet; + int ret = rpi_firmware_property(fw, + RPI_FIRMWARE_GET_FIRMWARE_REVISION, + &packet, sizeof(packet)); + + if (ret == 0) { + struct tm tm; + + time_to_tm(packet, 0, &tm); + + dev_info(fw->cl.dev, + "Attached to firmware from %04ld-%02d-%02d %02d:%02d\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min); + } +} + +static int rpi_firmware_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpi_firmware *fw; + + fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL); + if (!fw) + return -ENOMEM; + + fw->cl.dev = dev; + fw->cl.rx_callback = response_callback; + fw->cl.tx_block = true; + + fw->chan = mbox_request_channel(&fw->cl, 0); + if (IS_ERR(fw->chan)) { + int ret = PTR_ERR(fw->chan); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get mbox channel: %d\n", ret); + return ret; + } + + init_completion(&fw->c); + + platform_set_drvdata(pdev, fw); + + rpi_firmware_print_firmware_revision(fw); + + return 0; +} + +static int rpi_firmware_remove(struct platform_device *pdev) +{ + struct rpi_firmware *fw = platform_get_drvdata(pdev); + + mbox_free_channel(fw->chan); + + return 0; +} + +/** + * rpi_firmware_get - Get pointer to rpi_firmware structure. + * @firmware_node: Pointer to the firmware Device Tree node. + * + * Returns NULL is the firmware device is not ready. + */ +struct rpi_firmware *rpi_firmware_get(struct device_node *firmware_node) +{ + struct platform_device *pdev = of_find_device_by_node(firmware_node); + + if (!pdev) + return NULL; + + return platform_get_drvdata(pdev); +} +EXPORT_SYMBOL_GPL(rpi_firmware_get); + +static const struct of_device_id rpi_firmware_of_match[] = { + { .compatible = "raspberrypi,bcm2835-firmware", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rpi_firmware_of_match); + +static struct platform_driver rpi_firmware_driver = { + .driver = { + .name = "raspberrypi-firmware", + .of_match_table = rpi_firmware_of_match, + }, + .probe = rpi_firmware_probe, + .remove = rpi_firmware_remove, +}; +module_platform_driver(rpi_firmware_driver); + +MODULE_AUTHOR("Eric Anholt <eric@anholt.net>"); +MODULE_DESCRIPTION("Raspberry Pi firmware driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 796569eeaf1d..842b0043ad94 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -321,6 +321,14 @@ config SENSORS_APPLESMC Say Y here if you have an applicable laptop and want to experience the awesome power of applesmc. +config SENSORS_ARM_SCPI + tristate "ARM SCPI Sensors" + depends on ARM_SCPI_PROTOCOL + help + This driver provides support for temperature, voltage, current + and power sensors available on ARM Ltd's SCP based platforms. The + actual number and type of sensors exported depend on the platform. + config SENSORS_ASB100 tristate "Asus ASB100 Bach" depends on X86 && I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 01855ee641d1..12a32398fdcc 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o +obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o diff --git a/drivers/hwmon/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c new file mode 100644 index 000000000000..2c1241bbf9af --- /dev/null +++ b/drivers/hwmon/scpi-hwmon.c @@ -0,0 +1,288 @@ +/* + * System Control and Power Interface(SCPI) based hwmon sensor driver + * + * Copyright (C) 2015 ARM Ltd. + * Punit Agrawal <punit.agrawal@arm.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 "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. + */ + +#include <linux/hwmon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/scpi_protocol.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/thermal.h> + +struct sensor_data { + struct scpi_sensor_info info; + struct device_attribute dev_attr_input; + struct device_attribute dev_attr_label; + char input[20]; + char label[20]; +}; + +struct scpi_thermal_zone { + struct list_head list; + int sensor_id; + struct scpi_sensors *scpi_sensors; + struct thermal_zone_device *tzd; +}; + +struct scpi_sensors { + struct scpi_ops *scpi_ops; + struct sensor_data *data; + struct list_head thermal_zones; + struct attribute **attrs; + struct attribute_group group; + const struct attribute_group *groups[2]; +}; + +static int scpi_read_temp(void *dev, int *temp) +{ + struct scpi_thermal_zone *zone = dev; + struct scpi_sensors *scpi_sensors = zone->scpi_sensors; + struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; + struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id]; + u32 value; + int ret; + + ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); + if (ret) + return ret; + + *temp = value; + return 0; +} + +/* hwmon callback functions */ +static ssize_t +scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev); + struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; + struct sensor_data *sensor; + u32 value; + int ret; + + sensor = container_of(attr, struct sensor_data, dev_attr_input); + + ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); + if (ret) + return ret; + + return sprintf(buf, "%u\n", value); +} + +static ssize_t +scpi_show_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_data *sensor; + + sensor = container_of(attr, struct sensor_data, dev_attr_label); + + return sprintf(buf, "%s\n", sensor->info.name); +} + +static void +unregister_thermal_zones(struct platform_device *pdev, + struct scpi_sensors *scpi_sensors) +{ + struct list_head *pos; + + list_for_each(pos, &scpi_sensors->thermal_zones) { + struct scpi_thermal_zone *zone; + + zone = list_entry(pos, struct scpi_thermal_zone, list); + thermal_zone_of_sensor_unregister(&pdev->dev, zone->tzd); + } +} + +static struct thermal_zone_of_device_ops scpi_sensor_ops = { + .get_temp = scpi_read_temp, +}; + +static int scpi_hwmon_probe(struct platform_device *pdev) +{ + u16 nr_sensors, i; + int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0; + struct scpi_ops *scpi_ops; + struct device *hwdev, *dev = &pdev->dev; + struct scpi_sensors *scpi_sensors; + int ret; + + scpi_ops = get_scpi_ops(); + if (!scpi_ops) + return -EPROBE_DEFER; + + ret = scpi_ops->sensor_get_capability(&nr_sensors); + if (ret) + return ret; + + if (!nr_sensors) + return -ENODEV; + + scpi_sensors = devm_kzalloc(dev, sizeof(*scpi_sensors), GFP_KERNEL); + if (!scpi_sensors) + return -ENOMEM; + + scpi_sensors->data = devm_kcalloc(dev, nr_sensors, + sizeof(*scpi_sensors->data), GFP_KERNEL); + if (!scpi_sensors->data) + return -ENOMEM; + + scpi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1, + sizeof(*scpi_sensors->attrs), GFP_KERNEL); + if (!scpi_sensors->attrs) + return -ENOMEM; + + scpi_sensors->scpi_ops = scpi_ops; + + for (i = 0; i < nr_sensors; i++) { + struct sensor_data *sensor = &scpi_sensors->data[i]; + + ret = scpi_ops->sensor_get_info(i, &sensor->info); + if (ret) + return ret; + + switch (sensor->info.class) { + case TEMPERATURE: + snprintf(sensor->input, sizeof(sensor->input), + "temp%d_input", num_temp + 1); + snprintf(sensor->label, sizeof(sensor->input), + "temp%d_label", num_temp + 1); + num_temp++; + break; + case VOLTAGE: + snprintf(sensor->input, sizeof(sensor->input), + "in%d_input", num_volt); + snprintf(sensor->label, sizeof(sensor->input), + "in%d_label", num_volt); + num_volt++; + break; + case CURRENT: + snprintf(sensor->input, sizeof(sensor->input), + "curr%d_input", num_current + 1); + snprintf(sensor->label, sizeof(sensor->input), + "curr%d_label", num_current + 1); + num_current++; + break; + case POWER: + snprintf(sensor->input, sizeof(sensor->input), + "power%d_input", num_power + 1); + snprintf(sensor->label, sizeof(sensor->input), + "power%d_label", num_power + 1); + num_power++; + break; + default: + break; + } + + sensor->dev_attr_input.attr.mode = S_IRUGO; + sensor->dev_attr_input.show = scpi_show_sensor; + sensor->dev_attr_input.attr.name = sensor->input; + + sensor->dev_attr_label.attr.mode = S_IRUGO; + sensor->dev_attr_label.show = scpi_show_label; + sensor->dev_attr_label.attr.name = sensor->label; + + scpi_sensors->attrs[i << 1] = &sensor->dev_attr_input.attr; + scpi_sensors->attrs[(i << 1) + 1] = &sensor->dev_attr_label.attr; + + sysfs_attr_init(scpi_sensors->attrs[i << 1]); + sysfs_attr_init(scpi_sensors->attrs[(i << 1) + 1]); + } + + scpi_sensors->group.attrs = scpi_sensors->attrs; + scpi_sensors->groups[0] = &scpi_sensors->group; + + platform_set_drvdata(pdev, scpi_sensors); + + hwdev = devm_hwmon_device_register_with_groups(dev, + "scpi_sensors", scpi_sensors, scpi_sensors->groups); + + if (IS_ERR(hwdev)) + return PTR_ERR(hwdev); + + /* + * Register the temperature sensors with the thermal framework + * to allow their usage in setting up the thermal zones from + * device tree. + * + * NOTE: Not all temperature sensors maybe used for thermal + * control + */ + INIT_LIST_HEAD(&scpi_sensors->thermal_zones); + for (i = 0; i < nr_sensors; i++) { + struct sensor_data *sensor = &scpi_sensors->data[i]; + struct scpi_thermal_zone *zone; + + if (sensor->info.class != TEMPERATURE) + continue; + + zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL); + if (!zone) { + ret = -ENOMEM; + goto unregister_tzd; + } + + zone->sensor_id = i; + zone->scpi_sensors = scpi_sensors; + zone->tzd = thermal_zone_of_sensor_register(dev, i, zone, + &scpi_sensor_ops); + /* + * The call to thermal_zone_of_sensor_register returns + * an error for sensors that are not associated with + * any thermal zones or if the thermal subsystem is + * not configured. + */ + if (IS_ERR(zone->tzd)) { + devm_kfree(dev, zone); + continue; + } + list_add(&zone->list, &scpi_sensors->thermal_zones); + } + + return 0; + +unregister_tzd: + unregister_thermal_zones(pdev, scpi_sensors); + return ret; +} + +static int scpi_hwmon_remove(struct platform_device *pdev) +{ + struct scpi_sensors *scpi_sensors = platform_get_drvdata(pdev); + + unregister_thermal_zones(pdev, scpi_sensors); + + return 0; +} + +static const struct of_device_id scpi_of_match[] = { + {.compatible = "arm,scpi-sensors"}, + {}, +}; + +static struct platform_driver scpi_hwmon_platdrv = { + .driver = { + .name = "scpi-hwmon", + .owner = THIS_MODULE, + .of_match_table = scpi_of_match, + }, + .probe = scpi_hwmon_probe, + .remove = scpi_hwmon_remove, +}; +module_platform_driver(scpi_hwmon_platdrv); + +MODULE_AUTHOR("Punit Agrawal <punit.agrawal@arm.com>"); +MODULE_DESCRIPTION("ARM SCPI HWMON interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/pl172.c b/drivers/memory/pl172.c index b2ef6072fbf4..ff57195b4e37 100644 --- a/drivers/memory/pl172.c +++ b/drivers/memory/pl172.c @@ -118,7 +118,8 @@ static int pl172_setup_static(struct amba_device *adev, if (of_property_read_bool(np, "mpmc,extended-wait")) cfg |= MPMC_STATIC_CFG_EW; - if (of_property_read_bool(np, "mpmc,buffer-enable")) + if (amba_part(adev) == 0x172 && + of_property_read_bool(np, "mpmc,buffer-enable")) cfg |= MPMC_STATIC_CFG_B; if (of_property_read_bool(np, "mpmc,write-protect")) @@ -190,6 +191,8 @@ static int pl172_parse_cs_config(struct amba_device *adev, } static const char * const pl172_revisions[] = {"r1", "r2", "r2p3", "r2p4"}; +static const char * const pl175_revisions[] = {"r1"}; +static const char * const pl176_revisions[] = {"r0"}; static int pl172_probe(struct amba_device *adev, const struct amba_id *id) { @@ -202,6 +205,12 @@ static int pl172_probe(struct amba_device *adev, const struct amba_id *id) if (amba_part(adev) == 0x172) { if (amba_rev(adev) < ARRAY_SIZE(pl172_revisions)) rev = pl172_revisions[amba_rev(adev)]; + } else if (amba_part(adev) == 0x175) { + if (amba_rev(adev) < ARRAY_SIZE(pl175_revisions)) + rev = pl175_revisions[amba_rev(adev)]; + } else if (amba_part(adev) == 0x176) { + if (amba_rev(adev) < ARRAY_SIZE(pl176_revisions)) + rev = pl176_revisions[amba_rev(adev)]; } dev_info(dev, "ARM PL%x revision %s\n", amba_part(adev), rev); @@ -278,9 +287,20 @@ static int pl172_remove(struct amba_device *adev) } static const struct amba_id pl172_ids[] = { + /* PrimeCell MPMC PL172, EMC found on NXP LPC18xx and LPC43xx */ { - .id = 0x07341172, - .mask = 0xffffffff, + .id = 0x07041172, + .mask = 0x3f0fffff, + }, + /* PrimeCell MPMC PL175, EMC found on NXP LPC32xx */ + { + .id = 0x07041175, + .mask = 0x3f0fffff, + }, + /* PrimeCell MPMC PL176 */ + { + .id = 0x89041176, + .mask = 0xff0fffff, }, { 0, 0 }, }; diff --git a/drivers/misc/atmel_tclib.c b/drivers/misc/atmel_tclib.c index 0ca05c3ec8d6..ac24a4bd63f7 100644 --- a/drivers/misc/atmel_tclib.c +++ b/drivers/misc/atmel_tclib.c @@ -125,6 +125,10 @@ static int __init tc_probe(struct platform_device *pdev) if (IS_ERR(clk)) return PTR_ERR(clk); + tc->slow_clk = devm_clk_get(&pdev->dev, "slow_clk"); + if (IS_ERR(tc->slow_clk)) + return PTR_ERR(tc->slow_clk); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); tc->regs = devm_ioremap_resource(&pdev->dev, r); if (IS_ERR(tc->regs)) diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index 6da01b3bf6f4..75db585a2a94 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c @@ -305,7 +305,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, */ if (i == 5) { i = slowclk; - rate = 32768; + rate = clk_get_rate(tc->slow_clk); min = div_u64(NSEC_PER_SEC, rate); max = min << tc->tcb_config->counter_width; @@ -387,9 +387,9 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) tcbpwm = devm_kzalloc(&pdev->dev, sizeof(*tcbpwm), GFP_KERNEL); if (tcbpwm == NULL) { - atmel_tc_free(tc); + err = -ENOMEM; dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; + goto err_free_tc; } tcbpwm->chip.dev = &pdev->dev; @@ -400,17 +400,27 @@ static int atmel_tcb_pwm_probe(struct platform_device *pdev) tcbpwm->chip.npwm = NPWM; tcbpwm->tc = tc; + err = clk_prepare_enable(tc->slow_clk); + if (err) + goto err_free_tc; + spin_lock_init(&tcbpwm->lock); err = pwmchip_add(&tcbpwm->chip); - if (err < 0) { - atmel_tc_free(tc); - return err; - } + if (err < 0) + goto err_disable_clk; platform_set_drvdata(pdev, tcbpwm); return 0; + +err_disable_clk: + clk_disable_unprepare(tcbpwm->tc->slow_clk); + +err_free_tc: + atmel_tc_free(tc); + + return err; } static int atmel_tcb_pwm_remove(struct platform_device *pdev) @@ -418,6 +428,8 @@ static int atmel_tcb_pwm_remove(struct platform_device *pdev) struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev); int err; + clk_disable_unprepare(tcbpwm->tc->slow_clk); + err = pwmchip_remove(&tcbpwm->chip); if (err < 0) return err; diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index c9c0fcce98a7..4e853ed2c82b 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -3,6 +3,7 @@ menu "SOC (System On Chip) specific Drivers" source "drivers/soc/brcmstb/Kconfig" 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 4e27f10367f0..f2ba2e932ae1 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/ 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/qcom/Kconfig b/drivers/soc/qcom/Kconfig index ba47b70f4d85..eec76141d9b9 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -19,6 +19,15 @@ 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 + 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 + processors in a Qualcomm platform. + config QCOM_SMD tristate "Qualcomm Shared Memory Driver (SMD)" depends on QCOM_SMEM @@ -40,11 +49,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. diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index 1392ccf14a20..2969321e1b09 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -17,6 +17,7 @@ #include <linux/of_platform.h> #include <linux/io.h> #include <linux/interrupt.h> +#include <linux/slab.h> #include <linux/soc/qcom/smd.h> #include <linux/soc/qcom/smd-rpm.h> @@ -44,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; }; /** @@ -57,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; }; /** @@ -74,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]; }; }; @@ -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 = 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; - memcpy(pkt.payload, buf, count); + pkt->req.msg_id = cpu_to_le32(msg_id++); + 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); + 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; @@ -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; } @@ -148,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; @@ -179,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; diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c index a6155c917d52..86b598cff91a 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 @@ -85,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, }, }; @@ -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; @@ -215,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; @@ -224,46 +221,104 @@ struct smd_channel_info { u8 fTAIL; u8 fSTATE; u8 fBLOCKREADINTR; - u32 tail; - u32 head; + __le32 tail; + __le32 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. */ 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; }; -#define GET_RX_CHANNEL_INFO(channel, param) \ - (channel->rx_info_word ? \ - channel->rx_info_word->param : \ - channel->rx_info->param) - -#define SET_RX_CHANNEL_INFO(channel, param, value) \ - (channel->rx_info_word ? \ - (channel->rx_info_word->param = value) : \ - (channel->rx_info->param = value)) - -#define GET_TX_CHANNEL_INFO(channel, param) \ - (channel->tx_info_word ? \ - channel->tx_info_word->param : \ - channel->tx_info->param) +struct smd_channel_info_word_pair { + struct smd_channel_info_word tx; + struct smd_channel_info_word rx; +}; -#define SET_TX_CHANNEL_INFO(channel, param, value) \ - (channel->tx_info_word ? \ - (channel->tx_info_word->param = value) : \ - (channel->tx_info->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 @@ -274,9 +329,9 @@ struct smd_channel_info_word { */ 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 @@ -305,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); @@ -350,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); @@ -364,20 +419,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); } } @@ -395,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); } @@ -412,7 +462,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); @@ -491,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; @@ -502,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 */ @@ -513,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 (;;) { @@ -522,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) @@ -533,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(); @@ -627,7 +677,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); @@ -665,12 +715,16 @@ 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; /* 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; + + /* Reject packets that are too big */ + if (tlen >= channel->fifo_size) return -EINVAL; ret = mutex_lock_interruptible(&channel->tx_lock); @@ -683,7 +737,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 || @@ -691,15 +745,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(); @@ -727,6 +781,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); } @@ -854,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; @@ -880,19 +945,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; @@ -978,21 +1041,20 @@ 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 * 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); @@ -1000,10 +1062,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; @@ -1040,20 +1103,19 @@ 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; + u32 eflags, cid; 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++) { entry = &alloc_tbl[i]; + eflags = le32_to_cpu(entry->flags); if (test_bit(i, edge->allocated[tbl])) continue; @@ -1063,14 +1125,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)) @@ -1227,11 +1290,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 52365188a1c2..19019aa092e8 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; } @@ -378,10 +418,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,100 +429,94 @@ 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 = le32_to_cpu(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 = le32_to_cpu(entry->size); + return area->virt_base + le32_to_cpu(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; - 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 -EINVAL; + return ERR_PTR(-EINVAL); } - if (hdr->item == item) { - if (ptr != NULL) - *ptr = p + sizeof(*hdr) + hdr->padding_hdr; - + 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 0; + return entry_to_item(e); } - p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; + e = private_entry_next(e); } - 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); @@ -506,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; @@ -518,13 +552,11 @@ 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; - 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; } @@ -534,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, @@ -544,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, @@ -588,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; @@ -614,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; @@ -626,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); @@ -666,39 +714,17 @@ 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 (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; } @@ -730,8 +756,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; } diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig new file mode 100644 index 000000000000..7140ff825598 --- /dev/null +++ b/drivers/soc/rockchip/Kconfig @@ -0,0 +1,18 @@ +if ARCH_ROCKCHIP || COMPILE_TEST + +# +# 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. + +endif 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..534c58937a56 --- /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 <linux/io.h> +#include <linux/err.h> +#include <linux/pm_clock.h> +#include <linux/pm_domain.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <dt-bindings/power/rk3288-power.h> + +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 at index %d: %d\n", + node->name, 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); |