linux/drivers/clk/sunxi/clk-sun4i-display.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright 2015 Maxime Ripard
   4 *
   5 * Maxime Ripard <maxime.ripard@free-electrons.com>
   6 */
   7
   8#include <linux/clk-provider.h>
   9#include <linux/io.h>
  10#include <linux/kernel.h>
  11#include <linux/of_address.h>
  12#include <linux/reset-controller.h>
  13#include <linux/slab.h>
  14#include <linux/spinlock.h>
  15
  16struct sun4i_a10_display_clk_data {
  17        bool    has_div;
  18        u8      num_rst;
  19        u8      parents;
  20
  21        u8      offset_en;
  22        u8      offset_div;
  23        u8      offset_mux;
  24        u8      offset_rst;
  25
  26        u8      width_div;
  27        u8      width_mux;
  28
  29        u32     flags;
  30};
  31
  32struct reset_data {
  33        void __iomem                    *reg;
  34        spinlock_t                      *lock;
  35        struct reset_controller_dev     rcdev;
  36        u8                              offset;
  37};
  38
  39static DEFINE_SPINLOCK(sun4i_a10_display_lock);
  40
  41static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev)
  42{
  43        return container_of(rcdev, struct reset_data, rcdev);
  44};
  45
  46static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev,
  47                                    unsigned long id)
  48{
  49        struct reset_data *data = rcdev_to_reset_data(rcdev);
  50        unsigned long flags;
  51        u32 reg;
  52
  53        spin_lock_irqsave(data->lock, flags);
  54
  55        reg = readl(data->reg);
  56        writel(reg & ~BIT(data->offset + id), data->reg);
  57
  58        spin_unlock_irqrestore(data->lock, flags);
  59
  60        return 0;
  61}
  62
  63static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev,
  64                                      unsigned long id)
  65{
  66        struct reset_data *data = rcdev_to_reset_data(rcdev);
  67        unsigned long flags;
  68        u32 reg;
  69
  70        spin_lock_irqsave(data->lock, flags);
  71
  72        reg = readl(data->reg);
  73        writel(reg | BIT(data->offset + id), data->reg);
  74
  75        spin_unlock_irqrestore(data->lock, flags);
  76
  77        return 0;
  78}
  79
  80static int sun4i_a10_display_status(struct reset_controller_dev *rcdev,
  81                                    unsigned long id)
  82{
  83        struct reset_data *data = rcdev_to_reset_data(rcdev);
  84
  85        return !(readl(data->reg) & BIT(data->offset + id));
  86}
  87
  88static const struct reset_control_ops sun4i_a10_display_reset_ops = {
  89        .assert         = sun4i_a10_display_assert,
  90        .deassert       = sun4i_a10_display_deassert,
  91        .status         = sun4i_a10_display_status,
  92};
  93
  94static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev,
  95                                         const struct of_phandle_args *spec)
  96{
  97        /* We only have a single reset signal */
  98        return 0;
  99}
 100
 101static void __init sun4i_a10_display_init(struct device_node *node,
 102                                          const struct sun4i_a10_display_clk_data *data)
 103{
 104        const char *parents[4];
 105        const char *clk_name = node->name;
 106        struct reset_data *reset_data;
 107        struct clk_divider *div = NULL;
 108        struct clk_gate *gate;
 109        struct resource res;
 110        struct clk_mux *mux;
 111        void __iomem *reg;
 112        struct clk *clk;
 113        int ret;
 114
 115        of_property_read_string(node, "clock-output-names", &clk_name);
 116
 117        reg = of_io_request_and_map(node, 0, of_node_full_name(node));
 118        if (IS_ERR(reg)) {
 119                pr_err("%s: Could not map the clock registers\n", clk_name);
 120                return;
 121        }
 122
 123        ret = of_clk_parent_fill(node, parents, data->parents);
 124        if (ret != data->parents) {
 125                pr_err("%s: Could not retrieve the parents\n", clk_name);
 126                goto unmap;
 127        }
 128
 129        mux = kzalloc(sizeof(*mux), GFP_KERNEL);
 130        if (!mux)
 131                goto unmap;
 132
 133        mux->reg = reg;
 134        mux->shift = data->offset_mux;
 135        mux->mask = (1 << data->width_mux) - 1;
 136        mux->lock = &sun4i_a10_display_lock;
 137
 138        gate = kzalloc(sizeof(*gate), GFP_KERNEL);
 139        if (!gate)
 140                goto free_mux;
 141
 142        gate->reg = reg;
 143        gate->bit_idx = data->offset_en;
 144        gate->lock = &sun4i_a10_display_lock;
 145
 146        if (data->has_div) {
 147                div = kzalloc(sizeof(*div), GFP_KERNEL);
 148                if (!div)
 149                        goto free_gate;
 150
 151                div->reg = reg;
 152                div->shift = data->offset_div;
 153                div->width = data->width_div;
 154                div->lock = &sun4i_a10_display_lock;
 155        }
 156
 157        clk = clk_register_composite(NULL, clk_name,
 158                                     parents, data->parents,
 159                                     &mux->hw, &clk_mux_ops,
 160                                     data->has_div ? &div->hw : NULL,
 161                                     data->has_div ? &clk_divider_ops : NULL,
 162                                     &gate->hw, &clk_gate_ops,
 163                                     data->flags);
 164        if (IS_ERR(clk)) {
 165                pr_err("%s: Couldn't register the clock\n", clk_name);
 166                goto free_div;
 167        }
 168
 169        ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
 170        if (ret) {
 171                pr_err("%s: Couldn't register DT provider\n", clk_name);
 172                goto free_clk;
 173        }
 174
 175        if (!data->num_rst)
 176                return;
 177
 178        reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
 179        if (!reset_data)
 180                goto free_of_clk;
 181
 182        reset_data->reg = reg;
 183        reset_data->offset = data->offset_rst;
 184        reset_data->lock = &sun4i_a10_display_lock;
 185        reset_data->rcdev.nr_resets = data->num_rst;
 186        reset_data->rcdev.ops = &sun4i_a10_display_reset_ops;
 187        reset_data->rcdev.of_node = node;
 188
 189        if (data->num_rst == 1) {
 190                reset_data->rcdev.of_reset_n_cells = 0;
 191                reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate;
 192        } else {
 193                reset_data->rcdev.of_reset_n_cells = 1;
 194        }
 195
 196        if (reset_controller_register(&reset_data->rcdev)) {
 197                pr_err("%s: Couldn't register the reset controller\n",
 198                       clk_name);
 199                goto free_reset;
 200        }
 201
 202        return;
 203
 204free_reset:
 205        kfree(reset_data);
 206free_of_clk:
 207        of_clk_del_provider(node);
 208free_clk:
 209        clk_unregister_composite(clk);
 210free_div:
 211        kfree(div);
 212free_gate:
 213        kfree(gate);
 214free_mux:
 215        kfree(mux);
 216unmap:
 217        iounmap(reg);
 218        of_address_to_resource(node, 0, &res);
 219        release_mem_region(res.start, resource_size(&res));
 220}
 221
 222static const struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data __initconst = {
 223        .num_rst        = 2,
 224        .parents        = 4,
 225        .offset_en      = 31,
 226        .offset_rst     = 29,
 227        .offset_mux     = 24,
 228        .width_mux      = 2,
 229        .flags          = CLK_SET_RATE_PARENT,
 230};
 231
 232static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node)
 233{
 234        sun4i_a10_display_init(node, &sun4i_a10_tcon_ch0_data);
 235}
 236CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk",
 237               sun4i_a10_tcon_ch0_setup);
 238
 239static const struct sun4i_a10_display_clk_data sun4i_a10_display_data __initconst = {
 240        .has_div        = true,
 241        .num_rst        = 1,
 242        .parents        = 3,
 243        .offset_en      = 31,
 244        .offset_rst     = 30,
 245        .offset_mux     = 24,
 246        .offset_div     = 0,
 247        .width_mux      = 2,
 248        .width_div      = 4,
 249};
 250
 251static void __init sun4i_a10_display_setup(struct device_node *node)
 252{
 253        sun4i_a10_display_init(node, &sun4i_a10_display_data);
 254}
 255CLK_OF_DECLARE(sun4i_a10_display, "allwinner,sun4i-a10-display-clk",
 256               sun4i_a10_display_setup);
 257