162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2023 Nuvoton Technology Corp.
462306a36Sopenharmony_ci * Author: Chi-Fang Li <cfli0@nuvoton.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/clk-provider.h>
862306a36Sopenharmony_ci#include <linux/device.h>
962306a36Sopenharmony_ci#include <linux/regmap.h>
1062306a36Sopenharmony_ci#include <linux/spinlock.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "clk-ma35d1.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistruct ma35d1_adc_clk_div {
1562306a36Sopenharmony_ci	struct clk_hw hw;
1662306a36Sopenharmony_ci	void __iomem *reg;
1762306a36Sopenharmony_ci	u8 shift;
1862306a36Sopenharmony_ci	u8 width;
1962306a36Sopenharmony_ci	u32 mask;
2062306a36Sopenharmony_ci	const struct clk_div_table *table;
2162306a36Sopenharmony_ci	/* protects concurrent access to clock divider registers */
2262306a36Sopenharmony_ci	spinlock_t *lock;
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic inline struct ma35d1_adc_clk_div *to_ma35d1_adc_clk_div(struct clk_hw *_hw)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	return container_of(_hw, struct ma35d1_adc_clk_div, hw);
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic unsigned long ma35d1_clkdiv_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	unsigned int val;
3362306a36Sopenharmony_ci	struct ma35d1_adc_clk_div *dclk = to_ma35d1_adc_clk_div(hw);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	val = readl_relaxed(dclk->reg) >> dclk->shift;
3662306a36Sopenharmony_ci	val &= clk_div_mask(dclk->width);
3762306a36Sopenharmony_ci	val += 1;
3862306a36Sopenharmony_ci	return divider_recalc_rate(hw, parent_rate, val, dclk->table,
3962306a36Sopenharmony_ci				   CLK_DIVIDER_ROUND_CLOSEST, dclk->width);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic long ma35d1_clkdiv_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct ma35d1_adc_clk_div *dclk = to_ma35d1_adc_clk_div(hw);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	return divider_round_rate(hw, rate, prate, dclk->table,
4762306a36Sopenharmony_ci				  dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic int ma35d1_clkdiv_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	int value;
5362306a36Sopenharmony_ci	unsigned long flags = 0;
5462306a36Sopenharmony_ci	u32 data;
5562306a36Sopenharmony_ci	struct ma35d1_adc_clk_div *dclk = to_ma35d1_adc_clk_div(hw);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	value = divider_get_val(rate, parent_rate, dclk->table,
5862306a36Sopenharmony_ci				dclk->width, CLK_DIVIDER_ROUND_CLOSEST);
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	spin_lock_irqsave(dclk->lock, flags);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	data = readl_relaxed(dclk->reg);
6362306a36Sopenharmony_ci	data &= ~(clk_div_mask(dclk->width) << dclk->shift);
6462306a36Sopenharmony_ci	data |= (value - 1) << dclk->shift;
6562306a36Sopenharmony_ci	data |= dclk->mask;
6662306a36Sopenharmony_ci	writel_relaxed(data, dclk->reg);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	spin_unlock_irqrestore(dclk->lock, flags);
6962306a36Sopenharmony_ci	return 0;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic const struct clk_ops ma35d1_adc_clkdiv_ops = {
7362306a36Sopenharmony_ci	.recalc_rate = ma35d1_clkdiv_recalc_rate,
7462306a36Sopenharmony_ci	.round_rate = ma35d1_clkdiv_round_rate,
7562306a36Sopenharmony_ci	.set_rate = ma35d1_clkdiv_set_rate,
7662306a36Sopenharmony_ci};
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistruct clk_hw *ma35d1_reg_adc_clkdiv(struct device *dev, const char *name,
7962306a36Sopenharmony_ci				     struct clk_hw *parent_hw, spinlock_t *lock,
8062306a36Sopenharmony_ci				     unsigned long flags, void __iomem *reg,
8162306a36Sopenharmony_ci				     u8 shift, u8 width, u32 mask_bit)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct ma35d1_adc_clk_div *div;
8462306a36Sopenharmony_ci	struct clk_init_data init;
8562306a36Sopenharmony_ci	struct clk_div_table *table;
8662306a36Sopenharmony_ci	struct clk_parent_data pdata = { .index = 0 };
8762306a36Sopenharmony_ci	u32 max_div, min_div;
8862306a36Sopenharmony_ci	struct clk_hw *hw;
8962306a36Sopenharmony_ci	int ret;
9062306a36Sopenharmony_ci	int i;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
9362306a36Sopenharmony_ci	if (!div)
9462306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	max_div = clk_div_mask(width) + 1;
9762306a36Sopenharmony_ci	min_div = 1;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	table = devm_kcalloc(dev, max_div + 1, sizeof(*table), GFP_KERNEL);
10062306a36Sopenharmony_ci	if (!table)
10162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	for (i = 0; i < max_div; i++) {
10462306a36Sopenharmony_ci		table[i].val = min_div + i;
10562306a36Sopenharmony_ci		table[i].div = 2 * table[i].val;
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci	table[max_div].val = 0;
10862306a36Sopenharmony_ci	table[max_div].div = 0;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	memset(&init, 0, sizeof(init));
11162306a36Sopenharmony_ci	init.name = name;
11262306a36Sopenharmony_ci	init.ops = &ma35d1_adc_clkdiv_ops;
11362306a36Sopenharmony_ci	init.flags |= flags;
11462306a36Sopenharmony_ci	pdata.hw = parent_hw;
11562306a36Sopenharmony_ci	init.parent_data = &pdata;
11662306a36Sopenharmony_ci	init.num_parents = 1;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	div->reg = reg;
11962306a36Sopenharmony_ci	div->shift = shift;
12062306a36Sopenharmony_ci	div->width = width;
12162306a36Sopenharmony_ci	div->mask = mask_bit ? BIT(mask_bit) : 0;
12262306a36Sopenharmony_ci	div->lock = lock;
12362306a36Sopenharmony_ci	div->hw.init = &init;
12462306a36Sopenharmony_ci	div->table = table;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	hw = &div->hw;
12762306a36Sopenharmony_ci	ret = devm_clk_hw_register(dev, hw);
12862306a36Sopenharmony_ci	if (ret)
12962306a36Sopenharmony_ci		return ERR_PTR(ret);
13062306a36Sopenharmony_ci	return hw;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ma35d1_reg_adc_clkdiv);
133