mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-29 11:11:45 -04:00
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:
commit
43a0a98aa8
140 changed files with 4776 additions and 2641 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o
|
||||
obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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} },
|
||||
|
|
|
|||
|
|
@ -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} },
|
||||
|
|
|
|||
245
drivers/soc/samsung/pm_domains.c
Normal file
245
drivers/soc/samsung/pm_domains.c
Normal 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);
|
||||
|
|
@ -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, ®s) < 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(®s));
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
7
drivers/soc/ux500/Kconfig
Normal file
7
drivers/soc/ux500/Kconfig
Normal 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.
|
||||
1
drivers/soc/ux500/Makefile
Normal file
1
drivers/soc/ux500/Makefile
Normal file
|
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_UX500_SOC_ID) += ux500-soc-id.o
|
||||
222
drivers/soc/ux500/ux500-soc-id.c
Normal file
222
drivers/soc/ux500/ux500-soc-id.c
Normal 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);
|
||||
Loading…
Reference in a new issue