| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * BCM63268 Timer Clock and Reset Controller Driver |
| * |
| * Copyright (C) 2023 Álvaro Fernández Rojas <noltari@gmail.com> |
| */ |
| |
| #include <linux/clk-provider.h> |
| #include <linux/container_of.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/io.h> |
| #include <linux/platform_device.h> |
| #include <linux/reset-controller.h> |
| #include <linux/spinlock.h> |
| |
| #include <dt-bindings/clock/bcm63268-clock.h> |
| |
| #define BCM63268_TIMER_RESET_SLEEP_MIN_US 10000 |
| #define BCM63268_TIMER_RESET_SLEEP_MAX_US 20000 |
| |
| struct bcm63268_tclkrst_hw { |
| void __iomem *regs; |
| spinlock_t lock; |
| |
| struct reset_controller_dev rcdev; |
| struct clk_hw_onecell_data data; |
| }; |
| |
| struct bcm63268_tclk_table_entry { |
| const char * const name; |
| u8 bit; |
| }; |
| |
| static const struct bcm63268_tclk_table_entry bcm63268_timer_clocks[] = { |
| { |
| .name = "ephy1", |
| .bit = BCM63268_TCLK_EPHY1, |
| }, { |
| .name = "ephy2", |
| .bit = BCM63268_TCLK_EPHY2, |
| }, { |
| .name = "ephy3", |
| .bit = BCM63268_TCLK_EPHY3, |
| }, { |
| .name = "gphy1", |
| .bit = BCM63268_TCLK_GPHY1, |
| }, { |
| .name = "dsl", |
| .bit = BCM63268_TCLK_DSL, |
| }, { |
| .name = "wakeon_ephy", |
| .bit = BCM63268_TCLK_WAKEON_EPHY, |
| }, { |
| .name = "wakeon_dsl", |
| .bit = BCM63268_TCLK_WAKEON_DSL, |
| }, { |
| .name = "fap1_pll", |
| .bit = BCM63268_TCLK_FAP1, |
| }, { |
| .name = "fap2_pll", |
| .bit = BCM63268_TCLK_FAP2, |
| }, { |
| .name = "uto_50", |
| .bit = BCM63268_TCLK_UTO_50, |
| }, { |
| .name = "uto_extin", |
| .bit = BCM63268_TCLK_UTO_EXTIN, |
| }, { |
| .name = "usb_ref", |
| .bit = BCM63268_TCLK_USB_REF, |
| }, { |
| /* sentinel */ |
| } |
| }; |
| |
| static inline struct bcm63268_tclkrst_hw * |
| to_bcm63268_timer_reset(struct reset_controller_dev *rcdev) |
| { |
| return container_of(rcdev, struct bcm63268_tclkrst_hw, rcdev); |
| } |
| |
| static int bcm63268_timer_reset_update(struct reset_controller_dev *rcdev, |
| unsigned long id, bool assert) |
| { |
| struct bcm63268_tclkrst_hw *reset = to_bcm63268_timer_reset(rcdev); |
| unsigned long flags; |
| uint32_t val; |
| |
| spin_lock_irqsave(&reset->lock, flags); |
| val = __raw_readl(reset->regs); |
| if (assert) |
| val &= ~BIT(id); |
| else |
| val |= BIT(id); |
| __raw_writel(val, reset->regs); |
| spin_unlock_irqrestore(&reset->lock, flags); |
| |
| return 0; |
| } |
| |
| static int bcm63268_timer_reset_assert(struct reset_controller_dev *rcdev, |
| unsigned long id) |
| { |
| return bcm63268_timer_reset_update(rcdev, id, true); |
| } |
| |
| static int bcm63268_timer_reset_deassert(struct reset_controller_dev *rcdev, |
| unsigned long id) |
| { |
| return bcm63268_timer_reset_update(rcdev, id, false); |
| } |
| |
| static int bcm63268_timer_reset_reset(struct reset_controller_dev *rcdev, |
| unsigned long id) |
| { |
| bcm63268_timer_reset_update(rcdev, id, true); |
| usleep_range(BCM63268_TIMER_RESET_SLEEP_MIN_US, |
| BCM63268_TIMER_RESET_SLEEP_MAX_US); |
| |
| bcm63268_timer_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(BCM63268_TIMER_RESET_SLEEP_MIN_US, |
| BCM63268_TIMER_RESET_SLEEP_MAX_US); |
| |
| return 0; |
| } |
| |
| static int bcm63268_timer_reset_status(struct reset_controller_dev *rcdev, |
| unsigned long id) |
| { |
| struct bcm63268_tclkrst_hw *reset = to_bcm63268_timer_reset(rcdev); |
| |
| return !(__raw_readl(reset->regs) & BIT(id)); |
| } |
| |
| static const struct reset_control_ops bcm63268_timer_reset_ops = { |
| .assert = bcm63268_timer_reset_assert, |
| .deassert = bcm63268_timer_reset_deassert, |
| .reset = bcm63268_timer_reset_reset, |
| .status = bcm63268_timer_reset_status, |
| }; |
| |
| static int bcm63268_tclk_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| const struct bcm63268_tclk_table_entry *entry; |
| struct bcm63268_tclkrst_hw *hw; |
| struct clk_hw *clk; |
| u8 maxbit = 0; |
| int i, ret; |
| |
| for (entry = bcm63268_timer_clocks; entry->name; entry++) |
| maxbit = max(maxbit, entry->bit); |
| maxbit++; |
| |
| hw = devm_kzalloc(&pdev->dev, struct_size(hw, data.hws, maxbit), |
| GFP_KERNEL); |
| if (!hw) |
| return -ENOMEM; |
| |
| platform_set_drvdata(pdev, hw); |
| |
| spin_lock_init(&hw->lock); |
| |
| hw->data.num = maxbit; |
| for (i = 0; i < maxbit; i++) |
| hw->data.hws[i] = ERR_PTR(-ENODEV); |
| |
| hw->regs = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(hw->regs)) |
| return PTR_ERR(hw->regs); |
| |
| for (entry = bcm63268_timer_clocks; entry->name; entry++) { |
| clk = devm_clk_hw_register_gate(dev, entry->name, NULL, 0, |
| hw->regs, entry->bit, |
| CLK_GATE_BIG_ENDIAN, |
| &hw->lock); |
| if (IS_ERR(clk)) |
| return PTR_ERR(clk); |
| |
| hw->data.hws[entry->bit] = clk; |
| } |
| |
| ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, |
| &hw->data); |
| if (ret) |
| return ret; |
| |
| hw->rcdev.of_node = dev->of_node; |
| hw->rcdev.ops = &bcm63268_timer_reset_ops; |
| |
| ret = devm_reset_controller_register(dev, &hw->rcdev); |
| if (ret) |
| dev_err(dev, "Failed to register reset controller\n"); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id bcm63268_tclk_dt_ids[] = { |
| { .compatible = "brcm,bcm63268-timer-clocks" }, |
| { /* sentinel */ } |
| }; |
| |
| static struct platform_driver bcm63268_tclk = { |
| .probe = bcm63268_tclk_probe, |
| .driver = { |
| .name = "bcm63268-timer-clock", |
| .of_match_table = bcm63268_tclk_dt_ids, |
| }, |
| }; |
| builtin_platform_driver(bcm63268_tclk); |