diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-10-20 00:10:27 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-10-20 00:10:27 -0300 |
commit | d0b2f91bede3bd5e3d24dd6803e56eee959c1797 (patch) | |
tree | 7fee4ab0509879c373c4f2cbd5b8a5be5b4041ee /drivers/soc | |
parent | e914f8eb445e8f74b00303c19c2ffceaedd16a05 (diff) |
Linux-libre 4.8.2-gnupck-4.8.2-gnu
Diffstat (limited to 'drivers/soc')
31 files changed, 1832 insertions, 82 deletions
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index cb58ef0d9..fe42a2fdf 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -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 diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 380230f03..50c23d0bd 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -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/ diff --git a/drivers/soc/bcm/Kconfig b/drivers/soc/bcm/Kconfig index 3066edea1..a39b0d58d 100644 --- a/drivers/soc/bcm/Kconfig +++ b/drivers/soc/bcm/Kconfig @@ -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 diff --git a/drivers/soc/bcm/Makefile b/drivers/soc/bcm/Makefile index 63aa3eb23..dc4fced72 100644 --- a/drivers/soc/bcm/Makefile +++ b/drivers/soc/bcm/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_RASPBERRYPI_POWER) += raspberrypi-power.o +obj-$(CONFIG_SOC_BRCMSTB) += brcmstb/ diff --git a/drivers/soc/bcm/brcmstb/Makefile b/drivers/soc/bcm/brcmstb/Makefile new file mode 100644 index 000000000..9120b2715 --- /dev/null +++ b/drivers/soc/bcm/brcmstb/Makefile @@ -0,0 +1 @@ +obj-y += common.o biuctrl.o diff --git a/drivers/soc/bcm/brcmstb/biuctrl.c b/drivers/soc/bcm/brcmstb/biuctrl.c new file mode 100644 index 000000000..3c39415d4 --- /dev/null +++ b/drivers/soc/bcm/brcmstb/biuctrl.c @@ -0,0 +1,117 @@ +/* + * Broadcom STB SoCs Bus Unit Interface controls + * + * Copyright (C) 2015, Broadcom Corporation + * + * 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. + */ + +#define pr_fmt(fmt) "brcmstb: " KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#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 + +static void __iomem *cpubiuctrl_base; +static bool mcp_wr_pairing_en; + +static int __init mcp_write_pairing_set(void) +{ + u32 creds = 0; + + if (!cpubiuctrl_base) + return -1; + + creds = readl_relaxed(cpubiuctrl_base + CPU_CREDIT_REG_OFFSET); + if (mcp_wr_pairing_en) { + pr_info("MCP: Enabling write pairing\n"); + writel_relaxed(creds | CPU_CREDIT_REG_MCPx_WR_PAIRING_EN_MASK, + cpubiuctrl_base + CPU_CREDIT_REG_OFFSET); + } else if (creds & CPU_CREDIT_REG_MCPx_WR_PAIRING_EN_MASK) { + pr_info("MCP: Disabling write pairing\n"); + writel_relaxed(creds & ~CPU_CREDIT_REG_MCPx_WR_PAIRING_EN_MASK, + cpubiuctrl_base + CPU_CREDIT_REG_OFFSET); + } else { + pr_info("MCP: Write pairing already disabled\n"); + } + + return 0; +} + +static int __init setup_hifcpubiuctrl_regs(void) +{ + struct device_node *np; + int ret = 0; + + np = of_find_compatible_node(NULL, NULL, "brcm,brcmstb-cpu-biu-ctrl"); + if (!np) { + pr_err("missing BIU control node\n"); + return -ENODEV; + } + + cpubiuctrl_base = of_iomap(np, 0); + if (!cpubiuctrl_base) { + pr_err("failed to remap BIU control base\n"); + ret = -ENOMEM; + goto out; + } + + mcp_wr_pairing_en = of_property_read_bool(np, "brcm,write-pairing"); +out: + of_node_put(np); + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static u32 cpu_credit_reg_dump; /* for save/restore */ + +static int brcmstb_cpu_credit_reg_suspend(void) +{ + if (cpubiuctrl_base) + cpu_credit_reg_dump = + readl_relaxed(cpubiuctrl_base + CPU_CREDIT_REG_OFFSET); + return 0; +} + +static void brcmstb_cpu_credit_reg_resume(void) +{ + if (cpubiuctrl_base) + writel_relaxed(cpu_credit_reg_dump, + cpubiuctrl_base + CPU_CREDIT_REG_OFFSET); +} + +static struct syscore_ops brcmstb_cpu_credit_syscore_ops = { + .suspend = brcmstb_cpu_credit_reg_suspend, + .resume = brcmstb_cpu_credit_reg_resume, +}; +#endif + + +void __init brcmstb_biuctrl_init(void) +{ + int ret; + + setup_hifcpubiuctrl_regs(); + + ret = mcp_write_pairing_set(); + if (ret) { + pr_err("MCP: Unable to disable write pairing!\n"); + return; + } + +#ifdef CONFIG_PM_SLEEP + register_syscore_ops(&brcmstb_cpu_credit_syscore_ops); +#endif +} diff --git a/drivers/soc/bcm/brcmstb/common.c b/drivers/soc/bcm/brcmstb/common.c new file mode 100644 index 000000000..94e733555 --- /dev/null +++ b/drivers/soc/bcm/brcmstb/common.c @@ -0,0 +1,99 @@ +/* + * Copyright © 2014 NVIDIA Corporation + * Copyright © 2015 Broadcom Corporation + * + * 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/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/soc/brcmstb/brcmstb.h> +#include <linux/sys_soc.h> + +#include <soc/brcmstb/common.h> + +static u32 family_id; +static u32 product_id; + +static const struct of_device_id brcmstb_machine_match[] = { + { .compatible = "brcm,brcmstb", }, + { } +}; + +bool soc_is_brcmstb(void) +{ + struct device_node *root; + + root = of_find_node_by_path("/"); + if (!root) + return false; + + return of_match_node(brcmstb_machine_match, root) != NULL; +} + +static const struct of_device_id sun_top_ctrl_match[] = { + { .compatible = "brcm,brcmstb-sun-top-ctrl", }, + { } +}; + +static int __init brcmstb_soc_device_init(void) +{ + struct soc_device_attribute *soc_dev_attr; + struct soc_device *soc_dev; + struct device_node *sun_top_ctrl; + void __iomem *sun_top_ctrl_base; + int ret = 0; + + sun_top_ctrl = of_find_matching_node(NULL, sun_top_ctrl_match); + if (!sun_top_ctrl) + return -ENODEV; + + sun_top_ctrl_base = of_iomap(sun_top_ctrl, 0); + if (!sun_top_ctrl_base) + return -ENODEV; + + family_id = readl(sun_top_ctrl_base); + product_id = readl(sun_top_ctrl_base + 0x4); + + soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); + if (!soc_dev_attr) { + ret = -ENOMEM; + goto out; + } + + soc_dev_attr->family = kasprintf(GFP_KERNEL, "%x", + family_id >> 28 ? + family_id >> 16 : family_id >> 8); + soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%x", + product_id >> 28 ? + product_id >> 16 : product_id >> 8); + soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%c%d", + ((product_id & 0xf0) >> 4) + 'A', + product_id & 0xf); + + soc_dev = soc_device_register(soc_dev_attr); + if (IS_ERR(soc_dev)) { + kfree(soc_dev_attr->family); + kfree(soc_dev_attr->soc_id); + kfree(soc_dev_attr->revision); + kfree(soc_dev_attr); + ret = -ENODEV; + goto out; + } + + return 0; + +out: + iounmap(sun_top_ctrl_base); + return ret; +} +arch_initcall(brcmstb_soc_device_init); diff --git a/drivers/soc/fsl/qe/Kconfig b/drivers/soc/fsl/qe/Kconfig index 20978f205..73a2e08b4 100644 --- a/drivers/soc/fsl/qe/Kconfig +++ b/drivers/soc/fsl/qe/Kconfig @@ -22,7 +22,7 @@ config UCC_SLOW config UCC_FAST bool - default y if UCC_GETH + default y if UCC_GETH || QE_TDM help This option provides qe_lib support to UCC fast protocols: HDLC, Ethernet, ATM, transparent @@ -31,6 +31,10 @@ config UCC bool default y if UCC_FAST || UCC_SLOW +config QE_TDM + bool + default y if FSL_UCC_HDLC + config QE_USB bool default y if USB_FSL_QE diff --git a/drivers/soc/fsl/qe/Makefile b/drivers/soc/fsl/qe/Makefile index ffac5410c..2031d385b 100644 --- a/drivers/soc/fsl/qe/Makefile +++ b/drivers/soc/fsl/qe/Makefile @@ -6,5 +6,6 @@ obj-$(CONFIG_CPM) += qe_common.o obj-$(CONFIG_UCC) += ucc.o obj-$(CONFIG_UCC_SLOW) += ucc_slow.o obj-$(CONFIG_UCC_FAST) += ucc_fast.o +obj-$(CONFIG_QE_TDM) += qe_tdm.o obj-$(CONFIG_QE_USB) += usb.o obj-$(CONFIG_QE_GPIO) += gpio.o diff --git a/drivers/soc/fsl/qe/qe.c b/drivers/soc/fsl/qe/qe.c index 709fc6380..7026507e6 100644 --- a/drivers/soc/fsl/qe/qe.c +++ b/drivers/soc/fsl/qe/qe.c @@ -239,6 +239,12 @@ enum qe_clock qe_clock_source(const char *source) if (strcasecmp(source, "none") == 0) return QE_CLK_NONE; + if (strcmp(source, "tsync_pin") == 0) + return QE_TSYNC_PIN; + + if (strcmp(source, "rsync_pin") == 0) + return QE_RSYNC_PIN; + if (strncasecmp(source, "brg", 3) == 0) { i = simple_strtoul(source + 3, NULL, 10); if ((i >= 1) && (i <= 16)) diff --git a/drivers/soc/fsl/qe/qe_tdm.c b/drivers/soc/fsl/qe/qe_tdm.c new file mode 100644 index 000000000..5e48b1470 --- /dev/null +++ b/drivers/soc/fsl/qe/qe_tdm.c @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2015 Freescale Semiconductor, Inc. All rights reserved. + * + * Authors: Zhao Qiang <qiang.zhao@nxp.com> + * + * Description: + * QE TDM API Set - TDM specific routines implementations. + * + * 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. + */ +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <soc/fsl/qe/qe_tdm.h> + +static int set_tdm_framer(const char *tdm_framer_type) +{ + if (strcmp(tdm_framer_type, "e1") == 0) + return TDM_FRAMER_E1; + else if (strcmp(tdm_framer_type, "t1") == 0) + return TDM_FRAMER_T1; + else + return -EINVAL; +} + +static void set_si_param(struct ucc_tdm *utdm, struct ucc_tdm_info *ut_info) +{ + struct si_mode_info *si_info = &ut_info->si_info; + + if (utdm->tdm_mode == TDM_INTERNAL_LOOPBACK) { + si_info->simr_crt = 1; + si_info->simr_rfsd = 0; + } +} + +int ucc_of_parse_tdm(struct device_node *np, struct ucc_tdm *utdm, + struct ucc_tdm_info *ut_info) +{ + const char *sprop; + int ret = 0; + u32 val; + struct resource *res; + struct device_node *np2; + static int siram_init_flag; + struct platform_device *pdev; + + sprop = of_get_property(np, "fsl,rx-sync-clock", NULL); + if (sprop) { + ut_info->uf_info.rx_sync = qe_clock_source(sprop); + if ((ut_info->uf_info.rx_sync < QE_CLK_NONE) || + (ut_info->uf_info.rx_sync > QE_RSYNC_PIN)) { + pr_err("QE-TDM: Invalid rx-sync-clock property\n"); + return -EINVAL; + } + } else { + pr_err("QE-TDM: Invalid rx-sync-clock property\n"); + return -EINVAL; + } + + sprop = of_get_property(np, "fsl,tx-sync-clock", NULL); + if (sprop) { + ut_info->uf_info.tx_sync = qe_clock_source(sprop); + if ((ut_info->uf_info.tx_sync < QE_CLK_NONE) || + (ut_info->uf_info.tx_sync > QE_TSYNC_PIN)) { + pr_err("QE-TDM: Invalid tx-sync-clock property\n"); + return -EINVAL; + } + } else { + pr_err("QE-TDM: Invalid tx-sync-clock property\n"); + return -EINVAL; + } + + ret = of_property_read_u32_index(np, "fsl,tx-timeslot-mask", 0, &val); + if (ret) { + pr_err("QE-TDM: Invalid tx-timeslot-mask property\n"); + return -EINVAL; + } + utdm->tx_ts_mask = val; + + ret = of_property_read_u32_index(np, "fsl,rx-timeslot-mask", 0, &val); + if (ret) { + ret = -EINVAL; + pr_err("QE-TDM: Invalid rx-timeslot-mask property\n"); + return ret; + } + utdm->rx_ts_mask = val; + + ret = of_property_read_u32_index(np, "fsl,tdm-id", 0, &val); + if (ret) { + ret = -EINVAL; + pr_err("QE-TDM: No fsl,tdm-id property for this UCC\n"); + return ret; + } + utdm->tdm_port = val; + ut_info->uf_info.tdm_num = utdm->tdm_port; + + if (of_get_property(np, "fsl,tdm-internal-loopback", NULL)) + utdm->tdm_mode = TDM_INTERNAL_LOOPBACK; + else + utdm->tdm_mode = TDM_NORMAL; + + sprop = of_get_property(np, "fsl,tdm-framer-type", NULL); + if (!sprop) { + ret = -EINVAL; + pr_err("QE-TDM: No tdm-framer-type property for UCC\n"); + return ret; + } + ret = set_tdm_framer(sprop); + if (ret < 0) + return -EINVAL; + utdm->tdm_framer_type = ret; + + ret = of_property_read_u32_index(np, "fsl,siram-entry-id", 0, &val); + if (ret) { + ret = -EINVAL; + pr_err("QE-TDM: No siram entry id for UCC\n"); + return ret; + } + utdm->siram_entry_id = val; + + set_si_param(utdm, ut_info); + + np2 = of_find_compatible_node(NULL, NULL, "fsl,t1040-qe-si"); + if (!np2) + return -EINVAL; + + pdev = of_find_device_by_node(np2); + if (!pdev) { + pr_err("%s: failed to lookup pdev\n", np2->name); + of_node_put(np2); + return -EINVAL; + } + + of_node_put(np2); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + utdm->si_regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(utdm->si_regs)) { + ret = PTR_ERR(utdm->si_regs); + goto err_miss_siram_property; + } + + np2 = of_find_compatible_node(NULL, NULL, "fsl,t1040-qe-siram"); + if (!np2) { + ret = -EINVAL; + goto err_miss_siram_property; + } + + pdev = of_find_device_by_node(np2); + if (!pdev) { + ret = -EINVAL; + pr_err("%s: failed to lookup pdev\n", np2->name); + of_node_put(np2); + goto err_miss_siram_property; + } + + of_node_put(np2); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + utdm->siram = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(utdm->siram)) { + ret = PTR_ERR(utdm->siram); + goto err_miss_siram_property; + } + + if (siram_init_flag == 0) { + memset_io(utdm->siram, 0, res->end - res->start + 1); + siram_init_flag = 1; + } + + return ret; + +err_miss_siram_property: + devm_iounmap(&pdev->dev, utdm->si_regs); + return ret; +} + +void ucc_tdm_init(struct ucc_tdm *utdm, struct ucc_tdm_info *ut_info) +{ + struct si1 __iomem *si_regs; + u16 __iomem *siram; + u16 siram_entry_valid; + u16 siram_entry_closed; + u16 ucc_num; + u8 csel; + u16 sixmr; + u16 tdm_port; + u32 siram_entry_id; + u32 mask; + int i; + + si_regs = utdm->si_regs; + siram = utdm->siram; + ucc_num = ut_info->uf_info.ucc_num; + tdm_port = utdm->tdm_port; + siram_entry_id = utdm->siram_entry_id; + + if (utdm->tdm_framer_type == TDM_FRAMER_T1) + utdm->num_of_ts = 24; + if (utdm->tdm_framer_type == TDM_FRAMER_E1) + utdm->num_of_ts = 32; + + /* set siram table */ + csel = (ucc_num < 4) ? ucc_num + 9 : ucc_num - 3; + + siram_entry_valid = SIR_CSEL(csel) | SIR_BYTE | SIR_CNT(0); + siram_entry_closed = SIR_IDLE | SIR_BYTE | SIR_CNT(0); + + for (i = 0; i < utdm->num_of_ts; i++) { + mask = 0x01 << i; + + if (utdm->tx_ts_mask & mask) + iowrite16be(siram_entry_valid, + &siram[siram_entry_id * 32 + i]); + else + iowrite16be(siram_entry_closed, + &siram[siram_entry_id * 32 + i]); + + if (utdm->rx_ts_mask & mask) + iowrite16be(siram_entry_valid, + &siram[siram_entry_id * 32 + 0x200 + i]); + else + iowrite16be(siram_entry_closed, + &siram[siram_entry_id * 32 + 0x200 + i]); + } + + setbits16(&siram[(siram_entry_id * 32) + (utdm->num_of_ts - 1)], + SIR_LAST); + setbits16(&siram[(siram_entry_id * 32) + 0x200 + (utdm->num_of_ts - 1)], + SIR_LAST); + + /* Set SIxMR register */ + sixmr = SIMR_SAD(siram_entry_id); + + sixmr &= ~SIMR_SDM_MASK; + + if (utdm->tdm_mode == TDM_INTERNAL_LOOPBACK) + sixmr |= SIMR_SDM_INTERNAL_LOOPBACK; + else + sixmr |= SIMR_SDM_NORMAL; + + sixmr |= SIMR_RFSD(ut_info->si_info.simr_rfsd) | + SIMR_TFSD(ut_info->si_info.simr_tfsd); + + if (ut_info->si_info.simr_crt) + sixmr |= SIMR_CRT; + if (ut_info->si_info.simr_sl) + sixmr |= SIMR_SL; + if (ut_info->si_info.simr_ce) + sixmr |= SIMR_CE; + if (ut_info->si_info.simr_fe) + sixmr |= SIMR_FE; + if (ut_info->si_info.simr_gm) + sixmr |= SIMR_GM; + + switch (tdm_port) { + case 0: + iowrite16be(sixmr, &si_regs->sixmr1[0]); + break; + case 1: + iowrite16be(sixmr, &si_regs->sixmr1[1]); + break; + case 2: + iowrite16be(sixmr, &si_regs->sixmr1[2]); + break; + case 3: + iowrite16be(sixmr, &si_regs->sixmr1[3]); + break; + default: + pr_err("QE-TDM: can not find tdm sixmr reg\n"); + break; + } +} diff --git a/drivers/soc/fsl/qe/ucc.c b/drivers/soc/fsl/qe/ucc.c index b59d3358f..c646d8713 100644 --- a/drivers/soc/fsl/qe/ucc.c +++ b/drivers/soc/fsl/qe/ucc.c @@ -25,6 +25,12 @@ #include <soc/fsl/qe/qe.h> #include <soc/fsl/qe/ucc.h> +#define UCC_TDM_NUM 8 +#define RX_SYNC_SHIFT_BASE 30 +#define TX_SYNC_SHIFT_BASE 14 +#define RX_CLK_SHIFT_BASE 28 +#define TX_CLK_SHIFT_BASE 12 + int ucc_set_qe_mux_mii_mng(unsigned int ucc_num) { unsigned long flags; @@ -210,3 +216,447 @@ int ucc_set_qe_mux_rxtx(unsigned int ucc_num, enum qe_clock clock, return 0; } + +static int ucc_get_tdm_common_clk(u32 tdm_num, enum qe_clock clock) +{ + int clock_bits = -EINVAL; + + /* + * for TDM[0, 1, 2, 3], TX and RX use common + * clock source BRG3,4 and CLK1,2 + * for TDM[4, 5, 6, 7], TX and RX use common + * clock source BRG12,13 and CLK23,24 + */ + switch (tdm_num) { + case 0: + case 1: + case 2: + case 3: + switch (clock) { + case QE_BRG3: + clock_bits = 1; + break; + case QE_BRG4: + clock_bits = 2; + break; + case QE_CLK1: + clock_bits = 4; + break; + case QE_CLK2: + clock_bits = 5; + break; + default: + break; + } + break; + case 4: + case 5: + case 6: + case 7: + switch (clock) { + case QE_BRG12: + clock_bits = 1; + break; + case QE_BRG13: + clock_bits = 2; + break; + case QE_CLK23: + clock_bits = 4; + break; + case QE_CLK24: + clock_bits = 5; + break; + default: + break; + } + break; + default: + break; + } + + return clock_bits; +} + +static int ucc_get_tdm_rx_clk(u32 tdm_num, enum qe_clock clock) +{ + int clock_bits = -EINVAL; + + switch (tdm_num) { + case 0: + switch (clock) { + case QE_CLK3: + clock_bits = 6; + break; + case QE_CLK8: + clock_bits = 7; + break; + default: + break; + } + break; + case 1: + switch (clock) { + case QE_CLK5: + clock_bits = 6; + break; + case QE_CLK10: + clock_bits = 7; + break; + default: + break; + } + break; + case 2: + switch (clock) { + case QE_CLK7: + clock_bits = 6; + break; + case QE_CLK12: + clock_bits = 7; + break; + default: + break; + } + break; + case 3: + switch (clock) { + case QE_CLK9: + clock_bits = 6; + break; + case QE_CLK14: + clock_bits = 7; + break; + default: + break; + } + break; + case 4: + switch (clock) { + case QE_CLK11: + clock_bits = 6; + break; + case QE_CLK16: + clock_bits = 7; + break; + default: + break; + } + break; + case 5: + switch (clock) { + case QE_CLK13: + clock_bits = 6; + break; + case QE_CLK18: + clock_bits = 7; + break; + default: + break; + } + break; + case 6: + switch (clock) { + case QE_CLK15: + clock_bits = 6; + break; + case QE_CLK20: + clock_bits = 7; + break; + default: + break; + } + break; + case 7: + switch (clock) { + case QE_CLK17: + clock_bits = 6; + break; + case QE_CLK22: + clock_bits = 7; + break; + default: + break; + } + break; + } + + return clock_bits; +} + +static int ucc_get_tdm_tx_clk(u32 tdm_num, enum qe_clock clock) +{ + int clock_bits = -EINVAL; + + switch (tdm_num) { + case 0: + switch (clock) { + case QE_CLK4: + clock_bits = 6; + break; + case QE_CLK9: + clock_bits = 7; + break; + default: + break; + } + break; + case 1: + switch (clock) { + case QE_CLK6: + clock_bits = 6; + break; + case QE_CLK11: + clock_bits = 7; + break; + default: + break; + } + break; + case 2: + switch (clock) { + case QE_CLK8: + clock_bits = 6; + break; + case QE_CLK13: + clock_bits = 7; + break; + default: + break; + } + break; + case 3: + switch (clock) { + case QE_CLK10: + clock_bits = 6; + break; + case QE_CLK15: + clock_bits = 7; + break; + default: + break; + } + break; + case 4: + switch (clock) { + case QE_CLK12: + clock_bits = 6; + break; + case QE_CLK17: + clock_bits = 7; + break; + default: + break; + } + break; + case 5: + switch (clock) { + case QE_CLK14: + clock_bits = 6; + break; + case QE_CLK19: + clock_bits = 7; + break; + default: + break; + } + break; + case 6: + switch (clock) { + case QE_CLK16: + clock_bits = 6; + break; + case QE_CLK21: + clock_bits = 7; + break; + default: + break; + } + break; + case 7: + switch (clock) { + case QE_CLK18: + clock_bits = 6; + break; + case QE_CLK3: + clock_bits = 7; + break; + default: + break; + } + break; + } + + return clock_bits; +} + +/* tdm_num: TDM A-H port num is 0-7 */ +static int ucc_get_tdm_rxtx_clk(enum comm_dir mode, u32 tdm_num, + enum qe_clock clock) +{ + int clock_bits; + + clock_bits = ucc_get_tdm_common_clk(tdm_num, clock); + if (clock_bits > 0) + return clock_bits; + if (mode == COMM_DIR_RX) + clock_bits = ucc_get_tdm_rx_clk(tdm_num, clock); + if (mode == COMM_DIR_TX) + clock_bits = ucc_get_tdm_tx_clk(tdm_num, clock); + return clock_bits; +} + +static u32 ucc_get_tdm_clk_shift(enum comm_dir mode, u32 tdm_num) +{ + u32 shift; + + shift = (mode == COMM_DIR_RX) ? RX_CLK_SHIFT_BASE : TX_CLK_SHIFT_BASE; + if (tdm_num < 4) + shift -= tdm_num * 4; + else + shift -= (tdm_num - 4) * 4; + + return shift; +} + +int ucc_set_tdm_rxtx_clk(u32 tdm_num, enum qe_clock clock, + enum comm_dir mode) +{ + int clock_bits; + u32 shift; + struct qe_mux __iomem *qe_mux_reg; + __be32 __iomem *cmxs1cr; + + qe_mux_reg = &qe_immr->qmx; + + if (tdm_num > 7 || tdm_num < 0) + return -EINVAL; + + /* The communications direction must be RX or TX */ + if (mode != COMM_DIR_RX && mode != COMM_DIR_TX) + return -EINVAL; + + clock_bits = ucc_get_tdm_rxtx_clk(mode, tdm_num, clock); + if (clock_bits < 0) + return -EINVAL; + + shift = ucc_get_tdm_clk_shift(mode, tdm_num); + + cmxs1cr = (tdm_num < 4) ? &qe_mux_reg->cmxsi1cr_l : + &qe_mux_reg->cmxsi1cr_h; + + qe_clrsetbits32(cmxs1cr, QE_CMXUCR_TX_CLK_SRC_MASK << shift, + clock_bits << shift); + + return 0; +} + +static int ucc_get_tdm_sync_source(u32 tdm_num, enum qe_clock clock, + enum comm_dir mode) +{ + int source = -EINVAL; + + if (mode == COMM_DIR_RX && clock == QE_RSYNC_PIN) { + source = 0; + return source; + } + if (mode == COMM_DIR_TX && clock == QE_TSYNC_PIN) { + source = 0; + return source; + } + + switch (tdm_num) { + case 0: + case 1: + switch (clock) { + case QE_BRG9: + source = 1; + break; + case QE_BRG10: + source = 2; + break; + default: + break; + } + break; + case 2: + case 3: + switch (clock) { + case QE_BRG9: + source = 1; + break; + case QE_BRG11: + source = 2; + break; + default: + break; + } + break; + case 4: + case 5: + switch (clock) { + case QE_BRG13: + source = 1; + break; + case QE_BRG14: + source = 2; + break; + default: + break; + } + break; + case 6: + case 7: + switch (clock) { + case QE_BRG13: + source = 1; + break; + case QE_BRG15: + source = 2; + break; + default: + break; + } + break; + } + + return source; +} + +static u32 ucc_get_tdm_sync_shift(enum comm_dir mode, u32 tdm_num) +{ + u32 shift; + + shift = (mode == COMM_DIR_RX) ? RX_SYNC_SHIFT_BASE : RX_SYNC_SHIFT_BASE; + shift -= tdm_num * 2; + + return shift; +} + +int ucc_set_tdm_rxtx_sync(u32 tdm_num, enum qe_clock clock, + enum comm_dir mode) +{ + int source; + u32 shift; + struct qe_mux *qe_mux_reg; + + qe_mux_reg = &qe_immr->qmx; + + if (tdm_num >= UCC_TDM_NUM) + return -EINVAL; + + /* The communications direction must be RX or TX */ + if (mode != COMM_DIR_RX && mode != COMM_DIR_TX) + return -EINVAL; + + source = ucc_get_tdm_sync_source(tdm_num, clock, mode); + if (source < 0) + return -EINVAL; + + shift = ucc_get_tdm_sync_shift(mode, tdm_num); + + qe_clrsetbits32(&qe_mux_reg->cmxsi1syr, + QE_CMXUCR_TX_CLK_SRC_MASK << shift, + source << shift); + + return 0; +} diff --git a/drivers/soc/fsl/qe/ucc_fast.c b/drivers/soc/fsl/qe/ucc_fast.c index a7689310f..83d8d16e3 100644 --- a/drivers/soc/fsl/qe/ucc_fast.c +++ b/drivers/soc/fsl/qe/ucc_fast.c @@ -327,6 +327,42 @@ int ucc_fast_init(struct ucc_fast_info * uf_info, struct ucc_fast_private ** ucc ucc_fast_free(uccf); return -EINVAL; } + } else { + /* tdm Rx clock routing */ + if ((uf_info->rx_clock != QE_CLK_NONE) && + ucc_set_tdm_rxtx_clk(uf_info->tdm_num, uf_info->rx_clock, + COMM_DIR_RX)) { + pr_err("%s: illegal value for RX clock", __func__); + ucc_fast_free(uccf); + return -EINVAL; + } + + /* tdm Tx clock routing */ + if ((uf_info->tx_clock != QE_CLK_NONE) && + ucc_set_tdm_rxtx_clk(uf_info->tdm_num, uf_info->tx_clock, + COMM_DIR_TX)) { + pr_err("%s: illegal value for TX clock", __func__); + ucc_fast_free(uccf); + return -EINVAL; + } + + /* tdm Rx sync clock routing */ + if ((uf_info->rx_sync != QE_CLK_NONE) && + ucc_set_tdm_rxtx_sync(uf_info->tdm_num, uf_info->rx_sync, + COMM_DIR_RX)) { + pr_err("%s: illegal value for RX clock", __func__); + ucc_fast_free(uccf); + return -EINVAL; + } + + /* tdm Tx sync clock routing */ + if ((uf_info->tx_sync != QE_CLK_NONE) && + ucc_set_tdm_rxtx_sync(uf_info->tdm_num, uf_info->tx_sync, + COMM_DIR_TX)) { + pr_err("%s: illegal value for TX clock", __func__); + ucc_fast_free(uccf); + return -EINVAL; + } } /* Set interrupt mask register at UCC level. */ diff --git a/drivers/soc/qcom/smem_state.c b/drivers/soc/qcom/smem_state.c index 54261decb..d5437ca76 100644 --- a/drivers/soc/qcom/smem_state.c +++ b/drivers/soc/qcom/smem_state.c @@ -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); } diff --git a/drivers/soc/qcom/smp2p.c b/drivers/soc/qcom/smp2p.c index 9c2788b8f..f51fb2ea7 100644 --- a/drivers/soc/qcom/smp2p.c +++ b/drivers/soc/qcom/smp2p.c @@ -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,7 +343,7 @@ 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); + 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; diff --git a/drivers/soc/qcom/smsm.c b/drivers/soc/qcom/smsm.c index 6b777af1b..d0337b2a7 100644 --- a/drivers/soc/qcom/smsm.c +++ b/drivers/soc/qcom/smsm.c @@ -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; diff --git a/drivers/soc/qcom/wcnss_ctrl.c b/drivers/soc/qcom/wcnss_ctrl.c index f633dcc96..859445eb9 100644 --- a/drivers/soc/qcom/wcnss_ctrl.c +++ b/drivers/soc/qcom/wcnss_ctrl.c @@ -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 "/*(DEBLOBBED)*/" @@ -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; + + 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 qcom_smd_send(wcnss->channel, &msg, sizeof(msg)); + 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 = reject_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); + + schedule_work(&wcnss->probe_work); + + return 0; +} + +static void wcnss_ctrl_remove(struct qcom_smd_device *sdev) +{ + struct wcnss_ctrl *wcnss = qcom_smd_get_drvdata(sdev->channel); - return wcnss_request_version(wcnss); + cancel_work_sync(&wcnss->probe_work); + of_platform_depopulate(&sdev->dev); } -static const struct qcom_smd_id wcnss_ctrl_smd_match[] = { - { .name = "WCNSS_CTRL" }, +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, }, }; diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile index 151fcd3f0..623039c35 100644 --- a/drivers/soc/renesas/Makefile +++ b/drivers/soc/renesas/Makefile @@ -1,7 +1,9 @@ obj-$(CONFIG_ARCH_R8A7779) += rcar-sysc.o r8a7779-sysc.o obj-$(CONFIG_ARCH_R8A7790) += rcar-sysc.o r8a7790-sysc.o obj-$(CONFIG_ARCH_R8A7791) += rcar-sysc.o r8a7791-sysc.o +obj-$(CONFIG_ARCH_R8A7792) += rcar-sysc.o r8a7792-sysc.o # R-Car M2-N is identical to R-Car M2-W w.r.t. power domains. obj-$(CONFIG_ARCH_R8A7793) += rcar-sysc.o r8a7791-sysc.o obj-$(CONFIG_ARCH_R8A7794) += rcar-sysc.o r8a7794-sysc.o obj-$(CONFIG_ARCH_R8A7795) += rcar-sysc.o r8a7795-sysc.o +obj-$(CONFIG_ARCH_R8A7796) += rcar-sysc.o r8a7796-sysc.o diff --git a/drivers/soc/renesas/r8a7792-sysc.c b/drivers/soc/renesas/r8a7792-sysc.c new file mode 100644 index 000000000..ca7467d7b --- /dev/null +++ b/drivers/soc/renesas/r8a7792-sysc.c @@ -0,0 +1,34 @@ +/* + * Renesas R-Car V2H (R8A7792) System Controller + * + * Copyright (C) 2016 Cogent Embedded Inc. + * + * 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 of the License. + */ + +#include <linux/bug.h> +#include <linux/init.h> +#include <linux/kernel.h> + +#include <dt-bindings/power/r8a7792-sysc.h> + +#include "rcar-sysc.h" + +static const struct rcar_sysc_area r8a7792_areas[] __initconst = { + { "always-on", 0, 0, R8A7792_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, + { "ca15-scu", 0x180, 0, R8A7792_PD_CA15_SCU, R8A7792_PD_ALWAYS_ON, + PD_SCU }, + { "ca15-cpu0", 0x40, 0, R8A7792_PD_CA15_CPU0, R8A7792_PD_CA15_SCU, + PD_CPU_NOCR }, + { "ca15-cpu1", 0x40, 1, R8A7792_PD_CA15_CPU1, R8A7792_PD_CA15_SCU, + PD_CPU_NOCR }, + { "sgx", 0xc0, 0, R8A7792_PD_SGX, R8A7792_PD_ALWAYS_ON }, + { "imp", 0x140, 0, R8A7792_PD_IMP, R8A7792_PD_ALWAYS_ON }, +}; + +const struct rcar_sysc_info r8a7792_sysc_info __initconst = { + .areas = r8a7792_areas, + .num_areas = ARRAY_SIZE(r8a7792_areas), +}; diff --git a/drivers/soc/renesas/r8a7796-sysc.c b/drivers/soc/renesas/r8a7796-sysc.c new file mode 100644 index 000000000..f700c842b --- /dev/null +++ b/drivers/soc/renesas/r8a7796-sysc.c @@ -0,0 +1,48 @@ +/* + * Renesas R-Car M3-W System Controller + * + * Copyright (C) 2016 Glider bvba + * + * 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 of the License. + */ + +#include <linux/bug.h> +#include <linux/kernel.h> + +#include <dt-bindings/power/r8a7796-sysc.h> + +#include "rcar-sysc.h" + +static const struct rcar_sysc_area r8a7796_areas[] __initconst = { + { "always-on", 0, 0, R8A7796_PD_ALWAYS_ON, -1, PD_ALWAYS_ON }, + { "ca57-scu", 0x1c0, 0, R8A7796_PD_CA57_SCU, R8A7796_PD_ALWAYS_ON, + PD_SCU }, + { "ca57-cpu0", 0x80, 0, R8A7796_PD_CA57_CPU0, R8A7796_PD_CA57_SCU, + PD_CPU_NOCR }, + { "ca57-cpu1", 0x80, 1, R8A7796_PD_CA57_CPU1, R8A7796_PD_CA57_SCU, + PD_CPU_NOCR }, + { "ca53-scu", 0x140, 0, R8A7796_PD_CA53_SCU, R8A7796_PD_ALWAYS_ON, + PD_SCU }, + { "ca53-cpu0", 0x200, 0, R8A7796_PD_CA53_CPU0, R8A7796_PD_CA53_SCU, + PD_CPU_NOCR }, + { "ca53-cpu1", 0x200, 1, R8A7796_PD_CA53_CPU1, R8A7796_PD_CA53_SCU, + PD_CPU_NOCR }, + { "ca53-cpu2", 0x200, 2, R8A7796_PD_CA53_CPU2, R8A7796_PD_CA53_SCU, + PD_CPU_NOCR }, + { "ca53-cpu3", 0x200, 3, R8A7796_PD_CA53_CPU3, R8A7796_PD_CA53_SCU, + PD_CPU_NOCR }, + { "cr7", 0x240, 0, R8A7796_PD_CR7, R8A7796_PD_ALWAYS_ON }, + { "a3vc", 0x380, 0, R8A7796_PD_A3VC, R8A7796_PD_ALWAYS_ON }, + { "a2vc0", 0x3c0, 0, R8A7796_PD_A2VC0, R8A7796_PD_A3VC }, + { "a2vc1", 0x3c0, 1, R8A7796_PD_A2VC1, R8A7796_PD_A3VC }, + { "3dg-a", 0x100, 0, R8A7796_PD_3DG_A, R8A7796_PD_ALWAYS_ON }, + { "3dg-b", 0x100, 1, R8A7796_PD_3DG_B, R8A7796_PD_3DG_A }, + { "a3ir", 0x180, 0, R8A7796_PD_A3IR, R8A7796_PD_ALWAYS_ON }, +}; + +const struct rcar_sysc_info r8a7796_sysc_info __initconst = { + .areas = r8a7796_areas, + .num_areas = ARRAY_SIZE(r8a7796_areas), +}; diff --git a/drivers/soc/renesas/rcar-sysc.c b/drivers/soc/renesas/rcar-sysc.c index 79dbc7708..65c8e1eb9 100644 --- a/drivers/soc/renesas/rcar-sysc.c +++ b/drivers/soc/renesas/rcar-sysc.c @@ -164,15 +164,6 @@ static bool rcar_sysc_power_is_off(const struct rcar_sysc_ch *sysc_ch) return false; } -void __iomem *rcar_sysc_init(phys_addr_t base) -{ - rcar_sysc_base = ioremap_nocache(base, PAGE_SIZE); - if (!rcar_sysc_base) - panic("unable to ioremap R-Car SYSC hardware block\n"); - - return rcar_sysc_base; -} - struct rcar_sysc_pd { struct generic_pm_domain genpd; struct rcar_sysc_ch ch; @@ -293,6 +284,9 @@ static const struct of_device_id rcar_sysc_matches[] = { #ifdef CONFIG_ARCH_R8A7791 { .compatible = "renesas,r8a7791-sysc", .data = &r8a7791_sysc_info }, #endif +#ifdef CONFIG_ARCH_R8A7792 + { .compatible = "renesas,r8a7792-sysc", .data = &r8a7792_sysc_info }, +#endif #ifdef CONFIG_ARCH_R8A7793 /* R-Car M2-N is identical to R-Car M2-W w.r.t. power domains. */ { .compatible = "renesas,r8a7793-sysc", .data = &r8a7791_sysc_info }, @@ -303,6 +297,9 @@ static const struct of_device_id rcar_sysc_matches[] = { #ifdef CONFIG_ARCH_R8A7795 { .compatible = "renesas,r8a7795-sysc", .data = &r8a7795_sysc_info }, #endif +#ifdef CONFIG_ARCH_R8A7796 + { .compatible = "renesas,r8a7796-sysc", .data = &r8a7796_sysc_info }, +#endif { /* sentinel */ } }; @@ -322,6 +319,9 @@ static int __init rcar_sysc_pd_init(void) unsigned int i; int error; + if (rcar_sysc_base) + return 0; + np = of_find_matching_node_and_match(NULL, rcar_sysc_matches, &match); if (!np) return -ENODEV; @@ -392,10 +392,35 @@ static int __init rcar_sysc_pd_init(void) domains->domains[area->isr_bit] = &pd->genpd; } - of_genpd_add_provider_onecell(np, &domains->onecell_data); + error = of_genpd_add_provider_onecell(np, &domains->onecell_data); out_put: of_node_put(np); return error; } early_initcall(rcar_sysc_pd_init); + +void __init rcar_sysc_init(phys_addr_t base, u32 syscier) +{ + u32 syscimr; + + if (!rcar_sysc_pd_init()) + return; + + rcar_sysc_base = ioremap_nocache(base, PAGE_SIZE); + + /* + * Mask all interrupt sources to prevent the CPU from receiving them. + * Make sure not to clear reserved bits that were set before. + */ + syscimr = ioread32(rcar_sysc_base + SYSCIMR); + syscimr |= syscier; + pr_debug("%s: syscimr = 0x%08x\n", __func__, syscimr); + iowrite32(syscimr, rcar_sysc_base + SYSCIMR); + + /* + * SYSC needs all interrupt sources enabled to control power. + */ + pr_debug("%s: syscier = 0x%08x\n", __func__, syscier); + iowrite32(syscier, rcar_sysc_base + SYSCIER); +} diff --git a/drivers/soc/renesas/rcar-sysc.h b/drivers/soc/renesas/rcar-sysc.h index 5e766174c..77dbe8614 100644 --- a/drivers/soc/renesas/rcar-sysc.h +++ b/drivers/soc/renesas/rcar-sysc.h @@ -53,6 +53,8 @@ struct rcar_sysc_info { extern const struct rcar_sysc_info r8a7779_sysc_info; extern const struct rcar_sysc_info r8a7790_sysc_info; extern const struct rcar_sysc_info r8a7791_sysc_info; +extern const struct rcar_sysc_info r8a7792_sysc_info; extern const struct rcar_sysc_info r8a7794_sysc_info; extern const struct rcar_sysc_info r8a7795_sysc_info; +extern const struct rcar_sysc_info r8a7796_sysc_info; #endif /* __SOC_RENESAS_RCAR_SYSC_H__ */ diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig index d7fc12300..245533907 100644 --- a/drivers/soc/samsung/Kconfig +++ b/drivers/soc/samsung/Kconfig @@ -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 diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile index f64ac4d80..3619f2ecd 100644 --- a/drivers/soc/samsung/Makefile +++ b/drivers/soc/samsung/Makefile @@ -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 diff --git a/drivers/soc/samsung/exynos3250-pmu.c b/drivers/soc/samsung/exynos3250-pmu.c index 20b3ab8aa..af2f54e14 100644 --- a/drivers/soc/samsung/exynos3250-pmu.c +++ b/drivers/soc/samsung/exynos3250-pmu.c @@ -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} }, diff --git a/drivers/soc/samsung/exynos5420-pmu.c b/drivers/soc/samsung/exynos5420-pmu.c index b962fb6a5..3f2c64180 100644 --- a/drivers/soc/samsung/exynos5420-pmu.c +++ b/drivers/soc/samsung/exynos5420-pmu.c @@ -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} }, diff --git a/drivers/soc/samsung/pm_domains.c b/drivers/soc/samsung/pm_domains.c new file mode 100644 index 000000000..4822346aa --- /dev/null +++ b/drivers/soc/samsung/pm_domains.c @@ -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); diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 5031c89b0..71c834f38 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -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)) + 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; + } - if (tegra_powergate_of_get_resets(pg, np)) + 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; + } - off = !tegra_powergate_is_powered(pg->id); + 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; @@ -1250,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; @@ -1485,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) { /* @@ -1523,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; } diff --git a/drivers/soc/ux500/Kconfig b/drivers/soc/ux500/Kconfig new file mode 100644 index 000000000..025a44aef --- /dev/null +++ b/drivers/soc/ux500/Kconfig @@ -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. diff --git a/drivers/soc/ux500/Makefile b/drivers/soc/ux500/Makefile new file mode 100644 index 000000000..0b87ad04b --- /dev/null +++ b/drivers/soc/ux500/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_UX500_SOC_ID) += ux500-soc-id.o diff --git a/drivers/soc/ux500/ux500-soc-id.c b/drivers/soc/ux500/ux500-soc-id.c new file mode 100644 index 000000000..6c1be74e5 --- /dev/null +++ b/drivers/soc/ux500/ux500-soc-id.c @@ -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); |