3351 lines · 83.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * ti-sysc.c - Texas Instruments sysc interconnect target driver * * TI SoCs have an interconnect target wrapper IP for many devices. The wrapper * IP manages clock gating, resets, and PM capabilities for the connected devices. * * Copyright (C) 2017-2024 Texas Instruments Incorporated - https://www.ti.com/ * * Many features are based on the earlier omap_hwmod arch code with thanks to all * the people who developed and debugged the code over the years: * * Copyright (C) 2009-2011 Nokia Corporation * Copyright (C) 2011-2021 Texas Instruments Incorporated - https://www.ti.com/ */ #include <linux/io.h>#include <linux/clk.h>#include <linux/clkdev.h>#include <linux/cpu_pm.h>#include <linux/delay.h>#include <linux/list.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/pm_domain.h>#include <linux/pm_runtime.h>#include <linux/reset.h>#include <linux/of_address.h>#include <linux/of_platform.h>#include <linux/slab.h>#include <linux/sys_soc.h>#include <linux/timekeeping.h>#include <linux/iopoll.h> #include <linux/platform_data/ti-sysc.h> #include <dt-bindings/bus/ti-sysc.h> #define DIS_ISP		BIT(2)#define DIS_IVA		BIT(1)#define DIS_SGX		BIT(0) #define SOC_FLAG(match, flag)	{ .machine = match, .data = (void *)(flag), } #define MAX_MODULE_SOFTRESET_WAIT		10000 enum sysc_soc {	SOC_UNKNOWN,	SOC_2420,	SOC_2430,	SOC_AM33,	SOC_3430,	SOC_AM35,	SOC_3630,	SOC_4430,	SOC_4460,	SOC_4470,	SOC_5430,	SOC_AM3,	SOC_AM4,	SOC_DRA7,}; struct sysc_address {	unsigned long base;	struct list_head node;}; struct sysc_module {	struct sysc *ddata;	struct list_head node;}; struct sysc_soc_info {	unsigned long general_purpose:1;	enum sysc_soc soc;	struct mutex list_lock;	/* disabled and restored modules list lock */	struct list_head disabled_modules;	struct list_head restored_modules;	struct notifier_block nb;}; enum sysc_clocks {	SYSC_FCK,	SYSC_ICK,	SYSC_OPTFCK0,	SYSC_OPTFCK1,	SYSC_OPTFCK2,	SYSC_OPTFCK3,	SYSC_OPTFCK4,	SYSC_OPTFCK5,	SYSC_OPTFCK6,	SYSC_OPTFCK7,	SYSC_MAX_CLOCKS,}; static struct sysc_soc_info *sysc_soc;static const char * const reg_names[] = { "rev", "sysc", "syss", };static const char * const clock_names[SYSC_MAX_CLOCKS] = {	"fck", "ick", "opt0", "opt1", "opt2", "opt3", "opt4",	"opt5", "opt6", "opt7",}; #define SYSC_IDLEMODE_MASK		3#define SYSC_CLOCKACTIVITY_MASK		3 /** * struct sysc - TI sysc interconnect target module registers and capabilities * @dev: struct device pointer * @module_pa: physical address of the interconnect target module * @module_size: size of the interconnect target module * @module_va: virtual address of the interconnect target module * @offsets: register offsets from module base * @mdata: ti-sysc to hwmod translation data for a module * @clocks: clocks used by the interconnect target module * @clock_roles: clock role names for the found clocks * @nr_clocks: number of clocks used by the interconnect target module * @rsts: resets used by the interconnect target module * @legacy_mode: configured for legacy mode if set * @cap: interconnect target module capabilities * @cfg: interconnect target module configuration * @cookie: data used by legacy platform callbacks * @name: name if available * @revision: interconnect target module revision * @sysconfig: saved sysconfig register value * @reserved: target module is reserved and already in use * @enabled: sysc runtime enabled status * @needs_resume: runtime resume needed on resume from suspend * @child_needs_resume: runtime resume needed for child on resume from suspend * @idle_work: work structure used to perform delayed idle on a module * @pre_reset_quirk: module specific pre-reset quirk * @post_reset_quirk: module specific post-reset quirk * @reset_done_quirk: module specific reset done quirk * @module_enable_quirk: module specific enable quirk * @module_disable_quirk: module specific disable quirk * @module_unlock_quirk: module specific sysconfig unlock quirk * @module_lock_quirk: module specific sysconfig lock quirk */struct sysc {	struct device *dev;	u64 module_pa;	u32 module_size;	void __iomem *module_va;	int offsets[SYSC_MAX_REGS];	struct ti_sysc_module_data *mdata;	struct clk **clocks;	const char **clock_roles;	int nr_clocks;	struct reset_control *rsts;	const char *legacy_mode;	const struct sysc_capabilities *cap;	struct sysc_config cfg;	struct ti_sysc_cookie cookie;	const char *name;	u32 revision;	u32 sysconfig;	unsigned int reserved:1;	unsigned int enabled:1;	unsigned int needs_resume:1;	unsigned int child_needs_resume:1;	struct delayed_work idle_work;	void (*pre_reset_quirk)(struct sysc *sysc);	void (*post_reset_quirk)(struct sysc *sysc);	void (*reset_done_quirk)(struct sysc *sysc);	void (*module_enable_quirk)(struct sysc *sysc);	void (*module_disable_quirk)(struct sysc *sysc);	void (*module_unlock_quirk)(struct sysc *sysc);	void (*module_lock_quirk)(struct sysc *sysc);}; static void sysc_parse_dts_quirks(struct sysc *ddata, struct device_node *np,				  bool is_child);static int sysc_reset(struct sysc *ddata); static void sysc_write(struct sysc *ddata, int offset, u32 value){	if (ddata->cfg.quirks & SYSC_QUIRK_16BIT) {		writew_relaxed(value & 0xffff, ddata->module_va + offset); 		/* Only i2c revision has LO and HI register with stride of 4 */		if (ddata->offsets[SYSC_REVISION] >= 0 &&		    offset == ddata->offsets[SYSC_REVISION]) {			u16 hi = value >> 16; 			writew_relaxed(hi, ddata->module_va + offset + 4);		} 		return;	} 	writel_relaxed(value, ddata->module_va + offset);} static u32 sysc_read(struct sysc *ddata, int offset){	if (ddata->cfg.quirks & SYSC_QUIRK_16BIT) {		u32 val; 		val = readw_relaxed(ddata->module_va + offset); 		/* Only i2c revision has LO and HI register with stride of 4 */		if (ddata->offsets[SYSC_REVISION] >= 0 &&		    offset == ddata->offsets[SYSC_REVISION]) {			u16 tmp = readw_relaxed(ddata->module_va + offset + 4); 			val |= tmp << 16;		} 		return val;	} 	return readl_relaxed(ddata->module_va + offset);} static bool sysc_opt_clks_needed(struct sysc *ddata){	return !!(ddata->cfg.quirks & SYSC_QUIRK_OPT_CLKS_NEEDED);} static u32 sysc_read_revision(struct sysc *ddata){	int offset = ddata->offsets[SYSC_REVISION]; 	if (offset < 0)		return 0; 	return sysc_read(ddata, offset);} static u32 sysc_read_sysconfig(struct sysc *ddata){	int offset = ddata->offsets[SYSC_SYSCONFIG]; 	if (offset < 0)		return 0; 	return sysc_read(ddata, offset);} static u32 sysc_read_sysstatus(struct sysc *ddata){	int offset = ddata->offsets[SYSC_SYSSTATUS]; 	if (offset < 0)		return 0; 	return sysc_read(ddata, offset);} static int sysc_poll_reset_sysstatus(struct sysc *ddata){	int error, retries;	u32 syss_done, rstval; 	if (ddata->cfg.quirks & SYSS_QUIRK_RESETDONE_INVERTED)		syss_done = 0;	else		syss_done = ddata->cfg.syss_mask; 	if (likely(!timekeeping_suspended)) {		error = readx_poll_timeout_atomic(sysc_read_sysstatus, ddata,				rstval, (rstval & ddata->cfg.syss_mask) ==				syss_done, 100, MAX_MODULE_SOFTRESET_WAIT);	} else {		retries = MAX_MODULE_SOFTRESET_WAIT;		while (retries--) {			rstval = sysc_read_sysstatus(ddata);			if ((rstval & ddata->cfg.syss_mask) == syss_done)				return 0;			udelay(2); /* Account for udelay flakeyness */		}		error = -ETIMEDOUT;	} 	return error;} static int sysc_poll_reset_sysconfig(struct sysc *ddata){	int error, retries;	u32 sysc_mask, rstval; 	sysc_mask = BIT(ddata->cap->regbits->srst_shift); 	if (likely(!timekeeping_suspended)) {		error = readx_poll_timeout_atomic(sysc_read_sysconfig, ddata,				rstval, !(rstval & sysc_mask),				100, MAX_MODULE_SOFTRESET_WAIT);	} else {		retries = MAX_MODULE_SOFTRESET_WAIT;		while (retries--) {			rstval = sysc_read_sysconfig(ddata);			if (!(rstval & sysc_mask))				return 0;			udelay(2); /* Account for udelay flakeyness */		}		error = -ETIMEDOUT;	} 	return error;} /* Poll on reset status */static int sysc_wait_softreset(struct sysc *ddata){	int syss_offset, error = 0; 	if (ddata->cap->regbits->srst_shift < 0)		return 0; 	syss_offset = ddata->offsets[SYSC_SYSSTATUS]; 	if (syss_offset >= 0)		error = sysc_poll_reset_sysstatus(ddata);	else if (ddata->cfg.quirks & SYSC_QUIRK_RESET_STATUS)		error = sysc_poll_reset_sysconfig(ddata); 	return error;} static int sysc_add_named_clock_from_child(struct sysc *ddata,					   const char *name,					   const char *optfck_name){	struct device_node *np = ddata->dev->of_node;	struct device_node *child;	struct clk_lookup *cl;	struct clk *clock;	const char *n; 	if (name)		n = name;	else		n = optfck_name; 	/* Does the clock alias already exist? */	clock = of_clk_get_by_name(np, n);	if (!IS_ERR(clock)) {		clk_put(clock); 		return 0;	} 	child = of_get_next_available_child(np, NULL);	if (!child)		return -ENODEV; 	clock = devm_get_clk_from_child(ddata->dev, child, name);	if (IS_ERR(clock))		return PTR_ERR(clock); 	/*	 * Use clkdev_add() instead of clkdev_alloc() to avoid the MAX_DEV_ID	 * limit for clk_get(). If cl ever needs to be freed, it should be done	 * with clkdev_drop().	 */	cl = kzalloc_obj(*cl);	if (!cl)		return -ENOMEM; 	cl->con_id = n;	cl->dev_id = dev_name(ddata->dev);	cl->clk = clock;	clkdev_add(cl); 	clk_put(clock); 	return 0;} static int sysc_init_ext_opt_clock(struct sysc *ddata, const char *name){	const char *optfck_name;	int error, index; 	if (ddata->nr_clocks < SYSC_OPTFCK0)		index = SYSC_OPTFCK0;	else		index = ddata->nr_clocks; 	if (name)		optfck_name = name;	else		optfck_name = clock_names[index]; 	error = sysc_add_named_clock_from_child(ddata, name, optfck_name);	if (error)		return error; 	ddata->clock_roles[index] = optfck_name;	ddata->nr_clocks++; 	return 0;} static int sysc_get_one_clock(struct sysc *ddata, const char *name){	int error, i, index = -ENODEV; 	if (!strncmp(clock_names[SYSC_FCK], name, 3))		index = SYSC_FCK;	else if (!strncmp(clock_names[SYSC_ICK], name, 3))		index = SYSC_ICK; 	if (index < 0) {		for (i = SYSC_OPTFCK0; i < SYSC_MAX_CLOCKS; i++) {			if (!ddata->clocks[i]) {				index = i;				break;			}		}	} 	if (index < 0) {		dev_err(ddata->dev, "clock %s not added\n", name);		return index;	} 	ddata->clocks[index] = devm_clk_get(ddata->dev, name);	if (IS_ERR(ddata->clocks[index])) {		dev_err(ddata->dev, "clock get error for %s: %li\n",			name, PTR_ERR(ddata->clocks[index])); 		return PTR_ERR(ddata->clocks[index]);	} 	error = clk_prepare(ddata->clocks[index]);	if (error) {		dev_err(ddata->dev, "clock prepare error for %s: %i\n",			name, error); 		return error;	} 	return 0;} static int sysc_get_clocks(struct sysc *ddata){	struct device_node *np = ddata->dev->of_node;	struct property *prop;	const char *name;	int nr_fck = 0, nr_ick = 0, i, error = 0; 	ddata->clock_roles = devm_kcalloc(ddata->dev,					  SYSC_MAX_CLOCKS,					  sizeof(*ddata->clock_roles),					  GFP_KERNEL);	if (!ddata->clock_roles)		return -ENOMEM; 	of_property_for_each_string(np, "clock-names", prop, name) {		if (!strncmp(clock_names[SYSC_FCK], name, 3))			nr_fck++;		if (!strncmp(clock_names[SYSC_ICK], name, 3))			nr_ick++;		ddata->clock_roles[ddata->nr_clocks] = name;		ddata->nr_clocks++;	} 	if (ddata->nr_clocks < 1)		return 0; 	if ((ddata->cfg.quirks & SYSC_QUIRK_EXT_OPT_CLOCK)) {		error = sysc_init_ext_opt_clock(ddata, NULL);		if (error)			return error;	} 	if (ddata->nr_clocks > SYSC_MAX_CLOCKS) {		dev_err(ddata->dev, "too many clocks for %pOF\n", np); 		return -EINVAL;	} 	if (nr_fck > 1 || nr_ick > 1) {		dev_err(ddata->dev, "max one fck and ick for %pOF\n", np); 		return -EINVAL;	} 	/* Always add a slot for main clocks fck and ick even if unused */	if (!nr_fck)		ddata->nr_clocks++;	if (!nr_ick)		ddata->nr_clocks++; 	ddata->clocks = devm_kcalloc(ddata->dev,				     ddata->nr_clocks, sizeof(*ddata->clocks),				     GFP_KERNEL);	if (!ddata->clocks)		return -ENOMEM; 	for (i = 0; i < SYSC_MAX_CLOCKS; i++) {		const char *name = ddata->clock_roles[i]; 		if (!name)			continue; 		error = sysc_get_one_clock(ddata, name);

Lines 1–500 of 3351.