|  | // 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/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 }, | 
|  | { .compatible = "sophgo,sg2042-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; | 
|  |  | 
|  | membase = devm_platform_get_and_ioremap_resource(pdev, 0, &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); |