| // 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.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; | |
| 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, | |
| }; | |
| /* | |
| * 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 sys_off_data *data) | |
| { | |
| struct intel_reset_data *reset_data = data->cb_data; | |
| 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]); | |
| devm_register_restart_handler(&pdev->dev, intel_reset_restart_handler, data); | |
| 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); |