diff options
Diffstat (limited to 'drivers/mxc/pm/dvfs_dptc.c')
-rw-r--r-- | drivers/mxc/pm/dvfs_dptc.c | 1248 |
1 files changed, 1248 insertions, 0 deletions
diff --git a/drivers/mxc/pm/dvfs_dptc.c b/drivers/mxc/pm/dvfs_dptc.c new file mode 100644 index 000000000000..d78cec974dae --- /dev/null +++ b/drivers/mxc/pm/dvfs_dptc.c @@ -0,0 +1,1248 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file dvfs_dptc.c + * + * @brief Driver for the Freescale Semiconductor MXC DVFS & DPTC module. + * + * The DVFS & DPTC driver + * driver is designed as a character driver which interacts with the MXC + * DVFS & DPTC hardware. Upon initialization, the DVFS & DPTC driver initializes + * the DVFS & DPTC hardware sets up driver nodes attaches to the DVFS & DPTC + * interrupts and initializes internal data structures. When the DVFS or DPTC + * interrupt occurs the driver checks the cause of the interrupt + * (lower voltage/frequency, increase voltage/frequency or emergency) and changes + * the CPU voltage and/or frequency according to translation table that is loaded + * into the driver (the voltage changes are done by calling some routines + * of the mc13783 driver). + * + * @ingroup PM_MX31 PM_MXC91321 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/workqueue.h> +#include <linux/proc_fs.h> +#include <linux/jiffies.h> +#include <linux/vmalloc.h> +#include <asm/uaccess.h> +#include <asm/semaphore.h> +#include <asm/arch/hardware.h> +#include <asm/arch/pmic_external.h> + +/* + * Module header files + */ +#include "dvfs_dptc.h" +#include <asm/arch/dptc.h> + +#ifdef CONFIG_MXC_DVFS +#include <asm/arch/dvfs.h> +#endif + +/* + * Prototypes + */ +static int dvfs_dptc_open(struct inode *inode, struct file *filp); +static int dvfs_dptc_release(struct inode *inode, struct file *filp); +static int dvfs_dptc_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg); +#ifdef CONFIG_MXC_DVFS_SDMA +static ssize_t dvfs_dptc_read(struct file *filp, char __user * buf, + size_t count, loff_t * ppos); +#endif + +#ifndef CONFIG_MXC_DVFS_SDMA +static irqreturn_t dvfs_dptc_irq(int irq, void *dev_id); +#else +static void dvfs_dptc_sdma_callback(dvfs_dptc_params_s * params); +#endif + +#ifdef CONFIG_MXC_DPTC +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_dptc_driver = { + .driver = { + .name = "mxc_dptc", + }, + .suspend = mxc_dptc_suspend, + .resume = mxc_dptc_resume, +}; + +/*! + * This is platform device structure for adding MU + */ +static struct platform_device mxc_dptc_device = { + .name = "mxc_dptc", + .id = 0, +}; +#endif + +/* + * Global variables + */ + +/*! + * The dvfs_dptc_params structure holds all the internal DPTC driver parameters + * (current working point, current frequency, translation table and DPTC + * log buffer). + */ +static dvfs_dptc_params_s dvfs_dptc_params; + +/*! + * Holds the automatically selected DPTC driver major number. + */ +static int major; + +static struct class *mxc_dvfs_dptc_class; + +/* + * This mutex makes the Read,Write and IOCTL command mutual exclusive. + */ +DECLARE_MUTEX(access_mutex); + +/*! + * This structure contains pointers for device driver entry point. + * The driver register function in init module will call this + * structure. + */ +static struct file_operations fops = { + .open = dvfs_dptc_open, + .release = dvfs_dptc_release, + .ioctl = dvfs_dptc_ioctl, +#ifdef CONFIG_MXC_DVFS_SDMA + .read = dvfs_dptc_read, +#endif +}; + +#ifdef CONFIG_MXC_DVFS_SDMA +/* + * Update pointers to physical addresses of DVFS & DPTC table + * for SDMA usage + * + * @param dvfs_dptc_tables_ptr pointer to the DVFS & + * DPTC translation table. + */ +static void dvfs_dptc_virt_2_phys(dvfs_dptc_tables_s * dvfs_dptc_table) +{ + int i; + + /* Update DCVR pointers */ + for (i = 0; i < dvfs_dptc_table->dvfs_state_num; i++) { + dvfs_dptc_table->dcvr[i] = (dcvr_state *) + sdma_virt_to_phys(dvfs_dptc_table->dcvr[i]); + } + dvfs_dptc_table->dcvr = (dcvr_state **) + sdma_virt_to_phys(dvfs_dptc_table->dcvr); + dvfs_dptc_table->table = (dvfs_state *) + sdma_virt_to_phys(dvfs_dptc_table->table); +} + +/* + * Update pointers to virtual addresses of DVFS & DPTC table + * for ARM usage + * + * @param dvfs_dptc_tables_ptr pointer to the DVFS & + * DPTC translation table. + */ +static void dvfs_dptc_phys_2_virt(dvfs_dptc_tables_s * dvfs_dptc_table) +{ + int i; + + dvfs_dptc_table->table = sdma_phys_to_virt + ((unsigned long)dvfs_dptc_table->table); + dvfs_dptc_table->dcvr = sdma_phys_to_virt + ((unsigned long)dvfs_dptc_table->dcvr); + + /* Update DCVR pointers */ + for (i = 0; i < dvfs_dptc_table->dvfs_state_num; i++) { + dvfs_dptc_table->dcvr[i] = + sdma_phys_to_virt((unsigned long)dvfs_dptc_table->dcvr[i]); + } +} +#endif + +/*! + * This function frees power management table structures + */ +static void free_dvfs_dptc_table(void) +{ + int i; + +#ifdef CONFIG_MXC_DVFS_SDMA + dvfs_dptc_phys_2_virt(dvfs_dptc_params.dvfs_dptc_tables_ptr); +#endif + + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr[i]); + } + + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr); + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->table); + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr->wp); + + sdma_free(dvfs_dptc_params.dvfs_dptc_tables_ptr); + + dvfs_dptc_params.dvfs_dptc_tables_ptr = 0; +} + +/* + * DVFS & DPTC table parsing function + * reads the next line of the table in text format + * + * @param str pointer to the previous line + * + * @return pointer to the next line + */ +static char *pm_table_get_next_line(char *str) +{ + char *line_ptr; + int flag = 0; + + if (strlen(str) == 0) + return str; + + line_ptr = strchr(str, '\n') + 1; + + while (!flag) { + if (strlen(line_ptr) == 0) { + flag = 1; + } else if (line_ptr[0] == '\n') { + line_ptr++; + } else if (line_ptr[0] == '#') { + line_ptr = pm_table_get_next_line(line_ptr); + } else { + flag = 1; + } + } + + return line_ptr; +} + +/* + * DVFS & DPTC table parsing function + * sets the values of DVFS & DPTC tables from + * table in text format + * + * @param pm_table pointer to the table in binary format + * @param pm_str pointer to the table in text format + * + * @return 0 on success, error code on failure + */ +static int dvfs_dptc_parse_table(dvfs_dptc_tables_s * pm_table, char *pm_str) +{ + char *pm_str_ptr; + int i, j, n; + dptc_wp *wp; + dvfs_state *table; + + pm_str_ptr = pm_str; + + n = sscanf(pm_str_ptr, "WORKING POINT %d\n", &pm_table->wp_num); + + if (n != 1) { + printk(KERN_WARNING "Failed read WORKING POINT number\n"); + return -1; + } + + pm_table->curr_wp = 0; + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + if (cpu_is_mx31() || cpu_is_mx32()) { + pm_table->dvfs_state_num = 4; + pm_table->use_four_freq = 1; + } else { + pm_table->dvfs_state_num = 1; + } + + pm_table->wp = + (dptc_wp *) sdma_malloc(sizeof(dptc_wp) * pm_table->wp_num); + if (!pm_table->wp) { + printk(KERN_ERR "Failed allocating memory\n"); + return -ENOMEM; + } + + for (i = 0; i < pm_table->wp_num; i++) { + + wp = &pm_table->wp[i]; + + wp->wp_index = i; + + if (cpu_is_mx31() || cpu_is_mx32()) { + n = sscanf(pm_str_ptr, "WP 0x%x 0x%x 0x%x 0x%x\n", + (unsigned int *)&wp->pmic_values[0], + (unsigned int *)&wp->pmic_values[1], + (unsigned int *)&wp->pmic_values[2], + (unsigned int *)&wp->pmic_values[3]); + + if (n != 4) { + printk(KERN_WARNING "Failed read WP %d\n", i); + sdma_free(pm_table->wp); + return -1; + } + } else { + n = sscanf(pm_str_ptr, "WP 0x%x\n", + (unsigned int *)&wp->pmic_values[0]); + + if (n != 1) { + printk(KERN_WARNING "Failed read WP %d\n", i); + sdma_free(pm_table->wp); + return -1; + } + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + + } + + pm_table->table = + (dvfs_state *) sdma_malloc(sizeof(dvfs_state) * + pm_table->dvfs_state_num); + + if (!pm_table->table) { + printk(KERN_WARNING "Failed allocating memory\n"); + sdma_free(pm_table->wp); + return -ENOMEM; + } + + if (cpu_is_mx31() || cpu_is_mx32()) { + for (i = 0; i < pm_table->dvfs_state_num; i++) { + table = &pm_table->table[i]; + + n = sscanf(pm_str_ptr, + "FREQ %d %d 0x%x 0x%x 0x%x 0x%x %d\n", + (unsigned int *)&table->pll_sw_up, + (unsigned int *)&table->pll_sw_down, + (unsigned int *)&table->pdr0_up, + (unsigned int *)&table->pdr0_down, + (unsigned int *)&table->pll_up, + (unsigned int *)&table->pll_down, + (unsigned int *)&table->vscnt); + + if (n != 7) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + pm_table->dcvr = + (dcvr_state **) sdma_malloc(sizeof(dcvr_state *) * + pm_table->dvfs_state_num); + + if (!pm_table->dcvr) { + printk(KERN_WARNING "Failed allocating memory\n"); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -ENOMEM; + } + + for (i = 0; i < pm_table->dvfs_state_num; i++) { + pm_table->dcvr[i] = + (dcvr_state *) sdma_malloc(sizeof(dcvr_state) * + pm_table->wp_num); + + if (!pm_table->dcvr[i]) { + printk(KERN_WARNING "Failed allocating memory\n"); + + for (j = i - 1; j >= 0; j--) { + sdma_free(pm_table->dcvr[j]); + } + + sdma_free(pm_table->dcvr); + return -ENOMEM; + } + + for (j = 0; j < pm_table->wp_num; j++) { + + n = sscanf(pm_str_ptr, "DCVR 0x%x 0x%x 0x%x 0x%x\n", + &pm_table->dcvr[i][j].dcvr_reg[0].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[1].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[2].AsInt, + &pm_table->dcvr[i][j].dcvr_reg[3].AsInt); + + if (n != 4) { + printk(KERN_WARNING "Failed read FREQ %d\n", i); + + for (j = i; j >= 0; j--) { + sdma_free(pm_table->dcvr[j]); + } + sdma_free(pm_table->dcvr); + sdma_free(pm_table->table); + sdma_free(pm_table->wp); + return -1; + } + + pm_str_ptr = pm_table_get_next_line(pm_str_ptr); + } + } + + return 0; +} + +/* + * Initializes the default values of DVFS & DPTC table + * + * @return 0 on success, error code on failure + */ +static int __init dvfs_dptc_init_default_table(void) +{ + int res = 0; + char *table_str; + struct clk *clk; + + dvfs_dptc_tables_s *default_table; + + default_table = sdma_malloc(sizeof(dvfs_dptc_tables_s)); + + if (!default_table) { + return -ENOMEM; + } + + table_str = default_table_str; + if (cpu_is_mx31() || cpu_is_mx32()) { + if (cpu_is_mx31_rev(CHIP_REV_2_0) < 0) { + clk = clk_get(NULL, "ckih"); + if (clk_get_rate(clk) == 27000000) { + printk(KERN_INFO + "DVFS & DPTC: using 27MHz CKIH table\n"); +#ifdef CONFIG_ARCH_MX3 + table_str = default_table_str_27ckih; +#endif + } + } else { +#ifdef CONFIG_ARCH_MX3 + table_str = default_table_str_rev2; +#endif + } + clk_put(clk); + } + + memset(default_table, 0, sizeof(dvfs_dptc_tables_s)); + res = dvfs_dptc_parse_table(default_table, table_str); + + if (res == 0) { + dvfs_dptc_params.dvfs_dptc_tables_ptr = default_table; + } + + return res; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +/*! + * This function is called for SDMA channel initialization. + * + * @param params pointer to the DPTC driver parameters structure. + * + * @return 0 to indicate success else returns a negative number. + */ +static int init_sdma_channel(dvfs_dptc_params_s * params) +{ + dma_channel_params sdma_params; + dma_request_t sdma_request; + int i; + int res = 0; + + params->sdma_channel = 0; + res = mxc_request_dma(¶ms->sdma_channel, "DVFS_DPTC"); + if (res < 0) { + printk(KERN_ERR "Failed allocate SDMA channel for DVFS_DPTC\n"); + return res; + } + + memset(&sdma_params, 0, sizeof(dma_channel_params)); + sdma_params.peripheral_type = CCM; + sdma_params.transfer_type = per_2_emi; + sdma_params.event_id = DMA_REQ_CCM; + sdma_params.callback = (dma_callback_t) dvfs_dptc_sdma_callback; + sdma_params.arg = params; + sdma_params.per_address = CCM_BASE_ADDR; + sdma_params.watermark_level = + sdma_virt_to_phys(params->dvfs_dptc_tables_ptr); + sdma_params.bd_number = 2; + + res = mxc_dma_setup_channel(params->sdma_channel, &sdma_params); + + if (res == 0) { + memset(&sdma_request, 0, sizeof(dma_request_t)); + + for (i = 0; i < DVFS_LB_SDMA_BD; i++) { + sdma_request.destAddr = (__u8 *) + (params->dvfs_log_buffer_phys + + i * (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) / + DVFS_LB_SDMA_BD); + sdma_request.count = DVFS_LB_SIZE / DVFS_LB_SDMA_BD; + sdma_request.bd_cont = 1; + + mxc_dma_set_config(params->sdma_channel, &sdma_request, + i); + } + + mxc_dma_start(params->sdma_channel); + } + + return res; +} +#endif + +/*! + * This function is called for module initialization. + * It initializes the driver data structures, sets up the DPTC hardware, + * registers the DPTC driver, creates a proc file system read entry and + * attaches the driver to the DPTC interrupt. + * + * @return 0 to indicate success else returns a negative number. + * + */ +static int __init dvfs_dptc_init(void) +{ + int res = 0; + struct class_device *temp_class; + + res = dvfs_dptc_init_default_table(); + + if (res < 0) { + printk(KERN_WARNING "Failed parsing default DPTC table\n"); + return res; + } +#ifdef CONFIG_MXC_DPTC + /* Initialize DPTC hardware */ + res = init_dptc_controller(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } +#endif + +#ifdef CONFIG_MXC_DVFS + /* Initialize DVFS hardware */ + res = init_dvfs_controller(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } + + /* Enable 4 mc13783 output voltages */ + pmic_write_reg(REG_ARBITRATION_SWITCHERS, 1, (1 << 5)); + + /* Enable mc13783 voltage ready signal */ + pmic_write_reg(REG_INTERRUPT_MASK_1, 0, (1 << 11)); + + /* Set mc13783 DVS speed 25mV each 4us */ + pmic_write_reg(REG_SWITCHERS_4, 1, (1 << 6)); + pmic_write_reg(REG_SWITCHERS_4, 0, (1 << 7)); + + dvfs_update_freqs_table(dvfs_dptc_params.dvfs_dptc_tables_ptr); +#endif + +#ifdef CONFIG_MXC_DVFS_SDMA + /* Update addresses to physical */ + if (res == 0) { + dvfs_dptc_virt_2_phys(dvfs_dptc_params.dvfs_dptc_tables_ptr); + } + + res = init_sdma_channel(&dvfs_dptc_params); + if (res < 0) { + free_dvfs_dptc_table(); + return res; + } +#endif + + /* Initialize internal driver structures */ + dvfs_dptc_params.dptc_is_active = FALSE; + +#ifdef CONFIG_MXC_DVFS + dvfs_dptc_params.dvfs_is_active = FALSE; +#endif + + /* + * Register DPTC driver as a char driver with an automatically allocated + * major number. + */ + major = register_chrdev(0, DEVICE_NAME, &fops); + + /* + * Return error if a negative major number is returned. + */ + if (major < 0) { + printk(KERN_ERR + "DPTC: Registering driver failed with %d\n", major); + free_dvfs_dptc_table(); + return major; + } + + mxc_dvfs_dptc_class = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(mxc_dvfs_dptc_class)) { + printk(KERN_ERR "DPTC: Error creating class.\n"); + res = PTR_ERR(mxc_dvfs_dptc_class); + goto err_out1; + } + + temp_class = + class_device_create(mxc_dvfs_dptc_class, NULL, MKDEV(major, 0), + NULL, DEVICE_NAME); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "DPTC: Error creating class device.\n"); + res = PTR_ERR(temp_class); + goto err_out2; + } +#ifndef CONFIG_MXC_DVFS_SDMA + /* request the DPTC interrupt */ + res = request_irq(INT_CCM, dvfs_dptc_irq, 0, DEVICE_NAME, NULL); + /* + * If res is not 0, then where was an error + * during attaching to DPTC interrupt. + * Exit and return error code. + */ + if (res) { + printk(KERN_ERR "DPTC: Unable to attach to DPTC interrupt"); + goto err_out3; + } + /* request the DVFS interrupt */ + res = request_irq(INT_DVFS, dvfs_dptc_irq, 0, DEVICE_NAME, NULL); + if (res) { + printk(KERN_ERR "DVFS: Unable to attach to DVFS interrupt"); + goto err_out4; + } +#endif + +#ifdef CONFIG_MXC_DPTC + /* Register low power modes functions */ + res = platform_driver_register(&mxc_dptc_driver); + if (res == 0) { + res = platform_device_register(&mxc_dptc_device); + if (res != 0) { + goto err_out5; + } + } +#endif + dvfs_dptc_params.suspended = FALSE; + + return res; + + err_out5: + free_irq(INT_DVFS, NULL); +#ifndef CONFIG_MXC_DVFS_SDMA + err_out4: +#endif + free_irq(INT_CCM, NULL); +#ifndef CONFIG_MXC_DVFS_SDMA + err_out3: +#endif + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + err_out2: + class_destroy(mxc_dvfs_dptc_class); + err_out1: + unregister_chrdev(major, DEVICE_NAME); + free_dvfs_dptc_table(); + printk(KERN_ERR "DVFS&DPTC driver was not initialized\n"); + return res; +} + +/*! + * This function is called whenever the module is removed from the kernel. It + * unregisters the DVFS & DPTC driver from kernel, frees the irq number + * and removes the proc file system entry. + */ +static void __exit dvfs_dptc_cleanup(void) +{ +#ifdef CONFIG_MXC_DPTC + /* Unregister low power modes functions */ + platform_driver_unregister(&mxc_dptc_driver); + platform_device_unregister(&mxc_dptc_device); +#endif + + free_dvfs_dptc_table(); + + /* Un-register the driver and remove its node */ + class_device_destroy(mxc_dvfs_dptc_class, MKDEV(major, 0)); + class_destroy(mxc_dvfs_dptc_class); + unregister_chrdev(major, DEVICE_NAME); + + /* release the DPTC interrupt */ + free_irq(INT_CCM, NULL); + /* release the DVFS interrupt */ + free_irq(INT_DVFS, NULL); + + /* remove the DPTC proc file system entry */ + remove_proc_entry(PROC_NODE_NAME, NULL); +} + +/*! + * This function is called when the driver is opened. This function + * checks if the user that open the device has root privileges. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dvfs_dptc_open(struct inode *inode, struct file *filp) +{ + /* + * check if the program that opened the driver has root + * privileges, if not return error. + */ + if (!capable(CAP_SYS_ADMIN)) { + return -EACCES; + } + + if (dvfs_dptc_params.suspended) { + return -EPERM; + } + + return 0; +} + +/*! + * This function is called when the driver is close. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * + * @return The function returns 0 on success and a non-zero value on + * failure. + * + */ +static int dvfs_dptc_release(struct inode *inode, struct file *filp) +{ + return 0; +} + +/*! + * This function dumps dptc translation table into string pointer + * + * @param str string pointer + */ +static void dvfs_dptc_dump_table(char *str) +{ + int i, j; + dcvr_state **dcvr_arr; + dcvr_state *dcvr_row; + dvfs_state *table; + + memset(str, 0, MAX_TABLE_SIZE); + + sprintf(str, "WORKING POINT %d\n", + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num); + str += strlen(str); + + for (i = 0; i < dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num; i++) { + if (cpu_is_mx31() || cpu_is_mx32()) { + sprintf(str, "WP 0x%x 0x%x 0x%x 0x%x\n", (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[0], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[1], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[2], (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[3]); + } else { + sprintf(str, "WP 0x%x\n", (unsigned int) + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp[i]. + pmic_values[0]); + } + + str += strlen(str); + } + + if (cpu_is_mx31() || cpu_is_mx32()) { + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; + i++) { + table = dvfs_dptc_params.dvfs_dptc_tables_ptr->table; +#ifdef CONFIG_MXC_DVFS_SDMA + table = sdma_phys_to_virt((unsigned long)table); +#endif + sprintf(str, + "FREQ %d %d 0x%x 0x%x 0x%x 0x%x %d\n", + (unsigned int)table[i].pll_sw_up, + (unsigned int)table[i].pll_sw_down, + (unsigned int)table[i].pdr0_up, + (unsigned int)table[i].pdr0_down, + (unsigned int)table[i].pll_up, + (unsigned int)table[i].pll_down, + (unsigned int)table[i].vscnt); + + str += strlen(str); + } + } + + for (i = 0; + i < dvfs_dptc_params.dvfs_dptc_tables_ptr->dvfs_state_num; i++) { + dcvr_arr = dvfs_dptc_params.dvfs_dptc_tables_ptr->dcvr; +#ifdef CONFIG_MXC_DVFS_SDMA + dcvr_arr = sdma_phys_to_virt((unsigned long)dcvr_arr); +#endif + dcvr_row = dcvr_arr[i]; +#ifdef CONFIG_MXC_DVFS_SDMA + dcvr_row = sdma_phys_to_virt((unsigned long)dcvr_row); +#endif + + for (j = 0; + j < dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num; j++) { + sprintf(str, + "DCVR 0x%x 0x%x 0x%x 0x%x\n", + dcvr_row[j].dcvr_reg[0].AsInt, + dcvr_row[j].dcvr_reg[1].AsInt, + dcvr_row[j].dcvr_reg[2].AsInt, + dcvr_row[j].dcvr_reg[3].AsInt); + + str += strlen(str); + } + } +} + +/*! + * This function reads DVFS & DPTC translation table from user + * + * @param user_table pointer to user table + * @return 0 on success, error code on failure + */ +int dvfs_dptc_set_table(char *user_table) +{ + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + char *tmp_str_ptr; + dvfs_dptc_tables_s *dptc_table; + + if ((cpu_is_mx31() || cpu_is_mx32()) && + (dvfs_dptc_params.dptc_is_active == TRUE || + dvfs_dptc_params.dvfs_is_active == TRUE)) { + ret_val = -EINVAL; + return ret_val; + } else if (dvfs_dptc_params.dptc_is_active == TRUE) { + ret_val = -EINVAL; + return ret_val; + } + + tmp_str = vmalloc(MAX_TABLE_SIZE); + + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + memset(tmp_str, 0, MAX_TABLE_SIZE); + tmp_str_ptr = tmp_str; + + /* + * read num_of_wp and dvfs_state_num + * parameters from new table + */ + while (tmp_str_ptr - tmp_str < MAX_TABLE_SIZE && + (!copy_from_user(tmp_str_ptr, user_table, 1)) && + tmp_str_ptr[0] != 0) { + tmp_str_ptr++; + user_table++; + } + if (tmp_str_ptr == tmp_str) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +didn't copy a character\n"); + ret_val = -EFAULT; + } else if (tmp_str_ptr - tmp_str == MAX_TABLE_SIZE) { + /* error reading from table */ + printk(KERN_ERR "Failed reading table from user, \ +read more than %d\n", MAX_TABLE_SIZE); + ret_val = -EFAULT; + } else { + /* + * copy table from user and set it as + * the current DPTC table + */ + dptc_table = sdma_malloc(sizeof(dvfs_dptc_tables_s)); + + if (!dptc_table) { + ret_val = -ENOMEM; + } else { + ret_val = + dvfs_dptc_parse_table(dptc_table, tmp_str); + + if (ret_val == 0) { + free_dvfs_dptc_table(); + dvfs_dptc_params.dvfs_dptc_tables_ptr = + dptc_table; + +#ifdef CONFIG_MXC_DVFS + dvfs_update_freqs_table + (dvfs_dptc_params. + dvfs_dptc_tables_ptr); +#endif + +#ifdef CONFIG_MXC_DVFS_SDMA + /* Update addresses to physical */ + dvfs_dptc_virt_2_phys(dvfs_dptc_params. + dvfs_dptc_tables_ptr); + mxc_free_dma(dvfs_dptc_params. + sdma_channel); + init_sdma_channel(&dvfs_dptc_params); +#endif +#ifdef CONFIG_MXC_DPTC + set_dptc_curr_freq(&dvfs_dptc_params, + 0); + set_dptc_wp(&dvfs_dptc_params, 0); +#endif + } + } + + } + + vfree(tmp_str); + } + + return ret_val; +} + +#ifdef CONFIG_MXC_DVFS_SDMA +static ssize_t dvfs_dptc_read(struct file *filp, char __user * buf, + size_t count, loff_t * ppos) +{ + size_t count0, count1; + + while (dvfs_dptc_params.chars_in_buffer < count) { + //count = dvfs_dptc_params.chars_in_buffer; + waitqueue_active(&dvfs_dptc_params.dvfs_pred_wait); + wake_up(&dvfs_dptc_params.dvfs_pred_wait); + schedule(); + } + + if (dvfs_dptc_params.read_ptr + count < + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) { + count0 = count; + count1 = 0; + } else { + count0 = + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8 - + dvfs_dptc_params.read_ptr; + count1 = count - count0; + } + + copy_to_user(buf, dvfs_dptc_params.read_ptr, count0); + copy_to_user(buf + count0, dvfs_dptc_params.dvfs_log_buffer, count1); + + if (count1 == 0) { + dvfs_dptc_params.read_ptr += count; + } else { + dvfs_dptc_params.read_ptr = + dvfs_dptc_params.dvfs_log_buffer + count1; + } + + if (dvfs_dptc_params.read_ptr == + dvfs_dptc_params.dvfs_log_buffer + + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) { + dvfs_dptc_params.read_ptr = dvfs_dptc_params.dvfs_log_buffer; + } + + dvfs_dptc_params.chars_in_buffer -= count; + + return count; +} +#endif + +/*! + * This function is called when a ioctl call is made from user space. + * + * @param inode Pointer to device inode + * @param filp Pointer to device file structure + * @param cmd Ioctl command + * @param arg Ioctl argument + * + * Following are the ioctl commands for user to use:\n + * DPTC_IOCTENABLE : Enables the DPTC module.\n + * DPTC_IOCTDISABLE : Disables the DPTC module.\n + * DPTC_IOCSENABLERC : Enables DPTC reference circuits.\n + * DPTC_IOCSDISABLERC : Disables DPTC reference circuits.\n + * DPTC_IOCGETSTATE : Returns 1 if the DPTC module is enabled, + * returns 0 if the DPTC module is disabled.\n + * DPTC_IOCSWP : Sets working point.\n + * PM_IOCSTABLE : Sets translation table.\n + * PM_IOCGTABLE : Gets translation table.\n + * DVFS_IOCTENABLE : Enables DVFS + * DVFS_IOCTDISABLE : Disables DVFS + * DVFS_IOCGSTATE : Returns 1 if the DVFS module is enabled, + * returns 0 if the DVFS module is disabled.\n + * DVFS_IOCSSWGP : Sets the value of DVFS SW general + * purpose bits.\n + * DVFS_IOCSWFI : Sets the status of WFI monitoring.\n + * PM_IOCGFREQ : Returns current CPU frequency in Hz + * DVFS_IOCSFREQ : Sets DVFS frequency when DVFS\n + * HW is disabled.\n + * + * @return The function returns 0 on success and a non-zero value on + * failure. + */ +static int dvfs_dptc_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct clk *clk; + unsigned int tmp; + int ret_val = -ENOIOCTLCMD; + char *tmp_str; + + tmp = arg; + + if (dvfs_dptc_params.suspended) { + return -EPERM; + } + + down(&access_mutex); + + pr_debug("DVFS_DPTC ioctl (%d)\n", cmd); + + switch (cmd) { +#ifdef CONFIG_MXC_DPTC + /* Enable the DPTC module */ + case DPTC_IOCTENABLE: + ret_val = start_dptc(&dvfs_dptc_params); + break; + + /* Disable the DPTC module */ + case DPTC_IOCTDISABLE: + ret_val = stop_dptc(&dvfs_dptc_params); + break; + + case DPTC_IOCSENABLERC: + ret_val = enable_ref_circuits(&dvfs_dptc_params, tmp); + break; + + case DPTC_IOCSDISABLERC: + ret_val = disable_ref_circuits(&dvfs_dptc_params, tmp); + break; + /* + * Return the DPTC module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DPTC_IOCGSTATE: + ret_val = dvfs_dptc_params.dptc_is_active; + break; + case DPTC_IOCSWP: + if (dvfs_dptc_params.dptc_is_active == FALSE) { + if (arg >= 0 && + arg < + dvfs_dptc_params.dvfs_dptc_tables_ptr->wp_num) { + set_dptc_wp(&dvfs_dptc_params, arg); + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + +#endif /* CONFIG_MXC_DPTC */ + + /* Update DPTC table */ + case PM_IOCSTABLE: + ret_val = dvfs_dptc_set_table((char *)arg); + break; + + case PM_IOCGTABLE: + tmp_str = vmalloc(MAX_TABLE_SIZE); + if (tmp_str < 0) { + ret_val = (int)tmp_str; + } else { + dvfs_dptc_dump_table(tmp_str); + if (copy_to_user((char *)tmp, tmp_str, strlen(tmp_str))) { + printk(KERN_ERR + "Failed copy %d characters to 0x%x\n", + strlen(tmp_str), tmp); + ret_val = -EFAULT; + } else { + ret_val = 0; + } + vfree(tmp_str); + } + break; + +#ifdef CONFIG_MXC_DVFS + /* Enable the DVFS module */ + case DVFS_IOCTENABLE: + ret_val = start_dvfs(&dvfs_dptc_params); + break; + + /* Disable the DVFS module */ + case DVFS_IOCTDISABLE: + ret_val = stop_dvfs(&dvfs_dptc_params); + break; + /* + * Return the DVFS module current state. + * Returns 1 if the DPTC module is enabled, else returns 0 + */ + case DVFS_IOCGSTATE: + ret_val = dvfs_dptc_params.dvfs_is_active; + break; + case DVFS_IOCSSWGP: + ret_val = set_sw_gp((unsigned char)arg); + break; + case DVFS_IOCSWFI: + ret_val = set_wfi((unsigned char)arg); + break; + case DVFS_IOCSFREQ: + if (dvfs_dptc_params.dvfs_is_active == FALSE || + dvfs_dptc_params.dvfs_mode == DVFS_PRED_MODE) { + if (arg >= 0 && + arg < + dvfs_dptc_params.dvfs_dptc_tables_ptr-> + dvfs_state_num) { + ret_val = dvfs_set_state(arg); + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } + break; + case DVFS_IOCSMODE: +#ifdef CONFIG_MXC_DVFS_SDMA + if (dvfs_dptc_params.dvfs_is_active == FALSE) { + if ((unsigned int)arg == DVFS_HW_MODE || + (unsigned int)arg == DVFS_PRED_MODE) { + dvfs_dptc_params.dvfs_mode = (unsigned int)arg; + ret_val = 0; + } else { + ret_val = -EINVAL; + } + } else { + ret_val = -EINVAL; + } +#else + /* Predictive mode is supported only in SDMA mode */ + ret_val = -EINVAL; +#endif + break; +#endif /* CONFIG_MXC_DVFS */ + case PM_IOCGFREQ: + clk = clk_get(NULL, "cpu_clk"); + ret_val = clk_get_rate(clk); + break; + + /* Unknown ioctl command -> return error */ + default: + printk(KERN_ERR "Unknown ioctl command 0x%x\n", cmd); + ret_val = -ENOIOCTLCMD; + } + + up(&access_mutex); + + return ret_val; +} + +#ifndef CONFIG_MXC_DVFS_SDMA +/*! + * This function is the DPTC & DVFS Interrupt handler. + * This function wakes-up the dvfs_dptc_workqueue_handler function that handles the + * DPTC interrupt. + * + * @param irq The Interrupt number + * @param dev_id Driver private data + * + * @result The function returns \b IRQ_RETVAL(1) if interrupt was handled, + * returns \b IRQ_RETVAL(0) if the interrupt was not handled. + * \b IRQ_RETVAL is defined in include/linux/interrupt.h. + */ +static irqreturn_t dvfs_dptc_irq(int irq, void *dev_id) +{ + +#ifdef CONFIG_MXC_DPTC + if (dvfs_dptc_params.dptc_is_active == TRUE) { + dptc_irq(); + } +#endif + +#ifdef CONFIG_MXC_DVFS + if (dvfs_dptc_params.dvfs_is_active == TRUE) { + dvfs_irq(&dvfs_dptc_params); + } +#endif + + return IRQ_RETVAL(1); +} +#else +/*! + * This function is the DPTC & DVFS SDMA callback. + * + * @param params pointer to the DVFS & DPTC driver parameters structure. + */ +static void dvfs_dptc_sdma_callback(dvfs_dptc_params_s * params) +{ + dma_request_t sdma_request_params; + int i; + + for (i = 0; i < DVFS_LB_SDMA_BD; i++) { + mxc_dma_get_config(params->sdma_channel, + &sdma_request_params, i); + + if (sdma_request_params.bd_error == 1) { + printk(KERN_WARNING + "Error in DVFS-DPTC buffer descriptor\n"); + } + + if (sdma_request_params.bd_done == 0) { + params->chars_in_buffer += + (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8) / + DVFS_LB_SDMA_BD; + + if (params->chars_in_buffer > + (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8)) { + params->chars_in_buffer = + DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / 8; + params->read_ptr = params->dvfs_log_buffer; + } + + sdma_request_params.destAddr = + (__u8 *) (params->dvfs_log_buffer_phys + + i * (DVFS_LB_SIZE * DVFS_LB_SAMPLE_SIZE / + 8) / DVFS_LB_SDMA_BD); + sdma_request_params.count = + DVFS_LB_SIZE / DVFS_LB_SDMA_BD; + sdma_request_params.bd_cont = 1; + mxc_dma_set_config(params->sdma_channel, + &sdma_request_params, i); + + if (params->dvfs_mode == DVFS_PRED_MODE) { + wake_up_interruptible(¶ms->dvfs_pred_wait); + } + } + } + +#ifdef CONFIG_MXC_DPTC + if (params->prev_wp != params->dvfs_dptc_tables_ptr->curr_wp) { + dptc_irq(); + } +#endif +} +#endif + +module_init(dvfs_dptc_init); +module_exit(dvfs_dptc_cleanup); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DVFS & DPTC driver"); +MODULE_LICENSE("GPL"); |