HSI: omap-ssi: add clk change support
This adds support for frequency changes of the SSI functional clock, which may occur due to DVFS. Acked-by: Pavel Machek <pavel@ucw.cz> Signed-off-By: Sebastian Reichel <sre@kernel.org>
This commit is contained in:
		
							parent
							
								
									0fae198988
								
							
						
					
					
						commit
						4bcf741452
					
				| @ -134,6 +134,8 @@ struct gdd_trn { | |||||||
|  * @gdd_tasklet: bottom half for DMA transfers |  * @gdd_tasklet: bottom half for DMA transfers | ||||||
|  * @gdd_trn: Array of GDD transaction data for ongoing GDD transfers |  * @gdd_trn: Array of GDD transaction data for ongoing GDD transfers | ||||||
|  * @lock: lock to serialize access to GDD |  * @lock: lock to serialize access to GDD | ||||||
|  |  * @fck_nb: DVFS notfifier block | ||||||
|  |  * @fck_rate: clock rate | ||||||
|  * @loss_count: To follow if we need to restore context or not |  * @loss_count: To follow if we need to restore context or not | ||||||
|  * @max_speed: Maximum TX speed (Kb/s) set by the clients. |  * @max_speed: Maximum TX speed (Kb/s) set by the clients. | ||||||
|  * @sysconfig: SSI controller saved context |  * @sysconfig: SSI controller saved context | ||||||
| @ -151,6 +153,7 @@ struct omap_ssi_controller { | |||||||
| 	struct tasklet_struct	gdd_tasklet; | 	struct tasklet_struct	gdd_tasklet; | ||||||
| 	struct gdd_trn		gdd_trn[SSI_MAX_GDD_LCH]; | 	struct gdd_trn		gdd_trn[SSI_MAX_GDD_LCH]; | ||||||
| 	spinlock_t		lock; | 	spinlock_t		lock; | ||||||
|  | 	struct notifier_block	fck_nb; | ||||||
| 	unsigned long		fck_rate; | 	unsigned long		fck_rate; | ||||||
| 	u32			loss_count; | 	u32			loss_count; | ||||||
| 	u32			max_speed; | 	u32			max_speed; | ||||||
| @ -164,6 +167,9 @@ struct omap_ssi_controller { | |||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | void omap_ssi_port_update_fclk(struct hsi_controller *ssi, | ||||||
|  | 			       struct omap_ssi_port *omap_port); | ||||||
|  | 
 | ||||||
| extern struct platform_driver ssi_port_pdriver; | extern struct platform_driver ssi_port_pdriver; | ||||||
| 
 | 
 | ||||||
| #endif /* __LINUX_HSI_OMAP_SSI_H__ */ | #endif /* __LINUX_HSI_OMAP_SSI_H__ */ | ||||||
|  | |||||||
| @ -290,6 +290,64 @@ static unsigned long ssi_get_clk_rate(struct hsi_controller *ssi) | |||||||
| 	return rate; | 	return rate; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int ssi_clk_event(struct notifier_block *nb, unsigned long event, | ||||||
|  | 								void *data) | ||||||
|  | { | ||||||
|  | 	struct omap_ssi_controller *omap_ssi = container_of(nb, | ||||||
|  | 					struct omap_ssi_controller, fck_nb); | ||||||
|  | 	struct hsi_controller *ssi = to_hsi_controller(omap_ssi->dev); | ||||||
|  | 	struct clk_notifier_data *clk_data = data; | ||||||
|  | 	struct omap_ssi_port *omap_port; | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	switch (event) { | ||||||
|  | 	case PRE_RATE_CHANGE: | ||||||
|  | 		dev_dbg(&ssi->device, "pre rate change\n"); | ||||||
|  | 
 | ||||||
|  | 		for (i = 0; i < ssi->num_ports; i++) { | ||||||
|  | 			omap_port = omap_ssi->port[i]; | ||||||
|  | 
 | ||||||
|  | 			if (!omap_port) | ||||||
|  | 				continue; | ||||||
|  | 
 | ||||||
|  | 			/* Workaround for SWBREAK + CAwake down race in CMT */ | ||||||
|  | 			tasklet_disable(&omap_port->wake_tasklet); | ||||||
|  | 
 | ||||||
|  | 			/* stop all ssi communication */ | ||||||
|  | 			pinctrl_pm_select_idle_state(omap_port->pdev); | ||||||
|  | 			udelay(1); /* wait for racing frames */ | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		break; | ||||||
|  | 	case ABORT_RATE_CHANGE: | ||||||
|  | 		dev_dbg(&ssi->device, "abort rate change\n"); | ||||||
|  | 		/* Fall through */ | ||||||
|  | 	case POST_RATE_CHANGE: | ||||||
|  | 		dev_dbg(&ssi->device, "post rate change (%lu -> %lu)\n", | ||||||
|  | 			clk_data->old_rate, clk_data->new_rate); | ||||||
|  | 		omap_ssi->fck_rate = DIV_ROUND_CLOSEST(clk_data->new_rate, 1000); /* KHz */ | ||||||
|  | 
 | ||||||
|  | 		for (i = 0; i < ssi->num_ports; i++) { | ||||||
|  | 			omap_port = omap_ssi->port[i]; | ||||||
|  | 
 | ||||||
|  | 			if (!omap_port) | ||||||
|  | 				continue; | ||||||
|  | 
 | ||||||
|  | 			omap_ssi_port_update_fclk(ssi, omap_port); | ||||||
|  | 
 | ||||||
|  | 			/* resume ssi communication */ | ||||||
|  | 			pinctrl_pm_select_default_state(omap_port->pdev); | ||||||
|  | 			tasklet_enable(&omap_port->wake_tasklet); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return NOTIFY_DONE; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int ssi_get_iomem(struct platform_device *pd, | static int ssi_get_iomem(struct platform_device *pd, | ||||||
| 		const char *name, void __iomem **pbase, dma_addr_t *phy) | 		const char *name, void __iomem **pbase, dma_addr_t *phy) | ||||||
| { | { | ||||||
| @ -369,6 +427,10 @@ static int ssi_add_controller(struct hsi_controller *ssi, | |||||||
| 		goto out_err; | 		goto out_err; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	omap_ssi->fck_nb.notifier_call = ssi_clk_event; | ||||||
|  | 	omap_ssi->fck_nb.priority = INT_MAX; | ||||||
|  | 	clk_notifier_register(omap_ssi->fck, &omap_ssi->fck_nb); | ||||||
|  | 
 | ||||||
| 	/* TODO: find register, which can be used to detect context loss */ | 	/* TODO: find register, which can be used to detect context loss */ | ||||||
| 	omap_ssi->get_loss = NULL; | 	omap_ssi->get_loss = NULL; | ||||||
| 
 | 
 | ||||||
| @ -432,6 +494,7 @@ static void ssi_remove_controller(struct hsi_controller *ssi) | |||||||
| 	int id = ssi->id; | 	int id = ssi->id; | ||||||
| 	tasklet_kill(&omap_ssi->gdd_tasklet); | 	tasklet_kill(&omap_ssi->gdd_tasklet); | ||||||
| 	hsi_unregister_controller(ssi); | 	hsi_unregister_controller(ssi); | ||||||
|  | 	clk_notifier_unregister(omap_ssi->fck, &omap_ssi->fck_nb); | ||||||
| 	ida_simple_remove(&platform_omap_ssi_ida, id); | 	ida_simple_remove(&platform_omap_ssi_ida, id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ | |||||||
| #include <linux/platform_device.h> | #include <linux/platform_device.h> | ||||||
| #include <linux/dma-mapping.h> | #include <linux/dma-mapping.h> | ||||||
| #include <linux/pm_runtime.h> | #include <linux/pm_runtime.h> | ||||||
|  | #include <linux/delay.h> | ||||||
| 
 | 
 | ||||||
| #include <linux/gpio/consumer.h> | #include <linux/gpio/consumer.h> | ||||||
| #include <linux/debugfs.h> | #include <linux/debugfs.h> | ||||||
| @ -514,6 +515,11 @@ static int ssi_flush(struct hsi_client *cl) | |||||||
| 
 | 
 | ||||||
| 	pm_runtime_get_sync(omap_port->pdev); | 	pm_runtime_get_sync(omap_port->pdev); | ||||||
| 	spin_lock_bh(&omap_port->lock); | 	spin_lock_bh(&omap_port->lock); | ||||||
|  | 
 | ||||||
|  | 	/* stop all ssi communication */ | ||||||
|  | 	pinctrl_pm_select_idle_state(omap_port->pdev); | ||||||
|  | 	udelay(1); /* wait for racing frames */ | ||||||
|  | 
 | ||||||
| 	/* Stop all DMA transfers */ | 	/* Stop all DMA transfers */ | ||||||
| 	for (i = 0; i < SSI_MAX_GDD_LCH; i++) { | 	for (i = 0; i < SSI_MAX_GDD_LCH; i++) { | ||||||
| 		msg = omap_ssi->gdd_trn[i].msg; | 		msg = omap_ssi->gdd_trn[i].msg; | ||||||
| @ -550,6 +556,10 @@ static int ssi_flush(struct hsi_client *cl) | |||||||
| 		ssi_flush_queue(&omap_port->rxqueue[i], NULL); | 		ssi_flush_queue(&omap_port->rxqueue[i], NULL); | ||||||
| 	} | 	} | ||||||
| 	ssi_flush_queue(&omap_port->brkqueue, NULL); | 	ssi_flush_queue(&omap_port->brkqueue, NULL); | ||||||
|  | 
 | ||||||
|  | 	/* Resume SSI communication */ | ||||||
|  | 	pinctrl_pm_select_default_state(omap_port->pdev); | ||||||
|  | 
 | ||||||
| 	spin_unlock_bh(&omap_port->lock); | 	spin_unlock_bh(&omap_port->lock); | ||||||
| 	pm_runtime_put_sync(omap_port->pdev); | 	pm_runtime_put_sync(omap_port->pdev); | ||||||
| 
 | 
 | ||||||
| @ -1302,6 +1312,16 @@ static int ssi_restore_divisor(struct omap_ssi_port *omap_port) | |||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void omap_ssi_port_update_fclk(struct hsi_controller *ssi, | ||||||
|  | 			       struct omap_ssi_port *omap_port) | ||||||
|  | { | ||||||
|  | 	/* update divisor */ | ||||||
|  | 	u32 div = ssi_calculate_div(ssi); | ||||||
|  | 	omap_port->sst.divisor = div; | ||||||
|  | 	ssi_restore_divisor(omap_port); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL_GPL(omap_ssi_port_update_fclk); | ||||||
|  | 
 | ||||||
| static int omap_ssi_port_runtime_suspend(struct device *dev) | static int omap_ssi_port_runtime_suspend(struct device *dev) | ||||||
| { | { | ||||||
| 	struct hsi_port *port = dev_get_drvdata(dev); | 	struct hsi_port *port = dev_get_drvdata(dev); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user