| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2021 BAIKAL ELECTRONICS, JSC |
| * |
| * Authors: |
| * Serge Semin <Sergey.Semin@baikalelectronics.ru> |
| * |
| * Baikal-T1 CCU Resets interface driver |
| */ |
| |
| #define pr_fmt(fmt) "bt1-ccu-rst: " fmt |
| |
| #include <linux/bits.h> |
| #include <linux/delay.h> |
| #include <linux/kernel.h> |
| #include <linux/of.h> |
| #include <linux/printk.h> |
| #include <linux/regmap.h> |
| #include <linux/reset-controller.h> |
| #include <linux/slab.h> |
| |
| #include <dt-bindings/reset/bt1-ccu.h> |
| |
| #include "ccu-rst.h" |
| |
| #define CCU_AXI_MAIN_BASE 0x030 |
| #define CCU_AXI_DDR_BASE 0x034 |
| #define CCU_AXI_SATA_BASE 0x038 |
| #define CCU_AXI_GMAC0_BASE 0x03C |
| #define CCU_AXI_GMAC1_BASE 0x040 |
| #define CCU_AXI_XGMAC_BASE 0x044 |
| #define CCU_AXI_PCIE_M_BASE 0x048 |
| #define CCU_AXI_PCIE_S_BASE 0x04C |
| #define CCU_AXI_USB_BASE 0x050 |
| #define CCU_AXI_HWA_BASE 0x054 |
| #define CCU_AXI_SRAM_BASE 0x058 |
| |
| #define CCU_SYS_DDR_BASE 0x02c |
| #define CCU_SYS_SATA_REF_BASE 0x060 |
| #define CCU_SYS_APB_BASE 0x064 |
| #define CCU_SYS_PCIE_BASE 0x144 |
| |
| #define CCU_RST_DELAY_US 1 |
| |
| #define CCU_RST_TRIG(_base, _ofs) \ |
| { \ |
| .type = CCU_RST_TRIG, \ |
| .base = _base, \ |
| .mask = BIT(_ofs), \ |
| } |
| |
| #define CCU_RST_DIR(_base, _ofs) \ |
| { \ |
| .type = CCU_RST_DIR, \ |
| .base = _base, \ |
| .mask = BIT(_ofs), \ |
| } |
| |
| struct ccu_rst_info { |
| enum ccu_rst_type type; |
| unsigned int base; |
| unsigned int mask; |
| }; |
| |
| /* |
| * Each AXI-bus clock divider is equipped with the corresponding clock-consumer |
| * domain reset (it's self-deasserted reset control). |
| */ |
| static const struct ccu_rst_info axi_rst_info[] = { |
| [CCU_AXI_MAIN_RST] = CCU_RST_TRIG(CCU_AXI_MAIN_BASE, 1), |
| [CCU_AXI_DDR_RST] = CCU_RST_TRIG(CCU_AXI_DDR_BASE, 1), |
| [CCU_AXI_SATA_RST] = CCU_RST_TRIG(CCU_AXI_SATA_BASE, 1), |
| [CCU_AXI_GMAC0_RST] = CCU_RST_TRIG(CCU_AXI_GMAC0_BASE, 1), |
| [CCU_AXI_GMAC1_RST] = CCU_RST_TRIG(CCU_AXI_GMAC1_BASE, 1), |
| [CCU_AXI_XGMAC_RST] = CCU_RST_TRIG(CCU_AXI_XGMAC_BASE, 1), |
| [CCU_AXI_PCIE_M_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_M_BASE, 1), |
| [CCU_AXI_PCIE_S_RST] = CCU_RST_TRIG(CCU_AXI_PCIE_S_BASE, 1), |
| [CCU_AXI_USB_RST] = CCU_RST_TRIG(CCU_AXI_USB_BASE, 1), |
| [CCU_AXI_HWA_RST] = CCU_RST_TRIG(CCU_AXI_HWA_BASE, 1), |
| [CCU_AXI_SRAM_RST] = CCU_RST_TRIG(CCU_AXI_SRAM_BASE, 1), |
| }; |
| |
| /* |
| * SATA reference clock domain and APB-bus domain are connected with the |
| * sefl-deasserted reset control, which can be activated via the corresponding |
| * clock divider register. DDR and PCIe sub-domains can be reset with directly |
| * controlled reset signals. Resetting the DDR controller though won't end up |
| * well while the Linux kernel is working. |
| */ |
| static const struct ccu_rst_info sys_rst_info[] = { |
| [CCU_SYS_SATA_REF_RST] = CCU_RST_TRIG(CCU_SYS_SATA_REF_BASE, 1), |
| [CCU_SYS_APB_RST] = CCU_RST_TRIG(CCU_SYS_APB_BASE, 1), |
| [CCU_SYS_DDR_FULL_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 1), |
| [CCU_SYS_DDR_INIT_RST] = CCU_RST_DIR(CCU_SYS_DDR_BASE, 2), |
| [CCU_SYS_PCIE_PCS_PHY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 0), |
| [CCU_SYS_PCIE_PIPE0_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 4), |
| [CCU_SYS_PCIE_CORE_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 8), |
| [CCU_SYS_PCIE_PWR_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 9), |
| [CCU_SYS_PCIE_STICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 10), |
| [CCU_SYS_PCIE_NSTICKY_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 11), |
| [CCU_SYS_PCIE_HOT_RST] = CCU_RST_DIR(CCU_SYS_PCIE_BASE, 12), |
| }; |
| |
| static int ccu_rst_reset(struct reset_controller_dev *rcdev, unsigned long idx) |
| { |
| struct ccu_rst *rst = to_ccu_rst(rcdev); |
| const struct ccu_rst_info *info = &rst->rsts_info[idx]; |
| |
| if (info->type != CCU_RST_TRIG) |
| return -EOPNOTSUPP; |
| |
| regmap_update_bits(rst->sys_regs, info->base, info->mask, info->mask); |
| |
| /* The next delay must be enough to cover all the resets. */ |
| udelay(CCU_RST_DELAY_US); |
| |
| return 0; |
| } |
| |
| static int ccu_rst_set(struct reset_controller_dev *rcdev, |
| unsigned long idx, bool high) |
| { |
| struct ccu_rst *rst = to_ccu_rst(rcdev); |
| const struct ccu_rst_info *info = &rst->rsts_info[idx]; |
| |
| if (info->type != CCU_RST_DIR) |
| return high ? -EOPNOTSUPP : 0; |
| |
| return regmap_update_bits(rst->sys_regs, info->base, |
| info->mask, high ? info->mask : 0); |
| } |
| |
| static int ccu_rst_assert(struct reset_controller_dev *rcdev, |
| unsigned long idx) |
| { |
| return ccu_rst_set(rcdev, idx, true); |
| } |
| |
| static int ccu_rst_deassert(struct reset_controller_dev *rcdev, |
| unsigned long idx) |
| { |
| return ccu_rst_set(rcdev, idx, false); |
| } |
| |
| static int ccu_rst_status(struct reset_controller_dev *rcdev, |
| unsigned long idx) |
| { |
| struct ccu_rst *rst = to_ccu_rst(rcdev); |
| const struct ccu_rst_info *info = &rst->rsts_info[idx]; |
| u32 val; |
| |
| if (info->type != CCU_RST_DIR) |
| return -EOPNOTSUPP; |
| |
| regmap_read(rst->sys_regs, info->base, &val); |
| |
| return !!(val & info->mask); |
| } |
| |
| static const struct reset_control_ops ccu_rst_ops = { |
| .reset = ccu_rst_reset, |
| .assert = ccu_rst_assert, |
| .deassert = ccu_rst_deassert, |
| .status = ccu_rst_status, |
| }; |
| |
| struct ccu_rst *ccu_rst_hw_register(const struct ccu_rst_init_data *rst_init) |
| { |
| struct ccu_rst *rst; |
| int ret; |
| |
| if (!rst_init) |
| return ERR_PTR(-EINVAL); |
| |
| rst = kzalloc(sizeof(*rst), GFP_KERNEL); |
| if (!rst) |
| return ERR_PTR(-ENOMEM); |
| |
| rst->sys_regs = rst_init->sys_regs; |
| if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-axi")) { |
| rst->rcdev.nr_resets = ARRAY_SIZE(axi_rst_info); |
| rst->rsts_info = axi_rst_info; |
| } else if (of_device_is_compatible(rst_init->np, "baikal,bt1-ccu-sys")) { |
| rst->rcdev.nr_resets = ARRAY_SIZE(sys_rst_info); |
| rst->rsts_info = sys_rst_info; |
| } else { |
| pr_err("Incompatible DT node '%s' specified\n", |
| of_node_full_name(rst_init->np)); |
| ret = -EINVAL; |
| goto err_kfree_rst; |
| } |
| |
| rst->rcdev.owner = THIS_MODULE; |
| rst->rcdev.ops = &ccu_rst_ops; |
| rst->rcdev.of_node = rst_init->np; |
| |
| ret = reset_controller_register(&rst->rcdev); |
| if (ret) { |
| pr_err("Couldn't register '%s' reset controller\n", |
| of_node_full_name(rst_init->np)); |
| goto err_kfree_rst; |
| } |
| |
| return rst; |
| |
| err_kfree_rst: |
| kfree(rst); |
| |
| return ERR_PTR(ret); |
| } |
| |
| void ccu_rst_hw_unregister(struct ccu_rst *rst) |
| { |
| reset_controller_unregister(&rst->rcdev); |
| |
| kfree(rst); |
| } |