Commit 9638685e authored by Linus Torvalds's avatar Linus Torvalds

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:
 "Driver updates for ARM SoCs.  Some for SoC-family code under
  drivers/soc, but also some other driver updates that don't belong
  anywhere else.  We also bring in the drivers/reset code through
  arm-soc.

  Some of the larger updates:

   - Qualcomm support for SMEM, SMSM, SMP2P.  All used to communicate
     with other parts of the chip/board on these platforms, all
     proprietary protocols that don't fit into other subsystems and live
     in drivers/soc for now.

   - System bus driver for UniPhier

   - Driver for the TI Wakeup M3 IPC device

   - Power management for Raspberry PI

  + Again a bunch of other smaller updates and patches"

* tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (38 commits)
  bus: uniphier: allow only built-in driver
  ARM: bcm2835: clarify RASPBERRYPI_FIRMWARE dependency
  MAINTAINERS: Drop Kumar Gala from QCOM
  bus: uniphier-system-bus: add UniPhier System Bus driver
  ARM: bcm2835: add rpi power domain driver
  dt-bindings: add rpi power domain driver bindings
  ARM: bcm2835: Define two new packets from the latest firmware.
  drivers/soc: make mediatek/mtk-scpsys.c explicitly non-modular
  soc: mediatek: SCPSYS: Add regulator support
  MAINTAINERS: Change QCOM entries
  soc: qcom: smd-rpm: Add existing platform support
  memory/tegra: Add number of TLB lines for Tegra124
  reset: hi6220: fix modular build
  soc: qcom: Introduce WCNSS_CTRL SMD client
  ARM: qcom: select ARM_CPU_SUSPEND for power management
  MAINTAINERS: Add rules for Qualcomm dts files
  soc: qcom: enable smsm/smp2p modular build
  serial: msm_serial: Make config tristate
  soc: qcom: smp2p: Qualcomm Shared Memory Point to Point
  soc: qcom: smsm: Add driver for Qualcomm SMSM
  ...
parents 03d7d124 ce96cb73
UniPhier System Bus
The UniPhier System Bus is an external bus that connects on-board devices to
the UniPhier SoC. It is a simple (semi-)parallel bus with address, data, and
some control signals. It supports up to 8 banks (chip selects).
Before any access to the bus, the bus controller must be configured; the bus
controller registers provide the control for the translation from the offset
within each bank to the CPU-viewed address. The needed setup includes the base
address, the size of each bank. Optionally, some timing parameters can be
optimized for faster bus access.
Required properties:
- compatible: should be "socionext,uniphier-system-bus".
- reg: offset and length of the register set for the bus controller device.
- #address-cells: should be 2. The first cell is the bank number (chip select).
The second cell is the address offset within the bank.
- #size-cells: should be 1.
- ranges: should provide a proper address translation from the System Bus to
the parent bus.
Note:
The address region(s) that can be assigned for the System Bus is implementation
defined. Some SoCs can use 0x00000000-0x0fffffff and 0x40000000-0x4fffffff,
while other SoCs can only use 0x40000000-0x4fffffff. There might be additional
limitations depending on SoCs and the boot mode. The address translation is
arbitrary as long as the banks are assigned in the supported address space with
the required alignment and they do not overlap one another.
For example, it is possible to map:
bank 0 to 0x42000000-0x43ffffff, bank 5 to 0x46000000-0x46ffffff
It is also possible to map:
bank 0 to 0x48000000-0x49ffffff, bank 5 to 0x44000000-0x44ffffff
There is no reason to stick to a particular translation mapping, but the
"ranges" property should provide a "reasonable" default that is known to work.
The software should initialize the bus controller according to it.
Example:
system-bus {
compatible = "socionext,uniphier-system-bus";
reg = <0x58c00000 0x400>;
#address-cells = <2>;
#size-cells = <1>;
ranges = <1 0x00000000 0x42000000 0x02000000
5 0x00000000 0x46000000 0x01000000>;
ethernet@1,01f00000 {
compatible = "smsc,lan9115";
reg = <1 0x01f00000 0x1000>;
interrupts = <0 48 4>
phy-mode = "mii";
};
uart@5,00200000 {
compatible = "ns16550a";
reg = <5 0x00200000 0x20>;
interrupts = <0 49 4>
clock-frequency = <12288000>;
};
};
In this example,
- the Ethernet device is connected at the offset 0x01f00000 of CS1 and
mapped to 0x43f00000 of the parent bus.
- the UART device is connected at the offset 0x00200000 of CS5 and
mapped to 0x46200000 of the parent bus.
Hisilicon System Reset Controller
======================================
Please also refer to reset.txt in this directory for common reset
controller binding usage.
The reset controller registers are part of the system-ctl block on
hi6220 SoC.
Required properties:
- compatible: may be "hisilicon,hi6220-sysctrl"
- reg: should be register base and length as documented in the
datasheet
- #reset-cells: 1, see below
Example:
sys_ctrl: sys_ctrl@f7030000 {
compatible = "hisilicon,hi6220-sysctrl", "syscon";
reg = <0x0 0xf7030000 0x0 0x2000>;
#clock-cells = <1>;
#reset-cells = <1>;
};
Specifying reset lines connected to IP modules
==============================================
example:
uart1: serial@..... {
...
resets = <&sys_ctrl PERIPH_RSTEN3_UART1>;
...
};
The index could be found in <dt-bindings/reset/hisi,hi6220-resets.h>.
Raspberry Pi power domain driver
Required properties:
- compatible: Should be "raspberrypi,bcm2835-power".
- firmware: Reference to the RPi firmware device node.
- #power-domain-cells: Should be <1>, we providing multiple power domains.
The valid defines for power domain are:
RPI_POWER_DOMAIN_I2C0
RPI_POWER_DOMAIN_I2C1
RPI_POWER_DOMAIN_I2C2
RPI_POWER_DOMAIN_VIDEO_SCALER
RPI_POWER_DOMAIN_VPU1
RPI_POWER_DOMAIN_HDMI
RPI_POWER_DOMAIN_USB
RPI_POWER_DOMAIN_VEC
RPI_POWER_DOMAIN_JPEG
RPI_POWER_DOMAIN_H264
RPI_POWER_DOMAIN_V3D
RPI_POWER_DOMAIN_ISP
RPI_POWER_DOMAIN_UNICAM0
RPI_POWER_DOMAIN_UNICAM1
RPI_POWER_DOMAIN_CCP2RX
RPI_POWER_DOMAIN_CSI2
RPI_POWER_DOMAIN_CPI
RPI_POWER_DOMAIN_DSI0
RPI_POWER_DOMAIN_DSI1
RPI_POWER_DOMAIN_TRANSPOSER
RPI_POWER_DOMAIN_CCP2TX
RPI_POWER_DOMAIN_CDP
RPI_POWER_DOMAIN_ARM
Example:
power: power {
compatible = "raspberrypi,bcm2835-power";
firmware = <&firmware>;
#power-domain-cells = <1>;
};
Example for using power domain:
&usb {
power-domains = <&power RPI_POWER_DOMAIN_USB>;
};
Wakeup M3 IPC Driver
=====================
The TI AM33xx and AM43xx family of devices use a small Cortex M3 co-processor
(commonly referred to as Wakeup M3 or CM3) to help with various low power tasks
that cannot be controlled from the MPU, like suspend/resume and certain deep
C-states for CPU Idle. Once the wkup_m3_ipc driver uses the wkup_m3_rproc driver
to boot the wkup_m3, it handles communication with the CM3 using IPC registers
present in the SoC's control module and a mailbox. The wkup_m3_ipc exposes an
API to allow the SoC PM code to execute specific PM tasks.
Wkup M3 Device Node:
====================
A wkup_m3_ipc device node is used to represent the IPC registers within an
SoC.
Required properties:
--------------------
- compatible: Should be,
"ti,am3352-wkup-m3-ipc" for AM33xx SoCs
"ti,am4372-wkup-m3-ipc" for AM43xx SoCs
- reg: Contains the IPC register address space to communicate
with the Wakeup M3 processor
- interrupts: Contains the interrupt information for the wkup_m3
interrupt that signals the MPU.
- ti,rproc: phandle to the wkup_m3 rproc node so the IPC driver
can boot it.
- mboxes: phandles used by IPC framework to get correct mbox
channel for communication. Must point to appropriate
mbox_wkupm3 child node.
Example:
--------
/* AM33xx */
l4_wkup: l4_wkup@44c00000 {
...
scm: scm@210000 {
compatible = "ti,am3-scm", "simple-bus";
reg = <0x210000 0x2000>;
#address-cells = <1>;
#size-cells = <1>;
ranges = <0 0x210000 0x2000>;
...
wkup_m3_ipc: wkup_m3_ipc@1324 {
compatible = "ti,am3352-wkup-m3-ipc";
reg = <0x1324 0x24>;
interrupts = <78>;
ti,rproc = <&wkup_m3>;
mboxes = <&mailbox &mbox_wkupm3>;
};
...
};
};
......@@ -1415,12 +1415,13 @@ W: http://www.arm.linux.org.uk/
S: Maintained
ARM/QUALCOMM SUPPORT
M: Kumar Gala <galak@codeaurora.org>
M: Andy Gross <agross@codeaurora.org>
M: David Brown <davidb@codeaurora.org>
M: Andy Gross <andy.gross@linaro.org>
M: David Brown <david.brown@linaro.org>
L: linux-arm-msm@vger.kernel.org
L: linux-soc@vger.kernel.org
S: Maintained
F: arch/arm/boot/dts/qcom-*.dts
F: arch/arm/boot/dts/qcom-*.dtsi
F: arch/arm/mach-qcom/
F: drivers/soc/qcom/
F: drivers/tty/serial/msm_serial.h
......@@ -1428,7 +1429,7 @@ F: drivers/tty/serial/msm_serial.c
F: drivers/*/pm8???-*
F: drivers/mfd/ssbi.c
F: drivers/firmware/qcom_scm.c
T: git git://git.kernel.org/pub/scm/linux/kernel/git/galak/linux-qcom.git
T: git git://git.kernel.org/pub/scm/linux/kernel/git/agross/linux.git
ARM/RADISYS ENP2611 MACHINE SUPPORT
M: Lennert Buytenhek <kernel@wantstofly.org>
......@@ -1673,6 +1674,7 @@ F: arch/arm/include/asm/hardware/cache-uniphier.h
F: arch/arm/mach-uniphier/
F: arch/arm/mm/cache-uniphier.c
F: arch/arm64/boot/dts/socionext/
F: drivers/bus/uniphier-system-bus.c
F: drivers/i2c/busses/i2c-uniphier*
F: drivers/pinctrl/uniphier/
F: drivers/tty/serial/8250/8250_uniphier.c
......
......@@ -147,6 +147,7 @@ sys_ctrl: sys_ctrl@f7030000 {
compatible = "hisilicon,hi6220-sysctrl", "syscon";
reg = <0x0 0xf7030000 0x0 0x2000>;
#clock-cells = <1>;
#reset-cells = <1>;
};
media_ctrl: media_ctrl@f4410000 {
......
......@@ -131,6 +131,14 @@ config SUNXI_RSB
with various RSB based devices, such as AXP223, AXP8XX PMICs,
and AC100/AC200 ICs.
config UNIPHIER_SYSTEM_BUS
bool "UniPhier System Bus driver"
depends on ARCH_UNIPHIER && OF
default y
help
Support for UniPhier System Bus, a simple external bus. This is
needed to use on-board devices connected to UniPhier SoCs.
config VEXPRESS_CONFIG
bool "Versatile Express configuration bus"
default y if ARCH_VEXPRESS
......
......@@ -17,4 +17,5 @@ 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_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o
obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o
/*
* Copyright (C) 2015 Masahiro Yamada <yamada.masahiro@socionext.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/io.h>
#include <linux/log2.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
/* System Bus Controller registers */
#define UNIPHIER_SBC_BASE 0x100 /* base address of bank0 space */
#define UNIPHIER_SBC_BASE_BE BIT(0) /* bank_enable */
#define UNIPHIER_SBC_CTRL0 0x200 /* timing parameter 0 of bank0 */
#define UNIPHIER_SBC_CTRL1 0x204 /* timing parameter 1 of bank0 */
#define UNIPHIER_SBC_CTRL2 0x208 /* timing parameter 2 of bank0 */
#define UNIPHIER_SBC_CTRL3 0x20c /* timing parameter 3 of bank0 */
#define UNIPHIER_SBC_CTRL4 0x300 /* timing parameter 4 of bank0 */
#define UNIPHIER_SBC_STRIDE 0x10 /* register stride to next bank */
#define UNIPHIER_SBC_NR_BANKS 8 /* number of banks (chip select) */
#define UNIPHIER_SBC_BASE_DUMMY 0xffffffff /* data to squash bank 0, 1 */
struct uniphier_system_bus_bank {
u32 base;
u32 end;
};
struct uniphier_system_bus_priv {
struct device *dev;
void __iomem *membase;
struct uniphier_system_bus_bank bank[UNIPHIER_SBC_NR_BANKS];
};
static int uniphier_system_bus_add_bank(struct uniphier_system_bus_priv *priv,
int bank, u32 addr, u64 paddr, u32 size)
{
u64 end, mask;
dev_dbg(priv->dev,
"range found: bank = %d, addr = %08x, paddr = %08llx, size = %08x\n",
bank, addr, paddr, size);
if (bank >= ARRAY_SIZE(priv->bank)) {
dev_err(priv->dev, "unsupported bank number %d\n", bank);
return -EINVAL;
}
if (priv->bank[bank].base || priv->bank[bank].end) {
dev_err(priv->dev,
"range for bank %d has already been specified\n", bank);
return -EINVAL;
}
if (paddr > U32_MAX) {
dev_err(priv->dev, "base address %llx is too high\n", paddr);
return -EINVAL;
}
end = paddr + size;
if (addr > paddr) {
dev_err(priv->dev,
"base %08x cannot be mapped to %08llx of parent\n",
addr, paddr);
return -EINVAL;
}
paddr -= addr;
paddr = round_down(paddr, 0x00020000);
end = round_up(end, 0x00020000);
if (end > U32_MAX) {
dev_err(priv->dev, "end address %08llx is too high\n", end);
return -EINVAL;
}
mask = paddr ^ (end - 1);
mask = roundup_pow_of_two(mask);
paddr = round_down(paddr, mask);
end = round_up(end, mask);
priv->bank[bank].base = paddr;
priv->bank[bank].end = end;
dev_dbg(priv->dev, "range added: bank = %d, addr = %08x, end = %08x\n",
bank, priv->bank[bank].base, priv->bank[bank].end);
return 0;
}
static int uniphier_system_bus_check_overlap(
const struct uniphier_system_bus_priv *priv)
{
int i, j;
for (i = 0; i < ARRAY_SIZE(priv->bank); i++) {
for (j = i + 1; j < ARRAY_SIZE(priv->bank); j++) {
if (priv->bank[i].end > priv->bank[j].base ||
priv->bank[i].base < priv->bank[j].end) {
dev_err(priv->dev,
"region overlap between bank%d and bank%d\n",
i, j);
return -EINVAL;
}
}
}
return 0;
}
static void uniphier_system_bus_check_boot_swap(
struct uniphier_system_bus_priv *priv)
{
void __iomem *base_reg = priv->membase + UNIPHIER_SBC_BASE;
int is_swapped;
is_swapped = !(readl(base_reg) & UNIPHIER_SBC_BASE_BE);
dev_dbg(priv->dev, "Boot Swap: %s\n", is_swapped ? "on" : "off");
/*
* If BOOT_SWAP was asserted on power-on-reset, the CS0 and CS1 are
* swapped. In this case, bank0 and bank1 should be swapped as well.
*/
if (is_swapped)
swap(priv->bank[0], priv->bank[1]);
}
static void uniphier_system_bus_set_reg(
const struct uniphier_system_bus_priv *priv)
{
void __iomem *base_reg = priv->membase + UNIPHIER_SBC_BASE;
u32 base, end, mask, val;
int i;
for (i = 0; i < ARRAY_SIZE(priv->bank); i++) {
base = priv->bank[i].base;
end = priv->bank[i].end;
if (base == end) {
/*
* If SBC_BASE0 or SBC_BASE1 is set to zero, the access
* to anywhere in the system bus space is routed to
* bank 0 (if boot swap if off) or bank 1 (if boot swap
* if on). It means that CPUs cannot get access to
* bank 2 or later. In other words, bank 0/1 cannot
* be disabled even if its bank_enable bits is cleared.
* This seems odd, but it is how this hardware goes.
* As a workaround, dummy data (0xffffffff) should be
* set when the bank 0/1 is unused. As for bank 2 and
* later, they can be simply disable by clearing the
* bank_enable bit.
*/
if (i < 2)
val = UNIPHIER_SBC_BASE_DUMMY;
else
val = 0;
} else {
mask = base ^ (end - 1);
val = base & 0xfffe0000;
val |= (~mask >> 16) & 0xfffe;
val |= UNIPHIER_SBC_BASE_BE;
}
dev_dbg(priv->dev, "SBC_BASE[%d] = 0x%08x\n", i, val);
writel(val, base_reg + UNIPHIER_SBC_STRIDE * i);
}
}
static int uniphier_system_bus_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct uniphier_system_bus_priv *priv;
struct resource *regs;
const __be32 *ranges;
u32 cells, addr, size;
u64 paddr;
int pna, bank, rlen, rone, ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->membase = devm_ioremap_resource(dev, regs);
if (IS_ERR(priv->membase))
return PTR_ERR(priv->membase);
priv->dev = dev;
pna = of_n_addr_cells(dev->of_node);
ret = of_property_read_u32(dev->of_node, "#address-cells", &cells);
if (ret) {
dev_err(dev, "failed to get #address-cells\n");
return ret;
}
if (cells != 2) {
dev_err(dev, "#address-cells must be 2\n");
return -EINVAL;
}
ret = of_property_read_u32(dev->of_node, "#size-cells", &cells);
if (ret) {
dev_err(dev, "failed to get #size-cells\n");
return ret;
}
if (cells != 1) {
dev_err(dev, "#size-cells must be 1\n");
return -EINVAL;
}
ranges = of_get_property(dev->of_node, "ranges", &rlen);
if (!ranges) {
dev_err(dev, "failed to get ranges property\n");
return -ENOENT;
}
rlen /= sizeof(*ranges);
rone = pna + 2;
for (; rlen >= rone; rlen -= rone) {
bank = be32_to_cpup(ranges++);
addr = be32_to_cpup(ranges++);
paddr = of_translate_address(dev->of_node, ranges);
if (paddr == OF_BAD_ADDR)
return -EINVAL;
ranges += pna;
size = be32_to_cpup(ranges++);
ret = uniphier_system_bus_add_bank(priv, bank, addr,
paddr, size);
if (ret)
return ret;
}
ret = uniphier_system_bus_check_overlap(priv);
if (ret)
return ret;
uniphier_system_bus_check_boot_swap(priv);
uniphier_system_bus_set_reg(priv);
/* Now, the bus is configured. Populate platform_devices below it */
return of_platform_populate(dev->of_node, of_default_bus_match_table,
NULL, dev);
}
static const struct of_device_id uniphier_system_bus_match[] = {
{ .compatible = "socionext,uniphier-system-bus" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uniphier_system_bus_match);
static struct platform_driver uniphier_system_bus_driver = {
.probe = uniphier_system_bus_probe,
.driver = {
.name = "uniphier-system-bus",
.of_match_table = uniphier_system_bus_match,
},
};
module_platform_driver(uniphier_system_bus_driver);
MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>");
MODULE_DESCRIPTION("UniPhier System Bus driver");
MODULE_LICENSE("GPL");
......@@ -1007,6 +1007,7 @@ static const struct tegra_smmu_soc tegra124_smmu_soc = {
.num_swgroups = ARRAY_SIZE(tegra124_swgroups),
.supports_round_robin_arbitration = true,
.supports_request_limit = true,
.num_tlb_lines = 32,
.num_asids = 128,
};
......
......@@ -13,3 +13,4 @@ menuconfig RESET_CONTROLLER
If unsure, say no.
source "drivers/reset/sti/Kconfig"
source "drivers/reset/hisilicon/Kconfig"
obj-$(CONFIG_RESET_CONTROLLER) += core.o
obj-y += core.o
obj-$(CONFIG_ARCH_LPC18XX) += reset-lpc18xx.o
obj-$(CONFIG_ARCH_SOCFPGA) += reset-socfpga.o
obj-$(CONFIG_ARCH_BERLIN) += reset-berlin.o
obj-$(CONFIG_ARCH_SUNXI) += reset-sunxi.o
obj-$(CONFIG_ARCH_STI) += sti/
obj-$(CONFIG_ARCH_HISI) += hisilicon/
obj-$(CONFIG_ARCH_ZYNQ) += reset-zynq.o
obj-$(CONFIG_ATH79) += reset-ath79.o
......@@ -30,7 +30,6 @@ static LIST_HEAD(reset_controller_list);
*/
struct reset_control {
struct reset_controller_dev *rcdev;
struct device *dev;
unsigned int id;
};
......@@ -95,7 +94,7 @@ int reset_control_reset(struct reset_control *rstc)
if (rstc->rcdev->ops->reset)
return rstc->rcdev->ops->reset(rstc->rcdev, rstc->id);
return -ENOSYS;
return -ENOTSUPP;
}
EXPORT_SYMBOL_GPL(reset_control_reset);
......@@ -108,7 +107,7 @@ int reset_control_assert(struct reset_control *rstc)
if (rstc->rcdev->ops->assert)
return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id);
return -ENOSYS;
return -ENOTSUPP;
}
EXPORT_SYMBOL_GPL(reset_control_assert);
......@@ -121,7 +120,7 @@ int reset_control_deassert(struct reset_control *rstc)
if (rstc->rcdev->ops->deassert)
return rstc->rcdev->ops->deassert(rstc->rcdev, rstc->id);
return -ENOSYS;
return -ENOTSUPP;
}
EXPORT_SYMBOL_GPL(reset_control_deassert);
......@@ -136,32 +135,29 @@ int reset_control_status(struct reset_control *rstc)
if (rstc->rcdev->ops->status)
return rstc->rcdev->ops->status(rstc->rcdev, rstc->id);
return -ENOSYS;
return -ENOTSUPP;
}
EXPORT_SYMBOL_GPL(reset_control_status);
/**
* of_reset_control_get - Lookup and obtain a reference to a reset controller.
* of_reset_control_get_by_index - Lookup and obtain a reference to a reset
* controller by index.
* @node: device to be reset by the controller
* @id: reset line name
* @index: index of the reset controller
*
* Returns a struct reset_control or IS_ERR() condition containing errno.
*
* Use of id names is optional.
* This is to be used to perform a list of resets for a device or power domain
* in whatever order. Returns a struct reset_control or IS_ERR() condition
* containing errno.
*/
struct reset_control *of_reset_control_get(struct device_node *node,
const char *id)
struct reset_control *of_reset_control_get_by_index(struct device_node *node,
int index)
{
struct reset_control *rstc = ERR_PTR(-EPROBE_DEFER);
struct reset_controller_dev *r, *rcdev;
struct of_phandle_args args;
int index = 0;
int rstc_id;
int ret;
if (id)
index = of_property_match_string(node,
"reset-names", id);
ret = of_parse_phandle_with_args(node, "resets", "#reset-cells",
index, &args);
if (ret)
......@@ -202,6 +198,30 @@ struct reset_control *of_reset_control_get(struct device_node *node,
return rstc;
}
EXPORT_SYMBOL_GPL(of_reset_control_get_by_index);
/**
* of_reset_control_get - Lookup and obtain a reference to a reset controller.
* @node: device to be reset by the controller
* @id: reset line name
*
* Returns a struct reset_control or IS_ERR() condition containing errno.
*
* Use of id names is optional.
*/
struct reset_control *of_reset_control_get(struct device_node *node,
const char *id)
{
int index = 0;
if (id) {
index = of_property_match_string(node,
"reset-names", id);
if (index < 0)
return ERR_PTR(-ENOENT);
}
return of_reset_control_get_by_index(node, index);
}
EXPORT_SYMBOL_GPL(of_reset_control_get);
/**
......@@ -215,16 +235,10 @@ EXPORT_SYMBOL_GPL(of_reset_control_get);
*/
struct reset_control *reset_control_get(struct device *dev, const char *id)
{
struct reset_control *rstc;
if (!dev)
return ERR_PTR(-EINVAL);
rstc = of_reset_control_get(dev->of_node, id);
if (!IS_ERR(rstc))
rstc->dev = dev;
return rstc;
return of_reset_control_get(dev->of_node, id);
}
EXPORT_SYMBOL_GPL(reset_control_get);
......
config COMMON_RESET_HI6220
tristate "Hi6220 Reset Driver"
depends on (ARCH_HISI && RESET_CONTROLLER)
help
Build the Hisilicon Hi6220 reset driver.
obj-$(CONFIG_COMMON_RESET_HI6220) += hi6220_reset.o
/*
* Hisilicon Hi6220 reset controller driver
*
* Copyright (c) 2015 Hisilicon Limited.
*
* Author: Feng Chen <puck.chen@hisilicon.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.
*/
#include <linux/io.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/bitops.h>
#include <linux/of.h>
#include <linux/reset-controller.h>
#include <linux/reset.h>
#include <linux/platform_device.h>
#define ASSERT_OFFSET 0x300
#define DEASSERT_OFFSET 0x304
#define MAX_INDEX 0x509
#define to_reset_data(x) container_of(x, struct hi6220_reset_data, rc_dev)
struct hi6220_reset_data {
void __iomem *assert_base;
void __iomem *deassert_base;
struct reset_controller_dev rc_dev;
};
static int hi6220_reset_assert(struct reset_controller_dev *rc_dev,
unsigned long idx)
{
struct hi6220_reset_data *data = to_reset_data(rc_dev);
int bank = idx >> 8;
int offset = idx & 0xff;
writel(BIT(offset), data->assert_base + (bank * 0x10));
return 0;
}
static int hi6220_reset_deassert(struct reset_controller_dev *rc_dev,
unsigned long idx)
{
struct hi6220_reset_data *data = to_reset_data(rc_dev);
int bank = idx >> 8;
int offset = idx & 0xff;
writel(BIT(offset), data->deassert_base + (bank * 0x10));
return 0;
}
static struct reset_control_ops hi6220_reset_ops = {
.assert = hi6220_reset_assert,
.deassert = hi6220_reset_deassert,
};
static int hi6220_reset_probe(struct platform_device *pdev)
{
struct hi6220_reset_data *data;
struct resource *res;
void __iomem *src_base;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
src_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(src_base))
return PTR_ERR(src_base);
data->assert_base = src_base + ASSERT_OFFSET;
data->deassert_base = src_base + DEASSERT_OFFSET;
data->rc_dev.nr_resets = MAX_INDEX;
data->rc_dev.ops = &hi6220_reset_ops;
data->rc_dev.of_node = pdev->dev.of_node;
reset_controller_register(&data->rc_dev);
return 0;
}
static const struct of_device_id hi6220_reset_match[] = {
{ .compatible = "hisilicon,hi6220-sysctrl" },
{ },
};
static struct platform_driver hi6220_reset_driver = {
.probe = hi6220_reset_probe,
.driver = {
.name = "reset-hi6220",
.of_match_table = hi6220_reset_match,
},
};
static int __init hi6220_reset_init(void)
{
return platform_driver_register(&hi6220_reset_driver);
}
postcore_initcall(hi6220_reset_init);
......@@ -15,13 +15,17 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/reset-controller.h>
#include <linux/reboot.h>
struct ath79_reset {
struct reset_controller_dev rcdev;
struct notifier_block restart_nb;
void __iomem *base;
spinlock_t lock;
};
#define FULL_CHIP_RESET 24
static int ath79_reset_update(struct reset_controller_dev *rcdev,
unsigned long id, bool assert)
{
......@@ -72,10 +76,22 @@ static struct reset_control_ops ath79_reset_ops = {
.status = ath79_reset_status,
};
static int ath79_reset_restart_handler(struct notifier_block *nb,
unsigned long action, void *data)
{
struct ath79_reset *ath79_reset =
container_of(nb, struct ath79_reset, restart_nb);
ath79_reset_assert(&ath79_reset->rcdev, FULL_CHIP_RESET);
return NOTIFY_DONE;
}
static int ath79_reset_probe(struct platform_device *pdev)
{
struct ath79_reset *ath79_reset;
struct resource *res;
int err;
ath79_reset = devm_kzalloc(&pdev->dev,
sizeof(*ath79_reset), GFP_KERNEL);
......@@ -96,13 +112,25 @@ static int ath79_reset_probe(struct platform_device *pdev)
ath79_reset->rcdev.of_reset_n_cells = 1;
ath79_reset->rcdev.nr_resets = 32;
return reset_controller_register(&ath79_reset->rcdev);
err = reset_controller_register(&ath79_reset->rcdev);
if (err)
return err;
ath79_reset->restart_nb.notifier_call = ath79_reset_restart_handler;
ath79_reset->restart_nb.priority = 128;
err = register_restart_handler(&ath79_reset->restart_nb);
if (err)
dev_warn(&pdev->dev, "Failed to register restart handler\n");
return 0;
}
static int ath79_reset_remove(struct platform_device *pdev)
{
struct ath79_reset *ath79_reset = platform_get_drvdata(pdev);
unregister_restart_handler(&ath79_reset->restart_nb);
reset_controller_unregister(&ath79_reset->rcdev);
return 0;
......
......@@ -87,9 +87,7 @@ static int berlin2_reset_probe(struct platform_device *pdev)
priv->rcdev.of_reset_n_cells = 2;
priv->rcdev.of_xlate = berlin_reset_xlate;
reset_controller_register(&priv->rcdev);
return 0;
return reset_controller_register(&priv->rcdev);
}
static const struct of_device_id berlin_reset_dt_match[] = {
......
......@@ -133,9 +133,8 @@ static int socfpga_reset_probe(struct platform_device *pdev)
data->rcdev.nr_resets = NR_BANKS * BITS_PER_LONG;
data->rcdev.ops = &socfpga_reset_ops;
data->rcdev.of_node = pdev->dev.of_node;
reset_controller_register(&data->rcdev);
return 0;
return reset_controller_register(&data->rcdev);
}
static int socfpga_reset_remove(struct platform_device *pdev)
......
......@@ -108,9 +108,8 @@ static int sunxi_reset_init(struct device_node *np)
data->rcdev.nr_resets = size * 32;
data->rcdev.ops = &sunxi_reset_ops;
data->rcdev.of_node = np;
reset_controller_register(&data->rcdev);
return 0;
return reset_controller_register(&data->rcdev);
err_alloc:
kfree(data);
......@@ -122,7 +121,7 @@ static int sunxi_reset_init(struct device_node *np)
* our system, before we can even think of using a regular device
* driver for it.
*/
static const struct of_device_id sunxi_early_reset_dt_ids[] __initdata = {
static const struct of_device_id sunxi_early_reset_dt_ids[] __initconst = {
{ .compatible = "allwinner,sun6i-a31-ahb1-reset", },
{ /* sentinel */ },
};
......
......@@ -121,9 +121,8 @@ static int zynq_reset_probe(struct platform_device *pdev)
priv->rcdev.nr_resets = resource_size(res) / 4 * BITS_PER_LONG;
priv->rcdev.ops = &zynq_reset_ops;
priv->rcdev.of_node = pdev->dev.of_node;
reset_controller_register(&priv->rcdev);
return 0;
return reset_controller_register(&priv->rcdev);
}
static int zynq_reset_remove(struct platform_device *pdev)
......
......@@ -52,6 +52,7 @@ static const struct syscfg_reset_channel_data stih407_powerdowns[] = {
};
/* Reset Generator control 0/1 */
#define SYSCFG_5128 0x200
#define SYSCFG_5131 0x20c
#define SYSCFG_5132 0x210
......@@ -96,6 +97,10 @@ static const struct syscfg_reset_channel_data stih407_softresets[] = {
[STIH407_ERAM_HVA_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 1),
[STIH407_LPM_SOFTRESET] = STIH407_SRST_SBC(SYSCFG_4002, 2),
[STIH407_KEYSCAN_SOFTRESET] = STIH407_SRST_LPM(LPM_SYSCFG_1, 8),
[STIH407_ST231_AUD_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 26),
[STIH407_ST231_DMU_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 27),
[STIH407_ST231_GP0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 28),
[STIH407_ST231_GP1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5128, 2),
};
/* PicoPHY reset/control */
......
......@@ -103,17 +103,42 @@ static int syscfg_reset_deassert(struct reset_controller_dev *rcdev,
static int syscfg_reset_dev(struct reset_controller_dev *rcdev,
unsigned long idx)
{
int err = syscfg_reset_assert(rcdev, idx);
int err;
err = syscfg_reset_assert(rcdev, idx);
if (err)
return err;
return syscfg_reset_deassert(rcdev, idx);
}
static int syscfg_reset_status(struct reset_controller_dev *rcdev,
unsigned long idx)
{
struct syscfg_reset_controller *rst = to_syscfg_reset_controller(rcdev);
const struct syscfg_reset_channel *ch;
u32 ret_val = 0;
int err;
if (idx >= rcdev->nr_resets)
return -EINVAL;
ch = &rst->channels[idx];
if (ch->ack)
err = regmap_field_read(ch->ack, &ret_val);
else
err = regmap_field_read(ch->reset, &ret_val);
if (err)
return err;
return rst->active_low ? !ret_val : !!ret_val;
}
static struct reset_control_ops syscfg_reset_ops = {
.reset = syscfg_reset_dev,
.assert = syscfg_reset_assert,
.deassert = syscfg_reset_deassert,
.status = syscfg_reset_status,
};
static int syscfg_reset_controller_register(struct device *dev,
......
menu "SOC (System On Chip) specific Drivers"
source "drivers/soc/bcm/Kconfig"
source "drivers/soc/brcmstb/Kconfig"
source "drivers/soc/fsl/qe/Kconfig"
source "drivers/soc/mediatek/Kconfig"
......
......@@ -2,6 +2,7 @@
# Makefile for the Linux Kernel SOC specific device drivers.
#
obj-y += bcm/
obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/
obj-$(CONFIG_ARCH_DOVE) += dove/
obj-$(CONFIG_MACH_DOVE) += dove/
......
config RASPBERRYPI_POWER
bool "Raspberry Pi power domain driver"
depends on ARCH_BCM2835 || COMPILE_TEST
depends on RASPBERRYPI_FIRMWARE=y
select PM_GENERIC_DOMAINS if PM
select PM_GENERIC_DOMAINS_OF if PM
help
This enables support for the RPi power domains which can be enabled
or disabled via the RPi firmware.
obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o
/* (C) 2015 Pengutronix, Alexander Aring <aar@pengutronix.de>
*
* 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.
*
* Authors:
* Alexander Aring <aar@pengutronix.de>
* Eric Anholt <eric@anholt.net>
*/
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <dt-bindings/power/raspberrypi-power.h>
#include <soc/bcm2835/raspberrypi-firmware.h>
/*
* Firmware indices for the old power domains interface. Only a few
* of them were actually implemented.
*/
#define RPI_OLD_POWER_DOMAIN_USB 3
#define RPI_OLD_POWER_DOMAIN_V3D 10
struct rpi_power_domain {
u32 domain;
bool enabled;
bool old_interface;
struct generic_pm_domain base;
struct rpi_firmware *fw;
};
struct rpi_power_domains {
bool has_new_interface;
struct genpd_onecell_data xlate;
struct rpi_firmware *fw;
struct rpi_power_domain domains[RPI_POWER_DOMAIN_COUNT];
};
/*
* Packet definition used by RPI_FIRMWARE_SET_POWER_STATE and
* RPI_FIRMWARE_SET_DOMAIN_STATE
*/
struct rpi_power_domain_packet {
u32 domain;
u32 on;
} __packet;
/*
* Asks the firmware to enable or disable power on a specific power
* domain.
*/
static int rpi_firmware_set_power(struct rpi_power_domain *rpi_domain, bool on)
{
struct rpi_power_domain_packet packet;
packet.domain = rpi_domain->domain;
packet.on = on;
return rpi_firmware_property(rpi_domain->fw,
rpi_domain->old_interface ?
RPI_FIRMWARE_SET_POWER_STATE :
RPI_FIRMWARE_SET_DOMAIN_STATE,
&packet, sizeof(packet));
}
static int rpi_domain_off(struct generic_pm_domain *domain)
{
struct rpi_power_domain *rpi_domain =
container_of(domain, struct rpi_power_domain, base);
return rpi_firmware_set_power(rpi_domain, false);
}
static int rpi_domain_on(struct generic_pm_domain *domain)
{
struct rpi_power_domain *rpi_domain =
container_of(domain, struct rpi_power_domain, base);
return rpi_firmware_set_power(rpi_domain, true);
}
static void rpi_common_init_power_domain(struct rpi_power_domains *rpi_domains,
int xlate_index, const char *name)
{
struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
dom->fw = rpi_domains->fw;
dom->base.name = name;
dom->base.power_on = rpi_domain_on;
dom->base.power_off = rpi_domain_off;
/*
* Treat all power domains as off at boot.
*
* The firmware itself may be keeping some domains on, but
* from Linux's perspective all we control is the refcounts
* that we give to the firmware, and we can't ask the firmware
* to turn off something that we haven't ourselves turned on.
*/
pm_genpd_init(&dom->base, NULL, true);
rpi_domains->xlate.domains[xlate_index] = &dom->base;
}
static void rpi_init_power_domain(struct rpi_power_domains *rpi_domains,
int xlate_index, const char *name)
{
struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
if (!rpi_domains->has_new_interface)
return;
/* The DT binding index is the firmware's domain index minus one. */
dom->domain = xlate_index + 1;
rpi_common_init_power_domain(rpi_domains, xlate_index, name);
}
static void rpi_init_old_power_domain(struct rpi_power_domains *rpi_domains,
int xlate_index, int domain,
const char *name)
{
struct rpi_power_domain *dom = &rpi_domains->domains[xlate_index];
dom->old_interface = true;
dom->domain = domain;
rpi_common_init_power_domain(rpi_domains, xlate_index, name);
}
/*
* Detects whether the firmware supports the new power domains interface.
*
* The firmware doesn't actually return an error on an unknown tag,
* and just skips over it, so we do the detection by putting an
* unexpected value in the return field and checking if it was
* unchanged.
*/
static bool
rpi_has_new_domain_support(struct rpi_power_domains *rpi_domains)
{
struct rpi_power_domain_packet packet;
int ret;
packet.domain = RPI_POWER_DOMAIN_ARM;
packet.on = ~0;
ret = rpi_firmware_property(rpi_domains->fw,
RPI_FIRMWARE_GET_DOMAIN_STATE,
&packet, sizeof(packet));
return ret == 0 && packet.on != ~0;
}
static int rpi_power_probe(struct platform_device *pdev)
{
struct device_node *fw_np;
struct device *dev = &pdev->dev;
struct rpi_power_domains *rpi_domains;
rpi_domains = devm_kzalloc(dev, sizeof(*rpi_domains), GFP_KERNEL);
if (!rpi_domains)
return -ENOMEM;
rpi_domains->xlate.domains =
devm_kzalloc(dev, sizeof(*rpi_domains->xlate.domains) *
RPI_POWER_DOMAIN_COUNT, GFP_KERNEL);
if (!rpi_domains->xlate.domains)
return -ENOMEM;
rpi_domains->xlate.num_domains = RPI_POWER_DOMAIN_COUNT;
fw_np = of_parse_phandle(pdev->dev.of_node, "firmware", 0);
if (!fw_np) {
dev_err(&pdev->dev, "no firmware node\n");
return -ENODEV;
}
rpi_domains->fw = rpi_firmware_get(fw_np);
of_node_put(fw_np);
if (!rpi_domains->fw)
return -EPROBE_DEFER;
rpi_domains->has_new_interface =
rpi_has_new_domain_support(rpi_domains);
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C0, "I2C0");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C1, "I2C1");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_I2C2, "I2C2");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VIDEO_SCALER,
"VIDEO_SCALER");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VPU1, "VPU1");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_HDMI, "HDMI");
/*
* Use the old firmware interface for USB power, so that we
* can turn it on even if the firmware hasn't been updated.
*/
rpi_init_old_power_domain(rpi_domains, RPI_POWER_DOMAIN_USB,
RPI_OLD_POWER_DOMAIN_USB, "USB");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_VEC, "VEC");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_JPEG, "JPEG");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_H264, "H264");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_V3D, "V3D");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ISP, "ISP");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM0, "UNICAM0");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_UNICAM1, "UNICAM1");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2RX, "CCP2RX");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CSI2, "CSI2");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CPI, "CPI");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI0, "DSI0");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_DSI1, "DSI1");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_TRANSPOSER,
"TRANSPOSER");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CCP2TX, "CCP2TX");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_CDP, "CDP");
rpi_init_power_domain(rpi_domains, RPI_POWER_DOMAIN_ARM, "ARM");
of_genpd_add_provider_onecell(dev->of_node, &rpi_domains->xlate);
platform_set_drvdata(pdev, rpi_domains);
return 0;
}
static const struct of_device_id rpi_power_of_match[] = {
{ .compatible = "raspberrypi,bcm2835-power", },
{},
};
MODULE_DEVICE_TABLE(of, rpi_power_of_match);
static struct platform_driver rpi_power_driver = {
.driver = {
.name = "raspberrypi-power",
.of_match_table = rpi_power_of_match,
},
.probe = rpi_power_probe,
};
builtin_platform_driver(rpi_power_driver);
MODULE_AUTHOR("Alexander Aring <aar@pengutronix.de>");
MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
MODULE_DESCRIPTION("Raspberry Pi power domain driver");
MODULE_LICENSE("GPL v2");
......@@ -15,12 +15,13 @@
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/regmap.h>
#include <linux/soc/mediatek/infracfg.h>
#include <linux/regulator/consumer.h>
#include <dt-bindings/power/mt8173-power.h>
#define SPM_VDE_PWR_CON 0x0210
......@@ -179,6 +180,7 @@ struct scp_domain {
u32 sram_pdn_ack_bits;
u32 bus_prot_mask;
bool active_wakeup;
struct regulator *supply;
};
struct scp {
......@@ -221,6 +223,12 @@ static int scpsys_power_on(struct generic_pm_domain *genpd)
int ret;
int i;
if (scpd->supply) {
ret = regulator_enable(scpd->supply);
if (ret)
return ret;
}
for (i = 0; i < MAX_CLKS && scpd->clk[i]; i++) {
ret = clk_prepare_enable(scpd->clk[i]);
if (ret) {
......@@ -299,6 +307,9 @@ static int scpsys_power_on(struct generic_pm_domain *genpd)
clk_disable_unprepare(scpd->clk[i]);
}
err_clk:
if (scpd->supply)
regulator_disable(scpd->supply);
dev_err(scp->dev, "Failed to power on domain %s\n", genpd->name);
return ret;
......@@ -379,6 +390,9 @@ static int scpsys_power_off(struct generic_pm_domain *genpd)
for (i = 0; i < MAX_CLKS && scpd->clk[i]; i++)
clk_disable_unprepare(scpd->clk[i]);
if (scpd->supply)
regulator_disable(scpd->supply);
return 0;
out:
......@@ -448,6 +462,19 @@ static int __init scpsys_probe(struct platform_device *pdev)
return PTR_ERR(scp->infracfg);
}
for (i = 0; i < NUM_DOMAINS; i++) {
struct scp_domain *scpd = &scp->domains[i];
const struct scp_domain_data *data = &scp_domain_data[i];
scpd->supply = devm_regulator_get_optional(&pdev->dev, data->name);
if (IS_ERR(scpd->supply)) {
if (PTR_ERR(scpd->supply) == -ENODEV)
scpd->supply = NULL;
else
return PTR_ERR(scpd->supply);
}
}
pd_data->num_domains = NUM_DOMAINS;
for (i = 0; i < NUM_DOMAINS; i++) {
......@@ -521,5 +548,4 @@ static struct platform_driver scpsys_drv = {
.of_match_table = of_match_ptr(of_scpsys_match_tbl),
},
};
module_platform_driver_probe(scpsys_drv, scpsys_probe);
builtin_platform_driver_probe(scpsys_drv, scpsys_probe);
......@@ -13,6 +13,7 @@ config QCOM_GSBI
config QCOM_PM
bool "Qualcomm Power Management"
depends on ARCH_QCOM && !ARM64
select ARM_CPU_SUSPEND
select QCOM_SCM
help
QCOM Platform specific power driver to manage cores and L2 low power
......@@ -49,3 +50,29 @@ 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_STATE
bool
config QCOM_SMP2P
tristate "Qualcomm Shared Memory Point to Point support"
depends on QCOM_SMEM
select QCOM_SMEM_STATE
help
Say yes here to support the Qualcomm Shared Memory Point to Point
protocol.
config QCOM_SMSM
tristate "Qualcomm Shared Memory State Machine"
depends on QCOM_SMEM
select QCOM_SMEM_STATE
help
Say yes here to support the Qualcomm Shared Memory State Machine.
The state machine is represented by bits in shared memory.
config QCOM_WCNSS_CTRL
tristate "Qualcomm WCNSS control driver"
depends on QCOM_SMD
help
Client driver for the WCNSS_CTRL SMD channel, used to download nv
firmware to a newly booted WCNSS chip.
......@@ -3,3 +3,7 @@ obj-$(CONFIG_QCOM_PM) += spm.o
obj-$(CONFIG_QCOM_SMD) += smd.o
obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
obj-$(CONFIG_QCOM_SMEM) += smem.o
obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
......@@ -219,6 +219,8 @@ static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev)
}
static const struct of_device_id qcom_smd_rpm_of_match[] = {
{ .compatible = "qcom,rpm-apq8084" },
{ .compatible = "qcom,rpm-msm8916" },
{ .compatible = "qcom,rpm-msm8974" },
{}
};
......
/*
* Copyright (c) 2015, Sony Mobile Communications Inc.
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem_state.h>
static LIST_HEAD(smem_states);
static DEFINE_MUTEX(list_lock);
/**
* struct qcom_smem_state - state context
* @refcount: refcount for the state
* @orphan: boolean indicator that this state has been unregistered
* @list: entry in smem_states list
* @of_node: of_node to use for matching the state in DT
* @priv: implementation private data
* @ops: ops for the state
*/
struct qcom_smem_state {
struct kref refcount;
bool orphan;
struct list_head list;
struct device_node *of_node;
void *priv;
struct qcom_smem_state_ops ops;
};
/**
* qcom_smem_state_update_bits() - update the masked bits in state with value
* @state: state handle acquired by calling qcom_smem_state_get()
* @mask: bit mask for the change
* @value: new value for the masked bits
*
* Returns 0 on success, otherwise negative errno.
*/
int qcom_smem_state_update_bits(struct qcom_smem_state *state,
u32 mask,
u32 value)
{
if (state->orphan)
return -ENXIO;
if (!state->ops.update_bits)
return -ENOTSUPP;
return state->ops.update_bits(state->priv, mask, value);
}
EXPORT_SYMBOL_GPL(qcom_smem_state_update_bits);
static struct qcom_smem_state *of_node_to_state(struct device_node *np)
{
struct qcom_smem_state *state;
mutex_lock(&list_lock);
list_for_each_entry(state, &smem_states, list) {
if (state->of_node == np) {
kref_get(&state->refcount);
goto unlock;
}
}
state = ERR_PTR(-EPROBE_DEFER);
unlock:
mutex_unlock(&list_lock);
return state;
}
/**
* qcom_smem_state_get() - acquire handle to a state
* @dev: client device pointer
* @con_id: name of the state to lookup
* @bit: flags from the state reference, indicating which bit's affected
*
* Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be
* called to release the returned state handle.
*/
struct qcom_smem_state *qcom_smem_state_get(struct device *dev,
const char *con_id,
unsigned *bit)
{
struct qcom_smem_state *state;
struct of_phandle_args args;
int index = 0;
int ret;
if (con_id) {
index = of_property_match_string(dev->of_node,
"qcom,state-names",
con_id);
if (index < 0) {
dev_err(dev, "missing qcom,state-names\n");
return ERR_PTR(index);
}
}
ret = of_parse_phandle_with_args(dev->of_node,
"qcom,state",
"#qcom,state-cells",
index,
&args);
if (ret) {
dev_err(dev, "failed to parse qcom,state property\n");
return ERR_PTR(ret);
}
if (args.args_count != 1) {
dev_err(dev, "invalid #qcom,state-cells\n");
return ERR_PTR(-EINVAL);
}
state = of_node_to_state(args.np);
if (IS_ERR(state))
goto put;
*bit = args.args[0];
put:
of_node_put(args.np);
return state;
}
EXPORT_SYMBOL_GPL(qcom_smem_state_get);
static void qcom_smem_state_release(struct kref *ref)
{
struct qcom_smem_state *state = container_of(ref, struct qcom_smem_state, refcount);
list_del(&state->list);
kfree(state);
}
/**
* qcom_smem_state_put() - release state handle
* @state: state handle to be released
*/
void qcom_smem_state_put(struct qcom_smem_state *state)
{
mutex_lock(&list_lock);
kref_put(&state->refcount, qcom_smem_state_release);
mutex_unlock(&list_lock);
}
EXPORT_SYMBOL_GPL(qcom_smem_state_put);
/**
* qcom_smem_state_register() - register a new state
* @of_node: of_node used for matching client lookups
* @ops: implementation ops
* @priv: implementation specific private data
*/
struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node,
const struct qcom_smem_state_ops *ops,
void *priv)
{
struct qcom_smem_state *state;
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state)
return ERR_PTR(-ENOMEM);
kref_init(&state->refcount);
state->of_node = of_node;
state->ops = *ops;
state->priv = priv;
mutex_lock(&list_lock);
list_add(&state->list, &smem_states);
mutex_unlock(&list_lock);
return state;
}
EXPORT_SYMBOL_GPL(qcom_smem_state_register);
/**
* qcom_smem_state_unregister() - unregister a registered state
* @state: state handle to be unregistered
*/
void qcom_smem_state_unregister(struct qcom_smem_state *state)
{
state->orphan = true;
qcom_smem_state_put(state);
}
EXPORT_SYMBOL_GPL(qcom_smem_state_unregister);
/*
* Copyright (c) 2015, Sony Mobile Communications AB.
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
#include <linux/spinlock.h>
/*
* The Shared Memory Point to Point (SMP2P) protocol facilitates communication
* of a single 32-bit value between two processors. Each value has a single
* writer (the local side) and a single reader (the remote side). Values are
* uniquely identified in the system by the directed edge (local processor ID
* to remote processor ID) and a string identifier.
*
* Each processor is responsible for creating the outgoing SMEM items and each
* item is writable by the local processor and readable by the remote
* processor. By using two separate SMEM items that are single-reader and
* single-writer, SMP2P does not require any remote locking mechanisms.
*
* The driver uses the Linux GPIO and interrupt framework to expose a virtual
* GPIO for each outbound entry and a virtual interrupt controller for each
* inbound entry.
*/
#define SMP2P_MAX_ENTRY 16
#define SMP2P_MAX_ENTRY_NAME 16
#define SMP2P_FEATURE_SSR_ACK 0x1
#define SMP2P_MAGIC 0x504d5324
/**
* struct smp2p_smem_item - in memory communication structure
* @magic: magic number
* @version: version - must be 1
* @features: features flag - currently unused
* @local_pid: processor id of sending end
* @remote_pid: processor id of receiving end
* @total_entries: number of entries - always SMP2P_MAX_ENTRY
* @valid_entries: number of allocated entries
* @flags:
* @entries: individual communication entries
* @name: name of the entry
* @value: content of the entry
*/
struct smp2p_smem_item {
u32 magic;
u8 version;
unsigned features:24;
u16 local_pid;
u16 remote_pid;
u16 total_entries;
u16 valid_entries;
u32 flags;
struct {
u8 name[SMP2P_MAX_ENTRY_NAME];
u32 value;
} entries[SMP2P_MAX_ENTRY];
} __packed;
/**
* struct smp2p_entry - driver context matching one entry
* @node: list entry to keep track of allocated entries
* @smp2p: reference to the device driver context
* @name: name of the entry, to match against smp2p_smem_item
* @value: pointer to smp2p_smem_item entry value
* @last_value: last handled value
* @domain: irq_domain for inbound entries
* @irq_enabled:bitmap to track enabled irq bits
* @irq_rising: bitmap to mark irq bits for rising detection
* @irq_falling:bitmap to mark irq bits for falling detection
* @state: smem state handle
* @lock: spinlock to protect read-modify-write of the value
*/
struct smp2p_entry {
struct list_head node;
struct qcom_smp2p *smp2p;
const char *name;
u32 *value;
u32 last_value;
struct irq_domain *domain;
DECLARE_BITMAP(irq_enabled, 32);
DECLARE_BITMAP(irq_rising, 32);
DECLARE_BITMAP(irq_falling, 32);
struct qcom_smem_state *state;
spinlock_t lock;
};
#define SMP2P_INBOUND 0
#define SMP2P_OUTBOUND 1
/**
* struct qcom_smp2p - device driver context
* @dev: device driver handle
* @in: pointer to the inbound smem item
* @smem_items: ids of the two smem items
* @valid_entries: already scanned inbound entries
* @local_pid: processor id of the inbound edge
* @remote_pid: processor id of the outbound edge
* @ipc_regmap: regmap for the outbound ipc
* @ipc_offset: offset within the regmap
* @ipc_bit: bit in regmap@offset to kick to signal remote processor
* @inbound: list of inbound entries
* @outbound: list of outbound entries
*/
struct qcom_smp2p {
struct device *dev;
struct smp2p_smem_item *in;
struct smp2p_smem_item *out;
unsigned smem_items[SMP2P_OUTBOUND + 1];
unsigned valid_entries;
unsigned local_pid;
unsigned remote_pid;
struct regmap *ipc_regmap;
int ipc_offset;
int ipc_bit;
struct list_head inbound;
struct list_head outbound;
};
static void qcom_smp2p_kick(struct qcom_smp2p *smp2p)
{
/* Make sure any updated data is written before the kick */
wmb();
regmap_write(smp2p->ipc_regmap, smp2p->ipc_offset, BIT(smp2p->ipc_bit));
}
/**
* qcom_smp2p_intr() - interrupt handler for incoming notifications
* @irq: unused
* @data: smp2p driver context
*
* Handle notifications from the remote side to handle newly allocated entries
* or any changes to the state bits of existing entries.
*/
static irqreturn_t qcom_smp2p_intr(int irq, void *data)
{
struct smp2p_smem_item *in;
struct smp2p_entry *entry;
struct qcom_smp2p *smp2p = data;
unsigned smem_id = smp2p->smem_items[SMP2P_INBOUND];
unsigned pid = smp2p->remote_pid;
size_t size;
int irq_pin;
u32 status;
char buf[SMP2P_MAX_ENTRY_NAME];
u32 val;
int i;
in = smp2p->in;
/* Acquire smem item, if not already found */
if (!in) {
in = qcom_smem_get(pid, smem_id, &size);
if (IS_ERR(in)) {
dev_err(smp2p->dev,
"Unable to acquire remote smp2p item\n");
return IRQ_HANDLED;
}
smp2p->in = in;
}
/* Match newly created entries */
for (i = smp2p->valid_entries; i < in->valid_entries; i++) {
list_for_each_entry(entry, &smp2p->inbound, node) {
memcpy_fromio(buf, in->entries[i].name, sizeof(buf));
if (!strcmp(buf, entry->name)) {
entry->value = &in->entries[i].value;
break;
}
}
}
smp2p->valid_entries = i;
/* Fire interrupts based on any value changes */
list_for_each_entry(entry, &smp2p->inbound, node) {
/* Ignore entries not yet allocated by the remote side */
if (!entry->value)
continue;
val = readl(entry->value);
status = val ^ entry->last_value;
entry->last_value = val;
/* No changes of this entry? */
if (!status)
continue;
for_each_set_bit(i, entry->irq_enabled, 32) {
if (!(status & BIT(i)))
continue;
if ((val & BIT(i) && test_bit(i, entry->irq_rising)) ||
(!(val & BIT(i)) && test_bit(i, entry->irq_falling))) {
irq_pin = irq_find_mapping(entry->domain, i);
handle_nested_irq(irq_pin);
}
}
}
return IRQ_HANDLED;
}
static void smp2p_mask_irq(struct irq_data *irqd)
{
struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd);
clear_bit(irq, entry->irq_enabled);
}
static void smp2p_unmask_irq(struct irq_data *irqd)
{
struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd);
set_bit(irq, entry->irq_enabled);
}
static int smp2p_set_irq_type(struct irq_data *irqd, unsigned int type)
{
struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd);
if (!(type & IRQ_TYPE_EDGE_BOTH))
return -EINVAL;
if (type & IRQ_TYPE_EDGE_RISING)
set_bit(irq, entry->irq_rising);
else
clear_bit(irq, entry->irq_rising);
if (type & IRQ_TYPE_EDGE_FALLING)
set_bit(irq, entry->irq_falling);
else
clear_bit(irq, entry->irq_falling);
return 0;
}
static struct irq_chip smp2p_irq_chip = {
.name = "smp2p",
.irq_mask = smp2p_mask_irq,
.irq_unmask = smp2p_unmask_irq,
.irq_set_type = smp2p_set_irq_type,
};
static int smp2p_irq_map(struct irq_domain *d,
unsigned int irq,
irq_hw_number_t hw)
{
struct smp2p_entry *entry = d->host_data;
irq_set_chip_and_handler(irq, &smp2p_irq_chip, handle_level_irq);
irq_set_chip_data(irq, entry);
irq_set_nested_thread(irq, 1);
irq_set_noprobe(irq);
return 0;
}
static const struct irq_domain_ops smp2p_irq_ops = {
.map = smp2p_irq_map,
.xlate = irq_domain_xlate_twocell,
};
static int qcom_smp2p_inbound_entry(struct qcom_smp2p *smp2p,
struct smp2p_entry *entry,
struct device_node *node)
{
entry->domain = irq_domain_add_linear(node, 32, &smp2p_irq_ops, entry);
if (!entry->domain) {
dev_err(smp2p->dev, "failed to add irq_domain\n");
return -ENOMEM;
}
return 0;
}
static int smp2p_update_bits(void *data, u32 mask, u32 value)
{
struct smp2p_entry *entry = data;
u32 orig;
u32 val;
spin_lock(&entry->lock);
val = orig = readl(entry->value);
val &= ~mask;
val |= value;
writel(val, entry->value);
spin_unlock(&entry->lock);
if (val != orig)
qcom_smp2p_kick(entry->smp2p);
return 0;
}
static const struct qcom_smem_state_ops smp2p_state_ops = {
.update_bits = smp2p_update_bits,
};
static int qcom_smp2p_outbound_entry(struct qcom_smp2p *smp2p,
struct smp2p_entry *entry,
struct device_node *node)
{
struct smp2p_smem_item *out = smp2p->out;
char buf[SMP2P_MAX_ENTRY_NAME] = {};
/* Allocate an entry from the smem item */
strlcpy(buf, entry->name, SMP2P_MAX_ENTRY_NAME);
memcpy_toio(out->entries[out->valid_entries].name, buf, SMP2P_MAX_ENTRY_NAME);
out->valid_entries++;
/* Make the logical entry reference the physical value */
entry->value = &out->entries[out->valid_entries].value;
entry->state = qcom_smem_state_register(node, &smp2p_state_ops, entry);
if (IS_ERR(entry->state)) {
dev_err(smp2p->dev, "failed to register qcom_smem_state\n");
return PTR_ERR(entry->state);
}
return 0;
}
static int qcom_smp2p_alloc_outbound_item(struct qcom_smp2p *smp2p)
{
struct smp2p_smem_item *out;
unsigned smem_id = smp2p->smem_items[SMP2P_OUTBOUND];
unsigned pid = smp2p->remote_pid;
int ret;
ret = qcom_smem_alloc(pid, smem_id, sizeof(*out));
if (ret < 0 && ret != -EEXIST) {
if (ret != -EPROBE_DEFER)
dev_err(smp2p->dev,
"unable to allocate local smp2p item\n");
return ret;
}
out = qcom_smem_get(pid, smem_id, NULL);
if (IS_ERR(out)) {
dev_err(smp2p->dev, "Unable to acquire local smp2p item\n");
return PTR_ERR(out);
}
memset(out, 0, sizeof(*out));
out->magic = SMP2P_MAGIC;
out->local_pid = smp2p->local_pid;
out->remote_pid = smp2p->remote_pid;
out->total_entries = SMP2P_MAX_ENTRY;
out->valid_entries = 0;
/*
* Make sure the rest of the header is written before we validate the
* item by writing a valid version number.
*/
wmb();
out->version = 1;
qcom_smp2p_kick(smp2p);
smp2p->out = out;
return 0;
}
static int smp2p_parse_ipc(struct qcom_smp2p *smp2p)
{
struct device_node *syscon;
struct device *dev = smp2p->dev;
const char *key;
int ret;
syscon = of_parse_phandle(dev->of_node, "qcom,ipc", 0);
if (!syscon) {
dev_err(dev, "no qcom,ipc node\n");
return -ENODEV;
}
smp2p->ipc_regmap = syscon_node_to_regmap(syscon);
if (IS_ERR(smp2p->ipc_regmap))
return PTR_ERR(smp2p->ipc_regmap);
key = "qcom,ipc";
ret = of_property_read_u32_index(dev->of_node, key, 1, &smp2p->ipc_offset);
if (ret < 0) {
dev_err(dev, "no offset in %s\n", key);
return -EINVAL;
}
ret = of_property_read_u32_index(dev->of_node, key, 2, &smp2p->ipc_bit);
if (ret < 0) {
dev_err(dev, "no bit in %s\n", key);
return -EINVAL;
}
return 0;
}
static int qcom_smp2p_probe(struct platform_device *pdev)
{
struct smp2p_entry *entry;
struct device_node *node;
struct qcom_smp2p *smp2p;
const char *key;
int irq;
int ret;
smp2p = devm_kzalloc(&pdev->dev, sizeof(*smp2p), GFP_KERNEL);
if (!smp2p)
return -ENOMEM;
smp2p->dev = &pdev->dev;
INIT_LIST_HEAD(&smp2p->inbound);
INIT_LIST_HEAD(&smp2p->outbound);
platform_set_drvdata(pdev, smp2p);
ret = smp2p_parse_ipc(smp2p);
if (ret)
return ret;
key = "qcom,smem";
ret = of_property_read_u32_array(pdev->dev.of_node, key,
smp2p->smem_items, 2);
if (ret)
return ret;
key = "qcom,local-pid";
ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->local_pid);
if (ret < 0) {
dev_err(&pdev->dev, "failed to read %s\n", key);
return -EINVAL;
}
key = "qcom,remote-pid";
ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->remote_pid);
if (ret < 0) {
dev_err(&pdev->dev, "failed to read %s\n", key);
return -EINVAL;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "unable to acquire smp2p interrupt\n");
return irq;
}
ret = qcom_smp2p_alloc_outbound_item(smp2p);
if (ret < 0)
return ret;
for_each_available_child_of_node(pdev->dev.of_node, node) {
entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL);
if (!entry) {
ret = -ENOMEM;
goto unwind_interfaces;
}
entry->smp2p = smp2p;
spin_lock_init(&entry->lock);
ret = of_property_read_string(node, "qcom,entry-name", &entry->name);
if (ret < 0)
goto unwind_interfaces;
if (of_property_read_bool(node, "interrupt-controller")) {
ret = qcom_smp2p_inbound_entry(smp2p, entry, node);
if (ret < 0)
goto unwind_interfaces;
list_add(&entry->node, &smp2p->inbound);
} else {
ret = qcom_smp2p_outbound_entry(smp2p, entry, node);
if (ret < 0)
goto unwind_interfaces;
list_add(&entry->node, &smp2p->outbound);
}
}
/* Kick the outgoing edge after allocating entries */
qcom_smp2p_kick(smp2p);
ret = devm_request_threaded_irq(&pdev->dev, irq,
NULL, qcom_smp2p_intr,
IRQF_ONESHOT,
"smp2p", (void *)smp2p);
if (ret) {
dev_err(&pdev->dev, "failed to request interrupt\n");
goto unwind_interfaces;
}
return 0;
unwind_interfaces:
list_for_each_entry(entry, &smp2p->inbound, node)
irq_domain_remove(entry->domain);
list_for_each_entry(entry, &smp2p->outbound, node)
qcom_smem_state_unregister(entry->state);
smp2p->out->valid_entries = 0;
return ret;
}
static int qcom_smp2p_remove(struct platform_device *pdev)
{
struct qcom_smp2p *smp2p = platform_get_drvdata(pdev);
struct smp2p_entry *entry;
list_for_each_entry(entry, &smp2p->inbound, node)
irq_domain_remove(entry->domain);
list_for_each_entry(entry, &smp2p->outbound, node)
qcom_smem_state_unregister(entry->state);
smp2p->out->valid_entries = 0;
return 0;
}
static const struct of_device_id qcom_smp2p_of_match[] = {
{ .compatible = "qcom,smp2p" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_smp2p_of_match);
static struct platform_driver qcom_smp2p_driver = {
.probe = qcom_smp2p_probe,
.remove = qcom_smp2p_remove,
.driver = {
.name = "qcom_smp2p",
.of_match_table = qcom_smp2p_of_match,
},
};
module_platform_driver(qcom_smp2p_driver);
MODULE_DESCRIPTION("Qualcomm Shared Memory Point to Point driver");
MODULE_LICENSE("GPL v2");
/*
* Copyright (c) 2015, Sony Mobile Communications Inc.
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/interrupt.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/regmap.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
/*
* This driver implements the Qualcomm Shared Memory State Machine, a mechanism
* for communicating single bit state information to remote processors.
*
* The implementation is based on two sections of shared memory; the first
* holding the state bits and the second holding a matrix of subscription bits.
*
* The state bits are structured in entries of 32 bits, each belonging to one
* system in the SoC. The entry belonging to the local system is considered
* read-write, while the rest should be considered read-only.
*
* The subscription matrix consists of N bitmaps per entry, denoting interest
* in updates of the entry for each of the N hosts. Upon updating a state bit
* each host's subscription bitmap should be queried and the remote system
* should be interrupted if they request so.
*
* The subscription matrix is laid out in entry-major order:
* entry0: [host0 ... hostN]
* .
* .
* entryM: [host0 ... hostN]
*
* A third, optional, shared memory region might contain information regarding
* the number of entries in the state bitmap as well as number of columns in
* the subscription matrix.
*/
/*
* Shared memory identifiers, used to acquire handles to respective memory
* region.
*/
#define SMEM_SMSM_SHARED_STATE 85
#define SMEM_SMSM_CPU_INTR_MASK 333
#define SMEM_SMSM_SIZE_INFO 419
/*
* Default sizes, in case SMEM_SMSM_SIZE_INFO is not found.
*/
#define SMSM_DEFAULT_NUM_ENTRIES 8
#define SMSM_DEFAULT_NUM_HOSTS 3
struct smsm_entry;
struct smsm_host;
/**
* struct qcom_smsm - smsm driver context
* @dev: smsm device pointer
* @local_host: column in the subscription matrix representing this system
* @num_hosts: number of columns in the subscription matrix
* @num_entries: number of entries in the state map and rows in the subscription
* matrix
* @local_state: pointer to the local processor's state bits
* @subscription: pointer to local processor's row in subscription matrix
* @state: smem state handle
* @lock: spinlock for read-modify-write of the outgoing state
* @entries: context for each of the entries
* @hosts: context for each of the hosts
*/
struct qcom_smsm {
struct device *dev;
u32 local_host;
u32 num_hosts;
u32 num_entries;
u32 *local_state;
u32 *subscription;
struct qcom_smem_state *state;
spinlock_t lock;
struct smsm_entry *entries;
struct smsm_host *hosts;
};
/**
* struct smsm_entry - per remote processor entry context
* @smsm: back-reference to driver context
* @domain: IRQ domain for this entry, if representing a remote system
* @irq_enabled: bitmap of which state bits IRQs are enabled
* @irq_rising: bitmap tracking if rising bits should be propagated
* @irq_falling: bitmap tracking if falling bits should be propagated
* @last_value: snapshot of state bits last time the interrupts where propagated
* @remote_state: pointer to this entry's state bits
* @subscription: pointer to a row in the subscription matrix representing this
* entry
*/
struct smsm_entry {
struct qcom_smsm *smsm;
struct irq_domain *domain;
DECLARE_BITMAP(irq_enabled, 32);
DECLARE_BITMAP(irq_rising, 32);
DECLARE_BITMAP(irq_falling, 32);
u32 last_value;
u32 *remote_state;
u32 *subscription;
};
/**
* struct smsm_host - representation of a remote host
* @ipc_regmap: regmap for outgoing interrupt
* @ipc_offset: offset in @ipc_regmap for outgoing interrupt
* @ipc_bit: bit in @ipc_regmap + @ipc_offset for outgoing interrupt
*/
struct smsm_host {
struct regmap *ipc_regmap;
int ipc_offset;
int ipc_bit;
};
/**
* smsm_update_bits() - change bit in outgoing entry and inform subscribers
* @data: smsm context pointer
* @offset: bit in the entry
* @value: new value
*
* Used to set and clear the bits in the outgoing/local entry and inform
* subscribers about the change.
*/
static int smsm_update_bits(void *data, u32 mask, u32 value)
{
struct qcom_smsm *smsm = data;
struct smsm_host *hostp;
unsigned long flags;
u32 changes;
u32 host;
u32 orig;
u32 val;
spin_lock_irqsave(&smsm->lock, flags);
/* Update the entry */
val = orig = readl(smsm->local_state);
val &= ~mask;
val |= value;
/* Don't signal if we didn't change the value */
changes = val ^ orig;
if (!changes) {
spin_unlock_irqrestore(&smsm->lock, flags);
goto done;
}
/* Write out the new value */
writel(val, smsm->local_state);
spin_unlock_irqrestore(&smsm->lock, flags);
/* Make sure the value update is ordered before any kicks */
wmb();
/* Iterate over all hosts to check whom wants a kick */
for (host = 0; host < smsm->num_hosts; host++) {
hostp = &smsm->hosts[host];
val = readl(smsm->subscription + host);
if (val & changes && hostp->ipc_regmap) {
regmap_write(hostp->ipc_regmap,
hostp->ipc_offset,
BIT(hostp->ipc_bit));
}
}
done:
return 0;
}
static const struct qcom_smem_state_ops smsm_state_ops = {
.update_bits = smsm_update_bits,
};
/**
* smsm_intr() - cascading IRQ handler for SMSM
* @irq: unused
* @data: entry related to this IRQ
*
* This function cascades an incoming interrupt from a remote system, based on
* the state bits and configuration.
*/
static irqreturn_t smsm_intr(int irq, void *data)
{
struct smsm_entry *entry = data;
unsigned i;
int irq_pin;
u32 changed;
u32 val;
val = readl(entry->remote_state);
changed = val ^ entry->last_value;
entry->last_value = val;
for_each_set_bit(i, entry->irq_enabled, 32) {
if (!(changed & BIT(i)))
continue;
if (val & BIT(i)) {
if (test_bit(i, entry->irq_rising)) {
irq_pin = irq_find_mapping(entry->domain, i);
handle_nested_irq(irq_pin);
}
} else {
if (test_bit(i, entry->irq_falling)) {
irq_pin = irq_find_mapping(entry->domain, i);
handle_nested_irq(irq_pin);
}
}
}
return IRQ_HANDLED;
}
/**
* smsm_mask_irq() - un-subscribe from cascades of IRQs of a certain staus bit
* @irqd: IRQ handle to be masked
*
* This un-subscribes the local CPU from interrupts upon changes to the defines
* status bit. The bit is also cleared from cascading.
*/
static void smsm_mask_irq(struct irq_data *irqd)
{
struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd);
struct qcom_smsm *smsm = entry->smsm;
u32 val;
if (entry->subscription) {
val = readl(entry->subscription + smsm->local_host);
val &= ~BIT(irq);
writel(val, entry->subscription + smsm->local_host);
}
clear_bit(irq, entry->irq_enabled);
}
/**
* smsm_unmask_irq() - subscribe to cascades of IRQs of a certain status bit
* @irqd: IRQ handle to be unmasked
*
* This subscribes the local CPU to interrupts upon changes to the defined
* status bit. The bit is also marked for cascading.
*/
static void smsm_unmask_irq(struct irq_data *irqd)
{
struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd);
struct qcom_smsm *smsm = entry->smsm;
u32 val;
set_bit(irq, entry->irq_enabled);
if (entry->subscription) {
val = readl(entry->subscription + smsm->local_host);
val |= BIT(irq);
writel(val, entry->subscription + smsm->local_host);
}
}
/**
* smsm_set_irq_type() - updates the requested IRQ type for the cascading
* @irqd: consumer interrupt handle
* @type: requested flags
*/
static int smsm_set_irq_type(struct irq_data *irqd, unsigned int type)
{
struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd);
if (!(type & IRQ_TYPE_EDGE_BOTH))
return -EINVAL;
if (type & IRQ_TYPE_EDGE_RISING)
set_bit(irq, entry->irq_rising);
else
clear_bit(irq, entry->irq_rising);
if (type & IRQ_TYPE_EDGE_FALLING)
set_bit(irq, entry->irq_falling);
else
clear_bit(irq, entry->irq_falling);
return 0;
}
static struct irq_chip smsm_irq_chip = {
.name = "smsm",
.irq_mask = smsm_mask_irq,
.irq_unmask = smsm_unmask_irq,
.irq_set_type = smsm_set_irq_type,
};
/**
* smsm_irq_map() - sets up a mapping for a cascaded IRQ
* @d: IRQ domain representing an entry
* @irq: IRQ to set up
* @hw: unused
*/
static int smsm_irq_map(struct irq_domain *d,
unsigned int irq,
irq_hw_number_t hw)
{
struct smsm_entry *entry = d->host_data;
irq_set_chip_and_handler(irq, &smsm_irq_chip, handle_level_irq);
irq_set_chip_data(irq, entry);
irq_set_nested_thread(irq, 1);
return 0;
}
static const struct irq_domain_ops smsm_irq_ops = {
.map = smsm_irq_map,
.xlate = irq_domain_xlate_twocell,
};
/**
* smsm_parse_ipc() - parses a qcom,ipc-%d device tree property
* @smsm: smsm driver context
* @host_id: index of the remote host to be resolved
*
* Parses device tree to acquire the information needed for sending the
* outgoing interrupts to a remote host - identified by @host_id.
*/
static int smsm_parse_ipc(struct qcom_smsm *smsm, unsigned host_id)
{
struct device_node *syscon;
struct device_node *node = smsm->dev->of_node;
struct smsm_host *host = &smsm->hosts[host_id];
char key[16];
int ret;
snprintf(key, sizeof(key), "qcom,ipc-%d", host_id);
syscon = of_parse_phandle(node, key, 0);
if (!syscon)
return 0;
host->ipc_regmap = syscon_node_to_regmap(syscon);
if (IS_ERR(host->ipc_regmap))
return PTR_ERR(host->ipc_regmap);
ret = of_property_read_u32_index(node, key, 1, &host->ipc_offset);
if (ret < 0) {
dev_err(smsm->dev, "no offset in %s\n", key);
return -EINVAL;
}
ret = of_property_read_u32_index(node, key, 2, &host->ipc_bit);
if (ret < 0) {
dev_err(smsm->dev, "no bit in %s\n", key);
return -EINVAL;
}
return 0;
}
/**
* smsm_inbound_entry() - parse DT and set up an entry representing a remote system
* @smsm: smsm driver context
* @entry: entry context to be set up
* @node: dt node containing the entry's properties
*/
static int smsm_inbound_entry(struct qcom_smsm *smsm,
struct smsm_entry *entry,
struct device_node *node)
{
int ret;
int irq;
irq = irq_of_parse_and_map(node, 0);
if (!irq) {
dev_err(smsm->dev, "failed to parse smsm interrupt\n");
return -EINVAL;
}
ret = devm_request_threaded_irq(smsm->dev, irq,
NULL, smsm_intr,
IRQF_ONESHOT,
"smsm", (void *)entry);
if (ret) {
dev_err(smsm->dev, "failed to request interrupt\n");
return ret;
}
entry->domain = irq_domain_add_linear(node, 32, &smsm_irq_ops, entry);
if (!entry->domain) {
dev_err(smsm->dev, "failed to add irq_domain\n");
return -ENOMEM;
}
return 0;
}
/**
* smsm_get_size_info() - parse the optional memory segment for sizes
* @smsm: smsm driver context
*
* Attempt to acquire the number of hosts and entries from the optional shared
* memory location. Not being able to find this segment should indicate that
* we're on a older system where these values was hard coded to
* SMSM_DEFAULT_NUM_ENTRIES and SMSM_DEFAULT_NUM_HOSTS.
*
* Returns 0 on success, negative errno on failure.
*/
static int smsm_get_size_info(struct qcom_smsm *smsm)
{
size_t size;
struct {
u32 num_hosts;
u32 num_entries;
u32 reserved0;
u32 reserved1;
} *info;
info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SIZE_INFO, &size);
if (PTR_ERR(info) == -ENOENT || size != sizeof(*info)) {
dev_warn(smsm->dev, "no smsm size info, using defaults\n");
smsm->num_entries = SMSM_DEFAULT_NUM_ENTRIES;
smsm->num_hosts = SMSM_DEFAULT_NUM_HOSTS;
return 0;
} else if (IS_ERR(info)) {
dev_err(smsm->dev, "unable to retrieve smsm size info\n");
return PTR_ERR(info);
}
smsm->num_entries = info->num_entries;
smsm->num_hosts = info->num_hosts;
dev_dbg(smsm->dev,
"found custom size of smsm: %d entries %d hosts\n",
smsm->num_entries, smsm->num_hosts);
return 0;
}
static int qcom_smsm_probe(struct platform_device *pdev)
{
struct device_node *local_node;
struct device_node *node;
struct smsm_entry *entry;
struct qcom_smsm *smsm;
u32 *intr_mask;
size_t size;
u32 *states;
u32 id;
int ret;
smsm = devm_kzalloc(&pdev->dev, sizeof(*smsm), GFP_KERNEL);
if (!smsm)
return -ENOMEM;
smsm->dev = &pdev->dev;
spin_lock_init(&smsm->lock);
ret = smsm_get_size_info(smsm);
if (ret)
return ret;
smsm->entries = devm_kcalloc(&pdev->dev,
smsm->num_entries,
sizeof(struct smsm_entry),
GFP_KERNEL);
if (!smsm->entries)
return -ENOMEM;
smsm->hosts = devm_kcalloc(&pdev->dev,
smsm->num_hosts,
sizeof(struct smsm_host),
GFP_KERNEL);
if (!smsm->hosts)
return -ENOMEM;
local_node = of_find_node_with_property(pdev->dev.of_node, "#qcom,state-cells");
if (!local_node) {
dev_err(&pdev->dev, "no state entry\n");
return -EINVAL;
}
of_property_read_u32(pdev->dev.of_node,
"qcom,local-host",
&smsm->local_host);
/* Parse the host properties */
for (id = 0; id < smsm->num_hosts; id++) {
ret = smsm_parse_ipc(smsm, id);
if (ret < 0)
return ret;
}
/* Acquire the main SMSM state vector */
ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE,
smsm->num_entries * sizeof(u32));
if (ret < 0 && ret != -EEXIST) {
dev_err(&pdev->dev, "unable to allocate shared state entry\n");
return ret;
}
states = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE, NULL);
if (IS_ERR(states)) {
dev_err(&pdev->dev, "Unable to acquire shared state entry\n");
return PTR_ERR(states);
}
/* Acquire the list of interrupt mask vectors */
size = smsm->num_entries * smsm->num_hosts * sizeof(u32);
ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, size);
if (ret < 0 && ret != -EEXIST) {
dev_err(&pdev->dev, "unable to allocate smsm interrupt mask\n");
return ret;
}
intr_mask = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, NULL);
if (IS_ERR(intr_mask)) {
dev_err(&pdev->dev, "unable to acquire shared memory interrupt mask\n");
return PTR_ERR(intr_mask);
}
/* Setup the reference to the local state bits */
smsm->local_state = states + smsm->local_host;
smsm->subscription = intr_mask + smsm->local_host * smsm->num_hosts;
/* Register the outgoing state */
smsm->state = qcom_smem_state_register(local_node, &smsm_state_ops, smsm);
if (IS_ERR(smsm->state)) {
dev_err(smsm->dev, "failed to register qcom_smem_state\n");
return PTR_ERR(smsm->state);
}
/* Register handlers for remote processor entries of interest. */
for_each_available_child_of_node(pdev->dev.of_node, node) {
if (!of_property_read_bool(node, "interrupt-controller"))
continue;
ret = of_property_read_u32(node, "reg", &id);
if (ret || id >= smsm->num_entries) {
dev_err(&pdev->dev, "invalid reg of entry\n");
if (!ret)
ret = -EINVAL;
goto unwind_interfaces;
}
entry = &smsm->entries[id];
entry->smsm = smsm;
entry->remote_state = states + id;
/* Setup subscription pointers and unsubscribe to any kicks */
entry->subscription = intr_mask + id * smsm->num_hosts;
writel(0, entry->subscription + smsm->local_host);
ret = smsm_inbound_entry(smsm, entry, node);
if (ret < 0)
goto unwind_interfaces;
}
platform_set_drvdata(pdev, smsm);
return 0;
unwind_interfaces:
for (id = 0; id < smsm->num_entries; id++)
if (smsm->entries[id].domain)
irq_domain_remove(smsm->entries[id].domain);
qcom_smem_state_unregister(smsm->state);
return ret;
}
static int qcom_smsm_remove(struct platform_device *pdev)
{
struct qcom_smsm *smsm = platform_get_drvdata(pdev);
unsigned id;
for (id = 0; id < smsm->num_entries; id++)
if (smsm->entries[id].domain)
irq_domain_remove(smsm->entries[id].domain);
qcom_smem_state_unregister(smsm->state);
return 0;
}
static const struct of_device_id qcom_smsm_of_match[] = {
{ .compatible = "qcom,smsm" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_smsm_of_match);
static struct platform_driver qcom_smsm_driver = {
.probe = qcom_smsm_probe,
.remove = qcom_smsm_remove,
.driver = {
.name = "qcom-smsm",
.of_match_table = qcom_smsm_of_match,
},
};
module_platform_driver(qcom_smsm_driver);
MODULE_DESCRIPTION("Qualcomm Shared Memory State Machine driver");
MODULE_LICENSE("GPL v2");
/*
* Copyright (c) 2015, Sony Mobile Communications Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smd.h>
#define WCNSS_REQUEST_TIMEOUT (5 * HZ)
#define NV_FRAGMENT_SIZE 3072
#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
/**
* struct wcnss_ctrl - driver context
* @dev: device handle
* @channel: SMD channel handle
* @ack: completion for outstanding requests
* @ack_status: status of the outstanding request
* @download_nv_work: worker for uploading nv binary
*/
struct wcnss_ctrl {
struct device *dev;
struct qcom_smd_channel *channel;
struct completion ack;
int ack_status;
struct work_struct download_nv_work;
};
/* message types */
enum {
WCNSS_VERSION_REQ = 0x01000000,
WCNSS_VERSION_RESP,
WCNSS_DOWNLOAD_NV_REQ,
WCNSS_DOWNLOAD_NV_RESP,
WCNSS_UPLOAD_CAL_REQ,
WCNSS_UPLOAD_CAL_RESP,
WCNSS_DOWNLOAD_CAL_REQ,
WCNSS_DOWNLOAD_CAL_RESP,
};
/**
* struct wcnss_msg_hdr - common packet header for requests and responses
* @type: packet message type
* @len: total length of the packet, including this header
*/
struct wcnss_msg_hdr {
u32 type;
u32 len;
} __packed;
/**
* struct wcnss_version_resp - version request response
* @hdr: common packet wcnss_msg_hdr header
*/
struct wcnss_version_resp {
struct wcnss_msg_hdr hdr;
u8 major;
u8 minor;
u8 version;
u8 revision;
} __packed;
/**
* struct wcnss_download_nv_req - firmware fragment request
* @hdr: common packet wcnss_msg_hdr header
* @seq: sequence number of this fragment
* @last: boolean indicator of this being the last fragment of the binary
* @frag_size: length of this fragment
* @fragment: fragment data
*/
struct wcnss_download_nv_req {
struct wcnss_msg_hdr hdr;
u16 seq;
u16 last;
u32 frag_size;
u8 fragment[];
} __packed;
/**
* struct wcnss_download_nv_resp - firmware download response
* @hdr: common packet wcnss_msg_hdr header
* @status: boolean to indicate success of the download
*/
struct wcnss_download_nv_resp {
struct wcnss_msg_hdr hdr;
u8 status;
} __packed;
/**
* wcnss_ctrl_smd_callback() - handler from SMD responses
* @qsdev: smd device handle
* @data: pointer to the incoming data packet
* @count: size of the incoming data packet
*
* Handles any incoming packets from the remote WCNSS_CTRL service.
*/
static int wcnss_ctrl_smd_callback(struct qcom_smd_device *qsdev,
const void *data,
size_t count)
{
struct wcnss_ctrl *wcnss = dev_get_drvdata(&qsdev->dev);
const struct wcnss_download_nv_resp *nvresp;
const struct wcnss_version_resp *version;
const struct wcnss_msg_hdr *hdr = data;
switch (hdr->type) {
case WCNSS_VERSION_RESP:
if (count != sizeof(*version)) {
dev_err(wcnss->dev,
"invalid size of version response\n");
break;
}
version = data;
dev_info(wcnss->dev, "WCNSS Version %d.%d %d.%d\n",
version->major, version->minor,
version->version, version->revision);
schedule_work(&wcnss->download_nv_work);
break;
case WCNSS_DOWNLOAD_NV_RESP:
if (count != sizeof(*nvresp)) {
dev_err(wcnss->dev,
"invalid size of download response\n");
break;
}
nvresp = data;
wcnss->ack_status = nvresp->status;
complete(&wcnss->ack);
break;
default:
dev_info(wcnss->dev, "unknown message type %d\n", hdr->type);
break;
}
return 0;
}
/**
* wcnss_request_version() - send a version request to WCNSS
* @wcnss: wcnss ctrl driver context
*/
static int wcnss_request_version(struct wcnss_ctrl *wcnss)
{
struct wcnss_msg_hdr msg;
msg.type = WCNSS_VERSION_REQ;
msg.len = sizeof(msg);
return qcom_smd_send(wcnss->channel, &msg, sizeof(msg));
}
/**
* wcnss_download_nv() - send nv binary to WCNSS
* @work: work struct to acquire wcnss context
*/
static void wcnss_download_nv(struct work_struct *work)
{
struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, download_nv_work);
struct wcnss_download_nv_req *req;
const struct firmware *fw;
const void *data;
ssize_t left;
int ret;
req = kzalloc(sizeof(*req) + NV_FRAGMENT_SIZE, GFP_KERNEL);
if (!req)
return;
ret = request_firmware(&fw, NVBIN_FILE, wcnss->dev);
if (ret) {
dev_err(wcnss->dev, "Failed to load nv file %s: %d\n",
NVBIN_FILE, ret);
goto free_req;
}
data = fw->data;
left = fw->size;
req->hdr.type = WCNSS_DOWNLOAD_NV_REQ;
req->hdr.len = sizeof(*req) + NV_FRAGMENT_SIZE;
req->last = 0;
req->frag_size = NV_FRAGMENT_SIZE;
req->seq = 0;
do {
if (left <= NV_FRAGMENT_SIZE) {
req->last = 1;
req->frag_size = left;
req->hdr.len = sizeof(*req) + left;
}
memcpy(req->fragment, data, req->frag_size);
ret = qcom_smd_send(wcnss->channel, req, req->hdr.len);
if (ret) {
dev_err(wcnss->dev, "failed to send smd packet\n");
goto release_fw;
}
/* Increment for next fragment */
req->seq++;
data += req->hdr.len;
left -= NV_FRAGMENT_SIZE;
} while (left > 0);
ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_REQUEST_TIMEOUT);
if (!ret)
dev_err(wcnss->dev, "timeout waiting for nv upload ack\n");
else if (wcnss->ack_status != 1)
dev_err(wcnss->dev, "nv upload response failed err: %d\n",
wcnss->ack_status);
release_fw:
release_firmware(fw);
free_req:
kfree(req);
}
static int wcnss_ctrl_probe(struct qcom_smd_device *sdev)
{
struct wcnss_ctrl *wcnss;
wcnss = devm_kzalloc(&sdev->dev, sizeof(*wcnss), GFP_KERNEL);
if (!wcnss)
return -ENOMEM;
wcnss->dev = &sdev->dev;
wcnss->channel = sdev->channel;
init_completion(&wcnss->ack);
INIT_WORK(&wcnss->download_nv_work, wcnss_download_nv);
dev_set_drvdata(&sdev->dev, wcnss);
return wcnss_request_version(wcnss);
}
static const struct qcom_smd_id wcnss_ctrl_smd_match[] = {
{ .name = "WCNSS_CTRL" },
{}
};
static struct qcom_smd_driver wcnss_ctrl_driver = {
.probe = wcnss_ctrl_probe,
.callback = wcnss_ctrl_smd_callback,
.smd_match_table = wcnss_ctrl_smd_match,
.driver = {
.name = "qcom_wcnss_ctrl",
.owner = THIS_MODULE,
},
};
module_qcom_smd_driver(wcnss_ctrl_driver);
MODULE_DESCRIPTION("Qualcomm WCNSS control driver");
MODULE_LICENSE("GPL v2");
......@@ -28,4 +28,14 @@ config KEYSTONE_NAVIGATOR_DMA
If unsure, say N.
config WKUP_M3_IPC
tristate "TI AMx3 Wkup-M3 IPC Driver"
depends on WKUP_M3_RPROC
depends on OMAP2PLUS_MBOX
help
TI AM33XX and AM43XX have a Cortex M3, the Wakeup M3, to handle
low power transitions. This IPC driver provides the necessary API
to communicate and use the Wakeup M3 for PM features like suspend
resume and boots it using wkup_m3_rproc driver.
endif # SOC_TI
......@@ -4,3 +4,4 @@
obj-$(CONFIG_KEYSTONE_NAVIGATOR_QMSS) += knav_qmss.o
knav_qmss-y := knav_qmss_queue.o knav_qmss_acc.o
obj-$(CONFIG_KEYSTONE_NAVIGATOR_DMA) += knav_dma.o
obj-$(CONFIG_WKUP_M3_IPC) += wkup_m3_ipc.o
/*
* AMx3 Wkup M3 IPC driver
*
* Copyright (C) 2015 Texas Instruments, Inc.
*
* Dave Gerlach <d-gerlach@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/omap-mailbox.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/suspend.h>
#include <linux/wkup_m3_ipc.h>
#define AM33XX_CTRL_IPC_REG_COUNT 0x8
#define AM33XX_CTRL_IPC_REG_OFFSET(m) (0x4 + 4 * (m))
/* AM33XX M3_TXEV_EOI register */
#define AM33XX_CONTROL_M3_TXEV_EOI 0x00
#define AM33XX_M3_TXEV_ACK (0x1 << 0)
#define AM33XX_M3_TXEV_ENABLE (0x0 << 0)
#define IPC_CMD_DS0 0x4
#define IPC_CMD_STANDBY 0xc
#define IPC_CMD_IDLE 0x10
#define IPC_CMD_RESET 0xe
#define DS_IPC_DEFAULT 0xffffffff
#define M3_VERSION_UNKNOWN 0x0000ffff
#define M3_BASELINE_VERSION 0x191
#define M3_STATUS_RESP_MASK (0xffff << 16)
#define M3_FW_VERSION_MASK 0xffff
#define M3_STATE_UNKNOWN 0
#define M3_STATE_RESET 1
#define M3_STATE_INITED 2
#define M3_STATE_MSG_FOR_LP 3
#define M3_STATE_MSG_FOR_RESET 4
static struct wkup_m3_ipc *m3_ipc_state;
static void am33xx_txev_eoi(struct wkup_m3_ipc *m3_ipc)
{
writel(AM33XX_M3_TXEV_ACK,
m3_ipc->ipc_mem_base + AM33XX_CONTROL_M3_TXEV_EOI);
}
static void am33xx_txev_enable(struct wkup_m3_ipc *m3_ipc)
{
writel(AM33XX_M3_TXEV_ENABLE,
m3_ipc->ipc_mem_base + AM33XX_CONTROL_M3_TXEV_EOI);
}
static void wkup_m3_ctrl_ipc_write(struct wkup_m3_ipc *m3_ipc,
u32 val, int ipc_reg_num)
{
if (WARN(ipc_reg_num < 0 || ipc_reg_num > AM33XX_CTRL_IPC_REG_COUNT,
"ipc register operation out of range"))
return;
writel(val, m3_ipc->ipc_mem_base +
AM33XX_CTRL_IPC_REG_OFFSET(ipc_reg_num));
}
static unsigned int wkup_m3_ctrl_ipc_read(struct wkup_m3_ipc *m3_ipc,
int ipc_reg_num)
{
if (WARN(ipc_reg_num < 0 || ipc_reg_num > AM33XX_CTRL_IPC_REG_COUNT,
"ipc register operation out of range"))
return 0;
return readl(m3_ipc->ipc_mem_base +
AM33XX_CTRL_IPC_REG_OFFSET(ipc_reg_num));
}
static int wkup_m3_fw_version_read(struct wkup_m3_ipc *m3_ipc)
{
int val;
val = wkup_m3_ctrl_ipc_read(m3_ipc, 2);
return val & M3_FW_VERSION_MASK;
}
static irqreturn_t wkup_m3_txev_handler(int irq, void *ipc_data)
{
struct wkup_m3_ipc *m3_ipc = ipc_data;
struct device *dev = m3_ipc->dev;
int ver = 0;
am33xx_txev_eoi(m3_ipc);
switch (m3_ipc->state) {
case M3_STATE_RESET:
ver = wkup_m3_fw_version_read(m3_ipc);
if (ver == M3_VERSION_UNKNOWN ||
ver < M3_BASELINE_VERSION) {
dev_warn(dev, "CM3 Firmware Version %x not supported\n",
ver);
} else {
dev_info(dev, "CM3 Firmware Version = 0x%x\n", ver);
}
m3_ipc->state = M3_STATE_INITED;
complete(&m3_ipc->sync_complete);
break;
case M3_STATE_MSG_FOR_RESET:
m3_ipc->state = M3_STATE_INITED;
complete(&m3_ipc->sync_complete);
break;
case M3_STATE_MSG_FOR_LP:
complete(&m3_ipc->sync_complete);
break;
case M3_STATE_UNKNOWN:
dev_warn(dev, "Unknown CM3 State\n");
}
am33xx_txev_enable(m3_ipc);
return IRQ_HANDLED;
}
static int wkup_m3_ping(struct wkup_m3_ipc *m3_ipc)
{
struct device *dev = m3_ipc->dev;
mbox_msg_t dummy_msg = 0;
int ret;
if (!m3_ipc->mbox) {
dev_err(dev,
"No IPC channel to communicate with wkup_m3!\n");
return -EIO;
}
/*
* Write a dummy message to the mailbox in order to trigger the RX
* interrupt to alert the M3 that data is available in the IPC
* registers. We must enable the IRQ here and disable it after in
* the RX callback to avoid multiple interrupts being received
* by the CM3.
*/
ret = mbox_send_message(m3_ipc->mbox, &dummy_msg);
if (ret < 0) {
dev_err(dev, "%s: mbox_send_message() failed: %d\n",
__func__, ret);
return ret;
}
ret = wait_for_completion_timeout(&m3_ipc->sync_complete,
msecs_to_jiffies(500));
if (!ret) {
dev_err(dev, "MPU<->CM3 sync failure\n");
m3_ipc->state = M3_STATE_UNKNOWN;
return -EIO;
}
mbox_client_txdone(m3_ipc->mbox, 0);
return 0;
}
static int wkup_m3_ping_noirq(struct wkup_m3_ipc *m3_ipc)
{
struct device *dev = m3_ipc->dev;
mbox_msg_t dummy_msg = 0;
int ret;
if (!m3_ipc->mbox) {
dev_err(dev,
"No IPC channel to communicate with wkup_m3!\n");
return -EIO;
}
ret = mbox_send_message(m3_ipc->mbox, &dummy_msg);
if (ret < 0) {
dev_err(dev, "%s: mbox_send_message() failed: %d\n",
__func__, ret);
return ret;
}
mbox_client_txdone(m3_ipc->mbox, 0);
return 0;
}
static int wkup_m3_is_available(struct wkup_m3_ipc *m3_ipc)
{
return ((m3_ipc->state != M3_STATE_RESET) &&
(m3_ipc->state != M3_STATE_UNKNOWN));
}
/* Public functions */
/**
* wkup_m3_set_mem_type - Pass wkup_m3 which type of memory is in use
* @mem_type: memory type value read directly from emif
*
* wkup_m3 must know what memory type is in use to properly suspend
* and resume.
*/
static void wkup_m3_set_mem_type(struct wkup_m3_ipc *m3_ipc, int mem_type)
{
m3_ipc->mem_type = mem_type;
}
/**
* wkup_m3_set_resume_address - Pass wkup_m3 resume address
* @addr: Physical address from which resume code should execute
*/
static void wkup_m3_set_resume_address(struct wkup_m3_ipc *m3_ipc, void *addr)
{
m3_ipc->resume_addr = (unsigned long)addr;
}
/**
* wkup_m3_request_pm_status - Retrieve wkup_m3 status code after suspend
*
* Returns code representing the status of a low power mode transition.
* 0 - Successful transition
* 1 - Failure to transition to low power state
*/
static int wkup_m3_request_pm_status(struct wkup_m3_ipc *m3_ipc)
{
unsigned int i;
int val;
val = wkup_m3_ctrl_ipc_read(m3_ipc, 1);
i = M3_STATUS_RESP_MASK & val;
i >>= __ffs(M3_STATUS_RESP_MASK);
return i;
}
/**
* wkup_m3_prepare_low_power - Request preparation for transition to
* low power state
* @state: A kernel suspend state to enter, either MEM or STANDBY
*
* Returns 0 if preparation was successful, otherwise returns error code
*/
static int wkup_m3_prepare_low_power(struct wkup_m3_ipc *m3_ipc, int state)
{
struct device *dev = m3_ipc->dev;
int m3_power_state;
int ret = 0;
if (!wkup_m3_is_available(m3_ipc))
return -ENODEV;
switch (state) {
case WKUP_M3_DEEPSLEEP:
m3_power_state = IPC_CMD_DS0;
break;
case WKUP_M3_STANDBY:
m3_power_state = IPC_CMD_STANDBY;
break;
case WKUP_M3_IDLE:
m3_power_state = IPC_CMD_IDLE;
break;
default:
return 1;
}
/* Program each required IPC register then write defaults to others */
wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->resume_addr, 0);
wkup_m3_ctrl_ipc_write(m3_ipc, m3_power_state, 1);
wkup_m3_ctrl_ipc_write(m3_ipc, m3_ipc->mem_type, 4);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 2);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 3);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 5);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 6);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 7);
m3_ipc->state = M3_STATE_MSG_FOR_LP;
if (state == WKUP_M3_IDLE)
ret = wkup_m3_ping_noirq(m3_ipc);
else
ret = wkup_m3_ping(m3_ipc);
if (ret) {
dev_err(dev, "Unable to ping CM3\n");
return ret;
}
return 0;
}
/**
* wkup_m3_finish_low_power - Return m3 to reset state
*
* Returns 0 if reset was successful, otherwise returns error code
*/
static int wkup_m3_finish_low_power(struct wkup_m3_ipc *m3_ipc)
{
struct device *dev = m3_ipc->dev;
int ret = 0;
if (!wkup_m3_is_available(m3_ipc))
return -ENODEV;
wkup_m3_ctrl_ipc_write(m3_ipc, IPC_CMD_RESET, 1);
wkup_m3_ctrl_ipc_write(m3_ipc, DS_IPC_DEFAULT, 2);
m3_ipc->state = M3_STATE_MSG_FOR_RESET;
ret = wkup_m3_ping(m3_ipc);
if (ret) {
dev_err(dev, "Unable to ping CM3\n");
return ret;
}
return 0;
}
static struct wkup_m3_ipc_ops ipc_ops = {
.set_mem_type = wkup_m3_set_mem_type,
.set_resume_address = wkup_m3_set_resume_address,
.prepare_low_power = wkup_m3_prepare_low_power,
.finish_low_power = wkup_m3_finish_low_power,
.request_pm_status = wkup_m3_request_pm_status,
};
/**
* wkup_m3_ipc_get - Return handle to wkup_m3_ipc
*
* Returns NULL if the wkup_m3 is not yet available, otherwise returns
* pointer to wkup_m3_ipc struct.
*/
struct wkup_m3_ipc *wkup_m3_ipc_get(void)
{
if (m3_ipc_state)
get_device(m3_ipc_state->dev);
else
return NULL;
return m3_ipc_state;
}
EXPORT_SYMBOL_GPL(wkup_m3_ipc_get);
/**
* wkup_m3_ipc_put - Free handle to wkup_m3_ipc returned from wkup_m3_ipc_get
* @m3_ipc: A pointer to wkup_m3_ipc struct returned by wkup_m3_ipc_get
*/
void wkup_m3_ipc_put(struct wkup_m3_ipc *m3_ipc)
{
if (m3_ipc_state)
put_device(m3_ipc_state->dev);
}
EXPORT_SYMBOL_GPL(wkup_m3_ipc_put);
static void wkup_m3_rproc_boot_thread(struct wkup_m3_ipc *m3_ipc)
{
struct device *dev = m3_ipc->dev;
int ret;
wait_for_completion(&m3_ipc->rproc->firmware_loading_complete);
init_completion(&m3_ipc->sync_complete);
ret = rproc_boot(m3_ipc->rproc);
if (ret)
dev_err(dev, "rproc_boot failed\n");
do_exit(0);
}
static int wkup_m3_ipc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int irq, ret;
phandle rproc_phandle;
struct rproc *m3_rproc;
struct resource *res;
struct task_struct *task;
struct wkup_m3_ipc *m3_ipc;
m3_ipc = devm_kzalloc(dev, sizeof(*m3_ipc), GFP_KERNEL);
if (!m3_ipc)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
m3_ipc->ipc_mem_base = devm_ioremap_resource(dev, res);
if (IS_ERR(m3_ipc->ipc_mem_base)) {
dev_err(dev, "could not ioremap ipc_mem\n");
return PTR_ERR(m3_ipc->ipc_mem_base);
}
irq = platform_get_irq(pdev, 0);
if (!irq) {
dev_err(&pdev->dev, "no irq resource\n");
return -ENXIO;
}
ret = devm_request_irq(dev, irq, wkup_m3_txev_handler,
0, "wkup_m3_txev", m3_ipc);
if (ret) {
dev_err(dev, "request_irq failed\n");
return ret;
}
m3_ipc->mbox_client.dev = dev;
m3_ipc->mbox_client.tx_done = NULL;
m3_ipc->mbox_client.tx_prepare = NULL;
m3_ipc->mbox_client.rx_callback = NULL;
m3_ipc->mbox_client.tx_block = false;
m3_ipc->mbox_client.knows_txdone = false;
m3_ipc->mbox = mbox_request_channel(&m3_ipc->mbox_client, 0);
if (IS_ERR(m3_ipc->mbox)) {
dev_err(dev, "IPC Request for A8->M3 Channel failed! %ld\n",
PTR_ERR(m3_ipc->mbox));
return PTR_ERR(m3_ipc->mbox);
}
if (of_property_read_u32(dev->of_node, "ti,rproc", &rproc_phandle)) {
dev_err(&pdev->dev, "could not get rproc phandle\n");
ret = -ENODEV;
goto err_free_mbox;
}
m3_rproc = rproc_get_by_phandle(rproc_phandle);
if (!m3_rproc) {
dev_err(&pdev->dev, "could not get rproc handle\n");
ret = -EPROBE_DEFER;
goto err_free_mbox;
}
m3_ipc->rproc = m3_rproc;
m3_ipc->dev = dev;
m3_ipc->state = M3_STATE_RESET;
m3_ipc->ops = &ipc_ops;
/*
* Wait for firmware loading completion in a thread so we
* can boot the wkup_m3 as soon as it's ready without holding
* up kernel boot
*/
task = kthread_run((void *)wkup_m3_rproc_boot_thread, m3_ipc,
"wkup_m3_rproc_loader");
if (IS_ERR(task)) {
dev_err(dev, "can't create rproc_boot thread\n");
goto err_put_rproc;
}
m3_ipc_state = m3_ipc;
return 0;
err_put_rproc:
rproc_put(m3_rproc);
err_free_mbox:
mbox_free_channel(m3_ipc->mbox);
return ret;
}
static int wkup_m3_ipc_remove(struct platform_device *pdev)
{
mbox_free_channel(m3_ipc_state->mbox);
rproc_shutdown(m3_ipc_state->rproc);
rproc_put(m3_ipc_state->rproc);
m3_ipc_state = NULL;
return 0;
}
static const struct of_device_id wkup_m3_ipc_of_match[] = {
{ .compatible = "ti,am3352-wkup-m3-ipc", },
{ .compatible = "ti,am4372-wkup-m3-ipc", },
{},
};
MODULE_DEVICE_TABLE(of, wkup_m3_ipc_of_match);
static struct platform_driver wkup_m3_ipc_driver = {
.probe = wkup_m3_ipc_probe,
.remove = wkup_m3_ipc_remove,
.driver = {
.name = "wkup_m3_ipc",
.of_match_table = wkup_m3_ipc_of_match,
},
};
module_platform_driver(wkup_m3_ipc_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("wkup m3 remote processor ipc driver");
MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>");
......@@ -1047,7 +1047,7 @@ config SERIAL_SGI_IOC3
say Y or M. Otherwise, say N.
config SERIAL_MSM
bool "MSM on-chip serial port support"
tristate "MSM on-chip serial port support"
depends on ARCH_QCOM
select SERIAL_CORE
......
/*
* 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.
*/
#ifndef _DT_BINDINGS_ARM_BCM2835_RPI_POWER_H
#define _DT_BINDINGS_ARM_BCM2835_RPI_POWER_H
/* These power domain indices are the firmware interface's indices
* minus one.
*/
#define RPI_POWER_DOMAIN_I2C0 0
#define RPI_POWER_DOMAIN_I2C1 1
#define RPI_POWER_DOMAIN_I2C2 2
#define RPI_POWER_DOMAIN_VIDEO_SCALER 3
#define RPI_POWER_DOMAIN_VPU1 4
#define RPI_POWER_DOMAIN_HDMI 5
#define RPI_POWER_DOMAIN_USB 6
#define RPI_POWER_DOMAIN_VEC 7
#define RPI_POWER_DOMAIN_JPEG 8
#define RPI_POWER_DOMAIN_H264 9
#define RPI_POWER_DOMAIN_V3D 10
#define RPI_POWER_DOMAIN_ISP 11
#define RPI_POWER_DOMAIN_UNICAM0 12
#define RPI_POWER_DOMAIN_UNICAM1 13
#define RPI_POWER_DOMAIN_CCP2RX 14
#define RPI_POWER_DOMAIN_CSI2 15
#define RPI_POWER_DOMAIN_CPI 16
#define RPI_POWER_DOMAIN_DSI0 17
#define RPI_POWER_DOMAIN_DSI1 18
#define RPI_POWER_DOMAIN_TRANSPOSER 19
#define RPI_POWER_DOMAIN_CCP2TX 20
#define RPI_POWER_DOMAIN_CDP 21
#define RPI_POWER_DOMAIN_ARM 22
#define RPI_POWER_DOMAIN_COUNT 23
#endif /* _DT_BINDINGS_ARM_BCM2835_RPI_POWER_H */
/**
* This header provides index for the reset controller
* based on hi6220 SoC.
*/
#ifndef _DT_BINDINGS_RESET_CONTROLLER_HI6220
#define _DT_BINDINGS_RESET_CONTROLLER_HI6220
#define PERIPH_RSTDIS0_MMC0 0x000
#define PERIPH_RSTDIS0_MMC1 0x001
#define PERIPH_RSTDIS0_MMC2 0x002
#define PERIPH_RSTDIS0_NANDC 0x003
#define PERIPH_RSTDIS0_USBOTG_BUS 0x004
#define PERIPH_RSTDIS0_POR_PICOPHY 0x005
#define PERIPH_RSTDIS0_USBOTG 0x006
#define PERIPH_RSTDIS0_USBOTG_32K 0x007
#define PERIPH_RSTDIS1_HIFI 0x100
#define PERIPH_RSTDIS1_DIGACODEC 0x105
#define PERIPH_RSTEN2_IPF 0x200
#define PERIPH_RSTEN2_SOCP 0x201
#define PERIPH_RSTEN2_DMAC 0x202
#define PERIPH_RSTEN2_SECENG 0x203
#define PERIPH_RSTEN2_ABB 0x204
#define PERIPH_RSTEN2_HPM0 0x205
#define PERIPH_RSTEN2_HPM1 0x206
#define PERIPH_RSTEN2_HPM2 0x207
#define PERIPH_RSTEN2_HPM3 0x208
#define PERIPH_RSTEN3_CSSYS 0x300
#define PERIPH_RSTEN3_I2C0 0x301
#define PERIPH_RSTEN3_I2C1 0x302
#define PERIPH_RSTEN3_I2C2 0x303
#define PERIPH_RSTEN3_I2C3 0x304
#define PERIPH_RSTEN3_UART1 0x305
#define PERIPH_RSTEN3_UART2 0x306
#define PERIPH_RSTEN3_UART3 0x307
#define PERIPH_RSTEN3_UART4 0x308
#define PERIPH_RSTEN3_SSP 0x309
#define PERIPH_RSTEN3_PWM 0x30a
#define PERIPH_RSTEN3_BLPWM 0x30b
#define PERIPH_RSTEN3_TSENSOR 0x30c
#define PERIPH_RSTEN3_DAPB 0x312
#define PERIPH_RSTEN3_HKADC 0x313
#define PERIPH_RSTEN3_CODEC_SSI 0x314
#define PERIPH_RSTEN3_PMUSSI1 0x316
#define PERIPH_RSTEN8_RS0 0x400
#define PERIPH_RSTEN8_RS2 0x401
#define PERIPH_RSTEN8_RS3 0x402
#define PERIPH_RSTEN8_MS0 0x403
#define PERIPH_RSTEN8_MS2 0x405
#define PERIPH_RSTEN8_XG2RAM0 0x406
#define PERIPH_RSTEN8_X2SRAM_TZMA 0x407
#define PERIPH_RSTEN8_SRAM 0x408
#define PERIPH_RSTEN8_HARQ 0x40a
#define PERIPH_RSTEN8_DDRC 0x40c
#define PERIPH_RSTEN8_DDRC_APB 0x40d
#define PERIPH_RSTEN8_DDRPACK_APB 0x40e
#define PERIPH_RSTEN8_DDRT 0x411
#define PERIPH_RSDIST9_CARM_DAP 0x500
#define PERIPH_RSDIST9_CARM_ATB 0x501
#define PERIPH_RSDIST9_CARM_LBUS 0x502
#define PERIPH_RSDIST9_CARM_POR 0x503
#define PERIPH_RSDIST9_CARM_CORE 0x504
#define PERIPH_RSDIST9_CARM_DBG 0x505
#define PERIPH_RSDIST9_CARM_L2 0x506
#define PERIPH_RSDIST9_CARM_SOCDBG 0x507
#define PERIPH_RSDIST9_CARM_ETM 0x508
#endif /*_DT_BINDINGS_RESET_CONTROLLER_HI6220*/
......@@ -52,6 +52,10 @@
#define STIH407_KEYSCAN_SOFTRESET 26
#define STIH407_USB2_PORT0_SOFTRESET 27
#define STIH407_USB2_PORT1_SOFTRESET 28
#define STIH407_ST231_AUD_SOFTRESET 29
#define STIH407_ST231_DMU_SOFTRESET 30
#define STIH407_ST231_GP0_SOFTRESET 31
#define STIH407_ST231_GP1_SOFTRESET 32
/* Picophy reset defines */
#define STIH407_PICOPHY0_RESET 0
......
......@@ -38,6 +38,9 @@ static inline struct reset_control *devm_reset_control_get_optional(
struct reset_control *of_reset_control_get(struct device_node *node,
const char *id);
struct reset_control *of_reset_control_get_by_index(
struct device_node *node, int index);
#else
static inline int reset_control_reset(struct reset_control *rstc)
......@@ -71,7 +74,7 @@ static inline void reset_control_put(struct reset_control *rstc)
static inline int device_reset_optional(struct device *dev)
{
return -ENOSYS;
return -ENOTSUPP;
}
static inline struct reset_control *__must_check reset_control_get(
......@@ -91,19 +94,25 @@ static inline struct reset_control *__must_check devm_reset_control_get(
static inline struct reset_control *reset_control_get_optional(
struct device *dev, const char *id)
{
return ERR_PTR(-ENOSYS);
return ERR_PTR(-ENOTSUPP);
}
static inline struct reset_control *devm_reset_control_get_optional(
struct device *dev, const char *id)
{
return ERR_PTR(-ENOSYS);
return ERR_PTR(-ENOTSUPP);
}
static inline struct reset_control *of_reset_control_get(
struct device_node *node, const char *id)
{
return ERR_PTR(-ENOSYS);
return ERR_PTR(-ENOTSUPP);
}
static inline struct reset_control *of_reset_control_get_by_index(
struct device_node *node, int index)
{
return ERR_PTR(-ENOTSUPP);
}
#endif /* CONFIG_RESET_CONTROLLER */
......
#ifndef __QCOM_SMEM_STATE__
#define __QCOM_SMEM_STATE__
struct qcom_smem_state;
struct qcom_smem_state_ops {
int (*update_bits)(void *, u32, u32);
};
struct qcom_smem_state *qcom_smem_state_get(struct device *dev, const char *con_id, unsigned *bit);
void qcom_smem_state_put(struct qcom_smem_state *);
int qcom_smem_state_update_bits(struct qcom_smem_state *state, u32 mask, u32 value);
struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node, const struct qcom_smem_state_ops *ops, void *data);
void qcom_smem_state_unregister(struct qcom_smem_state *state);
#endif
/*
* TI Wakeup M3 for AMx3 SoCs Power Management Routines
*
* Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
* Dave Gerlach <d-gerlach@ti.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* 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.
*/
#ifndef _LINUX_WKUP_M3_IPC_H
#define _LINUX_WKUP_M3_IPC_H
#define WKUP_M3_DEEPSLEEP 1
#define WKUP_M3_STANDBY 2
#define WKUP_M3_IDLE 3
#include <linux/mailbox_client.h>
struct wkup_m3_ipc_ops;
struct wkup_m3_ipc {
struct rproc *rproc;
void __iomem *ipc_mem_base;
struct device *dev;
int mem_type;
unsigned long resume_addr;
int state;
struct completion sync_complete;
struct mbox_client mbox_client;
struct mbox_chan *mbox;
struct wkup_m3_ipc_ops *ops;
};
struct wkup_m3_ipc_ops {
void (*set_mem_type)(struct wkup_m3_ipc *m3_ipc, int mem_type);
void (*set_resume_address)(struct wkup_m3_ipc *m3_ipc, void *addr);
int (*prepare_low_power)(struct wkup_m3_ipc *m3_ipc, int state);
int (*finish_low_power)(struct wkup_m3_ipc *m3_ipc);
int (*request_pm_status)(struct wkup_m3_ipc *m3_ipc);
};
struct wkup_m3_ipc *wkup_m3_ipc_get(void);
void wkup_m3_ipc_put(struct wkup_m3_ipc *m3_ipc);
#endif /* _LINUX_WKUP_M3_IPC_H */
......@@ -72,10 +72,12 @@ enum rpi_firmware_property_tag {
RPI_FIRMWARE_SET_ENABLE_QPU = 0x00030012,
RPI_FIRMWARE_GET_DISPMANX_RESOURCE_MEM_HANDLE = 0x00030014,
RPI_FIRMWARE_GET_EDID_BLOCK = 0x00030020,
RPI_FIRMWARE_GET_DOMAIN_STATE = 0x00030030,
RPI_FIRMWARE_SET_CLOCK_STATE = 0x00038001,
RPI_FIRMWARE_SET_CLOCK_RATE = 0x00038002,
RPI_FIRMWARE_SET_VOLTAGE = 0x00038003,
RPI_FIRMWARE_SET_TURBO = 0x00038009,
RPI_FIRMWARE_SET_DOMAIN_STATE = 0x00038030,
/* Dispmanx TAGS */
RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE = 0x00040001,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment