diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2015-11-10 15:00:03 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-11-10 15:00:03 -0800 |
commit | b44a3d2a85c64208a57362a1728efb58a6556cd6 (patch) | |
tree | 293302b3ac918eb75b442fa035eb976850163b1d /drivers | |
parent | 56e0464980febfa50432a070261579415c72664e (diff) | |
parent | d13a5c8c4c3dbe299659bcff805f79a2c83e2bbc (diff) | |
download | linux-b44a3d2a85c64208a57362a1728efb58a6556cd6.tar.bz2 |
Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC driver updates from Olof Johansson:
"As we've enabled multiplatform kernels on ARM, and greatly done away
with the contents under arch/arm/mach-*, there's still need for
SoC-related drivers to go somewhere.
Many of them go in through other driver trees, but we still have
drivers/soc to hold some of the "doesn't fit anywhere" lowlevel code
that might be shared between ARM and ARM64 (or just in general makes
sense to not have under the architecture directory).
This branch contains mostly such code:
- Drivers for qualcomm SoCs for SMEM, SMD and SMD-RPM, used to
communicate with power management blocks on these SoCs for use by
clock, regulator and bus frequency drivers.
- Allwinner Reduced Serial Bus driver, again used to communicate with
PMICs.
- Drivers for ARM's SCPI (System Control Processor). Not to be
confused with PSCI (Power State Coordination Interface). SCPI is
used to communicate with the assistant embedded cores doing power
management, and we have yet to see how many of them will implement
this for their hardware vs abstracting in other ways (or not at all
like in the past).
- To make confusion between SCPI and PSCI more likely, this release
also includes an update of PSCI to interface version 1.0.
- Rockchip support for power domains.
- A driver to talk to the firmware on Raspberry Pi"
* tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (57 commits)
soc: qcom: smd-rpm: Correct size of outgoing message
bus: sunxi-rsb: Add driver for Allwinner Reduced Serial Bus
bus: sunxi-rsb: Add Allwinner Reduced Serial Bus (RSB) controller bindings
ARM: bcm2835: add mutual inclusion protection
drivers: psci: make PSCI 1.0 functions initialization version dependent
dt-bindings: Correct paths in Rockchip power domains binding document
soc: rockchip: power-domain: don't try to print the clock name in error case
soc: qcom/smem: add HWSPINLOCK dependency
clk: berlin: add cpuclk
ARM: berlin: dts: add CLKID_CPU for BG2Q
ARM: bcm2835: Add the Raspberry Pi firmware driver
soc: qcom: smem: Move RPM message ram out of smem DT node
soc: qcom: smd-rpm: Correct the active vs sleep state flagging
soc: qcom: smd: delete unneeded of_node_put
firmware: qcom-scm: build for correct architecture level
soc: qcom: smd: Correct SMEM items for upper channels
qcom-scm: add missing prototype for qcom_scm_is_available()
qcom-scm: fix endianess issue in __qcom_scm_is_call_available
soc: qcom: smd: Reject send of too big packets
soc: qcom: smd: Handle big endian CPUs
...
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); |