// SPDX-License-Identifier: GPL-2.0+ /* * Amlogic Meson SDHC clock controller * * Copyright (C) 2020 Martin Blumenstingl */ #include #include #include #include #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; }