aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hsi
diff options
context:
space:
mode:
authorSebastian Reichel <sre@kernel.org>2016-01-30 19:52:38 -0500
committerSebastian Reichel <sre@kernel.org>2016-05-02 15:56:25 -0400
commit4bcf7414528a6b7ca52d28953a732a4cf36063e8 (patch)
treea70c2897861ee30a420153a94651a723a70aec1f /drivers/hsi
parent0fae198988b873d30fe9ecb6a6271afb36df97e9 (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.h6
-rw-r--r--drivers/hsi/controllers/omap_ssi_core.c63
-rw-r--r--drivers/hsi/controllers/omap_ssi_port.c20
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
170void omap_ssi_port_update_fclk(struct hsi_controller *ssi,
171 struct omap_ssi_port *omap_port);
172
167extern struct platform_driver ssi_port_pdriver; 173extern 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
293static 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
293static int ssi_get_iomem(struct platform_device *pd, 351static 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
1315void 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}
1323EXPORT_SYMBOL_GPL(omap_ssi_port_update_fclk);
1324
1305static int omap_ssi_port_runtime_suspend(struct device *dev) 1325static 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);