| // 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_bulk_data *clk_bulk_data, | |
| u8 bulk_index) | |
| { | |
| struct clk_parent_data parent_data = { .hw = parent }; | |
| int ret; | |
| ret = meson_mx_sdhc_clk_hw_register(dev, name_suffix, &parent_data, 1, | |
| &clk_gate_ops, hw); | |
| if (ret) | |
| return ret; | |
| clk_bulk_data[bulk_index].clk = devm_clk_hw_get_clk(dev, hw, name_suffix); | |
| return PTR_ERR_OR_ZERO(clk_bulk_data[bulk_index].clk); | |
| } | |
| 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, | |
| clk_bulk_data, 0); | |
| 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, | |
| clk_bulk_data, 1); | |
| 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, | |
| clk_bulk_data, 2); | |
| 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, | |
| clk_bulk_data, 3); | |
| return ret; | |
| } |