diff options
author | Djamil Elaidi <d-elaidi@ti.com> | 2011-12-07 11:06:10 +0100 |
---|---|---|
committer | Ziyann <jaraidaniel@gmail.com> | 2014-10-01 12:59:34 +0200 |
commit | f533310f9c1088329746aeb384230cd19daa4742 (patch) | |
tree | bda312302dde685dbae11230050b1fd06b458ede | |
parent | b8eed623c8609f05efe485ec41ccd04da66b9d20 (diff) | |
download | kernel_samsung_tuna-f533310f9c1088329746aeb384230cd19daa4742.zip kernel_samsung_tuna-f533310f9c1088329746aeb384230cd19daa4742.tar.gz kernel_samsung_tuna-f533310f9c1088329746aeb384230cd19daa4742.tar.bz2 |
OMAP4: HSI: Add dynamic OPP change
Added an IOCTL to change HSI FClk to 96MHz or 192MHz.
Decision of when to increase/decrease modem speed is up to Link Layer.
Constraint :
* RX/TX divisors and RX error counters need to be changed after Fclk is changed.
* Read/Write operation must not be pending when OPP change request is issued, otherwise
OPP change is aborted.
* Any Read/Write operation started during HSI OPP change operation will be aborted.
Change-Id: I06e15a205e614506994989ec6a40c0383e32776d
Signed-off-by: Djamil Elaidi <d-elaidi@ti.com>
-rw-r--r-- | arch/arm/plat-omap/include/plat/omap_hsi.h | 8 | ||||
-rw-r--r-- | drivers/omap_hsi/hsi-char.c | 12 | ||||
-rw-r--r-- | drivers/omap_hsi/hsi-if.c | 17 | ||||
-rw-r--r-- | drivers/omap_hsi/hsi-if.h | 2 | ||||
-rwxr-xr-x | drivers/omap_hsi/hsi_driver.c | 25 | ||||
-rwxr-xr-x | drivers/omap_hsi/hsi_driver.h | 10 | ||||
-rwxr-xr-x | drivers/omap_hsi/hsi_driver_if.c | 79 | ||||
-rw-r--r-- | include/linux/hsi_char.h | 2 | ||||
-rw-r--r-- | include/linux/hsi_driver_if.h | 5 |
9 files changed, 128 insertions, 32 deletions
diff --git a/arch/arm/plat-omap/include/plat/omap_hsi.h b/arch/arm/plat-omap/include/plat/omap_hsi.h index c64694e..d29062d 100644 --- a/arch/arm/plat-omap/include/plat/omap_hsi.h +++ b/arch/arm/plat-omap/include/plat/omap_hsi.h @@ -28,9 +28,13 @@ #ifndef __OMAP_HSI_H__ #define __OMAP_HSI_H__ +#define HSI_FCLK_LOW_SPEED 96000000 /* 96 MHz */ +#define HSI_FCLK_HI_SPEED 192000000 /* 192 MHz */ +#define HSI_FCLK_DPLL_CASCADING 98300000 /* 98.3 MHz */ + /* Set the HSI Functional Clock to 96MHz. - * This is to ensure HSI will function even at OPP50. */ -#define HSI_DEFAULT_FCLK 96000000 /* 96 MHz */ + * Warning : 192MHz will force OPP100 on VDD_CORE (no OPP50 possible) */ +#define HSI_DEFAULT_FCLK HSI_FCLK_LOW_SPEED /* 96 MHz */ #define HSI_PORT_OFFSET 0x1000 diff --git a/drivers/omap_hsi/hsi-char.c b/drivers/omap_hsi/hsi-char.c index 59193f4..8c0f406 100644 --- a/drivers/omap_hsi/hsi-char.c +++ b/drivers/omap_hsi/hsi-char.c @@ -361,6 +361,7 @@ static long hsi_char_ioctl(struct file *file, int ch = (int)file->private_data; unsigned int state; size_t size; + unsigned long fclock; struct hsi_rx_config rx_cfg; struct hsi_tx_config tx_cfg; int ret = 0; @@ -433,6 +434,17 @@ static long hsi_char_ioctl(struct file *file, if (copy_to_user((void __user *)arg, &size, sizeof(size))) ret = -EFAULT; break; + case CS_SET_HI_SPEED: + if (copy_from_user(&state, (void __user *)arg, sizeof(state))) + ret = -EFAULT; + else + if_hsi_set_hi_speed(ch, state); + break; + case CS_GET_SPEED: + if_hsi_get_speed(ch, &fclock); + if (copy_to_user((void __user *)arg, &fclock, sizeof(fclock))) + ret = -EFAULT; + break; default: ret = -ENOIOCTLCMD; break; diff --git a/drivers/omap_hsi/hsi-if.c b/drivers/omap_hsi/hsi-if.c index bb3e03d..8f6002f 100644 --- a/drivers/omap_hsi/hsi-if.c +++ b/drivers/omap_hsi/hsi-if.c @@ -335,6 +335,23 @@ void if_hsi_sw_reset(int ch) spin_unlock_bh(&hsi_iface.lock); } +void if_hsi_set_hi_speed(int ch, unsigned int state) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + + spin_lock_bh(&hsi_iface.lock); + hsi_ioctl(channel->dev, HSI_IOCTL_SET_HI_SPEED, &state); + spin_unlock_bh(&hsi_iface.lock); +} + +void if_hsi_get_speed(int ch, unsigned long *fclock) +{ + struct if_hsi_channel *channel; + channel = &hsi_iface.channels[ch]; + hsi_ioctl(channel->dev, HSI_IOCTL_GET_SPEED, fclock); +} + void if_hsi_get_fifo_occupancy(int ch, size_t *occ) { struct if_hsi_channel *channel; diff --git a/drivers/omap_hsi/hsi-if.h b/drivers/omap_hsi/hsi-if.h index f4bbf76..9cb120e 100644 --- a/drivers/omap_hsi/hsi-if.h +++ b/drivers/omap_hsi/hsi-if.h @@ -53,6 +53,8 @@ void if_hsi_set_acwakeline(int ch, unsigned int state); void if_hsi_get_acwakeline(int ch, unsigned int *state); void if_hsi_get_cawakeline(int ch, unsigned int *state); void if_hsi_set_wake_rx_3wires_mode(int ch, unsigned int state); +void if_hsi_set_hi_speed(int ch, unsigned int state); +void if_hsi_get_speed(int ch, unsigned long *fclock); int if_hsi_set_rx(int ch, struct hsi_rx_config *cfg); void if_hsi_get_rx(int ch, struct hsi_rx_config *cfg); int if_hsi_set_tx(int ch, struct hsi_tx_config *cfg); diff --git a/drivers/omap_hsi/hsi_driver.c b/drivers/omap_hsi/hsi_driver.c index 9efc2e3..a2ed3c2 100755 --- a/drivers/omap_hsi/hsi_driver.c +++ b/drivers/omap_hsi/hsi_driver.c @@ -174,7 +174,6 @@ int hsi_port_event_handler(struct hsi_port *p, unsigned int event, void *arg) struct hsi_channel *hsi_channel; int ch; - if (event == HSI_EVENT_HSR_DATAAVAILABLE) { /* The data-available event is channel-specific and must not be * broadcasted @@ -785,7 +784,8 @@ static int __init hsi_controller_init(struct hsi_dev *hsi_ctrl, } hsi_ctrl->max_p = pdata->num_ports; hsi_ctrl->clock_enabled = false; - hsi_ctrl->clock_rate = 0; + hsi_ctrl->clock_change_ongoing = false; + hsi_ctrl->hsi_fclk_current = 0; hsi_ctrl->in_dma_tasklet = false; hsi_ctrl->fifo_mapping_strategy = pdata->fifo_mapping_strategy; hsi_ctrl->dev = &pd->dev; @@ -835,6 +835,12 @@ static int __init hsi_platform_device_probe(struct platform_device *pd) return -ENXIO; } + /* Check if mandatory board functions are populated */ + if (!pdata->device_scale) { + dev_err(&pd->dev, "Missing platform device_scale function\n"); + return -ENOSYS; + } + hsi_ctrl = kzalloc(sizeof(*hsi_ctrl), GFP_KERNEL); if (hsi_ctrl == NULL) { dev_err(&pd->dev, "Could not allocate memory for" @@ -891,26 +897,21 @@ static int __init hsi_platform_device_probe(struct platform_device *pd) device_init_wakeup(hsi_ctrl->dev, true); /* Set the HSI FCLK to default. */ - if (!pdata->device_scale) { - dev_err(&pd->dev, "%s: No platform device_scale function\n", - __func__); - err = -ENXIO; - goto rollback3; - } + hsi_ctrl->hsi_fclk_req = pdata->default_hsi_fclk; err = pdata->device_scale(hsi_ctrl->dev, hsi_ctrl->dev, pdata->default_hsi_fclk); if (err == -EBUSY) { + /* PM framework init is late_initcall, so it may not yet be */ + /* initialized, so be prepared to retry later on open. */ dev_warn(&pd->dev, "Cannot set HSI FClk to default value: %ld. " - "Will retry on next open\n", - pdata->default_hsi_fclk); + "Will retry on next open\n", pdata->default_hsi_fclk); } else if (err) { dev_err(&pd->dev, "%s: Error %d setting HSI FClk to %ld.\n", __func__, err, pdata->default_hsi_fclk); goto rollback3; } else { - hsi_ctrl->clock_rate = pdata->default_hsi_fclk; + hsi_ctrl->hsi_fclk_current = pdata->default_hsi_fclk; } - /* From here no need for HSI HW access */ hsi_clocks_disable(hsi_ctrl->dev, __func__); diff --git a/drivers/omap_hsi/hsi_driver.h b/drivers/omap_hsi/hsi_driver.h index 7ad5655..226fe15 100755 --- a/drivers/omap_hsi/hsi_driver.h +++ b/drivers/omap_hsi/hsi_driver.h @@ -174,7 +174,9 @@ struct hsi_port { * @phy_base: HSI registers base physical address * @lock: Serializes access to internal data and regs * @clock_enabled: Indicates if HSI Clocks are ON - * @clock_rate: Indicates current HSI Fclock speed + * @clock_change_ongoing: Indicates if HSI FClk is being changed (OPP change) + * @hsi_fclk_req: Indicates what HSI FClk user requested (96MHz/192MHz) + * @hsi_fclk_current: Current HSI Fclock * @gdd_irq: GDD (DMA) irq number * @fifo_mapping_strategy: Selected strategy for fifo to ports/channels mapping * @gdd_usecount: Holds the number of ongoning DMA transfers @@ -182,8 +184,6 @@ struct hsi_port { * @gdd_chan_count: Number of available DMA channels on the device (must be ^2) * @in_dma_tasklet: True if DMA tasklet for the controller is currently running * @set_min_bus_tput: (PM) callback to set minimun bus throuput - * @clk_notifier_register: (PM) callabck for DVFS support - * @clk_notifier_unregister: (PM) callabck for DVFS support * @hsi_nb: (PM) Notification block for DVFS notification chain * @hsi_gdd_tasklet: Bottom half for DMA Interrupts when clocks are enabled * @dir: debugfs base directory @@ -197,7 +197,9 @@ struct hsi_dev { /* HSI_TODO: should be later renamed into hsi_controller*/ unsigned long phy_base; spinlock_t lock; /* Serializes access to internal data and regs */ bool clock_enabled; - unsigned long clock_rate; + bool clock_change_ongoing; + unsigned long hsi_fclk_req; + unsigned long hsi_fclk_current; int gdd_irq; unsigned int fifo_mapping_strategy; unsigned int gdd_usecount; diff --git a/drivers/omap_hsi/hsi_driver_if.c b/drivers/omap_hsi/hsi_driver_if.c index 82b7239..f7ab392 100755 --- a/drivers/omap_hsi/hsi_driver_if.c +++ b/drivers/omap_hsi/hsi_driver_if.c @@ -289,22 +289,10 @@ int hsi_open(struct hsi_device *dev) return -EINVAL; } - if (hsi_ctrl->clock_rate == 0) { + if (hsi_ctrl->hsi_fclk_current == 0) { struct hsi_platform_data *pdata; pdata = dev_get_platdata(hsi_ctrl->dev); - if (!pdata) { - dev_err(dev->device.parent, - "%s: Port %d Channel %d has no pdata\n", - __func__, dev->n_p, dev->n_ch); - return -EINVAL; - } - if (!pdata->device_scale) { - dev_err(dev->device.parent, - "%s: Undefined platform device_scale function\n", - __func__); - return -ENXIO; - } /* Retry to set the HSI FCLK to default. */ err = pdata->device_scale(hsi_ctrl->dev, hsi_ctrl->dev, @@ -318,7 +306,7 @@ int hsi_open(struct hsi_device *dev) } else { dev_info(dev->device.parent, "HSI clock is now %ld\n", pdata->default_hsi_fclk); - hsi_ctrl->clock_rate = pdata->default_hsi_fclk; + hsi_ctrl->hsi_fclk_current = pdata->default_hsi_fclk; } } spin_lock_bh(&hsi_ctrl->lock); @@ -397,6 +385,11 @@ int hsi_write(struct hsi_device *dev, u32 *addr, unsigned int size) spin_lock_bh(&hsi_ctrl->lock); + if (hsi_ctrl->clock_change_ongoing) { + dev_warn(hsi_ctrl->dev, "HSI Fclock change ongoing, retry.\n"); + spin_unlock_bh(&hsi_ctrl->lock); + return -EAGAIN; + } if (pm_runtime_suspended(hsi_ctrl->dev) || !hsi_ctrl->clock_enabled) dev_dbg(hsi_ctrl->dev, @@ -466,6 +459,13 @@ int hsi_read(struct hsi_device *dev, u32 *addr, unsigned int size) hsi_ctrl = ch->hsi_port->hsi_controller; spin_lock_bh(&hsi_ctrl->lock); + + if (hsi_ctrl->clock_change_ongoing) { + dev_warn(hsi_ctrl->dev, "HSI Fclock change ongoing, retry.\n"); + spin_unlock_bh(&hsi_ctrl->lock); + return -EAGAIN; + } + if (pm_runtime_suspended(hsi_ctrl->dev) || !hsi_ctrl->clock_enabled) dev_dbg(hsi_ctrl->dev, "hsi_read with HSI clocks OFF, clock_enabled = %d\n", @@ -745,6 +745,7 @@ int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg) int err = 0; int fifo = 0; u8 ret; + struct hsi_platform_data *pdata; if (unlikely((!dev) || (!dev->ch) || @@ -953,6 +954,56 @@ int hsi_ioctl(struct hsi_device *dev, unsigned int command, void *arg) hsi_outl_and(HSI_SET_WAKE_3_WIRES_MASK, base, HSI_SYS_SET_WAKE_REG(port)); break; + case HSI_IOCTL_SET_HI_SPEED: + if (!arg) { + err = -EINVAL; + goto out; + } + hsi_ctrl->hsi_fclk_req = *(unsigned int *)arg ? + HSI_FCLK_HI_SPEED : HSI_FCLK_LOW_SPEED; + + if (hsi_ctrl->hsi_fclk_req == hsi_ctrl->hsi_fclk_current) { + dev_dbg(hsi_ctrl->dev, "HSI FClk already @%ldHz\n", + hsi_ctrl->hsi_fclk_current); + goto out; + } + + if (hsi_is_controller_transfer_ongoing(hsi_ctrl)) { + err = -EBUSY; + goto out; + } + hsi_ctrl->clock_change_ongoing = true; + spin_unlock_bh(&hsi_ctrl->lock); + + pdata = dev_get_platdata(hsi_ctrl->dev); + + /* Set the HSI FCLK to requested value. */ + err = pdata->device_scale(hsi_ctrl->dev, hsi_ctrl->dev, + hsi_ctrl->hsi_fclk_req); + if (err < 0) { + dev_err(hsi_ctrl->dev, "%s: Cannot set HSI FClk to" + " %ldHz, err %d\n", __func__, + hsi_ctrl->hsi_fclk_req, err); + } else { + dev_info(hsi_ctrl->dev, "HSI FClk changed from %ldHz to" + " %ldHz\n", hsi_ctrl->hsi_fclk_current, + hsi_ctrl->hsi_fclk_req); + hsi_ctrl->hsi_fclk_current = hsi_ctrl->hsi_fclk_req; + } + + spin_lock_bh(&hsi_ctrl->lock); + hsi_ctrl->clock_change_ongoing = false; + + break; + case HSI_IOCTL_GET_SPEED: + if (!arg) { + err = -EINVAL; + goto out; + } + + *(unsigned long *)arg = hsi_ctrl->hsi_fclk_current; + break; + default: err = -ENOIOCTLCMD; break; diff --git a/include/linux/hsi_char.h b/include/linux/hsi_char.h index a85a3b0..1d87bcb 100644 --- a/include/linux/hsi_char.h +++ b/include/linux/hsi_char.h @@ -41,6 +41,8 @@ #define CS_GET_FIFO_OCCUPANCY CS_IOR(12, size_t) #define CS_GET_CAWAKELINE CS_IOR(13, unsigned int) #define CS_SET_WAKE_RX_3WIRES_MODE CS_IOR(14, unsigned int) +#define CS_SET_HI_SPEED CS_IOR(15, unsigned int) +#define CS_GET_SPEED CS_IOW(16, unsigned long) #define HSI_MODE_SLEEP 0 #define HSI_MODE_STREAM 1 diff --git a/include/linux/hsi_driver_if.h b/include/linux/hsi_driver_if.h index 6e7a0da..c7afe6b 100644 --- a/include/linux/hsi_driver_if.h +++ b/include/linux/hsi_driver_if.h @@ -38,6 +38,9 @@ #define HSI_HSR_DIVISOR_AUTO 0x1000 /* Activate auto Rx */ #define HSI_SSR_DIVISOR_USE_TIMEOUT 0x1001 /* De-activate auto-Rx (SSI) */ +#define HSI_SPEED_LOW_SPEED 0 /* Switch to 96MHz */ +#define HSI_SPEED_HI_SPEED 1 /* Switch to 192MHz */ + enum { HSI_EVENT_BREAK_DETECTED = 0, HSI_EVENT_ERROR, @@ -64,6 +67,8 @@ enum { HSI_IOCTL_GET_FIFO_OCCUPANCY, /* Get amount of words in RX FIFO */ HSI_IOCTL_SET_WAKE_RX_3WIRES_MODE, /* Enable RX wakeup 3-wires mode */ HSI_IOCTL_SET_WAKE_RX_4WIRES_MODE, /* Enable RX wakeup 4-wires mode */ + HSI_IOCTL_SET_HI_SPEED, /* Change HSI Fclock (96MHz/192MHz) */ + HSI_IOCTL_GET_SPEED, /* Get HSI Fclock (96MHz/192MHz) */ }; /* Forward references */ |