// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2015 Maxime Ripard * * Maxime Ripard <maxime.ripard@free-electrons.com> */ #include <linux/clk-provider.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/of_address.h> #include <linux/reset-controller.h> #include <linux/slab.h> #include <linux/spinlock.h> struct sun4i_a10_display_clk_data { bool has_div; u8 num_rst; u8 parents; u8 offset_en; u8 offset_div; u8 offset_mux; u8 offset_rst; u8 width_div; u8 width_mux; u32 flags; }; struct reset_data { void __iomem *reg; spinlock_t *lock; struct reset_controller_dev rcdev; u8 offset; }; static DEFINE_SPINLOCK(sun4i_a10_display_lock); static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev) { return container_of(rcdev, struct reset_data, rcdev); }; static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev, unsigned long id) { struct reset_data *data = rcdev_to_reset_data(rcdev); unsigned long flags; u32 reg; spin_lock_irqsave(data->lock, flags); reg = readl(data->reg); writel(reg & ~BIT(data->offset + id), data->reg); spin_unlock_irqrestore(data->lock, flags); return 0; } static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev, unsigned long id) { struct reset_data *data = rcdev_to_reset_data(rcdev); unsigned long flags; u32 reg; spin_lock_irqsave(data->lock, flags); reg = readl(data->reg); writel(reg | BIT(data->offset + id), data->reg); spin_unlock_irqrestore(data->lock, flags); return 0; } static int sun4i_a10_display_status(struct reset_controller_dev *rcdev, unsigned long id) { struct reset_data *data = rcdev_to_reset_data(rcdev); return !(readl(data->reg) & BIT(data->offset + id)); } static const struct reset_control_ops sun4i_a10_display_reset_ops = { .assert = sun4i_a10_display_assert, .deassert = sun4i_a10_display_deassert, .status = sun4i_a10_display_status, }; static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev, const struct of_phandle_args *spec) { /* We only have a single reset signal */ return 0; } static void __init sun4i_a10_display_init(struct device_node *node, const struct sun4i_a10_display_clk_data *data) { const char *parents[4]; const char *clk_name = node->name; struct reset_data *reset_data; struct clk_divider *div = NULL; struct clk_gate *gate; struct resource res; struct clk_mux *mux; void __iomem *reg; struct clk *clk; int ret; of_property_read_string(node, "clock-output-names", &clk_name); reg = of_io_request_and_map(node, 0, of_node_full_name(node)); if (IS_ERR(reg)) { pr_err("%s: Could not map the clock registers\n", clk_name); return; } ret = of_clk_parent_fill(node, parents, data->parents); if (ret != data->parents) { pr_err("%s: Could not retrieve the parents\n", clk_name); goto unmap; } mux = kzalloc(sizeof(*mux), GFP_KERNEL); if (!mux) goto unmap; mux->reg = reg; mux->shift = data->offset_mux; mux->mask = (1 << data->width_mux) - 1; mux->lock = &sun4i_a10_display_lock; gate = kzalloc(sizeof(*gate), GFP_KERNEL); if (!gate) goto free_mux; gate->reg = reg; gate->bit_idx = data->offset_en; gate->lock = &sun4i_a10_display_lock; if (data->has_div) { div = kzalloc(sizeof(*div), GFP_KERNEL); if (!div) goto free_gate; div->reg = reg; div->shift = data->offset_div; div->width = data->width_div; div->lock = &sun4i_a10_display_lock; } clk = clk_register_composite(NULL, clk_name, parents, data->parents, &mux->hw, &clk_mux_ops, data->has_div ? &div->hw : NULL, data->has_div ? &clk_divider_ops : NULL, &gate->hw, &clk_gate_ops, data->flags); if (IS_ERR(clk)) { pr_err("%s: Couldn't register the clock\n", clk_name); goto free_div; } ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); if (ret) { pr_err("%s: Couldn't register DT provider\n", clk_name); goto free_clk; } if (!data->num_rst) return; reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL); if (!reset_data) goto free_of_clk; reset_data->reg = reg; reset_data->offset = data->offset_rst; reset_data->lock = &sun4i_a10_display_lock; reset_data->rcdev.nr_resets = data->num_rst; reset_data->rcdev.ops = &sun4i_a10_display_reset_ops; reset_data->rcdev.of_node = node; if (data->num_rst == 1) { reset_data->rcdev.of_reset_n_cells = 0; reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate; } else { reset_data->rcdev.of_reset_n_cells = 1; } if (reset_controller_register(&reset_data->rcdev)) { pr_err("%s: Couldn't register the reset controller\n", clk_name); goto free_reset; } return; free_reset: kfree(reset_data); free_of_clk: of_clk_del_provider(node); free_clk: clk_unregister_composite(clk); free_div: kfree(div); free_gate: kfree(gate); free_mux: kfree(mux); unmap: iounmap(reg); of_address_to_resource(node, 0, &res); release_mem_region(res.start, resource_size(&res)); } static const struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data __initconst = { .num_rst = 2, .parents = 4, .offset_en = 31, .offset_rst = 29, .offset_mux = 24, .width_mux = 2, .flags = CLK_SET_RATE_PARENT, }; static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node) { sun4i_a10_display_init(node, &sun4i_a10_tcon_ch0_data); } CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk", sun4i_a10_tcon_ch0_setup); static const struct sun4i_a10_display_clk_data sun4i_a10_display_data __initconst = { .has_div = true, .num_rst = 1, .parents = 3, .offset_en = 31, .offset_rst = 30, .offset_mux = 24, .offset_div = 0, .width_mux = 2, .width_div = 4, }; static void __init sun4i_a10_display_setup(struct device_node *node) { sun4i_a10_display_init(node, &sun4i_a10_display_data); } CLK_OF_DECLARE(sun4i_a10_display, "allwinner,sun4i-a10-display-clk", sun4i_a10_display_setup);