162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:
662306a36Sopenharmony_ci *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
762306a36Sopenharmony_ci *   Dmitry Dunaev <dmitry.dunaev@baikalelectronics.ru>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Baikal-T1 CCU Dividers clock driver
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define pr_fmt(fmt) "bt1-ccu-div: " fmt
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/printk.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/clk-provider.h>
1962306a36Sopenharmony_ci#include <linux/reset-controller.h>
2062306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
2162306a36Sopenharmony_ci#include <linux/of.h>
2262306a36Sopenharmony_ci#include <linux/of_address.h>
2362306a36Sopenharmony_ci#include <linux/ioport.h>
2462306a36Sopenharmony_ci#include <linux/regmap.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include <dt-bindings/clock/bt1-ccu.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include "ccu-div.h"
2962306a36Sopenharmony_ci#include "ccu-rst.h"
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define CCU_AXI_MAIN_BASE		0x030
3262306a36Sopenharmony_ci#define CCU_AXI_DDR_BASE		0x034
3362306a36Sopenharmony_ci#define CCU_AXI_SATA_BASE		0x038
3462306a36Sopenharmony_ci#define CCU_AXI_GMAC0_BASE		0x03C
3562306a36Sopenharmony_ci#define CCU_AXI_GMAC1_BASE		0x040
3662306a36Sopenharmony_ci#define CCU_AXI_XGMAC_BASE		0x044
3762306a36Sopenharmony_ci#define CCU_AXI_PCIE_M_BASE		0x048
3862306a36Sopenharmony_ci#define CCU_AXI_PCIE_S_BASE		0x04C
3962306a36Sopenharmony_ci#define CCU_AXI_USB_BASE		0x050
4062306a36Sopenharmony_ci#define CCU_AXI_HWA_BASE		0x054
4162306a36Sopenharmony_ci#define CCU_AXI_SRAM_BASE		0x058
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define CCU_SYS_SATA_REF_BASE		0x060
4462306a36Sopenharmony_ci#define CCU_SYS_APB_BASE		0x064
4562306a36Sopenharmony_ci#define CCU_SYS_GMAC0_BASE		0x068
4662306a36Sopenharmony_ci#define CCU_SYS_GMAC1_BASE		0x06C
4762306a36Sopenharmony_ci#define CCU_SYS_XGMAC_BASE		0x070
4862306a36Sopenharmony_ci#define CCU_SYS_USB_BASE		0x074
4962306a36Sopenharmony_ci#define CCU_SYS_PVT_BASE		0x078
5062306a36Sopenharmony_ci#define CCU_SYS_HWA_BASE		0x07C
5162306a36Sopenharmony_ci#define CCU_SYS_UART_BASE		0x084
5262306a36Sopenharmony_ci#define CCU_SYS_TIMER0_BASE		0x088
5362306a36Sopenharmony_ci#define CCU_SYS_TIMER1_BASE		0x08C
5462306a36Sopenharmony_ci#define CCU_SYS_TIMER2_BASE		0x090
5562306a36Sopenharmony_ci#define CCU_SYS_WDT_BASE		0x150
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci#define CCU_DIV_VAR_INFO(_id, _name, _pname, _base, _width, _flags, _features) \
5862306a36Sopenharmony_ci	{								\
5962306a36Sopenharmony_ci		.id = _id,						\
6062306a36Sopenharmony_ci		.name = _name,						\
6162306a36Sopenharmony_ci		.parent_name = _pname,					\
6262306a36Sopenharmony_ci		.base = _base,						\
6362306a36Sopenharmony_ci		.type = CCU_DIV_VAR,					\
6462306a36Sopenharmony_ci		.width = _width,					\
6562306a36Sopenharmony_ci		.flags = _flags,					\
6662306a36Sopenharmony_ci		.features = _features					\
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci#define CCU_DIV_GATE_INFO(_id, _name, _pname, _base, _divider)	\
7062306a36Sopenharmony_ci	{							\
7162306a36Sopenharmony_ci		.id = _id,					\
7262306a36Sopenharmony_ci		.name = _name,					\
7362306a36Sopenharmony_ci		.parent_name = _pname,				\
7462306a36Sopenharmony_ci		.base = _base,					\
7562306a36Sopenharmony_ci		.type = CCU_DIV_GATE,				\
7662306a36Sopenharmony_ci		.divider = _divider				\
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci#define CCU_DIV_BUF_INFO(_id, _name, _pname, _base, _flags)	\
8062306a36Sopenharmony_ci	{							\
8162306a36Sopenharmony_ci		.id = _id,					\
8262306a36Sopenharmony_ci		.name = _name,					\
8362306a36Sopenharmony_ci		.parent_name = _pname,				\
8462306a36Sopenharmony_ci		.base = _base,					\
8562306a36Sopenharmony_ci		.type = CCU_DIV_BUF,				\
8662306a36Sopenharmony_ci		.flags = _flags					\
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci#define CCU_DIV_FIXED_INFO(_id, _name, _pname, _divider)	\
9062306a36Sopenharmony_ci	{							\
9162306a36Sopenharmony_ci		.id = _id,					\
9262306a36Sopenharmony_ci		.name = _name,					\
9362306a36Sopenharmony_ci		.parent_name = _pname,				\
9462306a36Sopenharmony_ci		.type = CCU_DIV_FIXED,				\
9562306a36Sopenharmony_ci		.divider = _divider				\
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistruct ccu_div_info {
9962306a36Sopenharmony_ci	unsigned int id;
10062306a36Sopenharmony_ci	const char *name;
10162306a36Sopenharmony_ci	const char *parent_name;
10262306a36Sopenharmony_ci	unsigned int base;
10362306a36Sopenharmony_ci	enum ccu_div_type type;
10462306a36Sopenharmony_ci	union {
10562306a36Sopenharmony_ci		unsigned int width;
10662306a36Sopenharmony_ci		unsigned int divider;
10762306a36Sopenharmony_ci	};
10862306a36Sopenharmony_ci	unsigned long flags;
10962306a36Sopenharmony_ci	unsigned long features;
11062306a36Sopenharmony_ci};
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistruct ccu_div_data {
11362306a36Sopenharmony_ci	struct device_node *np;
11462306a36Sopenharmony_ci	struct regmap *sys_regs;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	unsigned int divs_num;
11762306a36Sopenharmony_ci	const struct ccu_div_info *divs_info;
11862306a36Sopenharmony_ci	struct ccu_div **divs;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	struct ccu_rst *rsts;
12162306a36Sopenharmony_ci};
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci/*
12462306a36Sopenharmony_ci * AXI Main Interconnect (axi_main_clk) and DDR AXI-bus (axi_ddr_clk) clocks
12562306a36Sopenharmony_ci * must be left enabled in any case, since former one is responsible for
12662306a36Sopenharmony_ci * clocking a bus between CPU cores and the rest of the SoC components, while
12762306a36Sopenharmony_ci * the later is clocking the AXI-bus between DDR controller and the Main
12862306a36Sopenharmony_ci * Interconnect. So should any of these clocks get to be disabled, the system
12962306a36Sopenharmony_ci * will literally stop working. That's why we marked them as critical.
13062306a36Sopenharmony_ci */
13162306a36Sopenharmony_cistatic const struct ccu_div_info axi_info[] = {
13262306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_MAIN_CLK, "axi_main_clk", "pcie_clk",
13362306a36Sopenharmony_ci			 CCU_AXI_MAIN_BASE, 4,
13462306a36Sopenharmony_ci			 CLK_IS_CRITICAL, CCU_DIV_RESET_DOMAIN),
13562306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_DDR_CLK, "axi_ddr_clk", "sata_clk",
13662306a36Sopenharmony_ci			 CCU_AXI_DDR_BASE, 4,
13762306a36Sopenharmony_ci			 CLK_IS_CRITICAL | CLK_SET_RATE_GATE,
13862306a36Sopenharmony_ci			 CCU_DIV_RESET_DOMAIN),
13962306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_SATA_CLK, "axi_sata_clk", "sata_clk",
14062306a36Sopenharmony_ci			 CCU_AXI_SATA_BASE, 4,
14162306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
14262306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_GMAC0_CLK, "axi_gmac0_clk", "eth_clk",
14362306a36Sopenharmony_ci			 CCU_AXI_GMAC0_BASE, 4,
14462306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
14562306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_GMAC1_CLK, "axi_gmac1_clk", "eth_clk",
14662306a36Sopenharmony_ci			 CCU_AXI_GMAC1_BASE, 4,
14762306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
14862306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_XGMAC_CLK, "axi_xgmac_clk", "eth_clk",
14962306a36Sopenharmony_ci			 CCU_AXI_XGMAC_BASE, 4,
15062306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
15162306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_PCIE_M_CLK, "axi_pcie_m_clk", "pcie_clk",
15262306a36Sopenharmony_ci			 CCU_AXI_PCIE_M_BASE, 4,
15362306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
15462306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_PCIE_S_CLK, "axi_pcie_s_clk", "pcie_clk",
15562306a36Sopenharmony_ci			 CCU_AXI_PCIE_S_BASE, 4,
15662306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
15762306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_USB_CLK, "axi_usb_clk", "sata_clk",
15862306a36Sopenharmony_ci			 CCU_AXI_USB_BASE, 4,
15962306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
16062306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_HWA_CLK, "axi_hwa_clk", "sata_clk",
16162306a36Sopenharmony_ci			 CCU_AXI_HWA_BASE, 4,
16262306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN),
16362306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_AXI_SRAM_CLK, "axi_sram_clk", "eth_clk",
16462306a36Sopenharmony_ci			 CCU_AXI_SRAM_BASE, 4,
16562306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_RESET_DOMAIN)
16662306a36Sopenharmony_ci};
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci/*
16962306a36Sopenharmony_ci * APB-bus clock is marked as critical since it's a main communication bus
17062306a36Sopenharmony_ci * for the SoC devices registers IO-operations.
17162306a36Sopenharmony_ci */
17262306a36Sopenharmony_cistatic const struct ccu_div_info sys_info[] = {
17362306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_SATA_CLK, "sys_sata_clk",
17462306a36Sopenharmony_ci			 "sata_clk", CCU_SYS_SATA_REF_BASE, 4,
17562306a36Sopenharmony_ci			 CLK_SET_RATE_GATE,
17662306a36Sopenharmony_ci			 CCU_DIV_SKIP_ONE | CCU_DIV_LOCK_SHIFTED |
17762306a36Sopenharmony_ci			 CCU_DIV_RESET_DOMAIN),
17862306a36Sopenharmony_ci	CCU_DIV_BUF_INFO(CCU_SYS_SATA_REF_CLK, "sys_sata_ref_clk",
17962306a36Sopenharmony_ci			 "sys_sata_clk", CCU_SYS_SATA_REF_BASE,
18062306a36Sopenharmony_ci			 CLK_SET_RATE_PARENT),
18162306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_APB_CLK, "sys_apb_clk",
18262306a36Sopenharmony_ci			 "pcie_clk", CCU_SYS_APB_BASE, 5,
18362306a36Sopenharmony_ci			 CLK_IS_CRITICAL, CCU_DIV_BASIC | CCU_DIV_RESET_DOMAIN),
18462306a36Sopenharmony_ci	CCU_DIV_GATE_INFO(CCU_SYS_GMAC0_TX_CLK, "sys_gmac0_tx_clk",
18562306a36Sopenharmony_ci			  "eth_clk", CCU_SYS_GMAC0_BASE, 5),
18662306a36Sopenharmony_ci	CCU_DIV_FIXED_INFO(CCU_SYS_GMAC0_PTP_CLK, "sys_gmac0_ptp_clk",
18762306a36Sopenharmony_ci			   "eth_clk", 10),
18862306a36Sopenharmony_ci	CCU_DIV_GATE_INFO(CCU_SYS_GMAC1_TX_CLK, "sys_gmac1_tx_clk",
18962306a36Sopenharmony_ci			  "eth_clk", CCU_SYS_GMAC1_BASE, 5),
19062306a36Sopenharmony_ci	CCU_DIV_FIXED_INFO(CCU_SYS_GMAC1_PTP_CLK, "sys_gmac1_ptp_clk",
19162306a36Sopenharmony_ci			   "eth_clk", 10),
19262306a36Sopenharmony_ci	CCU_DIV_GATE_INFO(CCU_SYS_XGMAC_CLK, "sys_xgmac_clk",
19362306a36Sopenharmony_ci			  "eth_clk", CCU_SYS_XGMAC_BASE, 1),
19462306a36Sopenharmony_ci	CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_REF_CLK, "sys_xgmac_ref_clk",
19562306a36Sopenharmony_ci			   "sys_xgmac_clk", 8),
19662306a36Sopenharmony_ci	CCU_DIV_FIXED_INFO(CCU_SYS_XGMAC_PTP_CLK, "sys_xgmac_ptp_clk",
19762306a36Sopenharmony_ci			   "sys_xgmac_clk", 8),
19862306a36Sopenharmony_ci	CCU_DIV_GATE_INFO(CCU_SYS_USB_CLK, "sys_usb_clk",
19962306a36Sopenharmony_ci			  "eth_clk", CCU_SYS_USB_BASE, 10),
20062306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_PVT_CLK, "sys_pvt_clk",
20162306a36Sopenharmony_ci			 "ref_clk", CCU_SYS_PVT_BASE, 5,
20262306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, 0),
20362306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_HWA_CLK, "sys_hwa_clk",
20462306a36Sopenharmony_ci			 "sata_clk", CCU_SYS_HWA_BASE, 4,
20562306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, 0),
20662306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_UART_CLK, "sys_uart_clk",
20762306a36Sopenharmony_ci			 "eth_clk", CCU_SYS_UART_BASE, 17,
20862306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, 0),
20962306a36Sopenharmony_ci	CCU_DIV_FIXED_INFO(CCU_SYS_I2C1_CLK, "sys_i2c1_clk",
21062306a36Sopenharmony_ci			   "eth_clk", 10),
21162306a36Sopenharmony_ci	CCU_DIV_FIXED_INFO(CCU_SYS_I2C2_CLK, "sys_i2c2_clk",
21262306a36Sopenharmony_ci			   "eth_clk", 10),
21362306a36Sopenharmony_ci	CCU_DIV_FIXED_INFO(CCU_SYS_GPIO_CLK, "sys_gpio_clk",
21462306a36Sopenharmony_ci			   "ref_clk", 25),
21562306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_TIMER0_CLK, "sys_timer0_clk",
21662306a36Sopenharmony_ci			 "ref_clk", CCU_SYS_TIMER0_BASE, 17,
21762306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_BASIC),
21862306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_TIMER1_CLK, "sys_timer1_clk",
21962306a36Sopenharmony_ci			 "ref_clk", CCU_SYS_TIMER1_BASE, 17,
22062306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_BASIC),
22162306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_TIMER2_CLK, "sys_timer2_clk",
22262306a36Sopenharmony_ci			 "ref_clk", CCU_SYS_TIMER2_BASE, 17,
22362306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_BASIC),
22462306a36Sopenharmony_ci	CCU_DIV_VAR_INFO(CCU_SYS_WDT_CLK, "sys_wdt_clk",
22562306a36Sopenharmony_ci			 "eth_clk", CCU_SYS_WDT_BASE, 17,
22662306a36Sopenharmony_ci			 CLK_SET_RATE_GATE, CCU_DIV_SKIP_ONE_TO_THREE)
22762306a36Sopenharmony_ci};
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic struct ccu_div_data *axi_data;
23062306a36Sopenharmony_cistatic struct ccu_div_data *sys_data;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic void ccu_div_set_data(struct ccu_div_data *data)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	struct device_node *np = data->np;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	if (of_device_is_compatible(np, "baikal,bt1-ccu-axi"))
23762306a36Sopenharmony_ci		axi_data = data;
23862306a36Sopenharmony_ci	else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys"))
23962306a36Sopenharmony_ci		sys_data = data;
24062306a36Sopenharmony_ci	else
24162306a36Sopenharmony_ci		pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np));
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic struct ccu_div_data *ccu_div_get_data(struct device_node *np)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	if (of_device_is_compatible(np, "baikal,bt1-ccu-axi"))
24762306a36Sopenharmony_ci		return axi_data;
24862306a36Sopenharmony_ci	else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys"))
24962306a36Sopenharmony_ci		return sys_data;
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	pr_err("Invalid DT node '%s' specified\n", of_node_full_name(np));
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return NULL;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic struct ccu_div *ccu_div_find_desc(struct ccu_div_data *data,
25762306a36Sopenharmony_ci					 unsigned int clk_id)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	int idx;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	for (idx = 0; idx < data->divs_num; ++idx) {
26262306a36Sopenharmony_ci		if (data->divs_info[idx].id == clk_id)
26362306a36Sopenharmony_ci			return data->divs[idx];
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	return ERR_PTR(-EINVAL);
26762306a36Sopenharmony_ci}
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_cistatic struct ccu_div_data *ccu_div_create_data(struct device_node *np)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	struct ccu_div_data *data;
27262306a36Sopenharmony_ci	int ret;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	data = kzalloc(sizeof(*data), GFP_KERNEL);
27562306a36Sopenharmony_ci	if (!data)
27662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	data->np = np;
27962306a36Sopenharmony_ci	if (of_device_is_compatible(np, "baikal,bt1-ccu-axi")) {
28062306a36Sopenharmony_ci		data->divs_num = ARRAY_SIZE(axi_info);
28162306a36Sopenharmony_ci		data->divs_info = axi_info;
28262306a36Sopenharmony_ci	} else if (of_device_is_compatible(np, "baikal,bt1-ccu-sys")) {
28362306a36Sopenharmony_ci		data->divs_num = ARRAY_SIZE(sys_info);
28462306a36Sopenharmony_ci		data->divs_info = sys_info;
28562306a36Sopenharmony_ci	} else {
28662306a36Sopenharmony_ci		pr_err("Incompatible DT node '%s' specified\n",
28762306a36Sopenharmony_ci			of_node_full_name(np));
28862306a36Sopenharmony_ci		ret = -EINVAL;
28962306a36Sopenharmony_ci		goto err_kfree_data;
29062306a36Sopenharmony_ci	}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	data->divs = kcalloc(data->divs_num, sizeof(*data->divs), GFP_KERNEL);
29362306a36Sopenharmony_ci	if (!data->divs) {
29462306a36Sopenharmony_ci		ret = -ENOMEM;
29562306a36Sopenharmony_ci		goto err_kfree_data;
29662306a36Sopenharmony_ci	}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	return data;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_cierr_kfree_data:
30162306a36Sopenharmony_ci	kfree(data);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	return ERR_PTR(ret);
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_cistatic void ccu_div_free_data(struct ccu_div_data *data)
30762306a36Sopenharmony_ci{
30862306a36Sopenharmony_ci	kfree(data->divs);
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	kfree(data);
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic int ccu_div_find_sys_regs(struct ccu_div_data *data)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	data->sys_regs = syscon_node_to_regmap(data->np->parent);
31662306a36Sopenharmony_ci	if (IS_ERR(data->sys_regs)) {
31762306a36Sopenharmony_ci		pr_err("Failed to find syscon regs for '%s'\n",
31862306a36Sopenharmony_ci			of_node_full_name(data->np));
31962306a36Sopenharmony_ci		return PTR_ERR(data->sys_regs);
32062306a36Sopenharmony_ci	}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	return 0;
32362306a36Sopenharmony_ci}
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_cistatic struct clk_hw *ccu_div_of_clk_hw_get(struct of_phandle_args *clkspec,
32662306a36Sopenharmony_ci					    void *priv)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	struct ccu_div_data *data = priv;
32962306a36Sopenharmony_ci	struct ccu_div *div;
33062306a36Sopenharmony_ci	unsigned int clk_id;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	clk_id = clkspec->args[0];
33362306a36Sopenharmony_ci	div = ccu_div_find_desc(data, clk_id);
33462306a36Sopenharmony_ci	if (IS_ERR(div)) {
33562306a36Sopenharmony_ci		if (div != ERR_PTR(-EPROBE_DEFER))
33662306a36Sopenharmony_ci			pr_info("Invalid clock ID %d specified\n", clk_id);
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci		return ERR_CAST(div);
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	return ccu_div_get_clk_hw(div);
34262306a36Sopenharmony_ci}
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_cistatic int ccu_div_clk_register(struct ccu_div_data *data, bool defer)
34562306a36Sopenharmony_ci{
34662306a36Sopenharmony_ci	int idx, ret;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	for (idx = 0; idx < data->divs_num; ++idx) {
34962306a36Sopenharmony_ci		const struct ccu_div_info *info = &data->divs_info[idx];
35062306a36Sopenharmony_ci		struct ccu_div_init_data init = {0};
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci		if (!!(info->features & CCU_DIV_BASIC) ^ defer) {
35362306a36Sopenharmony_ci			if (!data->divs[idx])
35462306a36Sopenharmony_ci				data->divs[idx] = ERR_PTR(-EPROBE_DEFER);
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci			continue;
35762306a36Sopenharmony_ci		}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci		init.id = info->id;
36062306a36Sopenharmony_ci		init.name = info->name;
36162306a36Sopenharmony_ci		init.parent_name = info->parent_name;
36262306a36Sopenharmony_ci		init.np = data->np;
36362306a36Sopenharmony_ci		init.type = info->type;
36462306a36Sopenharmony_ci		init.flags = info->flags;
36562306a36Sopenharmony_ci		init.features = info->features;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci		if (init.type == CCU_DIV_VAR) {
36862306a36Sopenharmony_ci			init.base = info->base;
36962306a36Sopenharmony_ci			init.sys_regs = data->sys_regs;
37062306a36Sopenharmony_ci			init.width = info->width;
37162306a36Sopenharmony_ci		} else if (init.type == CCU_DIV_GATE) {
37262306a36Sopenharmony_ci			init.base = info->base;
37362306a36Sopenharmony_ci			init.sys_regs = data->sys_regs;
37462306a36Sopenharmony_ci			init.divider = info->divider;
37562306a36Sopenharmony_ci		} else if (init.type == CCU_DIV_BUF) {
37662306a36Sopenharmony_ci			init.base = info->base;
37762306a36Sopenharmony_ci			init.sys_regs = data->sys_regs;
37862306a36Sopenharmony_ci		} else {
37962306a36Sopenharmony_ci			init.divider = info->divider;
38062306a36Sopenharmony_ci		}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci		data->divs[idx] = ccu_div_hw_register(&init);
38362306a36Sopenharmony_ci		if (IS_ERR(data->divs[idx])) {
38462306a36Sopenharmony_ci			ret = PTR_ERR(data->divs[idx]);
38562306a36Sopenharmony_ci			pr_err("Couldn't register divider '%s' hw\n",
38662306a36Sopenharmony_ci				init.name);
38762306a36Sopenharmony_ci			goto err_hw_unregister;
38862306a36Sopenharmony_ci		}
38962306a36Sopenharmony_ci	}
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	return 0;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cierr_hw_unregister:
39462306a36Sopenharmony_ci	for (--idx; idx >= 0; --idx) {
39562306a36Sopenharmony_ci		if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer)
39662306a36Sopenharmony_ci			continue;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci		ccu_div_hw_unregister(data->divs[idx]);
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	return ret;
40262306a36Sopenharmony_ci}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic void ccu_div_clk_unregister(struct ccu_div_data *data, bool defer)
40562306a36Sopenharmony_ci{
40662306a36Sopenharmony_ci	int idx;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	/* Uninstall only the clocks registered on the specfied stage */
40962306a36Sopenharmony_ci	for (idx = 0; idx < data->divs_num; ++idx) {
41062306a36Sopenharmony_ci		if (!!(data->divs_info[idx].features & CCU_DIV_BASIC) ^ defer)
41162306a36Sopenharmony_ci			continue;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci		ccu_div_hw_unregister(data->divs[idx]);
41462306a36Sopenharmony_ci	}
41562306a36Sopenharmony_ci}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic int ccu_div_of_register(struct ccu_div_data *data)
41862306a36Sopenharmony_ci{
41962306a36Sopenharmony_ci	int ret;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	ret = of_clk_add_hw_provider(data->np, ccu_div_of_clk_hw_get, data);
42262306a36Sopenharmony_ci	if (ret) {
42362306a36Sopenharmony_ci		pr_err("Couldn't register dividers '%s' clock provider\n",
42462306a36Sopenharmony_ci		       of_node_full_name(data->np));
42562306a36Sopenharmony_ci	}
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	return ret;
42862306a36Sopenharmony_ci}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_cistatic int ccu_div_rst_register(struct ccu_div_data *data)
43162306a36Sopenharmony_ci{
43262306a36Sopenharmony_ci	struct ccu_rst_init_data init = {0};
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	init.sys_regs = data->sys_regs;
43562306a36Sopenharmony_ci	init.np = data->np;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	data->rsts = ccu_rst_hw_register(&init);
43862306a36Sopenharmony_ci	if (IS_ERR(data->rsts)) {
43962306a36Sopenharmony_ci		pr_err("Couldn't register divider '%s' reset controller\n",
44062306a36Sopenharmony_ci			of_node_full_name(data->np));
44162306a36Sopenharmony_ci		return PTR_ERR(data->rsts);
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	return 0;
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_cistatic int ccu_div_probe(struct platform_device *pdev)
44862306a36Sopenharmony_ci{
44962306a36Sopenharmony_ci	struct ccu_div_data *data;
45062306a36Sopenharmony_ci	int ret;
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	data = ccu_div_get_data(dev_of_node(&pdev->dev));
45362306a36Sopenharmony_ci	if (!data)
45462306a36Sopenharmony_ci		return -EINVAL;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	ret = ccu_div_clk_register(data, false);
45762306a36Sopenharmony_ci	if (ret)
45862306a36Sopenharmony_ci		return ret;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	ret = ccu_div_rst_register(data);
46162306a36Sopenharmony_ci	if (ret)
46262306a36Sopenharmony_ci		goto err_clk_unregister;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	return 0;
46562306a36Sopenharmony_ci
46662306a36Sopenharmony_cierr_clk_unregister:
46762306a36Sopenharmony_ci	ccu_div_clk_unregister(data, false);
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	return ret;
47062306a36Sopenharmony_ci}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_cistatic const struct of_device_id ccu_div_of_match[] = {
47362306a36Sopenharmony_ci	{ .compatible = "baikal,bt1-ccu-axi" },
47462306a36Sopenharmony_ci	{ .compatible = "baikal,bt1-ccu-sys" },
47562306a36Sopenharmony_ci	{ }
47662306a36Sopenharmony_ci};
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_cistatic struct platform_driver ccu_div_driver = {
47962306a36Sopenharmony_ci	.probe  = ccu_div_probe,
48062306a36Sopenharmony_ci	.driver = {
48162306a36Sopenharmony_ci		.name = "clk-ccu-div",
48262306a36Sopenharmony_ci		.of_match_table = ccu_div_of_match,
48362306a36Sopenharmony_ci		.suppress_bind_attrs = true,
48462306a36Sopenharmony_ci	},
48562306a36Sopenharmony_ci};
48662306a36Sopenharmony_cibuiltin_platform_driver(ccu_div_driver);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_cistatic __init void ccu_div_init(struct device_node *np)
48962306a36Sopenharmony_ci{
49062306a36Sopenharmony_ci	struct ccu_div_data *data;
49162306a36Sopenharmony_ci	int ret;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	data = ccu_div_create_data(np);
49462306a36Sopenharmony_ci	if (IS_ERR(data))
49562306a36Sopenharmony_ci		return;
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	ret = ccu_div_find_sys_regs(data);
49862306a36Sopenharmony_ci	if (ret)
49962306a36Sopenharmony_ci		goto err_free_data;
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	ret = ccu_div_clk_register(data, true);
50262306a36Sopenharmony_ci	if (ret)
50362306a36Sopenharmony_ci		goto err_free_data;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	ret = ccu_div_of_register(data);
50662306a36Sopenharmony_ci	if (ret)
50762306a36Sopenharmony_ci		goto err_clk_unregister;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	ccu_div_set_data(data);
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	return;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_cierr_clk_unregister:
51462306a36Sopenharmony_ci	ccu_div_clk_unregister(data, true);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_cierr_free_data:
51762306a36Sopenharmony_ci	ccu_div_free_data(data);
51862306a36Sopenharmony_ci}
51962306a36Sopenharmony_ciCLK_OF_DECLARE_DRIVER(ccu_axi, "baikal,bt1-ccu-axi", ccu_div_init);
52062306a36Sopenharmony_ciCLK_OF_DECLARE_DRIVER(ccu_sys, "baikal,bt1-ccu-sys", ccu_div_init);
521