diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/reset | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
53 files changed, 9392 insertions, 0 deletions
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig new file mode 100644 index 000000000..2a52c990d --- /dev/null +++ b/drivers/reset/Kconfig @@ -0,0 +1,327 @@ +# SPDX-License-Identifier: GPL-2.0-only +config ARCH_HAS_RESET_CONTROLLER + bool + +menuconfig RESET_CONTROLLER + bool "Reset Controller Support" + default y if ARCH_HAS_RESET_CONTROLLER + help + Generic Reset Controller support. + + This framework is designed to abstract reset handling of devices + via GPIOs or SoC-internal reset controller modules. + + If unsure, say no. + +if RESET_CONTROLLER + +config RESET_A10SR + tristate "Altera Arria10 System Resource Reset" + depends on MFD_ALTERA_A10SR || COMPILE_TEST + help + This option enables support for the external reset functions for + peripheral PHYs on the Altera Arria10 System Resource Chip. + +config RESET_ATH79 + bool "AR71xx Reset Driver" if COMPILE_TEST + default ATH79 + help + This enables the ATH79 reset controller driver that supports the + AR71xx SoC reset controller. + +config RESET_AXS10X + bool "AXS10x Reset Driver" if COMPILE_TEST + default ARC_PLAT_AXS10X + help + This enables the reset controller driver for AXS10x. + +config RESET_BCM6345 + bool "BCM6345 Reset Controller" + depends on BMIPS_GENERIC || COMPILE_TEST + default BMIPS_GENERIC + help + This enables the reset controller driver for BCM6345 SoCs. + +config RESET_BERLIN + tristate "Berlin Reset Driver" + depends on ARCH_BERLIN || COMPILE_TEST + default m if ARCH_BERLIN + help + This enables the reset controller driver for Marvell Berlin SoCs. + +config RESET_BRCMSTB + tristate "Broadcom STB reset controller" + depends on ARCH_BRCMSTB || COMPILE_TEST + default ARCH_BRCMSTB + help + This enables the reset controller driver for Broadcom STB SoCs using + a SUN_TOP_CTRL_SW_INIT style controller. + +config RESET_BRCMSTB_RESCAL + tristate "Broadcom STB RESCAL reset controller" + depends on HAS_IOMEM + depends on ARCH_BRCMSTB || COMPILE_TEST + default ARCH_BRCMSTB + help + This enables the RESCAL reset controller for SATA, PCIe0, or PCIe1 on + BCM7216. + +config RESET_HSDK + bool "Synopsys HSDK Reset Driver" + depends on HAS_IOMEM + depends on ARC_SOC_HSDK || COMPILE_TEST + help + This enables the reset controller driver for HSDK board. + +config RESET_IMX7 + tristate "i.MX7/8 Reset Driver" + depends on HAS_IOMEM + depends on SOC_IMX7D || (ARM64 && ARCH_MXC) || COMPILE_TEST + default y if SOC_IMX7D + select MFD_SYSCON + help + This enables the reset controller driver for i.MX7 SoCs. + +config RESET_INTEL_GW + bool "Intel Reset Controller Driver" + depends on X86 || COMPILE_TEST + depends on OF && HAS_IOMEM + select REGMAP_MMIO + help + This enables the reset controller driver for Intel Gateway SoCs. + Say Y to control the reset signals provided by reset controller. + Otherwise, say N. + +config RESET_K210 + bool "Reset controller driver for Canaan Kendryte K210 SoC" + depends on (SOC_CANAAN || COMPILE_TEST) && OF + select MFD_SYSCON + default SOC_CANAAN + help + Support for the Canaan Kendryte K210 RISC-V SoC reset controller. + Say Y if you want to control reset signals provided by this + controller. + +config RESET_LANTIQ + bool "Lantiq XWAY Reset Driver" if COMPILE_TEST + default SOC_TYPE_XWAY + help + This enables the reset controller driver for Lantiq / Intel XWAY SoCs. + +config RESET_LPC18XX + bool "LPC18xx/43xx Reset Driver" if COMPILE_TEST + default ARCH_LPC18XX + help + This enables the reset controller driver for NXP LPC18xx/43xx SoCs. + +config RESET_MCHP_SPARX5 + bool "Microchip Sparx5 reset driver" + depends on ARCH_SPARX5 || SOC_LAN966 || COMPILE_TEST + default y if SPARX5_SWITCH + select MFD_SYSCON + help + This driver supports switch core reset for the Microchip Sparx5 SoC. + +config RESET_MESON + tristate "Meson Reset Driver" + depends on ARCH_MESON || COMPILE_TEST + default ARCH_MESON + help + This enables the reset driver for Amlogic Meson SoCs. + +config RESET_MESON_AUDIO_ARB + tristate "Meson Audio Memory Arbiter Reset Driver" + depends on ARCH_MESON || COMPILE_TEST + help + This enables the reset driver for Audio Memory Arbiter of + Amlogic's A113 based SoCs + +config RESET_NPCM + bool "NPCM BMC Reset Driver" if COMPILE_TEST + default ARCH_NPCM + help + This enables the reset controller driver for Nuvoton NPCM + BMC SoCs. + +config RESET_OXNAS + bool + +config RESET_PISTACHIO + bool "Pistachio Reset Driver" + depends on MIPS || COMPILE_TEST + help + This enables the reset driver for ImgTec Pistachio SoCs. + +config RESET_POLARFIRE_SOC + bool "Microchip PolarFire SoC (MPFS) Reset Driver" + depends on AUXILIARY_BUS && MCHP_CLK_MPFS + default MCHP_CLK_MPFS + help + This driver supports peripheral reset for the Microchip PolarFire SoC + +config RESET_QCOM_AOSS + tristate "Qcom AOSS Reset Driver" + depends on ARCH_QCOM || COMPILE_TEST + help + This enables the AOSS (always on subsystem) reset driver + for Qualcomm SDM845 SoCs. Say Y if you want to control + reset signals provided by AOSS for Modem, Venus, ADSP, + GPU, Camera, Wireless, Display subsystem. Otherwise, say N. + +config RESET_QCOM_PDC + tristate "Qualcomm PDC Reset Driver" + depends on ARCH_QCOM || COMPILE_TEST + help + This enables the PDC (Power Domain Controller) reset driver + for Qualcomm Technologies Inc SDM845 SoCs. Say Y if you want + to control reset signals provided by PDC for Modem, Compute, + Display, GPU, Debug, AOP, Sensors, Audio, SP and APPS. + +config RESET_RASPBERRYPI + tristate "Raspberry Pi 4 Firmware Reset Driver" + depends on RASPBERRYPI_FIRMWARE || (RASPBERRYPI_FIRMWARE=n && COMPILE_TEST) + default USB_XHCI_PCI + help + Raspberry Pi 4's co-processor controls some of the board's HW + initialization process, but it's up to Linux to trigger it when + relevant. This driver provides a reset controller capable of + interfacing with RPi4's co-processor and model these firmware + initialization routines as reset lines. + +config RESET_RZG2L_USBPHY_CTRL + tristate "Renesas RZ/G2L USBPHY control driver" + depends on ARCH_RZG2L || COMPILE_TEST + help + Support for USBPHY Control found on RZ/G2L family. It mainly + controls reset and power down of the USB/PHY. + +config RESET_SCMI + tristate "Reset driver controlled via ARM SCMI interface" + depends on ARM_SCMI_PROTOCOL || COMPILE_TEST + default ARM_SCMI_PROTOCOL + help + This driver provides support for reset signal/domains that are + controlled by firmware that implements the SCMI interface. + + This driver uses SCMI Message Protocol to interact with the + firmware controlling all the reset signals. + +config RESET_SIMPLE + bool "Simple Reset Controller Driver" if COMPILE_TEST || EXPERT + default ARCH_ASPEED || ARCH_BCMBCA || ARCH_BITMAIN || ARCH_REALTEK || ARCH_STM32 || (ARCH_INTEL_SOCFPGA && ARM64) || ARCH_SUNXI || ARC + depends on HAS_IOMEM + help + This enables a simple reset controller driver for reset lines that + that can be asserted and deasserted by toggling bits in a contiguous, + exclusive register space. + + Currently this driver supports: + - Altera SoCFPGAs + - ASPEED BMC SoCs + - Bitmain BM1880 SoC + - Realtek SoCs + - RCC reset controller in STM32 MCUs + - Allwinner SoCs + - SiFive FU740 SoCs + +config RESET_SOCFPGA + bool "SoCFPGA Reset Driver" if COMPILE_TEST && (!ARM || !ARCH_INTEL_SOCFPGA) + default ARM && ARCH_INTEL_SOCFPGA + select RESET_SIMPLE + help + This enables the reset driver for the SoCFPGA ARMv7 platforms. This + driver gets initialized early during platform init calls. + +config RESET_STARFIVE_JH7100 + bool "StarFive JH7100 Reset Driver" + depends on SOC_STARFIVE || COMPILE_TEST + default SOC_STARFIVE + help + This enables the reset controller driver for the StarFive JH7100 SoC. + +config RESET_SUNPLUS + bool "Sunplus SoCs Reset Driver" if COMPILE_TEST + default ARCH_SUNPLUS + help + This enables the reset driver support for Sunplus SoCs. + The reset lines that can be asserted and deasserted by toggling bits + in a contiguous, exclusive register space. The register is HIWORD_MASKED, + which means each register holds 16 reset lines. + +config RESET_SUNXI + bool "Allwinner SoCs Reset Driver" if COMPILE_TEST && !ARCH_SUNXI + default ARCH_SUNXI + select RESET_SIMPLE + help + This enables the reset driver for Allwinner SoCs. + +config RESET_TI_SCI + tristate "TI System Control Interface (TI-SCI) reset driver" + depends on TI_SCI_PROTOCOL || (COMPILE_TEST && TI_SCI_PROTOCOL=n) + help + This enables the reset driver support over TI System Control Interface + available on some new TI's SoCs. If you wish to use reset resources + managed by the TI System Controller, say Y here. Otherwise, say N. + +config RESET_TI_SYSCON + tristate "TI SYSCON Reset Driver" + depends on HAS_IOMEM + select MFD_SYSCON + help + This enables the reset driver support for TI devices with + memory-mapped reset registers as part of a syscon device node. If + you wish to use the reset framework for such memory-mapped devices, + say Y here. Otherwise, say N. + +config RESET_TI_TPS380X + tristate "TI TPS380x Reset Driver" + select GPIOLIB + help + This enables the reset driver support for TI TPS380x devices. If + you wish to use the reset framework for such devices, say Y here. + Otherwise, say N. + +config RESET_TN48M_CPLD + tristate "Delta Networks TN48M switch CPLD reset controller" + depends on MFD_TN48M_CPLD || COMPILE_TEST + default MFD_TN48M_CPLD + help + This enables the reset controller driver for the Delta TN48M CPLD. + It provides reset signals for Armada 7040 and 385 SoC-s, Alleycat 3X + switch MAC-s, Alaska OOB ethernet PHY, Quad Alaska ethernet PHY-s and + Microchip PD69200 PoE PSE controller. + + This driver can also be built as a module. If so, the module will be + called reset-tn48m. + +config RESET_UNIPHIER + tristate "Reset controller driver for UniPhier SoCs" + depends on ARCH_UNIPHIER || COMPILE_TEST + depends on OF && MFD_SYSCON + default ARCH_UNIPHIER + help + Support for reset controllers on UniPhier SoCs. + Say Y if you want to control reset signals provided by System Control + block, Media I/O block, Peripheral Block. + +config RESET_UNIPHIER_GLUE + tristate "Reset driver in glue layer for UniPhier SoCs" + depends on (ARCH_UNIPHIER || COMPILE_TEST) && OF + default ARCH_UNIPHIER + select RESET_SIMPLE + help + Support for peripheral core reset included in its own glue layer + on UniPhier SoCs. Say Y if you want to control reset signals + provided by the glue layer. + +config RESET_ZYNQ + bool "ZYNQ Reset Driver" if COMPILE_TEST + default ARCH_ZYNQ + help + This enables the reset controller driver for Xilinx Zynq SoCs. + +source "drivers/reset/sti/Kconfig" +source "drivers/reset/hisilicon/Kconfig" +source "drivers/reset/tegra/Kconfig" + +endif diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile new file mode 100644 index 000000000..3e7e5fd63 --- /dev/null +++ b/drivers/reset/Makefile @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-y += core.o +obj-y += hisilicon/ +obj-$(CONFIG_ARCH_STI) += sti/ +obj-$(CONFIG_ARCH_TEGRA) += tegra/ +obj-$(CONFIG_RESET_A10SR) += reset-a10sr.o +obj-$(CONFIG_RESET_ATH79) += reset-ath79.o +obj-$(CONFIG_RESET_AXS10X) += reset-axs10x.o +obj-$(CONFIG_RESET_BCM6345) += reset-bcm6345.o +obj-$(CONFIG_RESET_BERLIN) += reset-berlin.o +obj-$(CONFIG_RESET_BRCMSTB) += reset-brcmstb.o +obj-$(CONFIG_RESET_BRCMSTB_RESCAL) += reset-brcmstb-rescal.o +obj-$(CONFIG_RESET_HSDK) += reset-hsdk.o +obj-$(CONFIG_RESET_IMX7) += reset-imx7.o +obj-$(CONFIG_RESET_INTEL_GW) += reset-intel-gw.o +obj-$(CONFIG_RESET_K210) += reset-k210.o +obj-$(CONFIG_RESET_LANTIQ) += reset-lantiq.o +obj-$(CONFIG_RESET_LPC18XX) += reset-lpc18xx.o +obj-$(CONFIG_RESET_MCHP_SPARX5) += reset-microchip-sparx5.o +obj-$(CONFIG_RESET_MESON) += reset-meson.o +obj-$(CONFIG_RESET_MESON_AUDIO_ARB) += reset-meson-audio-arb.o +obj-$(CONFIG_RESET_NPCM) += reset-npcm.o +obj-$(CONFIG_RESET_OXNAS) += reset-oxnas.o +obj-$(CONFIG_RESET_PISTACHIO) += reset-pistachio.o +obj-$(CONFIG_RESET_POLARFIRE_SOC) += reset-mpfs.o +obj-$(CONFIG_RESET_QCOM_AOSS) += reset-qcom-aoss.o +obj-$(CONFIG_RESET_QCOM_PDC) += reset-qcom-pdc.o +obj-$(CONFIG_RESET_RASPBERRYPI) += reset-raspberrypi.o +obj-$(CONFIG_RESET_RZG2L_USBPHY_CTRL) += reset-rzg2l-usbphy-ctrl.o +obj-$(CONFIG_RESET_SCMI) += reset-scmi.o +obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o +obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o +obj-$(CONFIG_RESET_STARFIVE_JH7100) += reset-starfive-jh7100.o +obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o +obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o +obj-$(CONFIG_RESET_TI_SCI) += reset-ti-sci.o +obj-$(CONFIG_RESET_TI_SYSCON) += reset-ti-syscon.o +obj-$(CONFIG_RESET_TI_TPS380X) += reset-tps380x.o +obj-$(CONFIG_RESET_TN48M_CPLD) += reset-tn48m.o +obj-$(CONFIG_RESET_UNIPHIER) += reset-uniphier.o +obj-$(CONFIG_RESET_UNIPHIER_GLUE) += reset-uniphier-glue.o +obj-$(CONFIG_RESET_ZYNQ) += reset-zynq.o +obj-$(CONFIG_ARCH_ZYNQMP) += reset-zynqmp.o diff --git a/drivers/reset/core.c b/drivers/reset/core.c new file mode 100644 index 000000000..92cc13ef3 --- /dev/null +++ b/drivers/reset/core.c @@ -0,0 +1,1289 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Reset Controller framework + * + * Copyright 2013 Philipp Zabel, Pengutronix + */ +#include <linux/atomic.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/acpi.h> +#include <linux/reset.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> + +static DEFINE_MUTEX(reset_list_mutex); +static LIST_HEAD(reset_controller_list); + +static DEFINE_MUTEX(reset_lookup_mutex); +static LIST_HEAD(reset_lookup_list); + +/** + * struct reset_control - a reset control + * @rcdev: a pointer to the reset controller device + * this reset control belongs to + * @list: list entry for the rcdev's reset controller list + * @id: ID of the reset controller in the reset + * controller device + * @refcnt: Number of gets of this reset_control + * @acquired: Only one reset_control may be acquired for a given rcdev and id. + * @shared: Is this a shared (1), or an exclusive (0) reset_control? + * @array: Is this an array of reset controls (1)? + * @deassert_count: Number of times this reset line has been deasserted + * @triggered_count: Number of times this reset line has been reset. Currently + * only used for shared resets, which means that the value + * will be either 0 or 1. + */ +struct reset_control { + struct reset_controller_dev *rcdev; + struct list_head list; + unsigned int id; + struct kref refcnt; + bool acquired; + bool shared; + bool array; + atomic_t deassert_count; + atomic_t triggered_count; +}; + +/** + * struct reset_control_array - an array of reset controls + * @base: reset control for compatibility with reset control API functions + * @num_rstcs: number of reset controls + * @rstc: array of reset controls + */ +struct reset_control_array { + struct reset_control base; + unsigned int num_rstcs; + struct reset_control *rstc[]; +}; + +static const char *rcdev_name(struct reset_controller_dev *rcdev) +{ + if (rcdev->dev) + return dev_name(rcdev->dev); + + if (rcdev->of_node) + return rcdev->of_node->full_name; + + return NULL; +} + +/** + * of_reset_simple_xlate - translate reset_spec to the reset line number + * @rcdev: a pointer to the reset controller device + * @reset_spec: reset line specifier as found in the device tree + * + * This static translation function is used by default if of_xlate in + * :c:type:`reset_controller_dev` is not set. It is useful for all reset + * controllers with 1:1 mapping, where reset lines can be indexed by number + * without gaps. + */ +static int of_reset_simple_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + if (reset_spec->args[0] >= rcdev->nr_resets) + return -EINVAL; + + return reset_spec->args[0]; +} + +/** + * reset_controller_register - register a reset controller device + * @rcdev: a pointer to the initialized reset controller device + */ +int reset_controller_register(struct reset_controller_dev *rcdev) +{ + if (!rcdev->of_xlate) { + rcdev->of_reset_n_cells = 1; + rcdev->of_xlate = of_reset_simple_xlate; + } + + INIT_LIST_HEAD(&rcdev->reset_control_head); + + mutex_lock(&reset_list_mutex); + list_add(&rcdev->list, &reset_controller_list); + mutex_unlock(&reset_list_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(reset_controller_register); + +/** + * reset_controller_unregister - unregister a reset controller device + * @rcdev: a pointer to the reset controller device + */ +void reset_controller_unregister(struct reset_controller_dev *rcdev) +{ + mutex_lock(&reset_list_mutex); + list_del(&rcdev->list); + mutex_unlock(&reset_list_mutex); +} +EXPORT_SYMBOL_GPL(reset_controller_unregister); + +static void devm_reset_controller_release(struct device *dev, void *res) +{ + reset_controller_unregister(*(struct reset_controller_dev **)res); +} + +/** + * devm_reset_controller_register - resource managed reset_controller_register() + * @dev: device that is registering this reset controller + * @rcdev: a pointer to the initialized reset controller device + * + * Managed reset_controller_register(). For reset controllers registered by + * this function, reset_controller_unregister() is automatically called on + * driver detach. See reset_controller_register() for more information. + */ +int devm_reset_controller_register(struct device *dev, + struct reset_controller_dev *rcdev) +{ + struct reset_controller_dev **rcdevp; + int ret; + + rcdevp = devres_alloc(devm_reset_controller_release, sizeof(*rcdevp), + GFP_KERNEL); + if (!rcdevp) + return -ENOMEM; + + ret = reset_controller_register(rcdev); + if (ret) { + devres_free(rcdevp); + return ret; + } + + *rcdevp = rcdev; + devres_add(dev, rcdevp); + + return ret; +} +EXPORT_SYMBOL_GPL(devm_reset_controller_register); + +/** + * reset_controller_add_lookup - register a set of lookup entries + * @lookup: array of reset lookup entries + * @num_entries: number of entries in the lookup array + */ +void reset_controller_add_lookup(struct reset_control_lookup *lookup, + unsigned int num_entries) +{ + struct reset_control_lookup *entry; + unsigned int i; + + mutex_lock(&reset_lookup_mutex); + for (i = 0; i < num_entries; i++) { + entry = &lookup[i]; + + if (!entry->dev_id || !entry->provider) { + pr_warn("%s(): reset lookup entry badly specified, skipping\n", + __func__); + continue; + } + + list_add_tail(&entry->list, &reset_lookup_list); + } + mutex_unlock(&reset_lookup_mutex); +} +EXPORT_SYMBOL_GPL(reset_controller_add_lookup); + +static inline struct reset_control_array * +rstc_to_array(struct reset_control *rstc) { + return container_of(rstc, struct reset_control_array, base); +} + +static int reset_control_array_reset(struct reset_control_array *resets) +{ + int ret, i; + + for (i = 0; i < resets->num_rstcs; i++) { + ret = reset_control_reset(resets->rstc[i]); + if (ret) + return ret; + } + + return 0; +} + +static int reset_control_array_rearm(struct reset_control_array *resets) +{ + struct reset_control *rstc; + int i; + + for (i = 0; i < resets->num_rstcs; i++) { + rstc = resets->rstc[i]; + + if (!rstc) + continue; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + if (rstc->shared) { + if (WARN_ON(atomic_read(&rstc->deassert_count) != 0)) + return -EINVAL; + } else { + if (!rstc->acquired) + return -EPERM; + } + } + + for (i = 0; i < resets->num_rstcs; i++) { + rstc = resets->rstc[i]; + + if (rstc && rstc->shared) + WARN_ON(atomic_dec_return(&rstc->triggered_count) < 0); + } + + return 0; +} + +static int reset_control_array_assert(struct reset_control_array *resets) +{ + int ret, i; + + for (i = 0; i < resets->num_rstcs; i++) { + ret = reset_control_assert(resets->rstc[i]); + if (ret) + goto err; + } + + return 0; + +err: + while (i--) + reset_control_deassert(resets->rstc[i]); + return ret; +} + +static int reset_control_array_deassert(struct reset_control_array *resets) +{ + int ret, i; + + for (i = 0; i < resets->num_rstcs; i++) { + ret = reset_control_deassert(resets->rstc[i]); + if (ret) + goto err; + } + + return 0; + +err: + while (i--) + reset_control_assert(resets->rstc[i]); + return ret; +} + +static int reset_control_array_acquire(struct reset_control_array *resets) +{ + unsigned int i; + int err; + + for (i = 0; i < resets->num_rstcs; i++) { + err = reset_control_acquire(resets->rstc[i]); + if (err < 0) + goto release; + } + + return 0; + +release: + while (i--) + reset_control_release(resets->rstc[i]); + + return err; +} + +static void reset_control_array_release(struct reset_control_array *resets) +{ + unsigned int i; + + for (i = 0; i < resets->num_rstcs; i++) + reset_control_release(resets->rstc[i]); +} + +static inline bool reset_control_is_array(struct reset_control *rstc) +{ + return rstc->array; +} + +/** + * reset_control_reset - reset the controlled device + * @rstc: reset controller + * + * On a shared reset line the actual reset pulse is only triggered once for the + * lifetime of the reset_control instance: for all but the first caller this is + * a no-op. + * Consumers must not use reset_control_(de)assert on shared reset lines when + * reset_control_reset has been used. + * + * If rstc is NULL it is an optional reset and the function will just + * return 0. + */ +int reset_control_reset(struct reset_control *rstc) +{ + int ret; + + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + if (reset_control_is_array(rstc)) + return reset_control_array_reset(rstc_to_array(rstc)); + + if (!rstc->rcdev->ops->reset) + return -ENOTSUPP; + + if (rstc->shared) { + if (WARN_ON(atomic_read(&rstc->deassert_count) != 0)) + return -EINVAL; + + if (atomic_inc_return(&rstc->triggered_count) != 1) + return 0; + } else { + if (!rstc->acquired) + return -EPERM; + } + + ret = rstc->rcdev->ops->reset(rstc->rcdev, rstc->id); + if (rstc->shared && ret) + atomic_dec(&rstc->triggered_count); + + return ret; +} +EXPORT_SYMBOL_GPL(reset_control_reset); + +/** + * reset_control_bulk_reset - reset the controlled devices in order + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Issue a reset on all provided reset controls, in order. + * + * See also: reset_control_reset() + */ +int reset_control_bulk_reset(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + ret = reset_control_reset(rstcs[i].rstc); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_reset); + +/** + * reset_control_rearm - allow shared reset line to be re-triggered" + * @rstc: reset controller + * + * On a shared reset line the actual reset pulse is only triggered once for the + * lifetime of the reset_control instance, except if this call is used. + * + * Calls to this function must be balanced with calls to reset_control_reset, + * a warning is thrown in case triggered_count ever dips below 0. + * + * Consumers must not use reset_control_(de)assert on shared reset lines when + * reset_control_reset or reset_control_rearm have been used. + * + * If rstc is NULL the function will just return 0. + */ +int reset_control_rearm(struct reset_control *rstc) +{ + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + if (reset_control_is_array(rstc)) + return reset_control_array_rearm(rstc_to_array(rstc)); + + if (rstc->shared) { + if (WARN_ON(atomic_read(&rstc->deassert_count) != 0)) + return -EINVAL; + + WARN_ON(atomic_dec_return(&rstc->triggered_count) < 0); + } else { + if (!rstc->acquired) + return -EPERM; + } + + return 0; +} +EXPORT_SYMBOL_GPL(reset_control_rearm); + +/** + * reset_control_assert - asserts the reset line + * @rstc: reset controller + * + * Calling this on an exclusive reset controller guarantees that the reset + * will be asserted. When called on a shared reset controller the line may + * still be deasserted, as long as other users keep it so. + * + * For shared reset controls a driver cannot expect the hw's registers and + * internal state to be reset, but must be prepared for this to happen. + * Consumers must not use reset_control_reset on shared reset lines when + * reset_control_(de)assert has been used. + * + * If rstc is NULL it is an optional reset and the function will just + * return 0. + */ +int reset_control_assert(struct reset_control *rstc) +{ + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + if (reset_control_is_array(rstc)) + return reset_control_array_assert(rstc_to_array(rstc)); + + if (rstc->shared) { + if (WARN_ON(atomic_read(&rstc->triggered_count) != 0)) + return -EINVAL; + + if (WARN_ON(atomic_read(&rstc->deassert_count) == 0)) + return -EINVAL; + + if (atomic_dec_return(&rstc->deassert_count) != 0) + return 0; + + /* + * Shared reset controls allow the reset line to be in any state + * after this call, so doing nothing is a valid option. + */ + if (!rstc->rcdev->ops->assert) + return 0; + } else { + /* + * If the reset controller does not implement .assert(), there + * is no way to guarantee that the reset line is asserted after + * this call. + */ + if (!rstc->rcdev->ops->assert) + return -ENOTSUPP; + + if (!rstc->acquired) { + WARN(1, "reset %s (ID: %u) is not acquired\n", + rcdev_name(rstc->rcdev), rstc->id); + return -EPERM; + } + } + + return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id); +} +EXPORT_SYMBOL_GPL(reset_control_assert); + +/** + * reset_control_bulk_assert - asserts the reset lines in order + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Assert the reset lines for all provided reset controls, in order. + * If an assertion fails, already asserted resets are deasserted again. + * + * See also: reset_control_assert() + */ +int reset_control_bulk_assert(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + ret = reset_control_assert(rstcs[i].rstc); + if (ret) + goto err; + } + + return 0; + +err: + while (i--) + reset_control_deassert(rstcs[i].rstc); + return ret; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_assert); + +/** + * reset_control_deassert - deasserts the reset line + * @rstc: reset controller + * + * After calling this function, the reset is guaranteed to be deasserted. + * Consumers must not use reset_control_reset on shared reset lines when + * reset_control_(de)assert has been used. + * + * If rstc is NULL it is an optional reset and the function will just + * return 0. + */ +int reset_control_deassert(struct reset_control *rstc) +{ + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + if (reset_control_is_array(rstc)) + return reset_control_array_deassert(rstc_to_array(rstc)); + + if (rstc->shared) { + if (WARN_ON(atomic_read(&rstc->triggered_count) != 0)) + return -EINVAL; + + if (atomic_inc_return(&rstc->deassert_count) != 1) + return 0; + } else { + if (!rstc->acquired) { + WARN(1, "reset %s (ID: %u) is not acquired\n", + rcdev_name(rstc->rcdev), rstc->id); + return -EPERM; + } + } + + /* + * If the reset controller does not implement .deassert(), we assume + * that it handles self-deasserting reset lines via .reset(). In that + * case, the reset lines are deasserted by default. If that is not the + * case, the reset controller driver should implement .deassert() and + * return -ENOTSUPP. + */ + if (!rstc->rcdev->ops->deassert) + return 0; + + return rstc->rcdev->ops->deassert(rstc->rcdev, rstc->id); +} +EXPORT_SYMBOL_GPL(reset_control_deassert); + +/** + * reset_control_bulk_deassert - deasserts the reset lines in reverse order + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Deassert the reset lines for all provided reset controls, in reverse order. + * If a deassertion fails, already deasserted resets are asserted again. + * + * See also: reset_control_deassert() + */ +int reset_control_bulk_deassert(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = num_rstcs - 1; i >= 0; i--) { + ret = reset_control_deassert(rstcs[i].rstc); + if (ret) + goto err; + } + + return 0; + +err: + while (i < num_rstcs) + reset_control_assert(rstcs[i++].rstc); + return ret; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_deassert); + +/** + * reset_control_status - returns a negative errno if not supported, a + * positive value if the reset line is asserted, or zero if the reset + * line is not asserted or if the desc is NULL (optional reset). + * @rstc: reset controller + */ +int reset_control_status(struct reset_control *rstc) +{ + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc)) || reset_control_is_array(rstc)) + return -EINVAL; + + if (rstc->rcdev->ops->status) + return rstc->rcdev->ops->status(rstc->rcdev, rstc->id); + + return -ENOTSUPP; +} +EXPORT_SYMBOL_GPL(reset_control_status); + +/** + * reset_control_acquire() - acquires a reset control for exclusive use + * @rstc: reset control + * + * This is used to explicitly acquire a reset control for exclusive use. Note + * that exclusive resets are requested as acquired by default. In order for a + * second consumer to be able to control the reset, the first consumer has to + * release it first. Typically the easiest way to achieve this is to call the + * reset_control_get_exclusive_released() to obtain an instance of the reset + * control. Such reset controls are not acquired by default. + * + * Consumers implementing shared access to an exclusive reset need to follow + * a specific protocol in order to work together. Before consumers can change + * a reset they must acquire exclusive access using reset_control_acquire(). + * After they are done operating the reset, they must release exclusive access + * with a call to reset_control_release(). Consumers are not granted exclusive + * access to the reset as long as another consumer hasn't released a reset. + * + * See also: reset_control_release() + */ +int reset_control_acquire(struct reset_control *rstc) +{ + struct reset_control *rc; + + if (!rstc) + return 0; + + if (WARN_ON(IS_ERR(rstc))) + return -EINVAL; + + if (reset_control_is_array(rstc)) + return reset_control_array_acquire(rstc_to_array(rstc)); + + mutex_lock(&reset_list_mutex); + + if (rstc->acquired) { + mutex_unlock(&reset_list_mutex); + return 0; + } + + list_for_each_entry(rc, &rstc->rcdev->reset_control_head, list) { + if (rstc != rc && rstc->id == rc->id) { + if (rc->acquired) { + mutex_unlock(&reset_list_mutex); + return -EBUSY; + } + } + } + + rstc->acquired = true; + + mutex_unlock(&reset_list_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(reset_control_acquire); + +/** + * reset_control_bulk_acquire - acquires reset controls for exclusive use + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * This is used to explicitly acquire reset controls requested with + * reset_control_bulk_get_exclusive_release() for temporary exclusive use. + * + * See also: reset_control_acquire(), reset_control_bulk_release() + */ +int reset_control_bulk_acquire(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + ret = reset_control_acquire(rstcs[i].rstc); + if (ret) + goto err; + } + + return 0; + +err: + while (i--) + reset_control_release(rstcs[i].rstc); + return ret; +} +EXPORT_SYMBOL_GPL(reset_control_bulk_acquire); + +/** + * reset_control_release() - releases exclusive access to a reset control + * @rstc: reset control + * + * Releases exclusive access right to a reset control previously obtained by a + * call to reset_control_acquire(). Until a consumer calls this function, no + * other consumers will be granted exclusive access. + * + * See also: reset_control_acquire() + */ +void reset_control_release(struct reset_control *rstc) +{ + if (!rstc || WARN_ON(IS_ERR(rstc))) + return; + + if (reset_control_is_array(rstc)) + reset_control_array_release(rstc_to_array(rstc)); + else + rstc->acquired = false; +} +EXPORT_SYMBOL_GPL(reset_control_release); + +/** + * reset_control_bulk_release() - releases exclusive access to reset controls + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + * + * Releases exclusive access right to reset controls previously obtained by a + * call to reset_control_bulk_acquire(). + * + * See also: reset_control_release(), reset_control_bulk_acquire() + */ +void reset_control_bulk_release(int num_rstcs, + struct reset_control_bulk_data *rstcs) +{ + int i; + + for (i = 0; i < num_rstcs; i++) + reset_control_release(rstcs[i].rstc); +} +EXPORT_SYMBOL_GPL(reset_control_bulk_release); + +static struct reset_control * +__reset_control_get_internal(struct reset_controller_dev *rcdev, + unsigned int index, bool shared, bool acquired) +{ + struct reset_control *rstc; + + lockdep_assert_held(&reset_list_mutex); + + list_for_each_entry(rstc, &rcdev->reset_control_head, list) { + if (rstc->id == index) { + /* + * Allow creating a secondary exclusive reset_control + * that is initially not acquired for an already + * controlled reset line. + */ + if (!rstc->shared && !shared && !acquired) + break; + + if (WARN_ON(!rstc->shared || !shared)) + return ERR_PTR(-EBUSY); + + kref_get(&rstc->refcnt); + return rstc; + } + } + + rstc = kzalloc(sizeof(*rstc), GFP_KERNEL); + if (!rstc) + return ERR_PTR(-ENOMEM); + + if (!try_module_get(rcdev->owner)) { + kfree(rstc); + return ERR_PTR(-ENODEV); + } + + rstc->rcdev = rcdev; + list_add(&rstc->list, &rcdev->reset_control_head); + rstc->id = index; + kref_init(&rstc->refcnt); + rstc->acquired = acquired; + rstc->shared = shared; + + return rstc; +} + +static void __reset_control_release(struct kref *kref) +{ + struct reset_control *rstc = container_of(kref, struct reset_control, + refcnt); + + lockdep_assert_held(&reset_list_mutex); + + module_put(rstc->rcdev->owner); + + list_del(&rstc->list); + kfree(rstc); +} + +static void __reset_control_put_internal(struct reset_control *rstc) +{ + lockdep_assert_held(&reset_list_mutex); + + if (IS_ERR_OR_NULL(rstc)) + return; + + kref_put(&rstc->refcnt, __reset_control_release); +} + +struct reset_control * +__of_reset_control_get(struct device_node *node, const char *id, int index, + bool shared, bool optional, bool acquired) +{ + struct reset_control *rstc; + struct reset_controller_dev *r, *rcdev; + struct of_phandle_args args; + int rstc_id; + int ret; + + if (!node) + return ERR_PTR(-EINVAL); + + if (id) { + index = of_property_match_string(node, + "reset-names", id); + if (index == -EILSEQ) + return ERR_PTR(index); + if (index < 0) + return optional ? NULL : ERR_PTR(-ENOENT); + } + + ret = of_parse_phandle_with_args(node, "resets", "#reset-cells", + index, &args); + if (ret == -EINVAL) + return ERR_PTR(ret); + if (ret) + return optional ? NULL : ERR_PTR(ret); + + mutex_lock(&reset_list_mutex); + rcdev = NULL; + list_for_each_entry(r, &reset_controller_list, list) { + if (args.np == r->of_node) { + rcdev = r; + break; + } + } + + if (!rcdev) { + rstc = ERR_PTR(-EPROBE_DEFER); + goto out; + } + + if (WARN_ON(args.args_count != rcdev->of_reset_n_cells)) { + rstc = ERR_PTR(-EINVAL); + goto out; + } + + rstc_id = rcdev->of_xlate(rcdev, &args); + if (rstc_id < 0) { + rstc = ERR_PTR(rstc_id); + goto out; + } + + /* reset_list_mutex also protects the rcdev's reset_control list */ + rstc = __reset_control_get_internal(rcdev, rstc_id, shared, acquired); + +out: + mutex_unlock(&reset_list_mutex); + of_node_put(args.np); + + return rstc; +} +EXPORT_SYMBOL_GPL(__of_reset_control_get); + +static struct reset_controller_dev * +__reset_controller_by_name(const char *name) +{ + struct reset_controller_dev *rcdev; + + lockdep_assert_held(&reset_list_mutex); + + list_for_each_entry(rcdev, &reset_controller_list, list) { + if (!rcdev->dev) + continue; + + if (!strcmp(name, dev_name(rcdev->dev))) + return rcdev; + } + + return NULL; +} + +static struct reset_control * +__reset_control_get_from_lookup(struct device *dev, const char *con_id, + bool shared, bool optional, bool acquired) +{ + const struct reset_control_lookup *lookup; + struct reset_controller_dev *rcdev; + const char *dev_id = dev_name(dev); + struct reset_control *rstc = NULL; + + mutex_lock(&reset_lookup_mutex); + + list_for_each_entry(lookup, &reset_lookup_list, list) { + if (strcmp(lookup->dev_id, dev_id)) + continue; + + if ((!con_id && !lookup->con_id) || + ((con_id && lookup->con_id) && + !strcmp(con_id, lookup->con_id))) { + mutex_lock(&reset_list_mutex); + rcdev = __reset_controller_by_name(lookup->provider); + if (!rcdev) { + mutex_unlock(&reset_list_mutex); + mutex_unlock(&reset_lookup_mutex); + /* Reset provider may not be ready yet. */ + return ERR_PTR(-EPROBE_DEFER); + } + + rstc = __reset_control_get_internal(rcdev, + lookup->index, + shared, acquired); + mutex_unlock(&reset_list_mutex); + break; + } + } + + mutex_unlock(&reset_lookup_mutex); + + if (!rstc) + return optional ? NULL : ERR_PTR(-ENOENT); + + return rstc; +} + +struct reset_control *__reset_control_get(struct device *dev, const char *id, + int index, bool shared, bool optional, + bool acquired) +{ + if (WARN_ON(shared && acquired)) + return ERR_PTR(-EINVAL); + + if (dev->of_node) + return __of_reset_control_get(dev->of_node, id, index, shared, + optional, acquired); + + return __reset_control_get_from_lookup(dev, id, shared, optional, + acquired); +} +EXPORT_SYMBOL_GPL(__reset_control_get); + +int __reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired) +{ + int ret, i; + + for (i = 0; i < num_rstcs; i++) { + rstcs[i].rstc = __reset_control_get(dev, rstcs[i].id, 0, + shared, optional, acquired); + if (IS_ERR(rstcs[i].rstc)) { + ret = PTR_ERR(rstcs[i].rstc); + goto err; + } + } + + return 0; + +err: + mutex_lock(&reset_list_mutex); + while (i--) + __reset_control_put_internal(rstcs[i].rstc); + mutex_unlock(&reset_list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(__reset_control_bulk_get); + +static void reset_control_array_put(struct reset_control_array *resets) +{ + int i; + + mutex_lock(&reset_list_mutex); + for (i = 0; i < resets->num_rstcs; i++) + __reset_control_put_internal(resets->rstc[i]); + mutex_unlock(&reset_list_mutex); + kfree(resets); +} + +/** + * reset_control_put - free the reset controller + * @rstc: reset controller + */ +void reset_control_put(struct reset_control *rstc) +{ + if (IS_ERR_OR_NULL(rstc)) + return; + + if (reset_control_is_array(rstc)) { + reset_control_array_put(rstc_to_array(rstc)); + return; + } + + mutex_lock(&reset_list_mutex); + __reset_control_put_internal(rstc); + mutex_unlock(&reset_list_mutex); +} +EXPORT_SYMBOL_GPL(reset_control_put); + +/** + * reset_control_bulk_put - free the reset controllers + * @num_rstcs: number of entries in rstcs array + * @rstcs: array of struct reset_control_bulk_data with reset controls set + */ +void reset_control_bulk_put(int num_rstcs, struct reset_control_bulk_data *rstcs) +{ + mutex_lock(&reset_list_mutex); + while (num_rstcs--) + __reset_control_put_internal(rstcs[num_rstcs].rstc); + mutex_unlock(&reset_list_mutex); +} +EXPORT_SYMBOL_GPL(reset_control_bulk_put); + +static void devm_reset_control_release(struct device *dev, void *res) +{ + reset_control_put(*(struct reset_control **)res); +} + +struct reset_control * +__devm_reset_control_get(struct device *dev, const char *id, int index, + bool shared, bool optional, bool acquired) +{ + struct reset_control **ptr, *rstc; + + ptr = devres_alloc(devm_reset_control_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + rstc = __reset_control_get(dev, id, index, shared, optional, acquired); + if (IS_ERR_OR_NULL(rstc)) { + devres_free(ptr); + return rstc; + } + + *ptr = rstc; + devres_add(dev, ptr); + + return rstc; +} +EXPORT_SYMBOL_GPL(__devm_reset_control_get); + +struct reset_control_bulk_devres { + int num_rstcs; + struct reset_control_bulk_data *rstcs; +}; + +static void devm_reset_control_bulk_release(struct device *dev, void *res) +{ + struct reset_control_bulk_devres *devres = res; + + reset_control_bulk_put(devres->num_rstcs, devres->rstcs); +} + +int __devm_reset_control_bulk_get(struct device *dev, int num_rstcs, + struct reset_control_bulk_data *rstcs, + bool shared, bool optional, bool acquired) +{ + struct reset_control_bulk_devres *ptr; + int ret; + + ptr = devres_alloc(devm_reset_control_bulk_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = __reset_control_bulk_get(dev, num_rstcs, rstcs, shared, optional, acquired); + if (ret < 0) { + devres_free(ptr); + return ret; + } + + ptr->num_rstcs = num_rstcs; + ptr->rstcs = rstcs; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(__devm_reset_control_bulk_get); + +/** + * __device_reset - find reset controller associated with the device + * and perform reset + * @dev: device to be reset by the controller + * @optional: whether it is optional to reset the device + * + * Convenience wrapper for __reset_control_get() and reset_control_reset(). + * This is useful for the common case of devices with single, dedicated reset + * lines. _RST firmware method will be called for devices with ACPI. + */ +int __device_reset(struct device *dev, bool optional) +{ + struct reset_control *rstc; + int ret; + +#ifdef CONFIG_ACPI + acpi_handle handle = ACPI_HANDLE(dev); + + if (handle) { + if (!acpi_has_method(handle, "_RST")) + return optional ? 0 : -ENOENT; + if (ACPI_FAILURE(acpi_evaluate_object(handle, "_RST", NULL, + NULL))) + return -EIO; + } +#endif + + rstc = __reset_control_get(dev, NULL, 0, 0, optional, true); + if (IS_ERR(rstc)) + return PTR_ERR(rstc); + + ret = reset_control_reset(rstc); + + reset_control_put(rstc); + + return ret; +} +EXPORT_SYMBOL_GPL(__device_reset); + +/* + * APIs to manage an array of reset controls. + */ + +/** + * of_reset_control_get_count - Count number of resets available with a device + * + * @node: device node that contains 'resets'. + * + * Returns positive reset count on success, or error number on failure and + * on count being zero. + */ +static int of_reset_control_get_count(struct device_node *node) +{ + int count; + + if (!node) + return -EINVAL; + + count = of_count_phandle_with_args(node, "resets", "#reset-cells"); + if (count == 0) + count = -ENOENT; + + return count; +} + +/** + * of_reset_control_array_get - Get a list of reset controls using + * device node. + * + * @np: device node for the device that requests the reset controls array + * @shared: whether reset controls are shared or not + * @optional: whether it is optional to get the reset controls + * @acquired: only one reset control may be acquired for a given controller + * and ID + * + * Returns pointer to allocated reset_control on success or error on failure + */ +struct reset_control * +of_reset_control_array_get(struct device_node *np, bool shared, bool optional, + bool acquired) +{ + struct reset_control_array *resets; + struct reset_control *rstc; + int num, i; + + num = of_reset_control_get_count(np); + if (num < 0) + return optional ? NULL : ERR_PTR(num); + + resets = kzalloc(struct_size(resets, rstc, num), GFP_KERNEL); + if (!resets) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < num; i++) { + rstc = __of_reset_control_get(np, NULL, i, shared, optional, + acquired); + if (IS_ERR(rstc)) + goto err_rst; + resets->rstc[i] = rstc; + } + resets->num_rstcs = num; + resets->base.array = true; + + return &resets->base; + +err_rst: + mutex_lock(&reset_list_mutex); + while (--i >= 0) + __reset_control_put_internal(resets->rstc[i]); + mutex_unlock(&reset_list_mutex); + + kfree(resets); + + return rstc; +} +EXPORT_SYMBOL_GPL(of_reset_control_array_get); + +/** + * devm_reset_control_array_get - Resource managed reset control array get + * + * @dev: device that requests the list of reset controls + * @shared: whether reset controls are shared or not + * @optional: whether it is optional to get the reset controls + * + * The reset control array APIs are intended for a list of resets + * that just have to be asserted or deasserted, without any + * requirements on the order. + * + * Returns pointer to allocated reset_control on success or error on failure + */ +struct reset_control * +devm_reset_control_array_get(struct device *dev, bool shared, bool optional) +{ + struct reset_control **ptr, *rstc; + + ptr = devres_alloc(devm_reset_control_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + rstc = of_reset_control_array_get(dev->of_node, shared, optional, true); + if (IS_ERR_OR_NULL(rstc)) { + devres_free(ptr); + return rstc; + } + + *ptr = rstc; + devres_add(dev, ptr); + + return rstc; +} +EXPORT_SYMBOL_GPL(devm_reset_control_array_get); + +static int reset_control_get_count_from_lookup(struct device *dev) +{ + const struct reset_control_lookup *lookup; + const char *dev_id; + int count = 0; + + if (!dev) + return -EINVAL; + + dev_id = dev_name(dev); + mutex_lock(&reset_lookup_mutex); + + list_for_each_entry(lookup, &reset_lookup_list, list) { + if (!strcmp(lookup->dev_id, dev_id)) + count++; + } + + mutex_unlock(&reset_lookup_mutex); + + if (count == 0) + count = -ENOENT; + + return count; +} + +/** + * reset_control_get_count - Count number of resets available with a device + * + * @dev: device for which to return the number of resets + * + * Returns positive reset count on success, or error number on failure and + * on count being zero. + */ +int reset_control_get_count(struct device *dev) +{ + if (dev->of_node) + return of_reset_control_get_count(dev->of_node); + + return reset_control_get_count_from_lookup(dev); +} +EXPORT_SYMBOL_GPL(reset_control_get_count); diff --git a/drivers/reset/hisilicon/Kconfig b/drivers/reset/hisilicon/Kconfig new file mode 100644 index 000000000..945ef7a12 --- /dev/null +++ b/drivers/reset/hisilicon/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +config COMMON_RESET_HI3660 + tristate "Hi3660 Reset Driver" + depends on ARCH_HISI || COMPILE_TEST + default ARCH_HISI + help + Build the Hisilicon Hi3660 reset driver. + +config COMMON_RESET_HI6220 + tristate "Hi6220 Reset Driver" + depends on ARCH_HISI || COMPILE_TEST + default ARCH_HISI + help + Build the Hisilicon Hi6220 reset driver. diff --git a/drivers/reset/hisilicon/Makefile b/drivers/reset/hisilicon/Makefile new file mode 100644 index 000000000..cf86d1308 --- /dev/null +++ b/drivers/reset/hisilicon/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_COMMON_RESET_HI6220) += hi6220_reset.o +obj-$(CONFIG_COMMON_RESET_HI3660) += reset-hi3660.o diff --git a/drivers/reset/hisilicon/hi6220_reset.c b/drivers/reset/hisilicon/hi6220_reset.c new file mode 100644 index 000000000..30951914a --- /dev/null +++ b/drivers/reset/hisilicon/hi6220_reset.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hisilicon Hi6220 reset controller driver + * + * Copyright (c) 2016 Linaro Limited. + * Copyright (c) 2015-2016 HiSilicon Limited. + * + * Author: Feng Chen <puck.chen@hisilicon.com> + */ + +#include <linux/io.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/reset-controller.h> +#include <linux/reset.h> +#include <linux/platform_device.h> + +#define PERIPH_ASSERT_OFFSET 0x300 +#define PERIPH_DEASSERT_OFFSET 0x304 +#define PERIPH_MAX_INDEX 0x509 + +#define SC_MEDIA_RSTEN 0x052C +#define SC_MEDIA_RSTDIS 0x0530 +#define MEDIA_MAX_INDEX 8 + +#define to_reset_data(x) container_of(x, struct hi6220_reset_data, rc_dev) + +enum hi6220_reset_ctrl_type { + PERIPHERAL, + MEDIA, + AO, +}; + +struct hi6220_reset_data { + struct reset_controller_dev rc_dev; + struct regmap *regmap; +}; + +static int hi6220_peripheral_assert(struct reset_controller_dev *rc_dev, + unsigned long idx) +{ + struct hi6220_reset_data *data = to_reset_data(rc_dev); + struct regmap *regmap = data->regmap; + u32 bank = idx >> 8; + u32 offset = idx & 0xff; + u32 reg = PERIPH_ASSERT_OFFSET + bank * 0x10; + + return regmap_write(regmap, reg, BIT(offset)); +} + +static int hi6220_peripheral_deassert(struct reset_controller_dev *rc_dev, + unsigned long idx) +{ + struct hi6220_reset_data *data = to_reset_data(rc_dev); + struct regmap *regmap = data->regmap; + u32 bank = idx >> 8; + u32 offset = idx & 0xff; + u32 reg = PERIPH_DEASSERT_OFFSET + bank * 0x10; + + return regmap_write(regmap, reg, BIT(offset)); +} + +static const struct reset_control_ops hi6220_peripheral_reset_ops = { + .assert = hi6220_peripheral_assert, + .deassert = hi6220_peripheral_deassert, +}; + +static int hi6220_media_assert(struct reset_controller_dev *rc_dev, + unsigned long idx) +{ + struct hi6220_reset_data *data = to_reset_data(rc_dev); + struct regmap *regmap = data->regmap; + + return regmap_write(regmap, SC_MEDIA_RSTEN, BIT(idx)); +} + +static int hi6220_media_deassert(struct reset_controller_dev *rc_dev, + unsigned long idx) +{ + struct hi6220_reset_data *data = to_reset_data(rc_dev); + struct regmap *regmap = data->regmap; + + return regmap_write(regmap, SC_MEDIA_RSTDIS, BIT(idx)); +} + +static const struct reset_control_ops hi6220_media_reset_ops = { + .assert = hi6220_media_assert, + .deassert = hi6220_media_deassert, +}; + +#define AO_SCTRL_SC_PW_CLKEN0 0x800 +#define AO_SCTRL_SC_PW_CLKDIS0 0x804 + +#define AO_SCTRL_SC_PW_RSTEN0 0x810 +#define AO_SCTRL_SC_PW_RSTDIS0 0x814 + +#define AO_SCTRL_SC_PW_ISOEN0 0x820 +#define AO_SCTRL_SC_PW_ISODIS0 0x824 +#define AO_MAX_INDEX 12 + +static int hi6220_ao_assert(struct reset_controller_dev *rc_dev, + unsigned long idx) +{ + struct hi6220_reset_data *data = to_reset_data(rc_dev); + struct regmap *regmap = data->regmap; + int ret; + + ret = regmap_write(regmap, AO_SCTRL_SC_PW_RSTEN0, BIT(idx)); + if (ret) + return ret; + + ret = regmap_write(regmap, AO_SCTRL_SC_PW_ISOEN0, BIT(idx)); + if (ret) + return ret; + + ret = regmap_write(regmap, AO_SCTRL_SC_PW_CLKDIS0, BIT(idx)); + return ret; +} + +static int hi6220_ao_deassert(struct reset_controller_dev *rc_dev, + unsigned long idx) +{ + struct hi6220_reset_data *data = to_reset_data(rc_dev); + struct regmap *regmap = data->regmap; + int ret; + + /* + * It was suggested to disable isolation before enabling + * the clocks and deasserting reset, to avoid glitches. + * But this order is preserved to keep it matching the + * vendor code. + */ + ret = regmap_write(regmap, AO_SCTRL_SC_PW_RSTDIS0, BIT(idx)); + if (ret) + return ret; + + ret = regmap_write(regmap, AO_SCTRL_SC_PW_ISODIS0, BIT(idx)); + if (ret) + return ret; + + ret = regmap_write(regmap, AO_SCTRL_SC_PW_CLKEN0, BIT(idx)); + return ret; +} + +static const struct reset_control_ops hi6220_ao_reset_ops = { + .assert = hi6220_ao_assert, + .deassert = hi6220_ao_deassert, +}; + +static int hi6220_reset_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + enum hi6220_reset_ctrl_type type; + struct hi6220_reset_data *data; + struct regmap *regmap; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + type = (uintptr_t)of_device_get_match_data(dev); + + regmap = syscon_node_to_regmap(np); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to get reset controller regmap\n"); + return PTR_ERR(regmap); + } + + data->regmap = regmap; + data->rc_dev.of_node = np; + if (type == MEDIA) { + data->rc_dev.ops = &hi6220_media_reset_ops; + data->rc_dev.nr_resets = MEDIA_MAX_INDEX; + } else if (type == PERIPHERAL) { + data->rc_dev.ops = &hi6220_peripheral_reset_ops; + data->rc_dev.nr_resets = PERIPH_MAX_INDEX; + } else { + data->rc_dev.ops = &hi6220_ao_reset_ops; + data->rc_dev.nr_resets = AO_MAX_INDEX; + } + + return reset_controller_register(&data->rc_dev); +} + +static const struct of_device_id hi6220_reset_match[] = { + { + .compatible = "hisilicon,hi6220-sysctrl", + .data = (void *)PERIPHERAL, + }, + { + .compatible = "hisilicon,hi6220-mediactrl", + .data = (void *)MEDIA, + }, + { + .compatible = "hisilicon,hi6220-aoctrl", + .data = (void *)AO, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, hi6220_reset_match); + +static struct platform_driver hi6220_reset_driver = { + .probe = hi6220_reset_probe, + .driver = { + .name = "reset-hi6220", + .of_match_table = hi6220_reset_match, + }, +}; + +static int __init hi6220_reset_init(void) +{ + return platform_driver_register(&hi6220_reset_driver); +} + +postcore_initcall(hi6220_reset_init); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/hisilicon/reset-hi3660.c b/drivers/reset/hisilicon/reset-hi3660.c new file mode 100644 index 000000000..965f5ceba --- /dev/null +++ b/drivers/reset/hisilicon/reset-hi3660.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2016-2017 Linaro Ltd. + * Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd. + */ +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +struct hi3660_reset_controller { + struct reset_controller_dev rst; + struct regmap *map; +}; + +#define to_hi3660_reset_controller(_rst) \ + container_of(_rst, struct hi3660_reset_controller, rst) + +static int hi3660_reset_program_hw(struct reset_controller_dev *rcdev, + unsigned long idx, bool assert) +{ + struct hi3660_reset_controller *rc = to_hi3660_reset_controller(rcdev); + unsigned int offset = idx >> 8; + unsigned int mask = BIT(idx & 0x1f); + + if (assert) + return regmap_write(rc->map, offset, mask); + else + return regmap_write(rc->map, offset + 4, mask); +} + +static int hi3660_reset_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return hi3660_reset_program_hw(rcdev, idx, true); +} + +static int hi3660_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return hi3660_reset_program_hw(rcdev, idx, false); +} + +static int hi3660_reset_dev(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + int err; + + err = hi3660_reset_assert(rcdev, idx); + if (err) + return err; + + return hi3660_reset_deassert(rcdev, idx); +} + +static const struct reset_control_ops hi3660_reset_ops = { + .reset = hi3660_reset_dev, + .assert = hi3660_reset_assert, + .deassert = hi3660_reset_deassert, +}; + +static int hi3660_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + unsigned int offset, bit; + + offset = reset_spec->args[0]; + bit = reset_spec->args[1]; + + return (offset << 8) | bit; +} + +static int hi3660_reset_probe(struct platform_device *pdev) +{ + struct hi3660_reset_controller *rc; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + + rc = devm_kzalloc(dev, sizeof(*rc), GFP_KERNEL); + if (!rc) + return -ENOMEM; + + rc->map = syscon_regmap_lookup_by_phandle(np, "hisilicon,rst-syscon"); + if (rc->map == ERR_PTR(-ENODEV)) { + /* fall back to the deprecated compatible */ + rc->map = syscon_regmap_lookup_by_phandle(np, + "hisi,rst-syscon"); + } + if (IS_ERR(rc->map)) { + dev_err(dev, "failed to get hisilicon,rst-syscon\n"); + return PTR_ERR(rc->map); + } + + rc->rst.ops = &hi3660_reset_ops, + rc->rst.of_node = np; + rc->rst.of_reset_n_cells = 2; + rc->rst.of_xlate = hi3660_reset_xlate; + + return reset_controller_register(&rc->rst); +} + +static const struct of_device_id hi3660_reset_match[] = { + { .compatible = "hisilicon,hi3660-reset", }, + {}, +}; +MODULE_DEVICE_TABLE(of, hi3660_reset_match); + +static struct platform_driver hi3660_reset_driver = { + .probe = hi3660_reset_probe, + .driver = { + .name = "hi3660-reset", + .of_match_table = hi3660_reset_match, + }, +}; + +static int __init hi3660_reset_init(void) +{ + return platform_driver_register(&hi3660_reset_driver); +} +arch_initcall(hi3660_reset_init); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:hi3660-reset"); +MODULE_DESCRIPTION("HiSilicon Hi3660 Reset Driver"); diff --git a/drivers/reset/reset-a10sr.c b/drivers/reset/reset-a10sr.c new file mode 100644 index 000000000..99b3bc838 --- /dev/null +++ b/drivers/reset/reset-a10sr.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright Intel Corporation (C) 2017. All Rights Reserved + * + * Reset driver for Altera Arria10 MAX5 System Resource Chip + * + * Adapted from reset-socfpga.c + */ + +#include <linux/err.h> +#include <linux/mfd/altera-a10sr.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> + +#include <dt-bindings/reset/altr,rst-mgr-a10sr.h> + +struct a10sr_reset { + struct reset_controller_dev rcdev; + struct regmap *regmap; +}; + +static inline struct a10sr_reset *to_a10sr_rst(struct reset_controller_dev *rc) +{ + return container_of(rc, struct a10sr_reset, rcdev); +} + +static inline int a10sr_reset_shift(unsigned long id) +{ + switch (id) { + case A10SR_RESET_ENET_HPS: + return 1; + case A10SR_RESET_PCIE: + case A10SR_RESET_FILE: + case A10SR_RESET_BQSPI: + case A10SR_RESET_USB: + return id + 11; + default: + return -EINVAL; + } +} + +static int a10sr_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct a10sr_reset *a10r = to_a10sr_rst(rcdev); + int offset = a10sr_reset_shift(id); + u8 mask = ALTR_A10SR_REG_BIT_MASK(offset); + int index = ALTR_A10SR_HPS_RST_REG + ALTR_A10SR_REG_OFFSET(offset); + + return regmap_update_bits(a10r->regmap, index, mask, assert ? 0 : mask); +} + +static int a10sr_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return a10sr_reset_update(rcdev, id, true); +} + +static int a10sr_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return a10sr_reset_update(rcdev, id, false); +} + +static int a10sr_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + int ret; + struct a10sr_reset *a10r = to_a10sr_rst(rcdev); + int offset = a10sr_reset_shift(id); + u8 mask = ALTR_A10SR_REG_BIT_MASK(offset); + int index = ALTR_A10SR_HPS_RST_REG + ALTR_A10SR_REG_OFFSET(offset); + unsigned int value; + + ret = regmap_read(a10r->regmap, index, &value); + if (ret < 0) + return ret; + + return !!(value & mask); +} + +static const struct reset_control_ops a10sr_reset_ops = { + .assert = a10sr_reset_assert, + .deassert = a10sr_reset_deassert, + .status = a10sr_reset_status, +}; + +static int a10sr_reset_probe(struct platform_device *pdev) +{ + struct altr_a10sr *a10sr = dev_get_drvdata(pdev->dev.parent); + struct a10sr_reset *a10r; + + a10r = devm_kzalloc(&pdev->dev, sizeof(struct a10sr_reset), + GFP_KERNEL); + if (!a10r) + return -ENOMEM; + + a10r->rcdev.owner = THIS_MODULE; + a10r->rcdev.nr_resets = A10SR_RESET_NUM; + a10r->rcdev.ops = &a10sr_reset_ops; + a10r->rcdev.of_node = pdev->dev.of_node; + a10r->regmap = a10sr->regmap; + + platform_set_drvdata(pdev, a10r); + + return devm_reset_controller_register(&pdev->dev, &a10r->rcdev); +} + +static const struct of_device_id a10sr_reset_of_match[] = { + { .compatible = "altr,a10sr-reset" }, + { }, +}; +MODULE_DEVICE_TABLE(of, a10sr_reset_of_match); + +static struct platform_driver a10sr_reset_driver = { + .probe = a10sr_reset_probe, + .driver = { + .name = "altr_a10sr_reset", + .of_match_table = a10sr_reset_of_match, + }, +}; +module_platform_driver(a10sr_reset_driver); + +MODULE_AUTHOR("Thor Thayer <thor.thayer@linux.intel.com>"); +MODULE_DESCRIPTION("Altera Arria10 System Resource Reset Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-ath79.c b/drivers/reset/reset-ath79.c new file mode 100644 index 000000000..e48d8fcb3 --- /dev/null +++ b/drivers/reset/reset-ath79.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AR71xx Reset Controller Driver + * Author: Alban Bedel + * + * Copyright (C) 2015 Alban Bedel <albeu@free.fr> + */ + +#include <linux/io.h> +#include <linux/init.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/reboot.h> + +struct ath79_reset { + struct reset_controller_dev rcdev; + struct notifier_block restart_nb; + void __iomem *base; + spinlock_t lock; +}; + +#define FULL_CHIP_RESET 24 + +static int ath79_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct ath79_reset *ath79_reset = + container_of(rcdev, struct ath79_reset, rcdev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&ath79_reset->lock, flags); + val = readl(ath79_reset->base); + if (assert) + val |= BIT(id); + else + val &= ~BIT(id); + writel(val, ath79_reset->base); + spin_unlock_irqrestore(&ath79_reset->lock, flags); + + return 0; +} + +static int ath79_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return ath79_reset_update(rcdev, id, true); +} + +static int ath79_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return ath79_reset_update(rcdev, id, false); +} + +static int ath79_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ath79_reset *ath79_reset = + container_of(rcdev, struct ath79_reset, rcdev); + u32 val; + + val = readl(ath79_reset->base); + + return !!(val & BIT(id)); +} + +static const struct reset_control_ops ath79_reset_ops = { + .assert = ath79_reset_assert, + .deassert = ath79_reset_deassert, + .status = ath79_reset_status, +}; + +static int ath79_reset_restart_handler(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct ath79_reset *ath79_reset = + container_of(nb, struct ath79_reset, restart_nb); + + ath79_reset_assert(&ath79_reset->rcdev, FULL_CHIP_RESET); + + return NOTIFY_DONE; +} + +static int ath79_reset_probe(struct platform_device *pdev) +{ + struct ath79_reset *ath79_reset; + struct resource *res; + int err; + + ath79_reset = devm_kzalloc(&pdev->dev, + sizeof(*ath79_reset), GFP_KERNEL); + if (!ath79_reset) + return -ENOMEM; + + platform_set_drvdata(pdev, ath79_reset); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ath79_reset->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ath79_reset->base)) + return PTR_ERR(ath79_reset->base); + + spin_lock_init(&ath79_reset->lock); + ath79_reset->rcdev.ops = &ath79_reset_ops; + ath79_reset->rcdev.owner = THIS_MODULE; + ath79_reset->rcdev.of_node = pdev->dev.of_node; + ath79_reset->rcdev.of_reset_n_cells = 1; + ath79_reset->rcdev.nr_resets = 32; + + err = devm_reset_controller_register(&pdev->dev, &ath79_reset->rcdev); + if (err) + return err; + + ath79_reset->restart_nb.notifier_call = ath79_reset_restart_handler; + ath79_reset->restart_nb.priority = 128; + + err = register_restart_handler(&ath79_reset->restart_nb); + if (err) + dev_warn(&pdev->dev, "Failed to register restart handler\n"); + + return 0; +} + +static const struct of_device_id ath79_reset_dt_ids[] = { + { .compatible = "qca,ar7100-reset", }, + { }, +}; + +static struct platform_driver ath79_reset_driver = { + .probe = ath79_reset_probe, + .driver = { + .name = "ath79-reset", + .of_match_table = ath79_reset_dt_ids, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(ath79_reset_driver); diff --git a/drivers/reset/reset-axs10x.c b/drivers/reset/reset-axs10x.c new file mode 100644 index 000000000..a854ef413 --- /dev/null +++ b/drivers/reset/reset-axs10x.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 Synopsys. + * + * Synopsys AXS10x reset driver. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> + +#define to_axs10x_rst(p) container_of((p), struct axs10x_rst, rcdev) + +#define AXS10X_MAX_RESETS 32 + +struct axs10x_rst { + void __iomem *regs_rst; + spinlock_t lock; + struct reset_controller_dev rcdev; +}; + +static int axs10x_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct axs10x_rst *rst = to_axs10x_rst(rcdev); + unsigned long flags; + + spin_lock_irqsave(&rst->lock, flags); + writel(BIT(id), rst->regs_rst); + spin_unlock_irqrestore(&rst->lock, flags); + + return 0; +} + +static const struct reset_control_ops axs10x_reset_ops = { + .reset = axs10x_reset_reset, +}; + +static int axs10x_reset_probe(struct platform_device *pdev) +{ + struct axs10x_rst *rst; + struct resource *mem; + + rst = devm_kzalloc(&pdev->dev, sizeof(*rst), GFP_KERNEL); + if (!rst) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rst->regs_rst = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(rst->regs_rst)) + return PTR_ERR(rst->regs_rst); + + spin_lock_init(&rst->lock); + + rst->rcdev.owner = THIS_MODULE; + rst->rcdev.ops = &axs10x_reset_ops; + rst->rcdev.of_node = pdev->dev.of_node; + rst->rcdev.nr_resets = AXS10X_MAX_RESETS; + + return devm_reset_controller_register(&pdev->dev, &rst->rcdev); +} + +static const struct of_device_id axs10x_reset_dt_match[] = { + { .compatible = "snps,axs10x-reset" }, + { }, +}; + +static struct platform_driver axs10x_reset_driver = { + .probe = axs10x_reset_probe, + .driver = { + .name = "axs10x-reset", + .of_match_table = axs10x_reset_dt_match, + }, +}; +builtin_platform_driver(axs10x_reset_driver); + +MODULE_AUTHOR("Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>"); +MODULE_DESCRIPTION("Synopsys AXS10x reset driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-bcm6345.c b/drivers/reset/reset-bcm6345.c new file mode 100644 index 000000000..ac6c7ad1d --- /dev/null +++ b/drivers/reset/reset-bcm6345.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BCM6345 Reset Controller Driver + * + * Copyright (C) 2020 Álvaro Fernández Rojas <noltari@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> + +#define BCM6345_RESET_NUM 32 +#define BCM6345_RESET_SLEEP_MIN_US 10000 +#define BCM6345_RESET_SLEEP_MAX_US 20000 + +struct bcm6345_reset { + struct reset_controller_dev rcdev; + void __iomem *base; + spinlock_t lock; +}; + +static inline struct bcm6345_reset * +to_bcm6345_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct bcm6345_reset, rcdev); +} + +static int bcm6345_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct bcm6345_reset *bcm6345_reset = to_bcm6345_reset(rcdev); + unsigned long flags; + uint32_t val; + + spin_lock_irqsave(&bcm6345_reset->lock, flags); + val = __raw_readl(bcm6345_reset->base); + if (assert) + val &= ~BIT(id); + else + val |= BIT(id); + __raw_writel(val, bcm6345_reset->base); + spin_unlock_irqrestore(&bcm6345_reset->lock, flags); + + return 0; +} + +static int bcm6345_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return bcm6345_reset_update(rcdev, id, true); +} + +static int bcm6345_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return bcm6345_reset_update(rcdev, id, false); +} + +static int bcm6345_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + bcm6345_reset_update(rcdev, id, true); + usleep_range(BCM6345_RESET_SLEEP_MIN_US, + BCM6345_RESET_SLEEP_MAX_US); + + bcm6345_reset_update(rcdev, id, false); + /* + * Ensure component is taken out reset state by sleeping also after + * deasserting the reset. Otherwise, the component may not be ready + * for operation. + */ + usleep_range(BCM6345_RESET_SLEEP_MIN_US, + BCM6345_RESET_SLEEP_MAX_US); + + return 0; +} + +static int bcm6345_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct bcm6345_reset *bcm6345_reset = to_bcm6345_reset(rcdev); + + return !(__raw_readl(bcm6345_reset->base) & BIT(id)); +} + +static const struct reset_control_ops bcm6345_reset_ops = { + .assert = bcm6345_reset_assert, + .deassert = bcm6345_reset_deassert, + .reset = bcm6345_reset_reset, + .status = bcm6345_reset_status, +}; + +static int bcm6345_reset_probe(struct platform_device *pdev) +{ + struct bcm6345_reset *bcm6345_reset; + + bcm6345_reset = devm_kzalloc(&pdev->dev, + sizeof(*bcm6345_reset), GFP_KERNEL); + if (!bcm6345_reset) + return -ENOMEM; + + platform_set_drvdata(pdev, bcm6345_reset); + + bcm6345_reset->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bcm6345_reset->base)) + return PTR_ERR(bcm6345_reset->base); + + spin_lock_init(&bcm6345_reset->lock); + bcm6345_reset->rcdev.ops = &bcm6345_reset_ops; + bcm6345_reset->rcdev.owner = THIS_MODULE; + bcm6345_reset->rcdev.of_node = pdev->dev.of_node; + bcm6345_reset->rcdev.of_reset_n_cells = 1; + bcm6345_reset->rcdev.nr_resets = BCM6345_RESET_NUM; + + return devm_reset_controller_register(&pdev->dev, + &bcm6345_reset->rcdev); +} + +static const struct of_device_id bcm6345_reset_of_match[] = { + { .compatible = "brcm,bcm6345-reset" }, + { /* sentinel */ }, +}; + +static struct platform_driver bcm6345_reset_driver = { + .probe = bcm6345_reset_probe, + .driver = { + .name = "bcm6345-reset", + .of_match_table = bcm6345_reset_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(bcm6345_reset_driver); diff --git a/drivers/reset/reset-berlin.c b/drivers/reset/reset-berlin.c new file mode 100644 index 000000000..2537ec05e --- /dev/null +++ b/drivers/reset/reset-berlin.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2014 Marvell Technology Group Ltd. + * + * Marvell Berlin reset driver + * + * Antoine Tenart <antoine.tenart@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define BERLIN_MAX_RESETS 32 + +#define to_berlin_reset_priv(p) \ + container_of((p), struct berlin_reset_priv, rcdev) + +struct berlin_reset_priv { + struct regmap *regmap; + struct reset_controller_dev rcdev; +}; + +static int berlin_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct berlin_reset_priv *priv = to_berlin_reset_priv(rcdev); + int offset = id >> 8; + int mask = BIT(id & 0x1f); + + regmap_write(priv->regmap, offset, mask); + + /* let the reset be effective */ + udelay(10); + + return 0; +} + +static const struct reset_control_ops berlin_reset_ops = { + .reset = berlin_reset_reset, +}; + +static int berlin_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + unsigned int offset, bit; + + offset = reset_spec->args[0]; + bit = reset_spec->args[1]; + + if (bit >= BERLIN_MAX_RESETS) + return -EINVAL; + + return (offset << 8) | bit; +} + +static int berlin2_reset_probe(struct platform_device *pdev) +{ + struct device_node *parent_np = of_get_parent(pdev->dev.of_node); + struct berlin_reset_priv *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = syscon_node_to_regmap(parent_np); + of_node_put(parent_np); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.ops = &berlin_reset_ops; + priv->rcdev.of_node = pdev->dev.of_node; + priv->rcdev.of_reset_n_cells = 2; + priv->rcdev.of_xlate = berlin_reset_xlate; + + return reset_controller_register(&priv->rcdev); +} + +static const struct of_device_id berlin_reset_dt_match[] = { + { .compatible = "marvell,berlin2-reset" }, + { }, +}; +MODULE_DEVICE_TABLE(of, berlin_reset_dt_match); + +static struct platform_driver berlin_reset_driver = { + .probe = berlin2_reset_probe, + .driver = { + .name = "berlin2-reset", + .of_match_table = berlin_reset_dt_match, + }, +}; +module_platform_driver(berlin_reset_driver); + +MODULE_AUTHOR("Antoine Tenart <antoine.tenart@free-electrons.com>"); +MODULE_AUTHOR("Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>"); +MODULE_DESCRIPTION("Synaptics Berlin reset controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/reset-brcmstb-rescal.c b/drivers/reset/reset-brcmstb-rescal.c new file mode 100644 index 000000000..433fa0c40 --- /dev/null +++ b/drivers/reset/reset-brcmstb-rescal.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2018-2020 Broadcom */ + +#include <linux/device.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> + +#define BRCM_RESCAL_START 0x0 +#define BRCM_RESCAL_START_BIT BIT(0) +#define BRCM_RESCAL_CTRL 0x4 +#define BRCM_RESCAL_STATUS 0x8 +#define BRCM_RESCAL_STATUS_BIT BIT(0) + +struct brcm_rescal_reset { + void __iomem *base; + struct device *dev; + struct reset_controller_dev rcdev; +}; + +static int brcm_rescal_reset_set(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct brcm_rescal_reset *data = + container_of(rcdev, struct brcm_rescal_reset, rcdev); + void __iomem *base = data->base; + u32 reg; + int ret; + + reg = readl(base + BRCM_RESCAL_START); + writel(reg | BRCM_RESCAL_START_BIT, base + BRCM_RESCAL_START); + reg = readl(base + BRCM_RESCAL_START); + if (!(reg & BRCM_RESCAL_START_BIT)) { + dev_err(data->dev, "failed to start SATA/PCIe rescal\n"); + return -EIO; + } + + ret = readl_poll_timeout(base + BRCM_RESCAL_STATUS, reg, + (reg & BRCM_RESCAL_STATUS_BIT), 100, 1000); + if (ret) { + dev_err(data->dev, "time out on SATA/PCIe rescal\n"); + return ret; + } + + reg = readl(base + BRCM_RESCAL_START); + writel(reg & ~BRCM_RESCAL_START_BIT, base + BRCM_RESCAL_START); + + dev_dbg(data->dev, "SATA/PCIe rescal success\n"); + + return 0; +} + +static int brcm_rescal_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + /* This is needed if #reset-cells == 0. */ + return 0; +} + +static const struct reset_control_ops brcm_rescal_reset_ops = { + .reset = brcm_rescal_reset_set, +}; + +static int brcm_rescal_reset_probe(struct platform_device *pdev) +{ + struct brcm_rescal_reset *data; + struct resource *res; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = 1; + data->rcdev.ops = &brcm_rescal_reset_ops; + data->rcdev.of_node = pdev->dev.of_node; + data->rcdev.of_xlate = brcm_rescal_reset_xlate; + data->dev = &pdev->dev; + + return devm_reset_controller_register(&pdev->dev, &data->rcdev); +} + +static const struct of_device_id brcm_rescal_reset_of_match[] = { + { .compatible = "brcm,bcm7216-pcie-sata-rescal" }, + { }, +}; +MODULE_DEVICE_TABLE(of, brcm_rescal_reset_of_match); + +static struct platform_driver brcm_rescal_reset_driver = { + .probe = brcm_rescal_reset_probe, + .driver = { + .name = "brcm-rescal-reset", + .of_match_table = brcm_rescal_reset_of_match, + } +}; +module_platform_driver(brcm_rescal_reset_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom SATA/PCIe rescal reset controller"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-brcmstb.c b/drivers/reset/reset-brcmstb.c new file mode 100644 index 000000000..42c9d5241 --- /dev/null +++ b/drivers/reset/reset-brcmstb.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Broadcom STB generic reset controller for SW_INIT style reset controller + * + * Author: Florian Fainelli <f.fainelli@gmail.com> + * Copyright (C) 2018 Broadcom + */ +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/types.h> + +struct brcmstb_reset { + void __iomem *base; + struct reset_controller_dev rcdev; +}; + +#define SW_INIT_SET 0x00 +#define SW_INIT_CLEAR 0x04 +#define SW_INIT_STATUS 0x08 + +#define SW_INIT_BIT(id) BIT((id) & 0x1f) +#define SW_INIT_BANK(id) ((id) >> 5) + +/* A full bank contains extra registers that we are not utilizing but still + * qualify as a single bank. + */ +#define SW_INIT_BANK_SIZE 0x18 + +static inline +struct brcmstb_reset *to_brcmstb(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct brcmstb_reset, rcdev); +} + +static int brcmstb_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + unsigned int off = SW_INIT_BANK(id) * SW_INIT_BANK_SIZE; + struct brcmstb_reset *priv = to_brcmstb(rcdev); + + writel_relaxed(SW_INIT_BIT(id), priv->base + off + SW_INIT_SET); + + return 0; +} + +static int brcmstb_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + unsigned int off = SW_INIT_BANK(id) * SW_INIT_BANK_SIZE; + struct brcmstb_reset *priv = to_brcmstb(rcdev); + + writel_relaxed(SW_INIT_BIT(id), priv->base + off + SW_INIT_CLEAR); + /* Maximum reset delay after de-asserting a line and seeing block + * operation is typically 14us for the worst case, build some slack + * here. + */ + usleep_range(100, 200); + + return 0; +} + +static int brcmstb_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + unsigned int off = SW_INIT_BANK(id) * SW_INIT_BANK_SIZE; + struct brcmstb_reset *priv = to_brcmstb(rcdev); + + return readl_relaxed(priv->base + off + SW_INIT_STATUS) & + SW_INIT_BIT(id); +} + +static const struct reset_control_ops brcmstb_reset_ops = { + .assert = brcmstb_reset_assert, + .deassert = brcmstb_reset_deassert, + .status = brcmstb_reset_status, +}; + +static int brcmstb_reset_probe(struct platform_device *pdev) +{ + struct device *kdev = &pdev->dev; + struct brcmstb_reset *priv; + struct resource *res; + + priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(kdev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + dev_set_drvdata(kdev, priv); + + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.nr_resets = DIV_ROUND_DOWN_ULL(resource_size(res), + SW_INIT_BANK_SIZE) * 32; + priv->rcdev.ops = &brcmstb_reset_ops; + priv->rcdev.of_node = kdev->of_node; + /* Use defaults: 1 cell and simple xlate function */ + + return devm_reset_controller_register(kdev, &priv->rcdev); +} + +static const struct of_device_id brcmstb_reset_of_match[] = { + { .compatible = "brcm,brcmstb-reset" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, brcmstb_reset_of_match); + +static struct platform_driver brcmstb_reset_driver = { + .probe = brcmstb_reset_probe, + .driver = { + .name = "brcmstb-reset", + .of_match_table = brcmstb_reset_of_match, + }, +}; +module_platform_driver(brcmstb_reset_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom STB reset controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/reset-hsdk.c b/drivers/reset/reset-hsdk.c new file mode 100644 index 000000000..4c7b8647b --- /dev/null +++ b/drivers/reset/reset-hsdk.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2017 Synopsys. + * + * Synopsys HSDK Development platform reset driver. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/types.h> + +#define to_hsdk_rst(p) container_of((p), struct hsdk_rst, rcdev) + +struct hsdk_rst { + void __iomem *regs_ctl; + void __iomem *regs_rst; + spinlock_t lock; + struct reset_controller_dev rcdev; +}; + +static const u32 rst_map[] = { + BIT(16), /* APB_RST */ + BIT(17), /* AXI_RST */ + BIT(18), /* ETH_RST */ + BIT(19), /* USB_RST */ + BIT(20), /* SDIO_RST */ + BIT(21), /* HDMI_RST */ + BIT(22), /* GFX_RST */ + BIT(25), /* DMAC_RST */ + BIT(31), /* EBI_RST */ +}; + +#define HSDK_MAX_RESETS ARRAY_SIZE(rst_map) + +#define CGU_SYS_RST_CTRL 0x0 +#define CGU_IP_SW_RESET 0x0 +#define CGU_IP_SW_RESET_DELAY_SHIFT 16 +#define CGU_IP_SW_RESET_DELAY_MASK GENMASK(31, CGU_IP_SW_RESET_DELAY_SHIFT) +#define CGU_IP_SW_RESET_DELAY 0 +#define CGU_IP_SW_RESET_RESET BIT(0) +#define SW_RESET_TIMEOUT 10000 + +static void hsdk_reset_config(struct hsdk_rst *rst, unsigned long id) +{ + writel(rst_map[id], rst->regs_ctl + CGU_SYS_RST_CTRL); +} + +static int hsdk_reset_do(struct hsdk_rst *rst) +{ + u32 reg; + + reg = readl(rst->regs_rst + CGU_IP_SW_RESET); + reg &= ~CGU_IP_SW_RESET_DELAY_MASK; + reg |= CGU_IP_SW_RESET_DELAY << CGU_IP_SW_RESET_DELAY_SHIFT; + reg |= CGU_IP_SW_RESET_RESET; + writel(reg, rst->regs_rst + CGU_IP_SW_RESET); + + /* wait till reset bit is back to 0 */ + return readl_poll_timeout_atomic(rst->regs_rst + CGU_IP_SW_RESET, reg, + !(reg & CGU_IP_SW_RESET_RESET), 5, SW_RESET_TIMEOUT); +} + +static int hsdk_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct hsdk_rst *rst = to_hsdk_rst(rcdev); + unsigned long flags; + int ret; + + spin_lock_irqsave(&rst->lock, flags); + hsdk_reset_config(rst, id); + ret = hsdk_reset_do(rst); + spin_unlock_irqrestore(&rst->lock, flags); + + return ret; +} + +static const struct reset_control_ops hsdk_reset_ops = { + .reset = hsdk_reset_reset, + .deassert = hsdk_reset_reset, +}; + +static int hsdk_reset_probe(struct platform_device *pdev) +{ + struct hsdk_rst *rst; + struct resource *mem; + + rst = devm_kzalloc(&pdev->dev, sizeof(*rst), GFP_KERNEL); + if (!rst) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rst->regs_ctl = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(rst->regs_ctl)) + return PTR_ERR(rst->regs_ctl); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + rst->regs_rst = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(rst->regs_rst)) + return PTR_ERR(rst->regs_rst); + + spin_lock_init(&rst->lock); + + rst->rcdev.owner = THIS_MODULE; + rst->rcdev.ops = &hsdk_reset_ops; + rst->rcdev.of_node = pdev->dev.of_node; + rst->rcdev.nr_resets = HSDK_MAX_RESETS; + rst->rcdev.of_reset_n_cells = 1; + + return reset_controller_register(&rst->rcdev); +} + +static const struct of_device_id hsdk_reset_dt_match[] = { + { .compatible = "snps,hsdk-reset" }, + { }, +}; + +static struct platform_driver hsdk_reset_driver = { + .probe = hsdk_reset_probe, + .driver = { + .name = "hsdk-reset", + .of_match_table = hsdk_reset_dt_match, + }, +}; +builtin_platform_driver(hsdk_reset_driver); + +MODULE_AUTHOR("Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>"); +MODULE_DESCRIPTION("Synopsys HSDK SDP reset driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-imx7.c b/drivers/reset/reset-imx7.c new file mode 100644 index 000000000..d2408725e --- /dev/null +++ b/drivers/reset/reset-imx7.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017, Impinj, Inc. + * + * i.MX7 System Reset Controller (SRC) driver + * + * Author: Andrey Smirnov <andrew.smirnov@gmail.com> + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/regmap.h> +#include <dt-bindings/reset/imx7-reset.h> +#include <dt-bindings/reset/imx8mq-reset.h> +#include <dt-bindings/reset/imx8mp-reset.h> + +struct imx7_src_signal { + unsigned int offset, bit; +}; + +struct imx7_src_variant { + const struct imx7_src_signal *signals; + unsigned int signals_num; + struct reset_control_ops ops; +}; + +struct imx7_src { + struct reset_controller_dev rcdev; + struct regmap *regmap; + const struct imx7_src_signal *signals; +}; + +enum imx7_src_registers { + SRC_A7RCR0 = 0x0004, + SRC_M4RCR = 0x000c, + SRC_ERCR = 0x0014, + SRC_HSICPHY_RCR = 0x001c, + SRC_USBOPHY1_RCR = 0x0020, + SRC_USBOPHY2_RCR = 0x0024, + SRC_MIPIPHY_RCR = 0x0028, + SRC_PCIEPHY_RCR = 0x002c, + SRC_DDRC_RCR = 0x1000, +}; + +static int imx7_reset_update(struct imx7_src *imx7src, + unsigned long id, unsigned int value) +{ + const struct imx7_src_signal *signal = &imx7src->signals[id]; + + return regmap_update_bits(imx7src->regmap, + signal->offset, signal->bit, value); +} + +static const struct imx7_src_signal imx7_src_signals[IMX7_RESET_NUM] = { + [IMX7_RESET_A7_CORE_POR_RESET0] = { SRC_A7RCR0, BIT(0) }, + [IMX7_RESET_A7_CORE_POR_RESET1] = { SRC_A7RCR0, BIT(1) }, + [IMX7_RESET_A7_CORE_RESET0] = { SRC_A7RCR0, BIT(4) }, + [IMX7_RESET_A7_CORE_RESET1] = { SRC_A7RCR0, BIT(5) }, + [IMX7_RESET_A7_DBG_RESET0] = { SRC_A7RCR0, BIT(8) }, + [IMX7_RESET_A7_DBG_RESET1] = { SRC_A7RCR0, BIT(9) }, + [IMX7_RESET_A7_ETM_RESET0] = { SRC_A7RCR0, BIT(12) }, + [IMX7_RESET_A7_ETM_RESET1] = { SRC_A7RCR0, BIT(13) }, + [IMX7_RESET_A7_SOC_DBG_RESET] = { SRC_A7RCR0, BIT(20) }, + [IMX7_RESET_A7_L2RESET] = { SRC_A7RCR0, BIT(21) }, + [IMX7_RESET_SW_M4C_RST] = { SRC_M4RCR, BIT(1) }, + [IMX7_RESET_SW_M4P_RST] = { SRC_M4RCR, BIT(2) }, + [IMX7_RESET_EIM_RST] = { SRC_ERCR, BIT(0) }, + [IMX7_RESET_HSICPHY_PORT_RST] = { SRC_HSICPHY_RCR, BIT(1) }, + [IMX7_RESET_USBPHY1_POR] = { SRC_USBOPHY1_RCR, BIT(0) }, + [IMX7_RESET_USBPHY1_PORT_RST] = { SRC_USBOPHY1_RCR, BIT(1) }, + [IMX7_RESET_USBPHY2_POR] = { SRC_USBOPHY2_RCR, BIT(0) }, + [IMX7_RESET_USBPHY2_PORT_RST] = { SRC_USBOPHY2_RCR, BIT(1) }, + [IMX7_RESET_MIPI_PHY_MRST] = { SRC_MIPIPHY_RCR, BIT(1) }, + [IMX7_RESET_MIPI_PHY_SRST] = { SRC_MIPIPHY_RCR, BIT(2) }, + [IMX7_RESET_PCIEPHY] = { SRC_PCIEPHY_RCR, BIT(2) | BIT(1) }, + [IMX7_RESET_PCIEPHY_PERST] = { SRC_PCIEPHY_RCR, BIT(3) }, + [IMX7_RESET_PCIE_CTRL_APPS_EN] = { SRC_PCIEPHY_RCR, BIT(6) }, + [IMX7_RESET_PCIE_CTRL_APPS_TURNOFF] = { SRC_PCIEPHY_RCR, BIT(11) }, + [IMX7_RESET_DDRC_PRST] = { SRC_DDRC_RCR, BIT(0) }, + [IMX7_RESET_DDRC_CORE_RST] = { SRC_DDRC_RCR, BIT(1) }, +}; + +static struct imx7_src *to_imx7_src(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct imx7_src, rcdev); +} + +static int imx7_reset_set(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct imx7_src *imx7src = to_imx7_src(rcdev); + const unsigned int bit = imx7src->signals[id].bit; + unsigned int value = assert ? bit : 0; + + switch (id) { + case IMX7_RESET_PCIEPHY: + /* + * wait for more than 10us to release phy g_rst and + * btnrst + */ + if (!assert) + udelay(10); + break; + + case IMX7_RESET_PCIE_CTRL_APPS_EN: + value = assert ? 0 : bit; + break; + } + + return imx7_reset_update(imx7src, id, value); +} + +static int imx7_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx7_reset_set(rcdev, id, true); +} + +static int imx7_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx7_reset_set(rcdev, id, false); +} + +static const struct imx7_src_variant variant_imx7 = { + .signals = imx7_src_signals, + .signals_num = ARRAY_SIZE(imx7_src_signals), + .ops = { + .assert = imx7_reset_assert, + .deassert = imx7_reset_deassert, + }, +}; + +enum imx8mq_src_registers { + SRC_A53RCR0 = 0x0004, + SRC_HDMI_RCR = 0x0030, + SRC_DISP_RCR = 0x0034, + SRC_GPU_RCR = 0x0040, + SRC_VPU_RCR = 0x0044, + SRC_PCIE2_RCR = 0x0048, + SRC_MIPIPHY1_RCR = 0x004c, + SRC_MIPIPHY2_RCR = 0x0050, + SRC_DDRC2_RCR = 0x1004, +}; + +enum imx8mp_src_registers { + SRC_SUPERMIX_RCR = 0x0018, + SRC_AUDIOMIX_RCR = 0x001c, + SRC_MLMIX_RCR = 0x0028, + SRC_GPU2D_RCR = 0x0038, + SRC_GPU3D_RCR = 0x003c, + SRC_VPU_G1_RCR = 0x0048, + SRC_VPU_G2_RCR = 0x004c, + SRC_VPUVC8KE_RCR = 0x0050, + SRC_NOC_RCR = 0x0054, +}; + +static const struct imx7_src_signal imx8mq_src_signals[IMX8MQ_RESET_NUM] = { + [IMX8MQ_RESET_A53_CORE_POR_RESET0] = { SRC_A53RCR0, BIT(0) }, + [IMX8MQ_RESET_A53_CORE_POR_RESET1] = { SRC_A53RCR0, BIT(1) }, + [IMX8MQ_RESET_A53_CORE_POR_RESET2] = { SRC_A53RCR0, BIT(2) }, + [IMX8MQ_RESET_A53_CORE_POR_RESET3] = { SRC_A53RCR0, BIT(3) }, + [IMX8MQ_RESET_A53_CORE_RESET0] = { SRC_A53RCR0, BIT(4) }, + [IMX8MQ_RESET_A53_CORE_RESET1] = { SRC_A53RCR0, BIT(5) }, + [IMX8MQ_RESET_A53_CORE_RESET2] = { SRC_A53RCR0, BIT(6) }, + [IMX8MQ_RESET_A53_CORE_RESET3] = { SRC_A53RCR0, BIT(7) }, + [IMX8MQ_RESET_A53_DBG_RESET0] = { SRC_A53RCR0, BIT(8) }, + [IMX8MQ_RESET_A53_DBG_RESET1] = { SRC_A53RCR0, BIT(9) }, + [IMX8MQ_RESET_A53_DBG_RESET2] = { SRC_A53RCR0, BIT(10) }, + [IMX8MQ_RESET_A53_DBG_RESET3] = { SRC_A53RCR0, BIT(11) }, + [IMX8MQ_RESET_A53_ETM_RESET0] = { SRC_A53RCR0, BIT(12) }, + [IMX8MQ_RESET_A53_ETM_RESET1] = { SRC_A53RCR0, BIT(13) }, + [IMX8MQ_RESET_A53_ETM_RESET2] = { SRC_A53RCR0, BIT(14) }, + [IMX8MQ_RESET_A53_ETM_RESET3] = { SRC_A53RCR0, BIT(15) }, + [IMX8MQ_RESET_A53_SOC_DBG_RESET] = { SRC_A53RCR0, BIT(20) }, + [IMX8MQ_RESET_A53_L2RESET] = { SRC_A53RCR0, BIT(21) }, + [IMX8MQ_RESET_SW_NON_SCLR_M4C_RST] = { SRC_M4RCR, BIT(0) }, + [IMX8MQ_RESET_SW_M4C_RST] = { SRC_M4RCR, BIT(1) }, + [IMX8MQ_RESET_SW_M4P_RST] = { SRC_M4RCR, BIT(2) }, + [IMX8MQ_RESET_M4_ENABLE] = { SRC_M4RCR, BIT(3) }, + [IMX8MQ_RESET_OTG1_PHY_RESET] = { SRC_USBOPHY1_RCR, BIT(0) }, + [IMX8MQ_RESET_OTG2_PHY_RESET] = { SRC_USBOPHY2_RCR, BIT(0) }, + [IMX8MQ_RESET_MIPI_DSI_RESET_BYTE_N] = { SRC_MIPIPHY_RCR, BIT(1) }, + [IMX8MQ_RESET_MIPI_DSI_RESET_N] = { SRC_MIPIPHY_RCR, BIT(2) }, + [IMX8MQ_RESET_MIPI_DSI_DPI_RESET_N] = { SRC_MIPIPHY_RCR, BIT(3) }, + [IMX8MQ_RESET_MIPI_DSI_ESC_RESET_N] = { SRC_MIPIPHY_RCR, BIT(4) }, + [IMX8MQ_RESET_MIPI_DSI_PCLK_RESET_N] = { SRC_MIPIPHY_RCR, BIT(5) }, + [IMX8MQ_RESET_PCIEPHY] = { SRC_PCIEPHY_RCR, + BIT(2) | BIT(1) }, + [IMX8MQ_RESET_PCIEPHY_PERST] = { SRC_PCIEPHY_RCR, BIT(3) }, + [IMX8MQ_RESET_PCIE_CTRL_APPS_EN] = { SRC_PCIEPHY_RCR, BIT(6) }, + [IMX8MQ_RESET_PCIE_CTRL_APPS_TURNOFF] = { SRC_PCIEPHY_RCR, BIT(11) }, + [IMX8MQ_RESET_HDMI_PHY_APB_RESET] = { SRC_HDMI_RCR, BIT(0) }, + [IMX8MQ_RESET_DISP_RESET] = { SRC_DISP_RCR, BIT(0) }, + [IMX8MQ_RESET_GPU_RESET] = { SRC_GPU_RCR, BIT(0) }, + [IMX8MQ_RESET_VPU_RESET] = { SRC_VPU_RCR, BIT(0) }, + [IMX8MQ_RESET_PCIEPHY2] = { SRC_PCIE2_RCR, + BIT(2) | BIT(1) }, + [IMX8MQ_RESET_PCIEPHY2_PERST] = { SRC_PCIE2_RCR, BIT(3) }, + [IMX8MQ_RESET_PCIE2_CTRL_APPS_EN] = { SRC_PCIE2_RCR, BIT(6) }, + [IMX8MQ_RESET_PCIE2_CTRL_APPS_TURNOFF] = { SRC_PCIE2_RCR, BIT(11) }, + [IMX8MQ_RESET_MIPI_CSI1_CORE_RESET] = { SRC_MIPIPHY1_RCR, BIT(0) }, + [IMX8MQ_RESET_MIPI_CSI1_PHY_REF_RESET] = { SRC_MIPIPHY1_RCR, BIT(1) }, + [IMX8MQ_RESET_MIPI_CSI1_ESC_RESET] = { SRC_MIPIPHY1_RCR, BIT(2) }, + [IMX8MQ_RESET_MIPI_CSI2_CORE_RESET] = { SRC_MIPIPHY2_RCR, BIT(0) }, + [IMX8MQ_RESET_MIPI_CSI2_PHY_REF_RESET] = { SRC_MIPIPHY2_RCR, BIT(1) }, + [IMX8MQ_RESET_MIPI_CSI2_ESC_RESET] = { SRC_MIPIPHY2_RCR, BIT(2) }, + [IMX8MQ_RESET_DDRC1_PRST] = { SRC_DDRC_RCR, BIT(0) }, + [IMX8MQ_RESET_DDRC1_CORE_RESET] = { SRC_DDRC_RCR, BIT(1) }, + [IMX8MQ_RESET_DDRC1_PHY_RESET] = { SRC_DDRC_RCR, BIT(2) }, + [IMX8MQ_RESET_DDRC2_PHY_RESET] = { SRC_DDRC2_RCR, BIT(0) }, + [IMX8MQ_RESET_DDRC2_CORE_RESET] = { SRC_DDRC2_RCR, BIT(1) }, + [IMX8MQ_RESET_DDRC2_PRST] = { SRC_DDRC2_RCR, BIT(2) }, +}; + +static int imx8mq_reset_set(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct imx7_src *imx7src = to_imx7_src(rcdev); + const unsigned int bit = imx7src->signals[id].bit; + unsigned int value = assert ? bit : 0; + + switch (id) { + case IMX8MQ_RESET_PCIEPHY: + case IMX8MQ_RESET_PCIEPHY2: + /* + * wait for more than 10us to release phy g_rst and + * btnrst + */ + if (!assert) + udelay(10); + break; + + case IMX8MQ_RESET_PCIE_CTRL_APPS_EN: + case IMX8MQ_RESET_PCIE2_CTRL_APPS_EN: + case IMX8MQ_RESET_MIPI_DSI_PCLK_RESET_N: + case IMX8MQ_RESET_MIPI_DSI_ESC_RESET_N: + case IMX8MQ_RESET_MIPI_DSI_DPI_RESET_N: + case IMX8MQ_RESET_MIPI_DSI_RESET_N: + case IMX8MQ_RESET_MIPI_DSI_RESET_BYTE_N: + case IMX8MQ_RESET_M4_ENABLE: + value = assert ? 0 : bit; + break; + } + + return imx7_reset_update(imx7src, id, value); +} + +static int imx8mq_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx8mq_reset_set(rcdev, id, true); +} + +static int imx8mq_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx8mq_reset_set(rcdev, id, false); +} + +static const struct imx7_src_variant variant_imx8mq = { + .signals = imx8mq_src_signals, + .signals_num = ARRAY_SIZE(imx8mq_src_signals), + .ops = { + .assert = imx8mq_reset_assert, + .deassert = imx8mq_reset_deassert, + }, +}; + +static const struct imx7_src_signal imx8mp_src_signals[IMX8MP_RESET_NUM] = { + [IMX8MP_RESET_A53_CORE_POR_RESET0] = { SRC_A53RCR0, BIT(0) }, + [IMX8MP_RESET_A53_CORE_POR_RESET1] = { SRC_A53RCR0, BIT(1) }, + [IMX8MP_RESET_A53_CORE_POR_RESET2] = { SRC_A53RCR0, BIT(2) }, + [IMX8MP_RESET_A53_CORE_POR_RESET3] = { SRC_A53RCR0, BIT(3) }, + [IMX8MP_RESET_A53_CORE_RESET0] = { SRC_A53RCR0, BIT(4) }, + [IMX8MP_RESET_A53_CORE_RESET1] = { SRC_A53RCR0, BIT(5) }, + [IMX8MP_RESET_A53_CORE_RESET2] = { SRC_A53RCR0, BIT(6) }, + [IMX8MP_RESET_A53_CORE_RESET3] = { SRC_A53RCR0, BIT(7) }, + [IMX8MP_RESET_A53_DBG_RESET0] = { SRC_A53RCR0, BIT(8) }, + [IMX8MP_RESET_A53_DBG_RESET1] = { SRC_A53RCR0, BIT(9) }, + [IMX8MP_RESET_A53_DBG_RESET2] = { SRC_A53RCR0, BIT(10) }, + [IMX8MP_RESET_A53_DBG_RESET3] = { SRC_A53RCR0, BIT(11) }, + [IMX8MP_RESET_A53_ETM_RESET0] = { SRC_A53RCR0, BIT(12) }, + [IMX8MP_RESET_A53_ETM_RESET1] = { SRC_A53RCR0, BIT(13) }, + [IMX8MP_RESET_A53_ETM_RESET2] = { SRC_A53RCR0, BIT(14) }, + [IMX8MP_RESET_A53_ETM_RESET3] = { SRC_A53RCR0, BIT(15) }, + [IMX8MP_RESET_A53_SOC_DBG_RESET] = { SRC_A53RCR0, BIT(20) }, + [IMX8MP_RESET_A53_L2RESET] = { SRC_A53RCR0, BIT(21) }, + [IMX8MP_RESET_SW_NON_SCLR_M7C_RST] = { SRC_M4RCR, BIT(0) }, + [IMX8MP_RESET_OTG1_PHY_RESET] = { SRC_USBOPHY1_RCR, BIT(0) }, + [IMX8MP_RESET_OTG2_PHY_RESET] = { SRC_USBOPHY2_RCR, BIT(0) }, + [IMX8MP_RESET_SUPERMIX_RESET] = { SRC_SUPERMIX_RCR, BIT(0) }, + [IMX8MP_RESET_AUDIOMIX_RESET] = { SRC_AUDIOMIX_RCR, BIT(0) }, + [IMX8MP_RESET_MLMIX_RESET] = { SRC_MLMIX_RCR, BIT(0) }, + [IMX8MP_RESET_PCIEPHY] = { SRC_PCIEPHY_RCR, BIT(2) }, + [IMX8MP_RESET_PCIEPHY_PERST] = { SRC_PCIEPHY_RCR, BIT(3) }, + [IMX8MP_RESET_PCIE_CTRL_APPS_EN] = { SRC_PCIEPHY_RCR, BIT(6) }, + [IMX8MP_RESET_PCIE_CTRL_APPS_TURNOFF] = { SRC_PCIEPHY_RCR, BIT(11) }, + [IMX8MP_RESET_HDMI_PHY_APB_RESET] = { SRC_HDMI_RCR, BIT(0) }, + [IMX8MP_RESET_MEDIA_RESET] = { SRC_DISP_RCR, BIT(0) }, + [IMX8MP_RESET_GPU2D_RESET] = { SRC_GPU2D_RCR, BIT(0) }, + [IMX8MP_RESET_GPU3D_RESET] = { SRC_GPU3D_RCR, BIT(0) }, + [IMX8MP_RESET_GPU_RESET] = { SRC_GPU_RCR, BIT(0) }, + [IMX8MP_RESET_VPU_RESET] = { SRC_VPU_RCR, BIT(0) }, + [IMX8MP_RESET_VPU_G1_RESET] = { SRC_VPU_G1_RCR, BIT(0) }, + [IMX8MP_RESET_VPU_G2_RESET] = { SRC_VPU_G2_RCR, BIT(0) }, + [IMX8MP_RESET_VPUVC8KE_RESET] = { SRC_VPUVC8KE_RCR, BIT(0) }, + [IMX8MP_RESET_NOC_RESET] = { SRC_NOC_RCR, BIT(0) }, +}; + +static int imx8mp_reset_set(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct imx7_src *imx7src = to_imx7_src(rcdev); + const unsigned int bit = imx7src->signals[id].bit; + unsigned int value = assert ? bit : 0; + + switch (id) { + case IMX8MP_RESET_PCIEPHY: + /* + * wait for more than 10us to release phy g_rst and + * btnrst + */ + if (!assert) + udelay(10); + break; + + case IMX8MP_RESET_PCIE_CTRL_APPS_EN: + case IMX8MP_RESET_PCIEPHY_PERST: + value = assert ? 0 : bit; + break; + } + + return imx7_reset_update(imx7src, id, value); +} + +static int imx8mp_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx8mp_reset_set(rcdev, id, true); +} + +static int imx8mp_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return imx8mp_reset_set(rcdev, id, false); +} + +static const struct imx7_src_variant variant_imx8mp = { + .signals = imx8mp_src_signals, + .signals_num = ARRAY_SIZE(imx8mp_src_signals), + .ops = { + .assert = imx8mp_reset_assert, + .deassert = imx8mp_reset_deassert, + }, +}; + +static int imx7_reset_probe(struct platform_device *pdev) +{ + struct imx7_src *imx7src; + struct device *dev = &pdev->dev; + struct regmap_config config = { .name = "src" }; + const struct imx7_src_variant *variant = of_device_get_match_data(dev); + + imx7src = devm_kzalloc(dev, sizeof(*imx7src), GFP_KERNEL); + if (!imx7src) + return -ENOMEM; + + imx7src->signals = variant->signals; + imx7src->regmap = syscon_node_to_regmap(dev->of_node); + if (IS_ERR(imx7src->regmap)) { + dev_err(dev, "Unable to get imx7-src regmap"); + return PTR_ERR(imx7src->regmap); + } + regmap_attach_dev(dev, imx7src->regmap, &config); + + imx7src->rcdev.owner = THIS_MODULE; + imx7src->rcdev.nr_resets = variant->signals_num; + imx7src->rcdev.ops = &variant->ops; + imx7src->rcdev.of_node = dev->of_node; + + return devm_reset_controller_register(dev, &imx7src->rcdev); +} + +static const struct of_device_id imx7_reset_dt_ids[] = { + { .compatible = "fsl,imx7d-src", .data = &variant_imx7 }, + { .compatible = "fsl,imx8mq-src", .data = &variant_imx8mq }, + { .compatible = "fsl,imx8mp-src", .data = &variant_imx8mp }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, imx7_reset_dt_ids); + +static struct platform_driver imx7_reset_driver = { + .probe = imx7_reset_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = imx7_reset_dt_ids, + }, +}; +module_platform_driver(imx7_reset_driver); + +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("NXP i.MX7 reset driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-intel-gw.c b/drivers/reset/reset-intel-gw.c new file mode 100644 index 000000000..effc177db --- /dev/null +++ b/drivers/reset/reset-intel-gw.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Intel Corporation. + * Lei Chuanhua <Chuanhua.lei@intel.com> + */ + +#include <linux/bitfield.h> +#include <linux/init.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +#define RCU_RST_STAT 0x0024 +#define RCU_RST_REQ 0x0048 + +#define REG_OFFSET_MASK GENMASK(31, 16) +#define BIT_OFFSET_MASK GENMASK(15, 8) +#define STAT_BIT_OFFSET_MASK GENMASK(7, 0) + +#define to_reset_data(x) container_of(x, struct intel_reset_data, rcdev) + +struct intel_reset_soc { + bool legacy; + u32 reset_cell_count; +}; + +struct intel_reset_data { + struct reset_controller_dev rcdev; + struct notifier_block restart_nb; + const struct intel_reset_soc *soc_data; + struct regmap *regmap; + struct device *dev; + u32 reboot_id; +}; + +static const struct regmap_config intel_rcu_regmap_config = { + .name = "intel-reset", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, +}; + +/* + * Reset status register offset relative to + * the reset control register(X) is X + 4 + */ +static u32 id_to_reg_and_bit_offsets(struct intel_reset_data *data, + unsigned long id, u32 *rst_req, + u32 *req_bit, u32 *stat_bit) +{ + *rst_req = FIELD_GET(REG_OFFSET_MASK, id); + *req_bit = FIELD_GET(BIT_OFFSET_MASK, id); + + if (data->soc_data->legacy) + *stat_bit = FIELD_GET(STAT_BIT_OFFSET_MASK, id); + else + *stat_bit = *req_bit; + + if (data->soc_data->legacy && *rst_req == RCU_RST_REQ) + return RCU_RST_STAT; + else + return *rst_req + 0x4; +} + +static int intel_set_clr_bits(struct intel_reset_data *data, unsigned long id, + bool set) +{ + u32 rst_req, req_bit, rst_stat, stat_bit, val; + int ret; + + rst_stat = id_to_reg_and_bit_offsets(data, id, &rst_req, + &req_bit, &stat_bit); + + val = set ? BIT(req_bit) : 0; + ret = regmap_update_bits(data->regmap, rst_req, BIT(req_bit), val); + if (ret) + return ret; + + return regmap_read_poll_timeout(data->regmap, rst_stat, val, + set == !!(val & BIT(stat_bit)), 20, + 200); +} + +static int intel_assert_device(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct intel_reset_data *data = to_reset_data(rcdev); + int ret; + + ret = intel_set_clr_bits(data, id, true); + if (ret) + dev_err(data->dev, "Reset assert failed %d\n", ret); + + return ret; +} + +static int intel_deassert_device(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct intel_reset_data *data = to_reset_data(rcdev); + int ret; + + ret = intel_set_clr_bits(data, id, false); + if (ret) + dev_err(data->dev, "Reset deassert failed %d\n", ret); + + return ret; +} + +static int intel_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct intel_reset_data *data = to_reset_data(rcdev); + u32 rst_req, req_bit, rst_stat, stat_bit, val; + int ret; + + rst_stat = id_to_reg_and_bit_offsets(data, id, &rst_req, + &req_bit, &stat_bit); + ret = regmap_read(data->regmap, rst_stat, &val); + if (ret) + return ret; + + return !!(val & BIT(stat_bit)); +} + +static const struct reset_control_ops intel_reset_ops = { + .assert = intel_assert_device, + .deassert = intel_deassert_device, + .status = intel_reset_status, +}; + +static int intel_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *spec) +{ + struct intel_reset_data *data = to_reset_data(rcdev); + u32 id; + + if (spec->args[1] > 31) + return -EINVAL; + + id = FIELD_PREP(REG_OFFSET_MASK, spec->args[0]); + id |= FIELD_PREP(BIT_OFFSET_MASK, spec->args[1]); + + if (data->soc_data->legacy) { + if (spec->args[2] > 31) + return -EINVAL; + + id |= FIELD_PREP(STAT_BIT_OFFSET_MASK, spec->args[2]); + } + + return id; +} + +static int intel_reset_restart_handler(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct intel_reset_data *reset_data; + + reset_data = container_of(nb, struct intel_reset_data, restart_nb); + intel_assert_device(&reset_data->rcdev, reset_data->reboot_id); + + return NOTIFY_DONE; +} + +static int intel_reset_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct intel_reset_data *data; + void __iomem *base; + u32 rb_id[3]; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->soc_data = of_device_get_match_data(dev); + if (!data->soc_data) + return -ENODEV; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + data->regmap = devm_regmap_init_mmio(dev, base, + &intel_rcu_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "regmap initialization failed\n"); + return PTR_ERR(data->regmap); + } + + ret = device_property_read_u32_array(dev, "intel,global-reset", rb_id, + data->soc_data->reset_cell_count); + if (ret) { + dev_err(dev, "Failed to get global reset offset!\n"); + return ret; + } + + data->dev = dev; + data->rcdev.of_node = np; + data->rcdev.owner = dev->driver->owner; + data->rcdev.ops = &intel_reset_ops; + data->rcdev.of_xlate = intel_reset_xlate; + data->rcdev.of_reset_n_cells = data->soc_data->reset_cell_count; + ret = devm_reset_controller_register(&pdev->dev, &data->rcdev); + if (ret) + return ret; + + data->reboot_id = FIELD_PREP(REG_OFFSET_MASK, rb_id[0]); + data->reboot_id |= FIELD_PREP(BIT_OFFSET_MASK, rb_id[1]); + + if (data->soc_data->legacy) + data->reboot_id |= FIELD_PREP(STAT_BIT_OFFSET_MASK, rb_id[2]); + + data->restart_nb.notifier_call = intel_reset_restart_handler; + data->restart_nb.priority = 128; + register_restart_handler(&data->restart_nb); + + return 0; +} + +static const struct intel_reset_soc xrx200_data = { + .legacy = true, + .reset_cell_count = 3, +}; + +static const struct intel_reset_soc lgm_data = { + .legacy = false, + .reset_cell_count = 2, +}; + +static const struct of_device_id intel_reset_match[] = { + { .compatible = "intel,rcu-lgm", .data = &lgm_data }, + { .compatible = "intel,rcu-xrx200", .data = &xrx200_data }, + {} +}; + +static struct platform_driver intel_reset_driver = { + .probe = intel_reset_probe, + .driver = { + .name = "intel-reset", + .of_match_table = intel_reset_match, + }, +}; + +static int __init intel_reset_init(void) +{ + return platform_driver_register(&intel_reset_driver); +} + +/* + * RCU is system core entity which is in Always On Domain whose clocks + * or resource initialization happens in system core initialization. + * Also, it is required for most of the platform or architecture + * specific devices to perform reset operation as part of initialization. + * So perform RCU as post core initialization. + */ +postcore_initcall(intel_reset_init); diff --git a/drivers/reset/reset-k210.c b/drivers/reset/reset-k210.c new file mode 100644 index 000000000..1b6e03522 --- /dev/null +++ b/drivers/reset/reset-k210.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2020 Western Digital Corporation or its affiliates. + */ +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/delay.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <soc/canaan/k210-sysctl.h> + +#include <dt-bindings/reset/k210-rst.h> + +#define K210_RST_MASK 0x27FFFFFF + +struct k210_rst { + struct regmap *map; + struct reset_controller_dev rcdev; +}; + +static inline struct k210_rst * +to_k210_rst(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct k210_rst, rcdev); +} + +static inline int k210_rst_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct k210_rst *ksr = to_k210_rst(rcdev); + + return regmap_update_bits(ksr->map, K210_SYSCTL_PERI_RESET, BIT(id), 1); +} + +static inline int k210_rst_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct k210_rst *ksr = to_k210_rst(rcdev); + + return regmap_update_bits(ksr->map, K210_SYSCTL_PERI_RESET, BIT(id), 0); +} + +static int k210_rst_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + int ret; + + ret = k210_rst_assert(rcdev, id); + if (ret == 0) { + udelay(10); + ret = k210_rst_deassert(rcdev, id); + } + + return ret; +} + +static int k210_rst_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct k210_rst *ksr = to_k210_rst(rcdev); + u32 reg, bit = BIT(id); + int ret; + + ret = regmap_read(ksr->map, K210_SYSCTL_PERI_RESET, ®); + if (ret) + return ret; + + return reg & bit; +} + +static int k210_rst_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + unsigned long id = reset_spec->args[0]; + + if (!(BIT(id) & K210_RST_MASK)) + return -EINVAL; + + return id; +} + +static const struct reset_control_ops k210_rst_ops = { + .assert = k210_rst_assert, + .deassert = k210_rst_deassert, + .reset = k210_rst_reset, + .status = k210_rst_status, +}; + +static int k210_rst_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *parent_np = of_get_parent(dev->of_node); + struct k210_rst *ksr; + + dev_info(dev, "K210 reset controller\n"); + + ksr = devm_kzalloc(dev, sizeof(*ksr), GFP_KERNEL); + if (!ksr) + return -ENOMEM; + + ksr->map = syscon_node_to_regmap(parent_np); + of_node_put(parent_np); + if (IS_ERR(ksr->map)) + return PTR_ERR(ksr->map); + + ksr->rcdev.owner = THIS_MODULE; + ksr->rcdev.dev = dev; + ksr->rcdev.of_node = dev->of_node; + ksr->rcdev.ops = &k210_rst_ops; + ksr->rcdev.nr_resets = fls(K210_RST_MASK); + ksr->rcdev.of_reset_n_cells = 1; + ksr->rcdev.of_xlate = k210_rst_xlate; + + return devm_reset_controller_register(dev, &ksr->rcdev); +} + +static const struct of_device_id k210_rst_dt_ids[] = { + { .compatible = "canaan,k210-rst" }, + { /* sentinel */ }, +}; + +static struct platform_driver k210_rst_driver = { + .probe = k210_rst_probe, + .driver = { + .name = "k210-rst", + .of_match_table = k210_rst_dt_ids, + }, +}; +builtin_platform_driver(k210_rst_driver); diff --git a/drivers/reset/reset-lantiq.c b/drivers/reset/reset-lantiq.c new file mode 100644 index 000000000..b936cfe85 --- /dev/null +++ b/drivers/reset/reset-lantiq.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2010 John Crispin <blogic@phrozen.org> + * Copyright (C) 2013-2015 Lantiq Beteiligungs-GmbH & Co.KG + * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + * Copyright (C) 2017 Hauke Mehrtens <hauke@hauke-m.de> + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +#define LANTIQ_RCU_RESET_TIMEOUT 10000 + +struct lantiq_rcu_reset_priv { + struct reset_controller_dev rcdev; + struct device *dev; + struct regmap *regmap; + u32 reset_offset; + u32 status_offset; +}; + +static struct lantiq_rcu_reset_priv *to_lantiq_rcu_reset_priv( + struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct lantiq_rcu_reset_priv, rcdev); +} + +static int lantiq_rcu_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct lantiq_rcu_reset_priv *priv = to_lantiq_rcu_reset_priv(rcdev); + unsigned int status = (id >> 8) & 0x1f; + u32 val; + int ret; + + ret = regmap_read(priv->regmap, priv->status_offset, &val); + if (ret) + return ret; + + return !!(val & BIT(status)); +} + +static int lantiq_rcu_reset_status_timeout(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + int ret; + int retry = LANTIQ_RCU_RESET_TIMEOUT; + + do { + ret = lantiq_rcu_reset_status(rcdev, id); + if (ret < 0) + return ret; + if (ret == assert) + return 0; + usleep_range(20, 40); + } while (--retry); + + return -ETIMEDOUT; +} + +static int lantiq_rcu_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct lantiq_rcu_reset_priv *priv = to_lantiq_rcu_reset_priv(rcdev); + unsigned int set = id & 0x1f; + u32 val = assert ? BIT(set) : 0; + int ret; + + ret = regmap_update_bits(priv->regmap, priv->reset_offset, BIT(set), + val); + if (ret) { + dev_err(priv->dev, "Failed to set reset bit %u\n", set); + return ret; + } + + + ret = lantiq_rcu_reset_status_timeout(rcdev, id, assert); + if (ret) + dev_err(priv->dev, "Failed to %s bit %u\n", + assert ? "assert" : "deassert", set); + + return ret; +} + +static int lantiq_rcu_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return lantiq_rcu_reset_update(rcdev, id, true); +} + +static int lantiq_rcu_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return lantiq_rcu_reset_update(rcdev, id, false); +} + +static int lantiq_rcu_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + int ret; + + ret = lantiq_rcu_reset_assert(rcdev, id); + if (ret) + return ret; + + return lantiq_rcu_reset_deassert(rcdev, id); +} + +static const struct reset_control_ops lantiq_rcu_reset_ops = { + .assert = lantiq_rcu_reset_assert, + .deassert = lantiq_rcu_reset_deassert, + .status = lantiq_rcu_reset_status, + .reset = lantiq_rcu_reset_reset, +}; + +static int lantiq_rcu_reset_of_parse(struct platform_device *pdev, + struct lantiq_rcu_reset_priv *priv) +{ + struct device *dev = &pdev->dev; + const __be32 *offset; + + priv->regmap = syscon_node_to_regmap(dev->of_node->parent); + if (IS_ERR(priv->regmap)) { + dev_err(&pdev->dev, "Failed to lookup RCU regmap\n"); + return PTR_ERR(priv->regmap); + } + + offset = of_get_address(dev->of_node, 0, NULL, NULL); + if (!offset) { + dev_err(&pdev->dev, "Failed to get RCU reset offset\n"); + return -ENOENT; + } + priv->reset_offset = __be32_to_cpu(*offset); + + offset = of_get_address(dev->of_node, 1, NULL, NULL); + if (!offset) { + dev_err(&pdev->dev, "Failed to get RCU status offset\n"); + return -ENOENT; + } + priv->status_offset = __be32_to_cpu(*offset); + + return 0; +} + +static int lantiq_rcu_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + unsigned int status, set; + + set = reset_spec->args[0]; + status = reset_spec->args[1]; + + if (set >= rcdev->nr_resets || status >= rcdev->nr_resets) + return -EINVAL; + + return (status << 8) | set; +} + +static int lantiq_rcu_reset_probe(struct platform_device *pdev) +{ + struct lantiq_rcu_reset_priv *priv; + int err; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + platform_set_drvdata(pdev, priv); + + err = lantiq_rcu_reset_of_parse(pdev, priv); + if (err) + return err; + + priv->rcdev.ops = &lantiq_rcu_reset_ops; + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.of_node = pdev->dev.of_node; + priv->rcdev.nr_resets = 32; + priv->rcdev.of_xlate = lantiq_rcu_reset_xlate; + priv->rcdev.of_reset_n_cells = 2; + + return devm_reset_controller_register(&pdev->dev, &priv->rcdev); +} + +static const struct of_device_id lantiq_rcu_reset_dt_ids[] = { + { .compatible = "lantiq,danube-reset", }, + { .compatible = "lantiq,xrx200-reset", }, + { }, +}; +MODULE_DEVICE_TABLE(of, lantiq_rcu_reset_dt_ids); + +static struct platform_driver lantiq_rcu_reset_driver = { + .probe = lantiq_rcu_reset_probe, + .driver = { + .name = "lantiq-reset", + .of_match_table = lantiq_rcu_reset_dt_ids, + }, +}; +module_platform_driver(lantiq_rcu_reset_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Lantiq XWAY RCU Reset Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/reset-lpc18xx.c b/drivers/reset/reset-lpc18xx.c new file mode 100644 index 000000000..35d8dd4cc --- /dev/null +++ b/drivers/reset/reset-lpc18xx.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Reset driver for NXP LPC18xx/43xx Reset Generation Unit (RGU). + * + * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/reset-controller.h> +#include <linux/spinlock.h> + +/* LPC18xx RGU registers */ +#define LPC18XX_RGU_CTRL0 0x100 +#define LPC18XX_RGU_CTRL1 0x104 +#define LPC18XX_RGU_ACTIVE_STATUS0 0x150 +#define LPC18XX_RGU_ACTIVE_STATUS1 0x154 + +#define LPC18XX_RGU_RESETS_PER_REG 32 + +/* Internal reset outputs */ +#define LPC18XX_RGU_CORE_RST 0 +#define LPC43XX_RGU_M0SUB_RST 12 +#define LPC43XX_RGU_M0APP_RST 56 + +struct lpc18xx_rgu_data { + struct reset_controller_dev rcdev; + struct notifier_block restart_nb; + struct clk *clk_delay; + struct clk *clk_reg; + void __iomem *base; + spinlock_t lock; + u32 delay_us; +}; + +#define to_rgu_data(p) container_of(p, struct lpc18xx_rgu_data, rcdev) + +static int lpc18xx_rgu_restart(struct notifier_block *nb, unsigned long mode, + void *cmd) +{ + struct lpc18xx_rgu_data *rc = container_of(nb, struct lpc18xx_rgu_data, + restart_nb); + + writel(BIT(LPC18XX_RGU_CORE_RST), rc->base + LPC18XX_RGU_CTRL0); + mdelay(2000); + + pr_emerg("%s: unable to restart system\n", __func__); + + return NOTIFY_DONE; +} + +/* + * The LPC18xx RGU has mostly self-deasserting resets except for the + * two reset lines going to the internal Cortex-M0 cores. + * + * To prevent the M0 core resets from accidentally getting deasserted + * status register must be check and bits in control register set to + * preserve the state. + */ +static int lpc18xx_rgu_setclear_reset(struct reset_controller_dev *rcdev, + unsigned long id, bool set) +{ + struct lpc18xx_rgu_data *rc = to_rgu_data(rcdev); + u32 stat_offset = LPC18XX_RGU_ACTIVE_STATUS0; + u32 ctrl_offset = LPC18XX_RGU_CTRL0; + unsigned long flags; + u32 stat, rst_bit; + + stat_offset += (id / LPC18XX_RGU_RESETS_PER_REG) * sizeof(u32); + ctrl_offset += (id / LPC18XX_RGU_RESETS_PER_REG) * sizeof(u32); + rst_bit = 1 << (id % LPC18XX_RGU_RESETS_PER_REG); + + spin_lock_irqsave(&rc->lock, flags); + stat = ~readl(rc->base + stat_offset); + if (set) + writel(stat | rst_bit, rc->base + ctrl_offset); + else + writel(stat & ~rst_bit, rc->base + ctrl_offset); + spin_unlock_irqrestore(&rc->lock, flags); + + return 0; +} + +static int lpc18xx_rgu_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return lpc18xx_rgu_setclear_reset(rcdev, id, true); +} + +static int lpc18xx_rgu_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return lpc18xx_rgu_setclear_reset(rcdev, id, false); +} + +/* Only M0 cores require explicit reset deassert */ +static int lpc18xx_rgu_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct lpc18xx_rgu_data *rc = to_rgu_data(rcdev); + + lpc18xx_rgu_assert(rcdev, id); + udelay(rc->delay_us); + + switch (id) { + case LPC43XX_RGU_M0SUB_RST: + case LPC43XX_RGU_M0APP_RST: + lpc18xx_rgu_setclear_reset(rcdev, id, false); + } + + return 0; +} + +static int lpc18xx_rgu_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct lpc18xx_rgu_data *rc = to_rgu_data(rcdev); + u32 bit, offset = LPC18XX_RGU_ACTIVE_STATUS0; + + offset += (id / LPC18XX_RGU_RESETS_PER_REG) * sizeof(u32); + bit = 1 << (id % LPC18XX_RGU_RESETS_PER_REG); + + return !(readl(rc->base + offset) & bit); +} + +static const struct reset_control_ops lpc18xx_rgu_ops = { + .reset = lpc18xx_rgu_reset, + .assert = lpc18xx_rgu_assert, + .deassert = lpc18xx_rgu_deassert, + .status = lpc18xx_rgu_status, +}; + +static int lpc18xx_rgu_probe(struct platform_device *pdev) +{ + struct lpc18xx_rgu_data *rc; + struct resource *res; + u32 fcclk, firc; + int ret; + + rc = devm_kzalloc(&pdev->dev, sizeof(*rc), GFP_KERNEL); + if (!rc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rc->base)) + return PTR_ERR(rc->base); + + rc->clk_reg = devm_clk_get(&pdev->dev, "reg"); + if (IS_ERR(rc->clk_reg)) { + dev_err(&pdev->dev, "reg clock not found\n"); + return PTR_ERR(rc->clk_reg); + } + + rc->clk_delay = devm_clk_get(&pdev->dev, "delay"); + if (IS_ERR(rc->clk_delay)) { + dev_err(&pdev->dev, "delay clock not found\n"); + return PTR_ERR(rc->clk_delay); + } + + ret = clk_prepare_enable(rc->clk_reg); + if (ret) { + dev_err(&pdev->dev, "unable to enable reg clock\n"); + return ret; + } + + ret = clk_prepare_enable(rc->clk_delay); + if (ret) { + dev_err(&pdev->dev, "unable to enable delay clock\n"); + goto dis_clk_reg; + } + + fcclk = clk_get_rate(rc->clk_reg) / USEC_PER_SEC; + firc = clk_get_rate(rc->clk_delay) / USEC_PER_SEC; + if (fcclk == 0 || firc == 0) + rc->delay_us = 2; + else + rc->delay_us = DIV_ROUND_UP(fcclk, firc * firc); + + spin_lock_init(&rc->lock); + + rc->rcdev.owner = THIS_MODULE; + rc->rcdev.nr_resets = 64; + rc->rcdev.ops = &lpc18xx_rgu_ops; + rc->rcdev.of_node = pdev->dev.of_node; + + platform_set_drvdata(pdev, rc); + + ret = reset_controller_register(&rc->rcdev); + if (ret) { + dev_err(&pdev->dev, "unable to register device\n"); + goto dis_clks; + } + + rc->restart_nb.priority = 192, + rc->restart_nb.notifier_call = lpc18xx_rgu_restart, + ret = register_restart_handler(&rc->restart_nb); + if (ret) + dev_warn(&pdev->dev, "failed to register restart handler\n"); + + return 0; + +dis_clks: + clk_disable_unprepare(rc->clk_delay); +dis_clk_reg: + clk_disable_unprepare(rc->clk_reg); + + return ret; +} + +static const struct of_device_id lpc18xx_rgu_match[] = { + { .compatible = "nxp,lpc1850-rgu" }, + { } +}; + +static struct platform_driver lpc18xx_rgu_driver = { + .probe = lpc18xx_rgu_probe, + .driver = { + .name = "lpc18xx-reset", + .of_match_table = lpc18xx_rgu_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(lpc18xx_rgu_driver); diff --git a/drivers/reset/reset-meson-audio-arb.c b/drivers/reset/reset-meson-audio-arb.c new file mode 100644 index 000000000..1dc06e08a --- /dev/null +++ b/drivers/reset/reset-meson-audio-arb.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// Copyright (c) 2018 BayLibre, SAS. +// Author: Jerome Brunet <jbrunet@baylibre.com> + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/reset-controller.h> +#include <linux/spinlock.h> + +#include <dt-bindings/reset/amlogic,meson-axg-audio-arb.h> + +struct meson_audio_arb_data { + struct reset_controller_dev rstc; + void __iomem *regs; + struct clk *clk; + const unsigned int *reset_bits; + spinlock_t lock; +}; + +struct meson_audio_arb_match_data { + const unsigned int *reset_bits; + unsigned int reset_num; +}; + +#define ARB_GENERAL_BIT 31 + +static const unsigned int axg_audio_arb_reset_bits[] = { + [AXG_ARB_TODDR_A] = 0, + [AXG_ARB_TODDR_B] = 1, + [AXG_ARB_TODDR_C] = 2, + [AXG_ARB_FRDDR_A] = 4, + [AXG_ARB_FRDDR_B] = 5, + [AXG_ARB_FRDDR_C] = 6, +}; + +static const struct meson_audio_arb_match_data axg_audio_arb_match = { + .reset_bits = axg_audio_arb_reset_bits, + .reset_num = ARRAY_SIZE(axg_audio_arb_reset_bits), +}; + +static const unsigned int sm1_audio_arb_reset_bits[] = { + [AXG_ARB_TODDR_A] = 0, + [AXG_ARB_TODDR_B] = 1, + [AXG_ARB_TODDR_C] = 2, + [AXG_ARB_FRDDR_A] = 4, + [AXG_ARB_FRDDR_B] = 5, + [AXG_ARB_FRDDR_C] = 6, + [AXG_ARB_TODDR_D] = 3, + [AXG_ARB_FRDDR_D] = 7, +}; + +static const struct meson_audio_arb_match_data sm1_audio_arb_match = { + .reset_bits = sm1_audio_arb_reset_bits, + .reset_num = ARRAY_SIZE(sm1_audio_arb_reset_bits), +}; + +static int meson_audio_arb_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + u32 val; + struct meson_audio_arb_data *arb = + container_of(rcdev, struct meson_audio_arb_data, rstc); + + spin_lock(&arb->lock); + val = readl(arb->regs); + + if (assert) + val &= ~BIT(arb->reset_bits[id]); + else + val |= BIT(arb->reset_bits[id]); + + writel(val, arb->regs); + spin_unlock(&arb->lock); + + return 0; +} + +static int meson_audio_arb_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + u32 val; + struct meson_audio_arb_data *arb = + container_of(rcdev, struct meson_audio_arb_data, rstc); + + val = readl(arb->regs); + + return !(val & BIT(arb->reset_bits[id])); +} + +static int meson_audio_arb_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return meson_audio_arb_update(rcdev, id, true); +} + +static int meson_audio_arb_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return meson_audio_arb_update(rcdev, id, false); +} + +static const struct reset_control_ops meson_audio_arb_rstc_ops = { + .assert = meson_audio_arb_assert, + .deassert = meson_audio_arb_deassert, + .status = meson_audio_arb_status, +}; + +static const struct of_device_id meson_audio_arb_of_match[] = { + { + .compatible = "amlogic,meson-axg-audio-arb", + .data = &axg_audio_arb_match, + }, { + .compatible = "amlogic,meson-sm1-audio-arb", + .data = &sm1_audio_arb_match, + }, + {} +}; +MODULE_DEVICE_TABLE(of, meson_audio_arb_of_match); + +static int meson_audio_arb_remove(struct platform_device *pdev) +{ + struct meson_audio_arb_data *arb = platform_get_drvdata(pdev); + + /* Disable all access */ + spin_lock(&arb->lock); + writel(0, arb->regs); + spin_unlock(&arb->lock); + + clk_disable_unprepare(arb->clk); + + return 0; +} + +static int meson_audio_arb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct meson_audio_arb_match_data *data; + struct meson_audio_arb_data *arb; + struct resource *res; + int ret; + + data = of_device_get_match_data(dev); + if (!data) + return -EINVAL; + + arb = devm_kzalloc(dev, sizeof(*arb), GFP_KERNEL); + if (!arb) + return -ENOMEM; + platform_set_drvdata(pdev, arb); + + arb->clk = devm_clk_get(dev, NULL); + if (IS_ERR(arb->clk)) { + if (PTR_ERR(arb->clk) != -EPROBE_DEFER) + dev_err(dev, "failed to get clock\n"); + return PTR_ERR(arb->clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + arb->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(arb->regs)) + return PTR_ERR(arb->regs); + + spin_lock_init(&arb->lock); + arb->reset_bits = data->reset_bits; + arb->rstc.nr_resets = data->reset_num; + arb->rstc.ops = &meson_audio_arb_rstc_ops; + arb->rstc.of_node = dev->of_node; + arb->rstc.owner = THIS_MODULE; + + /* + * Enable general : + * In the initial state, all memory interfaces are disabled + * and the general bit is on + */ + ret = clk_prepare_enable(arb->clk); + if (ret) { + dev_err(dev, "failed to enable arb clock\n"); + return ret; + } + writel(BIT(ARB_GENERAL_BIT), arb->regs); + + /* Register reset controller */ + ret = devm_reset_controller_register(dev, &arb->rstc); + if (ret) { + dev_err(dev, "failed to register arb reset controller\n"); + meson_audio_arb_remove(pdev); + } + + return ret; +} + +static struct platform_driver meson_audio_arb_pdrv = { + .probe = meson_audio_arb_probe, + .remove = meson_audio_arb_remove, + .driver = { + .name = "meson-audio-arb-reset", + .of_match_table = meson_audio_arb_of_match, + }, +}; +module_platform_driver(meson_audio_arb_pdrv); + +MODULE_DESCRIPTION("Amlogic A113 Audio Memory Arbiter"); +MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-meson.c b/drivers/reset/reset-meson.c new file mode 100644 index 000000000..26dc54778 --- /dev/null +++ b/drivers/reset/reset-meson.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Amlogic Meson Reset Controller driver + * + * Copyright (c) 2016 BayLibre, SAS. + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/of_device.h> + +#define BITS_PER_REG 32 + +struct meson_reset_param { + int reg_count; + int level_offset; +}; + +struct meson_reset { + void __iomem *reg_base; + const struct meson_reset_param *param; + struct reset_controller_dev rcdev; + spinlock_t lock; +}; + +static int meson_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct meson_reset *data = + container_of(rcdev, struct meson_reset, rcdev); + unsigned int bank = id / BITS_PER_REG; + unsigned int offset = id % BITS_PER_REG; + void __iomem *reg_addr = data->reg_base + (bank << 2); + + writel(BIT(offset), reg_addr); + + return 0; +} + +static int meson_reset_level(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct meson_reset *data = + container_of(rcdev, struct meson_reset, rcdev); + unsigned int bank = id / BITS_PER_REG; + unsigned int offset = id % BITS_PER_REG; + void __iomem *reg_addr; + unsigned long flags; + u32 reg; + + reg_addr = data->reg_base + data->param->level_offset + (bank << 2); + + spin_lock_irqsave(&data->lock, flags); + + reg = readl(reg_addr); + if (assert) + writel(reg & ~BIT(offset), reg_addr); + else + writel(reg | BIT(offset), reg_addr); + + spin_unlock_irqrestore(&data->lock, flags); + + return 0; +} + +static int meson_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return meson_reset_level(rcdev, id, true); +} + +static int meson_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return meson_reset_level(rcdev, id, false); +} + +static const struct reset_control_ops meson_reset_ops = { + .reset = meson_reset_reset, + .assert = meson_reset_assert, + .deassert = meson_reset_deassert, +}; + +static const struct meson_reset_param meson8b_param = { + .reg_count = 8, + .level_offset = 0x7c, +}; + +static const struct meson_reset_param meson_a1_param = { + .reg_count = 3, + .level_offset = 0x40, +}; + +static const struct meson_reset_param meson_s4_param = { + .reg_count = 6, + .level_offset = 0x40, +}; + +static const struct of_device_id meson_reset_dt_ids[] = { + { .compatible = "amlogic,meson8b-reset", .data = &meson8b_param}, + { .compatible = "amlogic,meson-gxbb-reset", .data = &meson8b_param}, + { .compatible = "amlogic,meson-axg-reset", .data = &meson8b_param}, + { .compatible = "amlogic,meson-a1-reset", .data = &meson_a1_param}, + { .compatible = "amlogic,meson-s4-reset", .data = &meson_s4_param}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, meson_reset_dt_ids); + +static int meson_reset_probe(struct platform_device *pdev) +{ + struct meson_reset *data; + struct resource *res; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->reg_base)) + return PTR_ERR(data->reg_base); + + data->param = of_device_get_match_data(&pdev->dev); + if (!data->param) + return -ENODEV; + + platform_set_drvdata(pdev, data); + + spin_lock_init(&data->lock); + + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = data->param->reg_count * BITS_PER_REG; + data->rcdev.ops = &meson_reset_ops; + data->rcdev.of_node = pdev->dev.of_node; + + return devm_reset_controller_register(&pdev->dev, &data->rcdev); +} + +static struct platform_driver meson_reset_driver = { + .probe = meson_reset_probe, + .driver = { + .name = "meson_reset", + .of_match_table = meson_reset_dt_ids, + }, +}; +module_platform_driver(meson_reset_driver); + +MODULE_DESCRIPTION("Amlogic Meson Reset Controller driver"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/reset/reset-microchip-sparx5.c b/drivers/reset/reset-microchip-sparx5.c new file mode 100644 index 000000000..f3528dd1d --- /dev/null +++ b/drivers/reset/reset-microchip-sparx5.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Microchip Sparx5 Switch Reset driver + * + * Copyright (c) 2020 Microchip Technology Inc. and its subsidiaries. + * + * The Sparx5 Chip Register Model can be browsed at this location: + * https://github.com/microchip-ung/sparx-5_reginfo + */ +#include <linux/mfd/syscon.h> +#include <linux/of_device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +struct reset_props { + u32 protect_reg; + u32 protect_bit; + u32 reset_reg; + u32 reset_bit; +}; + +struct mchp_reset_context { + struct regmap *cpu_ctrl; + struct regmap *gcb_ctrl; + struct reset_controller_dev rcdev; + const struct reset_props *props; +}; + +static struct regmap_config sparx5_reset_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int sparx5_switch_reset(struct mchp_reset_context *ctx) +{ + u32 val; + + /* Make sure the core is PROTECTED from reset */ + regmap_update_bits(ctx->cpu_ctrl, ctx->props->protect_reg, + ctx->props->protect_bit, ctx->props->protect_bit); + + /* Start soft reset */ + regmap_write(ctx->gcb_ctrl, ctx->props->reset_reg, + ctx->props->reset_bit); + + /* Wait for soft reset done */ + return regmap_read_poll_timeout(ctx->gcb_ctrl, ctx->props->reset_reg, val, + (val & ctx->props->reset_bit) == 0, + 1, 100); +} + +static int sparx5_reset_noop(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return 0; +} + +static const struct reset_control_ops sparx5_reset_ops = { + .reset = sparx5_reset_noop, +}; + +static int mchp_sparx5_map_syscon(struct platform_device *pdev, char *name, + struct regmap **target) +{ + struct device_node *syscon_np; + struct regmap *regmap; + int err; + + syscon_np = of_parse_phandle(pdev->dev.of_node, name, 0); + if (!syscon_np) + return -ENODEV; + regmap = syscon_node_to_regmap(syscon_np); + of_node_put(syscon_np); + if (IS_ERR(regmap)) { + err = PTR_ERR(regmap); + dev_err(&pdev->dev, "No '%s' map: %d\n", name, err); + return err; + } + *target = regmap; + return 0; +} + +static int mchp_sparx5_map_io(struct platform_device *pdev, int index, + struct regmap **target) +{ + struct resource *res; + struct regmap *map; + void __iomem *mem; + + mem = devm_platform_get_and_ioremap_resource(pdev, index, &res); + if (IS_ERR(mem)) { + dev_err(&pdev->dev, "Could not map resource %d\n", index); + return PTR_ERR(mem); + } + sparx5_reset_regmap_config.name = res->name; + map = devm_regmap_init_mmio(&pdev->dev, mem, &sparx5_reset_regmap_config); + if (IS_ERR(map)) + return PTR_ERR(map); + *target = map; + return 0; +} + +static int mchp_sparx5_reset_probe(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct mchp_reset_context *ctx; + int err; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + err = mchp_sparx5_map_syscon(pdev, "cpu-syscon", &ctx->cpu_ctrl); + if (err) + return err; + err = mchp_sparx5_map_io(pdev, 0, &ctx->gcb_ctrl); + if (err) + return err; + + ctx->rcdev.owner = THIS_MODULE; + ctx->rcdev.nr_resets = 1; + ctx->rcdev.ops = &sparx5_reset_ops; + ctx->rcdev.of_node = dn; + ctx->props = device_get_match_data(&pdev->dev); + + /* Issue the reset very early, our actual reset callback is a noop. */ + err = sparx5_switch_reset(ctx); + if (err) + return err; + + return devm_reset_controller_register(&pdev->dev, &ctx->rcdev); +} + +static const struct reset_props reset_props_sparx5 = { + .protect_reg = 0x84, + .protect_bit = BIT(10), + .reset_reg = 0x0, + .reset_bit = BIT(1), +}; + +static const struct reset_props reset_props_lan966x = { + .protect_reg = 0x88, + .protect_bit = BIT(5), + .reset_reg = 0x0, + .reset_bit = BIT(1), +}; + +static const struct of_device_id mchp_sparx5_reset_of_match[] = { + { + .compatible = "microchip,sparx5-switch-reset", + .data = &reset_props_sparx5, + }, { + .compatible = "microchip,lan966x-switch-reset", + .data = &reset_props_lan966x, + }, + { } +}; + +static struct platform_driver mchp_sparx5_reset_driver = { + .probe = mchp_sparx5_reset_probe, + .driver = { + .name = "sparx5-switch-reset", + .of_match_table = mchp_sparx5_reset_of_match, + }, +}; + +static int __init mchp_sparx5_reset_init(void) +{ + return platform_driver_register(&mchp_sparx5_reset_driver); +} + +/* + * Because this is a global reset, keep this postcore_initcall() to issue the + * reset as early as possible during the kernel startup. + */ +postcore_initcall(mchp_sparx5_reset_init); + +MODULE_DESCRIPTION("Microchip Sparx5 switch reset driver"); +MODULE_AUTHOR("Steen Hegelund <steen.hegelund@microchip.com>"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/reset/reset-mpfs.c b/drivers/reset/reset-mpfs.c new file mode 100644 index 000000000..e003e5059 --- /dev/null +++ b/drivers/reset/reset-mpfs.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PolarFire SoC (MPFS) Peripheral Clock Reset Controller + * + * Author: Conor Dooley <conor.dooley@microchip.com> + * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries. + * + */ +#include <linux/auxiliary_bus.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <dt-bindings/clock/microchip,mpfs-clock.h> +#include <soc/microchip/mpfs.h> + +/* + * The ENVM reset is the lowest bit in the register & I am using the CLK_FOO + * defines in the dt to make things easier to configure - so this is accounting + * for the offset of 3 there. + */ +#define MPFS_PERIPH_OFFSET CLK_ENVM +#define MPFS_NUM_RESETS 30u +#define MPFS_SLEEP_MIN_US 100 +#define MPFS_SLEEP_MAX_US 200 + +/* block concurrent access to the soft reset register */ +static DEFINE_SPINLOCK(mpfs_reset_lock); + +/* + * Peripheral clock resets + */ + +static int mpfs_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&mpfs_reset_lock, flags); + + reg = mpfs_reset_read(rcdev->dev); + reg |= BIT(id); + mpfs_reset_write(rcdev->dev, reg); + + spin_unlock_irqrestore(&mpfs_reset_lock, flags); + + return 0; +} + +static int mpfs_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&mpfs_reset_lock, flags); + + reg = mpfs_reset_read(rcdev->dev); + reg &= ~BIT(id); + mpfs_reset_write(rcdev->dev, reg); + + spin_unlock_irqrestore(&mpfs_reset_lock, flags); + + return 0; +} + +static int mpfs_status(struct reset_controller_dev *rcdev, unsigned long id) +{ + u32 reg = mpfs_reset_read(rcdev->dev); + + /* + * It is safe to return here as MPFS_NUM_RESETS makes sure the sign bit + * is never hit. + */ + return (reg & BIT(id)); +} + +static int mpfs_reset(struct reset_controller_dev *rcdev, unsigned long id) +{ + mpfs_assert(rcdev, id); + + usleep_range(MPFS_SLEEP_MIN_US, MPFS_SLEEP_MAX_US); + + mpfs_deassert(rcdev, id); + + return 0; +} + +static const struct reset_control_ops mpfs_reset_ops = { + .reset = mpfs_reset, + .assert = mpfs_assert, + .deassert = mpfs_deassert, + .status = mpfs_status, +}; + +static int mpfs_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + unsigned int index = reset_spec->args[0]; + + /* + * CLK_RESERVED does not map to a clock, but it does map to a reset, + * so it has to be accounted for here. It is the reset for the fabric, + * so if this reset gets called - do not reset it. + */ + if (index == CLK_RESERVED) { + dev_err(rcdev->dev, "Resetting the fabric is not supported\n"); + return -EINVAL; + } + + if (index < MPFS_PERIPH_OFFSET || index >= (MPFS_PERIPH_OFFSET + rcdev->nr_resets)) { + dev_err(rcdev->dev, "Invalid reset index %u\n", index); + return -EINVAL; + } + + return index - MPFS_PERIPH_OFFSET; +} + +static int mpfs_reset_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct device *dev = &adev->dev; + struct reset_controller_dev *rcdev; + + rcdev = devm_kzalloc(dev, sizeof(*rcdev), GFP_KERNEL); + if (!rcdev) + return -ENOMEM; + + rcdev->dev = dev; + rcdev->dev->parent = dev->parent; + rcdev->ops = &mpfs_reset_ops; + rcdev->of_node = dev->parent->of_node; + rcdev->of_reset_n_cells = 1; + rcdev->of_xlate = mpfs_reset_xlate; + rcdev->nr_resets = MPFS_NUM_RESETS; + + return devm_reset_controller_register(dev, rcdev); +} + +static const struct auxiliary_device_id mpfs_reset_ids[] = { + { + .name = "clk_mpfs.reset-mpfs", + }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, mpfs_reset_ids); + +static struct auxiliary_driver mpfs_reset_driver = { + .probe = mpfs_reset_probe, + .id_table = mpfs_reset_ids, +}; + +module_auxiliary_driver(mpfs_reset_driver); + +MODULE_DESCRIPTION("Microchip PolarFire SoC Reset Driver"); +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(MCHP_CLK_MPFS); diff --git a/drivers/reset/reset-npcm.c b/drivers/reset/reset-npcm.c new file mode 100644 index 000000000..f2333506b --- /dev/null +++ b/drivers/reset/reset-npcm.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Nuvoton Technology corporation. + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/reset-controller.h> +#include <linux/spinlock.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include <linux/of_address.h> + +/* NPCM7xx GCR registers */ +#define NPCM_MDLR_OFFSET 0x7C +#define NPCM7XX_MDLR_USBD0 BIT(9) +#define NPCM7XX_MDLR_USBD1 BIT(8) +#define NPCM7XX_MDLR_USBD2_4 BIT(21) +#define NPCM7XX_MDLR_USBD5_9 BIT(22) + +/* NPCM8xx MDLR bits */ +#define NPCM8XX_MDLR_USBD0_3 BIT(9) +#define NPCM8XX_MDLR_USBD4_7 BIT(22) +#define NPCM8XX_MDLR_USBD8 BIT(24) +#define NPCM8XX_MDLR_USBD9 BIT(21) + +#define NPCM_USB1PHYCTL_OFFSET 0x140 +#define NPCM_USB2PHYCTL_OFFSET 0x144 +#define NPCM_USB3PHYCTL_OFFSET 0x148 +#define NPCM_USBXPHYCTL_RS BIT(28) + +/* NPCM7xx Reset registers */ +#define NPCM_SWRSTR 0x14 +#define NPCM_SWRST BIT(2) + +#define NPCM_IPSRST1 0x20 +#define NPCM_IPSRST1_USBD1 BIT(5) +#define NPCM_IPSRST1_USBD2 BIT(8) +#define NPCM_IPSRST1_USBD3 BIT(25) +#define NPCM_IPSRST1_USBD4 BIT(22) +#define NPCM_IPSRST1_USBD5 BIT(23) +#define NPCM_IPSRST1_USBD6 BIT(24) + +#define NPCM_IPSRST2 0x24 +#define NPCM_IPSRST2_USB_HOST BIT(26) + +#define NPCM_IPSRST3 0x34 +#define NPCM_IPSRST3_USBD0 BIT(4) +#define NPCM_IPSRST3_USBD7 BIT(5) +#define NPCM_IPSRST3_USBD8 BIT(6) +#define NPCM_IPSRST3_USBD9 BIT(7) +#define NPCM_IPSRST3_USBPHY1 BIT(24) +#define NPCM_IPSRST3_USBPHY2 BIT(25) + +#define NPCM_IPSRST4 0x74 +#define NPCM_IPSRST4_USBPHY3 BIT(25) +#define NPCM_IPSRST4_USB_HOST2 BIT(31) + +#define NPCM_RC_RESETS_PER_REG 32 +#define NPCM_MASK_RESETS GENMASK(4, 0) + +enum { + BMC_NPCM7XX = 0, + BMC_NPCM8XX, +}; + +static const u32 npxm7xx_ipsrst[] = {NPCM_IPSRST1, NPCM_IPSRST2, NPCM_IPSRST3}; +static const u32 npxm8xx_ipsrst[] = {NPCM_IPSRST1, NPCM_IPSRST2, NPCM_IPSRST3, + NPCM_IPSRST4}; + +struct npcm_reset_info { + u32 bmc_id; + u32 num_ipsrst; + const u32 *ipsrst; +}; + +static const struct npcm_reset_info npxm7xx_reset_info[] = { + {.bmc_id = BMC_NPCM7XX, .num_ipsrst = 3, .ipsrst = npxm7xx_ipsrst}}; +static const struct npcm_reset_info npxm8xx_reset_info[] = { + {.bmc_id = BMC_NPCM8XX, .num_ipsrst = 4, .ipsrst = npxm8xx_ipsrst}}; + +struct npcm_rc_data { + struct reset_controller_dev rcdev; + struct notifier_block restart_nb; + const struct npcm_reset_info *info; + struct regmap *gcr_regmap; + u32 sw_reset_number; + void __iomem *base; + spinlock_t lock; +}; + +#define to_rc_data(p) container_of(p, struct npcm_rc_data, rcdev) + +static int npcm_rc_restart(struct notifier_block *nb, unsigned long mode, + void *cmd) +{ + struct npcm_rc_data *rc = container_of(nb, struct npcm_rc_data, + restart_nb); + + writel(NPCM_SWRST << rc->sw_reset_number, rc->base + NPCM_SWRSTR); + mdelay(1000); + + pr_emerg("%s: unable to restart system\n", __func__); + + return NOTIFY_DONE; +} + +static int npcm_rc_setclear_reset(struct reset_controller_dev *rcdev, + unsigned long id, bool set) +{ + struct npcm_rc_data *rc = to_rc_data(rcdev); + unsigned int rst_bit = BIT(id & NPCM_MASK_RESETS); + unsigned int ctrl_offset = id >> 8; + unsigned long flags; + u32 stat; + + spin_lock_irqsave(&rc->lock, flags); + stat = readl(rc->base + ctrl_offset); + if (set) + writel(stat | rst_bit, rc->base + ctrl_offset); + else + writel(stat & ~rst_bit, rc->base + ctrl_offset); + spin_unlock_irqrestore(&rc->lock, flags); + + return 0; +} + +static int npcm_rc_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + return npcm_rc_setclear_reset(rcdev, id, true); +} + +static int npcm_rc_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return npcm_rc_setclear_reset(rcdev, id, false); +} + +static int npcm_rc_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct npcm_rc_data *rc = to_rc_data(rcdev); + unsigned int rst_bit = BIT(id & NPCM_MASK_RESETS); + unsigned int ctrl_offset = id >> 8; + + return (readl(rc->base + ctrl_offset) & rst_bit); +} + +static int npcm_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + struct npcm_rc_data *rc = to_rc_data(rcdev); + unsigned int offset, bit; + bool offset_found = false; + int off_num; + + offset = reset_spec->args[0]; + for (off_num = 0 ; off_num < rc->info->num_ipsrst ; off_num++) { + if (offset == rc->info->ipsrst[off_num]) { + offset_found = true; + break; + } + } + + if (!offset_found) { + dev_err(rcdev->dev, "Error reset register (0x%x)\n", offset); + return -EINVAL; + } + + bit = reset_spec->args[1]; + if (bit >= NPCM_RC_RESETS_PER_REG) { + dev_err(rcdev->dev, "Error reset number (%d)\n", bit); + return -EINVAL; + } + + return (offset << 8) | bit; +} + +static const struct of_device_id npcm_rc_match[] = { + { .compatible = "nuvoton,npcm750-reset", .data = &npxm7xx_reset_info}, + { .compatible = "nuvoton,npcm845-reset", .data = &npxm8xx_reset_info}, + { } +}; + +static void npcm_usb_reset_npcm7xx(struct npcm_rc_data *rc) +{ + u32 mdlr, iprst1, iprst2, iprst3; + u32 ipsrst1_bits = 0; + u32 ipsrst2_bits = NPCM_IPSRST2_USB_HOST; + u32 ipsrst3_bits = 0; + + /* checking which USB device is enabled */ + regmap_read(rc->gcr_regmap, NPCM_MDLR_OFFSET, &mdlr); + if (!(mdlr & NPCM7XX_MDLR_USBD0)) + ipsrst3_bits |= NPCM_IPSRST3_USBD0; + if (!(mdlr & NPCM7XX_MDLR_USBD1)) + ipsrst1_bits |= NPCM_IPSRST1_USBD1; + if (!(mdlr & NPCM7XX_MDLR_USBD2_4)) + ipsrst1_bits |= (NPCM_IPSRST1_USBD2 | + NPCM_IPSRST1_USBD3 | + NPCM_IPSRST1_USBD4); + if (!(mdlr & NPCM7XX_MDLR_USBD0)) { + ipsrst1_bits |= (NPCM_IPSRST1_USBD5 | + NPCM_IPSRST1_USBD6); + ipsrst3_bits |= (NPCM_IPSRST3_USBD7 | + NPCM_IPSRST3_USBD8 | + NPCM_IPSRST3_USBD9); + } + + /* assert reset USB PHY and USB devices */ + iprst1 = readl(rc->base + NPCM_IPSRST1); + iprst2 = readl(rc->base + NPCM_IPSRST2); + iprst3 = readl(rc->base + NPCM_IPSRST3); + + iprst1 |= ipsrst1_bits; + iprst2 |= ipsrst2_bits; + iprst3 |= (ipsrst3_bits | NPCM_IPSRST3_USBPHY1 | + NPCM_IPSRST3_USBPHY2); + + writel(iprst1, rc->base + NPCM_IPSRST1); + writel(iprst2, rc->base + NPCM_IPSRST2); + writel(iprst3, rc->base + NPCM_IPSRST3); + + /* clear USB PHY RS bit */ + regmap_update_bits(rc->gcr_regmap, NPCM_USB1PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, 0); + regmap_update_bits(rc->gcr_regmap, NPCM_USB2PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, 0); + + /* deassert reset USB PHY */ + iprst3 &= ~(NPCM_IPSRST3_USBPHY1 | NPCM_IPSRST3_USBPHY2); + writel(iprst3, rc->base + NPCM_IPSRST3); + + udelay(50); + + /* set USB PHY RS bit */ + regmap_update_bits(rc->gcr_regmap, NPCM_USB1PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, NPCM_USBXPHYCTL_RS); + regmap_update_bits(rc->gcr_regmap, NPCM_USB2PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, NPCM_USBXPHYCTL_RS); + + /* deassert reset USB devices*/ + iprst1 &= ~ipsrst1_bits; + iprst2 &= ~ipsrst2_bits; + iprst3 &= ~ipsrst3_bits; + + writel(iprst1, rc->base + NPCM_IPSRST1); + writel(iprst2, rc->base + NPCM_IPSRST2); + writel(iprst3, rc->base + NPCM_IPSRST3); +} + +static void npcm_usb_reset_npcm8xx(struct npcm_rc_data *rc) +{ + u32 mdlr, iprst1, iprst2, iprst3, iprst4; + u32 ipsrst1_bits = 0; + u32 ipsrst2_bits = NPCM_IPSRST2_USB_HOST; + u32 ipsrst3_bits = 0; + u32 ipsrst4_bits = NPCM_IPSRST4_USB_HOST2 | NPCM_IPSRST4_USBPHY3; + + /* checking which USB device is enabled */ + regmap_read(rc->gcr_regmap, NPCM_MDLR_OFFSET, &mdlr); + if (!(mdlr & NPCM8XX_MDLR_USBD0_3)) { + ipsrst3_bits |= NPCM_IPSRST3_USBD0; + ipsrst1_bits |= (NPCM_IPSRST1_USBD1 | + NPCM_IPSRST1_USBD2 | + NPCM_IPSRST1_USBD3); + } + if (!(mdlr & NPCM8XX_MDLR_USBD4_7)) { + ipsrst1_bits |= (NPCM_IPSRST1_USBD4 | + NPCM_IPSRST1_USBD5 | + NPCM_IPSRST1_USBD6); + ipsrst3_bits |= NPCM_IPSRST3_USBD7; + } + + if (!(mdlr & NPCM8XX_MDLR_USBD8)) + ipsrst3_bits |= NPCM_IPSRST3_USBD8; + if (!(mdlr & NPCM8XX_MDLR_USBD9)) + ipsrst3_bits |= NPCM_IPSRST3_USBD9; + + /* assert reset USB PHY and USB devices */ + iprst1 = readl(rc->base + NPCM_IPSRST1); + iprst2 = readl(rc->base + NPCM_IPSRST2); + iprst3 = readl(rc->base + NPCM_IPSRST3); + iprst4 = readl(rc->base + NPCM_IPSRST4); + + iprst1 |= ipsrst1_bits; + iprst2 |= ipsrst2_bits; + iprst3 |= (ipsrst3_bits | NPCM_IPSRST3_USBPHY1 | + NPCM_IPSRST3_USBPHY2); + iprst4 |= ipsrst4_bits; + + writel(iprst1, rc->base + NPCM_IPSRST1); + writel(iprst2, rc->base + NPCM_IPSRST2); + writel(iprst3, rc->base + NPCM_IPSRST3); + writel(iprst4, rc->base + NPCM_IPSRST4); + + /* clear USB PHY RS bit */ + regmap_update_bits(rc->gcr_regmap, NPCM_USB1PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, 0); + regmap_update_bits(rc->gcr_regmap, NPCM_USB2PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, 0); + regmap_update_bits(rc->gcr_regmap, NPCM_USB3PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, 0); + + /* deassert reset USB PHY */ + iprst3 &= ~(NPCM_IPSRST3_USBPHY1 | NPCM_IPSRST3_USBPHY2); + writel(iprst3, rc->base + NPCM_IPSRST3); + iprst4 &= ~NPCM_IPSRST4_USBPHY3; + writel(iprst4, rc->base + NPCM_IPSRST4); + + /* set USB PHY RS bit */ + regmap_update_bits(rc->gcr_regmap, NPCM_USB1PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, NPCM_USBXPHYCTL_RS); + regmap_update_bits(rc->gcr_regmap, NPCM_USB2PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, NPCM_USBXPHYCTL_RS); + regmap_update_bits(rc->gcr_regmap, NPCM_USB3PHYCTL_OFFSET, + NPCM_USBXPHYCTL_RS, NPCM_USBXPHYCTL_RS); + + /* deassert reset USB devices*/ + iprst1 &= ~ipsrst1_bits; + iprst2 &= ~ipsrst2_bits; + iprst3 &= ~ipsrst3_bits; + iprst4 &= ~ipsrst4_bits; + + writel(iprst1, rc->base + NPCM_IPSRST1); + writel(iprst2, rc->base + NPCM_IPSRST2); + writel(iprst3, rc->base + NPCM_IPSRST3); + writel(iprst4, rc->base + NPCM_IPSRST4); +} + +/* + * The following procedure should be observed in USB PHY, USB device and + * USB host initialization at BMC boot + */ +static int npcm_usb_reset(struct platform_device *pdev, struct npcm_rc_data *rc) +{ + struct device *dev = &pdev->dev; + + rc->gcr_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "nuvoton,sysgcr"); + if (IS_ERR(rc->gcr_regmap)) { + dev_warn(&pdev->dev, "Failed to find nuvoton,sysgcr property, please update the device tree\n"); + dev_info(&pdev->dev, "Using nuvoton,npcm750-gcr for Poleg backward compatibility\n"); + rc->gcr_regmap = syscon_regmap_lookup_by_compatible("nuvoton,npcm750-gcr"); + if (IS_ERR(rc->gcr_regmap)) { + dev_err(&pdev->dev, "Failed to find nuvoton,npcm750-gcr"); + return PTR_ERR(rc->gcr_regmap); + } + } + + rc->info = (const struct npcm_reset_info *) + of_match_device(dev->driver->of_match_table, dev)->data; + switch (rc->info->bmc_id) { + case BMC_NPCM7XX: + npcm_usb_reset_npcm7xx(rc); + break; + case BMC_NPCM8XX: + npcm_usb_reset_npcm8xx(rc); + break; + default: + return -ENODEV; + } + + return 0; +} + +static const struct reset_control_ops npcm_rc_ops = { + .assert = npcm_rc_assert, + .deassert = npcm_rc_deassert, + .status = npcm_rc_status, +}; + +static int npcm_rc_probe(struct platform_device *pdev) +{ + struct npcm_rc_data *rc; + int ret; + + rc = devm_kzalloc(&pdev->dev, sizeof(*rc), GFP_KERNEL); + if (!rc) + return -ENOMEM; + + rc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rc->base)) + return PTR_ERR(rc->base); + + spin_lock_init(&rc->lock); + + rc->rcdev.owner = THIS_MODULE; + rc->rcdev.ops = &npcm_rc_ops; + rc->rcdev.of_node = pdev->dev.of_node; + rc->rcdev.of_reset_n_cells = 2; + rc->rcdev.of_xlate = npcm_reset_xlate; + + platform_set_drvdata(pdev, rc); + + ret = devm_reset_controller_register(&pdev->dev, &rc->rcdev); + if (ret) { + dev_err(&pdev->dev, "unable to register device\n"); + return ret; + } + + if (npcm_usb_reset(pdev, rc)) + dev_warn(&pdev->dev, "NPCM USB reset failed, can cause issues with UDC and USB host\n"); + + if (!of_property_read_u32(pdev->dev.of_node, "nuvoton,sw-reset-number", + &rc->sw_reset_number)) { + if (rc->sw_reset_number && rc->sw_reset_number < 5) { + rc->restart_nb.priority = 192, + rc->restart_nb.notifier_call = npcm_rc_restart, + ret = register_restart_handler(&rc->restart_nb); + if (ret) + dev_warn(&pdev->dev, "failed to register restart handler\n"); + } + } + + return ret; +} + +static struct platform_driver npcm_rc_driver = { + .probe = npcm_rc_probe, + .driver = { + .name = "npcm-reset", + .of_match_table = npcm_rc_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(npcm_rc_driver); diff --git a/drivers/reset/reset-oxnas.c b/drivers/reset/reset-oxnas.c new file mode 100644 index 000000000..8209f922d --- /dev/null +++ b/drivers/reset/reset-oxnas.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Oxford Semiconductor Reset Controller driver + * + * Copyright (C) 2016 Neil Armstrong <narmstrong@baylibre.com> + * Copyright (C) 2014 Ma Haijun <mahaijuns@gmail.com> + * Copyright (C) 2009 Oxford Semiconductor Ltd + */ +#include <linux/err.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +/* Regmap offsets */ +#define RST_SET_REGOFFSET 0x34 +#define RST_CLR_REGOFFSET 0x38 + +struct oxnas_reset { + struct regmap *regmap; + struct reset_controller_dev rcdev; +}; + +static int oxnas_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct oxnas_reset *data = + container_of(rcdev, struct oxnas_reset, rcdev); + + regmap_write(data->regmap, RST_SET_REGOFFSET, BIT(id)); + msleep(50); + regmap_write(data->regmap, RST_CLR_REGOFFSET, BIT(id)); + + return 0; +} + +static int oxnas_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct oxnas_reset *data = + container_of(rcdev, struct oxnas_reset, rcdev); + + regmap_write(data->regmap, RST_SET_REGOFFSET, BIT(id)); + + return 0; +} + +static int oxnas_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct oxnas_reset *data = + container_of(rcdev, struct oxnas_reset, rcdev); + + regmap_write(data->regmap, RST_CLR_REGOFFSET, BIT(id)); + + return 0; +} + +static const struct reset_control_ops oxnas_reset_ops = { + .reset = oxnas_reset_reset, + .assert = oxnas_reset_assert, + .deassert = oxnas_reset_deassert, +}; + +static const struct of_device_id oxnas_reset_dt_ids[] = { + { .compatible = "oxsemi,ox810se-reset", }, + { .compatible = "oxsemi,ox820-reset", }, + { /* sentinel */ }, +}; + +static int oxnas_reset_probe(struct platform_device *pdev) +{ + struct oxnas_reset *data; + struct device *parent; + + parent = pdev->dev.parent; + if (!parent) { + dev_err(&pdev->dev, "no parent\n"); + return -ENODEV; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = syscon_node_to_regmap(parent->of_node); + if (IS_ERR(data->regmap)) { + dev_err(&pdev->dev, "failed to get parent regmap\n"); + return PTR_ERR(data->regmap); + } + + platform_set_drvdata(pdev, data); + + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = 32; + data->rcdev.ops = &oxnas_reset_ops; + data->rcdev.of_node = pdev->dev.of_node; + + return devm_reset_controller_register(&pdev->dev, &data->rcdev); +} + +static struct platform_driver oxnas_reset_driver = { + .probe = oxnas_reset_probe, + .driver = { + .name = "oxnas-reset", + .of_match_table = oxnas_reset_dt_ids, + }, +}; +builtin_platform_driver(oxnas_reset_driver); diff --git a/drivers/reset/reset-pistachio.c b/drivers/reset/reset-pistachio.c new file mode 100644 index 000000000..151ceeb42 --- /dev/null +++ b/drivers/reset/reset-pistachio.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pistachio SoC Reset Controller driver + * + * Copyright (C) 2015 Imagination Technologies Ltd. + * + * Author: Damien Horsley <Damien.Horsley@imgtec.com> + */ + +#include <linux/init.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> +#include <linux/slab.h> +#include <linux/mfd/syscon.h> + +#include <dt-bindings/reset/pistachio-resets.h> + +#define PISTACHIO_SOFT_RESET 0 + +struct pistachio_reset_data { + struct reset_controller_dev rcdev; + struct regmap *periph_regs; +}; + +static inline int pistachio_reset_shift(unsigned long id) +{ + switch (id) { + case PISTACHIO_RESET_I2C0: + case PISTACHIO_RESET_I2C1: + case PISTACHIO_RESET_I2C2: + case PISTACHIO_RESET_I2C3: + case PISTACHIO_RESET_I2S_IN: + case PISTACHIO_RESET_PRL_OUT: + case PISTACHIO_RESET_SPDIF_OUT: + case PISTACHIO_RESET_SPI: + case PISTACHIO_RESET_PWM_PDM: + case PISTACHIO_RESET_UART0: + case PISTACHIO_RESET_UART1: + case PISTACHIO_RESET_QSPI: + case PISTACHIO_RESET_MDC: + case PISTACHIO_RESET_SDHOST: + case PISTACHIO_RESET_ETHERNET: + case PISTACHIO_RESET_IR: + case PISTACHIO_RESET_HASH: + case PISTACHIO_RESET_TIMER: + return id; + case PISTACHIO_RESET_I2S_OUT: + case PISTACHIO_RESET_SPDIF_IN: + case PISTACHIO_RESET_EVT: + return id + 6; + case PISTACHIO_RESET_USB_H: + case PISTACHIO_RESET_USB_PR: + case PISTACHIO_RESET_USB_PHY_PR: + case PISTACHIO_RESET_USB_PHY_PON: + return id + 7; + default: + return -EINVAL; + } +} + +static int pistachio_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct pistachio_reset_data *rd; + u32 mask; + int shift; + + rd = container_of(rcdev, struct pistachio_reset_data, rcdev); + shift = pistachio_reset_shift(id); + if (shift < 0) + return shift; + mask = BIT(shift); + + return regmap_update_bits(rd->periph_regs, PISTACHIO_SOFT_RESET, + mask, mask); +} + +static int pistachio_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct pistachio_reset_data *rd; + u32 mask; + int shift; + + rd = container_of(rcdev, struct pistachio_reset_data, rcdev); + shift = pistachio_reset_shift(id); + if (shift < 0) + return shift; + mask = BIT(shift); + + return regmap_update_bits(rd->periph_regs, PISTACHIO_SOFT_RESET, + mask, 0); +} + +static const struct reset_control_ops pistachio_reset_ops = { + .assert = pistachio_reset_assert, + .deassert = pistachio_reset_deassert, +}; + +static int pistachio_reset_probe(struct platform_device *pdev) +{ + struct pistachio_reset_data *rd; + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + + rd = devm_kzalloc(dev, sizeof(*rd), GFP_KERNEL); + if (!rd) + return -ENOMEM; + + rd->periph_regs = syscon_node_to_regmap(np->parent); + if (IS_ERR(rd->periph_regs)) + return PTR_ERR(rd->periph_regs); + + rd->rcdev.owner = THIS_MODULE; + rd->rcdev.nr_resets = PISTACHIO_RESET_MAX + 1; + rd->rcdev.ops = &pistachio_reset_ops; + rd->rcdev.of_node = np; + + return devm_reset_controller_register(dev, &rd->rcdev); +} + +static const struct of_device_id pistachio_reset_dt_ids[] = { + { .compatible = "img,pistachio-reset", }, + { /* sentinel */ }, +}; + +static struct platform_driver pistachio_reset_driver = { + .probe = pistachio_reset_probe, + .driver = { + .name = "pistachio-reset", + .of_match_table = pistachio_reset_dt_ids, + }, +}; +builtin_platform_driver(pistachio_reset_driver); diff --git a/drivers/reset/reset-qcom-aoss.c b/drivers/reset/reset-qcom-aoss.c new file mode 100644 index 000000000..9333b923d --- /dev/null +++ b/drivers/reset/reset-qcom-aoss.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 The Linux Foundation. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of_device.h> +#include <dt-bindings/reset/qcom,sdm845-aoss.h> + +struct qcom_aoss_reset_map { + unsigned int reg; +}; + +struct qcom_aoss_desc { + const struct qcom_aoss_reset_map *resets; + size_t num_resets; +}; + +struct qcom_aoss_reset_data { + struct reset_controller_dev rcdev; + void __iomem *base; + const struct qcom_aoss_desc *desc; +}; + +static const struct qcom_aoss_reset_map sdm845_aoss_resets[] = { + [AOSS_CC_MSS_RESTART] = {0x10000}, + [AOSS_CC_CAMSS_RESTART] = {0x11000}, + [AOSS_CC_VENUS_RESTART] = {0x12000}, + [AOSS_CC_GPU_RESTART] = {0x13000}, + [AOSS_CC_DISPSS_RESTART] = {0x14000}, + [AOSS_CC_WCSS_RESTART] = {0x20000}, + [AOSS_CC_LPASS_RESTART] = {0x30000}, +}; + +static const struct qcom_aoss_desc sdm845_aoss_desc = { + .resets = sdm845_aoss_resets, + .num_resets = ARRAY_SIZE(sdm845_aoss_resets), +}; + +static inline struct qcom_aoss_reset_data *to_qcom_aoss_reset_data( + struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct qcom_aoss_reset_data, rcdev); +} + +static int qcom_aoss_control_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct qcom_aoss_reset_data *data = to_qcom_aoss_reset_data(rcdev); + const struct qcom_aoss_reset_map *map = &data->desc->resets[idx]; + + writel(1, data->base + map->reg); + /* Wait 6 32kHz sleep cycles for reset */ + usleep_range(200, 300); + return 0; +} + +static int qcom_aoss_control_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct qcom_aoss_reset_data *data = to_qcom_aoss_reset_data(rcdev); + const struct qcom_aoss_reset_map *map = &data->desc->resets[idx]; + + writel(0, data->base + map->reg); + /* Wait 6 32kHz sleep cycles for reset */ + usleep_range(200, 300); + return 0; +} + +static int qcom_aoss_control_reset(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + qcom_aoss_control_assert(rcdev, idx); + + return qcom_aoss_control_deassert(rcdev, idx); +} + +static const struct reset_control_ops qcom_aoss_reset_ops = { + .reset = qcom_aoss_control_reset, + .assert = qcom_aoss_control_assert, + .deassert = qcom_aoss_control_deassert, +}; + +static int qcom_aoss_reset_probe(struct platform_device *pdev) +{ + struct qcom_aoss_reset_data *data; + struct device *dev = &pdev->dev; + const struct qcom_aoss_desc *desc; + struct resource *res; + + desc = of_device_get_match_data(dev); + if (!desc) + return -EINVAL; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->desc = desc; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->rcdev.owner = THIS_MODULE; + data->rcdev.ops = &qcom_aoss_reset_ops; + data->rcdev.nr_resets = desc->num_resets; + data->rcdev.of_node = dev->of_node; + + return devm_reset_controller_register(dev, &data->rcdev); +} + +static const struct of_device_id qcom_aoss_reset_of_match[] = { + { .compatible = "qcom,sdm845-aoss-cc", .data = &sdm845_aoss_desc }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_aoss_reset_of_match); + +static struct platform_driver qcom_aoss_reset_driver = { + .probe = qcom_aoss_reset_probe, + .driver = { + .name = "qcom_aoss_reset", + .of_match_table = qcom_aoss_reset_of_match, + }, +}; + +module_platform_driver(qcom_aoss_reset_driver); + +MODULE_DESCRIPTION("Qualcomm AOSS Reset Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-qcom-pdc.c b/drivers/reset/reset-qcom-pdc.c new file mode 100644 index 000000000..f22bb49a4 --- /dev/null +++ b/drivers/reset/reset-qcom-pdc.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 The Linux Foundation. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +#include <dt-bindings/reset/qcom,sdm845-pdc.h> + +#define RPMH_SDM845_PDC_SYNC_RESET 0x100 +#define RPMH_SC7280_PDC_SYNC_RESET 0x1000 + +struct qcom_pdc_reset_map { + u8 bit; +}; + +struct qcom_pdc_reset_desc { + const struct qcom_pdc_reset_map *resets; + size_t num_resets; + unsigned int offset; +}; + +struct qcom_pdc_reset_data { + struct reset_controller_dev rcdev; + struct regmap *regmap; + const struct qcom_pdc_reset_desc *desc; +}; + +static const struct regmap_config pdc_regmap_config = { + .name = "pdc-reset", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x20000, + .fast_io = true, +}; + +static const struct qcom_pdc_reset_map sdm845_pdc_resets[] = { + [PDC_APPS_SYNC_RESET] = {0}, + [PDC_SP_SYNC_RESET] = {1}, + [PDC_AUDIO_SYNC_RESET] = {2}, + [PDC_SENSORS_SYNC_RESET] = {3}, + [PDC_AOP_SYNC_RESET] = {4}, + [PDC_DEBUG_SYNC_RESET] = {5}, + [PDC_GPU_SYNC_RESET] = {6}, + [PDC_DISPLAY_SYNC_RESET] = {7}, + [PDC_COMPUTE_SYNC_RESET] = {8}, + [PDC_MODEM_SYNC_RESET] = {9}, +}; + +static const struct qcom_pdc_reset_desc sdm845_pdc_reset_desc = { + .resets = sdm845_pdc_resets, + .num_resets = ARRAY_SIZE(sdm845_pdc_resets), + .offset = RPMH_SDM845_PDC_SYNC_RESET, +}; + +static const struct qcom_pdc_reset_map sc7280_pdc_resets[] = { + [PDC_APPS_SYNC_RESET] = {0}, + [PDC_SP_SYNC_RESET] = {1}, + [PDC_AUDIO_SYNC_RESET] = {2}, + [PDC_SENSORS_SYNC_RESET] = {3}, + [PDC_AOP_SYNC_RESET] = {4}, + [PDC_DEBUG_SYNC_RESET] = {5}, + [PDC_GPU_SYNC_RESET] = {6}, + [PDC_DISPLAY_SYNC_RESET] = {7}, + [PDC_COMPUTE_SYNC_RESET] = {8}, + [PDC_MODEM_SYNC_RESET] = {9}, + [PDC_WLAN_RF_SYNC_RESET] = {10}, + [PDC_WPSS_SYNC_RESET] = {11}, +}; + +static const struct qcom_pdc_reset_desc sc7280_pdc_reset_desc = { + .resets = sc7280_pdc_resets, + .num_resets = ARRAY_SIZE(sc7280_pdc_resets), + .offset = RPMH_SC7280_PDC_SYNC_RESET, +}; + +static inline struct qcom_pdc_reset_data *to_qcom_pdc_reset_data( + struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct qcom_pdc_reset_data, rcdev); +} + +static int qcom_pdc_control_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct qcom_pdc_reset_data *data = to_qcom_pdc_reset_data(rcdev); + u32 mask = BIT(data->desc->resets[idx].bit); + + return regmap_update_bits(data->regmap, data->desc->offset, mask, mask); +} + +static int qcom_pdc_control_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct qcom_pdc_reset_data *data = to_qcom_pdc_reset_data(rcdev); + u32 mask = BIT(data->desc->resets[idx].bit); + + return regmap_update_bits(data->regmap, data->desc->offset, mask, 0); +} + +static const struct reset_control_ops qcom_pdc_reset_ops = { + .assert = qcom_pdc_control_assert, + .deassert = qcom_pdc_control_deassert, +}; + +static int qcom_pdc_reset_probe(struct platform_device *pdev) +{ + const struct qcom_pdc_reset_desc *desc; + struct qcom_pdc_reset_data *data; + struct device *dev = &pdev->dev; + void __iomem *base; + struct resource *res; + + desc = device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->desc = desc; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + data->regmap = devm_regmap_init_mmio(dev, base, &pdc_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "Unable to initialize regmap\n"); + return PTR_ERR(data->regmap); + } + + data->rcdev.owner = THIS_MODULE; + data->rcdev.ops = &qcom_pdc_reset_ops; + data->rcdev.nr_resets = desc->num_resets; + data->rcdev.of_node = dev->of_node; + + return devm_reset_controller_register(dev, &data->rcdev); +} + +static const struct of_device_id qcom_pdc_reset_of_match[] = { + { .compatible = "qcom,sc7280-pdc-global", .data = &sc7280_pdc_reset_desc }, + { .compatible = "qcom,sdm845-pdc-global", .data = &sdm845_pdc_reset_desc }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_pdc_reset_of_match); + +static struct platform_driver qcom_pdc_reset_driver = { + .probe = qcom_pdc_reset_probe, + .driver = { + .name = "qcom_pdc_reset", + .of_match_table = qcom_pdc_reset_of_match, + }, +}; +module_platform_driver(qcom_pdc_reset_driver); + +MODULE_DESCRIPTION("Qualcomm PDC Reset Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-raspberrypi.c b/drivers/reset/reset-raspberrypi.c new file mode 100644 index 000000000..fa23db554 --- /dev/null +++ b/drivers/reset/reset-raspberrypi.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Raspberry Pi 4 firmware reset driver + * + * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> + */ +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <soc/bcm2835/raspberrypi-firmware.h> +#include <dt-bindings/reset/raspberrypi,firmware-reset.h> + +struct rpi_reset { + struct reset_controller_dev rcdev; + struct rpi_firmware *fw; +}; + +static inline struct rpi_reset *to_rpi(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct rpi_reset, rcdev); +} + +static int rpi_reset_reset(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct rpi_reset *priv = to_rpi(rcdev); + u32 dev_addr; + int ret; + + switch (id) { + case RASPBERRYPI_FIRMWARE_RESET_ID_USB: + /* + * The Raspberry Pi 4 gets its USB functionality from VL805, a + * PCIe chip that implements xHCI. After a PCI reset, VL805's + * firmware may either be loaded directly from an EEPROM or, if + * not present, by the SoC's co-processor, VideoCore. rpi's + * VideoCore OS contains both the non public firmware load + * logic and the VL805 firmware blob. This triggers the + * aforementioned process. + * + * The pci device address is expected is expected by the + * firmware encoded like this: + * + * PCI_BUS << 20 | PCI_SLOT << 15 | PCI_FUNC << 12 + * + * But since rpi's PCIe is hardwired, we know the address in + * advance. + */ + dev_addr = 0x100000; + ret = rpi_firmware_property(priv->fw, RPI_FIRMWARE_NOTIFY_XHCI_RESET, + &dev_addr, sizeof(dev_addr)); + if (ret) + return ret; + + /* Wait for vl805 to startup */ + usleep_range(200, 1000); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct reset_control_ops rpi_reset_ops = { + .reset = rpi_reset_reset, +}; + +static int rpi_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rpi_firmware *fw; + struct device_node *np; + struct rpi_reset *priv; + + np = of_get_parent(dev->of_node); + if (!np) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + fw = devm_rpi_firmware_get(&pdev->dev, np); + of_node_put(np); + if (!fw) + return -EPROBE_DEFER; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + priv->fw = fw; + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.nr_resets = RASPBERRYPI_FIRMWARE_RESET_NUM_IDS; + priv->rcdev.ops = &rpi_reset_ops; + priv->rcdev.of_node = dev->of_node; + + return devm_reset_controller_register(dev, &priv->rcdev); +} + +static const struct of_device_id rpi_reset_of_match[] = { + { .compatible = "raspberrypi,firmware-reset" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rpi_reset_of_match); + +static struct platform_driver rpi_reset_driver = { + .probe = rpi_reset_probe, + .driver = { + .name = "raspberrypi-reset", + .of_match_table = rpi_reset_of_match, + }, +}; +module_platform_driver(rpi_reset_driver); + +MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>"); +MODULE_DESCRIPTION("Raspberry Pi 4 firmware reset driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/reset-rzg2l-usbphy-ctrl.c b/drivers/reset/reset-rzg2l-usbphy-ctrl.c new file mode 100644 index 000000000..a8dde4606 --- /dev/null +++ b/drivers/reset/reset-rzg2l-usbphy-ctrl.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/G2L USBPHY control driver + * + * Copyright (C) 2021 Renesas Electronics Corporation + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/reset-controller.h> + +#define RESET 0x000 + +#define RESET_SEL_PLLRESET BIT(12) +#define RESET_PLLRESET BIT(8) + +#define RESET_SEL_P2RESET BIT(5) +#define RESET_SEL_P1RESET BIT(4) +#define RESET_PHYRST_2 BIT(1) +#define RESET_PHYRST_1 BIT(0) + +#define PHY_RESET_PORT2 (RESET_SEL_P2RESET | RESET_PHYRST_2) +#define PHY_RESET_PORT1 (RESET_SEL_P1RESET | RESET_PHYRST_1) + +#define NUM_PORTS 2 + +struct rzg2l_usbphy_ctrl_priv { + struct reset_controller_dev rcdev; + struct reset_control *rstc; + void __iomem *base; + + spinlock_t lock; +}; + +#define rcdev_to_priv(x) container_of(x, struct rzg2l_usbphy_ctrl_priv, rcdev) + +static int rzg2l_usbphy_ctrl_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct rzg2l_usbphy_ctrl_priv *priv = rcdev_to_priv(rcdev); + u32 port_mask = PHY_RESET_PORT1 | PHY_RESET_PORT2; + void __iomem *base = priv->base; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->lock, flags); + val = readl(base + RESET); + val |= id ? PHY_RESET_PORT2 : PHY_RESET_PORT1; + if (port_mask == (val & port_mask)) + val |= RESET_PLLRESET; + writel(val, base + RESET); + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int rzg2l_usbphy_ctrl_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct rzg2l_usbphy_ctrl_priv *priv = rcdev_to_priv(rcdev); + void __iomem *base = priv->base; + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->lock, flags); + val = readl(base + RESET); + + val |= RESET_SEL_PLLRESET; + val &= ~(RESET_PLLRESET | (id ? PHY_RESET_PORT2 : PHY_RESET_PORT1)); + writel(val, base + RESET); + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int rzg2l_usbphy_ctrl_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct rzg2l_usbphy_ctrl_priv *priv = rcdev_to_priv(rcdev); + u32 port_mask; + + port_mask = id ? PHY_RESET_PORT2 : PHY_RESET_PORT1; + + return !!(readl(priv->base + RESET) & port_mask); +} + +static const struct of_device_id rzg2l_usbphy_ctrl_match_table[] = { + { .compatible = "renesas,rzg2l-usbphy-ctrl" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rzg2l_usbphy_ctrl_match_table); + +static const struct reset_control_ops rzg2l_usbphy_ctrl_reset_ops = { + .assert = rzg2l_usbphy_ctrl_assert, + .deassert = rzg2l_usbphy_ctrl_deassert, + .status = rzg2l_usbphy_ctrl_status, +}; + +static int rzg2l_usbphy_ctrl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rzg2l_usbphy_ctrl_priv *priv; + unsigned long flags; + int error; + u32 val; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(priv->rstc)) + return dev_err_probe(dev, PTR_ERR(priv->rstc), + "failed to get reset\n"); + + error = reset_control_deassert(priv->rstc); + if (error) + return error; + + priv->rcdev.ops = &rzg2l_usbphy_ctrl_reset_ops; + priv->rcdev.of_reset_n_cells = 1; + priv->rcdev.nr_resets = NUM_PORTS; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.dev = dev; + + error = devm_reset_controller_register(dev, &priv->rcdev); + if (error) + return error; + + spin_lock_init(&priv->lock); + dev_set_drvdata(dev, priv); + + pm_runtime_enable(&pdev->dev); + error = pm_runtime_resume_and_get(&pdev->dev); + if (error < 0) { + pm_runtime_disable(&pdev->dev); + reset_control_assert(priv->rstc); + return dev_err_probe(&pdev->dev, error, "pm_runtime_resume_and_get failed"); + } + + /* put pll and phy into reset state */ + spin_lock_irqsave(&priv->lock, flags); + val = readl(priv->base + RESET); + val |= RESET_SEL_PLLRESET | RESET_PLLRESET | PHY_RESET_PORT2 | PHY_RESET_PORT1; + writel(val, priv->base + RESET); + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int rzg2l_usbphy_ctrl_remove(struct platform_device *pdev) +{ + struct rzg2l_usbphy_ctrl_priv *priv = dev_get_drvdata(&pdev->dev); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + reset_control_assert(priv->rstc); + + return 0; +} + +static struct platform_driver rzg2l_usbphy_ctrl_driver = { + .driver = { + .name = "rzg2l_usbphy_ctrl", + .of_match_table = rzg2l_usbphy_ctrl_match_table, + }, + .probe = rzg2l_usbphy_ctrl_probe, + .remove = rzg2l_usbphy_ctrl_remove, +}; +module_platform_driver(rzg2l_usbphy_ctrl_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas RZ/G2L USBPHY Control"); +MODULE_AUTHOR("biju.das.jz@bp.renesas.com>"); diff --git a/drivers/reset/reset-scmi.c b/drivers/reset/reset-scmi.c new file mode 100644 index 000000000..4335811e0 --- /dev/null +++ b/drivers/reset/reset-scmi.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM System Control and Management Interface (ARM SCMI) reset driver + * + * Copyright (C) 2019-2021 ARM Ltd. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/device.h> +#include <linux/reset-controller.h> +#include <linux/scmi_protocol.h> + +static const struct scmi_reset_proto_ops *reset_ops; + +/** + * struct scmi_reset_data - reset controller information structure + * @rcdev: reset controller entity + * @ph: ARM SCMI protocol handle used for communication with system controller + */ +struct scmi_reset_data { + struct reset_controller_dev rcdev; + const struct scmi_protocol_handle *ph; +}; + +#define to_scmi_reset_data(p) container_of((p), struct scmi_reset_data, rcdev) +#define to_scmi_handle(p) (to_scmi_reset_data(p)->ph) + +/** + * scmi_reset_assert() - assert device reset + * @rcdev: reset controller entity + * @id: ID of the reset to be asserted + * + * This function implements the reset driver op to assert a device's reset + * using the ARM SCMI protocol. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int +scmi_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + const struct scmi_protocol_handle *ph = to_scmi_handle(rcdev); + + return reset_ops->assert(ph, id); +} + +/** + * scmi_reset_deassert() - deassert device reset + * @rcdev: reset controller entity + * @id: ID of the reset to be deasserted + * + * This function implements the reset driver op to deassert a device's reset + * using the ARM SCMI protocol. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int +scmi_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + const struct scmi_protocol_handle *ph = to_scmi_handle(rcdev); + + return reset_ops->deassert(ph, id); +} + +/** + * scmi_reset_reset() - reset the device + * @rcdev: reset controller entity + * @id: ID of the reset signal to be reset(assert + deassert) + * + * This function implements the reset driver op to trigger a device's + * reset signal using the ARM SCMI protocol. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int +scmi_reset_reset(struct reset_controller_dev *rcdev, unsigned long id) +{ + const struct scmi_protocol_handle *ph = to_scmi_handle(rcdev); + + return reset_ops->reset(ph, id); +} + +static const struct reset_control_ops scmi_reset_ops = { + .assert = scmi_reset_assert, + .deassert = scmi_reset_deassert, + .reset = scmi_reset_reset, +}; + +static int scmi_reset_probe(struct scmi_device *sdev) +{ + struct scmi_reset_data *data; + struct device *dev = &sdev->dev; + struct device_node *np = dev->of_node; + const struct scmi_handle *handle = sdev->handle; + struct scmi_protocol_handle *ph; + + if (!handle) + return -ENODEV; + + reset_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_RESET, &ph); + if (IS_ERR(reset_ops)) + return PTR_ERR(reset_ops); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->rcdev.ops = &scmi_reset_ops; + data->rcdev.owner = THIS_MODULE; + data->rcdev.of_node = np; + data->rcdev.nr_resets = reset_ops->num_domains_get(ph); + data->ph = ph; + + return devm_reset_controller_register(dev, &data->rcdev); +} + +static const struct scmi_device_id scmi_id_table[] = { + { SCMI_PROTOCOL_RESET, "reset" }, + { }, +}; +MODULE_DEVICE_TABLE(scmi, scmi_id_table); + +static struct scmi_driver scmi_reset_driver = { + .name = "scmi-reset", + .probe = scmi_reset_probe, + .id_table = scmi_id_table, +}; +module_scmi_driver(scmi_reset_driver); + +MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); +MODULE_DESCRIPTION("ARM SCMI reset controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-simple.c b/drivers/reset/reset-simple.c new file mode 100644 index 000000000..361a68314 --- /dev/null +++ b/drivers/reset/reset-simple.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Simple Reset Controller Driver + * + * Copyright (C) 2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> + * + * Based on Allwinner SoCs Reset Controller driver + * + * Copyright 2013 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/reset/reset-simple.h> +#include <linux/spinlock.h> + +static inline struct reset_simple_data * +to_reset_simple_data(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct reset_simple_data, rcdev); +} + +static int reset_simple_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct reset_simple_data *data = to_reset_simple_data(rcdev); + int reg_width = sizeof(u32); + int bank = id / (reg_width * BITS_PER_BYTE); + int offset = id % (reg_width * BITS_PER_BYTE); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&data->lock, flags); + + reg = readl(data->membase + (bank * reg_width)); + if (assert ^ data->active_low) + reg |= BIT(offset); + else + reg &= ~BIT(offset); + writel(reg, data->membase + (bank * reg_width)); + + spin_unlock_irqrestore(&data->lock, flags); + + return 0; +} + +static int reset_simple_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return reset_simple_update(rcdev, id, true); +} + +static int reset_simple_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return reset_simple_update(rcdev, id, false); +} + +static int reset_simple_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct reset_simple_data *data = to_reset_simple_data(rcdev); + int ret; + + if (!data->reset_us) + return -ENOTSUPP; + + ret = reset_simple_assert(rcdev, id); + if (ret) + return ret; + + usleep_range(data->reset_us, data->reset_us * 2); + + return reset_simple_deassert(rcdev, id); +} + +static int reset_simple_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct reset_simple_data *data = to_reset_simple_data(rcdev); + int reg_width = sizeof(u32); + int bank = id / (reg_width * BITS_PER_BYTE); + int offset = id % (reg_width * BITS_PER_BYTE); + u32 reg; + + reg = readl(data->membase + (bank * reg_width)); + + return !(reg & BIT(offset)) ^ !data->status_active_low; +} + +const struct reset_control_ops reset_simple_ops = { + .assert = reset_simple_assert, + .deassert = reset_simple_deassert, + .reset = reset_simple_reset, + .status = reset_simple_status, +}; +EXPORT_SYMBOL_GPL(reset_simple_ops); + +/** + * struct reset_simple_devdata - simple reset controller properties + * @reg_offset: offset between base address and first reset register. + * @nr_resets: number of resets. If not set, default to resource size in bits. + * @active_low: if true, bits are cleared to assert the reset. Otherwise, bits + * are set to assert the reset. + * @status_active_low: if true, bits read back as cleared while the reset is + * asserted. Otherwise, bits read back as set while the + * reset is asserted. + */ +struct reset_simple_devdata { + u32 reg_offset; + u32 nr_resets; + bool active_low; + bool status_active_low; +}; + +#define SOCFPGA_NR_BANKS 8 + +static const struct reset_simple_devdata reset_simple_socfpga = { + .reg_offset = 0x20, + .nr_resets = SOCFPGA_NR_BANKS * 32, + .status_active_low = true, +}; + +static const struct reset_simple_devdata reset_simple_active_low = { + .active_low = true, + .status_active_low = true, +}; + +static const struct of_device_id reset_simple_dt_ids[] = { + { .compatible = "altr,stratix10-rst-mgr", + .data = &reset_simple_socfpga }, + { .compatible = "st,stm32-rcc", }, + { .compatible = "allwinner,sun6i-a31-clock-reset", + .data = &reset_simple_active_low }, + { .compatible = "zte,zx296718-reset", + .data = &reset_simple_active_low }, + { .compatible = "aspeed,ast2400-lpc-reset" }, + { .compatible = "aspeed,ast2500-lpc-reset" }, + { .compatible = "aspeed,ast2600-lpc-reset" }, + { .compatible = "bitmain,bm1880-reset", + .data = &reset_simple_active_low }, + { .compatible = "brcm,bcm4908-misc-pcie-reset", + .data = &reset_simple_active_low }, + { .compatible = "snps,dw-high-reset" }, + { .compatible = "snps,dw-low-reset", + .data = &reset_simple_active_low }, + { /* sentinel */ }, +}; + +static int reset_simple_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct reset_simple_devdata *devdata; + struct reset_simple_data *data; + void __iomem *membase; + struct resource *res; + u32 reg_offset = 0; + + devdata = of_device_get_match_data(dev); + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + membase = devm_ioremap_resource(dev, res); + if (IS_ERR(membase)) + return PTR_ERR(membase); + + spin_lock_init(&data->lock); + data->membase = membase; + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = resource_size(res) * BITS_PER_BYTE; + data->rcdev.ops = &reset_simple_ops; + data->rcdev.of_node = dev->of_node; + + if (devdata) { + reg_offset = devdata->reg_offset; + if (devdata->nr_resets) + data->rcdev.nr_resets = devdata->nr_resets; + data->active_low = devdata->active_low; + data->status_active_low = devdata->status_active_low; + } + + data->membase += reg_offset; + + return devm_reset_controller_register(dev, &data->rcdev); +} + +static struct platform_driver reset_simple_driver = { + .probe = reset_simple_probe, + .driver = { + .name = "simple-reset", + .of_match_table = reset_simple_dt_ids, + }, +}; +builtin_platform_driver(reset_simple_driver); diff --git a/drivers/reset/reset-socfpga.c b/drivers/reset/reset-socfpga.c new file mode 100644 index 000000000..8c6492e56 --- /dev/null +++ b/drivers/reset/reset-socfpga.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018, Intel Corporation + * Copied from reset-sunxi.c + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/reset/reset-simple.h> +#include <linux/reset/socfpga.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#define SOCFPGA_NR_BANKS 8 + +static int a10_reset_init(struct device_node *np) +{ + struct reset_simple_data *data; + struct resource res; + resource_size_t size; + int ret; + u32 reg_offset = 0x10; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = of_address_to_resource(np, 0, &res); + if (ret) + goto err_alloc; + + size = resource_size(&res); + if (!request_mem_region(res.start, size, np->name)) { + ret = -EBUSY; + goto err_alloc; + } + + data->membase = ioremap(res.start, size); + if (!data->membase) { + ret = -ENOMEM; + goto release_region; + } + + if (of_property_read_u32(np, "altr,modrst-offset", ®_offset)) + pr_warn("missing altr,modrst-offset property, assuming 0x10\n"); + data->membase += reg_offset; + + spin_lock_init(&data->lock); + + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = SOCFPGA_NR_BANKS * 32; + data->rcdev.ops = &reset_simple_ops; + data->rcdev.of_node = np; + data->status_active_low = true; + + ret = reset_controller_register(&data->rcdev); + if (ret) + pr_err("unable to register device\n"); + + return ret; + +release_region: + release_mem_region(res.start, size); + +err_alloc: + kfree(data); + return ret; +}; + +/* + * These are the reset controller we need to initialize early on in + * our system, before we can even think of using a regular device + * driver for it. + * The controllers that we can register through the regular device + * model are handled by the simple reset driver directly. + */ +static const struct of_device_id socfpga_early_reset_dt_ids[] __initconst = { + { .compatible = "altr,rst-mgr", }, + { /* sentinel */ }, +}; + +void __init socfpga_reset_init(void) +{ + struct device_node *np; + + for_each_matching_node(np, socfpga_early_reset_dt_ids) + a10_reset_init(np); +} + +/* + * The early driver is problematic, because it doesn't register + * itself as a driver. This causes certain device links to prevent + * consumer devices from probing. The hacky solution is to register + * an empty driver, whose only job is to attach itself to the reset + * manager and call probe. + */ +static const struct of_device_id socfpga_reset_dt_ids[] = { + { .compatible = "altr,rst-mgr", }, + { /* sentinel */ }, +}; + +static int reset_simple_probe(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver reset_socfpga_driver = { + .probe = reset_simple_probe, + .driver = { + .name = "socfpga-reset", + .of_match_table = socfpga_reset_dt_ids, + }, +}; +builtin_platform_driver(reset_socfpga_driver); diff --git a/drivers/reset/reset-starfive-jh7100.c b/drivers/reset/reset-starfive-jh7100.c new file mode 100644 index 000000000..fc44b2fb3 --- /dev/null +++ b/drivers/reset/reset-starfive-jh7100.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Reset driver for the StarFive JH7100 SoC + * + * Copyright (C) 2021 Emil Renner Berthing <kernel@esmil.dk> + */ + +#include <linux/bitmap.h> +#include <linux/io.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/iopoll.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/spinlock.h> + +#include <dt-bindings/reset/starfive-jh7100.h> + +/* register offsets */ +#define JH7100_RESET_ASSERT0 0x00 +#define JH7100_RESET_ASSERT1 0x04 +#define JH7100_RESET_ASSERT2 0x08 +#define JH7100_RESET_ASSERT3 0x0c +#define JH7100_RESET_STATUS0 0x10 +#define JH7100_RESET_STATUS1 0x14 +#define JH7100_RESET_STATUS2 0x18 +#define JH7100_RESET_STATUS3 0x1c + +/* + * Writing a 1 to the n'th bit of the m'th ASSERT register asserts + * line 32m + n, and writing a 0 deasserts the same line. + * Most reset lines have their status inverted so a 0 bit in the STATUS + * register means the line is asserted and a 1 means it's deasserted. A few + * lines don't though, so store the expected value of the status registers when + * all lines are asserted. + */ +static const u64 jh7100_reset_asserted[2] = { + /* STATUS0 */ + BIT_ULL_MASK(JH7100_RST_U74) | + BIT_ULL_MASK(JH7100_RST_VP6_DRESET) | + BIT_ULL_MASK(JH7100_RST_VP6_BRESET) | + /* STATUS1 */ + BIT_ULL_MASK(JH7100_RST_HIFI4_DRESET) | + BIT_ULL_MASK(JH7100_RST_HIFI4_BRESET), + /* STATUS2 */ + BIT_ULL_MASK(JH7100_RST_E24) | + /* STATUS3 */ + 0, +}; + +struct jh7100_reset { + struct reset_controller_dev rcdev; + /* protect registers against concurrent read-modify-write */ + spinlock_t lock; + void __iomem *base; +}; + +static inline struct jh7100_reset * +jh7100_reset_from(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct jh7100_reset, rcdev); +} + +static int jh7100_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct jh7100_reset *data = jh7100_reset_from(rcdev); + unsigned long offset = BIT_ULL_WORD(id); + u64 mask = BIT_ULL_MASK(id); + void __iomem *reg_assert = data->base + JH7100_RESET_ASSERT0 + offset * sizeof(u64); + void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u64); + u64 done = jh7100_reset_asserted[offset] & mask; + u64 value; + unsigned long flags; + int ret; + + if (!assert) + done ^= mask; + + spin_lock_irqsave(&data->lock, flags); + + value = readq(reg_assert); + if (assert) + value |= mask; + else + value &= ~mask; + writeq(value, reg_assert); + + /* if the associated clock is gated, deasserting might otherwise hang forever */ + ret = readq_poll_timeout_atomic(reg_status, value, (value & mask) == done, 0, 1000); + + spin_unlock_irqrestore(&data->lock, flags); + return ret; +} + +static int jh7100_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return jh7100_reset_update(rcdev, id, true); +} + +static int jh7100_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return jh7100_reset_update(rcdev, id, false); +} + +static int jh7100_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + int ret; + + ret = jh7100_reset_assert(rcdev, id); + if (ret) + return ret; + + return jh7100_reset_deassert(rcdev, id); +} + +static int jh7100_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct jh7100_reset *data = jh7100_reset_from(rcdev); + unsigned long offset = BIT_ULL_WORD(id); + u64 mask = BIT_ULL_MASK(id); + void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u64); + u64 value = readq(reg_status); + + return !((value ^ jh7100_reset_asserted[offset]) & mask); +} + +static const struct reset_control_ops jh7100_reset_ops = { + .assert = jh7100_reset_assert, + .deassert = jh7100_reset_deassert, + .reset = jh7100_reset_reset, + .status = jh7100_reset_status, +}; + +static int __init jh7100_reset_probe(struct platform_device *pdev) +{ + struct jh7100_reset *data; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->rcdev.ops = &jh7100_reset_ops; + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = JH7100_RSTN_END; + data->rcdev.dev = &pdev->dev; + data->rcdev.of_node = pdev->dev.of_node; + spin_lock_init(&data->lock); + + return devm_reset_controller_register(&pdev->dev, &data->rcdev); +} + +static const struct of_device_id jh7100_reset_dt_ids[] = { + { .compatible = "starfive,jh7100-reset" }, + { /* sentinel */ } +}; + +static struct platform_driver jh7100_reset_driver = { + .driver = { + .name = "jh7100-reset", + .of_match_table = jh7100_reset_dt_ids, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver_probe(jh7100_reset_driver, jh7100_reset_probe); diff --git a/drivers/reset/reset-sunplus.c b/drivers/reset/reset-sunplus.c new file mode 100644 index 000000000..2f23ecaa7 --- /dev/null +++ b/drivers/reset/reset-sunplus.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* + * SP7021 reset driver + * + * Copyright (C) Sunplus Technology Co., Ltd. + * All rights reserved. + */ + +#include <linux/io.h> +#include <linux/init.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/reboot.h> + +/* HIWORD_MASK_REG BITS */ +#define BITS_PER_HWM_REG 16 + +/* resets HW info: reg_index_shift */ +static const u32 sp_resets[] = { +/* SP7021: mo_reset0 ~ mo_reset9 */ + 0x00, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x12, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x2a, + 0x2b, + 0x2d, + 0x2e, + 0x30, + 0x31, + 0x32, + 0x33, + 0x3d, + 0x3e, + 0x3f, + 0x42, + 0x44, + 0x4b, + 0x4c, + 0x4d, + 0x4e, + 0x4f, + 0x50, + 0x55, + 0x60, + 0x61, + 0x6a, + 0x6f, + 0x70, + 0x73, + 0x74, + 0x86, + 0x8a, + 0x8b, + 0x8d, + 0x8e, + 0x8f, + 0x90, + 0x92, + 0x93, + 0x94, + 0x95, + 0x96, + 0x97, + 0x98, + 0x99, +}; + +struct sp_reset { + struct reset_controller_dev rcdev; + struct notifier_block notifier; + void __iomem *base; +}; + +static inline struct sp_reset *to_sp_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct sp_reset, rcdev); +} + +static int sp_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct sp_reset *reset = to_sp_reset(rcdev); + int index = sp_resets[id] / BITS_PER_HWM_REG; + int shift = sp_resets[id] % BITS_PER_HWM_REG; + u32 val; + + val = (1 << (16 + shift)) | (assert << shift); + writel(val, reset->base + (index * 4)); + + return 0; +} + +static int sp_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sp_reset_update(rcdev, id, true); +} + +static int sp_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sp_reset_update(rcdev, id, false); +} + +static int sp_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct sp_reset *reset = to_sp_reset(rcdev); + int index = sp_resets[id] / BITS_PER_HWM_REG; + int shift = sp_resets[id] % BITS_PER_HWM_REG; + u32 reg; + + reg = readl(reset->base + (index * 4)); + + return !!(reg & BIT(shift)); +} + +static const struct reset_control_ops sp_reset_ops = { + .assert = sp_reset_assert, + .deassert = sp_reset_deassert, + .status = sp_reset_status, +}; + +static int sp_restart(struct notifier_block *nb, unsigned long mode, + void *cmd) +{ + struct sp_reset *reset = container_of(nb, struct sp_reset, notifier); + + sp_reset_assert(&reset->rcdev, 0); + sp_reset_deassert(&reset->rcdev, 0); + + return NOTIFY_DONE; +} + +static int sp_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sp_reset *reset; + struct resource *res; + int ret; + + reset = devm_kzalloc(dev, sizeof(*reset), GFP_KERNEL); + if (!reset) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reset->base = devm_ioremap_resource(dev, res); + if (IS_ERR(reset->base)) + return PTR_ERR(reset->base); + + reset->rcdev.ops = &sp_reset_ops; + reset->rcdev.owner = THIS_MODULE; + reset->rcdev.of_node = dev->of_node; + reset->rcdev.nr_resets = resource_size(res) / 4 * BITS_PER_HWM_REG; + + ret = devm_reset_controller_register(dev, &reset->rcdev); + if (ret) + return ret; + + reset->notifier.notifier_call = sp_restart; + reset->notifier.priority = 192; + + return register_restart_handler(&reset->notifier); +} + +static const struct of_device_id sp_reset_dt_ids[] = { + {.compatible = "sunplus,sp7021-reset",}, + { /* sentinel */ }, +}; + +static struct platform_driver sp_reset_driver = { + .probe = sp_reset_probe, + .driver = { + .name = "sunplus-reset", + .of_match_table = sp_reset_dt_ids, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(sp_reset_driver); diff --git a/drivers/reset/reset-sunxi.c b/drivers/reset/reset-sunxi.c new file mode 100644 index 000000000..e752594b6 --- /dev/null +++ b/drivers/reset/reset-sunxi.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Allwinner SoCs Reset Controller driver + * + * Copyright 2013 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/reset/reset-simple.h> +#include <linux/reset/sunxi.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +static int sunxi_reset_init(struct device_node *np) +{ + struct reset_simple_data *data; + struct resource res; + resource_size_t size; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = of_address_to_resource(np, 0, &res); + if (ret) + goto err_alloc; + + size = resource_size(&res); + if (!request_mem_region(res.start, size, np->name)) { + ret = -EBUSY; + goto err_alloc; + } + + data->membase = ioremap(res.start, size); + if (!data->membase) { + ret = -ENOMEM; + goto err_alloc; + } + + spin_lock_init(&data->lock); + + data->rcdev.owner = THIS_MODULE; + data->rcdev.nr_resets = size * 8; + data->rcdev.ops = &reset_simple_ops; + data->rcdev.of_node = np; + data->active_low = true; + + return reset_controller_register(&data->rcdev); + +err_alloc: + kfree(data); + return ret; +}; + +/* + * These are the reset controller we need to initialize early on in + * our system, before we can even think of using a regular device + * driver for it. + * The controllers that we can register through the regular device + * model are handled by the simple reset driver directly. + */ +static const struct of_device_id sunxi_early_reset_dt_ids[] __initconst = { + { .compatible = "allwinner,sun6i-a31-ahb1-reset", }, + { /* sentinel */ }, +}; + +void __init sun6i_reset_init(void) +{ + struct device_node *np; + + for_each_matching_node(np, sunxi_early_reset_dt_ids) + sunxi_reset_init(np); +} diff --git a/drivers/reset/reset-ti-sci.c b/drivers/reset/reset-ti-sci.c new file mode 100644 index 000000000..cc01fa5b0 --- /dev/null +++ b/drivers/reset/reset-ti-sci.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Texas Instrument's System Control Interface (TI-SCI) reset driver + * + * Copyright (C) 2015-2017 Texas Instruments Incorporated - https://www.ti.com/ + * Andrew F. Davis <afd@ti.com> + */ + +#include <linux/idr.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/soc/ti/ti_sci_protocol.h> + +/** + * struct ti_sci_reset_control - reset control structure + * @dev_id: SoC-specific device identifier + * @reset_mask: reset mask to use for toggling reset + * @lock: synchronize reset_mask read-modify-writes + */ +struct ti_sci_reset_control { + u32 dev_id; + u32 reset_mask; + struct mutex lock; +}; + +/** + * struct ti_sci_reset_data - reset controller information structure + * @rcdev: reset controller entity + * @dev: reset controller device pointer + * @sci: TI SCI handle used for communication with system controller + * @idr: idr structure for mapping ids to reset control structures + */ +struct ti_sci_reset_data { + struct reset_controller_dev rcdev; + struct device *dev; + const struct ti_sci_handle *sci; + struct idr idr; +}; + +#define to_ti_sci_reset_data(p) \ + container_of((p), struct ti_sci_reset_data, rcdev) + +/** + * ti_sci_reset_set() - program a device's reset + * @rcdev: reset controller entity + * @id: ID of the reset to toggle + * @assert: boolean flag to indicate assert or deassert + * + * This is a common internal function used to assert or deassert a device's + * reset using the TI SCI protocol. The device's reset is asserted if the + * @assert argument is true, or deasserted if @assert argument is false. + * The mechanism itself is a read-modify-write procedure, the current device + * reset register is read using a TI SCI device operation, the new value is + * set or un-set using the reset's mask, and the new reset value written by + * using another TI SCI device operation. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int ti_sci_reset_set(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct ti_sci_reset_data *data = to_ti_sci_reset_data(rcdev); + const struct ti_sci_handle *sci = data->sci; + const struct ti_sci_dev_ops *dev_ops = &sci->ops.dev_ops; + struct ti_sci_reset_control *control; + u32 reset_state; + int ret; + + control = idr_find(&data->idr, id); + if (!control) + return -EINVAL; + + mutex_lock(&control->lock); + + ret = dev_ops->get_device_resets(sci, control->dev_id, &reset_state); + if (ret) + goto out; + + if (assert) + reset_state |= control->reset_mask; + else + reset_state &= ~control->reset_mask; + + ret = dev_ops->set_device_resets(sci, control->dev_id, reset_state); +out: + mutex_unlock(&control->lock); + + return ret; +} + +/** + * ti_sci_reset_assert() - assert device reset + * @rcdev: reset controller entity + * @id: ID of the reset to be asserted + * + * This function implements the reset driver op to assert a device's reset + * using the TI SCI protocol. This invokes the function ti_sci_reset_set() + * with the corresponding parameters as passed in, but with the @assert + * argument set to true for asserting the reset. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int ti_sci_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return ti_sci_reset_set(rcdev, id, true); +} + +/** + * ti_sci_reset_deassert() - deassert device reset + * @rcdev: reset controller entity + * @id: ID of the reset to be deasserted + * + * This function implements the reset driver op to deassert a device's reset + * using the TI SCI protocol. This invokes the function ti_sci_reset_set() + * with the corresponding parameters as passed in, but with the @assert + * argument set to false for deasserting the reset. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int ti_sci_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return ti_sci_reset_set(rcdev, id, false); +} + +/** + * ti_sci_reset_status() - check device reset status + * @rcdev: reset controller entity + * @id: ID of reset to be checked + * + * This function implements the reset driver op to return the status of a + * device's reset using the TI SCI protocol. The reset register value is read + * by invoking the TI SCI device operation .get_device_resets(), and the + * status of the specific reset is extracted and returned using this reset's + * reset mask. + * + * Return: 0 if reset is deasserted, or a non-zero value if reset is asserted + */ +static int ti_sci_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ti_sci_reset_data *data = to_ti_sci_reset_data(rcdev); + const struct ti_sci_handle *sci = data->sci; + const struct ti_sci_dev_ops *dev_ops = &sci->ops.dev_ops; + struct ti_sci_reset_control *control; + u32 reset_state; + int ret; + + control = idr_find(&data->idr, id); + if (!control) + return -EINVAL; + + ret = dev_ops->get_device_resets(sci, control->dev_id, &reset_state); + if (ret) + return ret; + + return reset_state & control->reset_mask; +} + +static const struct reset_control_ops ti_sci_reset_ops = { + .assert = ti_sci_reset_assert, + .deassert = ti_sci_reset_deassert, + .status = ti_sci_reset_status, +}; + +/** + * ti_sci_reset_of_xlate() - translate a set of OF arguments to a reset ID + * @rcdev: reset controller entity + * @reset_spec: OF reset argument specifier + * + * This function performs the translation of the reset argument specifier + * values defined in a reset consumer device node. The function allocates a + * reset control structure for that device reset, and will be used by the + * driver for performing any reset functions on that reset. An idr structure + * is allocated and used to map to the reset control structure. This idr + * is used by the driver to do reset lookups. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int ti_sci_reset_of_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + struct ti_sci_reset_data *data = to_ti_sci_reset_data(rcdev); + struct ti_sci_reset_control *control; + + if (WARN_ON(reset_spec->args_count != rcdev->of_reset_n_cells)) + return -EINVAL; + + control = devm_kzalloc(data->dev, sizeof(*control), GFP_KERNEL); + if (!control) + return -ENOMEM; + + control->dev_id = reset_spec->args[0]; + control->reset_mask = reset_spec->args[1]; + mutex_init(&control->lock); + + return idr_alloc(&data->idr, control, 0, 0, GFP_KERNEL); +} + +static const struct of_device_id ti_sci_reset_of_match[] = { + { .compatible = "ti,sci-reset", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ti_sci_reset_of_match); + +static int ti_sci_reset_probe(struct platform_device *pdev) +{ + struct ti_sci_reset_data *data; + + if (!pdev->dev.of_node) + return -ENODEV; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->sci = devm_ti_sci_get_handle(&pdev->dev); + if (IS_ERR(data->sci)) + return PTR_ERR(data->sci); + + data->rcdev.ops = &ti_sci_reset_ops; + data->rcdev.owner = THIS_MODULE; + data->rcdev.of_node = pdev->dev.of_node; + data->rcdev.of_reset_n_cells = 2; + data->rcdev.of_xlate = ti_sci_reset_of_xlate; + data->dev = &pdev->dev; + idr_init(&data->idr); + + platform_set_drvdata(pdev, data); + + return reset_controller_register(&data->rcdev); +} + +static int ti_sci_reset_remove(struct platform_device *pdev) +{ + struct ti_sci_reset_data *data = platform_get_drvdata(pdev); + + reset_controller_unregister(&data->rcdev); + + idr_destroy(&data->idr); + + return 0; +} + +static struct platform_driver ti_sci_reset_driver = { + .probe = ti_sci_reset_probe, + .remove = ti_sci_reset_remove, + .driver = { + .name = "ti-sci-reset", + .of_match_table = ti_sci_reset_of_match, + }, +}; +module_platform_driver(ti_sci_reset_driver); + +MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>"); +MODULE_DESCRIPTION("TI System Control Interface (TI SCI) Reset driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-ti-syscon.c b/drivers/reset/reset-ti-syscon.c new file mode 100644 index 000000000..f0dd7ffc3 --- /dev/null +++ b/drivers/reset/reset-ti-syscon.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI SYSCON regmap reset driver + * + * Copyright (C) 2015-2016 Texas Instruments Incorporated - https://www.ti.com/ + * Andrew F. Davis <afd@ti.com> + * Suman Anna <afd@ti.com> + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +#include <dt-bindings/reset/ti-syscon.h> + +/** + * struct ti_syscon_reset_control - reset control structure + * @assert_offset: reset assert control register offset from syscon base + * @assert_bit: reset assert bit in the reset assert control register + * @deassert_offset: reset deassert control register offset from syscon base + * @deassert_bit: reset deassert bit in the reset deassert control register + * @status_offset: reset status register offset from syscon base + * @status_bit: reset status bit in the reset status register + * @flags: reset flag indicating how the (de)assert and status are handled + */ +struct ti_syscon_reset_control { + unsigned int assert_offset; + unsigned int assert_bit; + unsigned int deassert_offset; + unsigned int deassert_bit; + unsigned int status_offset; + unsigned int status_bit; + u32 flags; +}; + +/** + * struct ti_syscon_reset_data - reset controller information structure + * @rcdev: reset controller entity + * @regmap: regmap handle containing the memory-mapped reset registers + * @controls: array of reset controls + * @nr_controls: number of controls in control array + */ +struct ti_syscon_reset_data { + struct reset_controller_dev rcdev; + struct regmap *regmap; + struct ti_syscon_reset_control *controls; + unsigned int nr_controls; +}; + +#define to_ti_syscon_reset_data(_rcdev) \ + container_of(_rcdev, struct ti_syscon_reset_data, rcdev) + +/** + * ti_syscon_reset_assert() - assert device reset + * @rcdev: reset controller entity + * @id: ID of the reset to be asserted + * + * This function implements the reset driver op to assert a device's reset. + * This asserts the reset in a manner prescribed by the reset flags. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int ti_syscon_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ti_syscon_reset_data *data = to_ti_syscon_reset_data(rcdev); + struct ti_syscon_reset_control *control; + unsigned int mask, value; + + if (id >= data->nr_controls) + return -EINVAL; + + control = &data->controls[id]; + + if (control->flags & ASSERT_NONE) + return -ENOTSUPP; /* assert not supported for this reset */ + + mask = BIT(control->assert_bit); + value = (control->flags & ASSERT_SET) ? mask : 0x0; + + return regmap_write_bits(data->regmap, control->assert_offset, mask, value); +} + +/** + * ti_syscon_reset_deassert() - deassert device reset + * @rcdev: reset controller entity + * @id: ID of reset to be deasserted + * + * This function implements the reset driver op to deassert a device's reset. + * This deasserts the reset in a manner prescribed by the reset flags. + * + * Return: 0 for successful request, else a corresponding error value + */ +static int ti_syscon_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ti_syscon_reset_data *data = to_ti_syscon_reset_data(rcdev); + struct ti_syscon_reset_control *control; + unsigned int mask, value; + + if (id >= data->nr_controls) + return -EINVAL; + + control = &data->controls[id]; + + if (control->flags & DEASSERT_NONE) + return -ENOTSUPP; /* deassert not supported for this reset */ + + mask = BIT(control->deassert_bit); + value = (control->flags & DEASSERT_SET) ? mask : 0x0; + + return regmap_write_bits(data->regmap, control->deassert_offset, mask, value); +} + +/** + * ti_syscon_reset_status() - check device reset status + * @rcdev: reset controller entity + * @id: ID of the reset for which the status is being requested + * + * This function implements the reset driver op to return the status of a + * device's reset. + * + * Return: 0 if reset is deasserted, true if reset is asserted, else a + * corresponding error value + */ +static int ti_syscon_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct ti_syscon_reset_data *data = to_ti_syscon_reset_data(rcdev); + struct ti_syscon_reset_control *control; + unsigned int reset_state; + int ret; + + if (id >= data->nr_controls) + return -EINVAL; + + control = &data->controls[id]; + + if (control->flags & STATUS_NONE) + return -ENOTSUPP; /* status not supported for this reset */ + + ret = regmap_read(data->regmap, control->status_offset, &reset_state); + if (ret) + return ret; + + return !(reset_state & BIT(control->status_bit)) == + !(control->flags & STATUS_SET); +} + +static const struct reset_control_ops ti_syscon_reset_ops = { + .assert = ti_syscon_reset_assert, + .deassert = ti_syscon_reset_deassert, + .status = ti_syscon_reset_status, +}; + +static int ti_syscon_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct ti_syscon_reset_data *data; + struct regmap *regmap; + const __be32 *list; + struct ti_syscon_reset_control *controls; + int size, nr_controls, i; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + list = of_get_property(np, "ti,reset-bits", &size); + if (!list || (size / sizeof(*list)) % 7 != 0) { + dev_err(dev, "invalid DT reset description\n"); + return -EINVAL; + } + + nr_controls = (size / sizeof(*list)) / 7; + controls = devm_kcalloc(dev, nr_controls, sizeof(*controls), + GFP_KERNEL); + if (!controls) + return -ENOMEM; + + for (i = 0; i < nr_controls; i++) { + controls[i].assert_offset = be32_to_cpup(list++); + controls[i].assert_bit = be32_to_cpup(list++); + controls[i].deassert_offset = be32_to_cpup(list++); + controls[i].deassert_bit = be32_to_cpup(list++); + controls[i].status_offset = be32_to_cpup(list++); + controls[i].status_bit = be32_to_cpup(list++); + controls[i].flags = be32_to_cpup(list++); + } + + data->rcdev.ops = &ti_syscon_reset_ops; + data->rcdev.owner = THIS_MODULE; + data->rcdev.of_node = np; + data->rcdev.nr_resets = nr_controls; + data->regmap = regmap; + data->controls = controls; + data->nr_controls = nr_controls; + + platform_set_drvdata(pdev, data); + + return devm_reset_controller_register(dev, &data->rcdev); +} + +static const struct of_device_id ti_syscon_reset_of_match[] = { + { .compatible = "ti,syscon-reset", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ti_syscon_reset_of_match); + +static struct platform_driver ti_syscon_reset_driver = { + .probe = ti_syscon_reset_probe, + .driver = { + .name = "ti-syscon-reset", + .of_match_table = ti_syscon_reset_of_match, + }, +}; +module_platform_driver(ti_syscon_reset_driver); + +MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>"); +MODULE_AUTHOR("Suman Anna <s-anna@ti.com>"); +MODULE_DESCRIPTION("TI SYSCON Regmap Reset Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-tn48m.c b/drivers/reset/reset-tn48m.c new file mode 100644 index 000000000..130027291 --- /dev/null +++ b/drivers/reset/reset-tn48m.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Delta TN48M CPLD reset driver + * + * Copyright (C) 2021 Sartura Ltd. + * + * Author: Robert Marko <robert.marko@sartura.hr> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +#include <dt-bindings/reset/delta,tn48m-reset.h> + +#define TN48M_RESET_REG 0x10 + +#define TN48M_RESET_TIMEOUT_US 125000 +#define TN48M_RESET_SLEEP_US 10 + +struct tn48_reset_map { + u8 bit; +}; + +struct tn48_reset_data { + struct reset_controller_dev rcdev; + struct regmap *regmap; +}; + +static const struct tn48_reset_map tn48m_resets[] = { + [CPU_88F7040_RESET] = {0}, + [CPU_88F6820_RESET] = {1}, + [MAC_98DX3265_RESET] = {2}, + [PHY_88E1680_RESET] = {4}, + [PHY_88E1512_RESET] = {6}, + [POE_RESET] = {7}, +}; + +static inline struct tn48_reset_data *to_tn48_reset_data( + struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct tn48_reset_data, rcdev); +} + +static int tn48m_control_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tn48_reset_data *data = to_tn48_reset_data(rcdev); + unsigned int val; + + regmap_update_bits(data->regmap, TN48M_RESET_REG, + BIT(tn48m_resets[id].bit), 0); + + return regmap_read_poll_timeout(data->regmap, + TN48M_RESET_REG, + val, + val & BIT(tn48m_resets[id].bit), + TN48M_RESET_SLEEP_US, + TN48M_RESET_TIMEOUT_US); +} + +static int tn48m_control_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tn48_reset_data *data = to_tn48_reset_data(rcdev); + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, TN48M_RESET_REG, ®val); + if (ret < 0) + return ret; + + if (BIT(tn48m_resets[id].bit) & regval) + return 0; + else + return 1; +} + +static const struct reset_control_ops tn48_reset_ops = { + .reset = tn48m_control_reset, + .status = tn48m_control_status, +}; + +static int tn48m_reset_probe(struct platform_device *pdev) +{ + struct tn48_reset_data *data; + struct regmap *regmap; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) + return -ENODEV; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = regmap; + + data->rcdev.owner = THIS_MODULE; + data->rcdev.ops = &tn48_reset_ops; + data->rcdev.nr_resets = ARRAY_SIZE(tn48m_resets); + data->rcdev.of_node = pdev->dev.of_node; + + return devm_reset_controller_register(&pdev->dev, &data->rcdev); +} + +static const struct of_device_id tn48m_reset_of_match[] = { + { .compatible = "delta,tn48m-reset" }, + { } +}; +MODULE_DEVICE_TABLE(of, tn48m_reset_of_match); + +static struct platform_driver tn48m_reset_driver = { + .driver = { + .name = "delta-tn48m-reset", + .of_match_table = tn48m_reset_of_match, + }, + .probe = tn48m_reset_probe, +}; +module_platform_driver(tn48m_reset_driver); + +MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>"); +MODULE_DESCRIPTION("Delta TN48M CPLD reset driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/reset-tps380x.c b/drivers/reset/reset-tps380x.c new file mode 100644 index 000000000..09d511f06 --- /dev/null +++ b/drivers/reset/reset-tps380x.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TI TPS380x Supply Voltage Supervisor and Reset Controller Driver + * + * Copyright (C) 2022 Pengutronix, Marco Felsch <kernel@pengutronix.de> + * + * Based on Simple Reset Controller Driver + * + * Copyright (C) 2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/reset-controller.h> + +struct tps380x_reset { + struct reset_controller_dev rcdev; + struct gpio_desc *reset_gpio; + unsigned int reset_ms; +}; + +struct tps380x_reset_devdata { + unsigned int min_reset_ms; + unsigned int typ_reset_ms; + unsigned int max_reset_ms; +}; + +static inline +struct tps380x_reset *to_tps380x_reset(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct tps380x_reset, rcdev); +} + +static int +tps380x_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct tps380x_reset *tps380x = to_tps380x_reset(rcdev); + + gpiod_set_value_cansleep(tps380x->reset_gpio, 1); + + return 0; +} + +static int +tps380x_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct tps380x_reset *tps380x = to_tps380x_reset(rcdev); + + gpiod_set_value_cansleep(tps380x->reset_gpio, 0); + msleep(tps380x->reset_ms); + + return 0; +} + +static const struct reset_control_ops reset_tps380x_ops = { + .assert = tps380x_reset_assert, + .deassert = tps380x_reset_deassert, +}; + +static int tps380x_reset_of_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + /* No special handling needed, we have only one reset line per device */ + return 0; +} + +static int tps380x_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct tps380x_reset_devdata *devdata; + struct tps380x_reset *tps380x; + + devdata = device_get_match_data(dev); + if (!devdata) + return -EINVAL; + + tps380x = devm_kzalloc(dev, sizeof(*tps380x), GFP_KERNEL); + if (!tps380x) + return -ENOMEM; + + tps380x->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(tps380x->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(tps380x->reset_gpio), + "Failed to get GPIO\n"); + + tps380x->reset_ms = devdata->max_reset_ms; + + tps380x->rcdev.ops = &reset_tps380x_ops; + tps380x->rcdev.owner = THIS_MODULE; + tps380x->rcdev.dev = dev; + tps380x->rcdev.of_node = dev->of_node; + tps380x->rcdev.of_reset_n_cells = 0; + tps380x->rcdev.of_xlate = tps380x_reset_of_xlate; + tps380x->rcdev.nr_resets = 1; + + return devm_reset_controller_register(dev, &tps380x->rcdev); +} + +static const struct tps380x_reset_devdata tps3801_reset_data = { + .min_reset_ms = 120, + .typ_reset_ms = 200, + .max_reset_ms = 280, +}; + +static const struct of_device_id tps380x_reset_dt_ids[] = { + { .compatible = "ti,tps3801", .data = &tps3801_reset_data }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, tps380x_reset_dt_ids); + +static struct platform_driver tps380x_reset_driver = { + .probe = tps380x_reset_probe, + .driver = { + .name = "tps380x-reset", + .of_match_table = tps380x_reset_dt_ids, + }, +}; +module_platform_driver(tps380x_reset_driver); + +MODULE_AUTHOR("Marco Felsch <kernel@pengutronix.de>"); +MODULE_DESCRIPTION("TI TPS380x Supply Voltage Supervisor and Reset Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/reset/reset-uniphier-glue.c b/drivers/reset/reset-uniphier-glue.c new file mode 100644 index 000000000..15abac9fc --- /dev/null +++ b/drivers/reset/reset-uniphier-glue.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// reset-uniphier-glue.c - Glue layer reset driver for UniPhier +// Copyright 2018 Socionext Inc. +// Author: Kunihiko Hayashi <hayashi.kunihiko@socionext.com> + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/reset/reset-simple.h> + +#define MAX_CLKS 2 +#define MAX_RSTS 2 + +struct uniphier_glue_reset_soc_data { + int nclks; + const char * const *clock_names; + int nrsts; + const char * const *reset_names; +}; + +struct uniphier_glue_reset_priv { + struct clk_bulk_data clk[MAX_CLKS]; + struct reset_control_bulk_data rst[MAX_RSTS]; + struct reset_simple_data rdata; + const struct uniphier_glue_reset_soc_data *data; +}; + +static void uniphier_clk_disable(void *_priv) +{ + struct uniphier_glue_reset_priv *priv = _priv; + + clk_bulk_disable_unprepare(priv->data->nclks, priv->clk); +} + +static void uniphier_rst_assert(void *_priv) +{ + struct uniphier_glue_reset_priv *priv = _priv; + + reset_control_bulk_assert(priv->data->nrsts, priv->rst); +} + +static int uniphier_glue_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct uniphier_glue_reset_priv *priv; + struct resource *res; + int i, ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data = of_device_get_match_data(dev); + if (WARN_ON(!priv->data || priv->data->nclks > MAX_CLKS || + priv->data->nrsts > MAX_RSTS)) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->rdata.membase = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->rdata.membase)) + return PTR_ERR(priv->rdata.membase); + + for (i = 0; i < priv->data->nclks; i++) + priv->clk[i].id = priv->data->clock_names[i]; + ret = devm_clk_bulk_get(dev, priv->data->nclks, priv->clk); + if (ret) + return ret; + + for (i = 0; i < priv->data->nrsts; i++) + priv->rst[i].id = priv->data->reset_names[i]; + ret = devm_reset_control_bulk_get_shared(dev, priv->data->nrsts, + priv->rst); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(priv->data->nclks, priv->clk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, uniphier_clk_disable, priv); + if (ret) + return ret; + + ret = reset_control_bulk_deassert(priv->data->nrsts, priv->rst); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, uniphier_rst_assert, priv); + if (ret) + return ret; + + spin_lock_init(&priv->rdata.lock); + priv->rdata.rcdev.owner = THIS_MODULE; + priv->rdata.rcdev.nr_resets = resource_size(res) * BITS_PER_BYTE; + priv->rdata.rcdev.ops = &reset_simple_ops; + priv->rdata.rcdev.of_node = dev->of_node; + priv->rdata.active_low = true; + + platform_set_drvdata(pdev, priv); + + return devm_reset_controller_register(dev, &priv->rdata.rcdev); +} + +static const char * const uniphier_pro4_clock_reset_names[] = { + "gio", "link", +}; + +static const struct uniphier_glue_reset_soc_data uniphier_pro4_data = { + .nclks = ARRAY_SIZE(uniphier_pro4_clock_reset_names), + .clock_names = uniphier_pro4_clock_reset_names, + .nrsts = ARRAY_SIZE(uniphier_pro4_clock_reset_names), + .reset_names = uniphier_pro4_clock_reset_names, +}; + +static const char * const uniphier_pxs2_clock_reset_names[] = { + "link", +}; + +static const struct uniphier_glue_reset_soc_data uniphier_pxs2_data = { + .nclks = ARRAY_SIZE(uniphier_pxs2_clock_reset_names), + .clock_names = uniphier_pxs2_clock_reset_names, + .nrsts = ARRAY_SIZE(uniphier_pxs2_clock_reset_names), + .reset_names = uniphier_pxs2_clock_reset_names, +}; + +static const struct of_device_id uniphier_glue_reset_match[] = { + { + .compatible = "socionext,uniphier-pro4-usb3-reset", + .data = &uniphier_pro4_data, + }, + { + .compatible = "socionext,uniphier-pro5-usb3-reset", + .data = &uniphier_pro4_data, + }, + { + .compatible = "socionext,uniphier-pxs2-usb3-reset", + .data = &uniphier_pxs2_data, + }, + { + .compatible = "socionext,uniphier-ld20-usb3-reset", + .data = &uniphier_pxs2_data, + }, + { + .compatible = "socionext,uniphier-pxs3-usb3-reset", + .data = &uniphier_pxs2_data, + }, + { + .compatible = "socionext,uniphier-nx1-usb3-reset", + .data = &uniphier_pxs2_data, + }, + { + .compatible = "socionext,uniphier-pro4-ahci-reset", + .data = &uniphier_pro4_data, + }, + { + .compatible = "socionext,uniphier-pxs2-ahci-reset", + .data = &uniphier_pxs2_data, + }, + { + .compatible = "socionext,uniphier-pxs3-ahci-reset", + .data = &uniphier_pxs2_data, + }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uniphier_glue_reset_match); + +static struct platform_driver uniphier_glue_reset_driver = { + .probe = uniphier_glue_reset_probe, + .driver = { + .name = "uniphier-glue-reset", + .of_match_table = uniphier_glue_reset_match, + }, +}; +module_platform_driver(uniphier_glue_reset_driver); + +MODULE_AUTHOR("Kunihiko Hayashi <hayashi.kunihiko@socionext.com>"); +MODULE_DESCRIPTION("UniPhier Glue layer reset driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/reset-uniphier.c b/drivers/reset/reset-uniphier.c new file mode 100644 index 000000000..ff7580f38 --- /dev/null +++ b/drivers/reset/reset-uniphier.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 Socionext Inc. + * Author: Masahiro Yamada <yamada.masahiro@socionext.com> + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +struct uniphier_reset_data { + unsigned int id; + unsigned int reg; + unsigned int bit; + unsigned int flags; +#define UNIPHIER_RESET_ACTIVE_LOW BIT(0) +}; + +#define UNIPHIER_RESET_ID_END ((unsigned int)(-1)) + +#define UNIPHIER_RESET_END \ + { .id = UNIPHIER_RESET_ID_END } + +#define UNIPHIER_RESET(_id, _reg, _bit) \ + { \ + .id = (_id), \ + .reg = (_reg), \ + .bit = (_bit), \ + } + +#define UNIPHIER_RESETX(_id, _reg, _bit) \ + { \ + .id = (_id), \ + .reg = (_reg), \ + .bit = (_bit), \ + .flags = UNIPHIER_RESET_ACTIVE_LOW, \ + } + +/* System reset data */ +static const struct uniphier_reset_data uniphier_ld4_sys_reset_data[] = { + UNIPHIER_RESETX(2, 0x2000, 2), /* NAND */ + UNIPHIER_RESETX(8, 0x2000, 10), /* STDMAC (Ether, HSC, MIO) */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_pro4_sys_reset_data[] = { + UNIPHIER_RESETX(2, 0x2000, 2), /* NAND */ + UNIPHIER_RESETX(6, 0x2000, 12), /* Ether */ + UNIPHIER_RESETX(8, 0x2000, 10), /* STDMAC (HSC, MIO, RLE) */ + UNIPHIER_RESETX(12, 0x2000, 6), /* GIO (Ether, SATA, USB3) */ + UNIPHIER_RESETX(14, 0x2000, 17), /* USB30 */ + UNIPHIER_RESETX(15, 0x2004, 17), /* USB31 */ + UNIPHIER_RESETX(28, 0x2000, 18), /* SATA0 */ + UNIPHIER_RESETX(29, 0x2004, 18), /* SATA1 */ + UNIPHIER_RESETX(30, 0x2000, 19), /* SATA-PHY */ + UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_pro5_sys_reset_data[] = { + UNIPHIER_RESETX(2, 0x2000, 2), /* NAND */ + UNIPHIER_RESETX(8, 0x2000, 10), /* STDMAC (HSC) */ + UNIPHIER_RESETX(12, 0x2000, 6), /* GIO (PCIe, USB3) */ + UNIPHIER_RESETX(14, 0x2000, 17), /* USB30 */ + UNIPHIER_RESETX(15, 0x2004, 17), /* USB31 */ + UNIPHIER_RESETX(24, 0x2008, 2), /* PCIe */ + UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_pxs2_sys_reset_data[] = { + UNIPHIER_RESETX(2, 0x2000, 2), /* NAND */ + UNIPHIER_RESETX(6, 0x2000, 12), /* Ether */ + UNIPHIER_RESETX(8, 0x2000, 10), /* STDMAC (HSC, RLE) */ + UNIPHIER_RESETX(14, 0x2000, 17), /* USB30 */ + UNIPHIER_RESETX(15, 0x2004, 17), /* USB31 */ + UNIPHIER_RESETX(16, 0x2014, 4), /* USB30-PHY0 */ + UNIPHIER_RESETX(17, 0x2014, 0), /* USB30-PHY1 */ + UNIPHIER_RESETX(18, 0x2014, 2), /* USB30-PHY2 */ + UNIPHIER_RESETX(20, 0x2014, 5), /* USB31-PHY0 */ + UNIPHIER_RESETX(21, 0x2014, 1), /* USB31-PHY1 */ + UNIPHIER_RESETX(28, 0x2014, 12), /* SATA */ + UNIPHIER_RESET(30, 0x2014, 8), /* SATA-PHY (active high) */ + UNIPHIER_RESETX(40, 0x2000, 13), /* AIO */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_ld11_sys_reset_data[] = { + UNIPHIER_RESETX(2, 0x200c, 0), /* NAND */ + UNIPHIER_RESETX(4, 0x200c, 2), /* eMMC */ + UNIPHIER_RESETX(6, 0x200c, 6), /* Ether */ + UNIPHIER_RESETX(8, 0x200c, 8), /* STDMAC (HSC, MIO) */ + UNIPHIER_RESETX(9, 0x200c, 9), /* HSC */ + UNIPHIER_RESETX(40, 0x2008, 0), /* AIO */ + UNIPHIER_RESETX(41, 0x2008, 1), /* EVEA */ + UNIPHIER_RESETX(42, 0x2010, 2), /* EXIV */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_ld20_sys_reset_data[] = { + UNIPHIER_RESETX(2, 0x200c, 0), /* NAND */ + UNIPHIER_RESETX(4, 0x200c, 2), /* eMMC */ + UNIPHIER_RESETX(6, 0x200c, 6), /* Ether */ + UNIPHIER_RESETX(8, 0x200c, 8), /* STDMAC (HSC) */ + UNIPHIER_RESETX(9, 0x200c, 9), /* HSC */ + UNIPHIER_RESETX(14, 0x200c, 5), /* USB30 */ + UNIPHIER_RESETX(16, 0x200c, 12), /* USB30-PHY0 */ + UNIPHIER_RESETX(17, 0x200c, 13), /* USB30-PHY1 */ + UNIPHIER_RESETX(18, 0x200c, 14), /* USB30-PHY2 */ + UNIPHIER_RESETX(19, 0x200c, 15), /* USB30-PHY3 */ + UNIPHIER_RESETX(24, 0x200c, 4), /* PCIe */ + UNIPHIER_RESETX(40, 0x2008, 0), /* AIO */ + UNIPHIER_RESETX(41, 0x2008, 1), /* EVEA */ + UNIPHIER_RESETX(42, 0x2010, 2), /* EXIV */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_pxs3_sys_reset_data[] = { + UNIPHIER_RESETX(2, 0x200c, 0), /* NAND */ + UNIPHIER_RESETX(4, 0x200c, 2), /* eMMC */ + UNIPHIER_RESETX(6, 0x200c, 9), /* Ether0 */ + UNIPHIER_RESETX(7, 0x200c, 10), /* Ether1 */ + UNIPHIER_RESETX(8, 0x200c, 12), /* STDMAC */ + UNIPHIER_RESETX(12, 0x200c, 4), /* USB30 link */ + UNIPHIER_RESETX(13, 0x200c, 5), /* USB31 link */ + UNIPHIER_RESETX(16, 0x200c, 16), /* USB30-PHY0 */ + UNIPHIER_RESETX(17, 0x200c, 18), /* USB30-PHY1 */ + UNIPHIER_RESETX(18, 0x200c, 20), /* USB30-PHY2 */ + UNIPHIER_RESETX(20, 0x200c, 17), /* USB31-PHY0 */ + UNIPHIER_RESETX(21, 0x200c, 19), /* USB31-PHY1 */ + UNIPHIER_RESETX(24, 0x200c, 3), /* PCIe */ + UNIPHIER_RESETX(28, 0x200c, 7), /* SATA0 */ + UNIPHIER_RESETX(29, 0x200c, 8), /* SATA1 */ + UNIPHIER_RESETX(30, 0x200c, 21), /* SATA-PHY */ + UNIPHIER_RESETX(40, 0x2008, 0), /* AIO */ + UNIPHIER_RESETX(42, 0x2010, 2), /* EXIV */ + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_nx1_sys_reset_data[] = { + UNIPHIER_RESETX(4, 0x2008, 8), /* eMMC */ + UNIPHIER_RESETX(6, 0x200c, 0), /* Ether */ + UNIPHIER_RESETX(12, 0x200c, 16), /* USB30 link */ + UNIPHIER_RESETX(16, 0x200c, 24), /* USB30-PHY0 */ + UNIPHIER_RESETX(17, 0x200c, 25), /* USB30-PHY1 */ + UNIPHIER_RESETX(18, 0x200c, 26), /* USB30-PHY2 */ + UNIPHIER_RESETX(24, 0x200c, 8), /* PCIe */ + UNIPHIER_RESETX(52, 0x2010, 0), /* VOC */ + UNIPHIER_RESETX(58, 0x2010, 8), /* HDMI-Tx */ + UNIPHIER_RESET_END, +}; + +/* Media I/O reset data */ +#define UNIPHIER_MIO_RESET_SD(id, ch) \ + UNIPHIER_RESETX((id), 0x110 + 0x200 * (ch), 0) + +#define UNIPHIER_MIO_RESET_SD_BRIDGE(id, ch) \ + UNIPHIER_RESETX((id), 0x110 + 0x200 * (ch), 26) + +#define UNIPHIER_MIO_RESET_EMMC_HW_RESET(id, ch) \ + UNIPHIER_RESETX((id), 0x80 + 0x200 * (ch), 0) + +#define UNIPHIER_MIO_RESET_USB2(id, ch) \ + UNIPHIER_RESETX((id), 0x114 + 0x200 * (ch), 0) + +#define UNIPHIER_MIO_RESET_USB2_BRIDGE(id, ch) \ + UNIPHIER_RESETX((id), 0x110 + 0x200 * (ch), 24) + +#define UNIPHIER_MIO_RESET_DMAC(id) \ + UNIPHIER_RESETX((id), 0x110, 17) + +static const struct uniphier_reset_data uniphier_ld4_mio_reset_data[] = { + UNIPHIER_MIO_RESET_SD(0, 0), + UNIPHIER_MIO_RESET_SD(1, 1), + UNIPHIER_MIO_RESET_SD(2, 2), + UNIPHIER_MIO_RESET_SD_BRIDGE(3, 0), + UNIPHIER_MIO_RESET_SD_BRIDGE(4, 1), + UNIPHIER_MIO_RESET_SD_BRIDGE(5, 2), + UNIPHIER_MIO_RESET_EMMC_HW_RESET(6, 1), + UNIPHIER_MIO_RESET_DMAC(7), + UNIPHIER_MIO_RESET_USB2(8, 0), + UNIPHIER_MIO_RESET_USB2(9, 1), + UNIPHIER_MIO_RESET_USB2(10, 2), + UNIPHIER_MIO_RESET_USB2_BRIDGE(12, 0), + UNIPHIER_MIO_RESET_USB2_BRIDGE(13, 1), + UNIPHIER_MIO_RESET_USB2_BRIDGE(14, 2), + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_pro5_sd_reset_data[] = { + UNIPHIER_MIO_RESET_SD(0, 0), + UNIPHIER_MIO_RESET_SD(1, 1), + UNIPHIER_MIO_RESET_EMMC_HW_RESET(6, 1), + UNIPHIER_RESET_END, +}; + +/* Peripheral reset data */ +#define UNIPHIER_PERI_RESET_UART(id, ch) \ + UNIPHIER_RESETX((id), 0x114, 19 + (ch)) + +#define UNIPHIER_PERI_RESET_I2C(id, ch) \ + UNIPHIER_RESETX((id), 0x114, 5 + (ch)) + +#define UNIPHIER_PERI_RESET_FI2C(id, ch) \ + UNIPHIER_RESETX((id), 0x114, 24 + (ch)) + +#define UNIPHIER_PERI_RESET_SCSSI(id, ch) \ + UNIPHIER_RESETX((id), 0x110, 17 + (ch)) + +#define UNIPHIER_PERI_RESET_MCSSI(id) \ + UNIPHIER_RESETX((id), 0x114, 14) + +static const struct uniphier_reset_data uniphier_ld4_peri_reset_data[] = { + UNIPHIER_PERI_RESET_UART(0, 0), + UNIPHIER_PERI_RESET_UART(1, 1), + UNIPHIER_PERI_RESET_UART(2, 2), + UNIPHIER_PERI_RESET_UART(3, 3), + UNIPHIER_PERI_RESET_I2C(4, 0), + UNIPHIER_PERI_RESET_I2C(5, 1), + UNIPHIER_PERI_RESET_I2C(6, 2), + UNIPHIER_PERI_RESET_I2C(7, 3), + UNIPHIER_PERI_RESET_I2C(8, 4), + UNIPHIER_PERI_RESET_SCSSI(11, 0), + UNIPHIER_RESET_END, +}; + +static const struct uniphier_reset_data uniphier_pro4_peri_reset_data[] = { + UNIPHIER_PERI_RESET_UART(0, 0), + UNIPHIER_PERI_RESET_UART(1, 1), + UNIPHIER_PERI_RESET_UART(2, 2), + UNIPHIER_PERI_RESET_UART(3, 3), + UNIPHIER_PERI_RESET_FI2C(4, 0), + UNIPHIER_PERI_RESET_FI2C(5, 1), + UNIPHIER_PERI_RESET_FI2C(6, 2), + UNIPHIER_PERI_RESET_FI2C(7, 3), + UNIPHIER_PERI_RESET_FI2C(8, 4), + UNIPHIER_PERI_RESET_FI2C(9, 5), + UNIPHIER_PERI_RESET_FI2C(10, 6), + UNIPHIER_PERI_RESET_SCSSI(11, 0), + UNIPHIER_PERI_RESET_SCSSI(12, 1), + UNIPHIER_PERI_RESET_SCSSI(13, 2), + UNIPHIER_PERI_RESET_SCSSI(14, 3), + UNIPHIER_PERI_RESET_MCSSI(15), + UNIPHIER_RESET_END, +}; + +/* Analog signal amplifiers reset data */ +static const struct uniphier_reset_data uniphier_ld11_adamv_reset_data[] = { + UNIPHIER_RESETX(0, 0x10, 6), /* EVEA */ + UNIPHIER_RESET_END, +}; + +/* core implementaton */ +struct uniphier_reset_priv { + struct reset_controller_dev rcdev; + struct device *dev; + struct regmap *regmap; + const struct uniphier_reset_data *data; +}; + +#define to_uniphier_reset_priv(_rcdev) \ + container_of(_rcdev, struct uniphier_reset_priv, rcdev) + +static int uniphier_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, int assert) +{ + struct uniphier_reset_priv *priv = to_uniphier_reset_priv(rcdev); + const struct uniphier_reset_data *p; + + for (p = priv->data; p->id != UNIPHIER_RESET_ID_END; p++) { + unsigned int mask, val; + + if (p->id != id) + continue; + + mask = BIT(p->bit); + + if (assert) + val = mask; + else + val = ~mask; + + if (p->flags & UNIPHIER_RESET_ACTIVE_LOW) + val = ~val; + + return regmap_write_bits(priv->regmap, p->reg, mask, val); + } + + dev_err(priv->dev, "reset_id=%lu was not handled\n", id); + return -EINVAL; +} + +static int uniphier_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return uniphier_reset_update(rcdev, id, 1); +} + +static int uniphier_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return uniphier_reset_update(rcdev, id, 0); +} + +static int uniphier_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct uniphier_reset_priv *priv = to_uniphier_reset_priv(rcdev); + const struct uniphier_reset_data *p; + + for (p = priv->data; p->id != UNIPHIER_RESET_ID_END; p++) { + unsigned int val; + int ret, asserted; + + if (p->id != id) + continue; + + ret = regmap_read(priv->regmap, p->reg, &val); + if (ret) + return ret; + + asserted = !!(val & BIT(p->bit)); + + if (p->flags & UNIPHIER_RESET_ACTIVE_LOW) + asserted = !asserted; + + return asserted; + } + + dev_err(priv->dev, "reset_id=%lu was not found\n", id); + return -EINVAL; +} + +static const struct reset_control_ops uniphier_reset_ops = { + .assert = uniphier_reset_assert, + .deassert = uniphier_reset_deassert, + .status = uniphier_reset_status, +}; + +static int uniphier_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct uniphier_reset_priv *priv; + const struct uniphier_reset_data *p, *data; + struct regmap *regmap; + struct device_node *parent; + unsigned int nr_resets = 0; + + data = of_device_get_match_data(dev); + if (WARN_ON(!data)) + return -EINVAL; + + parent = of_get_parent(dev->of_node); /* parent should be syscon node */ + regmap = syscon_node_to_regmap(parent); + of_node_put(parent); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to get regmap (error %ld)\n", + PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + for (p = data; p->id != UNIPHIER_RESET_ID_END; p++) + nr_resets = max(nr_resets, p->id + 1); + + priv->rcdev.ops = &uniphier_reset_ops; + priv->rcdev.owner = dev->driver->owner; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.nr_resets = nr_resets; + priv->dev = dev; + priv->regmap = regmap; + priv->data = data; + + return devm_reset_controller_register(&pdev->dev, &priv->rcdev); +} + +static const struct of_device_id uniphier_reset_match[] = { + /* System reset */ + { + .compatible = "socionext,uniphier-ld4-reset", + .data = uniphier_ld4_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-pro4-reset", + .data = uniphier_pro4_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-sld8-reset", + .data = uniphier_ld4_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-pro5-reset", + .data = uniphier_pro5_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-pxs2-reset", + .data = uniphier_pxs2_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-ld11-reset", + .data = uniphier_ld11_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-ld20-reset", + .data = uniphier_ld20_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-pxs3-reset", + .data = uniphier_pxs3_sys_reset_data, + }, + { + .compatible = "socionext,uniphier-nx1-reset", + .data = uniphier_nx1_sys_reset_data, + }, + /* Media I/O reset, SD reset */ + { + .compatible = "socionext,uniphier-ld4-mio-reset", + .data = uniphier_ld4_mio_reset_data, + }, + { + .compatible = "socionext,uniphier-pro4-mio-reset", + .data = uniphier_ld4_mio_reset_data, + }, + { + .compatible = "socionext,uniphier-sld8-mio-reset", + .data = uniphier_ld4_mio_reset_data, + }, + { + .compatible = "socionext,uniphier-pro5-sd-reset", + .data = uniphier_pro5_sd_reset_data, + }, + { + .compatible = "socionext,uniphier-pxs2-sd-reset", + .data = uniphier_pro5_sd_reset_data, + }, + { + .compatible = "socionext,uniphier-ld11-mio-reset", + .data = uniphier_ld4_mio_reset_data, + }, + { + .compatible = "socionext,uniphier-ld11-sd-reset", + .data = uniphier_pro5_sd_reset_data, + }, + { + .compatible = "socionext,uniphier-ld20-sd-reset", + .data = uniphier_pro5_sd_reset_data, + }, + { + .compatible = "socionext,uniphier-pxs3-sd-reset", + .data = uniphier_pro5_sd_reset_data, + }, + { + .compatible = "socionext,uniphier-nx1-sd-reset", + .data = uniphier_pro5_sd_reset_data, + }, + /* Peripheral reset */ + { + .compatible = "socionext,uniphier-ld4-peri-reset", + .data = uniphier_ld4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-pro4-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-sld8-peri-reset", + .data = uniphier_ld4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-pro5-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-pxs2-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-ld11-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-ld20-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-pxs3-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, + { + .compatible = "socionext,uniphier-nx1-peri-reset", + .data = uniphier_pro4_peri_reset_data, + }, + /* Analog signal amplifiers reset */ + { + .compatible = "socionext,uniphier-ld11-adamv-reset", + .data = uniphier_ld11_adamv_reset_data, + }, + { + .compatible = "socionext,uniphier-ld20-adamv-reset", + .data = uniphier_ld11_adamv_reset_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uniphier_reset_match); + +static struct platform_driver uniphier_reset_driver = { + .probe = uniphier_reset_probe, + .driver = { + .name = "uniphier-reset", + .of_match_table = uniphier_reset_match, + }, +}; +module_platform_driver(uniphier_reset_driver); + +MODULE_AUTHOR("Masahiro Yamada <yamada.masahiro@socionext.com>"); +MODULE_DESCRIPTION("UniPhier Reset Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/reset-zynq.c b/drivers/reset/reset-zynq.c new file mode 100644 index 000000000..706bbbbb4 --- /dev/null +++ b/drivers/reset/reset-zynq.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, National Instruments Corp. + * + * Xilinx Zynq Reset controller driver + * + * Author: Moritz Fischer <moritz.fischer@ettus.com> + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/regmap.h> +#include <linux/types.h> + +struct zynq_reset_data { + struct regmap *slcr; + struct reset_controller_dev rcdev; + u32 offset; +}; + +#define to_zynq_reset_data(p) \ + container_of((p), struct zynq_reset_data, rcdev) + +static int zynq_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynq_reset_data *priv = to_zynq_reset_data(rcdev); + + int bank = id / BITS_PER_LONG; + int offset = id % BITS_PER_LONG; + + pr_debug("%s: %s reset bank %u offset %u\n", KBUILD_MODNAME, __func__, + bank, offset); + + return regmap_update_bits(priv->slcr, + priv->offset + (bank * 4), + BIT(offset), + BIT(offset)); +} + +static int zynq_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynq_reset_data *priv = to_zynq_reset_data(rcdev); + + int bank = id / BITS_PER_LONG; + int offset = id % BITS_PER_LONG; + + pr_debug("%s: %s reset bank %u offset %u\n", KBUILD_MODNAME, __func__, + bank, offset); + + return regmap_update_bits(priv->slcr, + priv->offset + (bank * 4), + BIT(offset), + ~BIT(offset)); +} + +static int zynq_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynq_reset_data *priv = to_zynq_reset_data(rcdev); + + int bank = id / BITS_PER_LONG; + int offset = id % BITS_PER_LONG; + int ret; + u32 reg; + + pr_debug("%s: %s reset bank %u offset %u\n", KBUILD_MODNAME, __func__, + bank, offset); + + ret = regmap_read(priv->slcr, priv->offset + (bank * 4), ®); + if (ret) + return ret; + + return !!(reg & BIT(offset)); +} + +static const struct reset_control_ops zynq_reset_ops = { + .assert = zynq_reset_assert, + .deassert = zynq_reset_deassert, + .status = zynq_reset_status, +}; + +static int zynq_reset_probe(struct platform_device *pdev) +{ + struct resource *res; + struct zynq_reset_data *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->slcr = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "syscon"); + if (IS_ERR(priv->slcr)) { + dev_err(&pdev->dev, "unable to get zynq-slcr regmap"); + return PTR_ERR(priv->slcr); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing IO resource\n"); + return -ENODEV; + } + + priv->offset = res->start; + + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.nr_resets = resource_size(res) / 4 * BITS_PER_LONG; + priv->rcdev.ops = &zynq_reset_ops; + priv->rcdev.of_node = pdev->dev.of_node; + + return devm_reset_controller_register(&pdev->dev, &priv->rcdev); +} + +static const struct of_device_id zynq_reset_dt_ids[] = { + { .compatible = "xlnx,zynq-reset", }, + { /* sentinel */ }, +}; + +static struct platform_driver zynq_reset_driver = { + .probe = zynq_reset_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = zynq_reset_dt_ids, + }, +}; +builtin_platform_driver(zynq_reset_driver); diff --git a/drivers/reset/reset-zynqmp.c b/drivers/reset/reset-zynqmp.c new file mode 100644 index 000000000..59dc0ff9a --- /dev/null +++ b/drivers/reset/reset-zynqmp.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Xilinx, Inc. + * + */ + +#include <linux/err.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset-controller.h> +#include <linux/firmware/xlnx-zynqmp.h> +#include <linux/of_device.h> + +#define ZYNQMP_NR_RESETS (ZYNQMP_PM_RESET_END - ZYNQMP_PM_RESET_START) +#define ZYNQMP_RESET_ID ZYNQMP_PM_RESET_START +#define VERSAL_NR_RESETS 95 + +struct zynqmp_reset_soc_data { + u32 reset_id; + u32 num_resets; +}; + +struct zynqmp_reset_data { + struct reset_controller_dev rcdev; + const struct zynqmp_reset_soc_data *data; +}; + +static inline struct zynqmp_reset_data * +to_zynqmp_reset_data(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct zynqmp_reset_data, rcdev); +} + +static int zynqmp_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + + return zynqmp_pm_reset_assert(priv->data->reset_id + id, + PM_RESET_ACTION_ASSERT); +} + +static int zynqmp_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + + return zynqmp_pm_reset_assert(priv->data->reset_id + id, + PM_RESET_ACTION_RELEASE); +} + +static int zynqmp_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + int err; + u32 val; + + err = zynqmp_pm_reset_get_status(priv->data->reset_id + id, &val); + if (err) + return err; + + return val; +} + +static int zynqmp_reset_reset(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct zynqmp_reset_data *priv = to_zynqmp_reset_data(rcdev); + + return zynqmp_pm_reset_assert(priv->data->reset_id + id, + PM_RESET_ACTION_PULSE); +} + +static int zynqmp_reset_of_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + return reset_spec->args[0]; +} + +static const struct zynqmp_reset_soc_data zynqmp_reset_data = { + .reset_id = ZYNQMP_RESET_ID, + .num_resets = ZYNQMP_NR_RESETS, +}; + +static const struct zynqmp_reset_soc_data versal_reset_data = { + .reset_id = 0, + .num_resets = VERSAL_NR_RESETS, +}; + +static const struct reset_control_ops zynqmp_reset_ops = { + .reset = zynqmp_reset_reset, + .assert = zynqmp_reset_assert, + .deassert = zynqmp_reset_deassert, + .status = zynqmp_reset_status, +}; + +static int zynqmp_reset_probe(struct platform_device *pdev) +{ + struct zynqmp_reset_data *priv; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data = of_device_get_match_data(&pdev->dev); + if (!priv->data) + return -EINVAL; + + platform_set_drvdata(pdev, priv); + + priv->rcdev.ops = &zynqmp_reset_ops; + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.of_node = pdev->dev.of_node; + priv->rcdev.nr_resets = priv->data->num_resets; + priv->rcdev.of_reset_n_cells = 1; + priv->rcdev.of_xlate = zynqmp_reset_of_xlate; + + return devm_reset_controller_register(&pdev->dev, &priv->rcdev); +} + +static const struct of_device_id zynqmp_reset_dt_ids[] = { + { .compatible = "xlnx,zynqmp-reset", .data = &zynqmp_reset_data, }, + { .compatible = "xlnx,versal-reset", .data = &versal_reset_data, }, + { /* sentinel */ }, +}; + +static struct platform_driver zynqmp_reset_driver = { + .probe = zynqmp_reset_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = zynqmp_reset_dt_ids, + }, +}; + +static int __init zynqmp_reset_init(void) +{ + return platform_driver_register(&zynqmp_reset_driver); +} + +arch_initcall(zynqmp_reset_init); diff --git a/drivers/reset/sti/Kconfig b/drivers/reset/sti/Kconfig new file mode 100644 index 000000000..9455e1c7a --- /dev/null +++ b/drivers/reset/sti/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +if ARCH_STI + +config STI_RESET_SYSCFG + bool + +config STIH407_RESET + bool + select STI_RESET_SYSCFG + +endif diff --git a/drivers/reset/sti/Makefile b/drivers/reset/sti/Makefile new file mode 100644 index 000000000..3eb30f7e8 --- /dev/null +++ b/drivers/reset/sti/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_STI_RESET_SYSCFG) += reset-syscfg.o + +obj-$(CONFIG_STIH407_RESET) += reset-stih407.o diff --git a/drivers/reset/sti/reset-stih407.c b/drivers/reset/sti/reset-stih407.c new file mode 100644 index 000000000..c3bf9a918 --- /dev/null +++ b/drivers/reset/sti/reset-stih407.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2014 STMicroelectronics (R&D) Limited + * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> + */ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <dt-bindings/reset/stih407-resets.h> +#include "reset-syscfg.h" + +/* STiH407 Peripheral powerdown definitions. */ +static const char stih407_core[] = "st,stih407-core-syscfg"; +static const char stih407_sbc_reg[] = "st,stih407-sbc-reg-syscfg"; +static const char stih407_lpm[] = "st,stih407-lpm-syscfg"; + +#define STIH407_PDN_0(_bit) \ + _SYSCFG_RST_CH(stih407_core, SYSCFG_5000, _bit, SYSSTAT_5500, _bit) +#define STIH407_PDN_1(_bit) \ + _SYSCFG_RST_CH(stih407_core, SYSCFG_5001, _bit, SYSSTAT_5501, _bit) +#define STIH407_PDN_ETH(_bit, _stat) \ + _SYSCFG_RST_CH(stih407_sbc_reg, SYSCFG_4032, _bit, SYSSTAT_4520, _stat) + +/* Powerdown requests control 0 */ +#define SYSCFG_5000 0x0 +#define SYSSTAT_5500 0x7d0 +/* Powerdown requests control 1 (High Speed Links) */ +#define SYSCFG_5001 0x4 +#define SYSSTAT_5501 0x7d4 + +/* Ethernet powerdown/status/reset */ +#define SYSCFG_4032 0x80 +#define SYSSTAT_4520 0x820 +#define SYSCFG_4002 0x8 + +static const struct syscfg_reset_channel_data stih407_powerdowns[] = { + [STIH407_EMISS_POWERDOWN] = STIH407_PDN_0(1), + [STIH407_NAND_POWERDOWN] = STIH407_PDN_0(0), + [STIH407_USB3_POWERDOWN] = STIH407_PDN_1(6), + [STIH407_USB2_PORT1_POWERDOWN] = STIH407_PDN_1(5), + [STIH407_USB2_PORT0_POWERDOWN] = STIH407_PDN_1(4), + [STIH407_PCIE1_POWERDOWN] = STIH407_PDN_1(3), + [STIH407_PCIE0_POWERDOWN] = STIH407_PDN_1(2), + [STIH407_SATA1_POWERDOWN] = STIH407_PDN_1(1), + [STIH407_SATA0_POWERDOWN] = STIH407_PDN_1(0), + [STIH407_ETH1_POWERDOWN] = STIH407_PDN_ETH(0, 2), +}; + +/* Reset Generator control 0/1 */ +#define SYSCFG_5128 0x200 +#define SYSCFG_5131 0x20c +#define SYSCFG_5132 0x210 + +#define LPM_SYSCFG_1 0x4 /* Softreset IRB & SBC UART */ + +#define STIH407_SRST_CORE(_reg, _bit) \ + _SYSCFG_RST_CH_NO_ACK(stih407_core, _reg, _bit) + +#define STIH407_SRST_SBC(_reg, _bit) \ + _SYSCFG_RST_CH_NO_ACK(stih407_sbc_reg, _reg, _bit) + +#define STIH407_SRST_LPM(_reg, _bit) \ + _SYSCFG_RST_CH_NO_ACK(stih407_lpm, _reg, _bit) + +static const struct syscfg_reset_channel_data stih407_softresets[] = { + [STIH407_ETH1_SOFTRESET] = STIH407_SRST_SBC(SYSCFG_4002, 4), + [STIH407_MMC1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 3), + [STIH407_USB2_PORT0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 28), + [STIH407_USB2_PORT1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 29), + [STIH407_PICOPHY_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 30), + [STIH407_IRB_SOFTRESET] = STIH407_SRST_LPM(LPM_SYSCFG_1, 6), + [STIH407_PCIE0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 6), + [STIH407_PCIE1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 15), + [STIH407_SATA0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 7), + [STIH407_SATA1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 16), + [STIH407_MIPHY0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 4), + [STIH407_MIPHY1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 13), + [STIH407_MIPHY2_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 22), + [STIH407_SATA0_PWR_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 5), + [STIH407_SATA1_PWR_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 14), + [STIH407_DELTA_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 3), + [STIH407_BLITTER_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 10), + [STIH407_HDTVOUT_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 11), + [STIH407_HDQVDP_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 12), + [STIH407_VDP_AUX_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 14), + [STIH407_COMPO_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 15), + [STIH407_HDMI_TX_PHY_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 21), + [STIH407_JPEG_DEC_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 23), + [STIH407_VP8_DEC_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 24), + [STIH407_GPU_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 30), + [STIH407_HVA_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 0), + [STIH407_ERAM_HVA_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5132, 1), + [STIH407_LPM_SOFTRESET] = STIH407_SRST_SBC(SYSCFG_4002, 2), + [STIH407_KEYSCAN_SOFTRESET] = STIH407_SRST_LPM(LPM_SYSCFG_1, 8), + [STIH407_ST231_AUD_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 26), + [STIH407_ST231_DMU_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 27), + [STIH407_ST231_GP0_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5131, 28), + [STIH407_ST231_GP1_SOFTRESET] = STIH407_SRST_CORE(SYSCFG_5128, 2), +}; + +/* PicoPHY reset/control */ +#define SYSCFG_5061 0x0f4 + +static const struct syscfg_reset_channel_data stih407_picophyresets[] = { + [STIH407_PICOPHY0_RESET] = STIH407_SRST_CORE(SYSCFG_5061, 5), + [STIH407_PICOPHY1_RESET] = STIH407_SRST_CORE(SYSCFG_5061, 6), + [STIH407_PICOPHY2_RESET] = STIH407_SRST_CORE(SYSCFG_5061, 7), +}; + +static const struct syscfg_reset_controller_data stih407_powerdown_controller = { + .wait_for_ack = true, + .nr_channels = ARRAY_SIZE(stih407_powerdowns), + .channels = stih407_powerdowns, +}; + +static const struct syscfg_reset_controller_data stih407_softreset_controller = { + .wait_for_ack = false, + .active_low = true, + .nr_channels = ARRAY_SIZE(stih407_softresets), + .channels = stih407_softresets, +}; + +static const struct syscfg_reset_controller_data stih407_picophyreset_controller = { + .wait_for_ack = false, + .nr_channels = ARRAY_SIZE(stih407_picophyresets), + .channels = stih407_picophyresets, +}; + +static const struct of_device_id stih407_reset_match[] = { + { + .compatible = "st,stih407-powerdown", + .data = &stih407_powerdown_controller, + }, + { + .compatible = "st,stih407-softreset", + .data = &stih407_softreset_controller, + }, + { + .compatible = "st,stih407-picophyreset", + .data = &stih407_picophyreset_controller, + }, + { /* sentinel */ }, +}; + +static struct platform_driver stih407_reset_driver = { + .probe = syscfg_reset_probe, + .driver = { + .name = "reset-stih407", + .of_match_table = stih407_reset_match, + }, +}; + +static int __init stih407_reset_init(void) +{ + return platform_driver_register(&stih407_reset_driver); +} + +arch_initcall(stih407_reset_init); diff --git a/drivers/reset/sti/reset-syscfg.c b/drivers/reset/sti/reset-syscfg.c new file mode 100644 index 000000000..b4b46e0f2 --- /dev/null +++ b/drivers/reset/sti/reset-syscfg.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2013 STMicroelectronics Limited + * Author: Stephen Gallimore <stephen.gallimore@st.com> + * + * Inspired by mach-imx/src.c + */ +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/types.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#include "reset-syscfg.h" + +/** + * struct syscfg_reset_channel - Reset channel regmap configuration + * + * @reset: regmap field for the channel's reset bit. + * @ack: regmap field for the channel's ack bit (optional). + */ +struct syscfg_reset_channel { + struct regmap_field *reset; + struct regmap_field *ack; +}; + +/** + * struct syscfg_reset_controller - A reset controller which groups together + * a set of related reset bits, which may be located in different system + * configuration registers. + * + * @rst: base reset controller structure. + * @active_low: are the resets in this controller active low, i.e. clearing + * the reset bit puts the hardware into reset. + * @channels: An array of reset channels for this controller. + */ +struct syscfg_reset_controller { + struct reset_controller_dev rst; + bool active_low; + struct syscfg_reset_channel *channels; +}; + +#define to_syscfg_reset_controller(_rst) \ + container_of(_rst, struct syscfg_reset_controller, rst) + +static int syscfg_reset_program_hw(struct reset_controller_dev *rcdev, + unsigned long idx, int assert) +{ + struct syscfg_reset_controller *rst = to_syscfg_reset_controller(rcdev); + const struct syscfg_reset_channel *ch; + u32 ctrl_val = rst->active_low ? !assert : !!assert; + int err; + + if (idx >= rcdev->nr_resets) + return -EINVAL; + + ch = &rst->channels[idx]; + + err = regmap_field_write(ch->reset, ctrl_val); + if (err) + return err; + + if (ch->ack) { + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + u32 ack_val; + + while (true) { + err = regmap_field_read(ch->ack, &ack_val); + if (err) + return err; + + if (ack_val == ctrl_val) + break; + + if (time_after(jiffies, timeout)) + return -ETIME; + + cpu_relax(); + } + } + + return 0; +} + +static int syscfg_reset_assert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return syscfg_reset_program_hw(rcdev, idx, true); +} + +static int syscfg_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + return syscfg_reset_program_hw(rcdev, idx, false); +} + +static int syscfg_reset_dev(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + int err; + + err = syscfg_reset_assert(rcdev, idx); + if (err) + return err; + + return syscfg_reset_deassert(rcdev, idx); +} + +static int syscfg_reset_status(struct reset_controller_dev *rcdev, + unsigned long idx) +{ + struct syscfg_reset_controller *rst = to_syscfg_reset_controller(rcdev); + const struct syscfg_reset_channel *ch; + u32 ret_val = 0; + int err; + + if (idx >= rcdev->nr_resets) + return -EINVAL; + + ch = &rst->channels[idx]; + if (ch->ack) + err = regmap_field_read(ch->ack, &ret_val); + else + err = regmap_field_read(ch->reset, &ret_val); + if (err) + return err; + + return rst->active_low ? !ret_val : !!ret_val; +} + +static const struct reset_control_ops syscfg_reset_ops = { + .reset = syscfg_reset_dev, + .assert = syscfg_reset_assert, + .deassert = syscfg_reset_deassert, + .status = syscfg_reset_status, +}; + +static int syscfg_reset_controller_register(struct device *dev, + const struct syscfg_reset_controller_data *data) +{ + struct syscfg_reset_controller *rc; + int i, err; + + rc = devm_kzalloc(dev, sizeof(*rc), GFP_KERNEL); + if (!rc) + return -ENOMEM; + + rc->channels = devm_kcalloc(dev, data->nr_channels, + sizeof(*rc->channels), GFP_KERNEL); + if (!rc->channels) + return -ENOMEM; + + rc->rst.ops = &syscfg_reset_ops; + rc->rst.of_node = dev->of_node; + rc->rst.nr_resets = data->nr_channels; + rc->active_low = data->active_low; + + for (i = 0; i < data->nr_channels; i++) { + struct regmap *map; + struct regmap_field *f; + const char *compatible = data->channels[i].compatible; + + map = syscon_regmap_lookup_by_compatible(compatible); + if (IS_ERR(map)) + return PTR_ERR(map); + + f = devm_regmap_field_alloc(dev, map, data->channels[i].reset); + if (IS_ERR(f)) + return PTR_ERR(f); + + rc->channels[i].reset = f; + + if (!data->wait_for_ack) + continue; + + f = devm_regmap_field_alloc(dev, map, data->channels[i].ack); + if (IS_ERR(f)) + return PTR_ERR(f); + + rc->channels[i].ack = f; + } + + err = reset_controller_register(&rc->rst); + if (!err) + dev_info(dev, "registered\n"); + + return err; +} + +int syscfg_reset_probe(struct platform_device *pdev) +{ + struct device *dev = pdev ? &pdev->dev : NULL; + const struct of_device_id *match; + + if (!dev || !dev->driver) + return -ENODEV; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return -EINVAL; + + return syscfg_reset_controller_register(dev, match->data); +} diff --git a/drivers/reset/sti/reset-syscfg.h b/drivers/reset/sti/reset-syscfg.h new file mode 100644 index 000000000..4e3e69de1 --- /dev/null +++ b/drivers/reset/sti/reset-syscfg.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2013 STMicroelectronics (R&D) Limited + * Author: Stephen Gallimore <stephen.gallimore@st.com> + */ +#ifndef __STI_RESET_SYSCFG_H +#define __STI_RESET_SYSCFG_H + +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/reset-controller.h> + +/** + * Reset channel description for a system configuration register based + * reset controller. + * + * @compatible: Compatible string of the syscon regmap containing this + * channel's control and ack (status) bits. + * @reset: Regmap field description of the channel's reset bit. + * @ack: Regmap field description of the channel's acknowledge bit. + */ +struct syscfg_reset_channel_data { + const char *compatible; + struct reg_field reset; + struct reg_field ack; +}; + +#define _SYSCFG_RST_CH(_c, _rr, _rb, _ar, _ab) \ + { .compatible = _c, \ + .reset = REG_FIELD(_rr, _rb, _rb), \ + .ack = REG_FIELD(_ar, _ab, _ab), } + +#define _SYSCFG_RST_CH_NO_ACK(_c, _rr, _rb) \ + { .compatible = _c, \ + .reset = REG_FIELD(_rr, _rb, _rb), } + +/** + * Description of a system configuration register based reset controller. + * + * @wait_for_ack: The controller will wait for reset assert and de-assert to + * be "ack'd" in a channel's ack field. + * @active_low: Are the resets in this controller active low, i.e. clearing + * the reset bit puts the hardware into reset. + * @nr_channels: The number of reset channels in this controller. + * @channels: An array of reset channel descriptions. + */ +struct syscfg_reset_controller_data { + bool wait_for_ack; + bool active_low; + int nr_channels; + const struct syscfg_reset_channel_data *channels; +}; + +/** + * syscfg_reset_probe(): platform device probe function used by syscfg + * reset controller drivers. This registers a reset + * controller configured by the OF match data for + * the compatible device which should be of type + * "struct syscfg_reset_controller_data". + * + * @pdev: platform device + */ +int syscfg_reset_probe(struct platform_device *pdev); + +#endif /* __STI_RESET_SYSCFG_H */ diff --git a/drivers/reset/tegra/Kconfig b/drivers/reset/tegra/Kconfig new file mode 100644 index 000000000..e4a9a389e --- /dev/null +++ b/drivers/reset/tegra/Kconfig @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +config RESET_TEGRA_BPMP + def_bool TEGRA_BPMP diff --git a/drivers/reset/tegra/Makefile b/drivers/reset/tegra/Makefile new file mode 100644 index 000000000..eccba896a --- /dev/null +++ b/drivers/reset/tegra/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_RESET_TEGRA_BPMP) += reset-bpmp.o diff --git a/drivers/reset/tegra/reset-bpmp.c b/drivers/reset/tegra/reset-bpmp.c new file mode 100644 index 000000000..4c5bba52b --- /dev/null +++ b/drivers/reset/tegra/reset-bpmp.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 NVIDIA Corporation + */ + +#include <linux/reset-controller.h> + +#include <soc/tegra/bpmp.h> +#include <soc/tegra/bpmp-abi.h> + +static struct tegra_bpmp *to_tegra_bpmp(struct reset_controller_dev *rstc) +{ + return container_of(rstc, struct tegra_bpmp, rstc); +} + +static int tegra_bpmp_reset_common(struct reset_controller_dev *rstc, + enum mrq_reset_commands command, + unsigned int id) +{ + struct tegra_bpmp *bpmp = to_tegra_bpmp(rstc); + struct mrq_reset_request request; + struct tegra_bpmp_message msg; + int err; + + memset(&request, 0, sizeof(request)); + request.cmd = command; + request.reset_id = id; + + memset(&msg, 0, sizeof(msg)); + msg.mrq = MRQ_RESET; + msg.tx.data = &request; + msg.tx.size = sizeof(request); + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err) + return err; + if (msg.rx.ret) + return -EINVAL; + + return 0; +} + +static int tegra_bpmp_reset_module(struct reset_controller_dev *rstc, + unsigned long id) +{ + return tegra_bpmp_reset_common(rstc, CMD_RESET_MODULE, id); +} + +static int tegra_bpmp_reset_assert(struct reset_controller_dev *rstc, + unsigned long id) +{ + return tegra_bpmp_reset_common(rstc, CMD_RESET_ASSERT, id); +} + +static int tegra_bpmp_reset_deassert(struct reset_controller_dev *rstc, + unsigned long id) +{ + return tegra_bpmp_reset_common(rstc, CMD_RESET_DEASSERT, id); +} + +static const struct reset_control_ops tegra_bpmp_reset_ops = { + .reset = tegra_bpmp_reset_module, + .assert = tegra_bpmp_reset_assert, + .deassert = tegra_bpmp_reset_deassert, +}; + +int tegra_bpmp_init_resets(struct tegra_bpmp *bpmp) +{ + bpmp->rstc.ops = &tegra_bpmp_reset_ops; + bpmp->rstc.owner = THIS_MODULE; + bpmp->rstc.of_node = bpmp->dev->of_node; + bpmp->rstc.nr_resets = bpmp->soc->num_resets; + + return devm_reset_controller_register(bpmp->dev, &bpmp->rstc); +} |