// SPDX-License-Identifier: GPL-2.0-only /* * Toshiba Visconti clock controller * * Copyright (c) 2021 TOSHIBA CORPORATION * Copyright (c) 2021 Toshiba Electronic Devices & Storage Corporation * * Nobuhiro Iwamatsu */ #include #include #include #include #include #include #include #include #include #include "clkc.h" static inline struct visconti_clk_gate *to_visconti_clk_gate(struct clk_hw *hw) { return container_of(hw, struct visconti_clk_gate, hw); } static int visconti_gate_clk_is_enabled(struct clk_hw *hw) { struct visconti_clk_gate *gate = to_visconti_clk_gate(hw); u32 clk = BIT(gate->ck_idx); u32 val; regmap_read(gate->regmap, gate->ckon_offset, &val); return (val & clk) ? 1 : 0; } static void visconti_gate_clk_disable(struct clk_hw *hw) { struct visconti_clk_gate *gate = to_visconti_clk_gate(hw); u32 clk = BIT(gate->ck_idx); unsigned long flags; spin_lock_irqsave(gate->lock, flags); if (!visconti_gate_clk_is_enabled(hw)) { spin_unlock_irqrestore(gate->lock, flags); return; } regmap_update_bits(gate->regmap, gate->ckoff_offset, clk, clk); spin_unlock_irqrestore(gate->lock, flags); } static int visconti_gate_clk_enable(struct clk_hw *hw) { struct visconti_clk_gate *gate = to_visconti_clk_gate(hw); u32 clk = BIT(gate->ck_idx); unsigned long flags; spin_lock_irqsave(gate->lock, flags); regmap_update_bits(gate->regmap, gate->ckon_offset, clk, clk); spin_unlock_irqrestore(gate->lock, flags); return 0; } static const struct clk_ops visconti_clk_gate_ops = { .enable = visconti_gate_clk_enable, .disable = visconti_gate_clk_disable, .is_enabled = visconti_gate_clk_is_enabled, }; static struct clk_hw *visconti_clk_register_gate(struct device *dev, const char *name, const char *parent_name, struct regmap *regmap, const struct visconti_clk_gate_table *clks, u32 rson_offset, u32 rsoff_offset, u8 rs_idx, spinlock_t *lock) { struct visconti_clk_gate *gate; struct clk_parent_data *pdata; struct clk_init_data init; struct clk_hw *hw; int ret; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return ERR_PTR(-ENOMEM); pdata->name = pdata->fw_name = parent_name; gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL); if (!gate) return ERR_PTR(-ENOMEM); init.name = name; init.ops = &visconti_clk_gate_ops; init.flags = clks->flags; init.parent_data = pdata; init.num_parents = 1; gate->regmap = regmap; gate->ckon_offset = clks->ckon_offset; gate->ckoff_offset = clks->ckoff_offset; gate->ck_idx = clks->ck_idx; gate->rson_offset = rson_offset; gate->rsoff_offset = rsoff_offset; gate->rs_idx = rs_idx; gate->lock = lock; gate->hw.init = &init; hw = &gate->hw; ret = devm_clk_hw_register(dev, hw); if (ret) hw = ERR_PTR(ret); return hw; } int visconti_clk_register_gates(struct visconti_clk_provider *ctx, const struct visconti_clk_gate_table *clks, int num_gate, const struct visconti_reset_data *reset, spinlock_t *lock) { struct device *dev = ctx->dev; int i; for (i = 0; i < num_gate; i++) { const char *parent_div_name = clks[i].parent_data[0].name; struct clk_parent_data *pdata; u32 rson_offset, rsoff_offset; struct clk_hw *gate_clk; struct clk_hw *div_clk; char *dev_name; u8 rs_idx; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; dev_name = devm_kasprintf(dev, GFP_KERNEL, "%s_div", clks[i].name); if (!dev_name) return -ENOMEM; if (clks[i].rs_id != NO_RESET) { rson_offset = reset[clks[i].rs_id].rson_offset; rsoff_offset = reset[clks[i].rs_id].rsoff_offset; rs_idx = reset[clks[i].rs_id].rs_idx; } else { rson_offset = rsoff_offset = rs_idx = -1; } div_clk = devm_clk_hw_register_fixed_factor(dev, dev_name, parent_div_name, 0, 1, clks[i].div); if (IS_ERR(div_clk)) return PTR_ERR(div_clk); gate_clk = visconti_clk_register_gate(dev, clks[i].name, dev_name, ctx->regmap, &clks[i], rson_offset, rsoff_offset, rs_idx, lock); if (IS_ERR(gate_clk)) { dev_err(dev, "%s: failed to register clock %s\n", __func__, clks[i].name); return PTR_ERR(gate_clk); } ctx->clk_data.hws[clks[i].id] = gate_clk; } return 0; } struct visconti_clk_provider *visconti_init_clk(struct device *dev, struct regmap *regmap, unsigned long nr_clks) { struct visconti_clk_provider *ctx; int i; ctx = devm_kzalloc(dev, struct_size(ctx, clk_data.hws, nr_clks), GFP_KERNEL); if (!ctx) return ERR_PTR(-ENOMEM); for (i = 0; i < nr_clks; ++i) ctx->clk_data.hws[i] = ERR_PTR(-ENOENT); ctx->clk_data.num = nr_clks; ctx->dev = dev; ctx->regmap = regmap; return ctx; }