diff options
Diffstat (limited to 'drivers/bus')
| -rw-r--r-- | drivers/bus/ti-sysc.c | 108 | 
1 files changed, 102 insertions, 6 deletions
| diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c index 9e958343a719..a73c707d14c3 100644 --- a/drivers/bus/ti-sysc.c +++ b/drivers/bus/ti-sysc.c @@ -6,6 +6,7 @@  #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> @@ -52,11 +53,18 @@ struct sysc_address {  	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 modules list lock */ +	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 { @@ -2477,6 +2485,79 @@ static struct dev_pm_domain sysc_child_pm_domain = {  	}  }; +/* Caller needs to take list_lock if ever used outside of cpu_pm */ +static void sysc_reinit_modules(struct sysc_soc_info *soc) +{ +	struct sysc_module *module; +	struct list_head *pos; +	struct sysc *ddata; +	int error = 0; + +	list_for_each(pos, &sysc_soc->restored_modules) { +		module = list_entry(pos, struct sysc_module, node); +		ddata = module->ddata; +		error = sysc_reinit_module(ddata, ddata->enabled); +	} +} + +/** + * sysc_context_notifier - optionally reset and restore module after idle + * @nb: notifier block + * @cmd: unused + * @v: unused + * + * Some interconnect target modules need to be restored, or reset and restored + * on CPU_PM CPU_PM_CLUSTER_EXIT notifier. This is needed at least for am335x + * OTG and GPMC target modules even if the modules are unused. + */ +static int sysc_context_notifier(struct notifier_block *nb, unsigned long cmd, +				 void *v) +{ +	struct sysc_soc_info *soc; + +	soc = container_of(nb, struct sysc_soc_info, nb); + +	switch (cmd) { +	case CPU_CLUSTER_PM_ENTER: +		break; +	case CPU_CLUSTER_PM_ENTER_FAILED:	/* No need to restore context */ +		break; +	case CPU_CLUSTER_PM_EXIT: +		sysc_reinit_modules(soc); +		break; +	} + +	return NOTIFY_OK; +} + +/** + * sysc_add_restored - optionally add reset and restore quirk hanlling + * @ddata: device data + */ +static void sysc_add_restored(struct sysc *ddata) +{ +	struct sysc_module *restored_module; + +	restored_module = kzalloc(sizeof(*restored_module), GFP_KERNEL); +	if (!restored_module) +		return; + +	restored_module->ddata = ddata; + +	mutex_lock(&sysc_soc->list_lock); + +	list_add(&restored_module->node, &sysc_soc->restored_modules); + +	if (sysc_soc->nb.notifier_call) +		goto out_unlock; + +	sysc_soc->nb.notifier_call = sysc_context_notifier; +	cpu_pm_register_notifier(&sysc_soc->nb); + +out_unlock: +	mutex_unlock(&sysc_soc->list_lock); +} +  /**   * sysc_legacy_idle_quirk - handle children in omap_device compatible way   * @ddata: device driver data @@ -2976,12 +3057,14 @@ static int sysc_add_disabled(unsigned long base)  }  /* - * One time init to detect the booted SoC and disable unavailable features. + * One time init to detect the booted SoC, disable unavailable features + * and initialize list for optional cpu_pm notifier. + *   * Note that we initialize static data shared across all ti-sysc instances   * so ddata is only used for SoC type. This can be called from module_init   * once we no longer need to rely on platform data.   */ -static int sysc_init_soc(struct sysc *ddata) +static int sysc_init_static_data(struct sysc *ddata)  {  	const struct soc_device_attribute *match;  	struct ti_sysc_platform_data *pdata; @@ -2997,6 +3080,7 @@ static int sysc_init_soc(struct sysc *ddata)  	mutex_init(&sysc_soc->list_lock);  	INIT_LIST_HEAD(&sysc_soc->disabled_modules); +	INIT_LIST_HEAD(&sysc_soc->restored_modules);  	sysc_soc->general_purpose = true;  	pdata = dev_get_platdata(ddata->dev); @@ -3060,15 +3144,24 @@ static int sysc_init_soc(struct sysc *ddata)  	return 0;  } -static void sysc_cleanup_soc(void) +static void sysc_cleanup_static_data(void)  { +	struct sysc_module *restored_module;  	struct sysc_address *disabled_module;  	struct list_head *pos, *tmp;  	if (!sysc_soc)  		return; +	if (sysc_soc->nb.notifier_call) +		cpu_pm_unregister_notifier(&sysc_soc->nb); +  	mutex_lock(&sysc_soc->list_lock); +	list_for_each_safe(pos, tmp, &sysc_soc->restored_modules) { +		restored_module = list_entry(pos, struct sysc_module, node); +		list_del(pos); +		kfree(restored_module); +	}  	list_for_each_safe(pos, tmp, &sysc_soc->disabled_modules) {  		disabled_module = list_entry(pos, struct sysc_address, node);  		list_del(pos); @@ -3136,7 +3229,7 @@ static int sysc_probe(struct platform_device *pdev)  	ddata->dev = &pdev->dev;  	platform_set_drvdata(pdev, ddata); -	error = sysc_init_soc(ddata); +	error = sysc_init_static_data(ddata);  	if (error)  		return error; @@ -3234,6 +3327,9 @@ static int sysc_probe(struct platform_device *pdev)  		pm_runtime_put(&pdev->dev);  	} +	if (ddata->cfg.quirks & SYSC_QUIRK_REINIT_ON_CTX_LOST) +		sysc_add_restored(ddata); +  	return 0;  err: @@ -3315,7 +3411,7 @@ static void __exit sysc_exit(void)  {  	bus_unregister_notifier(&platform_bus_type, &sysc_nb);  	platform_driver_unregister(&sysc_driver); -	sysc_cleanup_soc(); +	sysc_cleanup_static_data();  }  module_exit(sysc_exit); |