ARM: SoC driver updates for v4.8

Driver updates for ARM SoCs.
 
 A slew of changes this release cycle. The reset driver tree, that we merge
 through arm-soc for historical reasons, is also sizable this time around.
 
 Among the changes:
 
  - clps711x: Treewide changes to compatible strings, merged here for simplicity.
  - Qualcomm: SCM firmware driver cleanups, move to platform driver
  - ux500: Major cleanups, removal of old mach-specific infrastructure.
  - Atmel external bus memory driver
  - Move of brcmstb platform to the rest of bcm
  - PMC driver updates for tegra, various fixes and improvements
  - Samsung platform driver updates to support 64-bit Exynos platforms
  - Reset controller cleanups moving to devm_reset_controller_register() APIs
  - Reset controller driver for Amlogic Meson
  - Reset controller driver for Hisilicon hi6220
  - ARM SCPI power domain support
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJXnm1XAAoJEIwa5zzehBx35lcP/ApuQarIXeZCQZtjlUBV9McW
 o3o7FhKFHePmEPeoYCvVeK5D8NykTkQv3WpnCknoxPJzxGJF7jbPWQJcVnXfKOXD
 kTcyIK15WL2HHtSE3lYyLfyUPz8AbJyRt0l0cxgcg6jvo+uzlWooNz1y78rLIYzg
 UwRssj7OiHv4dsyYRHZIsjnB8gMWw8rYMk154gP2xy6MnNXXzzOVVnOiVqxSZBm+
 EgIIcROMOqkkHuFlClMYKluIgrmgz1Ypjf+FuAg7dqXZd+TGRrmGXeI7SkGThfLu
 nyvY3N18NViNu7xOUkI9zg7+ifyYM8Si9ylalSICSJdIAxZfiwFqFaLJvVWKU1rY
 rBOBjKckQI0/X9WYusFNFHcijhIFV8/FgGAnVRRMPdvlCss7Zp03C9mR4AEhmKMX
 rLG49x81hU1C+LftC59ml3iB8dhZrrRkbxNHjLFHVGWNrKMrmJKa8JhXGRAoNM+u
 LRauiuJZatqvLfISNvpfcoW2EashVoU3f+uC8ymT3QCyME3wZm0t7T4tllxhMfBl
 sOgJqNkTKDmPLofwm/dASiLML7ZF1WePScrFyOACnj9K4mUD+OaCnowtWoQPu0eI
 aNmT84oosJ2S9F/iUDPtFHXdzQ+1QPPfSiQ9FXMoauciVq/2F+pqq68yYgqoxFOG
 vmkmG2YM4Wyq43u0BONR
 =O8+y
 -----END PGP SIGNATURE-----

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.

  A slew of changes this release cycle.  The reset driver tree, that we
  merge through arm-soc for historical reasons, is also sizable this
  time around.

  Among the changes:

   - clps711x: Treewide changes to compatible strings, merged here for simplicity.
   - Qualcomm: SCM firmware driver cleanups, move to platform driver
   - ux500: Major cleanups, removal of old mach-specific infrastructure.
   - Atmel external bus memory driver
   - Move of brcmstb platform to the rest of bcm
   - PMC driver updates for tegra, various fixes and improvements
   - Samsung platform driver updates to support 64-bit Exynos platforms
   - Reset controller cleanups moving to devm_reset_controller_register() APIs
   - Reset controller driver for Amlogic Meson
   - Reset controller driver for Hisilicon hi6220
   - ARM SCPI power domain support"

* tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (100 commits)
  ARM: ux500: consolidate base platform files
  ARM: ux500: move soc_id driver to drivers/soc
  ARM: ux500: call ux500_setup_id later
  ARM: ux500: consolidate soc_device code in id.c
  ARM: ux500: remove cpu_is_u* helpers
  ARM: ux500: use CLK_OF_DECLARE()
  ARM: ux500: move l2x0 init to .init_irq
  mfd: db8500 stop passing around platform data
  ASoC: ab8500-codec: remove platform data based probe
  ARM: ux500: move ab8500_regulator_plat_data into driver
  ARM: ux500: remove unused regulator data
  soc: raspberrypi-power: add CONFIG_OF dependency
  firmware: scpi: add CONFIG_OF dependency
  video: clps711x-fb: Changing the compatibility string to match with the smallest supported chip
  input: clps711x-keypad: Changing the compatibility string to match with the smallest supported chip
  pwm: clps711x: Changing the compatibility string to match with the smallest supported chip
  serial: clps711x: Changing the compatibility string to match with the smallest supported chip
  irqchip: clps711x: Changing the compatibility string to match with the smallest supported chip
  clocksource: clps711x: Changing the compatibility string to match with the smallest supported chip
  clk: clps711x: Changing the compatibility string to match with the smallest supported chip
  ...
This commit is contained in:
Linus Torvalds 2016-08-01 18:36:01 -04:00
commit 43a0a98aa8
140 changed files with 4776 additions and 2641 deletions

View file

@ -1,7 +1,6 @@
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"
source "drivers/soc/qcom/Kconfig"
@ -10,6 +9,7 @@ source "drivers/soc/samsung/Kconfig"
source "drivers/soc/sunxi/Kconfig"
source "drivers/soc/tegra/Kconfig"
source "drivers/soc/ti/Kconfig"
source "drivers/soc/ux500/Kconfig"
source "drivers/soc/versatile/Kconfig"
endmenu

View file

@ -3,7 +3,6 @@
#
obj-y += bcm/
obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/
obj-$(CONFIG_ARCH_DOVE) += dove/
obj-$(CONFIG_MACH_DOVE) += dove/
obj-y += fsl/
@ -15,4 +14,5 @@ obj-$(CONFIG_SOC_SAMSUNG) += samsung/
obj-$(CONFIG_ARCH_SUNXI) += sunxi/
obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-$(CONFIG_SOC_TI) += ti/
obj-$(CONFIG_ARCH_U8500) += ux500/
obj-$(CONFIG_PLAT_VERSATILE) += versatile/

View file

@ -1,9 +1,23 @@
menu "Broadcom SoC drivers"
config RASPBERRYPI_POWER
bool "Raspberry Pi power domain driver"
depends on ARCH_BCM2835 || COMPILE_TEST
depends on ARCH_BCM2835 || (COMPILE_TEST && OF)
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.
config SOC_BRCMSTB
bool "Broadcom STB SoC drivers"
depends on ARM
select SOC_BUS
help
Enables drivers for the Broadcom Set-Top Box (STB) series of chips.
This option alone enables only some support code, while the drivers
can be enabled individually within this menu.
If unsure, say N.
endmenu

View file

@ -1 +1,2 @@
obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o
obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/

View file

@ -19,6 +19,7 @@
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/syscore_ops.h>
#include <linux/soc/brcmstb/brcmstb.h>
#define CPU_CREDIT_REG_OFFSET 0x184
#define CPU_CREDIT_REG_MCPx_WR_PAIRING_EN_MASK 0x70000000

View file

@ -1,10 +0,0 @@
menuconfig SOC_BRCMSTB
bool "Broadcom STB SoC drivers"
depends on ARM
select SOC_BUS
help
Enables drivers for the Broadcom Set-Top Box (STB) series of chips.
This option alone enables only some support code, while the drivers
can be enabled individually within this menu.
If unsure, say N.

View file

@ -104,26 +104,26 @@ struct qcom_smem_state *qcom_smem_state_get(struct device *dev,
if (con_id) {
index = of_property_match_string(dev->of_node,
"qcom,state-names",
"qcom,smem-state-names",
con_id);
if (index < 0) {
dev_err(dev, "missing qcom,state-names\n");
dev_err(dev, "missing qcom,smem-state-names\n");
return ERR_PTR(index);
}
}
ret = of_parse_phandle_with_args(dev->of_node,
"qcom,state",
"#qcom,state-cells",
"qcom,smem-states",
"#qcom,smem-state-cells",
index,
&args);
if (ret) {
dev_err(dev, "failed to parse qcom,state property\n");
dev_err(dev, "failed to parse qcom,smem-states property\n");
return ERR_PTR(ret);
}
if (args.args_count != 1) {
dev_err(dev, "invalid #qcom,state-cells\n");
dev_err(dev, "invalid #qcom,smem-state-cells\n");
return ERR_PTR(-EINVAL);
}

View file

@ -196,7 +196,7 @@ static irqreturn_t qcom_smp2p_intr(int irq, void *data)
/* 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));
memcpy(buf, in->entries[i].name, sizeof(buf));
if (!strcmp(buf, entry->name)) {
entry->value = &in->entries[i].value;
break;
@ -343,12 +343,13 @@ static int qcom_smp2p_outbound_entry(struct qcom_smp2p *smp2p,
/* 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++;
memcpy(out->entries[out->valid_entries].name, buf, SMP2P_MAX_ENTRY_NAME);
/* Make the logical entry reference the physical value */
entry->value = &out->entries[out->valid_entries].value;
out->valid_entries++;
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");

View file

@ -495,7 +495,7 @@ static int qcom_smsm_probe(struct platform_device *pdev)
if (!smsm->hosts)
return -ENOMEM;
local_node = of_find_node_with_property(pdev->dev.of_node, "#qcom,state-cells");
local_node = of_find_node_with_property(pdev->dev.of_node, "#qcom,smem-state-cells");
if (!local_node) {
dev_err(&pdev->dev, "no state entry\n");
return -EINVAL;

View file

@ -1,4 +1,5 @@
/*
* Copyright (c) 2016, Linaro Ltd.
* Copyright (c) 2015, Sony Mobile Communications Inc.
*
* This program is free software; you can redistribute it and/or modify
@ -14,8 +15,16 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smd.h>
#include <linux/io.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/soc/qcom/wcnss_ctrl.h>
#define WCNSS_REQUEST_TIMEOUT (5 * HZ)
#define WCNSS_CBC_TIMEOUT (10 * HZ)
#define WCNSS_ACK_DONE_BOOTING 1
#define WCNSS_ACK_COLD_BOOTING 2
#define NV_FRAGMENT_SIZE 3072
#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
@ -25,17 +34,19 @@
* @dev: device handle
* @channel: SMD channel handle
* @ack: completion for outstanding requests
* @cbc: completion for cbc complete indication
* @ack_status: status of the outstanding request
* @download_nv_work: worker for uploading nv binary
* @probe_work: worker for uploading nv binary
*/
struct wcnss_ctrl {
struct device *dev;
struct qcom_smd_channel *channel;
struct completion ack;
struct completion cbc;
int ack_status;
struct work_struct download_nv_work;
struct work_struct probe_work;
};
/* message types */
@ -48,6 +59,11 @@ enum {
WCNSS_UPLOAD_CAL_RESP,
WCNSS_DOWNLOAD_CAL_REQ,
WCNSS_DOWNLOAD_CAL_RESP,
WCNSS_VBAT_LEVEL_IND,
WCNSS_BUILD_VERSION_REQ,
WCNSS_BUILD_VERSION_RESP,
WCNSS_PM_CONFIG_REQ,
WCNSS_CBC_COMPLETE_IND,
};
/**
@ -128,7 +144,7 @@ static int wcnss_ctrl_smd_callback(struct qcom_smd_channel *channel,
version->major, version->minor,
version->version, version->revision);
schedule_work(&wcnss->download_nv_work);
complete(&wcnss->ack);
break;
case WCNSS_DOWNLOAD_NV_RESP:
if (count != sizeof(*nvresp)) {
@ -141,6 +157,10 @@ static int wcnss_ctrl_smd_callback(struct qcom_smd_channel *channel,
wcnss->ack_status = nvresp->status;
complete(&wcnss->ack);
break;
case WCNSS_CBC_COMPLETE_IND:
dev_dbg(wcnss->dev, "cold boot complete\n");
complete(&wcnss->cbc);
break;
default:
dev_info(wcnss->dev, "unknown message type %d\n", hdr->type);
break;
@ -156,20 +176,32 @@ static int wcnss_ctrl_smd_callback(struct qcom_smd_channel *channel,
static int wcnss_request_version(struct wcnss_ctrl *wcnss)
{
struct wcnss_msg_hdr msg;
int ret;
msg.type = WCNSS_VERSION_REQ;
msg.len = sizeof(msg);
ret = qcom_smd_send(wcnss->channel, &msg, sizeof(msg));
if (ret < 0)
return ret;
return qcom_smd_send(wcnss->channel, &msg, sizeof(msg));
ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_CBC_TIMEOUT);
if (!ret) {
dev_err(wcnss->dev, "timeout waiting for version response\n");
return -ETIMEDOUT;
}
return 0;
}
/**
* wcnss_download_nv() - send nv binary to WCNSS
* @work: work struct to acquire wcnss context
* @wcnss: wcnss_ctrl state handle
* @expect_cbc: indicator to caller that an cbc event is expected
*
* Returns 0 on success. Negative errno on failure.
*/
static void wcnss_download_nv(struct work_struct *work)
static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc)
{
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;
@ -178,10 +210,10 @@ static void wcnss_download_nv(struct work_struct *work)
req = kzalloc(sizeof(*req) + NV_FRAGMENT_SIZE, GFP_KERNEL);
if (!req)
return;
return -ENOMEM;
ret = request_firmware(&fw, NVBIN_FILE, wcnss->dev);
if (ret) {
if (ret < 0) {
dev_err(wcnss->dev, "Failed to load nv file %s: %d\n",
NVBIN_FILE, ret);
goto free_req;
@ -207,7 +239,7 @@ static void wcnss_download_nv(struct work_struct *work)
memcpy(req->fragment, data, req->frag_size);
ret = qcom_smd_send(wcnss->channel, req, req->hdr.len);
if (ret) {
if (ret < 0) {
dev_err(wcnss->dev, "failed to send smd packet\n");
goto release_fw;
}
@ -220,16 +252,58 @@ static void wcnss_download_nv(struct work_struct *work)
} while (left > 0);
ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_REQUEST_TIMEOUT);
if (!ret)
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);
ret = -ETIMEDOUT;
} else {
*expect_cbc = wcnss->ack_status == WCNSS_ACK_COLD_BOOTING;
ret = 0;
}
release_fw:
release_firmware(fw);
free_req:
kfree(req);
return ret;
}
/**
* qcom_wcnss_open_channel() - open additional SMD channel to WCNSS
* @wcnss: wcnss handle, retrieved from drvdata
* @name: SMD channel name
* @cb: callback to handle incoming data on the channel
*/
struct qcom_smd_channel *qcom_wcnss_open_channel(void *wcnss, const char *name, qcom_smd_cb_t cb)
{
struct wcnss_ctrl *_wcnss = wcnss;
return qcom_smd_open_channel(_wcnss->channel, name, cb);
}
EXPORT_SYMBOL(qcom_wcnss_open_channel);
static void wcnss_async_probe(struct work_struct *work)
{
struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, probe_work);
bool expect_cbc;
int ret;
ret = wcnss_request_version(wcnss);
if (ret < 0)
return;
ret = wcnss_download_nv(wcnss, &expect_cbc);
if (ret < 0)
return;
/* Wait for pending cold boot completion if indicated by the nv downloader */
if (expect_cbc) {
ret = wait_for_completion_timeout(&wcnss->cbc, WCNSS_REQUEST_TIMEOUT);
if (!ret)
dev_err(wcnss->dev, "expected cold boot completion\n");
}
of_platform_populate(wcnss->dev->of_node, NULL, NULL, wcnss->dev);
}
static int wcnss_ctrl_probe(struct qcom_smd_device *sdev)
@ -244,25 +318,38 @@ static int wcnss_ctrl_probe(struct qcom_smd_device *sdev)
wcnss->channel = sdev->channel;
init_completion(&wcnss->ack);
INIT_WORK(&wcnss->download_nv_work, wcnss_download_nv);
init_completion(&wcnss->cbc);
INIT_WORK(&wcnss->probe_work, wcnss_async_probe);
qcom_smd_set_drvdata(sdev->channel, wcnss);
dev_set_drvdata(&sdev->dev, wcnss);
return wcnss_request_version(wcnss);
schedule_work(&wcnss->probe_work);
return 0;
}
static const struct qcom_smd_id wcnss_ctrl_smd_match[] = {
{ .name = "WCNSS_CTRL" },
static void wcnss_ctrl_remove(struct qcom_smd_device *sdev)
{
struct wcnss_ctrl *wcnss = qcom_smd_get_drvdata(sdev->channel);
cancel_work_sync(&wcnss->probe_work);
of_platform_depopulate(&sdev->dev);
}
static const struct of_device_id wcnss_ctrl_of_match[] = {
{ .compatible = "qcom,wcnss", },
{}
};
static struct qcom_smd_driver wcnss_ctrl_driver = {
.probe = wcnss_ctrl_probe,
.remove = wcnss_ctrl_remove,
.callback = wcnss_ctrl_smd_callback,
.smd_match_table = wcnss_ctrl_smd_match,
.driver = {
.name = "qcom_wcnss_ctrl",
.owner = THIS_MODULE,
.of_match_table = wcnss_ctrl_of_match,
},
};

View file

@ -10,4 +10,8 @@ config EXYNOS_PMU
bool "Exynos PMU controller driver" if COMPILE_TEST
depends on (ARM && ARCH_EXYNOS) || ((ARM || ARM64) && COMPILE_TEST)
config EXYNOS_PM_DOMAINS
bool "Exynos PM domains" if COMPILE_TEST
depends on PM_GENERIC_DOMAINS || COMPILE_TEST
endif

View file

@ -1,2 +1,3 @@
obj-$(CONFIG_EXYNOS_PMU) += exynos-pmu.o exynos3250-pmu.o exynos4-pmu.o \
exynos5250-pmu.o exynos5420-pmu.o
obj-$(CONFIG_EXYNOS_PM_DOMAINS) += pm_domains.o

View file

@ -14,7 +14,7 @@
#include "exynos-pmu.h"
static struct exynos_pmu_conf exynos3250_pmu_config[] = {
static const struct exynos_pmu_conf exynos3250_pmu_config[] = {
/* { .offset = offset, .val = { AFTR, W-AFTR, SLEEP } */
{ EXYNOS3_ARM_CORE0_SYS_PWR_REG, { 0x0, 0x0, 0x2} },
{ EXYNOS3_DIS_IRQ_ARM_CORE0_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },

View file

@ -17,7 +17,7 @@
#include "exynos-pmu.h"
static struct exynos_pmu_conf exynos5420_pmu_config[] = {
static const struct exynos_pmu_conf exynos5420_pmu_config[] = {
/* { .offset = offset, .val = { AFTR, LPA, SLEEP } */
{ EXYNOS5_ARM_CORE0_SYS_PWR_REG, { 0x0, 0x0, 0x0} },
{ EXYNOS5_DIS_IRQ_ARM_CORE0_LOCAL_SYS_PWR_REG, { 0x0, 0x0, 0x0} },

View file

@ -0,0 +1,245 @@
/*
* Exynos Generic power domain support.
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Implementation of Exynos specific power domain control which is used in
* conjunction with runtime-pm. Support for both device-tree and non-device-tree
* based power domain support is included.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/io.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/pm_domain.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/sched.h>
#define MAX_CLK_PER_DOMAIN 4
struct exynos_pm_domain_config {
/* Value for LOCAL_PWR_CFG and STATUS fields for each domain */
u32 local_pwr_cfg;
};
/*
* Exynos specific wrapper around the generic power domain
*/
struct exynos_pm_domain {
void __iomem *base;
char const *name;
bool is_off;
struct generic_pm_domain pd;
struct clk *oscclk;
struct clk *clk[MAX_CLK_PER_DOMAIN];
struct clk *pclk[MAX_CLK_PER_DOMAIN];
struct clk *asb_clk[MAX_CLK_PER_DOMAIN];
u32 local_pwr_cfg;
};
static int exynos_pd_power(struct generic_pm_domain *domain, bool power_on)
{
struct exynos_pm_domain *pd;
void __iomem *base;
u32 timeout, pwr;
char *op;
int i;
pd = container_of(domain, struct exynos_pm_domain, pd);
base = pd->base;
for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
if (IS_ERR(pd->asb_clk[i]))
break;
clk_prepare_enable(pd->asb_clk[i]);
}
/* Set oscclk before powering off a domain*/
if (!power_on) {
for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
if (IS_ERR(pd->clk[i]))
break;
pd->pclk[i] = clk_get_parent(pd->clk[i]);
if (clk_set_parent(pd->clk[i], pd->oscclk))
pr_err("%s: error setting oscclk as parent to clock %d\n",
pd->name, i);
}
}
pwr = power_on ? pd->local_pwr_cfg : 0;
writel_relaxed(pwr, base);
/* Wait max 1ms */
timeout = 10;
while ((readl_relaxed(base + 0x4) & pd->local_pwr_cfg) != pwr) {
if (!timeout) {
op = (power_on) ? "enable" : "disable";
pr_err("Power domain %s %s failed\n", domain->name, op);
return -ETIMEDOUT;
}
timeout--;
cpu_relax();
usleep_range(80, 100);
}
/* Restore clocks after powering on a domain*/
if (power_on) {
for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
if (IS_ERR(pd->clk[i]))
break;
if (IS_ERR(pd->pclk[i]))
continue; /* Skip on first power up */
if (clk_set_parent(pd->clk[i], pd->pclk[i]))
pr_err("%s: error setting parent to clock%d\n",
pd->name, i);
}
}
for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
if (IS_ERR(pd->asb_clk[i]))
break;
clk_disable_unprepare(pd->asb_clk[i]);
}
return 0;
}
static int exynos_pd_power_on(struct generic_pm_domain *domain)
{
return exynos_pd_power(domain, true);
}
static int exynos_pd_power_off(struct generic_pm_domain *domain)
{
return exynos_pd_power(domain, false);
}
static const struct exynos_pm_domain_config exynos4210_cfg __initconst = {
.local_pwr_cfg = 0x7,
};
static const struct of_device_id exynos_pm_domain_of_match[] __initconst = {
{
.compatible = "samsung,exynos4210-pd",
.data = &exynos4210_cfg,
},
{ },
};
static __init int exynos4_pm_init_power_domain(void)
{
struct device_node *np;
const struct of_device_id *match;
for_each_matching_node_and_match(np, exynos_pm_domain_of_match, &match) {
const struct exynos_pm_domain_config *pm_domain_cfg;
struct exynos_pm_domain *pd;
int on, i;
pm_domain_cfg = match->data;
pd = kzalloc(sizeof(*pd), GFP_KERNEL);
if (!pd) {
pr_err("%s: failed to allocate memory for domain\n",
__func__);
of_node_put(np);
return -ENOMEM;
}
pd->pd.name = kstrdup_const(strrchr(np->full_name, '/') + 1,
GFP_KERNEL);
if (!pd->pd.name) {
kfree(pd);
of_node_put(np);
return -ENOMEM;
}
pd->name = pd->pd.name;
pd->base = of_iomap(np, 0);
if (!pd->base) {
pr_warn("%s: failed to map memory\n", __func__);
kfree_const(pd->pd.name);
kfree(pd);
continue;
}
pd->pd.power_off = exynos_pd_power_off;
pd->pd.power_on = exynos_pd_power_on;
pd->local_pwr_cfg = pm_domain_cfg->local_pwr_cfg;
for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
char clk_name[8];
snprintf(clk_name, sizeof(clk_name), "asb%d", i);
pd->asb_clk[i] = of_clk_get_by_name(np, clk_name);
if (IS_ERR(pd->asb_clk[i]))
break;
}
pd->oscclk = of_clk_get_by_name(np, "oscclk");
if (IS_ERR(pd->oscclk))
goto no_clk;
for (i = 0; i < MAX_CLK_PER_DOMAIN; i++) {
char clk_name[8];
snprintf(clk_name, sizeof(clk_name), "clk%d", i);
pd->clk[i] = of_clk_get_by_name(np, clk_name);
if (IS_ERR(pd->clk[i]))
break;
/*
* Skip setting parent on first power up.
* The parent at this time may not be useful at all.
*/
pd->pclk[i] = ERR_PTR(-EINVAL);
}
if (IS_ERR(pd->clk[0]))
clk_put(pd->oscclk);
no_clk:
on = readl_relaxed(pd->base + 0x4) & pd->local_pwr_cfg;
pm_genpd_init(&pd->pd, NULL, !on);
of_genpd_add_provider_simple(np, &pd->pd);
}
/* Assign the child power domains to their parents */
for_each_matching_node(np, exynos_pm_domain_of_match) {
struct generic_pm_domain *child_domain, *parent_domain;
struct of_phandle_args args;
args.np = np;
args.args_count = 0;
child_domain = of_genpd_get_from_provider(&args);
if (IS_ERR(child_domain))
continue;
if (of_parse_phandle_with_args(np, "power-domains",
"#power-domain-cells", 0, &args) != 0)
continue;
parent_domain = of_genpd_get_from_provider(&args);
if (IS_ERR(parent_domain))
continue;
if (pm_genpd_add_subdomain(parent_domain, child_domain))
pr_warn("%s failed to add subdomain: %s\n",
parent_domain->name, child_domain->name);
else
pr_info("%s has as child subdomain: %s.\n",
parent_domain->name, child_domain->name);
}
return 0;
}
core_initcall(exynos4_pm_init_power_domain);

View file

@ -51,6 +51,7 @@
#define PMC_CNTRL_CPU_PWRREQ_POLARITY (1 << 15) /* CPU pwr req polarity */
#define PMC_CNTRL_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */
#define PMC_CNTRL_INTR_POLARITY (1 << 17) /* inverts INTR polarity */
#define PMC_CNTRL_MAIN_RST (1 << 4)
#define DPD_SAMPLE 0x020
#define DPD_SAMPLE_ENABLE (1 << 0)
@ -80,6 +81,14 @@
#define PMC_SENSOR_CTRL_SCRATCH_WRITE (1 << 2)
#define PMC_SENSOR_CTRL_ENABLE_RST (1 << 1)
#define PMC_RST_STATUS 0x1b4
#define PMC_RST_STATUS_POR 0
#define PMC_RST_STATUS_WATCHDOG 1
#define PMC_RST_STATUS_SENSOR 2
#define PMC_RST_STATUS_SW_MAIN 3
#define PMC_RST_STATUS_LP0 4
#define PMC_RST_STATUS_AOTAG 5
#define IO_DPD_REQ 0x1b8
#define IO_DPD_REQ_CODE_IDLE (0 << 30)
#define IO_DPD_REQ_CODE_OFF (1 << 30)
@ -399,6 +408,7 @@ static int tegra_powergate_power_up(struct tegra_powergate *pg,
disable_clks:
tegra_powergate_disable_clocks(pg);
usleep_range(10, 20);
powergate_off:
tegra_powergate_set(pg->id, false);
@ -436,6 +446,7 @@ assert_resets:
usleep_range(10, 20);
tegra_powergate_reset_deassert(pg);
usleep_range(10, 20);
disable_clks:
tegra_powergate_disable_clocks(pg);
@ -540,6 +551,9 @@ int tegra_powergate_sequence_power_up(unsigned int id, struct clk *clk,
struct tegra_powergate pg;
int err;
if (!tegra_powergate_is_available(id))
return -EINVAL;
pg.id = id;
pg.clks = &clk;
pg.num_clks = 1;
@ -638,9 +652,10 @@ static int tegra_pmc_restart_notify(struct notifier_block *this,
tegra_pmc_writel(value, PMC_SCRATCH0);
value = tegra_pmc_readl(0);
value |= 0x10;
tegra_pmc_writel(value, 0);
/* reset everything but PMC_SCRATCH0 and PMC_RST_STATUS */
value = tegra_pmc_readl(PMC_CNTRL);
value |= PMC_CNTRL_MAIN_RST;
tegra_pmc_writel(value, PMC_CNTRL);
return NOTIFY_DONE;
}
@ -722,13 +737,14 @@ static int tegra_powergate_of_get_clks(struct tegra_powergate *pg,
err:
while (i--)
clk_put(pg->clks[i]);
kfree(pg->clks);
return err;
}
static int tegra_powergate_of_get_resets(struct tegra_powergate *pg,
struct device_node *np)
struct device_node *np, bool off)
{
struct reset_control *rst;
unsigned int i, count;
@ -748,6 +764,16 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg,
err = PTR_ERR(pg->resets[i]);
goto error;
}
if (off)
err = reset_control_assert(pg->resets[i]);
else
err = reset_control_deassert(pg->resets[i]);
if (err) {
reset_control_put(pg->resets[i]);
goto error;
}
}
pg->num_resets = count;
@ -757,6 +783,7 @@ static int tegra_powergate_of_get_resets(struct tegra_powergate *pg,
error:
while (i--)
reset_control_put(pg->resets[i]);
kfree(pg->resets);
return err;
@ -765,16 +792,19 @@ error:
static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
{
struct tegra_powergate *pg;
int id, err;
bool off;
int id;
pg = kzalloc(sizeof(*pg), GFP_KERNEL);
if (!pg)
goto error;
return;
id = tegra_powergate_lookup(pmc, np->name);
if (id < 0)
if (id < 0) {
dev_err(pmc->dev, "powergate lookup failed for %s: %d\n",
np->name, id);
goto free_mem;
}
/*
* Clear the bit for this powergate so it cannot be managed
@ -788,31 +818,64 @@ static void tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)
pg->genpd.power_on = tegra_genpd_power_on;
pg->pmc = pmc;
if (tegra_powergate_of_get_clks(pg, np))
goto set_available;
if (tegra_powergate_of_get_resets(pg, np))
goto remove_clks;
off = !tegra_powergate_is_powered(pg->id);
err = tegra_powergate_of_get_clks(pg, np);
if (err < 0) {
dev_err(pmc->dev, "failed to get clocks for %s: %d\n",
np->name, err);
goto set_available;
}
err = tegra_powergate_of_get_resets(pg, np, off);
if (err < 0) {
dev_err(pmc->dev, "failed to get resets for %s: %d\n",
np->name, err);
goto remove_clks;
}
if (!IS_ENABLED(CONFIG_PM_GENERIC_DOMAINS))
goto power_on_cleanup;
/*
* FIXME: If XHCI is enabled for Tegra, then power-up the XUSB
* host and super-speed partitions. Once the XHCI driver
* manages the partitions itself this code can be removed. Note
* that we don't register these partitions with the genpd core
* to avoid it from powering down the partitions as they appear
* to be unused.
*/
if (IS_ENABLED(CONFIG_USB_XHCI_TEGRA) &&
(id == TEGRA_POWERGATE_XUSBA || id == TEGRA_POWERGATE_XUSBC))
goto power_on_cleanup;
pm_genpd_init(&pg->genpd, NULL, off);
if (of_genpd_add_provider_simple(np, &pg->genpd))
err = of_genpd_add_provider_simple(np, &pg->genpd);
if (err < 0) {
dev_err(pmc->dev, "failed to add genpd provider for %s: %d\n",
np->name, err);
goto remove_resets;
}
dev_dbg(pmc->dev, "added power domain %s\n", pg->genpd.name);
return;
power_on_cleanup:
if (off)
WARN_ON(tegra_powergate_power_up(pg, true));
remove_resets:
while (pg->num_resets--)
reset_control_put(pg->resets[pg->num_resets]);
kfree(pg->resets);
remove_clks:
while (pg->num_clks--)
clk_put(pg->clks[pg->num_clks]);
kfree(pg->clks);
set_available:
@ -820,16 +883,20 @@ set_available:
free_mem:
kfree(pg);
error:
dev_err(pmc->dev, "failed to create power domain for %s\n", np->name);
}
static void tegra_powergate_init(struct tegra_pmc *pmc)
static void tegra_powergate_init(struct tegra_pmc *pmc,
struct device_node *parent)
{
struct device_node *np, *child;
unsigned int i;
np = of_get_child_by_name(pmc->dev->of_node, "powergates");
/* Create a bitmap of the available and valid partitions */
for (i = 0; i < pmc->soc->num_powergates; i++)
if (pmc->soc->powergates[i])
set_bit(i, pmc->powergates_available);
np = of_get_child_by_name(parent, "powergates");
if (!np)
return;
@ -1205,6 +1272,14 @@ static int tegra_pmc_probe(struct platform_device *pdev)
struct resource *res;
int err;
/*
* Early initialisation should have configured an initial
* register mapping and setup the soc data pointer. If these
* are not valid then something went badly wrong!
*/
if (WARN_ON(!pmc->base || !pmc->soc))
return -ENODEV;
err = tegra_pmc_parse_dt(pmc, pdev->dev.of_node);
if (err < 0)
return err;
@ -1242,8 +1317,6 @@ static int tegra_pmc_probe(struct platform_device *pdev)
return err;
}
tegra_powergate_init(pmc);
mutex_lock(&pmc->powergates_lock);
iounmap(pmc->base);
pmc->base = base;
@ -1477,10 +1550,11 @@ static int __init tegra_pmc_early_init(void)
const struct of_device_id *match;
struct device_node *np;
struct resource regs;
unsigned int i;
bool invert;
u32 value;
mutex_init(&pmc->powergates_lock);
np = of_find_matching_node_and_match(NULL, tegra_pmc_match, &match);
if (!np) {
/*
@ -1515,39 +1589,40 @@ static int __init tegra_pmc_early_init(void)
*/
if (of_address_to_resource(np, 0, &regs) < 0) {
pr_err("failed to get PMC registers\n");
of_node_put(np);
return -ENXIO;
}
pmc->soc = match->data;
}
pmc->base = ioremap_nocache(regs.start, resource_size(&regs));
if (!pmc->base) {
pr_err("failed to map PMC registers\n");
of_node_put(np);
return -ENXIO;
}
/* Create a bit-map of the available and valid partitions */
for (i = 0; i < pmc->soc->num_powergates; i++)
if (pmc->soc->powergates[i])
set_bit(i, pmc->powergates_available);
if (np) {
pmc->soc = match->data;
mutex_init(&pmc->powergates_lock);
tegra_powergate_init(pmc, np);
/*
* Invert the interrupt polarity if a PMC device tree node exists and
* contains the nvidia,invert-interrupt property.
*/
invert = of_property_read_bool(np, "nvidia,invert-interrupt");
/*
* Invert the interrupt polarity if a PMC device tree node
* exists and contains the nvidia,invert-interrupt property.
*/
invert = of_property_read_bool(np, "nvidia,invert-interrupt");
value = tegra_pmc_readl(PMC_CNTRL);
value = tegra_pmc_readl(PMC_CNTRL);
if (invert)
value |= PMC_CNTRL_INTR_POLARITY;
else
value &= ~PMC_CNTRL_INTR_POLARITY;
if (invert)
value |= PMC_CNTRL_INTR_POLARITY;
else
value &= ~PMC_CNTRL_INTR_POLARITY;
tegra_pmc_writel(value, PMC_CNTRL);
tegra_pmc_writel(value, PMC_CNTRL);
of_node_put(np);
}
return 0;
}

View file

@ -0,0 +1,7 @@
config UX500_SOC_ID
bool "SoC bus for ST-Ericsson ux500"
depends on ARCH_U8500 || COMPILE_TEST
default ARCH_U8500
help
Include support for the SoC bus on the ARM RealView platforms
providing some sysfs information about the ASIC variant.

View file

@ -0,0 +1 @@
obj-$(CONFIG_UX500_SOC_ID) += ux500-soc-id.o

View file

@ -0,0 +1,222 @@
/*
* Copyright (C) ST-Ericsson SA 2010
*
* Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson
* License terms: GNU General Public License (GPL) version 2
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/sys_soc.h>
#include <asm/cputype.h>
#include <asm/tlbflush.h>
#include <asm/cacheflush.h>
#include <asm/mach/map.h>
/**
* struct dbx500_asic_id - fields of the ASIC ID
* @process: the manufacturing process, 0x40 is 40 nm 0x00 is "standard"
* @partnumber: hithereto 0x8500 for DB8500
* @revision: version code in the series
*/
struct dbx500_asic_id {
u16 partnumber;
u8 revision;
u8 process;
};
static struct dbx500_asic_id dbx500_id;
static unsigned int __init ux500_read_asicid(phys_addr_t addr)
{
void __iomem *virt = ioremap(addr, 4);
unsigned int asicid;
if (!virt)
return 0;
asicid = readl(virt);
iounmap(virt);
return asicid;
}
static void ux500_print_soc_info(unsigned int asicid)
{
unsigned int rev = dbx500_id.revision;
pr_info("DB%4x ", dbx500_id.partnumber);
if (rev == 0x01)
pr_cont("Early Drop");
else if (rev >= 0xA0)
pr_cont("v%d.%d" , (rev >> 4) - 0xA + 1, rev & 0xf);
else
pr_cont("Unknown");
pr_cont(" [%#010x]\n", asicid);
}
static unsigned int partnumber(unsigned int asicid)
{
return (asicid >> 8) & 0xffff;
}
/*
* SOC MIDR ASICID ADDRESS ASICID VALUE
* DB8500ed 0x410fc090 0x9001FFF4 0x00850001
* DB8500v1 0x411fc091 0x9001FFF4 0x008500A0
* DB8500v1.1 0x411fc091 0x9001FFF4 0x008500A1
* DB8500v2 0x412fc091 0x9001DBF4 0x008500B0
* DB8520v2.2 0x412fc091 0x9001DBF4 0x008500B2
* DB5500v1 0x412fc091 0x9001FFF4 0x005500A0
* DB9540 0x413fc090 0xFFFFDBF4 0x009540xx
*/
static void __init ux500_setup_id(void)
{
unsigned int cpuid = read_cpuid_id();
unsigned int asicid = 0;
phys_addr_t addr = 0;
switch (cpuid) {
case 0x410fc090: /* DB8500ed */
case 0x411fc091: /* DB8500v1 */
addr = 0x9001FFF4;
break;
case 0x412fc091: /* DB8520 / DB8500v2 / DB5500v1 */
asicid = ux500_read_asicid(0x9001DBF4);
if (partnumber(asicid) == 0x8500 ||
partnumber(asicid) == 0x8520)
/* DB8500v2 */
break;
/* DB5500v1 */
addr = 0x9001FFF4;
break;
case 0x413fc090: /* DB9540 */
addr = 0xFFFFDBF4;
break;
}
if (addr)
asicid = ux500_read_asicid(addr);
if (!asicid) {
pr_err("Unable to identify SoC\n");
BUG();
}
dbx500_id.process = asicid >> 24;
dbx500_id.partnumber = partnumber(asicid);
dbx500_id.revision = asicid & 0xff;
ux500_print_soc_info(asicid);
}
static const char * __init ux500_get_machine(void)
{
return kasprintf(GFP_KERNEL, "DB%4x", dbx500_id.partnumber);
}
static const char * __init ux500_get_family(void)
{
return kasprintf(GFP_KERNEL, "ux500");
}
static const char * __init ux500_get_revision(void)
{
unsigned int rev = dbx500_id.revision;
if (rev == 0x01)
return kasprintf(GFP_KERNEL, "%s", "ED");
else if (rev >= 0xA0)
return kasprintf(GFP_KERNEL, "%d.%d",
(rev >> 4) - 0xA + 1, rev & 0xf);
return kasprintf(GFP_KERNEL, "%s", "Unknown");
}
static ssize_t ux500_get_process(struct device *dev,
struct device_attribute *attr,
char *buf)
{
if (dbx500_id.process == 0x00)
return sprintf(buf, "Standard\n");
return sprintf(buf, "%02xnm\n", dbx500_id.process);
}
static const char *db8500_read_soc_id(struct device_node *backupram)
{
void __iomem *base;
void __iomem *uid;
const char *retstr;
base = of_iomap(backupram, 0);
if (!base)
return NULL;
uid = base + 0x1fc0;
/* Throw these device-specific numbers into the entropy pool */
add_device_randomness(uid, 0x14);
retstr = kasprintf(GFP_KERNEL, "%08x%08x%08x%08x%08x",
readl((u32 *)uid+0),
readl((u32 *)uid+1), readl((u32 *)uid+2),
readl((u32 *)uid+3), readl((u32 *)uid+4));
iounmap(base);
return retstr;
}
static void __init soc_info_populate(struct soc_device_attribute *soc_dev_attr,
struct device_node *backupram)
{
soc_dev_attr->soc_id = db8500_read_soc_id(backupram);
soc_dev_attr->machine = ux500_get_machine();
soc_dev_attr->family = ux500_get_family();
soc_dev_attr->revision = ux500_get_revision();
}
static const struct device_attribute ux500_soc_attr =
__ATTR(process, S_IRUGO, ux500_get_process, NULL);
static int __init ux500_soc_device_init(void)
{
struct device *parent;
struct soc_device *soc_dev;
struct soc_device_attribute *soc_dev_attr;
struct device_node *backupram;
backupram = of_find_compatible_node(NULL, NULL, "ste,dbx500-backupram");
if (!backupram)
return 0;
ux500_setup_id();
soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL);
if (!soc_dev_attr)
return -ENOMEM;
soc_info_populate(soc_dev_attr, backupram);
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
kfree(soc_dev_attr);
return PTR_ERR(soc_dev);
}
parent = soc_device_to_device(soc_dev);
device_create_file(parent, &ux500_soc_attr);
return 0;
}
subsys_initcall(ux500_soc_device_init);