| // 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); |