Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * Amlogic Meson SDHC clock controller
0004  *
0005  * Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
0006  */
0007 
0008 #include <linux/clk.h>
0009 #include <linux/clk-provider.h>
0010 #include <linux/device.h>
0011 #include <linux/platform_device.h>
0012 
0013 #include "meson-mx-sdhc.h"
0014 
0015 struct meson_mx_sdhc_clkc {
0016     struct clk_mux          src_sel;
0017     struct clk_divider      div;
0018     struct clk_gate         mod_clk_en;
0019     struct clk_gate         tx_clk_en;
0020     struct clk_gate         rx_clk_en;
0021     struct clk_gate         sd_clk_en;
0022 };
0023 
0024 static const struct clk_parent_data meson_mx_sdhc_src_sel_parents[4] = {
0025     { .fw_name = "clkin0" },
0026     { .fw_name = "clkin1" },
0027     { .fw_name = "clkin2" },
0028     { .fw_name = "clkin3" },
0029 };
0030 
0031 static const struct clk_div_table meson_mx_sdhc_div_table[] = {
0032     { .div = 6, .val = 5, },
0033     { .div = 8, .val = 7, },
0034     { .div = 9, .val = 8, },
0035     { .div = 10, .val = 9, },
0036     { .div = 12, .val = 11, },
0037     { .div = 16, .val = 15, },
0038     { .div = 18, .val = 17, },
0039     { .div = 34, .val = 33, },
0040     { .div = 142, .val = 141, },
0041     { .div = 850, .val = 849, },
0042     { .div = 2126, .val = 2125, },
0043     { .div = 4096, .val = 4095, },
0044     { /* sentinel */ }
0045 };
0046 
0047 static int meson_mx_sdhc_clk_hw_register(struct device *dev,
0048                      const char *name_suffix,
0049                      const struct clk_parent_data *parents,
0050                      unsigned int num_parents,
0051                      const struct clk_ops *ops,
0052                      struct clk_hw *hw)
0053 {
0054     struct clk_init_data init = { };
0055     char clk_name[32];
0056 
0057     snprintf(clk_name, sizeof(clk_name), "%s#%s", dev_name(dev),
0058          name_suffix);
0059 
0060     init.name = clk_name;
0061     init.ops = ops;
0062     init.flags = CLK_SET_RATE_PARENT;
0063     init.parent_data = parents;
0064     init.num_parents = num_parents;
0065 
0066     hw->init = &init;
0067 
0068     return devm_clk_hw_register(dev, hw);
0069 }
0070 
0071 static int meson_mx_sdhc_gate_clk_hw_register(struct device *dev,
0072                           const char *name_suffix,
0073                           struct clk_hw *parent,
0074                           struct clk_hw *hw)
0075 {
0076     struct clk_parent_data parent_data = { .hw = parent };
0077 
0078     return meson_mx_sdhc_clk_hw_register(dev, name_suffix, &parent_data, 1,
0079                          &clk_gate_ops, hw);
0080 }
0081 
0082 int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base,
0083                 struct clk_bulk_data *clk_bulk_data)
0084 {
0085     struct clk_parent_data div_parent = { };
0086     struct meson_mx_sdhc_clkc *clkc_data;
0087     int ret;
0088 
0089     clkc_data = devm_kzalloc(dev, sizeof(*clkc_data), GFP_KERNEL);
0090     if (!clkc_data)
0091         return -ENOMEM;
0092 
0093     clkc_data->src_sel.reg = base + MESON_SDHC_CLKC;
0094     clkc_data->src_sel.mask = 0x3;
0095     clkc_data->src_sel.shift = 16;
0096     ret = meson_mx_sdhc_clk_hw_register(dev, "src_sel",
0097                         meson_mx_sdhc_src_sel_parents, 4,
0098                         &clk_mux_ops,
0099                         &clkc_data->src_sel.hw);
0100     if (ret)
0101         return ret;
0102 
0103     clkc_data->div.reg = base + MESON_SDHC_CLKC;
0104     clkc_data->div.shift = 0;
0105     clkc_data->div.width = 12;
0106     clkc_data->div.table = meson_mx_sdhc_div_table;
0107     div_parent.hw = &clkc_data->src_sel.hw;
0108     ret = meson_mx_sdhc_clk_hw_register(dev, "div", &div_parent, 1,
0109                         &clk_divider_ops,
0110                         &clkc_data->div.hw);
0111     if (ret)
0112         return ret;
0113 
0114     clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC;
0115     clkc_data->mod_clk_en.bit_idx = 15;
0116     ret = meson_mx_sdhc_gate_clk_hw_register(dev, "mod_clk_on",
0117                          &clkc_data->div.hw,
0118                          &clkc_data->mod_clk_en.hw);
0119     if (ret)
0120         return ret;
0121 
0122     clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC;
0123     clkc_data->tx_clk_en.bit_idx = 14;
0124     ret = meson_mx_sdhc_gate_clk_hw_register(dev, "tx_clk_on",
0125                          &clkc_data->div.hw,
0126                          &clkc_data->tx_clk_en.hw);
0127     if (ret)
0128         return ret;
0129 
0130     clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC;
0131     clkc_data->rx_clk_en.bit_idx = 13;
0132     ret = meson_mx_sdhc_gate_clk_hw_register(dev, "rx_clk_on",
0133                          &clkc_data->div.hw,
0134                          &clkc_data->rx_clk_en.hw);
0135     if (ret)
0136         return ret;
0137 
0138     clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC;
0139     clkc_data->sd_clk_en.bit_idx = 12;
0140     ret = meson_mx_sdhc_gate_clk_hw_register(dev, "sd_clk_on",
0141                          &clkc_data->div.hw,
0142                          &clkc_data->sd_clk_en.hw);
0143     if (ret)
0144         return ret;
0145 
0146     /*
0147      * TODO: Replace clk_hw.clk with devm_clk_hw_get_clk() once that is
0148      * available.
0149      */
0150     clk_bulk_data[0].clk = clkc_data->mod_clk_en.hw.clk;
0151     clk_bulk_data[1].clk = clkc_data->sd_clk_en.hw.clk;
0152     clk_bulk_data[2].clk = clkc_data->tx_clk_en.hw.clk;
0153     clk_bulk_data[3].clk = clkc_data->rx_clk_en.hw.clk;
0154 
0155     return 0;
0156 }