Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | // SPDX-License-Identifier: GPL-2.0+ /* * Amlogic Meson SDHC clock controller * * Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com> */ #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/device.h> #include <linux/platform_device.h> #include "meson-mx-sdhc.h" #define MESON_SDHC_NUM_BUILTIN_CLKS 6 struct meson_mx_sdhc_clkc { struct clk_mux src_sel; struct clk_divider div; struct clk_gate mod_clk_en; struct clk_gate tx_clk_en; struct clk_gate rx_clk_en; struct clk_gate sd_clk_en; }; static const struct clk_parent_data meson_mx_sdhc_src_sel_parents[4] = { { .fw_name = "clkin0" }, { .fw_name = "clkin1" }, { .fw_name = "clkin2" }, { .fw_name = "clkin3" }, }; static const struct clk_div_table meson_mx_sdhc_div_table[] = { { .div = 6, .val = 5, }, { .div = 8, .val = 7, }, { .div = 9, .val = 8, }, { .div = 10, .val = 9, }, { .div = 12, .val = 11, }, { .div = 16, .val = 15, }, { .div = 18, .val = 17, }, { .div = 34, .val = 33, }, { .div = 142, .val = 141, }, { .div = 850, .val = 849, }, { .div = 2126, .val = 2125, }, { .div = 4096, .val = 4095, }, { /* sentinel */ } }; static int meson_mx_sdhc_clk_hw_register(struct device *dev, const char *name_suffix, const struct clk_parent_data *parents, unsigned int num_parents, const struct clk_ops *ops, struct clk_hw *hw) { struct clk_init_data init = { }; char clk_name[32]; snprintf(clk_name, sizeof(clk_name), "%s#%s", dev_name(dev), name_suffix); init.name = clk_name; init.ops = ops; init.flags = CLK_SET_RATE_PARENT; init.parent_data = parents; init.num_parents = num_parents; hw->init = &init; return devm_clk_hw_register(dev, hw); } static int meson_mx_sdhc_gate_clk_hw_register(struct device *dev, const char *name_suffix, struct clk_hw *parent, struct clk_hw *hw) { struct clk_parent_data parent_data = { .hw = parent }; return meson_mx_sdhc_clk_hw_register(dev, name_suffix, &parent_data, 1, &clk_gate_ops, hw); } int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base, struct clk_bulk_data *clk_bulk_data) { struct clk_parent_data div_parent = { }; struct meson_mx_sdhc_clkc *clkc_data; int ret; clkc_data = devm_kzalloc(dev, sizeof(*clkc_data), GFP_KERNEL); if (!clkc_data) return -ENOMEM; clkc_data->src_sel.reg = base + MESON_SDHC_CLKC; clkc_data->src_sel.mask = 0x3; clkc_data->src_sel.shift = 16; ret = meson_mx_sdhc_clk_hw_register(dev, "src_sel", meson_mx_sdhc_src_sel_parents, 4, &clk_mux_ops, &clkc_data->src_sel.hw); if (ret) return ret; clkc_data->div.reg = base + MESON_SDHC_CLKC; clkc_data->div.shift = 0; clkc_data->div.width = 12; clkc_data->div.table = meson_mx_sdhc_div_table; div_parent.hw = &clkc_data->src_sel.hw; ret = meson_mx_sdhc_clk_hw_register(dev, "div", &div_parent, 1, &clk_divider_ops, &clkc_data->div.hw); if (ret) return ret; clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC; clkc_data->mod_clk_en.bit_idx = 15; ret = meson_mx_sdhc_gate_clk_hw_register(dev, "mod_clk_on", &clkc_data->div.hw, &clkc_data->mod_clk_en.hw); if (ret) return ret; clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC; clkc_data->tx_clk_en.bit_idx = 14; ret = meson_mx_sdhc_gate_clk_hw_register(dev, "tx_clk_on", &clkc_data->div.hw, &clkc_data->tx_clk_en.hw); if (ret) return ret; clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC; clkc_data->rx_clk_en.bit_idx = 13; ret = meson_mx_sdhc_gate_clk_hw_register(dev, "rx_clk_on", &clkc_data->div.hw, &clkc_data->rx_clk_en.hw); if (ret) return ret; clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC; clkc_data->sd_clk_en.bit_idx = 12; ret = meson_mx_sdhc_gate_clk_hw_register(dev, "sd_clk_on", &clkc_data->div.hw, &clkc_data->sd_clk_en.hw); if (ret) return ret; /* * TODO: Replace clk_hw.clk with devm_clk_hw_get_clk() once that is * available. */ clk_bulk_data[0].clk = clkc_data->mod_clk_en.hw.clk; clk_bulk_data[1].clk = clkc_data->sd_clk_en.hw.clk; clk_bulk_data[2].clk = clkc_data->tx_clk_en.hw.clk; clk_bulk_data[3].clk = clkc_data->rx_clk_en.hw.clk; return 0; } |