diff options
author | Sebastian Reichel <sre@kernel.org> | 2016-01-30 19:52:38 -0500 |
---|---|---|
committer | Sebastian Reichel <sre@kernel.org> | 2016-05-02 15:56:25 -0400 |
commit | 4bcf7414528a6b7ca52d28953a732a4cf36063e8 (patch) | |
tree | a70c2897861ee30a420153a94651a723a70aec1f /drivers/hsi | |
parent | 0fae198988b873d30fe9ecb6a6271afb36df97e9 (diff) |
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>
Diffstat (limited to 'drivers/hsi')
-rw-r--r-- | drivers/hsi/controllers/omap_ssi.h | 6 | ||||
-rw-r--r-- | drivers/hsi/controllers/omap_ssi_core.c | 63 | ||||
-rw-r--r-- | drivers/hsi/controllers/omap_ssi_port.c | 20 |
3 files changed, 89 insertions, 0 deletions
diff --git a/drivers/hsi/controllers/omap_ssi.h b/drivers/hsi/controllers/omap_ssi.h index e493321cb0c3..7b4dec2c69ff 100644 --- a/drivers/hsi/controllers/omap_ssi.h +++ b/drivers/hsi/controllers/omap_ssi.h | |||
@@ -134,6 +134,8 @@ struct gdd_trn { | |||
134 | * @gdd_tasklet: bottom half for DMA transfers | 134 | * @gdd_tasklet: bottom half for DMA transfers |
135 | * @gdd_trn: Array of GDD transaction data for ongoing GDD transfers | 135 | * @gdd_trn: Array of GDD transaction data for ongoing GDD transfers |
136 | * @lock: lock to serialize access to GDD | 136 | * @lock: lock to serialize access to GDD |
137 | * @fck_nb: DVFS notfifier block | ||
138 | * @fck_rate: clock rate | ||
137 | * @loss_count: To follow if we need to restore context or not | 139 | * @loss_count: To follow if we need to restore context or not |
138 | * @max_speed: Maximum TX speed (Kb/s) set by the clients. | 140 | * @max_speed: Maximum TX speed (Kb/s) set by the clients. |
139 | * @sysconfig: SSI controller saved context | 141 | * @sysconfig: SSI controller saved context |
@@ -151,6 +153,7 @@ struct omap_ssi_controller { | |||
151 | struct tasklet_struct gdd_tasklet; | 153 | struct tasklet_struct gdd_tasklet; |
152 | struct gdd_trn gdd_trn[SSI_MAX_GDD_LCH]; | 154 | struct gdd_trn gdd_trn[SSI_MAX_GDD_LCH]; |
153 | spinlock_t lock; | 155 | spinlock_t lock; |
156 | struct notifier_block fck_nb; | ||
154 | unsigned long fck_rate; | 157 | unsigned long fck_rate; |
155 | u32 loss_count; | 158 | u32 loss_count; |
156 | u32 max_speed; | 159 | u32 max_speed; |
@@ -164,6 +167,9 @@ struct omap_ssi_controller { | |||
164 | #endif | 167 | #endif |
165 | }; | 168 | }; |
166 | 169 | ||
170 | void omap_ssi_port_update_fclk(struct hsi_controller *ssi, | ||
171 | struct omap_ssi_port *omap_port); | ||
172 | |||
167 | extern struct platform_driver ssi_port_pdriver; | 173 | extern struct platform_driver ssi_port_pdriver; |
168 | 174 | ||
169 | #endif /* __LINUX_HSI_OMAP_SSI_H__ */ | 175 | #endif /* __LINUX_HSI_OMAP_SSI_H__ */ |
diff --git a/drivers/hsi/controllers/omap_ssi_core.c b/drivers/hsi/controllers/omap_ssi_core.c index 535c76038288..15b2a600d77b 100644 --- a/drivers/hsi/controllers/omap_ssi_core.c +++ b/drivers/hsi/controllers/omap_ssi_core.c | |||
@@ -290,6 +290,64 @@ static unsigned long ssi_get_clk_rate(struct hsi_controller *ssi) | |||
290 | return rate; | 290 | return rate; |
291 | } | 291 | } |
292 | 292 | ||
293 | static int ssi_clk_event(struct notifier_block *nb, unsigned long event, | ||
294 | void *data) | ||
295 | { | ||
296 | struct omap_ssi_controller *omap_ssi = container_of(nb, | ||
297 | struct omap_ssi_controller, fck_nb); | ||
298 | struct hsi_controller *ssi = to_hsi_controller(omap_ssi->dev); | ||
299 | struct clk_notifier_data *clk_data = data; | ||
300 | struct omap_ssi_port *omap_port; | ||
301 | int i; | ||
302 | |||
303 | switch (event) { | ||
304 | case PRE_RATE_CHANGE: | ||
305 | dev_dbg(&ssi->device, "pre rate change\n"); | ||
306 | |||
307 | for (i = 0; i < ssi->num_ports; i++) { | ||
308 | omap_port = omap_ssi->port[i]; | ||
309 | |||
310 | if (!omap_port) | ||
311 | continue; | ||
312 | |||
313 | /* Workaround for SWBREAK + CAwake down race in CMT */ | ||
314 | tasklet_disable(&omap_port->wake_tasklet); | ||
315 | |||
316 | /* stop all ssi communication */ | ||
317 | pinctrl_pm_select_idle_state(omap_port->pdev); | ||
318 | udelay(1); /* wait for racing frames */ | ||
319 | } | ||
320 | |||
321 | break; | ||
322 | case ABORT_RATE_CHANGE: | ||
323 | dev_dbg(&ssi->device, "abort rate change\n"); | ||
324 | /* Fall through */ | ||
325 | case POST_RATE_CHANGE: | ||
326 | dev_dbg(&ssi->device, "post rate change (%lu -> %lu)\n", | ||
327 | clk_data->old_rate, clk_data->new_rate); | ||
328 | omap_ssi->fck_rate = DIV_ROUND_CLOSEST(clk_data->new_rate, 1000); /* KHz */ | ||
329 | |||
330 | for (i = 0; i < ssi->num_ports; i++) { | ||
331 | omap_port = omap_ssi->port[i]; | ||
332 | |||
333 | if (!omap_port) | ||
334 | continue; | ||
335 | |||
336 | omap_ssi_port_update_fclk(ssi, omap_port); | ||
337 | |||
338 | /* resume ssi communication */ | ||
339 | pinctrl_pm_select_default_state(omap_port->pdev); | ||
340 | tasklet_enable(&omap_port->wake_tasklet); | ||
341 | } | ||
342 | |||
343 | break; | ||
344 | default: | ||
345 | break; | ||
346 | } | ||
347 | |||
348 | return NOTIFY_DONE; | ||
349 | } | ||
350 | |||
293 | static int ssi_get_iomem(struct platform_device *pd, | 351 | static int ssi_get_iomem(struct platform_device *pd, |
294 | const char *name, void __iomem **pbase, dma_addr_t *phy) | 352 | const char *name, void __iomem **pbase, dma_addr_t *phy) |
295 | { | 353 | { |
@@ -369,6 +427,10 @@ static int ssi_add_controller(struct hsi_controller *ssi, | |||
369 | goto out_err; | 427 | goto out_err; |
370 | } | 428 | } |
371 | 429 | ||
430 | omap_ssi->fck_nb.notifier_call = ssi_clk_event; | ||
431 | omap_ssi->fck_nb.priority = INT_MAX; | ||
432 | clk_notifier_register(omap_ssi->fck, &omap_ssi->fck_nb); | ||
433 | |||
372 | /* TODO: find register, which can be used to detect context loss */ | 434 | /* TODO: find register, which can be used to detect context loss */ |
373 | omap_ssi->get_loss = NULL; | 435 | omap_ssi->get_loss = NULL; |
374 | 436 | ||
@@ -432,6 +494,7 @@ static void ssi_remove_controller(struct hsi_controller *ssi) | |||
432 | int id = ssi->id; | 494 | int id = ssi->id; |
433 | tasklet_kill(&omap_ssi->gdd_tasklet); | 495 | tasklet_kill(&omap_ssi->gdd_tasklet); |
434 | hsi_unregister_controller(ssi); | 496 | hsi_unregister_controller(ssi); |
497 | clk_notifier_unregister(omap_ssi->fck, &omap_ssi->fck_nb); | ||
435 | ida_simple_remove(&platform_omap_ssi_ida, id); | 498 | ida_simple_remove(&platform_omap_ssi_ida, id); |
436 | } | 499 | } |
437 | 500 | ||
diff --git a/drivers/hsi/controllers/omap_ssi_port.c b/drivers/hsi/controllers/omap_ssi_port.c index 1569bbb53ee8..98b22e88085c 100644 --- a/drivers/hsi/controllers/omap_ssi_port.c +++ b/drivers/hsi/controllers/omap_ssi_port.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <linux/platform_device.h> | 23 | #include <linux/platform_device.h> |
24 | #include <linux/dma-mapping.h> | 24 | #include <linux/dma-mapping.h> |
25 | #include <linux/pm_runtime.h> | 25 | #include <linux/pm_runtime.h> |
26 | #include <linux/delay.h> | ||
26 | 27 | ||
27 | #include <linux/gpio/consumer.h> | 28 | #include <linux/gpio/consumer.h> |
28 | #include <linux/debugfs.h> | 29 | #include <linux/debugfs.h> |
@@ -514,6 +515,11 @@ static int ssi_flush(struct hsi_client *cl) | |||
514 | 515 | ||
515 | pm_runtime_get_sync(omap_port->pdev); | 516 | pm_runtime_get_sync(omap_port->pdev); |
516 | spin_lock_bh(&omap_port->lock); | 517 | spin_lock_bh(&omap_port->lock); |
518 | |||
519 | /* stop all ssi communication */ | ||
520 | pinctrl_pm_select_idle_state(omap_port->pdev); | ||
521 | udelay(1); /* wait for racing frames */ | ||
522 | |||
517 | /* Stop all DMA transfers */ | 523 | /* Stop all DMA transfers */ |
518 | for (i = 0; i < SSI_MAX_GDD_LCH; i++) { | 524 | for (i = 0; i < SSI_MAX_GDD_LCH; i++) { |
519 | msg = omap_ssi->gdd_trn[i].msg; | 525 | msg = omap_ssi->gdd_trn[i].msg; |
@@ -550,6 +556,10 @@ static int ssi_flush(struct hsi_client *cl) | |||
550 | ssi_flush_queue(&omap_port->rxqueue[i], NULL); | 556 | ssi_flush_queue(&omap_port->rxqueue[i], NULL); |
551 | } | 557 | } |
552 | ssi_flush_queue(&omap_port->brkqueue, NULL); | 558 | ssi_flush_queue(&omap_port->brkqueue, NULL); |
559 | |||
560 | /* Resume SSI communication */ | ||
561 | pinctrl_pm_select_default_state(omap_port->pdev); | ||
562 | |||
553 | spin_unlock_bh(&omap_port->lock); | 563 | spin_unlock_bh(&omap_port->lock); |
554 | pm_runtime_put_sync(omap_port->pdev); | 564 | pm_runtime_put_sync(omap_port->pdev); |
555 | 565 | ||
@@ -1302,6 +1312,16 @@ static int ssi_restore_divisor(struct omap_ssi_port *omap_port) | |||
1302 | return 0; | 1312 | return 0; |
1303 | } | 1313 | } |
1304 | 1314 | ||
1315 | void omap_ssi_port_update_fclk(struct hsi_controller *ssi, | ||
1316 | struct omap_ssi_port *omap_port) | ||
1317 | { | ||
1318 | /* update divisor */ | ||
1319 | u32 div = ssi_calculate_div(ssi); | ||
1320 | omap_port->sst.divisor = div; | ||
1321 | ssi_restore_divisor(omap_port); | ||
1322 | } | ||
1323 | EXPORT_SYMBOL_GPL(omap_ssi_port_update_fclk); | ||
1324 | |||
1305 | static int omap_ssi_port_runtime_suspend(struct device *dev) | 1325 | static int omap_ssi_port_runtime_suspend(struct device *dev) |
1306 | { | 1326 | { |
1307 | struct hsi_port *port = dev_get_drvdata(dev); | 1327 | struct hsi_port *port = dev_get_drvdata(dev); |