| // 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" |
| |
| 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; |
| } |