diff options
Diffstat (limited to 'drivers/mxc')
173 files changed, 90383 insertions, 0 deletions
diff --git a/drivers/mxc/Kconfig b/drivers/mxc/Kconfig new file mode 100644 index 000000000000..72225bbe91de --- /dev/null +++ b/drivers/mxc/Kconfig @@ -0,0 +1,39 @@ +# drivers/video/mxc/Kconfig + +if ARCH_MXC + +menu "MXC support drivers" + +config MXC_IPU + bool "Image Processing Unit Driver" + depends on !ARCH_MX21 + depends on !ARCH_MX27 + depends on !ARCH_MX25 + select MXC_IPU_V1 if !ARCH_MX37 && !ARCH_MX51 + select MXC_IPU_V3 if ARCH_MX37 || ARCH_MX51 + select MXC_IPU_V3D if ARCH_MX37 + select MXC_IPU_V3EX if ARCH_MX51 + help + If you plan to use the Image Processing unit, say + Y here. IPU is needed by Framebuffer and V4L2 drivers. + +source "drivers/mxc/ipu/Kconfig" +source "drivers/mxc/ipu3/Kconfig" + +source "drivers/mxc/ssi/Kconfig" +source "drivers/mxc/dam/Kconfig" +source "drivers/mxc/pmic/Kconfig" +source "drivers/mxc/mcu_pmic/Kconfig" +source "drivers/mxc/security/Kconfig" +source "drivers/mxc/hmp4e/Kconfig" +source "drivers/mxc/hw_event/Kconfig" +source "drivers/mxc/vpu/Kconfig" +source "drivers/mxc/asrc/Kconfig" +source "drivers/mxc/bt/Kconfig" +source "drivers/mxc/gps_ioctrl/Kconfig" +source "drivers/mxc/mlb/Kconfig" +source "drivers/mxc/adc/Kconfig" + +endmenu + +endif diff --git a/drivers/mxc/Makefile b/drivers/mxc/Makefile new file mode 100644 index 000000000000..6416bc429888 --- /dev/null +++ b/drivers/mxc/Makefile @@ -0,0 +1,17 @@ +obj-$(CONFIG_MXC_IPU_V1) += ipu/ +obj-$(CONFIG_MXC_IPU_V3) += ipu3/ +obj-$(CONFIG_MXC_SSI) += ssi/ +obj-$(CONFIG_MXC_DAM) += dam/ + +obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += mcu_pmic/ +obj-$(CONFIG_MXC_PMIC) += pmic/ + +obj-$(CONFIG_MXC_HMP4E) += hmp4e/ +obj-y += security/ +obj-$(CONFIG_MXC_VPU) += vpu/ +obj-$(CONFIG_MXC_HWEVENT) += hw_event/ +obj-$(CONFIG_MXC_ASRC) += asrc/ +obj-$(CONFIG_MXC_BLUETOOTH) += bt/ +obj-$(CONFIG_GPS_IOCTRL) += gps_ioctrl/ +obj-$(CONFIG_MXC_MLB) += mlb/ +obj-$(CONFIG_IMX_ADC) += adc/ diff --git a/drivers/mxc/adc/Kconfig b/drivers/mxc/adc/Kconfig new file mode 100644 index 000000000000..91ad23bc7cbe --- /dev/null +++ b/drivers/mxc/adc/Kconfig @@ -0,0 +1,14 @@ +# +# i.MX ADC devices +# + +menu "i.MX ADC support" + +config IMX_ADC + tristate "i.MX ADC" + depends on ARCH_MXC + default n + help + This selects the Freescale i.MX on-chip ADC driver. + +endmenu diff --git a/drivers/mxc/adc/Makefile b/drivers/mxc/adc/Makefile new file mode 100644 index 000000000000..e21e48ee1185 --- /dev/null +++ b/drivers/mxc/adc/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for i.MX adc devices. +# +obj-$(CONFIG_IMX_ADC) += imx_adc.o diff --git a/drivers/mxc/adc/imx_adc.c b/drivers/mxc/adc/imx_adc.c new file mode 100644 index 000000000000..11f0a66ee4bb --- /dev/null +++ b/drivers/mxc/adc/imx_adc.c @@ -0,0 +1,1045 @@ +/* + * Copyright 2009 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 adc/imx_adc.c + * @brief This is the main file of i.MX ADC driver. + * + * @ingroup IMX_ADC + */ + +/* + * Includes + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/imx_adc.h> +#include "imx_adc_reg.h" + +static int imx_adc_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait; + +/*! + * To indicate whether any of the adc devices are suspending + */ +static int suspend_flag; + +/*! + * The suspendq is used by blocking application calls + */ +static wait_queue_head_t suspendq; +static wait_queue_head_t tsq; + +static bool imx_adc_ready; +static bool ts_data_ready; +static int tsi_data = TSI_DATA; +static unsigned short ts_data_buf[16]; + +static struct class *imx_adc_class; +static struct imx_adc_data *adc_data; + +static DECLARE_MUTEX(general_convert_mutex); +static DECLARE_MUTEX(ts_convert_mutex); + +unsigned long tsc_base; + +int is_imx_adc_ready(void) +{ + return imx_adc_ready; +} +EXPORT_SYMBOL(is_imx_adc_ready); + +void tsc_clk_enable(void) +{ + unsigned long reg; + + clk_enable(adc_data->adc_clk); + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_IPG_CLK_EN; + __raw_writel(reg, tsc_base + TGCR); +} + +void tsc_clk_disable(void) +{ + unsigned long reg; + + clk_disable(adc_data->adc_clk); + + reg = __raw_readl(tsc_base + TGCR); + reg &= ~TGCR_IPG_CLK_EN; + __raw_writel(reg, tsc_base + TGCR); +} + +void tsc_self_reset(void) +{ + unsigned long reg; + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_TSC_RST; + __raw_writel(reg, tsc_base + TGCR); + + while (__raw_readl(tsc_base + TGCR) & TGCR_TSC_RST) + continue; +} + +/* Internal reference */ +void tsc_intref_enable(void) +{ + unsigned long reg; + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_INTREFEN; + __raw_writel(reg, tsc_base + TGCR); +} + +/* initialize touchscreen */ +void imx_tsc_init(void) +{ + unsigned long reg; + int lastitemid; + int dbtime; + + /* Level sense */ + reg = __raw_readl(tsc_base + TCQCR); + reg |= CQCR_PD_CFG; + reg |= (0xf << CQCR_FIFOWATERMARK_SHIFT); /* watermark */ + __raw_writel(reg, tsc_base + TCQCR); + + /* Configure 4-wire */ + reg = TSC_4WIRE_PRECHARGE; + reg |= CC_IGS; + __raw_writel(reg, tsc_base + TCC0); + + reg = TSC_4WIRE_TOUCH_DETECT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 32 << CC_SETTLING_TIME_SHIFT; /* it's important! */ + __raw_writel(reg, tsc_base + TCC1); + + reg = TSC_4WIRE_X_MEASUMENT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 16 << CC_SETTLING_TIME_SHIFT; /* settling time */ + __raw_writel(reg, tsc_base + TCC2); + + reg = TSC_4WIRE_Y_MEASUMENT; + reg |= 3 << CC_NOS_SHIFT; /* 4 samples */ + reg |= 16 << CC_SETTLING_TIME_SHIFT; /* settling time */ + __raw_writel(reg, tsc_base + TCC3); + + reg = (TCQ_ITEM_TCC0 << TCQ_ITEM7_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM6_SHIFT) | + (TCQ_ITEM_TCC1 << TCQ_ITEM5_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM4_SHIFT) | + (TCQ_ITEM_TCC3 << TCQ_ITEM3_SHIFT) | + (TCQ_ITEM_TCC2 << TCQ_ITEM2_SHIFT) | + (TCQ_ITEM_TCC1 << TCQ_ITEM1_SHIFT) | + (TCQ_ITEM_TCC0 << TCQ_ITEM0_SHIFT); + __raw_writel(reg, tsc_base + TCQ_ITEM_7_0); + + lastitemid = 5; + reg = __raw_readl(tsc_base + TCQCR); + reg = (reg & ~CQCR_LAST_ITEM_ID_MASK) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT); + __raw_writel(reg, tsc_base + TCQCR); + + /* pen down enable */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_PD_MSK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQMR); + reg &= ~TCQMR_PD_IRQ_MSK; + __raw_writel(reg, tsc_base + TCQMR); + + /* Debounce time = dbtime*8 adc clock cycles */ + reg = __raw_readl(tsc_base + TGCR); + dbtime = 3; + reg &= ~TGCR_PDBTIME_MASK; + reg |= dbtime << TGCR_PDBTIME_SHIFT; + reg |= TGCR_SLPC; + __raw_writel(reg, tsc_base + TGCR); + +} + +static irqreturn_t imx_adc_interrupt(int irq, void *dev_id) +{ + int reg; + + /* disable pen down detect */ + reg = __raw_readl(tsc_base + TGCR); + reg &= ~TGCR_PD_EN; + __raw_writel(reg, tsc_base + TGCR); + + ts_data_ready = 1; + wake_up_interruptible(&tsq); + return IRQ_HANDLED; +} + +enum IMX_ADC_STATUS imx_adc_read_general(unsigned short *result) +{ + unsigned long reg; + unsigned int data_num = 0; + + reg = __raw_readl(tsc_base + GCQCR); + reg |= CQCR_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + while (!(__raw_readl(tsc_base + GCQSR) & CQSR_EOQ)) + continue; + reg = __raw_readl(tsc_base + GCQCR); + reg &= ~CQCR_FQS; + __raw_writel(reg, tsc_base + GCQCR); + reg = __raw_readl(tsc_base + GCQSR); + reg |= CQSR_EOQ; + __raw_writel(reg, tsc_base + GCQSR); + + while (!(__raw_readl(tsc_base + GCQSR) & CQSR_EMPT)) { + result[data_num] = __raw_readl(tsc_base + GCQFIFO) >> + GCQFIFO_ADCOUT_SHIFT; + data_num++; + } + return IMX_ADC_SUCCESS; +} + +/*! + * This function will get raw (X,Y) value by converting the voltage + * @param touch_sample Pointer to touch sample + * + * return This funciton returns 0 if successful. + * + * + */ +enum IMX_ADC_STATUS imx_adc_read_ts(struct t_touch_screen *touch_sample, + int wait_tsi) +{ + int reg; + int data_num = 0; + int detect_sample1, detect_sample2; + + memset(ts_data_buf, 0, sizeof ts_data_buf); + touch_sample->valid_flag = 1; + + if (wait_tsi) { + /* Config idle for 4-wire */ + reg = TSC_4WIRE_TOUCH_DETECT; + __raw_writel(reg, tsc_base + TICR); + + /* Pen interrupt starts new conversion queue */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + + /* PDEN and PDBEN */ + reg = __raw_readl(tsc_base + TGCR); + reg |= (TGCR_PDB_EN | TGCR_PD_EN); + __raw_writel(reg, tsc_base + TGCR); + + wait_event_interruptible(tsq, ts_data_ready); + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ)) + continue; + /* stop the conversion */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + __raw_writel(reg, tsc_base + TCQCR); + reg = CQSR_PD | CQSR_EOQ; + __raw_writel(reg, tsc_base + TCQSR); + + /* change configuration for FQS mode */ + tsi_data = TSI_DATA; + reg = (0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) | + CC_XPULSW; + __raw_writel(reg, tsc_base + TICR); + } else { + /* FQS semaphore */ + down(&ts_convert_mutex); + + reg = (0x1 << CC_YPLLSW_SHIFT) | (0x1 << CC_XNURSW_SHIFT) | + CC_XPULSW; + __raw_writel(reg, tsc_base + TICR); + + /* FQS */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQCR); + reg |= CQCR_FQS; + __raw_writel(reg, tsc_base + TCQCR); + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EOQ)) + continue; + + /* stop FQS */ + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_FQS; + __raw_writel(reg, tsc_base + TCQCR); + + /* clear status bit */ + reg = __raw_readl(tsc_base + TCQSR); + reg |= CQSR_EOQ; + __raw_writel(reg, tsc_base + TCQSR); + tsi_data = FQS_DATA; + } + + while (!(__raw_readl(tsc_base + TCQSR) & CQSR_EMPT)) { + reg = __raw_readl(tsc_base + TCQFIFO); + ts_data_buf[data_num] = reg; + data_num++; + } + + touch_sample->x_position1 = ts_data_buf[4] >> 4; + touch_sample->x_position2 = ts_data_buf[5] >> 4; + touch_sample->x_position3 = ts_data_buf[6] >> 4; + touch_sample->y_position1 = ts_data_buf[9] >> 4; + touch_sample->y_position2 = ts_data_buf[10] >> 4; + touch_sample->y_position3 = ts_data_buf[11] >> 4; + + detect_sample1 = ts_data_buf[0]; + detect_sample2 = ts_data_buf[12]; + + if ((detect_sample1 > 0x6000) || (detect_sample2 > 0x6000)) + touch_sample->valid_flag = 0; + + ts_data_ready = 0; + + if (!(touch_sample->x_position1 || + touch_sample->x_position2 || touch_sample->x_position3)) + touch_sample->contact_resistance = 0; + else + touch_sample->contact_resistance = 1; + + if (tsi_data == FQS_DATA) + up(&ts_convert_mutex); + return IMX_ADC_SUCCESS; +} + +/*! + * This function performs filtering and rejection of excessive noise prone + * sampl. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int imx_adc_filter(struct t_touch_screen *ts_curr) +{ + + unsigned int ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3; + unsigned int sample_sumx, sample_sumy; + static unsigned int prev_x[FILTLEN], prev_y[FILTLEN]; + int index = 0; + unsigned int y_curr, x_curr; + static int filt_count; + /* Added a variable filt_type to decide filtering at run-time */ + unsigned int filt_type = 0; + + /* ignore the data converted when pen down and up */ + if ((ts_curr->contact_resistance == 0) || tsi_data == TSI_DATA) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + filt_count = 0; + return 0; + } + /* ignore the data valid */ + if (ts_curr->valid_flag == 0) + return -1; + + ydiff1 = abs(ts_curr->y_position1 - ts_curr->y_position2); + ydiff2 = abs(ts_curr->y_position2 - ts_curr->y_position3); + ydiff3 = abs(ts_curr->y_position1 - ts_curr->y_position3); + if ((ydiff1 > DELTA_Y_MAX) || + (ydiff2 > DELTA_Y_MAX) || (ydiff3 > DELTA_Y_MAX)) { + pr_debug("imx_adc_filter: Ret pos 1\n"); + return -1; + } + + xdiff1 = abs(ts_curr->x_position1 - ts_curr->x_position2); + xdiff2 = abs(ts_curr->x_position2 - ts_curr->x_position3); + xdiff3 = abs(ts_curr->x_position1 - ts_curr->x_position3); + + if ((xdiff1 > DELTA_X_MAX) || + (xdiff2 > DELTA_X_MAX) || (xdiff3 > DELTA_X_MAX)) { + pr_debug("imx_adc_filter: Ret pos 2\n"); + return -1; + } + /* Compute two closer values among the three available Y readouts */ + + if (ydiff1 < ydiff2) { + if (ydiff1 < ydiff3) { + /* Sample 0 & 1 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } else { + if (ydiff2 < ydiff3) { + /* Sample 1 & 2 closest together */ + sample_sumy = ts_curr->y_position2 + + ts_curr->y_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } + + /* + * Compute two closer values among the three available X + * readouts + */ + if (xdiff1 < xdiff2) { + if (xdiff1 < xdiff3) { + /* Sample 0 & 1 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position2; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } else { + if (xdiff2 < xdiff3) { + /* Sample 1 & 2 closest together */ + sample_sumx = ts_curr->x_position2 + + ts_curr->x_position3; + } else { + /* Sample 0 & 2 closest together */ + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } + + /* + * Wait FILTER_MIN_DELAY number of samples to restart + * filtering + */ + if (filt_count < FILTER_MIN_DELAY) { + /* + * Current output is the average of the two closer + * values and no filtering is used + */ + y_curr = (sample_sumy / 2); + x_curr = (sample_sumx / 2); + ts_curr->y_position = y_curr; + ts_curr->x_position = x_curr; + filt_count++; + + } else { + if (abs(sample_sumx - (prev_x[0] + prev_x[1])) > + (DELTA_X_MAX * 16)) { + pr_debug("imx_adc_filter: : Ret pos 3\n"); + return -1; + } + if (abs(sample_sumy - (prev_y[0] + prev_y[1])) > + (DELTA_Y_MAX * 16)) { + pr_debug("imx_adc_filter: : Ret pos 4\n"); + return -1; + } + sample_sumy /= 2; + sample_sumx /= 2; + /* Use hard filtering if the sample difference < 10 */ + if ((abs(sample_sumy - prev_y[0]) > 10) || + (abs(sample_sumx - prev_x[0]) > 10)) + filt_type = 1; + + /* + * Current outputs are the average of three previous + * values and the present readout + */ + y_curr = sample_sumy; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) + y_curr = y_curr + (prev_y[index]); + else + y_curr = y_curr + (prev_y[index] / 3); + } + if (filt_type == 0) + y_curr = y_curr >> 2; + else + y_curr = y_curr >> 1; + ts_curr->y_position = y_curr; + + x_curr = sample_sumx; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) + x_curr = x_curr + (prev_x[index]); + else + x_curr = x_curr + (prev_x[index] / 3); + } + if (filt_type == 0) + x_curr = x_curr >> 2; + else + x_curr = x_curr >> 1; + ts_curr->x_position = x_curr; + + } + + /* Update previous X and Y values */ + for (index = (FILTLEN - 1); index > 0; index--) { + prev_x[index] = prev_x[index - 1]; + prev_y[index] = prev_y[index - 1]; + } + + /* + * Current output will be the most recent past for the + * next sample + */ + prev_y[0] = y_curr; + prev_x[0] = x_curr; + + return 0; + +} + +/*! + * This function retrieves the current touch screen (X,Y) coordinates. + * + * @param touch_sample Pointer to touch sample. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_get_touch_sample(struct t_touch_screen + *touch_sample, int wait_tsi) +{ + if (imx_adc_read_ts(touch_sample, wait_tsi)) + return IMX_ADC_ERROR; + if (!imx_adc_filter(touch_sample)) + return IMX_ADC_SUCCESS; + else + return IMX_ADC_ERROR; +} +EXPORT_SYMBOL(imx_adc_get_touch_sample); + +/*! + * This is the suspend of power management for the i.MX ADC API. + * It supports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int imx_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (device_may_wakeup(&pdev->dev)) { + enable_irq_wake(adc_data->irq); + } else { + suspend_flag = 1; + tsc_clk_disable(); + } + return 0; +}; + +/*! + * This is the resume of power management for the i.MX adc API. + * It supports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int imx_adc_resume(struct platform_device *pdev) +{ + if (device_may_wakeup(&pdev->dev)) { + disable_irq_wake(adc_data->irq); + } else { + suspend_flag = 0; + tsc_clk_enable(); + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + } + return 0; +} + +/*! + * This function implements the open method on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int imx_adc_open(struct inode *inode, struct file *file) +{ + while (suspend_flag) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, !suspend_flag)) + return -ERESTARTSYS; + } + pr_debug("imx_adc : imx_adc_open()\n"); + return 0; +} + +/*! + * This function implements the release method on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int imx_adc_free(struct inode *inode, struct file *file) +{ + pr_debug("imx_adc : imx_adc_free()\n"); + return 0; +} + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +int imx_adc_init(void) +{ + int reg; + + pr_debug("imx_adc_init()\n"); + + if (suspend_flag) + return -EBUSY; + + tsc_clk_enable(); + + /* Reset */ + tsc_self_reset(); + + /* Internal reference */ + tsc_intref_enable(); + + /* Set power mode */ + reg = __raw_readl(tsc_base + TGCR) & ~TGCR_POWER_MASK; + reg |= TGCR_POWER_SAVE; + __raw_writel(reg, tsc_base + TGCR); + + imx_tsc_init(); + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_init); + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_deinit(void) +{ + pr_debug("imx_adc_deinit()\n"); + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_deinit); + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_convert(enum t_channel channel, + unsigned short *result) +{ + int reg; + int lastitemid; + struct t_touch_screen touch_sample; + + switch (channel) { + + case TS_X_POS: + imx_adc_get_touch_sample(&touch_sample, 0); + result[0] = touch_sample.x_position; + + /* if no pen down ,recover the register configuration */ + if (touch_sample.contact_resistance == 0) { + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_PDB_EN | TGCR_PD_EN; + __raw_writel(reg, tsc_base + TGCR); + } + break; + + case TS_Y_POS: + imx_adc_get_touch_sample(&touch_sample, 0); + result[1] = touch_sample.y_position; + + /* if no pen down ,recover the register configuration */ + if (touch_sample.contact_resistance == 0) { + reg = __raw_readl(tsc_base + TCQCR); + reg &= ~CQCR_QSM_MASK; + reg |= CQCR_QSM_PEN; + __raw_writel(reg, tsc_base + TCQCR); + + reg = __raw_readl(tsc_base + TGCR); + reg |= TGCR_PDB_EN | TGCR_PD_EN; + __raw_writel(reg, tsc_base + TGCR); + } + break; + + case GER_PURPOSE_ADC0: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC0; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_ADC1: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC1; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_ADC2: + down(&general_convert_mutex); + + lastitemid = 0; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + reg = TSC_GENERAL_ADC_GCC2; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + + case GER_PURPOSE_MULTICHNNEL: + down(&general_convert_mutex); + + reg = TSC_GENERAL_ADC_GCC0; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC0); + + reg = TSC_GENERAL_ADC_GCC1; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC1); + + reg = TSC_GENERAL_ADC_GCC2; + reg |= (3 << CC_NOS_SHIFT) | (16 << CC_SETTLING_TIME_SHIFT); + __raw_writel(reg, tsc_base + GCC2); + + reg = (GCQ_ITEM_GCC2 << GCQ_ITEM2_SHIFT) | + (GCQ_ITEM_GCC1 << GCQ_ITEM1_SHIFT) | + (GCQ_ITEM_GCC0 << GCQ_ITEM0_SHIFT); + __raw_writel(reg, tsc_base + GCQ_ITEM_7_0); + + lastitemid = 2; + reg = (0xf << CQCR_FIFOWATERMARK_SHIFT) | + (lastitemid << CQCR_LAST_ITEM_ID_SHIFT) | CQCR_QSM_FQS; + __raw_writel(reg, tsc_base + GCQCR); + + imx_adc_read_general(result); + up(&general_convert_mutex); + break; + default: + pr_debug("%s: bad channel number\n", __func__); + return IMX_ADC_ERROR; + } + + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_convert); + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns IMX_ADC_SUCCESS if successful. + */ +enum IMX_ADC_STATUS imx_adc_convert_multichnnel(enum t_channel channels, + unsigned short *result) +{ + imx_adc_convert(GER_PURPOSE_MULTICHNNEL, result); + return IMX_ADC_SUCCESS; +} +EXPORT_SYMBOL(imx_adc_convert_multichnnel); + +/*! + * This function implements IOCTL controls on an i.MX ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int imx_adc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct t_adc_convert_param *convert_param; + + if ((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D')) + return -ENOTTY; + + while (suspend_flag) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, !suspend_flag)) + return -ERESTARTSYS; + } + + switch (cmd) { + case IMX_ADC_INIT: + pr_debug("init adc\n"); + CHECK_ERROR(imx_adc_init()); + break; + + case IMX_ADC_DEINIT: + pr_debug("deinit adc\n"); + CHECK_ERROR(imx_adc_deinit()); + break; + + case IMX_ADC_CONVERT: + convert_param = kmalloc(sizeof(*convert_param), GFP_KERNEL); + if (convert_param == NULL) + return -ENOMEM; + if (copy_from_user(convert_param, + (struct t_adc_convert_param *)arg, + sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(imx_adc_convert(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((struct t_adc_convert_param *)arg, + convert_param, sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case IMX_ADC_CONVERT_MULTICHANNEL: + convert_param = kmalloc(sizeof(*convert_param), GFP_KERNEL); + if (convert_param == NULL) + return -ENOMEM; + if (copy_from_user(convert_param, + (struct t_adc_convert_param *)arg, + sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(imx_adc_convert_multichnnel + (convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((struct t_adc_convert_param *)arg, + convert_param, sizeof(*convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + default: + pr_debug("imx_adc_ioctl: unsupported ioctl command 0x%x\n", + cmd); + return -EINVAL; + } + return 0; +} + +static struct file_operations imx_adc_fops = { + .owner = THIS_MODULE, + .ioctl = imx_adc_ioctl, + .open = imx_adc_open, + .release = imx_adc_free, +}; + +static int imx_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + int retval; + struct device *temp_class; + struct resource *res; + void __iomem *base; + + /* ioremap the base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No TSC base address provided\n"); + goto err_out0; + } + base = ioremap(res->start, res->end - res->start); + if (base == NULL) { + dev_err(&pdev->dev, "failed to rebase TSC base address\n"); + goto err_out0; + } + tsc_base = (unsigned long)base; + + /* create the chrdev */ + imx_adc_major = register_chrdev(0, "imx_adc", &imx_adc_fops); + + if (imx_adc_major < 0) { + dev_err(&pdev->dev, "Unable to get a major for imx_adc\n"); + return imx_adc_major; + } + init_waitqueue_head(&suspendq); + init_waitqueue_head(&tsq); + + imx_adc_class = class_create(THIS_MODULE, "imx_adc"); + if (IS_ERR(imx_adc_class)) { + dev_err(&pdev->dev, "Error creating imx_adc class.\n"); + ret = PTR_ERR(imx_adc_class); + goto err_out1; + } + + temp_class = device_create(imx_adc_class, NULL, + MKDEV(imx_adc_major, 0), NULL, "imx_adc"); + if (IS_ERR(temp_class)) { + dev_err(&pdev->dev, "Error creating imx_adc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + adc_data = kmalloc(sizeof(struct imx_adc_data), GFP_KERNEL); + if (adc_data == NULL) + return -ENOMEM; + adc_data->irq = platform_get_irq(pdev, 0); + retval = request_irq(adc_data->irq, imx_adc_interrupt, + 0, MOD_NAME, MOD_NAME); + if (retval) { + return retval; + } + adc_data->adc_clk = clk_get(&pdev->dev, "tchscrn_clk"); + + ret = imx_adc_init(); + + if (ret != IMX_ADC_SUCCESS) { + dev_err(&pdev->dev, "Error in imx_adc_init.\n"); + goto err_out4; + } + imx_adc_ready = 1; + + /* By default, devices should wakeup if they can */ + /* So TouchScreen is set as "should wakeup" as it can */ + device_init_wakeup(&pdev->dev, 1); + + pr_info("i.MX ADC at 0x%x irq %d\n", (unsigned int)res->start, + adc_data->irq); + return ret; + +err_out4: + device_destroy(imx_adc_class, MKDEV(imx_adc_major, 0)); +err_out2: + class_destroy(imx_adc_class); +err_out1: + unregister_chrdev(imx_adc_major, "imx_adc"); +err_out0: + return ret; +} + +static int imx_adc_module_remove(struct platform_device *pdev) +{ + imx_adc_ready = 0; + imx_adc_deinit(); + device_destroy(imx_adc_class, MKDEV(imx_adc_major, 0)); + class_destroy(imx_adc_class); + unregister_chrdev(imx_adc_major, "imx_adc"); + free_irq(adc_data->irq, MOD_NAME); + kfree(adc_data); + pr_debug("i.MX ADC successfully removed\n"); + return 0; +} + +static struct platform_driver imx_adc_driver = { + .driver = { + .name = "imx_adc", + }, + .suspend = imx_adc_suspend, + .resume = imx_adc_resume, + .probe = imx_adc_module_probe, + .remove = imx_adc_module_remove, +}; + +/* + * Initialization and Exit + */ +static int __init imx_adc_module_init(void) +{ + pr_debug("i.MX ADC driver loading...\n"); + return platform_driver_register(&imx_adc_driver); +} + +static void __exit imx_adc_module_exit(void) +{ + platform_driver_unregister(&imx_adc_driver); + pr_debug("i.MX ADC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(imx_adc_module_init); +module_exit(imx_adc_module_exit); + +MODULE_DESCRIPTION("i.MX ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/adc/imx_adc_reg.h b/drivers/mxc/adc/imx_adc_reg.h new file mode 100644 index 000000000000..8ae776038fbc --- /dev/null +++ b/drivers/mxc/adc/imx_adc_reg.h @@ -0,0 +1,242 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef __IMX_ADC_H__ +#define __IMX_ADC_H__ + +/* TSC General Config Register */ +#define TGCR 0x000 +#define TGCR_IPG_CLK_EN (1 << 0) +#define TGCR_TSC_RST (1 << 1) +#define TGCR_FUNC_RST (1 << 2) +#define TGCR_SLPC (1 << 4) +#define TGCR_STLC (1 << 5) +#define TGCR_HSYNC_EN (1 << 6) +#define TGCR_HSYNC_POL (1 << 7) +#define TGCR_POWERMODE_SHIFT 8 +#define TGCR_POWER_OFF (0x0 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_SAVE (0x1 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_ON (0x3 << TGCR_POWERMODE_SHIFT) +#define TGCR_POWER_MASK (0x3 << TGCR_POWERMODE_SHIFT) +#define TGCR_INTREFEN (1 << 10) +#define TGCR_ADCCLKCFG_SHIFT 16 +#define TGCR_PD_EN (1 << 23) +#define TGCR_PDB_EN (1 << 24) +#define TGCR_PDBTIME_SHIFT 25 +#define TGCR_PDBTIME128 (0x3f << TGCR_PDBTIME_SHIFT) +#define TGCR_PDBTIME_MASK (0x7f << TGCR_PDBTIME_SHIFT) + +/* TSC General Status Register */ +#define TGSR 0x004 +#define TCQ_INT (1 << 0) +#define GCQ_INT (1 << 1) +#define SLP_INT (1 << 2) +#define TCQ_DMA (1 << 16) +#define GCQ_DMA (1 << 17) + +/* TSC IDLE Config Register */ +#define TICR 0x008 + +/* TouchScreen Convert Queue FIFO Register */ +#define TCQFIFO 0x400 +/* TouchScreen Convert Queue Control Register */ +#define TCQCR 0x404 +#define CQCR_QSM_SHIFT 0 +#define CQCR_QSM_STOP (0x0 << CQCR_QSM_SHIFT) +#define CQCR_QSM_PEN (0x1 << CQCR_QSM_SHIFT) +#define CQCR_QSM_FQS (0x2 << CQCR_QSM_SHIFT) +#define CQCR_QSM_FQS_PEN (0x3 << CQCR_QSM_SHIFT) +#define CQCR_QSM_MASK (0x3 << CQCR_QSM_SHIFT) +#define CQCR_FQS (1 << 2) +#define CQCR_RPT (1 << 3) +#define CQCR_LAST_ITEM_ID_SHIFT 4 +#define CQCR_LAST_ITEM_ID_MASK (0xf << CQCR_LAST_ITEM_ID_SHIFT) +#define CQCR_FIFOWATERMARK_SHIFT 8 +#define CQCR_FIFOWATERMARK_MASK (0xf << CQCR_FIFOWATERMARK_SHIFT) +#define CQCR_REPEATWAIT_SHIFT 12 +#define CQCR_REPEATWAIT_MASK (0xf << CQCR_REPEATWAIT_SHIFT) +#define CQCR_QRST (1 << 16) +#define CQCR_FRST (1 << 17) +#define CQCR_PD_MSK (1 << 18) +#define CQCR_PD_CFG (1 << 19) + +/* TouchScreen Convert Queue Status Register */ +#define TCQSR 0x408 +#define CQSR_PD (1 << 0) +#define CQSR_EOQ (1 << 1) +#define CQSR_FOR (1 << 4) +#define CQSR_FUR (1 << 5) +#define CQSR_FER (1 << 6) +#define CQSR_EMPT (1 << 13) +#define CQSR_FULL (1 << 14) +#define CQSR_FDRY (1 << 15) + +/* TouchScreen Convert Queue Mask Register */ +#define TCQMR 0x40c +#define TCQMR_PD_IRQ_MSK (1 << 0) +#define TCQMR_EOQ_IRQ_MSK (1 << 1) +#define TCQMR_FOR_IRQ_MSK (1 << 4) +#define TCQMR_FUR_IRQ_MSK (1 << 5) +#define TCQMR_FER_IRQ_MSK (1 << 6) +#define TCQMR_PD_DMA_MSK (1 << 16) +#define TCQMR_EOQ_DMA_MSK (1 << 17) +#define TCQMR_FOR_DMA_MSK (1 << 20) +#define TCQMR_FUR_DMA_MSK (1 << 21) +#define TCQMR_FER_DMA_MSK (1 << 22) +#define TCQMR_FDRY_DMA_MSK (1 << 31) + +/* TouchScreen Convert Queue ITEM 7~0 */ +#define TCQ_ITEM_7_0 0x420 + +/* TouchScreen Convert Queue ITEM 15~8 */ +#define TCQ_ITEM_15_8 0x424 + +#define TCQ_ITEM7_SHIFT 28 +#define TCQ_ITEM6_SHIFT 24 +#define TCQ_ITEM5_SHIFT 20 +#define TCQ_ITEM4_SHIFT 16 +#define TCQ_ITEM3_SHIFT 12 +#define TCQ_ITEM2_SHIFT 8 +#define TCQ_ITEM1_SHIFT 4 +#define TCQ_ITEM0_SHIFT 0 + +#define TCQ_ITEM_TCC0 0x0 +#define TCQ_ITEM_TCC1 0x1 +#define TCQ_ITEM_TCC2 0x2 +#define TCQ_ITEM_TCC3 0x3 +#define TCQ_ITEM_TCC4 0x4 +#define TCQ_ITEM_TCC5 0x5 +#define TCQ_ITEM_TCC6 0x6 +#define TCQ_ITEM_TCC7 0x7 +#define TCQ_ITEM_GCC7 0x8 +#define TCQ_ITEM_GCC6 0x9 +#define TCQ_ITEM_GCC5 0xa +#define TCQ_ITEM_GCC4 0xb +#define TCQ_ITEM_GCC3 0xc +#define TCQ_ITEM_GCC2 0xd +#define TCQ_ITEM_GCC1 0xe +#define TCQ_ITEM_GCC0 0xf + +/* TouchScreen Convert Config 0-7 */ +#define TCC0 0x440 +#define TCC1 0x444 +#define TCC2 0x448 +#define TCC3 0x44c +#define TCC4 0x450 +#define TCC5 0x454 +#define TCC6 0x458 +#define TCC7 0x45c +#define CC_PEN_IACK (1 << 1) +#define CC_SEL_REFN_SHIFT 2 +#define CC_SEL_REFN_YNLR (0x1 << CC_SEL_REFN_SHIFT) +#define CC_SEL_REFN_AGND (0x2 << CC_SEL_REFN_SHIFT) +#define CC_SEL_REFN_MASK (0x3 << CC_SEL_REFN_SHIFT) +#define CC_SELIN_SHIFT 4 +#define CC_SELIN_XPUL (0x0 << CC_SELIN_SHIFT) +#define CC_SELIN_YPLL (0x1 << CC_SELIN_SHIFT) +#define CC_SELIN_XNUR (0x2 << CC_SELIN_SHIFT) +#define CC_SELIN_YNLR (0x3 << CC_SELIN_SHIFT) +#define CC_SELIN_WIPER (0x4 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX0 (0x5 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX1 (0x6 << CC_SELIN_SHIFT) +#define CC_SELIN_INAUX2 (0x7 << CC_SELIN_SHIFT) +#define CC_SELIN_MASK (0x7 << CC_SELIN_SHIFT) +#define CC_SELREFP_SHIFT 7 +#define CC_SELREFP_YPLL (0x0 << CC_SELREFP_SHIFT) +#define CC_SELREFP_XPUL (0x1 << CC_SELREFP_SHIFT) +#define CC_SELREFP_EXT (0x2 << CC_SELREFP_SHIFT) +#define CC_SELREFP_INT (0x3 << CC_SELREFP_SHIFT) +#define CC_SELREFP_MASK (0x3 << CC_SELREFP_SHIFT) +#define CC_XPULSW (1 << 9) +#define CC_XNURSW_SHIFT 10 +#define CC_XNURSW_HIGH (0x0 << CC_XNURSW_SHIFT) +#define CC_XNURSW_OFF (0x1 << CC_XNURSW_SHIFT) +#define CC_XNURSW_LOW (0x3 << CC_XNURSW_SHIFT) +#define CC_XNURSW_MASK (0x3 << CC_XNURSW_SHIFT) +#define CC_YPLLSW_SHIFT 12 +#define CC_YPLLSW_MASK (0x3 << CC_YPLLSW_SHIFT) +#define CC_YNLRSW (1 << 14) +#define CC_WIPERSW (1 << 15) +#define CC_NOS_SHIFT 16 +#define CC_YPLLSW_HIGH (0x0 << CC_NOS_SHIFT) +#define CC_YPLLSW_OFF (0x1 << CC_NOS_SHIFT) +#define CC_YPLLSW_LOW (0x3 << CC_NOS_SHIFT +#define CC_NOS_MASK (0xf << CC_NOS_SHIFT) +#define CC_IGS (1 << 20) +#define CC_SETTLING_TIME_SHIFT 24 +#define CC_SETTLING_TIME_MASK (0xff << CC_SETTLING_TIME_SHIFT) + +#define TSC_4WIRE_PRECHARGE 0x158c +#define TSC_4WIRE_TOUCH_DETECT 0x578e + +#define TSC_4WIRE_X_MEASUMENT 0x1c90 +#define TSC_4WIRE_Y_MEASUMENT 0x4604 + +#define TSC_GENERAL_ADC_GCC0 0x17dc +#define TSC_GENERAL_ADC_GCC1 0x17ec +#define TSC_GENERAL_ADC_GCC2 0x17fc + +/* GeneralADC Convert Queue FIFO Register */ +#define GCQFIFO 0x800 +#define GCQFIFO_ADCOUT_SHIFT 4 +#define GCQFIFO_ADCOUT_MASK (0xfff << GCQFIFO_ADCOUT_SHIFT) +/* GeneralADC Convert Queue Control Register */ +#define GCQCR 0x804 +/* GeneralADC Convert Queue Status Register */ +#define GCQSR 0x808 +/* GeneralADC Convert Queue Mask Register */ +#define GCQMR 0x80c + +/* GeneralADC Convert Queue ITEM 7~0 */ +#define GCQ_ITEM_7_0 0x820 +/* GeneralADC Convert Queue ITEM 15~8 */ +#define GCQ_ITEM_15_8 0x824 + +#define GCQ_ITEM7_SHIFT 28 +#define GCQ_ITEM6_SHIFT 24 +#define GCQ_ITEM5_SHIFT 20 +#define GCQ_ITEM4_SHIFT 16 +#define GCQ_ITEM3_SHIFT 12 +#define GCQ_ITEM2_SHIFT 8 +#define GCQ_ITEM1_SHIFT 4 +#define GCQ_ITEM0_SHIFT 0 + +#define GCQ_ITEM_GCC0 0x0 +#define GCQ_ITEM_GCC1 0x1 +#define GCQ_ITEM_GCC2 0x2 +#define GCQ_ITEM_GCC3 0x3 + +/* GeneralADC Convert Config 0-7 */ +#define GCC0 0x840 +#define GCC1 0x844 +#define GCC2 0x848 +#define GCC3 0x84c +#define GCC4 0x850 +#define GCC5 0x854 +#define GCC6 0x858 +#define GCC7 0x85c + +/* TSC Test Register R/W */ +#define TTR 0xc00 +/* TSC Monitor Register 1, 2 */ +#define MNT1 0xc04 +#define MNT2 0xc04 + +#define DETECT_ITEM_ID_1 1 +#define DETECT_ITEM_ID_2 5 +#define TS_X_ITEM_ID 2 +#define TS_Y_ITEM_ID 3 +#define TSI_DATA 1 +#define FQS_DATA 0 + +#endif /* __IMX_ADC_H__ */ diff --git a/drivers/mxc/asrc/Kconfig b/drivers/mxc/asrc/Kconfig new file mode 100644 index 000000000000..a4c66b19f067 --- /dev/null +++ b/drivers/mxc/asrc/Kconfig @@ -0,0 +1,13 @@ +# +# ASRC configuration +# + +menu "MXC Asynchronous Sample Rate Converter support" + +config MXC_ASRC + tristate "ASRC support" + depends on ARCH_MX35 + ---help--- + Say Y to get the ASRC service. + +endmenu diff --git a/drivers/mxc/asrc/Makefile b/drivers/mxc/asrc/Makefile new file mode 100644 index 000000000000..0d2487d389c7 --- /dev/null +++ b/drivers/mxc/asrc/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the kernel Asynchronous Sample Rate Converter driver +# + +ifeq ($(CONFIG_ARCH_MX35),y) + obj-$(CONFIG_MXC_ASRC) += mxc_asrc.o +endif diff --git a/drivers/mxc/asrc/mxc_asrc.c b/drivers/mxc/asrc/mxc_asrc.c new file mode 100644 index 000000000000..62b8472433b6 --- /dev/null +++ b/drivers/mxc/asrc/mxc_asrc.c @@ -0,0 +1,1686 @@ +/* + * Copyright 2008-2009 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 mxc_asrc.c + * + * @brief MXC Asynchronous Sample Rate Converter + * + * @ingroup SOUND + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <linux/pagemap.h> +#include <linux/vmalloc.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/dma-mapping.h> +#include <linux/mxc_asrc.h> +#include <asm/irq.h> +#include <asm/memory.h> +#include <mach/dma.h> + +static int asrc_major; +static struct class *asrc_class; +#define ASRC_PROC_PATH "driver/asrc" + +#define ASRC_RATIO_DECIMAL_DEPTH 26 + +DEFINE_SPINLOCK(data_lock); +DEFINE_SPINLOCK(input_int_lock); +DEFINE_SPINLOCK(output_int_lock); + +#define AICPA 0 /* Input Clock Divider A Offset */ +#define AICDA 3 /* Input Clock Prescaler A Offset */ +#define AICPB 6 /* Input Clock Divider B Offset */ +#define AICDB 9 /* Input Clock Prescaler B Offset */ +#define AOCPA 12 /* Output Clock Divider A Offset */ +#define AOCDA 15 /* Output Clock Prescaler A Offset */ +#define AOCPB 18 /* Output Clock Divider B Offset */ +#define AOCDB 21 /* Output Clock Prescaler B Offset */ +#define AICPC 0 /* Input Clock Divider C Offset */ +#define AICDC 3 /* Input Clock Prescaler C Offset */ +#define AOCDC 6 /* Output Clock Prescaler C Offset */ +#define AOCPC 9 /* Output Clock Divider C Offset */ + +char *asrc_pair_id[] = { + [0] = "ASRC RX PAIR A", + [1] = "ASRC TX PAIR A", + [2] = "ASRC RX PAIR B", + [3] = "ASRC TX PAIR B", + [4] = "ASRC RX PAIR C", + [5] = "ASRC TX PAIR C", +}; + +enum asrc_status { + ASRC_ASRSTR_AIDEA = 0x01, + ASRC_ASRSTR_AIDEB = 0x02, + ASRC_ASRSTR_AIDEC = 0x04, + ASRC_ASRSTR_AODFA = 0x08, + ASRC_ASRSTR_AODFB = 0x10, + ASRC_ASRSTR_AODFC = 0x20, + ASRC_ASRSTR_AOLE = 0x40, + ASRC_ASRSTR_FPWT = 0x80, + ASRC_ASRSTR_AIDUA = 0x100, + ASRC_ASRSTR_AIDUB = 0x200, + ASRC_ASRSTR_AIDUC = 0x400, + ASRC_ASRSTR_AODOA = 0x800, + ASRC_ASRSTR_AODOB = 0x1000, + ASRC_ASRSTR_AODOC = 0x2000, + ASRC_ASRSTR_AIOLA = 0x4000, + ASRC_ASRSTR_AIOLB = 0x8000, + ASRC_ASRSTR_AIOLC = 0x10000, + ASRC_ASRSTR_AOOLA = 0x20000, + ASRC_ASRSTR_AOOLB = 0x40000, + ASRC_ASRSTR_AOOLC = 0x80000, + ASRC_ASRSTR_ATQOL = 0x100000, + ASRC_ASRSTR_DSLCNT = 0x200000, +}; + +/* Sample rates are aligned with that defined in pcm.h file */ +static const unsigned char asrc_process_table[][8][2] = { + /* 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */ +/*5512Hz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*8kHz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*11025Hz*/ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*16kHz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*22050Hz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, +/*32kHz*/ + {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, +/*44.1kHz*/ + {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, +/*48kHz*/ + {{0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, +/*64kHz*/ + {{1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, +/*88.2kHz*/ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, +/*96kHz*/ + {{1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, +/*176kHz*/ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, +/*192kHz*/ + {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, +}; + +static const unsigned char asrc_divider_table[] = { +/*5500Hz 8kHz 11025Hz 16kHz 22050kHz 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176400Hz 192kHz*/ + 0x07, 0x15, 0x06, 0x14, 0x05, 0x13, 0x04, 0x04, 0x12, 0x03, 0x03, 0x02, + 0x02, +}; + +static struct asrc_data *g_asrc_data; +static struct proc_dir_entry *proc_asrc; +static unsigned long asrc_vrt_base_addr; +static struct mxc_asrc_platform_data *mxc_asrc_data; + +static int asrc_set_clock_ratio(enum asrc_pair_index index, + int input_sample_rate, int output_sample_rate) +{ + int i; + int integ = 0; + unsigned long reg_val = 0; + + if (output_sample_rate == 0) + return -1; + while (input_sample_rate >= output_sample_rate) { + input_sample_rate -= output_sample_rate; + integ++; + } + reg_val |= (integ << 26); + + for (i = 1; i <= ASRC_RATIO_DECIMAL_DEPTH; i++) { + if ((input_sample_rate * 2) >= output_sample_rate) { + reg_val |= (1 << (ASRC_RATIO_DECIMAL_DEPTH - i)); + input_sample_rate = + input_sample_rate * 2 - output_sample_rate; + } else + input_sample_rate = input_sample_rate << 1; + + if (input_sample_rate == 0) + break; + } + + __raw_writel(reg_val, + (asrc_vrt_base_addr + ASRC_ASRIDRLA_REG + (index << 3))); + __raw_writel((reg_val >> 24), + (asrc_vrt_base_addr + ASRC_ASRIDRHA_REG + (index << 3))); + return 0; +} + +static int asrc_set_process_configuration(enum asrc_pair_index index, + int input_sample_rate, + int output_sample_rate) +{ + int i = 0, j = 0; + unsigned long reg; + switch (input_sample_rate) { + case 5512: + i = 0; + case 8000: + i = 1; + break; + case 11025: + i = 2; + break; + case 16000: + i = 3; + break; + case 22050: + i = 4; + break; + case 32000: + i = 5; + break; + case 44100: + i = 6; + break; + case 48000: + i = 7; + break; + case 64000: + i = 8; + break; + case 88200: + i = 9; + break; + case 96000: + i = 10; + break; + case 176400: + i = 11; + break; + case 192000: + i = 12; + break; + default: + return -1; + } + + switch (output_sample_rate) { + case 32000: + j = 0; + break; + case 44100: + j = 1; + break; + case 48000: + j = 2; + break; + case 64000: + j = 3; + break; + case 88200: + j = 4; + break; + case 96000: + j = 5; + break; + case 176400: + j = 6; + break; + case 192000: + j = 7; + break; + default: + return -1; + } + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + reg &= ~(0x0f << (6 + (index << 2))); + reg |= + ((asrc_process_table[i][j][0] << (6 + (index << 2))) | + (asrc_process_table[i][j][1] << (8 + (index << 2)))); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCFG_REG); + + return 0; +} + +static int asrc_get_asrck_clock_divider(int sample_rate) +{ + int i = 0; + switch (sample_rate) { + case 5500: + i = 0; + break; + case 8000: + i = 1; + break; + case 11025: + i = 2; + break; + case 16000: + i = 3; + break; + case 22050: + i = 4; + break; + case 32000: + i = 5; + break; + case 44100: + i = 6; + break; + case 48000: + i = 7; + break; + case 64000: + i = 8; + break; + case 88200: + i = 9; + break; + case 96000: + i = 10; + break; + case 176400: + i = 11; + break; + case 192000: + i = 12; + break; + default: + return -1; + } + + return asrc_divider_table[i]; +} + +int asrc_req_pair(int chn_num, enum asrc_pair_index *index) +{ + int err = 0; + unsigned long lock_flags; + spin_lock_irqsave(&data_lock, lock_flags); + + if (chn_num > 2) { + if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active + || (chn_num > g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_max)) + err = -EBUSY; + else { + *index = ASRC_PAIR_C; + g_asrc_data->asrc_pair[ASRC_PAIR_C].chn_num = chn_num; + g_asrc_data->asrc_pair[ASRC_PAIR_C].active = 1; + } + } else { + if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active || + (g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_max == 0)) { + if (g_asrc_data->asrc_pair[ASRC_PAIR_B]. + active + || (g_asrc_data->asrc_pair[ASRC_PAIR_B]. + chn_max == 0)) + err = -EBUSY; + else { + *index = ASRC_PAIR_B; + g_asrc_data->asrc_pair[ASRC_PAIR_B].chn_num = 2; + g_asrc_data->asrc_pair[ASRC_PAIR_B].active = 1; + } + } else { + *index = ASRC_PAIR_A; + g_asrc_data->asrc_pair[ASRC_PAIR_A].chn_num = 2; + g_asrc_data->asrc_pair[ASRC_PAIR_A].active = 1; + } + } + spin_unlock_irqrestore(&data_lock, lock_flags); + return err; +} + +EXPORT_SYMBOL(asrc_req_pair); + +void asrc_release_pair(enum asrc_pair_index index) +{ + unsigned long reg; + unsigned long lock_flags; + + spin_lock_irqsave(&data_lock, lock_flags); + g_asrc_data->asrc_pair[index].active = 0; + g_asrc_data->asrc_pair[index].overload_error = 0; + /********Disable PAIR*************/ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (index + 1)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + spin_unlock_irqrestore(&data_lock, lock_flags); +} + +EXPORT_SYMBOL(asrc_release_pair); + +int asrc_config_pair(struct asrc_config *config) +{ + int err = 0; + int reg, tmp, channel_num; + unsigned long lock_flags; + /* Set the channel number */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + spin_lock_irqsave(&data_lock, lock_flags); + g_asrc_data->asrc_pair[config->pair].chn_num = config->channel_num; + spin_unlock_irqrestore(&data_lock, lock_flags); + reg &= + ~((0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits)) << + (mxc_asrc_data->channel_bits * config->pair)); + if (mxc_asrc_data->channel_bits > 3) + channel_num = config->channel_num; + else + channel_num = (config->channel_num + 1) / 2; + tmp = channel_num << (mxc_asrc_data->channel_bits * config->pair); + reg |= tmp; + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + /* Set the clock source */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCSR_REG); + tmp = ~(0x0f << (config->pair << 2)); + reg &= tmp; + tmp = ~(0x0f << (12 + (config->pair << 2))); + reg &= tmp; + reg |= + ((config->inclk << (config->pair << 2)) | (config-> + outclk << (12 + + (config-> + pair << + 2)))); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCSR_REG); + + /* default setting */ + /* automatic selection for processing mode */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg |= (1 << (20 + config->pair)); + reg &= ~(1 << (14 + (config->pair << 1))); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRRA_REG); + reg &= 0xffbfffff; + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRRA_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg = reg & (~(1 << 23)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + /* Default Clock Divider Setting */ + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + if (config->pair == ASRC_PAIR_A) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + reg &= 0xfc0fc0; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX + || (config->inclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AICPA; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPA; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPA; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPA; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == OUTCLK_SPDIF_RX + || (config->outclk & 0x0f) == OUTCLK_SPDIF_TX) { + reg |= 7 << AOCPA; + } else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPA; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPA; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPA; + else + err = -EFAULT; + } + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + + } else if (config->pair == ASRC_PAIR_B) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + reg &= 0x03f03f; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX + || (config->inclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AICPB; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPB; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPB; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPB; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == INCLK_SPDIF_RX + || (config->outclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AOCPB; + } else if ((config->outclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPB; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPB; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPB; + else + err = -EFAULT; + } + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR1_REG); + + } else { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCDR2_REG); + reg &= 0; + /* Input Part */ + if ((config->inclk & 0x0f) == INCLK_SPDIF_RX + || (config->inclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AICPC; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + input_sample_rate); + reg |= tmp << AICPC; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AICPC; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AICPC; + else + err = -EFAULT; + } + /* Output Part */ + if ((config->outclk & 0x0f) == INCLK_SPDIF_RX + || (config->outclk & 0x0f) == INCLK_SPDIF_TX) { + reg |= 7 << AOCPC; + } else if ((config->outclk & 0x0f) == INCLK_ASRCK1_CLK) { + tmp = + asrc_get_asrck_clock_divider(config-> + output_sample_rate); + reg |= tmp << AOCPC; + } else { + if (config->word_width == 16 || config->word_width == 8) + reg |= 5 << AOCPC; + else if (config->word_width == 32 + || config->word_width == 24) + reg |= 6 << AOCPC; + else + err = -EFAULT; + } + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCDR2_REG); + + } + + /* check whether ideal ratio is a must */ + if ((config->inclk & 0x0f) == INCLK_NONE) { + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (20 + config->pair)); + reg |= (0x03 << (13 + (config->pair << 1))); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + err = asrc_set_clock_ratio(config->pair, + config->input_sample_rate, + config->output_sample_rate); + if (err < 0) + return err; + + err = asrc_set_process_configuration(config->pair, + config->input_sample_rate, + config-> + output_sample_rate); + if (err < 0) + return err; + } else if ((config->inclk & 0x0f) == INCLK_ASRCK1_CLK) { + if (config->input_sample_rate == 44100 + || config->input_sample_rate == 88200) { + pr_info + ("ASRC core clock cann't support sample rate %d\n", + config->input_sample_rate); + err = -EFAULT; + } + } else if ((config->outclk & 0x0f) == OUTCLK_ASRCK1_CLK) { + if (config->output_sample_rate == 44100 + || config->output_sample_rate == 88200) { + pr_info + ("ASRC core clock cann't support sample rate %d\n", + config->input_sample_rate); + err = -EFAULT; + } + } + + return err; +} + +EXPORT_SYMBOL(asrc_config_pair); + +void asrc_start_conv(enum asrc_pair_index index) +{ + int reg, reg_1; + unsigned long lock_flags; + int i; + + spin_lock_irqsave(&data_lock, lock_flags); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + if ((reg & 0x0E) == 0) + clk_enable(mxc_asrc_data->asrc_audio_clk); + reg |= (1 << (1 + index)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + while (!(reg & (1 << (index + 21)))) + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCFG_REG); + reg_1 = __raw_readl(asrc_vrt_base_addr + ASRC_ASRSTR_REG); + + reg = 0; + for (i = 0; i < 20; i++) { + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + __raw_writel(reg, + asrc_vrt_base_addr + ASRC_ASRDIA_REG + + (index << 3)); + } + + __raw_writel(0x40, asrc_vrt_base_addr + ASRC_ASRIER_REG); + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_start_conv); + +void asrc_stop_conv(enum asrc_pair_index index) +{ + int reg; + unsigned long lock_flags; + spin_lock_irqsave(&data_lock, lock_flags); + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCTR_REG); + reg &= ~(1 << (1 + index)); + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + if ((reg & 0x0E) == 0) + clk_disable(mxc_asrc_data->asrc_audio_clk); + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_stop_conv); + +/*! + * @brief asrc interrupt handler + */ +static irqreturn_t asrc_isr(int irq, void *dev_id) +{ + unsigned long status; + int reg = 0x40; + + status = __raw_readl(asrc_vrt_base_addr + ASRC_ASRSTR_REG); + if (g_asrc_data->asrc_pair[ASRC_PAIR_A].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUA) + g_asrc_data->asrc_pair[ASRC_PAIR_A].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } else if (g_asrc_data->asrc_pair[ASRC_PAIR_B].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUB) + g_asrc_data->asrc_pair[ASRC_PAIR_B].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } else if (g_asrc_data->asrc_pair[ASRC_PAIR_C].active == 1) { + if (status & ASRC_ASRSTR_ATQOL) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_TASK_Q_OVERLOAD; + if (status & ASRC_ASRSTR_AOOLC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_OUTPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AIOLC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_INPUT_TASK_OVERLOAD; + if (status & ASRC_ASRSTR_AODOC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_OUTPUT_BUFFER_OVERFLOW; + if (status & ASRC_ASRSTR_AIDUC) + g_asrc_data->asrc_pair[ASRC_PAIR_C].overload_error |= + ASRC_INPUT_BUFFER_UNDERRUN; + } + + /* try to clean the overload error */ + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRSTR_REG); + + return IRQ_HANDLED; +} + +void asrc_get_status(struct asrc_status_flags *flags) +{ + unsigned long lock_flags; + enum asrc_pair_index index; + + spin_lock_irqsave(&data_lock, lock_flags); + index = flags->index; + flags->overload_error = g_asrc_data->asrc_pair[index].overload_error; + + spin_unlock_irqrestore(&data_lock, lock_flags); + return; +} + +EXPORT_SYMBOL(asrc_get_status); + +static int mxc_init_asrc(void) +{ + /* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */ + __raw_writel(0x0001, asrc_vrt_base_addr + ASRC_ASRCTR_REG); + + /* Enable overflow interrupt */ + __raw_writel(0x00, asrc_vrt_base_addr + ASRC_ASRIER_REG); + + /* Default 6: 2: 2 channel assignment */ + __raw_writel((0x06 << mxc_asrc_data->channel_bits * + 2) | (0x02 << mxc_asrc_data->channel_bits) | 0x02, + asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + /* Parameter Registers recommended settings */ + __raw_writel(0x7fffff, asrc_vrt_base_addr + ASRC_ASRPM1_REG); + __raw_writel(0x255555, asrc_vrt_base_addr + ASRC_ASRPM2_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM3_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM4_REG); + __raw_writel(0xff7280, asrc_vrt_base_addr + ASRC_ASRPM5_REG); + + __raw_writel(0x001f00, asrc_vrt_base_addr + ASRC_ASRTFR1); + + /* Set the processing clock for 76KHz, 133M */ + __raw_writel(0x06D6, asrc_vrt_base_addr + ASRC_ASR76K_REG); + + /* Set the processing clock for 56KHz, 133M */ + __raw_writel(0x0947, asrc_vrt_base_addr + ASRC_ASR56K_REG); + + if (request_irq(MXC_INT_ASRC, asrc_isr, 0, "asrc", NULL)) + return -1; + + return 0; +} + +static int asrc_get_output_buffer_size(int input_buffer_size, + int input_sample_rate, + int output_sample_rate) +{ + int i = 0; + int outbuffer_size = 0; + int outsample = output_sample_rate; + while (outsample >= input_sample_rate) { + ++i; + outsample -= input_sample_rate; + } + outbuffer_size = i * input_buffer_size; + i = 1; + while (((input_buffer_size >> i) > 2) && (outsample != 0)) { + if (((outsample << 1) - input_sample_rate) >= 0) { + outsample = (outsample << 1) - input_sample_rate; + outbuffer_size += (input_buffer_size >> i); + } else { + outsample = outsample << 1; + } + i++; + } + outbuffer_size = (outbuffer_size >> 3) << 3; + return outbuffer_size; +} + +static void asrc_input_dma_callback(void *data, int error, unsigned int count) +{ + struct asrc_pair_params *params; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + + params = data; + + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_queue_empty--; + if (!list_empty(¶ms->input_queue)) { + block = + list_entry(params->input_queue.next, + struct dma_block, queue); + dma_request.src_addr = (dma_addr_t) block->dma_paddr; + dma_request.dst_addr = + (ASRC_BASE_ADDR + ASRC_ASRDIA_REG + (params->index << 3)); + dma_request.num_of_bytes = block->length; + mxc_dma_config(params->input_dma_channel, &dma_request, + 1, MXC_DMA_MODE_WRITE); + list_del(params->input_queue.next); + list_add_tail(&block->queue, ¶ms->input_done_queue); + params->input_queue_empty++; + } + params->input_counter++; + wake_up_interruptible(¶ms->input_wait_queue); + spin_unlock_irqrestore(&input_int_lock, lock_flags); + return; +} + +static void asrc_output_dma_callback(void *data, int error, unsigned int count) +{ + struct asrc_pair_params *params; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + + params = data; + + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_queue_empty--; + + if (!list_empty(¶ms->output_queue)) { + block = + list_entry(params->output_queue.next, + struct dma_block, queue); + dma_request.src_addr = + (ASRC_BASE_ADDR + ASRC_ASRDOA_REG + (params->index << 3)); + dma_request.dst_addr = (dma_addr_t) block->dma_paddr; + dma_request.num_of_bytes = block->length; + mxc_dma_config(params->output_dma_channel, &dma_request, + 1, MXC_DMA_MODE_READ); + list_del(params->output_queue.next); + list_add_tail(&block->queue, ¶ms->output_done_queue); + params->output_queue_empty++; + } + params->output_counter++; + wake_up_interruptible(¶ms->output_wait_queue); + spin_unlock_irqrestore(&output_int_lock, lock_flags); + return; +} + +static void mxc_free_dma_buf(struct asrc_pair_params *params) +{ + int i; + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + if (params->input_dma[i].dma_vaddr != NULL) { + dma_free_coherent(0, + params->input_buffer_size, + params->input_dma[i]. + dma_vaddr, + params->input_dma[i].dma_paddr); + params->input_dma[i].dma_vaddr = NULL; + } + if (params->output_dma[i].dma_vaddr != NULL) { + dma_free_coherent(0, + params->output_buffer_size, + params->output_dma[i]. + dma_vaddr, + params->output_dma[i].dma_paddr); + params->output_dma[i].dma_vaddr = NULL; + } + } + + return; +} + +static int mxc_allocate_dma_buf(struct asrc_pair_params *params) +{ + int i; + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + params->input_dma[i].dma_vaddr = + dma_alloc_coherent(0, params->input_buffer_size, + ¶ms->input_dma[i].dma_paddr, + GFP_DMA | GFP_KERNEL); + if (params->input_dma[i].dma_vaddr == NULL) { + mxc_free_dma_buf(params); + pr_info("can't allocate buff\n"); + return -ENOBUFS; + } + } + for (i = 0; i < ASRC_DMA_BUFFER_NUM; i++) { + params->output_dma[i].dma_vaddr = + dma_alloc_coherent(0, + params->output_buffer_size, + ¶ms->output_dma[i].dma_paddr, + GFP_DMA | GFP_KERNEL); + if (params->output_dma[i].dma_vaddr == NULL) { + mxc_free_dma_buf(params); + return -ENOBUFS; + } + } + + return 0; +} + +/*! + * asrc interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param cmd unsigned int + * + * @param arg unsigned long + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int asrc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct asrc_pair_params *params; + params = file->private_data; + + if (down_interruptible(¶ms->busy_lock)) + return -EBUSY; + switch (cmd) { + case ASRC_REQ_PAIR: + { + struct asrc_req req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct asrc_req))) { + err = -EFAULT; + break; + } + err = asrc_req_pair(req.chn_num, &req.index); + if (err < 0) + break; + params->pair_hold = 1; + params->index = req.index; + if (copy_to_user + ((void __user *)arg, &req, sizeof(struct asrc_req))) + err = -EFAULT; + + break; + } + case ASRC_CONFIG_PAIR: + { + struct asrc_config config; + mxc_dma_device_t rx_id, tx_id; + char *rx_name, *tx_name; + int channel = -1; + if (copy_from_user + (&config, (void __user *)arg, + sizeof(struct asrc_config))) { + err = -EFAULT; + break; + } + err = asrc_config_pair(&config); + if (err < 0) + break; + params->output_buffer_size = + asrc_get_output_buffer_size(config. + dma_buffer_size, + config. + input_sample_rate, + config. + output_sample_rate); + params->input_buffer_size = config.dma_buffer_size; + if (config.buffer_num > ASRC_DMA_BUFFER_NUM) + params->buffer_num = ASRC_DMA_BUFFER_NUM; + else + params->buffer_num = config.buffer_num; + err = mxc_allocate_dma_buf(params); + if (err < 0) + break; + + /* TBD - need to update when new SDMA interface ready */ + if (config.pair == ASRC_PAIR_A) { + rx_id = MXC_DMA_ASRC_A_RX; + tx_id = MXC_DMA_ASRC_A_TX; + rx_name = asrc_pair_id[0]; + tx_name = asrc_pair_id[1]; + } else if (config.pair == ASRC_PAIR_B) { + rx_id = MXC_DMA_ASRC_B_RX; + tx_id = MXC_DMA_ASRC_B_TX; + rx_name = asrc_pair_id[2]; + tx_name = asrc_pair_id[3]; + } else { + rx_id = MXC_DMA_ASRC_C_RX; + tx_id = MXC_DMA_ASRC_C_TX; + rx_name = asrc_pair_id[4]; + tx_name = asrc_pair_id[5]; + } + channel = mxc_dma_request(rx_id, rx_name); + params->input_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_input_dma_callback, + (void *)params); + channel = mxc_dma_request(tx_id, tx_name); + params->output_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_output_dma_callback, + (void *)params); + /* TBD - need to update when new SDMA interface ready */ + params->input_queue_empty = 0; + params->output_queue_empty = 0; + INIT_LIST_HEAD(¶ms->input_queue); + INIT_LIST_HEAD(¶ms->input_done_queue); + INIT_LIST_HEAD(¶ms->output_queue); + INIT_LIST_HEAD(¶ms->output_done_queue); + init_waitqueue_head(¶ms->input_wait_queue); + init_waitqueue_head(¶ms->output_wait_queue); + + if (copy_to_user + ((void __user *)arg, &config, + sizeof(struct asrc_config))) + err = -EFAULT; + break; + } + case ASRC_QUERYBUF: + { + struct asrc_querybuf buffer; + if (copy_from_user + (&buffer, (void __user *)arg, + sizeof(struct asrc_querybuf))) { + err = -EFAULT; + break; + } + buffer.input_offset = + (unsigned long)params->input_dma[buffer. + buffer_index]. + dma_paddr; + buffer.input_length = params->input_buffer_size; + buffer.output_offset = + (unsigned long)params->output_dma[buffer. + buffer_index]. + dma_paddr; + buffer.output_length = params->output_buffer_size; + if (copy_to_user + ((void __user *)arg, &buffer, + sizeof(struct asrc_querybuf))) + err = -EFAULT; + break; + } + case ASRC_RELEASE_PAIR: + { + enum asrc_pair_index index; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + + mxc_dma_free(params->input_dma_channel); + mxc_dma_free(params->output_dma_channel); + mxc_free_dma_buf(params); + asrc_release_pair(index); + params->pair_hold = 0; + break; + } + case ASRC_Q_INBUF: + { + struct asrc_buffer buf; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_dma[buf.index].index = buf.index; + params->input_dma[buf.index].length = buf.length; + list_add_tail(¶ms->input_dma[buf.index]. + queue, ¶ms->input_queue); + if (params->asrc_active == 0 + || params->input_queue_empty == 0) { + block = + list_entry(params->input_queue.next, + struct dma_block, queue); + dma_request.src_addr = + (dma_addr_t) block->dma_paddr; + dma_request.dst_addr = + (ASRC_BASE_ADDR + ASRC_ASRDIA_REG + + (params->index << 3)); + dma_request.num_of_bytes = block->length; + mxc_dma_config(params-> + input_dma_channel, + &dma_request, 1, + MXC_DMA_MODE_WRITE); + params->input_queue_empty++; + list_del(params->input_queue.next); + list_add_tail(&block->queue, + ¶ms->input_done_queue); + } + + spin_unlock_irqrestore(&input_int_lock, lock_flags); + break; + } + case ASRC_DQ_INBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + /* if ASRC is inactive, nonsense to DQ buffer */ + if (params->asrc_active == 0) { + err = -EFAULT; + buf.buf_valid = ASRC_BUF_NA; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + break; + } + + if (!wait_event_interruptible_timeout + (params->input_wait_queue, + params->input_counter != 0, 10 * HZ)) { + pr_info + ("ASRC_DQ_INBUF timeout counter %x\n", + params->input_counter); + err = -ETIME; + break; + } else if (signal_pending(current)) { + pr_info("ASRC_DQ_INBUF interrupt received\n"); + err = -ERESTARTSYS; + break; + } + spin_lock_irqsave(&input_int_lock, lock_flags); + params->input_counter--; + block = + list_entry(params->input_done_queue.next, + struct dma_block, queue); + list_del(params->input_done_queue.next); + spin_unlock_irqrestore(&input_int_lock, lock_flags); + buf.index = block->index; + buf.length = block->length; + buf.buf_valid = ASRC_BUF_AV; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + + break; + } + case ASRC_Q_OUTBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + mxc_dma_requestbuf_t dma_request; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_dma[buf.index].index = buf.index; + params->output_dma[buf.index].length = buf.length; + list_add_tail(¶ms->output_dma[buf.index]. + queue, ¶ms->output_queue); + if (params->asrc_active == 0 + || params->output_queue_empty == 0) { + block = + list_entry(params->output_queue. + next, struct dma_block, queue); + dma_request.src_addr = + (ASRC_BASE_ADDR + ASRC_ASRDOA_REG + + (params->index << 3)); + dma_request.dst_addr = + (dma_addr_t) block->dma_paddr; + dma_request.num_of_bytes = block->length; + mxc_dma_config(params-> + output_dma_channel, + &dma_request, 1, + MXC_DMA_MODE_READ); + list_del(params->output_queue.next); + list_add_tail(&block->queue, + ¶ms->output_done_queue); + params->output_queue_empty++; + } + + spin_unlock_irqrestore(&output_int_lock, lock_flags); + break; + } + case ASRC_DQ_OUTBUF:{ + struct asrc_buffer buf; + struct dma_block *block; + unsigned long lock_flags; + if (copy_from_user + (&buf, (void __user *)arg, + sizeof(struct asrc_buffer))) { + err = -EFAULT; + break; + } + /* if ASRC is inactive, nonsense to DQ buffer */ + if (params->asrc_active == 0) { + buf.buf_valid = ASRC_BUF_NA; + err = -EFAULT; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + break; + } + + if (!wait_event_interruptible_timeout + (params->output_wait_queue, + params->output_counter != 0, 10 * HZ)) { + pr_info + ("ASRC_DQ_OUTBUF timeout counter %x\n", + params->output_counter); + err = -ETIME; + break; + } else if (signal_pending(current)) { + pr_info("ASRC_DQ_INBUF interrupt received\n"); + err = -ERESTARTSYS; + break; + } + spin_lock_irqsave(&output_int_lock, lock_flags); + params->output_counter--; + block = + list_entry(params->output_done_queue.next, + struct dma_block, queue); + list_del(params->output_done_queue.next); + spin_unlock_irqrestore(&output_int_lock, lock_flags); + buf.index = block->index; + buf.length = block->length; + buf.buf_valid = ASRC_BUF_AV; + if (copy_to_user + ((void __user *)arg, &buf, + sizeof(struct asrc_buffer))) + err = -EFAULT; + + break; + } + case ASRC_START_CONV:{ + enum asrc_pair_index index; + unsigned long lock_flags; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + + spin_lock_irqsave(&input_int_lock, lock_flags); + if (params->input_queue_empty == 0) { + err = -EFAULT; + pr_info + ("ASRC_START_CONV - no block available\n"); + break; + } + spin_unlock_irqrestore(&input_int_lock, lock_flags); + params->asrc_active = 1; + + asrc_start_conv(index); + mxc_dma_enable(params->input_dma_channel); + mxc_dma_enable(params->output_dma_channel); + break; + } + case ASRC_STOP_CONV:{ + enum asrc_pair_index index; + if (copy_from_user + (&index, (void __user *)arg, + sizeof(enum asrc_pair_index))) { + err = -EFAULT; + break; + } + mxc_dma_disable(params->input_dma_channel); + mxc_dma_disable(params->output_dma_channel); + asrc_stop_conv(index); + params->asrc_active = 0; + break; + } + case ASRC_STATUS:{ + struct asrc_status_flags flags; + if (copy_from_user + (&flags, (void __user *)arg, + sizeof(struct asrc_status_flags))) { + err = -EFAULT; + break; + } + asrc_get_status(&flags); + if (copy_to_user + ((void __user *)arg, &flags, + sizeof(struct asrc_status_flags))) + err = -EFAULT; + break; + } + case ASRC_FLUSH:{ + /* flush input dma buffer */ + unsigned long lock_flags; + mxc_dma_device_t rx_id, tx_id; + char *rx_name, *tx_name; + int channel = -1; + spin_lock_irqsave(&input_int_lock, lock_flags); + while (!list_empty(¶ms->input_queue)) + list_del(params->input_queue.next); + while (!list_empty(¶ms->input_done_queue)) + list_del(params->input_done_queue.next); + params->input_counter = 0; + params->input_queue_empty = 0; + spin_unlock_irqrestore(&input_int_lock, lock_flags); + + /* flush output dma buffer */ + spin_lock_irqsave(&output_int_lock, lock_flags); + while (!list_empty(¶ms->output_queue)) + list_del(params->output_queue.next); + while (!list_empty(¶ms->output_done_queue)) + list_del(params->output_done_queue.next); + params->output_counter = 0; + params->output_queue_empty = 0; + spin_unlock_irqrestore(&output_int_lock, lock_flags); + + /* release DMA and request again */ + mxc_dma_free(params->input_dma_channel); + mxc_dma_free(params->output_dma_channel); + if (params->index == ASRC_PAIR_A) { + rx_id = MXC_DMA_ASRC_A_RX; + tx_id = MXC_DMA_ASRC_A_TX; + rx_name = asrc_pair_id[0]; + tx_name = asrc_pair_id[1]; + } else if (params->index == ASRC_PAIR_B) { + rx_id = MXC_DMA_ASRC_B_RX; + tx_id = MXC_DMA_ASRC_B_TX; + rx_name = asrc_pair_id[2]; + tx_name = asrc_pair_id[3]; + } else { + rx_id = MXC_DMA_ASRC_C_RX; + tx_id = MXC_DMA_ASRC_C_TX; + rx_name = asrc_pair_id[4]; + tx_name = asrc_pair_id[5]; + } + channel = mxc_dma_request(rx_id, rx_name); + params->input_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_input_dma_callback, + (void *)params); + channel = mxc_dma_request(tx_id, tx_name); + params->output_dma_channel = channel; + err = mxc_dma_callback_set(channel, (mxc_dma_callback_t) + asrc_output_dma_callback, + (void *)params); + + break; + } + default: + break; + } + + up(¶ms->busy_lock); + return err; +} + +/*! + * asrc interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENOBUFS failed to allocate buffer, ERESTARTSYS interrupted by user + */ +static int mxc_asrc_open(struct inode *inode, struct file *file) +{ + int err = 0; + struct asrc_pair_params *pair_params; + if (signal_pending(current)) + return -EINTR; + pair_params = kzalloc(sizeof(struct asrc_pair_params), GFP_KERNEL); + if (pair_params == NULL) { + pr_debug("Failed to allocate pair_params\n"); + err = -ENOBUFS; + } + + init_MUTEX(&pair_params->busy_lock); + file->private_data = pair_params; + return err; +} + +/*! + * asrc interface - close function + * + * @param inode struct inode * + * @param file structure file * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_asrc_close(struct inode *inode, struct file *file) +{ + struct asrc_pair_params *pair_params; + pair_params = file->private_data; + if (pair_params->asrc_active == 1) { + mxc_dma_disable(pair_params->input_dma_channel); + mxc_dma_disable(pair_params->output_dma_channel); + asrc_stop_conv(pair_params->index); + wake_up_interruptible(&pair_params->input_wait_queue); + wake_up_interruptible(&pair_params->output_wait_queue); + } + if (pair_params->pair_hold == 1) { + mxc_dma_free(pair_params->input_dma_channel); + mxc_dma_free(pair_params->output_dma_channel); + mxc_free_dma_buf(pair_params); + asrc_release_pair(pair_params->index); + } + kfree(pair_params); + file->private_data = NULL; + return 0; +} + +/*! + * asrc interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error + */ +static int mxc_asrc_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size; + int res = 0; + size = vma->vm_end - vma->vm_start; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) + return -ENOBUFS; + + vma->vm_flags &= ~VM_IO; + return res; +} + +static struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .ioctl = asrc_ioctl, + .mmap = mxc_asrc_mmap, + .open = mxc_asrc_open, + .release = mxc_asrc_close, +}; + +static int asrc_read_proc_attr(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + unsigned long reg; + int len = 0; + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + len += sprintf(page, "ANCA: %d\n", + (int)(reg & + (0xFFFFFFFF >> + (32 - mxc_asrc_data->channel_bits)))); + len += + sprintf(page + len, "ANCB: %d\n", + (int)((reg >> mxc_asrc_data-> + channel_bits) & (0xFFFFFFFF >> (32 - + mxc_asrc_data-> + channel_bits)))); + len += + sprintf(page + len, "ANCC: %d\n", + (int)((reg >> (mxc_asrc_data->channel_bits * 2)) & + (0xFFFFFFFF >> (32 - mxc_asrc_data->channel_bits)))); + + if (off > len) + return 0; + + *eof = (len <= count) ? 1 : 0; + *start = page + off; + + return min(count, len - (int)off); +} + +static int asrc_write_proc_attr(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + char buf[50]; + unsigned long reg; + int na, nb, nc; + int total; + if (count > 48) + return -EINVAL; + if (copy_from_user(buf, buffer, count)) { + pr_debug("Attr proc write, Failed to copy buffer from user\n"); + return -EFAULT; + } + + reg = __raw_readl(asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + sscanf(buf, "ANCA: %d\nANCB: %d\nANCC: %d", &na, &nb, &nc); + if (mxc_asrc_data->channel_bits > 3) + total = 10; + else + total = 5; + if ((na + nb + nc) != total) { + pr_info("Wrong ASRCNR settings\n"); + return -EFAULT; + } + reg = na | (nb << mxc_asrc_data-> + channel_bits) | (nc << (mxc_asrc_data->channel_bits * 2)); + + __raw_writel(reg, asrc_vrt_base_addr + ASRC_ASRCNCR_REG); + + return count; +} + +static void asrc_proc_create(void) +{ + struct proc_dir_entry *proc_attr; + proc_asrc = proc_mkdir(ASRC_PROC_PATH, NULL); + if (proc_asrc) { + proc_attr = create_proc_entry("ChSettings", + S_IFREG | S_IRUGO | + S_IWUSR, proc_asrc); + if (proc_attr) { + proc_attr->read_proc = asrc_read_proc_attr; + proc_attr->write_proc = asrc_write_proc_attr; + proc_attr->size = 48; + proc_attr->uid = proc_attr->gid = 0; + } else { + remove_proc_entry(ASRC_PROC_PATH, NULL); + pr_info("Failed to create proc attribute entry \n"); + } + } else { + pr_info("ASRC: Failed to create proc entry %s\n", + ASRC_PROC_PATH); + } +} + +/*! + * Entry point for the asrc device + * + * @param pdev Pionter to the registered platform device + * @return Error code indicating success or failure + */ +static int mxc_asrc_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct device *temp_class; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + g_asrc_data = kzalloc(sizeof(struct asrc_data), GFP_KERNEL); + + if (g_asrc_data == NULL) { + pr_info("Failed to allocate g_asrc_data\n"); + return -ENOMEM; + } + + g_asrc_data->asrc_pair[0].chn_max = 2; + g_asrc_data->asrc_pair[1].chn_max = 2; + g_asrc_data->asrc_pair[2].chn_max = 6; + g_asrc_data->asrc_pair[0].overload_error = 0; + g_asrc_data->asrc_pair[1].overload_error = 0; + g_asrc_data->asrc_pair[2].overload_error = 0; + + asrc_major = register_chrdev(asrc_major, "mxc_asrc", &asrc_fops); + if (asrc_major < 0) { + pr_info("Unable to register asrc device\n"); + err = -EBUSY; + goto error; + } + + asrc_class = class_create(THIS_MODULE, "mxc_asrc"); + if (IS_ERR(asrc_class)) { + err = PTR_ERR(asrc_class); + goto err_out_chrdev; + } + + temp_class = device_create(asrc_class, NULL, MKDEV(asrc_major, 0), + NULL, "mxc_asrc"); + if (IS_ERR(temp_class)) { + err = PTR_ERR(temp_class); + goto err_out_class; + } + + asrc_vrt_base_addr = + (unsigned long)ioremap(res->start, res->end - res->start + 1); + + mxc_asrc_data = + (struct mxc_asrc_platform_data *)pdev->dev.platform_data; + clk_enable(mxc_asrc_data->asrc_core_clk); + + asrc_proc_create(); + err = mxc_init_asrc(); + if (err < 0) + goto err_out_class; + + goto out; + + err_out_class: + clk_disable(mxc_asrc_data->asrc_core_clk); + device_destroy(asrc_class, MKDEV(asrc_major, 0)); + class_destroy(asrc_class); + err_out_chrdev: + unregister_chrdev(asrc_major, "mxc_asrc"); + error: + kfree(g_asrc_data); + out: + pr_info("mxc_asrc registered\n"); + return err; +} + +/*! + * Exit asrc device + * + * @param pdev Pionter to the registered platform device + * @return Error code indicating success or failure + */ +static int mxc_asrc_remove(struct platform_device *pdev) +{ + free_irq(MXC_INT_ASRC, NULL); + kfree(g_asrc_data); + clk_disable(mxc_asrc_data->asrc_core_clk); + mxc_asrc_data = NULL; + iounmap((unsigned long __iomem *)asrc_vrt_base_addr); + remove_proc_entry("ChSettings", proc_asrc); + remove_proc_entry(ASRC_PROC_PATH, NULL); + device_destroy(asrc_class, MKDEV(asrc_major, 0)); + class_destroy(asrc_class); + unregister_chrdev(asrc_major, "mxc_asrc"); + return 0; +} + +/*! mxc asrc driver definition + * + */ +static struct platform_driver mxc_asrc_driver = { + .driver = { + .name = "mxc_asrc", + }, + .probe = mxc_asrc_probe, + .remove = mxc_asrc_remove, +}; + +/*! + * Register asrc driver + * + */ +static __init int asrc_init(void) +{ + int ret; + ret = platform_driver_register(&mxc_asrc_driver); + return ret; +} + +/*! + * Exit and free the asrc data + * + */ static void __exit asrc_exit(void) +{ + platform_driver_unregister(&mxc_asrc_driver); + return; +} + +module_init(asrc_init); +module_exit(asrc_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Asynchronous Sample Rate Converter"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/bt/Kconfig b/drivers/mxc/bt/Kconfig new file mode 100644 index 000000000000..9dbfbe57a14f --- /dev/null +++ b/drivers/mxc/bt/Kconfig @@ -0,0 +1,13 @@ +# +# Bluetooth configuration +# + +menu "MXC Bluetooth support" + +config MXC_BLUETOOTH + tristate "MXC Bluetooth support" + depends on MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS + ---help--- + Say Y to get the third party Bluetooth service. + +endmenu diff --git a/drivers/mxc/bt/Makefile b/drivers/mxc/bt/Makefile new file mode 100644 index 000000000000..91bc4cff380e --- /dev/null +++ b/drivers/mxc/bt/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for the kernel Bluetooth power-on/reset +# +obj-$(CONFIG_MXC_BLUETOOTH) += mxc_bt.o diff --git a/drivers/mxc/bt/mxc_bt.c b/drivers/mxc/bt/mxc_bt.c new file mode 100644 index 000000000000..ae08b379ef70 --- /dev/null +++ b/drivers/mxc/bt/mxc_bt.c @@ -0,0 +1,128 @@ +/* + * Copyright 2008-2009 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 mxc_bt.c + * + * @brief MXC Thirty party Bluetooth + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/regulator/consumer.h> +#include <mach/hardware.h> + +static struct regulator *bt_vdd; +static struct regulator *bt_vdd_parent; +static struct regulator *bt_vusb; +static struct regulator *bt_vusb_parent; + +/*! + * This function poweron the bluetooth hardware module + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_bt_probe(struct platform_device *pdev) +{ + struct mxc_bt_platform_data *platform_data; + platform_data = (struct mxc_bt_platform_data *)pdev->dev.platform_data; + if (platform_data->bt_vdd) { + bt_vdd = regulator_get(&pdev->dev, platform_data->bt_vdd); + regulator_enable(bt_vdd); + } + if (platform_data->bt_vdd_parent) { + bt_vdd_parent = + regulator_get(&pdev->dev, platform_data->bt_vdd_parent); + regulator_enable(bt_vdd_parent); + } + if (platform_data->bt_vusb) { + bt_vusb = regulator_get(&pdev->dev, platform_data->bt_vusb); + regulator_enable(bt_vusb); + } + if (platform_data->bt_vusb_parent) { + bt_vusb_parent = + regulator_get(&pdev->dev, platform_data->bt_vusb_parent); + regulator_enable(bt_vusb_parent); + } + + if (platform_data->bt_reset != NULL) + platform_data->bt_reset(); + return 0; + +} + +/*! + * This function poweroff the bluetooth hardware module + * + * @param pdev Pointer to the platform device + * @return 0 on success, -1 otherwise. + */ +static int mxc_bt_remove(struct platform_device *pdev) +{ + struct mxc_bt_platform_data *platform_data; + platform_data = (struct mxc_bt_platform_data *)pdev->dev.platform_data; + if (bt_vdd) { + regulator_disable(bt_vdd); + regulator_put(bt_vdd); + } + if (bt_vdd_parent) { + regulator_disable(bt_vdd_parent); + regulator_put(bt_vdd_parent); + } + if (bt_vusb) { + regulator_disable(bt_vusb); + regulator_put(bt_vusb); + } + if (bt_vusb_parent) { + regulator_disable(bt_vusb_parent); + regulator_put(bt_vusb_parent); + } + return 0; + +} + +static struct platform_driver bluetooth_driver = { + .driver = { + .name = "mxc_bt", + }, + .probe = mxc_bt_probe, + .remove = mxc_bt_remove, +}; + +/*! + * Register bluetooth driver module + * + */ +static __init int bluetooth_init(void) +{ + return platform_driver_register(&bluetooth_driver); +} + +/*! + * Exit and free the bluetooth module + * + */ +static void __exit bluetooth_exit(void) +{ + platform_driver_unregister(&bluetooth_driver); +} + +module_init(bluetooth_init); +module_exit(bluetooth_exit); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC Thirty party Bluetooth"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/dam/Kconfig b/drivers/mxc/dam/Kconfig new file mode 100644 index 000000000000..7b4bee92f648 --- /dev/null +++ b/drivers/mxc/dam/Kconfig @@ -0,0 +1,13 @@ +# +# DAM API configuration +# + +menu "MXC Digital Audio Multiplexer support" + +config MXC_DAM + tristate "DAM support" + depends on ARCH_MXC + ---help--- + Say Y to get the Digital Audio Multiplexer services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/dam/Makefile b/drivers/mxc/dam/Makefile new file mode 100644 index 000000000000..b5afdc143dfa --- /dev/null +++ b/drivers/mxc/dam/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the kernel Digital Audio MUX (DAM) device driver. +# + +ifeq ($(CONFIG_ARCH_MX27),y) + obj-$(CONFIG_MXC_DAM) += dam_v1.o +else + obj-$(CONFIG_MXC_DAM) += dam.o +endif diff --git a/drivers/mxc/dam/dam.c b/drivers/mxc/dam/dam.c new file mode 100644 index 000000000000..532b02f65bb2 --- /dev/null +++ b/drivers/mxc/dam/dam.c @@ -0,0 +1,427 @@ +/* + * Copyright 2004-2009 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 dam.c + * @brief This is the brief documentation for this dam.c file. + * + * This file contains the implementation of the DAM driver main services + * + * @ingroup DAM + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include <mach/hardware.h> + +#define ModifyRegister32(a,b,c) (c = ( ( (c)&(~(a)) ) | (b) )) + +#define DAM_VIRT_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#ifndef _reg_DAM_PTCR1 +#define _reg_DAM_PTCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x00))) +#endif + +#ifndef _reg_DAM_PDCR1 +#define _reg_DAM_PDCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x04))) +#endif + +#ifndef _reg_DAM_PTCR2 +#define _reg_DAM_PTCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x08))) +#endif + +#ifndef _reg_DAM_PDCR2 +#define _reg_DAM_PDCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x0C))) +#endif + +#ifndef _reg_DAM_PTCR3 +#define _reg_DAM_PTCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x10))) +#endif + +#ifndef _reg_DAM_PDCR3 +#define _reg_DAM_PDCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x14))) +#endif + +#ifndef _reg_DAM_PTCR4 +#define _reg_DAM_PTCR4 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x18))) +#endif + +#ifndef _reg_DAM_PDCR4 +#define _reg_DAM_PDCR4 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x1C))) +#endif + +#ifndef _reg_DAM_PTCR5 +#define _reg_DAM_PTCR5 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x20))) +#endif + +#ifndef _reg_DAM_PDCR5 +#define _reg_DAM_PDCR5 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x24))) +#endif + +#ifndef _reg_DAM_PTCR6 +#define _reg_DAM_PTCR6 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x28))) +#endif + +#ifndef _reg_DAM_PDCR6 +#define _reg_DAM_PDCR6 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x2C))) +#endif + +#ifndef _reg_DAM_PTCR7 +#define _reg_DAM_PTCR7 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x30))) +#endif + +#ifndef _reg_DAM_PDCR7 +#define _reg_DAM_PDCR7 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x34))) +#endif + +#ifndef _reg_DAM_CNMCR +#define _reg_DAM_CNMCR (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x38))) +#endif + +#ifndef _reg_DAM_PTCR +#define _reg_DAM_PTCR(a) (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + a*8))) +#endif + +#ifndef _reg_DAM_PDCR +#define _reg_DAM_PDCR(a) (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 4 + a*8))) +#endif + +/*! + * PTCR Registers bit shift definitions + */ +#define dam_synchronous_mode_shift 11 +#define dam_receive_clock_select_shift 12 +#define dam_receive_clock_direction_shift 16 +#define dam_receive_frame_sync_select_shift 17 +#define dam_receive_frame_sync_direction_shift 21 +#define dam_transmit_clock_select_shift 22 +#define dam_transmit_clock_direction_shift 26 +#define dam_transmit_frame_sync_select_shift 27 +#define dam_transmit_frame_sync_direction_shift 31 +#define dam_selection_mask 0xF + +/*! + * HPDCR Register bit shift definitions + */ +#define dam_internal_network_mode_shift 0 +#define dam_mode_shift 8 +#define dam_transmit_receive_switch_shift 12 +#define dam_receive_data_select_shift 13 + +/*! + * HPDCR Register bit masq definitions + */ +#define dam_mode_masq 0x03 +#define dam_internal_network_mode_mask 0xFF + +/*! + * CNMCR Register bit shift definitions + */ +#define dam_ce_bus_port_cntlow_shift 0 +#define dam_ce_bus_port_cnthigh_shift 8 +#define dam_ce_bus_port_clkpol_shift 16 +#define dam_ce_bus_port_fspol_shift 17 +#define dam_ce_bus_port_enable_shift 18 + +#define DAM_NAME "dam" + +EXPORT_SYMBOL(dam_select_mode); +EXPORT_SYMBOL(dam_select_RxClk_direction); +EXPORT_SYMBOL(dam_select_RxClk_source); +EXPORT_SYMBOL(dam_select_RxD_source); +EXPORT_SYMBOL(dam_select_RxFS_direction); +EXPORT_SYMBOL(dam_select_RxFS_source); +EXPORT_SYMBOL(dam_select_TxClk_direction); +EXPORT_SYMBOL(dam_select_TxClk_source); +EXPORT_SYMBOL(dam_select_TxFS_direction); +EXPORT_SYMBOL(dam_select_TxFS_source); +EXPORT_SYMBOL(dam_set_internal_network_mode_mask); +EXPORT_SYMBOL(dam_set_synchronous); +EXPORT_SYMBOL(dam_switch_Tx_Rx); +EXPORT_SYMBOL(dam_reset_register); + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode) +{ + int result; + result = 0; + + ModifyRegister32(dam_mode_masq << dam_mode_shift, + the_mode << dam_mode_shift, _reg_DAM_PDCR(port)); + + return result; +} + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_PDCR(p_config)); +} + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << dam_receive_frame_sync_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << dam_transmit_clock_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction) +{ + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << dam_transmit_frame_sync_direction_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + ModifyRegister32(dam_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_PTCR(p_config)); +} + +/*! + * This function sets a bit mask that selects the port from which of the RxD + * signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask) +{ + int result; + result = 0; + + ModifyRegister32(dam_internal_network_mode_mask << + dam_internal_network_mode_shift, + bit_mask << dam_internal_network_mode_shift, + _reg_DAM_PDCR(port)); + + return result; +} + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous) +{ + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_PTCR(port)); +} + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) + * to (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value) +{ + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_PDCR(port)); +} + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port) +{ + ModifyRegister32(0xFFFFFFFF, 0x00000000, _reg_DAM_PTCR(port)); + ModifyRegister32(0xFFFFFFFF, 0x00000000, _reg_DAM_PDCR(port)); +} + +/*! + * This function implements the init function of the DAM device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init dam_init(void) +{ + return 0; +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit dam_exit(void) +{ +} + +module_init(dam_init); +module_exit(dam_exit); + +MODULE_DESCRIPTION("DAM char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/dam/dam.h b/drivers/mxc/dam/dam.h new file mode 100644 index 000000000000..cb9ead53f689 --- /dev/null +++ b/drivers/mxc/dam/dam.h @@ -0,0 +1,258 @@ +/* + * 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 + */ + + /*! + * @defgroup DAM Digital Audio Multiplexer (AUDMUX) Driver + */ + + /*! + * @file dam.h + * @brief This is the brief documentation for this dam.h file. + * + * This header file contains DAM driver functions prototypes. + * + * @ingroup DAM + */ + +#ifndef __MXC_DAM_H__ +#define __MXC_DAM_H__ + +/*! + * This enumeration describes the Digital Audio Multiplexer mode. + */ +typedef enum { + + /*! + * Normal mode + */ + normal_mode = 0, + + /*! + * Internal network mode + */ + internal_network_mode = 1, + + /*! + * CE bus network mode + */ + CE_bus_network_mode = 2 +} dam_mode; + +/*! + * This enumeration describes the port. + */ +typedef enum { + + /*! + * The port 1 + */ + port_1 = 0, + + /*! + * The port 2 + */ + port_2 = 1, + + /*! + * The port 3 + */ + port_3 = 2, + + /*! + * The port 4 + */ + port_4 = 3, + + /*! + * The port 5 + */ + port_5 = 4, + + /*! + * The port 6 + */ + port_6 = 5, + + /*! + * The port 7 + */ + port_7 = 6 +} dam_port; + +/*! + * This enumeration describes the signal direction. + */ +typedef enum { + + /*! + * Signal In + */ + signal_in = 0, + + /*! + * Signal Out + */ + signal_out = 1 +} signal_direction; + +/*! + * Test purpose definition + */ +#define TEST_DAM 1 + +#ifdef TEST_DAM + +#define DAM_IOCTL 0x55 +#define DAM_CONFIG_SSI1_MC13783 _IOWR(DAM_IOCTL, 1, int) +#define DAM_CONFIG_SSI2_MC13783 _IOWR(DAM_IOCTL, 2, int) +#define DAM_CONFIG_SSI_NETWORK_MODE_MC13783 _IOWR(DAM_IOCTL, 3, int) +#endif + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode); + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, bool from_RxClk, + dam_port p_source); + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source); + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, bool from_RxFS, + dam_port p_source); + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, bool from_RxClk, + dam_port p_source); + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction); + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, bool from_RxFS, + dam_port p_source); + +/*! + * This function sets a bit mask that selects the port from which of + * the RxD signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask); + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous); + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) to + * (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value); + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port); + +#endif diff --git a/drivers/mxc/dam/dam_v1.c b/drivers/mxc/dam/dam_v1.c new file mode 100644 index 000000000000..0fc88bb98a38 --- /dev/null +++ b/drivers/mxc/dam/dam_v1.c @@ -0,0 +1,617 @@ +/* + * Copyright 2004-2009 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 dam_v1.c + * @brief This is the brief documentation for this dam_v1.c file. + * + * This file contains the implementation of the DAM driver main services + * + * @ingroup DAM + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <asm/uaccess.h> +#include "dam.h" + +/*! + * This include to define bool type, false and true definitions. + */ +#include <mach/hardware.h> + +#define DAM_VIRT_BASE_ADDR IO_ADDRESS(AUDMUX_BASE_ADDR) + +#define ModifyRegister32(a,b,c) do{\ + __raw_writel( ((__raw_readl(c)) & (~(a))) | (b),(c) );\ +}while(0) + +#ifndef _reg_DAM_HPCR1 +#define _reg_DAM_HPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x00))) +#endif + +#ifndef _reg_DAM_HPCR2 +#define _reg_DAM_HPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x04))) +#endif + +#ifndef _reg_DAM_HPCR3 +#define _reg_DAM_HPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x08))) +#endif + +#ifndef _reg_DAM_PPCR1 +#define _reg_DAM_PPCR1 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x10))) +#endif + +#ifndef _reg_DAM_PPCR2 +#define _reg_DAM_PPCR2 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x14))) +#endif + +#ifndef _reg_DAM_PPCR3 +#define _reg_DAM_PPCR3 (*((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x1c))) +#endif + +#ifndef _reg_DAM_HPCR +#define _reg_DAM_HPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + (a)*4)) +#endif + +#ifndef _reg_DAM_PPCR +#define _reg_DAM_PPCR(a) ((volatile unsigned long *) \ + (DAM_VIRT_BASE_ADDR + 0x0c + (0x04 << (a-3)) )) +#endif + +/*! + * HPCR/PPCR Registers bit shift definitions + */ +#define dam_transmit_frame_sync_direction_shift 31 +#define dam_transmit_clock_direction_shift 30 +#define dam_transmit_frame_sync_select_shift 26 +#define dam_transmit_clock_select_shift 26 +#define dam_receive_frame_sync_direction_shift 25 +#define dam_receive_clock_direction_shift 24 +#define dam_receive_clock_select_shift 20 +#define dam_receive_frame_sync_select_shift 20 + +#define dam_receive_data_select_shift 13 +#define dam_synchronous_mode_shift 12 + +#define dam_transmit_receive_switch_shift 10 + +#define dam_mode_shift 8 +#define dam_internal_network_mode_shift 0 + +/*! + * HPCR/PPCR Register bit masq definitions + */ +//#define dam_selection_mask 0xF +#define dam_fs_selection_mask 0xF +#define dam_clk_selection_mask 0xF +#define dam_dat_selection_mask 0x7 +//#define dam_mode_masq 0x03 +#define dam_internal_network_mode_mask 0xFF + +/*! + * HPCR/PPCR Register reset value definitions + */ +#define dam_hpcr_default_value 0x00001000 +#define dam_ppcr_default_value 0x00001000 + +#define DAM_NAME "dam" +static struct class *mxc_dam_class; + +EXPORT_SYMBOL(dam_select_mode); +EXPORT_SYMBOL(dam_select_RxClk_direction); +EXPORT_SYMBOL(dam_select_RxClk_source); +EXPORT_SYMBOL(dam_select_RxD_source); +EXPORT_SYMBOL(dam_select_RxFS_direction); +EXPORT_SYMBOL(dam_select_RxFS_source); +EXPORT_SYMBOL(dam_select_TxClk_direction); +EXPORT_SYMBOL(dam_select_TxClk_source); +EXPORT_SYMBOL(dam_select_TxFS_direction); +EXPORT_SYMBOL(dam_select_TxFS_source); +EXPORT_SYMBOL(dam_set_internal_network_mode_mask); +EXPORT_SYMBOL(dam_set_synchronous); +EXPORT_SYMBOL(dam_switch_Tx_Rx); +EXPORT_SYMBOL(dam_reset_register); + +/*! + * DAM major + */ +#ifdef TEST_DAM +static int major_dam; + +typedef struct _mxc_cfg { + int reg; + int val; +} mxc_cfg; + +#endif + +/*! + * This function selects the operation mode of the port. + * + * @param port the DAM port to configure + * @param the_mode the operation mode of the port + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_select_mode(dam_port port, dam_mode the_mode) +{ + int result; + result = 0; + + if (port >= 3) + the_mode = normal_mode; + ModifyRegister32(1 << dam_mode_shift, + the_mode << dam_mode_shift, _reg_DAM_HPCR(port)); + + return result; +} + +/*! + * This function controls Receive clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx clock signal direction + */ +void dam_select_RxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_clock_direction_shift, + direction << dam_receive_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_RxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_receive_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_receive_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function selects the source port for the RxD data. + * + * @param p_config the DAM port to configure + * @param p_source the source port + */ +void dam_select_RxD_source(dam_port p_config, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_dat_selection_mask << + dam_receive_data_select_shift, + p_source << dam_receive_data_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Rx Frame Sync signal direction + */ +void dam_select_RxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_receive_frame_sync_direction_shift, + direction << + dam_receive_frame_sync_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Receive Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_RxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_receive_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_receive_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit clock signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx clock signal direction + */ +void dam_select_TxClk_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_clock_direction_shift, + direction << + dam_transmit_clock_direction_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function controls Transmit clock signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxClk the signal comes from RxClk or TxClk of + * the source port + * @param p_source the source port + */ +void dam_select_TxClk_source(dam_port p_config, + bool from_RxClk, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_clk_selection_mask << + dam_transmit_clock_select_shift, + ((from_RxClk << 3) | p_source) << + dam_transmit_clock_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal direction for the port. + * + * @param port the DAM port to configure + * @param direction the Tx Frame Sync signal direction + */ +void dam_select_TxFS_direction(dam_port port, signal_direction direction) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_frame_sync_direction_shift, + direction << + dam_transmit_frame_sync_direction_shift, + _reg_DAM_HPCR(port)); + } + return; +} + +/*! + * This function controls Transmit Frame Sync signal source for the port. + * + * @param p_config the DAM port to configure + * @param from_RxFS the signal comes from RxFS or TxFS of + * the source port + * @param p_source the source port + */ +void dam_select_TxFS_source(dam_port p_config, + bool from_RxFS, dam_port p_source) +{ + if (p_config < 3) { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_HPCR(p_config)); + } else { + ModifyRegister32(dam_fs_selection_mask << + dam_transmit_frame_sync_select_shift, + ((from_RxFS << 3) | p_source) << + dam_transmit_frame_sync_select_shift, + _reg_DAM_PPCR(p_config)); + } + return; +} + +/*! + * This function sets a bit mask that selects the port from which of the RxD + * signals are to be ANDed together for internal network mode. + * Bit 6 represents RxD from Port7 and bit0 represents RxD from Port1. + * 1 excludes RxDn from ANDing. 0 includes RxDn for ANDing. + * + * @param port the DAM port to configure + * @param bit_mask the bit mask + * + * @return This function returns the result of the operation + * (0 if successful, -1 otherwise). + */ +int dam_set_internal_network_mode_mask(dam_port port, unsigned char bit_mask) +{ + int result; + result = 0; + + ModifyRegister32(dam_internal_network_mode_mask << + dam_internal_network_mode_shift, + bit_mask << dam_internal_network_mode_shift, + _reg_DAM_HPCR(port)); + return result; +} + +/*! + * This function controls whether or not the port is in synchronous mode. + * When the synchronous mode is selected, the receive and the transmit sections + * use common clock and frame sync signals. + * When the synchronous mode is not selected, separate clock and frame sync + * signals are used for the transmit and the receive sections. + * The defaut value is the synchronous mode selected. + * + * @param port the DAM port to configure + * @param synchronous the state to assign + */ +void dam_set_synchronous(dam_port port, bool synchronous) +{ + if (port < 3) { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_synchronous_mode_shift, + synchronous << dam_synchronous_mode_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function swaps the transmit and receive signals from (Da-TxD, Db-RxD) + * to (Da-RxD, Db-TxD). + * This default signal configuration is Da-TxD, Db-RxD. + * + * @param port the DAM port to configure + * @param value the switch state + */ +void dam_switch_Tx_Rx(dam_port port, bool value) +{ + if (port < 3) { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(1 << dam_transmit_receive_switch_shift, + value << dam_transmit_receive_switch_shift, + _reg_DAM_PPCR(port)); + } + return; +} + +/*! + * This function resets the two registers of the selected port. + * + * @param port the DAM port to reset + */ +void dam_reset_register(dam_port port) +{ + if (port < 3) { + ModifyRegister32(0xFFFFFFFF, dam_hpcr_default_value, + _reg_DAM_HPCR(port)); + } else { + ModifyRegister32(0xFFFFFFFF, dam_ppcr_default_value, + _reg_DAM_PPCR(port)); + } + return; +} + +#ifdef TEST_DAM + +/*! + * This function implements IOCTL controls on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter :\n + * DAM_CONFIG_SSI1:\n + * data from port 1 to port 4, clock and FS from port 1 (SSI1)\n + * DAM_CONFIG_SSI2:\n + * data from port 2 to port 5, clock and FS from port 2 (SSI2)\n + * DAM_CONFIG_SSI_NETWORK_MODE:\n + * network mode for mix digital with data from port 1 to port4,\n + * data from port 2 to port 4, clock and FS from port 1 (SSI1) + * + * @return This function returns 0 if successful. + */ +static int dam_ioctl(struct inode *inode, + struct file *file, unsigned int cmd, unsigned long arg) +{ + return 0; +} + +/*! + * This function implements the open method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_open(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_open()\n"); */ + return 0; +} + +/*! + * This function implements the release method on a DAM device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int dam_free(struct inode *inode, struct file *file) +{ + /* DBG_PRINTK("ssi : dam_free()\n"); */ + return 0; +} + +/*! + * This structure defines file operations for a DAM device. + */ +static struct file_operations dam_fops = { + + /*! + * the owner + */ + .owner = THIS_MODULE, + + /*! + * the ioctl operation + */ + .ioctl = dam_ioctl, + + /*! + * the open operation + */ + .open = dam_open, + + /*! + * the release operation + */ + .release = dam_free, +}; + +#endif + +/*! + * This function implements the init function of the DAM device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init dam_init(void) +{ +#ifdef TEST_DAM + struct device *temp_class; + printk(KERN_DEBUG "dam : dam_init(void) \n"); + + major_dam = register_chrdev(0, DAM_NAME, &dam_fops); + if (major_dam < 0) { + printk(KERN_WARNING "Unable to get a major for dam"); + return major_dam; + } + + mxc_dam_class = class_create(THIS_MODULE, DAM_NAME); + if (IS_ERR(mxc_dam_class)) { + goto err_out; + } + + temp_class = device_create(mxc_dam_class, NULL, + MKDEV(major_dam, 0), NULL, DAM_NAME); + if (IS_ERR(temp_class)) { + goto err_out; + } +#endif + return 0; + + err_out: + printk(KERN_ERR "Error creating dam class device.\n"); + device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + return -1; +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit dam_exit(void) +{ +#ifdef TEST_DAM + device_destroy(mxc_dam_class, MKDEV(major_dam, 0)); + class_destroy(mxc_dam_class); + unregister_chrdev(major_dam, DAM_NAME); + printk(KERN_DEBUG "dam : successfully unloaded\n"); +#endif +} + +module_init(dam_init); +module_exit(dam_exit); + +MODULE_DESCRIPTION("DAM char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/gps_ioctrl/Kconfig b/drivers/mxc/gps_ioctrl/Kconfig new file mode 100644 index 000000000000..0a85d1636dd7 --- /dev/null +++ b/drivers/mxc/gps_ioctrl/Kconfig @@ -0,0 +1,13 @@ +# +# BROADCOM GPS configuration +# + +menu "Broadcom GPS ioctrl support" + +config GPS_IOCTRL + tristate "GPS ioctrl support" + depends on MACH_MX31_3DS || MACH_MX35_3DS || MACH_MX37_3DS || MACH_MX51_3DS + ---help--- + Say Y to enable Broadcom GPS ioctrl on MXC platform. + +endmenu diff --git a/drivers/mxc/gps_ioctrl/Makefile b/drivers/mxc/gps_ioctrl/Makefile new file mode 100644 index 000000000000..c52d7c9f151c --- /dev/null +++ b/drivers/mxc/gps_ioctrl/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the GPIO device driver module. +# +obj-$(CONFIG_GPS_IOCTRL) += gps_gpiodrv.o +gps_gpiodrv-objs := agpsgpiodev.o diff --git a/drivers/mxc/gps_ioctrl/agpsgpiodev.c b/drivers/mxc/gps_ioctrl/agpsgpiodev.c new file mode 100644 index 000000000000..2f64274bf37c --- /dev/null +++ b/drivers/mxc/gps_ioctrl/agpsgpiodev.c @@ -0,0 +1,331 @@ +/* + * Copyright 2008-2009 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 agpsgpiodev.c + * + * @brief Main file for GPIO kernel module. Contains driver entry/exit + * + */ + +#include <linux/module.h> +#include <linux/fs.h> /* Async notification */ +#include <linux/uaccess.h> /* for get_user, put_user, access_ok */ +#include <linux/sched.h> /* jiffies */ +#include <linux/poll.h> +#include <linux/device.h> +#include <linux/regulator/consumer.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/cdev.h> +#include <mach/hardware.h> +#include "agpsgpiodev.h" + +extern void gpio_gps_active(void); +extern void gpio_gps_inactive(void); +extern int gpio_gps_access(int para); + +struct mxc_gps_platform_data *mxc_gps_ioctrl_data; +static int Device_Open; /* Only allow a single user of this device */ +static struct cdev mxc_gps_cdev; +static dev_t agps_gpio_dev; +static struct class *gps_class; +static struct device *gps_class_dev; + +/* Write GPIO from user space */ +static int ioctl_writegpio(int arg) +{ + + /* Bit 0 of arg identifies the GPIO pin to write: + 0 = GPS_RESET_GPIO, 1 = GPS_POWER_GPIO. + Bit 1 of arg identifies the value to write (0 or 1). */ + + /* Bit 2 should be 0 to show this access is write */ + return gpio_gps_access(arg & (~0x4)); +} + +/* Read GPIO from user space */ +static int ioctl_readgpio(int arg) +{ + /* Bit 0 of arg identifies the GPIO pin to read: + 0 = GPS_RESET_GPIO. 1 = GPS_POWER_GPIO + Bit 2 should be 1 to show this access is read */ + return gpio_gps_access(arg | 0x4); +} + +static int device_open(struct inode *inode, struct file *fp) +{ + /* We don't want to talk to two processes at the same time. */ + if (Device_Open) { + printk(KERN_DEBUG "device_open() - Returning EBUSY. \ + Device already open... \n"); + return -EBUSY; + } + Device_Open++; /* BUGBUG : Not protected! */ + try_module_get(THIS_MODULE); + + return 0; +} + +static int device_release(struct inode *inode, struct file *fp) +{ + /* We're now ready for our next caller */ + Device_Open--; + module_put(THIS_MODULE); + + return 0; +} + +static int device_ioctl(struct inode *inode, struct file *fp, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + + /* Extract the type and number bitfields, and don't decode wrong cmds. + Return ENOTTY (inappropriate ioctl) before access_ok() */ + if (_IOC_TYPE(cmd) != MAJOR_NUM) { + printk(KERN_ERR + "device_ioctl() - Error! IOC_TYPE = %d. Expected %d\n", + _IOC_TYPE(cmd), MAJOR_NUM); + return -ENOTTY; + } + if (_IOC_NR(cmd) > IOCTL_MAXNUMBER) { + printk(KERN_ERR + "device_ioctl() - Error!" + "IOC_NR = %d greater than max supported(%d)\n", + _IOC_NR(cmd), IOCTL_MAXNUMBER); + return -ENOTTY; + } + + /* The direction is a bitmask, and VERIFY_WRITE catches R/W transfers. + `Type' is user-oriented, while access_ok is kernel-oriented, so the + concept of "read" and "write" is reversed. I think this is primarily + for good coding practice. You can easily do any kind of R/W access + without these checks and IOCTL code can be implemented "randomly"! */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = + !access_ok(VERIFY_WRITE, (void __user *)arg, + _IOC_SIZE(cmd)); + + else if (_IOC_DIR(cmd) & _IOC_WRITE) + err = + !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); + if (err) { + printk(KERN_ERR + "device_ioctl() - Error! User arg not valid" + "for selected access (R/W/RW). Cmd %d\n", + _IOC_TYPE(cmd)); + return -EFAULT; + } + + /* Note: Read and writing data to user buffer can be done using regular + pointer stuff but we may also use get_user() or put_user() */ + + /* Cmd and arg has been verified... */ + switch (cmd) { + case IOCTL_WRITEGPIO: + return ioctl_writegpio((int)arg); + case IOCTL_READGPIO: + return ioctl_readgpio((int)arg); + default: + printk(KERN_ERR "device_ioctl() - Invalid IOCTL (0x%x)\n", cmd); + return EINVAL; + } + return 0; +} + +struct file_operations Fops = { + .ioctl = device_ioctl, + .open = device_open, + .release = device_release, +}; + +/* Initialize the module - Register the character device */ +int init_chrdev(struct device *dev) +{ + int ret, gps_major; + + ret = alloc_chrdev_region(&agps_gpio_dev, 1, 1, "agps_gpio"); + gps_major = MAJOR(agps_gpio_dev); + if (ret < 0) { + dev_err(dev, "can't get major %d\n", gps_major); + goto err3; + } + + cdev_init(&mxc_gps_cdev, &Fops); + mxc_gps_cdev.owner = THIS_MODULE; + + ret = cdev_add(&mxc_gps_cdev, agps_gpio_dev, 1); + if (ret) { + dev_err(dev, "can't add cdev\n"); + goto err2; + } + + /* create class and device for udev information */ + gps_class = class_create(THIS_MODULE, "gps"); + if (IS_ERR(gps_class)) { + dev_err(dev, "failed to create gps class\n"); + ret = -ENOMEM; + goto err1; + } + + gps_class_dev = device_create(gps_class, NULL, MKDEV(gps_major, 1), NULL, + AGPSGPIO_DEVICE_FILE_NAME); + if (IS_ERR(gps_class_dev)) { + dev_err(dev, "failed to create gps gpio class device\n"); + ret = -ENOMEM; + goto err0; + } + + return 0; +err0: + class_destroy(gps_class); +err1: + cdev_del(&mxc_gps_cdev); +err2: + unregister_chrdev_region(agps_gpio_dev, 1); +err3: + return ret; +} + +/* Cleanup - unregister the appropriate file from /proc. */ +void cleanup_chrdev(void) +{ + /* destroy gps device class */ + device_destroy(gps_class, MKDEV(MAJOR(agps_gpio_dev), 1)); + class_destroy(gps_class); + + /* Unregister the device */ + cdev_del(&mxc_gps_cdev); + unregister_chrdev_region(agps_gpio_dev, 1); +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __init gps_ioctrl_probe(struct platform_device *pdev) +{ + struct regulator *gps_regu; + + mxc_gps_ioctrl_data = + (struct mxc_gps_platform_data *)pdev->dev.platform_data; + + /* open GPS GPO3 1v8 for GL gps support */ + if (mxc_gps_ioctrl_data->core_reg != NULL) { + mxc_gps_ioctrl_data->gps_regu_core = + regulator_get(&(pdev->dev), mxc_gps_ioctrl_data->core_reg); + gps_regu = mxc_gps_ioctrl_data->gps_regu_core; + if (!IS_ERR_VALUE((u32)gps_regu)) { + regulator_set_voltage(gps_regu, 1800000, 1800000); + regulator_enable(gps_regu); + } else { + return -1; + } + } + /* open GPS GPO1 2v8 for GL gps support */ + if (mxc_gps_ioctrl_data->analog_reg != NULL) { + mxc_gps_ioctrl_data->gps_regu_analog = + regulator_get(&(pdev->dev), + mxc_gps_ioctrl_data->analog_reg); + gps_regu = mxc_gps_ioctrl_data->gps_regu_analog; + if (!IS_ERR_VALUE((u32)gps_regu)) { + regulator_set_voltage(gps_regu, 2800000, 2800000); + regulator_enable(gps_regu); + } else { + return -1; + } + } + gpio_gps_active(); + + /* Register character device */ + init_chrdev(&(pdev->dev)); + return 0; +} + +static int gps_ioctrl_remove(struct platform_device *pdev) +{ + struct regulator *gps_regu; + + mxc_gps_ioctrl_data = + (struct mxc_gps_platform_data *)pdev->dev.platform_data; + + /* Character device cleanup.. */ + cleanup_chrdev(); + gpio_gps_inactive(); + + /* close GPS GPO3 1v8 for GL gps */ + gps_regu = mxc_gps_ioctrl_data->gps_regu_core; + if (mxc_gps_ioctrl_data->core_reg != NULL) { + regulator_disable(gps_regu); + regulator_put(gps_regu); + } + /* close GPS GPO1 2v8 for GL gps */ + gps_regu = mxc_gps_ioctrl_data->gps_regu_analog; + if (mxc_gps_ioctrl_data->analog_reg != NULL) { + regulator_disable(gps_regu); + regulator_put(gps_regu); + } + + return 0; +} + +static int gps_ioctrl_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* PowerEn toggle off */ + ioctl_writegpio(0x1); + return 0; +} + +static int gps_ioctrl_resume(struct platform_device *pdev) +{ + /* PowerEn pull up */ + ioctl_writegpio(0x3); + return 0; +} + +static struct platform_driver gps_ioctrl_driver = { + .probe = gps_ioctrl_probe, + .remove = gps_ioctrl_remove, + .suspend = gps_ioctrl_suspend, + .resume = gps_ioctrl_resume, + .driver = { + .name = "gps_ioctrl", + }, +}; + +/*! + * Entry point for GPS ioctrl module. + * + */ +static int __init gps_ioctrl_init(void) +{ + return platform_driver_register(&gps_ioctrl_driver); +} + +/*! + * unloading module. + * + */ +static void __exit gps_ioctrl_exit(void) +{ + platform_driver_unregister(&gps_ioctrl_driver); +} + +module_init(gps_ioctrl_init); +module_exit(gps_ioctrl_exit); +MODULE_DESCRIPTION("GPIO DEVICE DRIVER"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/gps_ioctrl/agpsgpiodev.h b/drivers/mxc/gps_ioctrl/agpsgpiodev.h new file mode 100644 index 000000000000..815890578f4a --- /dev/null +++ b/drivers/mxc/gps_ioctrl/agpsgpiodev.h @@ -0,0 +1,46 @@ +/* + * Copyright 2008-2009 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 agpsgpiodev.h + * + * @brief head file of Simple character device interface for AGPS kernel module. + * + * @ingroup + */ + +#ifndef AGPSGPIODEV_H +#define AGPSGPIODEV_H + +#include <linux/ioctl.h> + +#define USE_BLOCKING /* Test driver with blocking calls */ +#undef USE_FASYNC /* Test driver with async notification */ + +/* The major device number. We can't rely on dynamic registration any more + because ioctls need to know it */ +#define MAJOR_NUM 100 + +#define IOCTL_WRITEGPIO _IOWR(MAJOR_NUM, 1, char *) +#define IOCTL_READGPIO _IOR(MAJOR_NUM, 2, char *) +#define IOCTL_MAXNUMBER 2 + +/* The name of the device file */ +#define AGPSGPIO_DEVICE_FILE_NAME "agpsgpio" + +/* Exported prototypes */ +int init_chrdev(struct device *dev); +void cleanup_chrdev(void); +void wakeup(void); + +#endif diff --git a/drivers/mxc/hmp4e/Kconfig b/drivers/mxc/hmp4e/Kconfig new file mode 100644 index 000000000000..fdd7dbc041ba --- /dev/null +++ b/drivers/mxc/hmp4e/Kconfig @@ -0,0 +1,24 @@ +# +# MPEG4 Encoder kernel module configuration +# + +menu "MXC MPEG4 Encoder Kernel module support" + +config MXC_HMP4E + tristate "MPEG4 Encoder support" + depends on ARCH_MXC + depends on !ARCH_MX27 + default y + ---help--- + Say Y to get the MPEG4 Encoder kernel module available on + MXC platform. + +config MXC_HMP4E_DEBUG + bool "MXC MPEG4 Debug messages" + depends on MXC_HMP4E != n + default n + ---help--- + Say Y here if you need the Encoder driver to print debug messages. + This is an option for developers, most people should say N here. + +endmenu diff --git a/drivers/mxc/hmp4e/Makefile b/drivers/mxc/hmp4e/Makefile new file mode 100644 index 000000000000..0efe11fbdbc8 --- /dev/null +++ b/drivers/mxc/hmp4e/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the MPEG4 Encoder kernel module. + +obj-$(CONFIG_MXC_HMP4E) += mxc_hmp4e.o + +ifeq ($(CONFIG_MXC_HMP4E_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mxc/hmp4e/mxc_hmp4e.c b/drivers/mxc/hmp4e/mxc_hmp4e.c new file mode 100644 index 000000000000..38b84329d459 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.c @@ -0,0 +1,812 @@ +/* + * Copyright 2005-2009 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 + */ + +/* + * Encoder device driver (kernel module) + * + * Copyright (C) 2005 Hantro Products Oy. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> /* __init,__exit directives */ +#include <linux/mm.h> /* remap_page_range / remap_pfn_range */ +#include <linux/fs.h> /* for struct file_operations */ +#include <linux/errno.h> /* standard error codes */ +#include <linux/platform_device.h> /* for device registeration for PM */ +#include <linux/delay.h> /* for msleep_interruptible */ +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> /* for dma_alloc_consistent */ +#include <linux/clk.h> +#include <asm/uaccess.h> /* for ioctl __get_user, __put_user */ +#include <mach/hardware.h> +#include "mxc_hmp4e.h" /* MPEG4 encoder specific */ + +/* here's all the must remember stuff */ +typedef struct { + ulong buffaddr; + u32 buffsize; + ulong iobaseaddr; + u32 iosize; + volatile u32 *hwregs; + u32 irq; + u16 hwid_offset; + u16 intr_offset; + u16 busy_offset; + u16 type; /* Encoder type, CIF = 0, VGA = 1 */ + u16 clk_gate; + u16 busy_val; + struct fasync_struct *async_queue; +#ifdef CONFIG_PM + s32 suspend_state; + wait_queue_head_t power_queue; +#endif +} hmp4e_t; + +/* and this is our MAJOR; use 0 for dynamic allocation (recommended)*/ +static s32 hmp4e_major; + +static u32 hmp4e_phys; +static struct class *hmp4e_class; +static hmp4e_t hmp4e_data; + +/*! MPEG4 enc clock handle. */ +static struct clk *hmp4e_clk; + +/* + * avoid "enable_irq(x) unbalanced from ..." + * error messages from the kernel, since {ena,dis}able_irq() + * calls are stacked in kernel. + */ +static bool irq_enable = false; + +ulong base_port = MPEG4_ENC_BASE_ADDR; +u32 irq = MXC_INT_MPEG4_ENCODER; + +module_param(base_port, long, 000); +module_param(irq, int, 000); + +/*! + * These variables store the register values when HMP4E is in suspend mode. + */ +#ifdef CONFIG_PM +u32 io_regs[64]; +#endif + +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma); +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma); +static void hmp4e_reset(hmp4e_t * dev); +irqreturn_t hmp4e_isr(s32 irq, void *dev_id); + +/*! + * This funtion is called to write h/w register. + * + * @param val value to be written into the register + * @param offset register offset + * + */ +static inline void hmp4e_write(u32 val, u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + __raw_writel(val, (dev->hwregs + offset)); +} + +/*! + * This funtion is called to read h/w register. + * + * @param offset register offset + * + * @return This function returns the value read from the register. + * + */ +static inline u32 hmp4e_read(u32 offset) +{ + hmp4e_t *dev = &hmp4e_data; + u32 val; + + val = __raw_readl(dev->hwregs + offset); + + return val; +} + +/*! + * The device's mmap method. The VFS has kindly prepared the process's + * vm_area_struct for us, so we examine this to see what was requested. + * + * @param filp pointer to struct file + * @param vma pointer to struct vma_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_mmap(struct file *filp, struct vm_area_struct *vma) +{ + s32 result; + ulong offset = vma->vm_pgoff << PAGE_SHIFT; + + pr_debug("hmp4e_mmap: size = %lu off = 0x%08lx\n", + (unsigned long)(vma->vm_end - vma->vm_start), offset); + + if (offset == 0) { + result = hmp4e_map_buffer(filp, vma); + } else if (offset == hmp4e_data.iobaseaddr) { + result = hmp4e_map_hwregs(filp, vma); + } else { + pr_debug("hmp4e: mmap invalid value\n"); + result = -EINVAL; + } + + return result; +} + +/*! + * This funtion is called to handle ioctls. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * @param cmd ioctl command + * @param arg user data + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_ioctl(struct inode *inode, struct file *filp, + u32 cmd, ulong arg) +{ + s32 err = 0, retval = 0; + ulong offset = 0; + hmp4e_t *dev = &hmp4e_data; + write_t bwrite; + +#ifdef CONFIG_PM + wait_event_interruptible(hmp4e_data.power_queue, + hmp4e_data.suspend_state == 0); +#endif + + /* + * extract the type and number bitfields, and don't decode + * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() + */ + if (_IOC_TYPE(cmd) != HMP4E_IOC_MAGIC) { + pr_debug("hmp4e: ioctl invalid magic\n"); + return -ENOTTY; + } + + if (_IOC_NR(cmd) > HMP4E_IOC_MAXNR) { + pr_debug("hmp4e: ioctl exceeds max ioctl\n"); + return -ENOTTY; + } + + /* + * the direction is a bitmask, and VERIFY_WRITE catches R/W + * transfers. `Type' is user-oriented, while + * access_ok is kernel-oriented, so the concept of "read" and + * "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) { + err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); + } else if (_IOC_DIR(cmd) & _IOC_WRITE) { + err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); + } + + if (err) { + pr_debug("hmp4e: ioctl invalid direction\n"); + return -EFAULT; + } + + switch (cmd) { + case HMP4E_IOCHARDRESET: + break; + + case HMP4E_IOCGBUFBUSADDRESS: + retval = __put_user((ulong) hmp4e_phys, (u32 *) arg); + break; + + case HMP4E_IOCGBUFSIZE: + retval = __put_user(hmp4e_data.buffsize, (u32 *) arg); + break; + + case HMP4E_IOCSREGWRITE: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __copy_from_user(&bwrite, (u32 *) arg, + sizeof(write_t)); + + if (bwrite.offset <= hmp4e_data.iosize - 4) { + hmp4e_write(bwrite.data, (bwrite.offset / 4)); + } else { + pr_debug("hmp4e: HMP4E_IOCSREGWRITE failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCXREGREAD: + if (dev->type != 1) { /* This ioctl only for VGA */ + pr_debug("hmp4e: HMP4E_IOCSREGWRITE invalid\n"); + retval = -EINVAL; + break; + } + + retval = __get_user(offset, (ulong *) arg); + if (offset <= hmp4e_data.iosize - 4) { + __put_user(hmp4e_read((offset / 4)), (ulong *) arg); + } else { + pr_debug("hmp4e: HMP4E_IOCXREGREAD failed\n"); + retval = -EFAULT; + } + break; + + case HMP4E_IOCGHWOFFSET: + __put_user(hmp4e_data.iobaseaddr, (ulong *) arg); + break; + + case HMP4E_IOCGHWIOSIZE: + __put_user(hmp4e_data.iosize, (u32 *) arg); + break; + + case HMP4E_IOC_CLI: + if (irq_enable == true) { + disable_irq(hmp4e_data.irq); + irq_enable = false; + } + break; + + case HMP4E_IOC_STI: + if (irq_enable == false) { + enable_irq(hmp4e_data.irq); + irq_enable = true; + } + break; + + default: + pr_debug("unknown case %x\n", cmd); + } + + return retval; +} + +/*! + * This funtion is called when the device is opened. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_open(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = &hmp4e_data; + + filp->private_data = (void *)dev; + + if (request_irq(dev->irq, hmp4e_isr, 0, "mxc_hmp4e", dev) != 0) { + pr_debug("hmp4e: request irq failed\n"); + return -EBUSY; + } + + if (irq_enable == false) { + irq_enable = true; + } + clk_enable(hmp4e_clk); + return 0; +} + +static s32 hmp4e_fasync(s32 fd, struct file *filp, s32 mode) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * This funtion is called when the device is closed. + * + * @param inode pointer to struct inode + * @param filp pointer to struct file + * + * @return This function returns 0. + * + */ +static s32 hmp4e_release(struct inode *inode, struct file *filp) +{ + hmp4e_t *dev = (hmp4e_t *) filp->private_data; + + /* this is necessary if user process exited asynchronously */ + if (irq_enable == true) { + disable_irq(dev->irq); + irq_enable = false; + } + + /* reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* free the encoder IRQ */ + free_irq(dev->irq, (void *)dev); + + /* remove this filp from the asynchronusly notified filp's */ + hmp4e_fasync(-1, filp, 0); + clk_disable(hmp4e_clk); + return 0; +} + +/* VFS methods */ +static struct file_operations hmp4e_fops = { + .owner = THIS_MODULE, + .open = hmp4e_open, + .release = hmp4e_release, + .ioctl = hmp4e_ioctl, + .mmap = hmp4e_mmap, + .fasync = hmp4e_fasync, +}; + +/*! + * This funtion allocates physical contigous memory. + * + * @param size size of memory to be allocated + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_alloc(u32 size) +{ + hmp4e_data.buffsize = PAGE_ALIGN(size); + hmp4e_data.buffaddr = + (ulong) dma_alloc_coherent(NULL, hmp4e_data.buffsize, + (dma_addr_t *) & hmp4e_phys, + GFP_DMA | GFP_KERNEL); + + if (hmp4e_data.buffaddr == 0) { + printk(KERN_ERR "hmp4e: couldn't allocate data buffer\n"); + return -ENOMEM; + } + + memset((s8 *) hmp4e_data.buffaddr, 0, hmp4e_data.buffsize); + return 0; +} + +/*! + * This funtion frees the DMAed memory. + */ +static void hmp4e_free(void) +{ + if (hmp4e_data.buffaddr != 0) { + dma_free_coherent(NULL, hmp4e_data.buffsize, + (void *)hmp4e_data.buffaddr, hmp4e_phys); + hmp4e_data.buffaddr = 0; + } +} + +/*! + * This funtion maps the shared buffer in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_buffer(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (u32) vma->vm_start; + ulong size = (u32) (vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > hmp4e_data.buffsize) { + pr_debug("hmp4e: hmp4e_map_buffer, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + phys = hmp4e_phys; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, size, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping shared buffer\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This funtion maps the h/w register space in memory. + * + * @param filp pointer to struct file + * @param vma pointer to struct vm_area_struct + * + * @return This function returns 0 if successful or -ve value on error. + * + */ +static s32 hmp4e_map_hwregs(struct file *filp, struct vm_area_struct *vma) +{ + ulong phys; + ulong start = (unsigned long)vma->vm_start; + ulong size = (unsigned long)(vma->vm_end - vma->vm_start); + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_SIZE) { + pr_debug("hmp4e: hmp4e_map_hwregs, invalid size\n"); + return -EINVAL; + } + + vma->vm_flags |= VM_RESERVED | VM_IO; + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remember this won't work for vmalloc()d memory ! */ + phys = hmp4e_data.iobaseaddr; + + if (remap_pfn_range(vma, start, phys >> PAGE_SHIFT, hmp4e_data.iosize, + vma->vm_page_prot)) { + pr_debug("hmp4e: failed mmapping HW registers\n"); + return -EAGAIN; + } + + return 0; +} + +/*! + * This function is the interrupt service routine. + * + * @param irq the irq number + * @param dev_id driver data when ISR was regiatered + * + * @return The return value is IRQ_HANDLED. + * + */ +irqreturn_t hmp4e_isr(s32 irq, void *dev_id) +{ + hmp4e_t *dev = (hmp4e_t *) dev_id; + u32 offset = dev->intr_offset; + + u32 irq_status = hmp4e_read(offset); + + /* clear enc IRQ */ + hmp4e_write(irq_status & (~0x01), offset); + + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + return IRQ_HANDLED; +} + +/*! + * This function is called to reset the encoder. + * + * @param dev pointer to struct hmp4e_data + * + */ +static void hmp4e_reset(hmp4e_t * dev) +{ + s32 i; + + /* enable HCLK for register reset */ + hmp4e_write(dev->clk_gate, 0); + + /* Reset registers, except ECR0 (0x00) and ID (read-only) */ + for (i = 1; i < (dev->iosize / 4); i += 1) { + if (i == dev->hwid_offset) /* ID is read only */ + continue; + + /* Only for CIF, not used */ + if ((dev->type == 0) && (i == 14)) + continue; + + hmp4e_write(0, i); + } + + /* disable HCLK */ + hmp4e_write(0, 0); + return; +} + +/*! + * This function is called during the driver binding process. This function + * does the hardware initialization. + * + * @param dev the device structure used to store device specific + * information that is used by the suspend, resume and remove + * functions + * + * @return The function returns 0 if successful. + */ +static s32 hmp4e_probe(struct platform_device *pdev) +{ + s32 result; + u32 hwid; + struct device *temp_class; + + hmp4e_data.iobaseaddr = base_port; + hmp4e_data.irq = irq; + hmp4e_data.buffaddr = 0; + + /* map hw i/o registers into kernel space */ + hmp4e_data.hwregs = (volatile void *)IO_ADDRESS(hmp4e_data.iobaseaddr); + + hmp4e_clk = clk_get(&pdev->dev, "mpeg4_clk"); + if (IS_ERR(hmp4e_clk)) { + printk(KERN_INFO "hmp4e: Unable to get clock\n"); + return -EIO; + } + + clk_enable(hmp4e_clk); + + /* check hw id for encoder signature */ + hwid = hmp4e_read(7); + if ((hwid & 0xffff) == 0x1882) { /* CIF first */ + hmp4e_data.type = 0; + hmp4e_data.iosize = (16 * 4); + hmp4e_data.hwid_offset = 7; + hmp4e_data.intr_offset = 5; + hmp4e_data.clk_gate = (1 << 1); + hmp4e_data.buffsize = 512000; + hmp4e_data.busy_offset = 0; + hmp4e_data.busy_val = 1; + } else { + hwid = hmp4e_read((0x88 / 4)); + if ((hwid & 0xffff0000) == 0x52510000) { /* VGA */ + hmp4e_data.type = 1; + hmp4e_data.iosize = (35 * 4); + hmp4e_data.hwid_offset = (0x88 / 4); + hmp4e_data.intr_offset = (0x10 / 4); + hmp4e_data.clk_gate = (1 << 12); + hmp4e_data.buffsize = 1048576; + hmp4e_data.busy_offset = (0x10 / 4); + hmp4e_data.busy_val = (1 << 1); + } else { + printk(KERN_INFO "hmp4e: HW ID not found\n"); + goto error1; + } + } + + /* Reset hardware */ + hmp4e_reset(&hmp4e_data); + + /* allocate memory shared with ewl */ + result = hmp4e_alloc(hmp4e_data.buffsize); + if (result < 0) + goto error1; + + result = register_chrdev(hmp4e_major, "hmp4e", &hmp4e_fops); + if (result <= 0) { + pr_debug("hmp4e: unable to get major %d\n", hmp4e_major); + goto error2; + } + + hmp4e_major = result; + + hmp4e_class = class_create(THIS_MODULE, "hmp4e"); + if (IS_ERR(hmp4e_class)) { + pr_debug("Error creating hmp4e class.\n"); + goto error3; + } + + temp_class = device_create(hmp4e_class, NULL, MKDEV(hmp4e_major, 0), NULL, + "hmp4e"); + if (IS_ERR(temp_class)) { + pr_debug("Error creating hmp4e class device.\n"); + goto error4; + } + + platform_set_drvdata(pdev, &hmp4e_data); + +#ifdef CONFIG_PM + hmp4e_data.async_queue = NULL; + hmp4e_data.suspend_state = 0; + init_waitqueue_head(&hmp4e_data.power_queue); +#endif + + printk(KERN_INFO "hmp4e: %s encoder initialized\n", + hmp4e_data.type ? "VGA" : "CIF"); + clk_disable(hmp4e_clk); + return 0; + + error4: + class_destroy(hmp4e_class); + error3: + unregister_chrdev(hmp4e_major, "hmp4e"); + error2: + hmp4e_free(); + error1: + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + printk(KERN_INFO "hmp4e: module not inserted\n"); + return -EIO; +} + +/*! + * Dissociates the driver. + * + * @param dev the device structure + * + * @return The function always returns 0. + */ +static s32 hmp4e_remove(struct platform_device *pdev) +{ + device_destroy(hmp4e_class, MKDEV(hmp4e_major, 0)); + class_destroy(hmp4e_class); + unregister_chrdev(hmp4e_major, "hmp4e"); + hmp4e_free(); + clk_disable(hmp4e_clk); + clk_put(hmp4e_clk); + platform_set_drvdata(pdev, NULL); + return 0; +} + +#ifdef CONFIG_PM +/*! + * This is the suspend of power management for the Hantro MPEG4 module + * + * @param dev the device + * @param state the state + * + * @return This function always returns 0. + */ +static s32 hmp4e_suspend(struct platform_device *pdev, pm_message_t state) +{ + s32 i; + hmp4e_t *pdata = &hmp4e_data; + + /* + * how many times msleep_interruptible will be called before + * giving up + */ + s32 timeout = 10; + + pr_debug("hmp4e: Suspend\n"); + hmp4e_data.suspend_state = 1; + + /* check if encoder is currently running */ + while ((hmp4e_read(pdata->busy_offset) & (pdata->busy_val)) && + --timeout) { + pr_debug("hmp4e: encoder is running, going to sleep\n"); + msleep_interruptible((unsigned int)30); + } + + if (!timeout) { + pr_debug("hmp4e: timeout suspending, resetting encoder\n"); + hmp4e_write(hmp4e_read(pdata->busy_offset) & + (~pdata->busy_val), pdata->busy_offset); + } + + /* first read register 0 */ + io_regs[0] = hmp4e_read(0); + + /* then override HCLK to make sure other registers can be read */ + hmp4e_write(pdata->clk_gate, 0); + + /* read other registers */ + for (i = 1; i < (pdata->iosize / 4); i += 1) { + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + io_regs[i] = hmp4e_read(i); + } + + /* restore value of register 0 */ + hmp4e_write(io_regs[0], 0); + + /* stop HCLK */ + hmp4e_write(0, 0); + clk_disable(hmp4e_clk); + return 0; +}; + +/*! + * This is the resume of power management for the Hantro MPEG4 module + * It suports RESTORE state. + * + * @param pdev the platform device + * + * @return This function always returns 0 + */ +static s32 hmp4e_resume(struct platform_device *pdev) +{ + s32 i; + u32 status; + hmp4e_t *pdata = &hmp4e_data; + + pr_debug("hmp4e: Resume\n"); + clk_enable(hmp4e_clk); + + /* override HCLK to make sure registers can be written */ + hmp4e_write(pdata->clk_gate, 0x00); + + for (i = 1; i < (pdata->iosize / 4); i += 1) { + if (i == pdata->hwid_offset) /* Read only */ + continue; + + /* Only for CIF, not used */ + if ((pdata->type == 0) && (i == 14)) + continue; + + hmp4e_write(io_regs[i], i); + } + + /* write register 0 last */ + hmp4e_write(io_regs[0], 0x00); + + /* Clear the suspend flag */ + hmp4e_data.suspend_state = 0; + + /* Unblock the wait queue */ + wake_up_interruptible(&hmp4e_data.power_queue); + + /* Continue operations */ + status = hmp4e_read(pdata->intr_offset); + if (status & 0x1) { + hmp4e_write(status & (~0x01), pdata->intr_offset); + if (hmp4e_data.async_queue) + kill_fasync(&hmp4e_data.async_queue, SIGIO, POLL_IN); + } + + return 0; +}; + +#endif + +static struct platform_driver hmp4e_driver = { + .driver = { + .name = "mxc_hmp4e", + }, + .probe = hmp4e_probe, + .remove = hmp4e_remove, +#ifdef CONFIG_PM + .suspend = hmp4e_suspend, + .resume = hmp4e_resume, +#endif +}; + +static s32 __init hmp4e_init(void) +{ + printk(KERN_INFO "hmp4e: init\n"); + platform_driver_register(&hmp4e_driver); + return 0; +} + +static void __exit hmp4e_cleanup(void) +{ + platform_driver_unregister(&hmp4e_driver); + printk(KERN_INFO "hmp4e: module removed\n"); +} + +module_init(hmp4e_init); +module_exit(hmp4e_cleanup); + +/* module description */ +MODULE_AUTHOR("Hantro Products Oy"); +MODULE_DESCRIPTION("Device driver for Hantro's hardware based MPEG4 encoder"); +MODULE_SUPPORTED_DEVICE("5251/4251 MPEG4 Encoder"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/hmp4e/mxc_hmp4e.h b/drivers/mxc/hmp4e/mxc_hmp4e.h new file mode 100644 index 000000000000..f58831716346 --- /dev/null +++ b/drivers/mxc/hmp4e/mxc_hmp4e.h @@ -0,0 +1,70 @@ +/* + * Copyright 2005-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 + */ + +/* + * Encoder device driver (kernel module headers) + * + * Copyright (C) 2005 Hantro Products Oy. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef _HMP4ENC_H_ +#define _HMP4ENC_H_ +#include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */ + +/* this is for writing data through ioctl to registers*/ +typedef struct { + unsigned long data; + unsigned long offset; +} write_t; + +/* + * Ioctl definitions + */ + +/* Use 'k' as magic number */ +#define HMP4E_IOC_MAGIC 'k' +/* + * S means "Set" through a ptr, + * T means "Tell" directly with the argument value + * G means "Get": reply by setting through a pointer + * Q means "Query": response is on the return value + * X means "eXchange": G and S atomically + * H means "sHift": T and Q atomically + */ +#define HMP4E_IOCGBUFBUSADDRESS _IOR(HMP4E_IOC_MAGIC, 1, unsigned long *) +#define HMP4E_IOCGBUFSIZE _IOR(HMP4E_IOC_MAGIC, 2, unsigned int *) +#define HMP4E_IOCGHWOFFSET _IOR(HMP4E_IOC_MAGIC, 3, unsigned long *) +#define HMP4E_IOCGHWIOSIZE _IOR(HMP4E_IOC_MAGIC, 4, unsigned int *) +#define HMP4E_IOC_CLI _IO(HMP4E_IOC_MAGIC, 5) +#define HMP4E_IOC_STI _IO(HMP4E_IOC_MAGIC, 6) +#define HMP4E_IOCHARDRESET _IO(HMP4E_IOC_MAGIC, 7) +#define HMP4E_IOCSREGWRITE _IOW(HMP4E_IOC_MAGIC, 8, write_t) +#define HMP4E_IOCXREGREAD _IOWR(HMP4E_IOC_MAGIC, 9, unsigned long) + +#define HMP4E_IOC_MAXNR 9 + +#endif /* !_HMP4ENC_H_ */ diff --git a/drivers/mxc/hw_event/Kconfig b/drivers/mxc/hw_event/Kconfig new file mode 100644 index 000000000000..bcf479689501 --- /dev/null +++ b/drivers/mxc/hw_event/Kconfig @@ -0,0 +1,11 @@ +menu "MXC HARDWARE EVENT" + +config MXC_HWEVENT + bool "MXC Hardware Event Handler" + default y + depends on ARCH_MXC + help + If you plan to use the Hardware Event Handler in the MXC, say + Y here. If unsure, select Y. + +endmenu diff --git a/drivers/mxc/hw_event/Makefile b/drivers/mxc/hw_event/Makefile new file mode 100644 index 000000000000..a53fe2b45e04 --- /dev/null +++ b/drivers/mxc/hw_event/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_HWEVENT) += mxc_hw_event.o diff --git a/drivers/mxc/hw_event/mxc_hw_event.c b/drivers/mxc/hw_event/mxc_hw_event.c new file mode 100644 index 000000000000..5451c1c68859 --- /dev/null +++ b/drivers/mxc/hw_event/mxc_hw_event.c @@ -0,0 +1,265 @@ +/* + * Copyright 2007-2009 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 + */ + +/*! + * mxc_hw_event.c + * Collect the hardware events, send to user by netlink + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netlink.h> +#include <linux/sched.h> +#include <linux/list.h> +#include <linux/signal.h> +#include <linux/freezer.h> +#include <linux/kthread.h> +#include <net/sock.h> + +#include <mach/hw_events.h> + +#define EVENT_POOL_SIZE 10 + +struct hw_event_elem { + struct mxc_hw_event event; + struct list_head list; +}; + +static struct sock *nl_event_sock; /* netlink socket */ +static struct list_head event_head; +static struct list_head free_head; +static struct hw_event_elem events_pool[EVENT_POOL_SIZE]; /* event pool */ +static DEFINE_SPINLOCK(list_lock); +static DECLARE_WAIT_QUEUE_HEAD(event_wq); +static unsigned int seq; /* send seq */ +static int initialized; +static struct task_struct *hwevent_kthread; + +/*! + * main HW event handler thread + */ +static int hw_event_thread(void *data) +{ + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + unsigned int size; + struct hw_event_elem *event, *n; + LIST_HEAD(tmp_head); + DEFINE_WAIT(wait); + + while (1) { + + prepare_to_wait(&event_wq, &wait, TASK_INTERRUPTIBLE); + /* wait for event coming */ + if (!freezing(current) && !kthread_should_stop() && + list_empty(&event_head)) + schedule(); + finish_wait(&event_wq, &wait); + + try_to_freeze(); + + if (kthread_should_stop()) + break; + + /* fetch event from list */ + spin_lock_irq(&list_lock); + tmp_head = event_head; + tmp_head.prev->next = &tmp_head; + tmp_head.next->prev = &tmp_head; + /* clear the event list head */ + INIT_LIST_HEAD(&event_head); + spin_unlock_irq(&list_lock); + + list_for_each_entry_safe(event, n, &tmp_head, list) { + + size = NLMSG_SPACE(sizeof(struct mxc_hw_event)); + skb = alloc_skb(size, GFP_KERNEL); + if (!skb) { + /* if failed alloc skb, we drop this event */ + printk(KERN_WARNING + "mxc_hw_event: skb_alloc() failed\n"); + goto alloc_failure; + } + + /* put the netlink header struct to skb */ + nlh = + NLMSG_PUT(skb, 0, seq++, NLMSG_DONE, + size - sizeof(*nlh)); + + /* fill the netlink data */ + memcpy((struct mxc_hw_event *)NLMSG_DATA(nlh), + &event->event, sizeof(struct mxc_hw_event)); + + /* free the event node, set to unused */ + spin_lock_irq(&list_lock); + list_move(&event->list, &free_head); + spin_unlock_irq(&list_lock); + + /* send to all process that create this socket */ + NETLINK_CB(skb).pid = 0; /* sender pid */ + NETLINK_CB(skb).dst_group = HW_EVENT_GROUP; + /* broadcast the event */ + netlink_broadcast(nl_event_sock, skb, 0, HW_EVENT_GROUP, + GFP_KERNEL); + + continue; + nlmsg_failure: + printk(KERN_WARNING + "mxc_hw_event: No tailroom for NLMSG in skb\n"); + alloc_failure: + /* free the event node, set to unused */ + spin_lock_irq(&list_lock); + list_del(&event->list); + list_add_tail(&event->list, &free_head); + spin_unlock_irq(&list_lock); + } + } + + return 0; +} + +/*! + * + * @priority the event priority, REALTIME, EMERENCY, NORMAL + * @new_event event id to be send + */ +int hw_event_send(int priority, struct mxc_hw_event *new_event) +{ + unsigned int size; + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + struct mxc_hw_event *event; + struct hw_event_elem *event_elem; + int ret; + unsigned long flag; + struct list_head *list_node; + + if (!initialized) { + pr_info("HW Event module has not been initialized\n"); + return -1; + } + + if (priority == HWE_HIGH_PRIORITY) { + /** + * the most high priority event, + * we send it immediatly. + */ + + size = NLMSG_SPACE(sizeof(struct mxc_hw_event)); + + /* alloc skb */ + if (in_interrupt()) { + skb = alloc_skb(size, GFP_ATOMIC); + } else { + skb = alloc_skb(size, GFP_KERNEL); + } + if (!skb) { + /* if failed alloc skb, we drop this event */ + printk(KERN_WARNING + "hw_event send: skb_alloc() failed\n"); + goto send_later; + } + + /* put the netlink header struct to skb */ + nlh = NLMSG_PUT(skb, 0, seq++, NLMSG_DONE, size - sizeof(*nlh)); + + /* fill the netlink data */ + event = (struct mxc_hw_event *)NLMSG_DATA(nlh); + memcpy(event, new_event, sizeof(struct mxc_hw_event)); + + /* send to all process that create this socket */ + NETLINK_CB(skb).pid = 0; /* sender pid */ + NETLINK_CB(skb).dst_group = HW_EVENT_GROUP; + /* broadcast the event */ + ret = netlink_broadcast(nl_event_sock, skb, 0, HW_EVENT_GROUP, + in_interrupt()? GFP_ATOMIC : + GFP_KERNEL); + if (ret) { + + nlmsg_failure: + /* send failed */ + kfree_skb(skb); + goto send_later; + } + + return 0; + } + + send_later: + spin_lock_irqsave(&list_lock, flag); + if (list_empty(&free_head)) { + spin_unlock_irqrestore(&list_lock, flag); + /* no more free event node */ + printk(KERN_WARNING "mxc_event send: no more free node\n"); + return -1; + } + + /* get a free node from free list, and added to event list */ + list_node = free_head.next; + /* fill event */ + event_elem = list_entry(list_node, struct hw_event_elem, list); + event_elem->event = *new_event; + list_move(list_node, &event_head); + spin_unlock_irqrestore(&list_lock, flag); + + wake_up(&event_wq); + + return 0; +} + +static int __init mxc_hw_event_init(void) +{ + int i; + + /* initial the list head for event and free */ + INIT_LIST_HEAD(&free_head); + INIT_LIST_HEAD(&event_head); + + /* initial the free list */ + for (i = 0; i < EVENT_POOL_SIZE; i++) + list_add_tail(&events_pool[i].list, &free_head); + + /* create netlink kernel sock */ + nl_event_sock = + netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0, NULL, NULL, + THIS_MODULE); + if (!nl_event_sock) { + printk(KERN_WARNING + "mxc_hw_event: Fail to create netlink socket.\n"); + return 1; + } + + hwevent_kthread = kthread_run(hw_event_thread, NULL, "hwevent"); + if (IS_ERR(hwevent_kthread)) { + printk(KERN_WARNING + "mxc_hw_event: Fail to create hwevent thread.\n"); + return 1; + } + + initialized = 1; + + return 0; +} + +static void __exit mxc_hw_event_exit(void) +{ + kthread_stop(hwevent_kthread); + /* wait for thread completion */ + sock_release(nl_event_sock->sk_socket); +} + +module_init(mxc_hw_event_init); +module_exit(mxc_hw_event_exit); + +EXPORT_SYMBOL(hw_event_send); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ipu/Kconfig b/drivers/mxc/ipu/Kconfig new file mode 100644 index 000000000000..919cb598c294 --- /dev/null +++ b/drivers/mxc/ipu/Kconfig @@ -0,0 +1,4 @@ +config MXC_IPU_V1 + bool + +source "drivers/mxc/ipu/pf/Kconfig" diff --git a/drivers/mxc/ipu/Makefile b/drivers/mxc/ipu/Makefile new file mode 100644 index 000000000000..dc6d42ad75d6 --- /dev/null +++ b/drivers/mxc/ipu/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_MXC_IPU_V1) = mxc_ipu.o + +mxc_ipu-objs := ipu_common.o ipu_sdc.o ipu_adc.o ipu_ic.o ipu_csi.o ipu_device.o + +obj-$(CONFIG_MXC_IPU_PF) += pf/ diff --git a/drivers/mxc/ipu/ipu_adc.c b/drivers/mxc/ipu/ipu_adc.c new file mode 100644 index 000000000000..78bfa0d19381 --- /dev/null +++ b/drivers/mxc/ipu/ipu_adc.c @@ -0,0 +1,689 @@ +/* + * Copyright 2005-2009 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 ipu_adc.c + * + * @brief IPU ADC functions + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/*#define ADC_CHAN1_SA_MASK 0xFF800000 */ + +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width); + +int32_t _ipu_adc_init_channel(ipu_channel_t chan, display_port_t disp, + mcu_mode_t cmd, int16_t x_pos, int16_t y_pos) +{ + uint32_t reg; + uint32_t start_addr, stride; + unsigned long lock_flags; + uint32_t size; + + size = 0; + + switch (disp) { + case DISP0: + reg = __raw_readl(ADC_DISP0_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP1: + reg = __raw_readl(ADC_DISP1_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + case DISP2: + reg = __raw_readl(ADC_DISP2_CONF); + stride = reg & ADC_DISP_CONF_SL_MASK; + break; + default: + return -EINVAL; + } + + if (stride == 0) + return -EINVAL; + + stride++; + start_addr = (y_pos * stride) + x_pos; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + reg |= + ((uint32_t) size << 21 | (uint32_t) disp << 19 | (uint32_t) + cmd << 16); + + __raw_writel(start_addr, ADC_SYSCHA1_SA); + break; + + case ADC_SYS2: + reg &= ~0xFF008000; + reg |= + ((uint32_t) size << 29 | (uint32_t) disp << 27 | (uint32_t) + cmd << 24); + + __raw_writel(start_addr, ADC_SYSCHA2_SA); + break; + + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + reg |= + ((uint32_t) size << 5 | (uint32_t) disp << 3 | + ADC_CONF_PRP_EN); + + __raw_writel(start_addr, ADC_PRPCHAN_SA); + break; + + case MEM_PP_ADC: + reg &= ~0x00003F02; + reg |= + ((uint32_t) size << 10 | (uint32_t) disp << 8 | + ADC_CONF_PP_EN); + + __raw_writel(start_addr, ADC_PPCHAN_SA); + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t _ipu_adc_uninit_channel(ipu_channel_t chan) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(ADC_CONF); + + switch (chan) { + case ADC_SYS1: + reg &= ~0x00FF4000; + break; + case ADC_SYS2: + reg &= ~0xFF008000; + break; + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + reg &= ~0x000000F9; + break; + case MEM_PP_ADC: + reg &= ~0x00003F02; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -1; + break; + } + __raw_writel(reg, ADC_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +int32_t ipu_adc_write_template(display_port_t disp, uint32_t * pCmd, bool write) +{ + uint32_t ima_addr = 0; + uint32_t row_nu; + int i; + + /* Set IPU_IMA_ADDR (IPU Internal Memory Access Address) */ + /* MEM_NU = 0x0001 (CPM) */ + /* ROW_NU = 2*N ( N is channel number) */ + /* WORD_NU = 0 */ + if (write) { + row_nu = (uint32_t) disp *2 * ATM_ADDR_RANGE; + } else { + row_nu = ((uint32_t) disp * 2 + 1) * ATM_ADDR_RANGE; + } + + /* form template addr for IPU_IMA_ADDR */ + ima_addr = (0x3 << 16 /*Template memory */ | row_nu << 3); + + __raw_writel(ima_addr, IPU_IMA_ADDR); + + /* write template data for IPU_IMA_DATA */ + for (i = 0; i < TEMPLATE_BUF_SIZE; i++) + /* only DATA field are needed */ + __raw_writel(pCmd[i], IPU_IMA_DATA); + + return 0; +} + +int32_t +ipu_adc_write_cmd(display_port_t disp, cmddata_t type, + uint32_t cmd, const uint32_t * params, uint16_t numParams) +{ + uint16_t i; + int disable_di = 0; + u32 reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + if ((reg & IPU_CONF_DI_EN) == 0) { + disable_di = 1; + reg |= IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + } + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + __raw_writel((uint32_t) ((type ? 0x0 : 0x1) | disp << 1 | 0x10), + DI_DISP_LLA_CONF); + __raw_writel(cmd, DI_DISP_LLA_DATA); + udelay(3); + + __raw_writel((uint32_t) (0x10 | disp << 1 | 0x11), DI_DISP_LLA_CONF); + for (i = 0; i < numParams; i++) { + __raw_writel(params[i], DI_DISP_LLA_DATA); + udelay(3); + } + + if (disable_di) { + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(IPU_CONF); + reg &= ~IPU_CONF_DI_EN; + __raw_writel(reg, IPU_CONF); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } + + return 0; +} + +int32_t ipu_adc_set_update_mode(ipu_channel_t channel, + ipu_adc_update_mode_t mode, + uint32_t refresh_rate, unsigned long addr, + uint32_t * size) +{ + int32_t err = 0; + uint32_t ref_per, reg, src = 0; + unsigned long lock_flags; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPU_FS_DISP_FLOW); + reg &= ~FS_AUTO_REF_PER_MASK; + switch (mode) { + case IPU_ADC_REFRESH_NONE: + src = 0; + break; + case IPU_ADC_AUTO_REFRESH: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF; + break; + case IPU_ADC_AUTO_REFRESH_SNOOP: + if (refresh_rate == 0) { + err = -EINVAL; + goto err0; + } + ref_per = ipu_freq / ((1UL << 17) * refresh_rate); + ref_per--; + reg |= ref_per << FS_AUTO_REF_PER_OFFSET; + + src = FS_SRC_AUTOREF_SNOOP; + break; + case IPU_ADC_SNOOPING: + src = FS_SRC_SNOOP; + break; + } + + switch (channel) { + case ADC_SYS1: + reg &= ~FS_ADC1_SRC_SEL_MASK; + reg |= src << FS_ADC1_SRC_SEL_OFFSET; + break; + case ADC_SYS2: + reg &= ~FS_ADC2_SRC_SEL_MASK; + reg |= src << FS_ADC2_SRC_SEL_OFFSET; + break; + default: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + __raw_writel(reg, IPU_FS_DISP_FLOW); + + /* Setup bus snooping */ + if ((mode == IPU_ADC_AUTO_REFRESH_SNOOP) || (mode == IPU_ADC_SNOOPING)) { + err = mxc_snoop_set_config(0, addr, *size); + if (err > 0) { + *size = err; + err = 0; + } + } else { + mxc_snoop_set_config(0, 0, 0); + } + + err0: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return err; +} + +int32_t ipu_adc_get_snooping_status(uint32_t * statl, uint32_t * stath) +{ + return mxc_snoop_get_status(0, statl, stath); +} + +int32_t ipu_adc_init_panel(display_port_t disp, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint32_t stride, + ipu_adc_sig_cfg_t sig, + display_addressing_t addr, + uint32_t vsync_width, vsync_t mode) +{ + uint32_t temp; + unsigned long lock_flags; + uint32_t ser_conf; + uint32_t disp_conf; + uint32_t adc_disp_conf; + uint32_t adc_disp_vsync; + uint32_t old_pol; + + if ((disp != DISP1) && (disp != DISP2) && + (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL)) { + return -EINVAL; + } +/* adc_disp_conf = ((uint32_t)((((size == 3)||(size == 2))?1:0)<<14) | */ +/* (uint32_t)addr<<12 | (stride-1)); */ + adc_disp_conf = (uint32_t) addr << 12 | (stride - 1); + + _ipu_set_cmd_data_mappings(disp, pixel_fmt, sig.ifc_width); + + spin_lock_irqsave(&ipu_lock, lock_flags); + disp_conf = __raw_readl(DI_DISP_IF_CONF); + old_pol = __raw_readl(DI_DISP_SIG_POL); + adc_disp_vsync = __raw_readl(ADC_DISP_VSYNC); + + switch (disp) { + case DISP0: + __raw_writel(adc_disp_conf, ADC_DISP0_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP0_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D0_MODE_MASK | + ADC_DISP_VSYNC_D0_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x2000003FL; + old_pol |= sig.data_pol | sig.cs_pol << 1 | + sig.addr_pol << 2 | sig.read_pol << 3 | + sig.write_pol << 4 | sig.Vsync_pol << 5 | + sig.burst_pol << 29; + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x0000001FL; + disp_conf |= (sig.burst_mode << 3) | (sig.ifc_mode << 1) | + DI_CONF_DISP0_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP1: + __raw_writel(adc_disp_conf, ADC_DISP1_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x4000FF00L; + old_pol |= (sig.Vsync_pol << 6 | sig.data_pol << 8 | + sig.cs_pol << 9 | sig.addr_pol << 10 | + sig.read_pol << 11 | sig.write_pol << 12 | + sig.clk_pol << 14 | sig.burst_pol << 30); + __raw_writel(old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x00003F00L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP1_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 12); + } + disp_conf |= (sig.ifc_mode << 9) | DI_CONF_DISP1_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + case DISP2: + __raw_writel(adc_disp_conf, ADC_DISP2_CONF); + __raw_writel((((height - 1) << 16) | (width - 1)), + ADC_DISP12_SS); + + adc_disp_vsync &= ~(ADC_DISP_VSYNC_D12_MODE_MASK | + ADC_DISP_VSYNC_D12_WIDTH_MASK); + adc_disp_vsync |= (vsync_width << 16) | (uint32_t) mode; + + old_pol &= ~0x80FF0000L; + temp = (uint32_t) (sig.data_pol << 16 | sig.cs_pol << 17 | + sig.addr_pol << 18 | sig.read_pol << 19 | + sig.write_pol << 20 | sig.Vsync_pol << 6 | + sig.burst_pol << 31 | sig.clk_pol << 22); + __raw_writel(temp | old_pol, DI_DISP_SIG_POL); + + disp_conf &= ~0x003F0000L; + if (sig.ifc_mode >= IPU_ADC_IFC_MODE_3WIRE_SERIAL) { + ser_conf = (sig.ifc_width - 1) << + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET; + if (sig.ser_preamble_len) { + ser_conf |= DI_SER_DISPx_CONF_PREAMBLE_EN; + ser_conf |= sig.ser_preamble << + DI_SER_DISPx_CONF_PREAMBLE_OFFSET; + ser_conf |= (sig.ser_preamble_len - 1) << + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET; + + } + + ser_conf |= + sig.ser_rw_mode << DI_SER_DISPx_CONF_RW_CFG_OFFSET; + + if (sig.burst_mode == IPU_ADC_BURST_SERIAL) + ser_conf |= DI_SER_DISPx_CONF_BURST_MODE_EN; + __raw_writel(ser_conf, DI_SER_DISP2_CONF); + } else { /* parallel interface */ + disp_conf |= (uint32_t) (sig.burst_mode << 20); + } + disp_conf |= (sig.ifc_mode << 17) | DI_CONF_DISP2_EN; + __raw_writel(disp_conf, DI_DISP_IF_CONF); + break; + default: + break; + } + + __raw_writel(adc_disp_vsync, ADC_DISP_VSYNC); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_adc_init_ifc_timing(display_port_t disp, bool read, + uint32_t cycle_time, + uint32_t up_time, + uint32_t down_time, + uint32_t read_latch_time, uint32_t pixel_clk) +{ + uint32_t reg; + uint32_t time_conf3 = 0; + uint32_t clk_per; + uint32_t up_per; + uint32_t down_per; + uint32_t read_per; + uint32_t pixclk_per = 0; + uint32_t ipu_freq; + + ipu_freq = clk_get_rate(g_ipu_clk); + + clk_per = (cycle_time * (ipu_freq / 1000L) * 16L) / 1000000L; + up_per = (up_time * (ipu_freq / 1000L) * 4L) / 1000000L; + down_per = (down_time * (ipu_freq / 1000L) * 4L) / 1000000L; + + reg = (clk_per << DISPx_IF_CLK_PER_OFFSET) | + (up_per << DISPx_IF_CLK_UP_OFFSET) | + (down_per << DISPx_IF_CLK_DOWN_OFFSET); + + if (read) { + read_per = + (read_latch_time * (ipu_freq / 1000L) * 4L) / 1000000L; + if (pixel_clk) + pixclk_per = (ipu_freq * 16L) / pixel_clk; + time_conf3 = (read_per << DISPx_IF_CLK_READ_EN_OFFSET) | + (pixclk_per << DISPx_PIX_CLK_PER_OFFSET); + } + + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_1/2 = 0x%08X\n", reg); + dev_dbg(g_ipu_dev, "DI_DISPx_TIME_CONF_3 = 0x%08X\n", time_conf3); + + switch (disp) { + case DISP0: + if (read) { + __raw_writel(reg, DI_DISP0_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP0_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP0_TIME_CONF_1); + } + break; + case DISP1: + if (read) { + __raw_writel(reg, DI_DISP1_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP1_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP1_TIME_CONF_1); + } + break; + case DISP2: + if (read) { + __raw_writel(reg, DI_DISP2_TIME_CONF_2); + __raw_writel(time_conf3, DI_DISP2_TIME_CONF_3); + } else { + __raw_writel(reg, DI_DISP2_TIME_CONF_1); + } + break; + default: + return -EINVAL; + break; + } + + return 0; +} + +struct ipu_adc_di_map { + uint32_t map_byte1; + uint32_t map_byte2; + uint32_t map_byte3; + uint32_t cycle_cnt; +}; + +static const struct ipu_adc_di_map di_mappings[] = { + [0] = { + /* RGB888, 8-bit bus */ + .map_byte1 = 0x1600AAAA, + .map_byte2 = 0x00E05555, + .map_byte2 = 0x00070000, + .cycle_cnt = 3, + }, + [1] = { + /* RGB666, 8-bit bus */ + .map_byte1 = 0x1C00AAAF, + .map_byte2 = 0x00E0555F, + .map_byte3 = 0x0007000F, + .cycle_cnt = 3, + }, + [2] = { + /* RGB565, 8-bit bus */ + .map_byte1 = 0x008055BF, + .map_byte2 = 0x0142015F, + .map_byte3 = 0x0007003F, + .cycle_cnt = 2, + }, + [3] = { + /* RGB888, 24-bit bus */ + .map_byte1 = 0x0007000F, + .map_byte2 = 0x000F000F, + .map_byte3 = 0x0017000F, + .cycle_cnt = 1, + }, + [4] = { + /* RGB666, 18-bit bus */ + .map_byte1 = 0x0005000F, + .map_byte2 = 0x000B000F, + .map_byte3 = 0x0011000F, + .cycle_cnt = 1, + }, + [5] = { + /* RGB565, 16-bit bus */ + .map_byte1 = 0x0004003F, + .map_byte2 = 0x000A000F, + .map_byte3 = 0x000F003F, + .cycle_cnt = 1, + }, +}; + +/* Private methods */ +static void _ipu_set_cmd_data_mappings(display_port_t disp, + uint32_t pixel_fmt, int ifc_width) +{ + uint32_t reg; + u32 map = 0; + + if (ifc_width == 8) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 0; + break; + case IPU_PIX_FMT_RGB666: + map = 1; + break; + case IPU_PIX_FMT_RGB565: + map = 2; + break; + default: + break; + } + } else if (ifc_width >= 16) { + switch (pixel_fmt) { + case IPU_PIX_FMT_BGR24: + map = 3; + break; + case IPU_PIX_FMT_RGB666: + map = 4; + break; + case IPU_PIX_FMT_RGB565: + map = 5; + break; + default: + break; + } + } + + switch (disp) { + case DISP0: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP0_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP0_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP0_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP0_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP0_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP0_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP0_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP0_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP1: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP1_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP1_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP1_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP1_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP1_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP1_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP1_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP1_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + case DISP2: + if (ifc_width == 8) { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } else { + __raw_writel(0x00070000, DI_DISP2_CB0_MAP); + __raw_writel(0x000F0000, DI_DISP2_CB1_MAP); + __raw_writel(0x0000FFFF, DI_DISP2_CB2_MAP); + } + __raw_writel(di_mappings[map].map_byte1, DI_DISP2_DB0_MAP); + __raw_writel(di_mappings[map].map_byte2, DI_DISP2_DB1_MAP); + __raw_writel(di_mappings[map].map_byte3, DI_DISP2_DB2_MAP); + reg = __raw_readl(DI_DISP_ACC_CC); + reg &= ~DISP2_IF_CLK_CNT_D_MASK; + reg |= (di_mappings[map].cycle_cnt - 1) << + DISP2_IF_CLK_CNT_D_OFFSET; + __raw_writel(reg, DI_DISP_ACC_CC); + break; + default: + break; + } +} + +void ipu_disp_direct_write(ipu_channel_t channel, u32 value, u32 offset) +{ + /*TODO*/ +} + +int ipu_init_async_panel(int disp, int type, uint32_t cycle_time, + uint32_t pixel_fmt, ipu_adc_sig_cfg_t sig) +{ + /*TODO:uniform interface for ipu async panel init*/ + return -1; +} + +EXPORT_SYMBOL(ipu_adc_write_template); +EXPORT_SYMBOL(ipu_adc_write_cmd); +EXPORT_SYMBOL(ipu_adc_set_update_mode); +EXPORT_SYMBOL(ipu_adc_init_panel); +EXPORT_SYMBOL(ipu_adc_init_ifc_timing); diff --git a/drivers/mxc/ipu/ipu_common.c b/drivers/mxc/ipu/ipu_common.c new file mode 100644 index 000000000000..b2795fe83f0d --- /dev/null +++ b/drivers/mxc/ipu/ipu_common.c @@ -0,0 +1,1835 @@ +/* + * Copyright 2005-2009 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 ipu_common.c + * + * @brief This file contains the IPU driver common API functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* + * This type definition is used to define a node in the GPIO interrupt queue for + * registered interrupts for GPIO pins. Each node contains the GPIO signal number + * associated with the ISR and the actual ISR function pointer. + */ +struct ipu_irq_node { + irqreturn_t(*handler) (int, void *); /*!< the ISR */ + const char *name; /*!< device associated with the interrupt */ + void *dev_id; /*!< some unique information for the ISR */ + __u32 flags; /*!< not used */ +}; + +/* Globals */ +struct clk *g_ipu_clk; +struct clk *g_ipu_csi_clk; +static struct clk *dfm_clk; +int g_ipu_irq[2]; +int g_ipu_hw_rev; +bool g_sec_chan_en[21]; +uint32_t g_channel_init_mask; +DEFINE_SPINLOCK(ipu_lock); +struct device *g_ipu_dev; + +static struct ipu_irq_node ipu_irq_list[IPU_IRQ_COUNT]; +static const char driver_name[] = "mxc_ipu"; + +static uint32_t g_ipu_config = 0; +static uint32_t g_channel_init_mask_backup = 0; +static bool g_csi_used = false; + +/* Static functions */ +static irqreturn_t ipu_irq_handler(int irq, void *desc); +static void _ipu_pf_init(ipu_channel_params_t * params); +static void _ipu_pf_uninit(void); + +inline static uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((type == IPU_INPUT_BUFFER) ? ((uint32_t) ch & 0xFF) : + ((type == IPU_OUTPUT_BUFFER) ? (((uint32_t) ch >> 8) & 0xFF) + : (((uint32_t) ch >> 16) & 0xFF))); +}; + +inline static uint32_t DMAParamAddr(uint32_t dma_ch) +{ + return (0x10000 | (dma_ch << 4)); +}; + +/*! + * This function is called by the driver framework to initialize the IPU + * hardware. + * + * @param dev The device structure for the IPU passed in by the framework. + * + * @return This function returns 0 on success or negative error code on error + */ +static +int ipu_probe(struct platform_device *pdev) +{ +// struct platform_device *pdev = to_platform_device(dev); + struct mxc_ipu_config *ipu_conf = pdev->dev.platform_data; + + spin_lock_init(&ipu_lock); + + g_ipu_dev = &pdev->dev; + g_ipu_hw_rev = ipu_conf->rev; + + /* Register IPU interrupts */ + g_ipu_irq[0] = platform_get_irq(pdev, 0); + if (g_ipu_irq[0] < 0) + return -EINVAL; + + if (request_irq(g_ipu_irq[0], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request SYNC interrupt failed\n"); + return -EBUSY; + } + /* Some platforms have 2 IPU interrupts */ + g_ipu_irq[1] = platform_get_irq(pdev, 1); + if (g_ipu_irq[1] >= 0) { + if (request_irq + (g_ipu_irq[1], ipu_irq_handler, 0, driver_name, 0) != 0) { + dev_err(g_ipu_dev, "request ERR interrupt failed\n"); + return -EBUSY; + } + } + + /* Enable IPU and CSI clocks */ + /* Get IPU clock freq */ + g_ipu_clk = clk_get(&pdev->dev, "ipu_clk"); + dev_dbg(g_ipu_dev, "ipu_clk = %lu\n", clk_get_rate(g_ipu_clk)); + + g_ipu_csi_clk = clk_get(&pdev->dev, "csi_clk"); + + dfm_clk = clk_get(NULL, "dfm_clk"); + + clk_enable(g_ipu_clk); + + /* resetting the CONF register of the IPU */ + __raw_writel(0x00000000, IPU_CONF); + + __raw_writel(0x00100010L, DI_HSP_CLK_PER); + + /* Set SDC refresh channels as high priority */ + __raw_writel(0x0000C000L, IDMAC_CHA_PRI); + + /* Set to max back to back burst requests */ + __raw_writel(0x00000000L, IDMAC_CONF); + + register_ipu_device(); + + return 0; +} + +/*! + * This function is called to initialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to initalize. + * + * @param params Input parameter containing union of channel initialization + * parameters. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t * params) +{ + uint32_t ipu_conf; + uint32_t reg; + unsigned long lock_flags; + + dev_dbg(g_ipu_dev, "init channel = %d\n", IPU_CHAN_ID(channel)); + + if ((channel != MEM_SDC_BG) && (channel != MEM_SDC_FG) && + (channel != MEM_ROT_ENC_MEM) && (channel != MEM_ROT_VF_MEM) && + (channel != MEM_ROT_PP_MEM) && (channel != CSI_MEM) + && (params == NULL)) { + return -EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = __raw_readl(IPU_CONF); + if (ipu_conf == 0) { + clk_enable(g_ipu_clk); + } + + if (g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already initialized %d\n", + IPU_CHAN_ID(channel)); + } + + switch (channel) { + case CSI_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, true); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PRPVF_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(CSI_PRP_VF_ADC, + params->csi_prp_vf_adc.disp, + WriteTemplateNonSeq, + params->csi_prp_vf_adc.out_left, + params->csi_prp_vf_adc.out_top); + + _ipu_ic_init_prpvf(params, true); + break; + case MEM_PRP_VF_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_VF_IN_VALID, IPU_FS_PROC_FLOW); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, false); + break; + case MEM_ROT_VF_MEM: + _ipu_ic_init_rotate_vf(params); + break; + case CSI_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, true); + break; + case MEM_PRP_ENC_MEM: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | FS_ENC_IN_VALID, IPU_FS_PROC_FLOW); + _ipu_ic_init_prpenc(params, false); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_init_rotate_enc(params); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg | (FS_DEST_ADC << FS_PP_DEST_SEL_OFFSET), + IPU_FS_PROC_FLOW); + + _ipu_adc_init_channel(MEM_PP_ADC, params->mem_pp_adc.disp, + WriteTemplateNonSeq, + params->mem_pp_adc.out_left, + params->mem_pp_adc.out_top); + + if (params->mem_pp_adc.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_PP_MEM: + if (params->mem_pp_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_pp(params); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_init_rotate_pp(params); + break; + case CSI_MEM: + _ipu_ic_init_csi(params); + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + /* Enable PF block */ + _ipu_pf_init(params); + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_init_channel(ADC_SYS1, params->adc_sys1.disp, + params->adc_sys1.ch_mode, + params->adc_sys1.out_left, + params->adc_sys1.out_top); + break; + case ADC_SYS2: + _ipu_adc_init_channel(ADC_SYS2, params->adc_sys2.disp, + params->adc_sys2.ch_mode, + params->adc_sys2.out_left, + params->adc_sys2.out_top); + break; + default: + dev_err(g_ipu_dev, "Missing channel initialization\n"); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + /* Enable IPU sub module */ + g_channel_init_mask |= 1L << IPU_CHAN_ID(channel); + + if (g_channel_init_mask & 0x00000066L) { /*CSI */ + ipu_conf |= IPU_CONF_CSI_EN; + if (cpu_is_mx31() || cpu_is_mx32()) { + g_csi_used = true; + } + } + if (g_channel_init_mask & 0x00001FFFL) { /*IC */ + ipu_conf |= IPU_CONF_IC_EN; + } + if (g_channel_init_mask & 0x00000A10L) { /*ROT */ + ipu_conf |= IPU_CONF_ROT_EN; + } + if (g_channel_init_mask & 0x0001C000L) { /*SDC */ + ipu_conf |= IPU_CONF_SDC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00061140L) { /*ADC */ + ipu_conf |= IPU_CONF_ADC_EN | IPU_CONF_DI_EN; + } + if (g_channel_init_mask & 0x00380000L) { /*PF */ + ipu_conf |= IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to uninitialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to uninitalize. + */ +void ipu_uninit_channel(ipu_channel_t channel) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t dma, mask = 0; + uint32_t ipu_conf; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if ((g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already uninitialized %d\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Make sure channel is disabled */ + /* Get input and output dma channels */ + dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) + mask |= 1UL << dma; + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (dma != IDMA_CHAN_INVALID) { + mask |= 1UL << dma; + } + } + if (mask & __raw_readl(IDMAC_CHA_EN)) { + dev_err(g_ipu_dev, + "Channel %d is not disabled, disable first\n", + IPU_CHAN_ID(channel)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return; + } + + /* Reset the double buffer */ + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + __raw_writel(reg & ~mask, IPU_CHA_DB_MODE_SEL); + + g_sec_chan_en[IPU_CHAN_ID(channel)] = false; + + switch (channel) { + case CSI_MEM: + _ipu_ic_uninit_csi(); + break; + case CSI_PRP_VF_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PRPVF_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(CSI_PRP_VF_ADC); + + /* Fall thru */ + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + _ipu_ic_uninit_prpvf(); + break; + case MEM_PRP_VF_ADC: + break; + case MEM_ROT_VF_MEM: + _ipu_ic_uninit_rotate_vf(); + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + _ipu_ic_uninit_prpenc(); + break; + case MEM_ROT_ENC_MEM: + _ipu_ic_uninit_rotate_enc(); + break; + case MEM_PP_ADC: + reg = __raw_readl(IPU_FS_PROC_FLOW); + __raw_writel(reg & ~FS_PP_DEST_SEL_MASK, IPU_FS_PROC_FLOW); + + _ipu_adc_uninit_channel(MEM_PP_ADC); + + /* Fall thru */ + case MEM_PP_MEM: + _ipu_ic_uninit_pp(); + break; + case MEM_ROT_PP_MEM: + _ipu_ic_uninit_rotate_pp(); + break; + + case MEM_PF_Y_MEM: + _ipu_pf_uninit(); + break; + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + break; + + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + _ipu_adc_uninit_channel(ADC_SYS1); + break; + case ADC_SYS2: + _ipu_adc_uninit_channel(ADC_SYS2); + break; + case MEM_SDC_MASK: + case CHAN_NONE: + break; + } + + g_channel_init_mask &= ~(1L << IPU_CHAN_ID(channel)); + + ipu_conf = __raw_readl(IPU_CONF); + if ((g_channel_init_mask & 0x00000066L) == 0) { /*CSI */ + ipu_conf &= ~IPU_CONF_CSI_EN; + } + if ((g_channel_init_mask & 0x00001FFFL) == 0) { /*IC */ + ipu_conf &= ~IPU_CONF_IC_EN; + } + if ((g_channel_init_mask & 0x00000A10L) == 0) { /*ROT */ + ipu_conf &= ~IPU_CONF_ROT_EN; + } + if ((g_channel_init_mask & 0x0001C000L) == 0) { /*SDC */ + ipu_conf &= ~IPU_CONF_SDC_EN; + } + if ((g_channel_init_mask & 0x00061140L) == 0) { /*ADC */ + ipu_conf &= ~IPU_CONF_ADC_EN; + } + if ((g_channel_init_mask & 0x0007D140L) == 0) { /*DI */ + ipu_conf &= ~IPU_CONF_DI_EN; + } + if ((g_channel_init_mask & 0x00380000L) == 0) { /*PF */ + ipu_conf &= ~IPU_CONF_PF_EN; + } + __raw_writel(ipu_conf, IPU_CONF); + if (ipu_conf == 0) { + clk_disable(g_ipu_clk); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function is called to initialize a buffer for logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than \b IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of rotation + * channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u, uint32_t v) +{ + uint32_t params[10]; + unsigned long lock_flags; + uint32_t reg; + uint32_t dma_chan; + + dma_chan = channel_2_dma(channel, type); + + if (stride < width * bytes_per_pixel(pixel_fmt)) + stride = width * bytes_per_pixel(pixel_fmt); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (stride % 4) { + dev_err(g_ipu_dev, + "Stride must be 32-bit aligned, stride = %d\n", stride); + return -EINVAL; + } + /* IC channels' width must be multiple of 8 pixels */ + if ((dma_chan <= 13) && (width % 8)) { + dev_err(g_ipu_dev, "width must be 8 pixel multiple\n"); + return -EINVAL; + } + /* Build parameter memory data for DMA channel */ + _ipu_ch_param_set_size(params, pixel_fmt, width, height, stride, u, v); + _ipu_ch_param_set_buffer(params, phyaddr_0, phyaddr_1); + _ipu_ch_param_set_rotation(params, rot_mode); + /* Some channels (rotation) have restriction on burst length */ + if ((dma_chan == 10) || (dma_chan == 11) || (dma_chan == 13)) { + _ipu_ch_param_set_burst_size(params, 8); + } else if (dma_chan == 24) { /* PF QP channel */ + _ipu_ch_param_set_burst_size(params, 4); + } else if (dma_chan == 25) { /* PF H264 BS channel */ + _ipu_ch_param_set_burst_size(params, 16); + } else if (((dma_chan == 14) || (dma_chan == 15)) && + pixel_fmt == IPU_PIX_FMT_RGB565) { + _ipu_ch_param_set_burst_size(params, 16); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + _ipu_write_param_mem(DMAParamAddr(dma_chan), params, 10); + + reg = __raw_readl(IPU_CHA_DB_MODE_SEL); + if (phyaddr_1) { + reg |= 1UL << dma_chan; + } else { + reg &= ~(1UL << dma_chan); + } + __raw_writel(reg, IPU_CHA_DB_MODE_SEL); + + /* Reset to buffer 0 */ + __raw_writel(1UL << dma_chan, IPU_CHA_CUR_BUF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function is called to update the physical address of a buffer for + * a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number to update. + * 0 or 1 are the only valid values. + * + * @param phyaddr Input parameter buffer physical address. + * + * @return This function returns 0 on success or negative error code on + * fail. This function will fail if the buffer is set to ready. + */ +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bufNum == 0) { + reg = __raw_readl(IPU_CHA_BUF0_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0008UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } else { + reg = __raw_readl(IPU_CHA_BUF1_RDY); + if (reg & (1UL << dma_chan)) { + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EACCES; + } + __raw_writel(DMAParamAddr(dma_chan) + 0x0009UL, IPU_IMA_ADDR); + __raw_writel(phyaddr, IPU_IMA_DATA); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + dev_dbg(g_ipu_dev, "IPU: update IDMA ch %d buf %d = 0x%08X\n", + dma_chan, bufNum, phyaddr); + return 0; +} + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_select_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + __raw_writel(1UL << dma_chan, IPU_CHA_BUF0_RDY); + } else { + /*Mark buffer 1 as ready. */ + __raw_writel(1UL << dma_chan, IPU_CHA_BUF1_RDY); + } + return 0; +} + +/*! + * This function links 2 channels together for automatic frame + * synchronization. The output of the source channel is linked to the input of + * the destination channel. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + bool isProc; + uint32_t value; + uint32_t mask; + uint32_t offset; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /* PROCESS THE OUTPUT DMA CH */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Link VF->"); + isProc = true; + mask = FS_PRPVF_DEST_SEL_MASK; + offset = FS_PRPVF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_11) ? FS_DEST_ROT : /*->VF_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + /* ->ADCDirect */ + 0; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Link VF_ROT->"); + isProc = true; + mask = FS_PRPVF_ROT_DEST_SEL_MASK; + offset = FS_PRPVF_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /*->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Link ENC->"); + isProc = true; + mask = 0; + offset = 0; + value = (in_dma == IDMA_IC_10) ? FS_PRPENC_DEST_SEL : /*->ENC_ROT */ + 0; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Link PP->"); + isProc = true; + mask = FS_PP_DEST_SEL_MASK; + offset = FS_PP_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_13) ? FS_DEST_ROT : /*->PP_ROT */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + /* ->ADCDirect */ + 0; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Link PP_ROT->"); + isProc = true; + mask = FS_PP_ROT_DEST_SEL_MASK; + offset = FS_PP_ROT_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_DEST_ROT : /*->PP */ + (in_dma == IDMA_ADC_SYS1_WR) ? FS_DEST_ADC1 : /* ->ADC1 */ + (in_dma == IDMA_ADC_SYS2_WR) ? FS_DEST_ADC2 : /* ->ADC2 */ + (in_dma == IDMA_SDC_BG) ? FS_DEST_SDC_BG : /*->SDC_BG */ + (in_dma == IDMA_SDC_FG) ? FS_DEST_SDC_FG : /*->SDC_FG */ + 0; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Link PF->"); + isProc = true; + mask = FS_PF_DEST_SEL_MASK; + offset = FS_PF_DEST_SEL_OFFSET; + value = (in_dma == IDMA_IC_5) ? FS_PF_DEST_PP : + (in_dma == IDMA_IC_13) ? FS_PF_DEST_ROT : 0; + break; + + /* Invalid Chainings: ENC_ROT-> */ + default: + pr_debug("Link Invalid->"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + /* PROCESS THE INPUT DMA CH */ + switch (in_dma) { + /* ->VF_ROT */ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_1) ? FS_PRPVF_ROT_SRC_SEL : /*VF-> */ + 0; + break; + + /* ->ENC_ROT */ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + isProc = true; + mask = 0; + offset = 0; + value = (out_dma == IDMA_IC_0) ? FS_PRPENC_ROT_SRC_SEL : /*ENC-> */ + 0; + break; + + /* ->PP */ + case IDMA_IC_5: + pr_debug("PP\n"); + isProc = true; + mask = FS_PP_SRC_SEL_MASK; + offset = FS_PP_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_12) ? FS_PP_SRC_ROT : /*PP_ROT-> */ + 0; + break; + + /* ->PP_ROT */ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + isProc = true; + mask = FS_PP_ROT_SRC_SEL_MASK; + offset = FS_PP_ROT_SRC_SEL_OFFSET; + value = (out_dma == IDMA_PF_Y_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_U_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_PF_V_OUT) ? FS_PP_SRC_PF : /*PF-> */ + (out_dma == IDMA_IC_2) ? FS_ROT_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_BG */ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + isProc = false; + mask = FS_SDC_BG_SRC_SEL_MASK; + offset = FS_SDC_BG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->SDC_FG */ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + isProc = false; + mask = FS_SDC_FG_SRC_SEL_MASK; + offset = FS_SDC_FG_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC1 */ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + isProc = false; + mask = FS_ADC1_SRC_SEL_MASK; + offset = FS_ADC1_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /* ->ADC2 */ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + isProc = false; + mask = FS_ADC2_SRC_SEL_MASK; + offset = FS_ADC2_SRC_SEL_OFFSET; + value = (out_dma == IDMA_IC_9) ? FS_SRC_ROT_VF : /*VF_ROT-> */ + (out_dma == IDMA_IC_12) ? FS_SRC_ROT_PP : /*PP_ROT-> */ + (out_dma == IDMA_IC_1) ? FS_SRC_VF : /*VF-> */ + (out_dma == IDMA_IC_2) ? FS_SRC_PP : /*PP-> */ + 0; + break; + + /*Invalid chains: */ + /* ->ENC, ->VF, ->PF, ->VF_COMBINE, ->PP_COMBINE */ + default: + pr_debug("Invalid\n"); + value = 0; + break; + + } + + if (value) { + if (isProc) { + fs_proc_flow &= ~mask; + fs_proc_flow |= (value << offset); + } else { + fs_disp_flow &= ~mask; + fs_disp_flow |= (value << offset); + } + } else { + dev_err(g_ipu_dev, "Invalid channel chaining %d -> %d\n", + out_dma, in_dma); + return -EINVAL; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function unlinks 2 channels and disables automatic frame + * synchronization. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + unsigned long lock_flags; + uint32_t out_dma; + uint32_t in_dma; + uint32_t fs_proc_flow; + uint32_t fs_disp_flow; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow = __raw_readl(IPU_FS_PROC_FLOW); + fs_disp_flow = __raw_readl(IPU_FS_DISP_FLOW); + + out_dma = (1UL << channel_2_dma(src_ch, IPU_OUTPUT_BUFFER)); + in_dma = (1UL << channel_2_dma(dest_ch, IPU_INPUT_BUFFER)); + + /*clear the src_ch's output destination */ + switch (out_dma) { + /*VF-> */ + case IDMA_IC_1: + pr_debug("Unlink VF->"); + fs_proc_flow &= ~FS_PRPVF_DEST_SEL_MASK; + break; + + /*VF_ROT-> */ + case IDMA_IC_9: + pr_debug("Unlink VF_Rot->"); + fs_proc_flow &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + break; + + /*ENC-> */ + case IDMA_IC_0: + pr_debug("Unlink ENC->"); + fs_proc_flow &= ~FS_PRPENC_DEST_SEL; + break; + + /*PP-> */ + case IDMA_IC_2: + pr_debug("Unlink PP->"); + fs_proc_flow &= ~FS_PP_DEST_SEL_MASK; + break; + + /*PP_ROT-> */ + case IDMA_IC_12: + pr_debug("Unlink PP_ROT->"); + fs_proc_flow &= ~FS_PP_ROT_DEST_SEL_MASK; + break; + + /*PF-> */ + case IDMA_PF_Y_OUT: + case IDMA_PF_U_OUT: + case IDMA_PF_V_OUT: + pr_debug("Unlink PF->"); + fs_proc_flow &= ~FS_PF_DEST_SEL_MASK; + break; + + default: /*ENC_ROT-> */ + pr_debug("Unlink Invalid->"); + break; + } + + /*clear the dest_ch's input source */ + switch (in_dma) { + /*->VF_ROT*/ + case IDMA_IC_11: + pr_debug("VF_ROT\n"); + fs_proc_flow &= ~FS_PRPVF_ROT_SRC_SEL; + break; + + /*->Enc_ROT*/ + case IDMA_IC_10: + pr_debug("ENC_ROT\n"); + fs_proc_flow &= ~FS_PRPENC_ROT_SRC_SEL; + break; + + /*->PP*/ + case IDMA_IC_5: + pr_debug("PP\n"); + fs_proc_flow &= ~FS_PP_SRC_SEL_MASK; + break; + + /*->PP_ROT*/ + case IDMA_IC_13: + pr_debug("PP_ROT\n"); + fs_proc_flow &= ~FS_PP_ROT_SRC_SEL_MASK; + break; + + /*->SDC_FG*/ + case IDMA_SDC_FG: + pr_debug("SDC_FG\n"); + fs_disp_flow &= ~FS_SDC_FG_SRC_SEL_MASK; + break; + + /*->SDC_BG*/ + case IDMA_SDC_BG: + pr_debug("SDC_BG\n"); + fs_disp_flow &= ~FS_SDC_BG_SRC_SEL_MASK; + break; + + /*->ADC1*/ + case IDMA_ADC_SYS1_WR: + pr_debug("ADC_SYS1\n"); + fs_disp_flow &= ~FS_ADC1_SRC_SEL_MASK; + break; + + /*->ADC2*/ + case IDMA_ADC_SYS2_WR: + pr_debug("ADC_SYS2\n"); + fs_disp_flow &= ~FS_ADC2_SRC_SEL_MASK; + break; + + default: /*->VF, ->ENC, ->VF_COMBINE, ->PP_COMBINE, ->PF*/ + pr_debug("Invalid\n"); + break; + } + + __raw_writel(fs_proc_flow, IPU_FS_PROC_FLOW); + __raw_writel(fs_disp_flow, IPU_FS_DISP_FLOW); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function check whether a logical channel was enabled. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 1 while request channel is enabled or + * 0 for not enabled. + */ +int32_t ipu_is_channel_busy(ipu_channel_t channel) +{ + uint32_t reg; + uint32_t in_dma; + uint32_t out_dma; + + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + + reg = __raw_readl(IDMAC_CHA_EN); + + if (reg & ((1UL << out_dma) | (1UL << in_dma))) + return 1; + return 0; +} +EXPORT_SYMBOL(ipu_is_channel_busy); + +/*! + * This function enables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_channel(ipu_channel_t channel) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t sec_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN); + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + reg |= 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + reg |= 1UL << in_dma; + + /* Get secondary input dma channel */ + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) { + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) { + reg |= 1UL << sec_dma; + } + } + + __raw_writel(reg | chan_mask, IDMAC_CHA_EN); + + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_enable_task(channel); + } else if (channel == MEM_SDC_BG) { + dev_dbg(g_ipu_dev, "Initializing SDC BG\n"); + _ipu_sdc_bg_init(NULL); + } else if (channel == MEM_SDC_FG) { + dev_dbg(g_ipu_dev, "Initializing SDC FG\n"); + _ipu_sdc_fg_init(NULL); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} + +/*! + * This function disables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param wait_for_stop Flag to set whether to wait for channel end + * of frame or return immediately. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t sec_dma; + uint32_t in_dma; + uint32_t out_dma; + uint32_t chan_mask = 0; + uint32_t timeout; + uint32_t eof_intr; + uint32_t enabled; + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + if (out_dma != IDMA_CHAN_INVALID) + chan_mask = 1UL << out_dma; + in_dma = channel_2_dma(channel, IPU_INPUT_BUFFER); + if (in_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << in_dma; + sec_dma = channel_2_dma(channel, IPU_SEC_INPUT_BUFFER); + if (sec_dma != IDMA_CHAN_INVALID) + chan_mask |= 1UL << sec_dma; + + if (wait_for_stop && channel != MEM_SDC_FG && channel != MEM_SDC_BG) { + timeout = 40; + while ((__raw_readl(IDMAC_CHA_BUSY) & chan_mask) || + (_ipu_channel_status(channel) == TASK_STAT_ACTIVE)) { + timeout--; + msleep(10); + if (timeout == 0) { + printk + (KERN_INFO + "MXC IPU: Warning - timeout waiting for channel to stop,\n" + "\tbuf0_rdy = 0x%08X, buf1_rdy = 0x%08X\n" + "\tbusy = 0x%08X, tstat = 0x%08X\n\tmask = 0x%08X\n", + __raw_readl(IPU_CHA_BUF0_RDY), + __raw_readl(IPU_CHA_BUF1_RDY), + __raw_readl(IDMAC_CHA_BUSY), + __raw_readl(IPU_TASKS_STAT), chan_mask); + break; + } + } + dev_dbg(g_ipu_dev, "timeout = %d * 10ms\n", 40 - timeout); + } + /* SDC BG and FG must be disabled before DMA is disabled */ + if ((channel == MEM_SDC_BG) || (channel == MEM_SDC_FG)) { + + if (channel == MEM_SDC_BG) + eof_intr = IPU_IRQ_SDC_BG_EOF; + else + eof_intr = IPU_IRQ_SDC_FG_EOF; + + /* Wait for any buffer flips to finsh */ + timeout = 4; + while (timeout && + ((__raw_readl(IPU_CHA_BUF0_RDY) & chan_mask) || + (__raw_readl(IPU_CHA_BUF1_RDY) & chan_mask))) { + msleep(10); + timeout--; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + ipu_clear_irq(eof_intr); + if (channel == MEM_SDC_BG) + enabled = _ipu_sdc_bg_uninit(); + else + enabled = _ipu_sdc_fg_uninit(); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (enabled && wait_for_stop) { + timeout = 5; + } else { + timeout = 0; + } + while (timeout && !ipu_get_irq_status(eof_intr)) { + msleep(5); + timeout--; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Disable IC task */ + if (IPU_CHAN_ID(channel) <= IPU_CHAN_ID(MEM_PP_ADC)) { + _ipu_ic_disable_task(channel); + } + + /* Disable DMA channel(s) */ + reg = __raw_readl(IDMAC_CHA_EN); + __raw_writel(reg & ~chan_mask, IDMAC_CHA_EN); + + /* Clear DMA related interrupts */ + __raw_writel(chan_mask, IPU_INT_STAT_1); + __raw_writel(chan_mask, IPU_INT_STAT_2); + __raw_writel(chan_mask, IPU_INT_STAT_4); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +static +irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + uint32_t line_base = 0; + uint32_t line; + irqreturn_t result = IRQ_NONE; + uint32_t int_stat; + + int_stat = __raw_readl(IPU_INT_STAT_1); + int_stat &= __raw_readl(IPU_INT_CTRL_1); + __raw_writel(int_stat, IPU_INT_STAT_1); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 32; + int_stat = __raw_readl(IPU_INT_STAT_2); + int_stat &= __raw_readl(IPU_INT_CTRL_2); + __raw_writel(int_stat, IPU_INT_STAT_2); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 64; + int_stat = __raw_readl(IPU_INT_STAT_3); + int_stat &= __raw_readl(IPU_INT_CTRL_3); + __raw_writel(int_stat, IPU_INT_STAT_3); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 96; + int_stat = __raw_readl(IPU_INT_STAT_4); + int_stat &= __raw_readl(IPU_INT_CTRL_4); + __raw_writel(int_stat, IPU_INT_STAT_4); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + line_base = 128; + int_stat = __raw_readl(IPU_INT_STAT_5); + int_stat &= __raw_readl(IPU_INT_CTRL_5); + __raw_writel(int_stat, IPU_INT_STAT_5); + while ((line = ffs(int_stat)) != 0) { + int_stat &= ~(1UL << (line - 1)); + line += line_base - 1; + result |= + ipu_irq_list[line].handler(line, ipu_irq_list[line].dev_id); + } + + return result; +} + +/*! + * This function enables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to enable interrupt for. + * + */ +void ipu_enable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg |= IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function disables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to disable interrupt for. + * + */ +void ipu_disable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg &= ~IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * This function clears the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to clear interrupt for. + * + */ +void ipu_clear_irq(uint32_t irq) +{ + __raw_writel(IPUIRQ_2_MASK(irq), IPUIRQ_2_STATREG(irq)); +} + +/*! + * This function returns the current interrupt status for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @return Returns true if the interrupt is pending/asserted or false if + * the interrupt is not pending. + */ +bool ipu_get_irq_status(uint32_t irq) +{ + uint32_t reg = __raw_readl(IPUIRQ_2_STATREG(irq)); + + if (reg & IPUIRQ_2_MASK(irq)) + return true; + else + return false; +} + +/*! + * This function registers an interrupt handler function for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param handler Input parameter for address of the handler + * function. + * + * @param irq_flags Flags for interrupt mode. Currently not used. + * + * @param devname Input parameter for string name of driver + * registering the handler. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id) +{ + unsigned long lock_flags; + + BUG_ON(irq >= IPU_IRQ_COUNT); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (ipu_irq_list[irq].handler != NULL) { + dev_err(g_ipu_dev, + "ipu_request_irq - handler already installed on irq %d\n", + irq); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + ipu_irq_list[irq].handler = handler; + ipu_irq_list[irq].flags = irq_flags; + ipu_irq_list[irq].dev_id = dev_id; + ipu_irq_list[irq].name = devname; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + ipu_enable_irq(irq); /* enable the interrupt */ + + return 0; +} + +/*! + * This function unregisters an interrupt handler for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. This must match value passed to + * ipu_request_irq(). + * + */ +void ipu_free_irq(uint32_t irq, void *dev_id) +{ + ipu_disable_irq(irq); /* disable the interrupt */ + + if (ipu_irq_list[irq].dev_id == dev_id) { + ipu_irq_list[irq].handler = NULL; + } +} + +/*! + * This function sets the post-filter pause row for h.264 mode. + * + * @param pause_row The last row to process before pausing. + * + * @return This function returns 0 on success or negative error code on + * fail. + * + */ +int32_t ipu_pf_set_pause_row(uint32_t pause_row) +{ + int32_t retval = 0; + uint32_t timeout = 5; + unsigned long lock_flags; + uint32_t reg; + + reg = __raw_readl(IPU_TASKS_STAT); + while ((reg & TSTAT_PF_MASK) && ((reg & TSTAT_PF_H264_PAUSE) == 0)) { + timeout--; + msleep(5); + if (timeout == 0) { + dev_err(g_ipu_dev, "PF Timeout - tstat = 0x%08X\n", + __raw_readl(IPU_TASKS_STAT)); + retval = -ETIMEDOUT; + goto err0; + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(PF_CONF); + + /* Set the pause row */ + if (pause_row) { + reg &= ~PF_CONF_PAUSE_ROW_MASK; + reg |= PF_CONF_PAUSE_EN | pause_row << PF_CONF_PAUSE_ROW_SHIFT; + } else { + reg &= ~(PF_CONF_PAUSE_EN | PF_CONF_PAUSE_ROW_MASK); + } + __raw_writel(reg, PF_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + err0: + return retval; +} + +/* Private functions */ +void _ipu_write_param_mem(uint32_t addr, uint32_t * data, uint32_t numWords) +{ + for (; numWords > 0; numWords--) { + dev_dbg(g_ipu_dev, + "write param mem - addr = 0x%08X, data = 0x%08X\n", + addr, *data); + __raw_writel(addr, IPU_IMA_ADDR); + __raw_writel(*data++, IPU_IMA_DATA); + addr++; + if ((addr & 0x7) == 5) { + addr &= ~0x7; /* set to word 0 */ + addr += 8; /* increment to next row */ + } + } +} + +static void _ipu_pf_init(ipu_channel_params_t * params) +{ + uint32_t reg; + + /*Setup the type of filtering required */ + switch (params->mem_pf_mem.operation) { + case PF_MPEG4_DEBLOCK: + case PF_MPEG4_DERING: + case PF_MPEG4_DEBLOCK_DERING: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + break; + case PF_H264_DEBLOCK: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = true; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = true; + break; + default: + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; + return; + break; + } + reg = params->mem_pf_mem.operation; + __raw_writel(reg, PF_CONF); +} + +static void _ipu_pf_uninit(void) +{ + __raw_writel(0x0L, PF_CONF); + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_Y_MEM)] = false; + g_sec_chan_en[IPU_CHAN_ID(MEM_PF_U_MEM)] = false; +} + +uint32_t _ipu_channel_status(ipu_channel_t channel) +{ + uint32_t stat = 0; + uint32_t task_stat_reg = __raw_readl(IPU_TASKS_STAT); + + switch (channel) { + case CSI_MEM: + stat = + (task_stat_reg & TSTAT_CSI2MEM_MASK) >> + TSTAT_CSI2MEM_OFFSET; + break; + case CSI_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_ADC: + case MEM_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_ROT_VF_MEM: + stat = + (task_stat_reg & TSTAT_VF_ROT_MASK) >> TSTAT_VF_ROT_OFFSET; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + stat = (task_stat_reg & TSTAT_ENC_MASK) >> TSTAT_ENC_OFFSET; + break; + case MEM_ROT_ENC_MEM: + stat = + (task_stat_reg & TSTAT_ENC_ROT_MASK) >> + TSTAT_ENC_ROT_OFFSET; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + stat = (task_stat_reg & TSTAT_PP_MASK) >> TSTAT_PP_OFFSET; + break; + case MEM_ROT_PP_MEM: + stat = + (task_stat_reg & TSTAT_PP_ROT_MASK) >> TSTAT_PP_ROT_OFFSET; + break; + + case MEM_PF_Y_MEM: + case MEM_PF_U_MEM: + case MEM_PF_V_MEM: + stat = (task_stat_reg & TSTAT_PF_MASK) >> TSTAT_PF_OFFSET; + break; + case MEM_SDC_BG: + break; + case MEM_SDC_FG: + break; + case ADC_SYS1: + stat = + (task_stat_reg & TSTAT_ADCSYS1_MASK) >> + TSTAT_ADCSYS1_OFFSET; + break; + case ADC_SYS2: + stat = + (task_stat_reg & TSTAT_ADCSYS2_MASK) >> + TSTAT_ADCSYS2_OFFSET; + break; + case MEM_SDC_MASK: + default: + stat = TASK_STAT_IDLE; + break; + } + return stat; +} + +uint32_t bytes_per_pixel(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /*generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV422P: + return 1; + break; + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + break; + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + break; + case IPU_PIX_FMT_GENERIC_32: /*generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_ABGR32: + return 4; + break; + default: + return 1; + break; + } + return 0; +} + +void ipu_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + /* TODO */ +} +EXPORT_SYMBOL(ipu_set_csc_coefficients); + +ipu_color_space_t format_to_colorspace(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_RGB32: + return RGB; + break; + + default: + return YCbCr; + break; + } + return RGB; + +} + +static u32 saved_disp3_time_conf; +static bool in_lpdr_mode; +static struct clk *default_ipu_parent_clk; + +int ipu_lowpwr_display_enable(void) +{ + unsigned long rate, div; + struct clk *parent_clk = g_ipu_clk; + + if (in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + default_ipu_parent_clk = clk_get_parent(g_ipu_clk); + in_lpdr_mode = true; + + /* Calculate current pixel clock rate */ + rate = clk_get_rate(g_ipu_clk) * 16; + saved_disp3_time_conf = __raw_readl(DI_DISP3_TIME_CONF); + div = saved_disp3_time_conf & 0xFFF; + rate /= div; + rate *= 4; /* min hsp clk is 4x pixel clk */ + + /* Initialize DFM clock */ + rate = clk_round_rate(dfm_clk, rate); + clk_set_rate(dfm_clk, rate); + clk_enable(dfm_clk); + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + /* Set display clock divider to divide by 4 */ + __raw_writel(((0x8) << 22) | 0x40, DI_DISP3_TIME_CONF); + + clk_set_parent(parent_clk, dfm_clk); + + return 0; +} + +int ipu_lowpwr_display_disable(void) +{ + struct clk *parent_clk = g_ipu_clk; + + if (!in_lpdr_mode || IS_ERR(dfm_clk)) { + return -EINVAL; + } + + if (g_channel_init_mask != (1L << IPU_CHAN_ID(MEM_SDC_BG))) { + dev_err(g_ipu_dev, "LPDR mode requires only SDC BG active.\n"); + return -EINVAL; + } + + in_lpdr_mode = false; + + /* Wait for next VSYNC */ + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC), + IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_SDC_DISP3_VSYNC)) & + IPUIRQ_2_MASK(IPU_IRQ_SDC_DISP3_VSYNC)) == 0) + msleep_interruptible(1); + + __raw_writel(saved_disp3_time_conf, DI_DISP3_TIME_CONF); + clk_set_parent(parent_clk, default_ipu_parent_clk); + clk_disable(dfm_clk); + + return 0; +} + +static int ipu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + g_ipu_config = __raw_readl(IPU_CONF); + clk_enable(g_ipu_csi_clk); + __raw_writel(0x51, IPU_CONF); + g_channel_init_mask_backup = g_channel_init_mask; + g_channel_init_mask |= 2; + } + } else if (cpu_is_mx35()) { + g_ipu_config = __raw_readl(IPU_CONF); + /* Disable the clock of display otherwise the backlight cannot + * be close after camera/tvin related test */ + __raw_writel(g_ipu_config & 0xfbf, IPU_CONF); + } + + return 0; +} + +static int ipu_resume(struct platform_device *pdev) +{ + if (cpu_is_mx31() || cpu_is_mx32()) { + /* work-around for i.Mx31 SR mode after camera related test */ + if (g_csi_used) { + __raw_writel(g_ipu_config, IPU_CONF); + clk_disable(g_ipu_csi_clk); + g_channel_init_mask = g_channel_init_mask_backup; + } + } else if (cpu_is_mx35()) { + __raw_writel(g_ipu_config, IPU_CONF); + } + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "mxc_ipu", + }, + .probe = ipu_probe, + .suspend = ipu_suspend, + .resume = ipu_resume, +}; + +int32_t __init ipu_gen_init(void) +{ + int32_t ret; + + ret = platform_driver_register(&mxcipu_driver); + return 0; +} + +subsys_initcall(ipu_gen_init); + +static void __exit ipu_gen_uninit(void) +{ + if (g_ipu_irq[0]) + free_irq(g_ipu_irq[0], 0); + if (g_ipu_irq[1]) + free_irq(g_ipu_irq[1], 0); + + platform_driver_unregister(&mxcipu_driver); +} + +module_exit(ipu_gen_uninit); + +EXPORT_SYMBOL(ipu_init_channel); +EXPORT_SYMBOL(ipu_uninit_channel); +EXPORT_SYMBOL(ipu_init_channel_buffer); +EXPORT_SYMBOL(ipu_unlink_channels); +EXPORT_SYMBOL(ipu_update_channel_buffer); +EXPORT_SYMBOL(ipu_select_buffer); +EXPORT_SYMBOL(ipu_link_channels); +EXPORT_SYMBOL(ipu_enable_channel); +EXPORT_SYMBOL(ipu_disable_channel); +EXPORT_SYMBOL(ipu_enable_irq); +EXPORT_SYMBOL(ipu_disable_irq); +EXPORT_SYMBOL(ipu_clear_irq); +EXPORT_SYMBOL(ipu_get_irq_status); +EXPORT_SYMBOL(ipu_request_irq); +EXPORT_SYMBOL(ipu_free_irq); +EXPORT_SYMBOL(ipu_pf_set_pause_row); +EXPORT_SYMBOL(bytes_per_pixel); diff --git a/drivers/mxc/ipu/ipu_csi.c b/drivers/mxc/ipu/ipu_csi.c new file mode 100644 index 000000000000..10708cf3ba54 --- /dev/null +++ b/drivers/mxc/ipu/ipu_csi.c @@ -0,0 +1,222 @@ +/* + * Copyright 2005-2009 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 ipu_csi.c + * + * @brief IPU CMOS Sensor interface functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" + +static bool gipu_csi_get_mclk_flag = false; +static int csi_mclk_flag = 0; + +extern void gpio_sensor_suspend(bool flag); + +/*! + * ipu_csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param sig ipu_csi_signal_cfg_t structure + * + * @return 0 for success, -EINVAL for error + */ +int32_t +ipu_csi_init_interface(uint16_t width, uint16_t height, uint32_t pixel_fmt, + ipu_csi_signal_cfg_t sig) +{ + uint32_t data = 0; + + /* Set SENS_DATA_FORMAT bits (8 and 9) + RGB or YUV444 is 0 which is current value in data so not set explicitly + This is also the default value if attempts are made to set it to + something invalid. */ + switch (pixel_fmt) { + case IPU_PIX_FMT_UYVY: + data = CSI_SENS_CONF_DATA_FMT_YUV422; + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR24: + data = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; + break; + case IPU_PIX_FMT_GENERIC: + data = CSI_SENS_CONF_DATA_FMT_BAYER; + break; + default: + return -EINVAL; + } + + /* Set the CSI_SENS_CONF register remaining fields */ + data |= sig.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | + sig.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | + sig.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | + sig.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | + sig.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | + sig.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | + sig.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | + sig.sens_clksrc << CSI_SENS_CONF_SENS_CLKSRC_SHIFT; + + __raw_writel(data, CSI_SENS_CONF); + + /* Setup frame size */ + __raw_writel(width | height << 16, CSI_SENS_FRM_SIZE); + + __raw_writel(width << 16, CSI_FLASH_STROBE_1); + __raw_writel(height << 16 | 0x22, CSI_FLASH_STROBE_2); + + /* Set CCIR registers */ + if ((sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) || + (sig.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED)) { + __raw_writel(0x40030, CSI_CCIR_CODE_1); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3); + } + + dev_dbg(g_ipu_dev, "CSI_SENS_CONF = 0x%08X\n", + __raw_readl(CSI_SENS_CONF)); + dev_dbg(g_ipu_dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", + __raw_readl(CSI_ACT_FRM_SIZE)); + + return 0; +} + +/*! + * ipu_csi_flash_strobe + * + * @param flag true to turn on flash strobe + * + * @return 0 for success + */ +void ipu_csi_flash_strobe(bool flag) +{ + if (flag == true) { + __raw_writel(__raw_readl(CSI_FLASH_STROBE_2) | 0x1, + CSI_FLASH_STROBE_2); + } +} + +/*! + * ipu_csi_enable_mclk + * + * @param src enum define which source to control the clk + * CSI_MCLK_VF CSI_MCLK_ENC CSI_MCLK_RAW CSI_MCLK_I2C + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return 0 for success + */ +int32_t ipu_csi_enable_mclk(int src, bool flag, bool wait) +{ + if (flag == true) { + csi_mclk_flag |= src; + } else { + csi_mclk_flag &= ~src; + } + + if (gipu_csi_get_mclk_flag == flag) + return 0; + + if (flag == true) { + clk_enable(g_ipu_csi_clk); + if (wait == true) + msleep(10); + /*printk("enable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = true; + } else if (csi_mclk_flag == 0) { + clk_disable(g_ipu_csi_clk); + /*printk("disable csi clock from source %d\n", src); */ + gipu_csi_get_mclk_flag = flag; + } + + return 0; +} + +/*! + * ipu_csi_read_mclk_flag + * + * @return csi_mclk_flag + */ +int ipu_csi_read_mclk_flag(void) +{ + return csi_mclk_flag; +} + +/*! + * ipu_csi_get_window_size + * + * @param width pointer to window width + * @param height pointer to window height + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_get_window_size(uint32_t *width, uint32_t *height, + uint32_t dummy) +{ + uint32_t reg; + + reg = __raw_readl(CSI_ACT_FRM_SIZE); + *width = (reg & 0xFFFF) + 1; + *height = (reg >> 16 & 0xFFFF) + 1; +} + +/*! + * ipu_csi_set_window_size + * + * @param width window width + * @param height window height + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height, uint32_t dummy) +{ + __raw_writel((width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE); +} + +/*! + * ipu_csi_set_window_pos + * + * @param left uint32 window x start + * @param top uint32 window y start + * @param dummy dummy for IPUv1 to keep the same interface with IPUv3 + * + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top, uint32_t dummy) +{ + uint32_t temp = __raw_readl(CSI_OUT_FRM_CTRL); + temp &= 0xffff0000; + temp = top | (left << 8) | temp; + __raw_writel(temp, CSI_OUT_FRM_CTRL); +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(ipu_csi_set_window_pos); +EXPORT_SYMBOL(ipu_csi_set_window_size); +EXPORT_SYMBOL(ipu_csi_get_window_size); +EXPORT_SYMBOL(ipu_csi_read_mclk_flag); +EXPORT_SYMBOL(ipu_csi_enable_mclk); +EXPORT_SYMBOL(ipu_csi_flash_strobe); +EXPORT_SYMBOL(ipu_csi_init_interface); diff --git a/drivers/mxc/ipu/ipu_device.c b/drivers/mxc/ipu/ipu_device.c new file mode 100644 index 000000000000..5fd1c51ec9b1 --- /dev/null +++ b/drivers/mxc/ipu/ipu_device.c @@ -0,0 +1,696 @@ +/* + * Copyright 2005-2009 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 ipu_device.c + * + * @brief This file contains the IPU driver device interface and fops functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* Strucutures and variables for exporting MXC IPU as device*/ + +#define MAX_Q_SIZE 10 + +static int mxc_ipu_major; +static struct class *mxc_ipu_class; + +DEFINE_SPINLOCK(queue_lock); +static DECLARE_MUTEX(user_mutex); + +static wait_queue_head_t waitq; +static int pending_events = 0; +int read_ptr = 0; +int write_ptr = 0; + +ipu_event_info events[MAX_Q_SIZE]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(ipu_event_info *p) +{ + unsigned long flags; + int ret = 0, i, cnt, found = 0; + spin_lock_irqsave(&queue_lock, flags); + if (pending_events != 0) { + if (write_ptr > read_ptr) + cnt = write_ptr - read_ptr; + else + cnt = MAX_Q_SIZE - read_ptr + write_ptr; + for (i = 0; i < cnt; i++) { + if (p->irq == events[read_ptr].irq) { + *p = events[read_ptr]; + events[read_ptr].irq = 0; + read_ptr++; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + found = 1; + break; + } + + if (events[read_ptr].irq) { + events[write_ptr] = events[read_ptr]; + events[read_ptr].irq = 0; + write_ptr++; + if (write_ptr >= MAX_Q_SIZE) + write_ptr = 0; + } else + pending_events--; + + read_ptr++; + if (read_ptr >= MAX_Q_SIZE) + read_ptr = 0; + } + if (found) + pending_events--; + else + ret = -1; + } else { + ret = -1; + } + + spin_unlock_irqrestore(&queue_lock, flags); + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + ipu_event_info e; + + e.irq = irq; + e.dev = dev_id; + events[write_ptr] = e; + write_ptr++; + if (write_ptr >= MAX_Q_SIZE) + write_ptr = 0; + pending_events++; + /* Wakeup any blocking user context */ + wake_up_interruptible(&waitq); + return IRQ_HANDLED; +} + +static int mxc_ipu_open(struct inode *inode, struct file *file) +{ + int ret = 0; + return ret; +} +static int mxc_ipu_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + + case IPU_INIT_CHANNEL: + { + ipu_channel_parm parm; + if (copy_from_user + (&parm, (ipu_channel_parm *) arg, + sizeof(ipu_channel_parm))) { + return -EFAULT; + } + if (!parm.flag) { + ret = + ipu_init_channel(parm.channel, + &parm.params); + } else { + ret = ipu_init_channel(parm.channel, NULL); + } + } + break; + + case IPU_UNINIT_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_uninit_channel(ch); + } + break; + + case IPU_INIT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_init_channel_buffer(parm.channel, parm.type, + parm.pixel_fmt, + parm.width, parm.height, + parm.stride, + parm.rot_mode, + parm.phyaddr_0, + parm.phyaddr_1, + parm.u_offset, + parm.v_offset); + + } + break; + + case IPU_UPDATE_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + if ((parm.phyaddr_0 != (dma_addr_t) NULL) + && (parm.phyaddr_1 == (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_0); + } else if ((parm.phyaddr_0 == (dma_addr_t) NULL) + && (parm.phyaddr_1 != (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer(parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_1); + } else { + ret = -1; + } + + } + break; + case IPU_SELECT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) { + return -EFAULT; + } + ret = + ipu_select_buffer(parm.channel, parm.type, + parm.bufNum); + + } + break; + case IPU_LINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_link_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_UNLINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) { + return -EFAULT; + } + ret = ipu_unlink_channels(link.src_ch, link.dest_ch); + + } + break; + case IPU_ENABLE_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_enable_channel(ch); + } + break; + case IPU_DISABLE_CHANNEL: + { + ipu_channel_info info; + if (copy_from_user + (&info, (ipu_channel_info *) arg, + sizeof(ipu_channel_info))) { + return -EFAULT; + } + ret = ipu_disable_channel(info.channel, info.stop); + } + break; + case IPU_ENABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_enable_irq(irq); + } + break; + case IPU_DISABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_disable_irq(irq); + } + break; + case IPU_CLEAR_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_clear_irq(irq); + } + break; + case IPU_FREE_IRQ: + { + ipu_irq_info info; + if (copy_from_user + (&info, (ipu_irq_info *) arg, + sizeof(ipu_irq_info))) { + return -EFAULT; + } + ipu_free_irq(info.irq, info.dev_id); + } + break; + case IPU_REQUEST_IRQ_STATUS: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ret = ipu_get_irq_status(irq); + } + break; + case IPU_SDC_INIT_PANEL: + { + ipu_sdc_panel_info sinfo; + if (copy_from_user + (&sinfo, (ipu_sdc_panel_info *) arg, + sizeof(ipu_sdc_panel_info))) { + return -EFAULT; + } + ret = + ipu_sdc_init_panel(sinfo.panel, sinfo.pixel_clk, + sinfo.width, sinfo.height, + sinfo.pixel_fmt, + sinfo.hStartWidth, + sinfo.hSyncWidth, + sinfo.hEndWidth, + sinfo.vStartWidth, + sinfo.vSyncWidth, + sinfo.vEndWidth, sinfo.signal); + } + break; + case IPU_SDC_SET_WIN_POS: + { + ipu_sdc_window_pos pos; + if (copy_from_user + (&pos, (ipu_sdc_window_pos *) arg, + sizeof(ipu_sdc_window_pos))) { + return -EFAULT; + } + ret = + ipu_disp_set_window_pos(pos.channel, pos.x_pos, + pos.y_pos); + + } + break; + case IPU_SDC_SET_GLOBAL_ALPHA: + { + ipu_sdc_global_alpha g; + if (copy_from_user + (&g, (ipu_sdc_global_alpha *) arg, + sizeof(ipu_sdc_global_alpha))) { + return -EFAULT; + } + ret = ipu_sdc_set_global_alpha(g.enable, g.alpha); + } + break; + case IPU_SDC_SET_COLOR_KEY: + { + ipu_sdc_color_key c; + if (copy_from_user + (&c, (ipu_sdc_color_key *) arg, + sizeof(ipu_sdc_color_key))) { + return -EFAULT; + } + ret = + ipu_sdc_set_color_key(c.channel, c.enable, + c.colorKey); + } + break; + case IPU_SDC_SET_BRIGHTNESS: + { + uint8_t b; + int __user *argp = (void __user *)arg; + if (get_user(b, argp)) + return -EFAULT; + ret = ipu_sdc_set_brightness(b); + + } + break; + case IPU_REGISTER_GENERIC_ISR: + { + ipu_event_info info; + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) { + return -EFAULT; + } + ret = + ipu_request_irq(info.irq, mxc_ipu_generic_handler, + 0, "video_sink", info.dev); + } + break; + case IPU_GET_EVENT: + /* User will have to allocate event_type structure and pass the pointer in arg */ + { + ipu_event_info info; + int r = -1; + + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + r = get_events(&info); + if (r == -1) { + wait_event_interruptible_timeout(waitq, + (pending_events != 0), 2 * HZ); + r = get_events(&info); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((ipu_event_info *) arg, + &info, sizeof(ipu_event_info))) + ret = 0; + } + } + break; + case IPU_ADC_WRITE_TEMPLATE: + { + ipu_adc_template temp; + if (copy_from_user + (&temp, (ipu_adc_template *) arg, sizeof(temp))) { + return -EFAULT; + } + ret = + ipu_adc_write_template(temp.disp, temp.pCmd, + temp.write); + } + break; + case IPU_ADC_UPDATE: + { + ipu_adc_update update; + if (copy_from_user + (&update, (ipu_adc_update *) arg, sizeof(update))) { + return -EFAULT; + } + ret = + ipu_adc_set_update_mode(update.channel, update.mode, + update.refresh_rate, + update.addr, update.size); + } + break; + case IPU_ADC_SNOOP: + { + ipu_adc_snoop snoop; + if (copy_from_user + (&snoop, (ipu_adc_snoop *) arg, sizeof(snoop))) { + return -EFAULT; + } + ret = + ipu_adc_get_snooping_status(snoop.statl, + snoop.stath); + } + break; + case IPU_ADC_CMD: + { + ipu_adc_cmd cmd; + if (copy_from_user + (&cmd, (ipu_adc_cmd *) arg, sizeof(cmd))) { + return -EFAULT; + } + ret = + ipu_adc_write_cmd(cmd.disp, cmd.type, cmd.cmd, + cmd.params, cmd.numParams); + } + break; + case IPU_ADC_INIT_PANEL: + { + ipu_adc_panel panel; + if (copy_from_user + (&panel, (ipu_adc_panel *) arg, sizeof(panel))) { + return -EFAULT; + } + ret = + ipu_adc_init_panel(panel.disp, panel.width, + panel.height, panel.pixel_fmt, + panel.stride, panel.signal, + panel.addr, panel.vsync_width, + panel.mode); + } + break; + case IPU_ADC_IFC_TIMING: + { + ipu_adc_ifc_timing t; + if (copy_from_user + (&t, (ipu_adc_ifc_timing *) arg, sizeof(t))) { + return -EFAULT; + } + ret = + ipu_adc_init_ifc_timing(t.disp, t.read, + t.cycle_time, t.up_time, + t.down_time, + t.read_latch_time, + t.pixel_clk); + } + break; + case IPU_CSI_INIT_INTERFACE: + { + ipu_csi_interface c; + if (copy_from_user + (&c, (ipu_csi_interface *) arg, sizeof(c))) + return -EFAULT; + ret = + ipu_csi_init_interface(c.width, c.height, + c.pixel_fmt, c.signal); + } + break; + case IPU_CSI_ENABLE_MCLK: + { + ipu_csi_mclk m; + if (copy_from_user(&m, (ipu_csi_mclk *) arg, sizeof(m))) + return -EFAULT; + ret = ipu_csi_enable_mclk(m.src, m.flag, m.wait); + } + break; + case IPU_CSI_READ_MCLK_FLAG: + { + ret = ipu_csi_read_mclk_flag(); + } + break; + case IPU_CSI_FLASH_STROBE: + { + bool strobe; + int __user *argp = (void __user *)arg; + if (get_user(strobe, argp)) + return -EFAULT; + ipu_csi_flash_strobe(strobe); + } + break; + case IPU_CSI_GET_WIN_SIZE: + { + ipu_csi_window_size w; + int dummy = 0; + ipu_csi_get_window_size(&w.width, &w.height, dummy); + if (copy_to_user + ((ipu_csi_window_size *) arg, &w, sizeof(w))) + return -EFAULT; + } + break; + case IPU_CSI_SET_WIN_SIZE: + { + ipu_csi_window_size w; + int dummy = 0; + if (copy_from_user + (&w, (ipu_csi_window_size *) arg, sizeof(w))) + return -EFAULT; + ipu_csi_set_window_size(w.width, w.height, dummy); + } + break; + case IPU_CSI_SET_WINDOW: + { + ipu_csi_window p; + int dummy = 0; + if (copy_from_user + (&p, (ipu_csi_window *) arg, sizeof(p))) + return -EFAULT; + ipu_csi_set_window_pos(p.left, p.top, dummy); + } + break; + case IPU_PF_SET_PAUSE_ROW: + { + uint32_t p; + int __user *argp = (void __user *)arg; + if (get_user(p, argp)) + return -EFAULT; + ret = ipu_pf_set_pause_row(p); + } + break; + case IPU_ALOC_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + info.vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(info.size), + &info.paddr, + GFP_DMA | GFP_KERNEL); + if (info.vaddr == 0) { + printk(KERN_ERR "dma alloc failed!\n"); + return -ENOBUFS; + } + if (copy_to_user((ipu_mem_info *) arg, &info, + sizeof(ipu_mem_info)) > 0) + return -EFAULT; + } + break; + case IPU_FREE_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + if (info.vaddr != 0) + dma_free_coherent(0, PAGE_ALIGN(info.size), + info.vaddr, info.paddr); + else + return -EFAULT; + } + break; + case IPU_IS_CHAN_BUSY: + { + ipu_channel_t chan; + if (copy_from_user + (&chan, (ipu_channel_t *)arg, + sizeof(ipu_channel_t))) + return -EFAULT; + + if (ipu_is_channel_busy(chan)) + ret = 1; + else + ret = 0; + } + break; + default: + break; + + } + return ret; +} + +static int mxc_ipu_mmap(struct file *file, struct vm_area_struct *vma) +{ + vma->vm_page_prot = pgprot_writethru(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + printk(KERN_ERR + "mmap failed!\n"); + return -ENOBUFS; + } + return 0; +} + +static int mxc_ipu_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations mxc_ipu_fops = { + .owner = THIS_MODULE, + .open = mxc_ipu_open, + .mmap = mxc_ipu_mmap, + .release = mxc_ipu_release, + .ioctl = mxc_ipu_ioctl +}; + +int register_ipu_device() +{ + int ret = 0; + struct device *temp; + mxc_ipu_major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); + if (mxc_ipu_major < 0) { + printk(KERN_ERR + "Unable to register Mxc Ipu as a char device\n"); + return mxc_ipu_major; + } + + mxc_ipu_class = class_create(THIS_MODULE, "mxc_ipu"); + if (IS_ERR(mxc_ipu_class)) { + printk(KERN_ERR "Unable to create class for Mxc Ipu\n"); + ret = PTR_ERR(mxc_ipu_class); + goto err1; + } + + temp = device_create(mxc_ipu_class, NULL, MKDEV(mxc_ipu_major, 0), NULL, + "mxc_ipu"); + + if (IS_ERR(temp)) { + printk(KERN_ERR "Unable to create class device for Mxc Ipu\n"); + ret = PTR_ERR(temp); + goto err2; + } + spin_lock_init(&queue_lock); + init_waitqueue_head(&waitq); + return ret; + + err2: + class_destroy(mxc_ipu_class); + err1: + unregister_chrdev(mxc_ipu_major, "mxc_ipu"); + return ret; + +} diff --git a/drivers/mxc/ipu/ipu_ic.c b/drivers/mxc/ipu/ipu_ic.c new file mode 100644 index 000000000000..cdf823a2760b --- /dev/null +++ b/drivers/mxc/ipu/ipu_ic.c @@ -0,0 +1,592 @@ +/* + * Copyright 2005-2009 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 ipu_ic.c + * + * @brief IPU IC functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum { + IC_TASK_VIEWFINDER, + IC_TASK_ENCODER, + IC_TASK_POST_PROCESSOR +}; + +extern int g_ipu_hw_rev; +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format); +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t * resizeCoeff, + uint32_t * downsizeCoeff); + +void _ipu_ic_enable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf |= IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf |= IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf |= IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + // ??? + ic_conf |= IC_CONF_RWS_EN | IC_CONF_PRPENC_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_disable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_ADC: + case MEM_PRP_VF_ADC: + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_ADC: + case MEM_PP_MEM: + ic_conf &= ~IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf &= ~IC_CONF_PP_ROT_EN; + break; + case CSI_MEM: + // ??? + ic_conf &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_init_prpvf(ipu_channel_params_t * params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_height, + params->mem_prp_vf_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_width, + params->mem_prp_vf_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_VF_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PRPVF_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_prp_vf_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_prp_vf_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + +#ifndef CONFIG_VIRTIO_SUPPORT /* Setting RWS_EN doesn't work in Virtio */ + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } +#endif + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpvf(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPVF_EN | IC_CONF_PRPVF_CMB | + IC_CONF_PRPVF_CSC2 | IC_CONF_PRPVF_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_vf(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_vf(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PRPVF_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_csi(ipu_channel_params_t * params) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_CSI_MEM_WR_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_uninit_csi(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_prpenc(ipu_channel_params_t * params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_height, + params->mem_prp_enc_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_width, + params->mem_prp_enc_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_ENC_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* TODO: ERROR! */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_ENCODER, YCbCr, RGB); + ic_conf |= IC_CONF_PRPENC_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (src_is_csi) { + ic_conf &= ~IC_CONF_RWS_EN; + } else { + ic_conf |= IC_CONF_RWS_EN; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpenc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_enc(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_enc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_ROT_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_pp(ipu_channel_params_t * params) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_height, + params->mem_pp_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_width, + params->mem_pp_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PP_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_pp_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt); + ic_conf |= IC_CONF_PP_CSC2; /* Enable RGB->YCBCR CSC */ + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable YCBCR->RGB CSC */ + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PP_CMB; + + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB); + ic_conf |= IC_CONF_PP_CSC1; /* Enable RGB->RGB CSC */ + + if (params->mem_pp_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + } else { + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + } + + if (params->mem_pp_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_pp(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PP_EN | IC_CONF_PP_CSC1 | IC_CONF_PP_CSC2 | + IC_CONF_PP_CMB); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_pp(ipu_channel_params_t * params) +{ +} + +void _ipu_ic_uninit_rotate_pp(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PP_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format) +{ +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ + static const uint32_t rgb2ycbcr_coeff[4][3] = { + {0x004D, 0x0096, 0x001D}, + {0x01D5, 0x01AB, 0x0080}, + {0x0080, 0x0195, 0x01EB}, + {0x0000, 0x0200, 0x0200}, /* A0, A1, A2 */ + }; + + /* transparent RGB->RGB matrix for combining + */ + static const uint32_t rgb2rgb_coeff[4][3] = { + {0x0080, 0x0000, 0x0000}, + {0x0000, 0x0080, 0x0000}, + {0x0000, 0x0000, 0x0080}, + {0x0000, 0x0000, 0x0000}, /* A0, A1, A2 */ + }; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ + static const uint32_t ycbcr2rgb_coeff[4][3] = { + {149, 0, 204}, + {149, 462, 408}, + {149, 255, 0}, + {8192 - 446, 266, 8192 - 554}, /* A0, A1, A2 */ + }; + + uint32_t param[2]; + uint32_t address = 0; + + if (g_ipu_hw_rev > 1) { + if (ic_task == IC_TASK_VIEWFINDER) { + address = 0x645 << 3; + } else if (ic_task == IC_TASK_ENCODER) { + address = 0x321 << 3; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + address = 0x96C << 3; + } else { + BUG(); + } + } else { + if (ic_task == IC_TASK_VIEWFINDER) { + address = 0x5a5 << 3; + } else if (ic_task == IC_TASK_ENCODER) { + address = 0x2d1 << 3; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + address = 0x87c << 3; + } else { + BUG(); + } + } + + if ((in_format == YCbCr) && (out_format == RGB)) { + /* Init CSC1 (YCbCr->RGB) */ + param[0] = + (ycbcr2rgb_coeff[3][0] << 27) | (ycbcr2rgb_coeff[0][0] << + 18) | + (ycbcr2rgb_coeff[1][1] << 9) | ycbcr2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (ycbcr2rgb_coeff[3][0] >> 5) | (2L << (40 - 32)); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][1] << 27) | (ycbcr2rgb_coeff[0][1] << + 18) | + (ycbcr2rgb_coeff[1][0] << 9) | ycbcr2rgb_coeff[2][0]; + param[1] = (ycbcr2rgb_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (ycbcr2rgb_coeff[3][2] << 27) | (ycbcr2rgb_coeff[0][2] << + 18) | + (ycbcr2rgb_coeff[1][2] << 9) | ycbcr2rgb_coeff[2][1]; + param[1] = (ycbcr2rgb_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == YCbCr)) { + /* Init CSC1 (RGB->YCbCr) */ + param[0] = + (rgb2ycbcr_coeff[3][0] << 27) | (rgb2ycbcr_coeff[0][0] << + 18) | + (rgb2ycbcr_coeff[1][1] << 9) | rgb2ycbcr_coeff[2][2]; + /* scale = 1, sat = 0 */ + param[1] = (rgb2ycbcr_coeff[3][0] >> 5) | (1UL << 8); + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][1] << 27) | (rgb2ycbcr_coeff[0][1] << + 18) | + (rgb2ycbcr_coeff[1][0] << 9) | rgb2ycbcr_coeff[2][0]; + param[1] = (rgb2ycbcr_coeff[3][1] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2ycbcr_coeff[3][2] << 27) | (rgb2ycbcr_coeff[0][2] << + 18) | + (rgb2ycbcr_coeff[1][2] << 9) | rgb2ycbcr_coeff[2][1]; + param[1] = (rgb2ycbcr_coeff[3][2] >> 5); + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else if ((in_format == RGB) && (out_format == RGB)) { + /* Init CSC1 */ + param[0] = + (rgb2rgb_coeff[3][0] << 27) | (rgb2rgb_coeff[0][0] << 18) | + (rgb2rgb_coeff[1][1] << 9) | rgb2rgb_coeff[2][2]; + /* scale = 2, sat = 0 */ + param[1] = (rgb2rgb_coeff[3][0] >> 5) | (2UL << 8); + + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][1] << 27) | (rgb2rgb_coeff[0][1] << 18) | + (rgb2rgb_coeff[1][0] << 9) | rgb2rgb_coeff[2][0]; + param[1] = (rgb2rgb_coeff[3][1] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + + param[0] = + (rgb2rgb_coeff[3][2] << 27) | (rgb2rgb_coeff[0][2] << 18) | + (rgb2rgb_coeff[1][2] << 9) | rgb2rgb_coeff[2][1]; + param[1] = (rgb2rgb_coeff[3][2] >> 5); + + address += 1L << 3; + _ipu_write_param_mem(address, param, 2); + + dev_dbg(g_ipu_dev, + "addr 0x%04X: word0 = 0x%08X, word1 = 0x%08X\n", + address, param[0], param[1]); + } else { + dev_err(g_ipu_dev, "Unsupported color space conversion\n"); + } +} + +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t * resizeCoeff, + uint32_t * downsizeCoeff) +{ + uint32_t tempSize; + uint32_t tempDownsize; + + /* Cannot downsize more than 8:1 */ + if ((outSize << 3) < inSize) { + return false; + } + /* compute downsizing coefficient */ + tempDownsize = 0; + tempSize = inSize; + while ((tempSize >= outSize * 2) && (tempDownsize < 2)) { + tempSize >>= 1; + tempDownsize++; + } + *downsizeCoeff = tempDownsize; + + /* compute resizing coefficient using the following equation: + resizeCoeff = M*(SI -1)/(SO - 1) + where M = 2^13, SI - input size, SO - output size */ + *resizeCoeff = (8192L * (tempSize - 1)) / (outSize - 1); + if (*resizeCoeff >= 16384L) { + dev_err(g_ipu_dev, "Warning! Overflow on resize coeff.\n"); + *resizeCoeff = 0x3FFF; + } + + dev_dbg(g_ipu_dev, "resizing from %u -> %u pixels, " + "downsize=%u, resize=%u.%lu (reg=%u)\n", inSize, outSize, + *downsizeCoeff, (*resizeCoeff >= 8192L) ? 1 : 0, + ((*resizeCoeff & 0x1FFF) * 10000L) / 8192L, *resizeCoeff); + + return true; +} diff --git a/drivers/mxc/ipu/ipu_param_mem.h b/drivers/mxc/ipu/ipu_param_mem.h new file mode 100644 index 000000000000..07bd03a81e3f --- /dev/null +++ b/drivers/mxc/ipu/ipu_param_mem.h @@ -0,0 +1,176 @@ +/* + * Copyright 2005-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 + */ +#ifndef __INCLUDE_IPU_PARAM_MEM_H__ +#define __INCLUDE_IPU_PARAM_MEM_H__ + +#include <linux/types.h> + +static __inline void _ipu_ch_param_set_size(uint32_t * params, + uint32_t pixel_fmt, uint16_t width, + uint16_t height, uint16_t stride, + uint32_t u, uint32_t v) +{ + uint32_t u_offset = 0; + uint32_t v_offset = 0; + memset(params, 0, 40); + + params[3] = + (uint32_t) ((width - 1) << 12) | ((uint32_t) (height - 1) << 24); + params[4] = (uint32_t) (height - 1) >> 8; + params[7] = (uint32_t) (stride - 1) << 3; + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + /*Represents 8-bit Generic data */ + params[7] |= 3 | (7UL << (81 - 64)) | (31L << (89 - 64)); /* BPP & PFS */ + params[8] = 2; /* SAT = use 32-bit access */ + break; + case IPU_PIX_FMT_GENERIC_32: + /*Represents 32-bit Generic data */ + params[7] |= (7UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */ + params[8] = 2; /* SAT = use 32-bit access */ + break; + case IPU_PIX_FMT_RGB565: + params[7] |= 2L | (4UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */ + params[8] = 2 | /* SAT = 32-bit access */ + (0UL << (99 - 96)) | /* Red bit offset */ + (5UL << (104 - 96)) | /* Green bit offset */ + (11UL << (109 - 96)) | /* Blue bit offset */ + (16UL << (114 - 96)) | /* Alpha bit offset */ + (4UL << (119 - 96)) | /* Red bit width - 1 */ + (5UL << (122 - 96)) | /* Green bit width - 1 */ + (4UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_BGR24: /* 24 BPP & RGB PFS */ + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (8UL << (104 - 96)) | /* Green bit offset */ + (16UL << (109 - 96)) | /* Blue bit offset */ + (24UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_RGB24: /* 24 BPP & RGB PFS */ + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (16UL << (99 - 96)) | /* Red bit offset */ + (8UL << (104 - 96)) | /* Green bit offset */ + (0UL << (109 - 96)) | /* Blue bit offset */ + (24UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + break; + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (8UL << (99 - 96)) | /* Red bit offset */ + (16UL << (104 - 96)) | /* Green bit offset */ + (24UL << (109 - 96)) | /* Blue bit offset */ + (0UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + params[9] = 7; /* Alpha bit width - 1 */ + break; + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (24UL << (99 - 96)) | /* Red bit offset */ + (16UL << (104 - 96)) | /* Green bit offset */ + (8UL << (109 - 96)) | /* Blue bit offset */ + (0UL << (114 - 96)) | /* Alpha bit offset */ + (7UL << (119 - 96)) | /* Red bit width - 1 */ + (7UL << (122 - 96)) | /* Green bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */ + params[9] = 7; /* Alpha bit width - 1 */ + break; + case IPU_PIX_FMT_ABGR32: + /* BPP & pixel fmt */ + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); + params[8] = 2 | /* SAT = 32-bit access */ + (0UL << (99 - 96)) | /* Alpha bit offset */ + (8UL << (104 - 96)) | /* Blue bit offset */ + (16UL << (109 - 96)) | /* Green bit offset */ + (24UL << (114 - 96)) | /* Red bit offset */ + (7UL << (119 - 96)) | /* Alpha bit width - 1 */ + (7UL << (122 - 96)) | /* Blue bit width - 1 */ + (uint32_t) (7UL << (125 - 96)); /* Green bit width - 1 */ + params[9] = 7; /* Red bit width - 1 */ + break; + case IPU_PIX_FMT_UYVY: + /* BPP & pixel format */ + params[7] |= 2 | (6UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + /* BPP & pixel format */ + params[7] |= 3 | (3UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 4 : v; + break; + case IPU_PIX_FMT_YVU422P: + /* BPP & pixel format */ + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + v_offset = (v == 0) ? stride * height : v; + u_offset = (u == 0) ? v_offset + v_offset / 2 : u; + break; + case IPU_PIX_FMT_YUV422P: + /* BPP & pixel format */ + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64)); + params[8] = 2; /* SAT = 32-bit access */ + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 2 : v; + break; + default: + dev_err(g_ipu_dev, "mxc ipu: unimplemented pixel format\n"); + break; + } + + params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32)); + params[2] = u_offset >> (64 - 53); + params[2] |= v_offset << (79 - 64); + params[3] |= v_offset >> (96 - 79); +} + +static __inline void _ipu_ch_param_set_burst_size(uint32_t * params, + uint16_t burst_pixels) +{ + params[7] &= ~(0x3FL << (89 - 64)); + params[7] |= (uint32_t) (burst_pixels - 1) << (89 - 64); +}; + +static __inline void _ipu_ch_param_set_buffer(uint32_t * params, + dma_addr_t buf0, dma_addr_t buf1) +{ + params[5] = buf0; + params[6] = buf1; +}; + +static __inline void _ipu_ch_param_set_rotation(uint32_t * params, + ipu_rotate_mode_t rot) +{ + params[7] |= (uint32_t) rot << (84 - 64); +}; + +void _ipu_write_param_mem(uint32_t addr, uint32_t * data, uint32_t numWords); + +#endif diff --git a/drivers/mxc/ipu/ipu_prv.h b/drivers/mxc/ipu/ipu_prv.h new file mode 100644 index 000000000000..b76a2f2357fd --- /dev/null +++ b/drivers/mxc/ipu/ipu_prv.h @@ -0,0 +1,59 @@ +/* + * Copyright 2005-2009 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 + */ +#ifndef __INCLUDE_IPU_PRV_H__ +#define __INCLUDE_IPU_PRV_H__ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> + +/* Globals */ +extern struct device *g_ipu_dev; +extern spinlock_t ipu_lock; +extern struct clk *g_ipu_clk; +extern struct clk *g_ipu_csi_clk; + +int register_ipu_device(void); +ipu_color_space_t format_to_colorspace(uint32_t fmt); + +uint32_t _ipu_channel_status(ipu_channel_t channel); + +void _ipu_sdc_fg_init(ipu_channel_params_t * params); +uint32_t _ipu_sdc_fg_uninit(void); +void _ipu_sdc_bg_init(ipu_channel_params_t * params); +uint32_t _ipu_sdc_bg_uninit(void); + +void _ipu_ic_enable_task(ipu_channel_t channel); +void _ipu_ic_disable_task(ipu_channel_t channel); +void _ipu_ic_init_prpvf(ipu_channel_params_t * params, bool src_is_csi); +void _ipu_ic_uninit_prpvf(void); +void _ipu_ic_init_rotate_vf(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_vf(void); +void _ipu_ic_init_csi(ipu_channel_params_t * params); +void _ipu_ic_uninit_csi(void); +void _ipu_ic_init_prpenc(ipu_channel_params_t * params, bool src_is_csi); +void _ipu_ic_uninit_prpenc(void); +void _ipu_ic_init_rotate_enc(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_enc(void); +void _ipu_ic_init_pp(ipu_channel_params_t * params); +void _ipu_ic_uninit_pp(void); +void _ipu_ic_init_rotate_pp(ipu_channel_params_t * params); +void _ipu_ic_uninit_rotate_pp(void); + +int32_t _ipu_adc_init_channel(ipu_channel_t chan, display_port_t disp, + mcu_mode_t cmd, int16_t x_pos, int16_t y_pos); +int32_t _ipu_adc_uninit_channel(ipu_channel_t chan); + +#endif /* __INCLUDE_IPU_PRV_H__ */ diff --git a/drivers/mxc/ipu/ipu_regs.h b/drivers/mxc/ipu/ipu_regs.h new file mode 100644 index 000000000000..d3ac76ea7834 --- /dev/null +++ b/drivers/mxc/ipu/ipu_regs.h @@ -0,0 +1,396 @@ +/* + * Copyright 2005-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 ipu_regs.h + * + * @brief IPU Register definitions + * + * @ingroup IPU + */ +#ifndef __IPU_REGS_INCLUDED__ +#define __IPU_REGS_INCLUDED__ + +#define IPU_REG_BASE IO_ADDRESS(IPU_CTRL_BASE_ADDR) + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CONF (IPU_REG_BASE + 0x0000) +#define IPU_CHA_BUF0_RDY (IPU_REG_BASE + 0x0004) +#define IPU_CHA_BUF1_RDY (IPU_REG_BASE + 0x0008) +#define IPU_CHA_DB_MODE_SEL (IPU_REG_BASE + 0x000C) +#define IPU_CHA_CUR_BUF (IPU_REG_BASE + 0x0010) +#define IPU_FS_PROC_FLOW (IPU_REG_BASE + 0x0014) +#define IPU_FS_DISP_FLOW (IPU_REG_BASE + 0x0018) +#define IPU_TASKS_STAT (IPU_REG_BASE + 0x001C) +#define IPU_IMA_ADDR (IPU_REG_BASE + 0x0020) +#define IPU_IMA_DATA (IPU_REG_BASE + 0x0024) +#define IPU_INT_CTRL_1 (IPU_REG_BASE + 0x0028) +#define IPU_INT_CTRL_2 (IPU_REG_BASE + 0x002C) +#define IPU_INT_CTRL_3 (IPU_REG_BASE + 0x0030) +#define IPU_INT_CTRL_4 (IPU_REG_BASE + 0x0034) +#define IPU_INT_CTRL_5 (IPU_REG_BASE + 0x0038) +#define IPU_INT_STAT_1 (IPU_REG_BASE + 0x003C) +#define IPU_INT_STAT_2 (IPU_REG_BASE + 0x0040) +#define IPU_INT_STAT_3 (IPU_REG_BASE + 0x0044) +#define IPU_INT_STAT_4 (IPU_REG_BASE + 0x0048) +#define IPU_INT_STAT_5 (IPU_REG_BASE + 0x004C) +#define IPU_BRK_CTRL_1 (IPU_REG_BASE + 0x0050) +#define IPU_BRK_CTRL_2 (IPU_REG_BASE + 0x0054) +#define IPU_BRK_STAT (IPU_REG_BASE + 0x0058) +#define IPU_DIAGB_CTRL (IPU_REG_BASE + 0x005C) +/* CMOS Sensor Interface Registers */ +#define CSI_SENS_CONF (IPU_REG_BASE + 0x0060) +#define CSI_SENS_FRM_SIZE (IPU_REG_BASE + 0x0064) +#define CSI_ACT_FRM_SIZE (IPU_REG_BASE + 0x0068) +#define CSI_OUT_FRM_CTRL (IPU_REG_BASE + 0x006C) +#define CSI_TST_CTRL (IPU_REG_BASE + 0x0070) +#define CSI_CCIR_CODE_1 (IPU_REG_BASE + 0x0074) +#define CSI_CCIR_CODE_2 (IPU_REG_BASE + 0x0078) +#define CSI_CCIR_CODE_3 (IPU_REG_BASE + 0x007C) +#define CSI_FLASH_STROBE_1 (IPU_REG_BASE + 0x0080) +#define CSI_FLASH_STROBE_2 (IPU_REG_BASE + 0x0084) +/* Image Converter Registers */ +#define IC_CONF (IPU_REG_BASE + 0x0088) +#define IC_PRP_ENC_RSC (IPU_REG_BASE + 0x008C) +#define IC_PRP_VF_RSC (IPU_REG_BASE + 0x0090) +#define IC_PP_RSC (IPU_REG_BASE + 0x0094) +#define IC_CMBP_1 (IPU_REG_BASE + 0x0098) +#define IC_CMBP_2 (IPU_REG_BASE + 0x009C) +#define PF_CONF (IPU_REG_BASE + 0x00A0) +#define IDMAC_CONF (IPU_REG_BASE + 0x00A4) +#define IDMAC_CHA_EN (IPU_REG_BASE + 0x00A8) +#define IDMAC_CHA_PRI (IPU_REG_BASE + 0x00AC) +#define IDMAC_CHA_BUSY (IPU_REG_BASE + 0x00B0) +/* SDC Registers */ +#define SDC_COM_CONF (IPU_REG_BASE + 0x00B4) +#define SDC_GW_CTRL (IPU_REG_BASE + 0x00B8) +#define SDC_FG_POS (IPU_REG_BASE + 0x00BC) +#define SDC_BG_POS (IPU_REG_BASE + 0x00C0) +#define SDC_CUR_POS (IPU_REG_BASE + 0x00C4) +#define SDC_PWM_CTRL (IPU_REG_BASE + 0x00C8) +#define SDC_CUR_MAP (IPU_REG_BASE + 0x00CC) +#define SDC_HOR_CONF (IPU_REG_BASE + 0x00D0) +#define SDC_VER_CONF (IPU_REG_BASE + 0x00D4) +#define SDC_SHARP_CONF_1 (IPU_REG_BASE + 0x00D8) +#define SDC_SHARP_CONF_2 (IPU_REG_BASE + 0x00DC) +/* ADC Registers */ +#define ADC_CONF (IPU_REG_BASE + 0x00E0) +#define ADC_SYSCHA1_SA (IPU_REG_BASE + 0x00E4) +#define ADC_SYSCHA2_SA (IPU_REG_BASE + 0x00E8) +#define ADC_PRPCHAN_SA (IPU_REG_BASE + 0x00EC) +#define ADC_PPCHAN_SA (IPU_REG_BASE + 0x00F0) +#define ADC_DISP0_CONF (IPU_REG_BASE + 0x00F4) +#define ADC_DISP0_RD_AP (IPU_REG_BASE + 0x00F8) +#define ADC_DISP0_RDM (IPU_REG_BASE + 0x00FC) +#define ADC_DISP0_SS (IPU_REG_BASE + 0x0100) +#define ADC_DISP1_CONF (IPU_REG_BASE + 0x0104) +#define ADC_DISP1_RD_AP (IPU_REG_BASE + 0x0108) +#define ADC_DISP1_RDM (IPU_REG_BASE + 0x010C) +#define ADC_DISP12_SS (IPU_REG_BASE + 0x0110) +#define ADC_DISP2_CONF (IPU_REG_BASE + 0x0114) +#define ADC_DISP2_RD_AP (IPU_REG_BASE + 0x0118) +#define ADC_DISP2_RDM (IPU_REG_BASE + 0x011C) +#define ADC_DISP_VSYNC (IPU_REG_BASE + 0x0120) +/* Display Interface re(sters */ +#define DI_DISP_IF_CONF (IPU_REG_BASE + 0x0124) +#define DI_DISP_SIG_POL (IPU_REG_BASE + 0x0128) +#define DI_SER_DISP1_CONF (IPU_REG_BASE + 0x012C) +#define DI_SER_DISP2_CONF (IPU_REG_BASE + 0x0130) +#define DI_HSP_CLK_PER (IPU_REG_BASE + 0x0134) +#define DI_DISP0_TIME_CONF_1 (IPU_REG_BASE + 0x0138) +#define DI_DISP0_TIME_CONF_2 (IPU_REG_BASE + 0x013C) +#define DI_DISP0_TIME_CONF_3 (IPU_REG_BASE + 0x0140) +#define DI_DISP1_TIME_CONF_1 (IPU_REG_BASE + 0x0144) +#define DI_DISP1_TIME_CONF_2 (IPU_REG_BASE + 0x0148) +#define DI_DISP1_TIME_CONF_3 (IPU_REG_BASE + 0x014C) +#define DI_DISP2_TIME_CONF_1 (IPU_REG_BASE + 0x0150) +#define DI_DISP2_TIME_CONF_2 (IPU_REG_BASE + 0x0154) +#define DI_DISP2_TIME_CONF_3 (IPU_REG_BASE + 0x0158) +#define DI_DISP3_TIME_CONF (IPU_REG_BASE + 0x015C) +#define DI_DISP0_DB0_MAP (IPU_REG_BASE + 0x0160) +#define DI_DISP0_DB1_MAP (IPU_REG_BASE + 0x0164) +#define DI_DISP0_DB2_MAP (IPU_REG_BASE + 0x0168) +#define DI_DISP0_CB0_MAP (IPU_REG_BASE + 0x016C) +#define DI_DISP0_CB1_MAP (IPU_REG_BASE + 0x0170) +#define DI_DISP0_CB2_MAP (IPU_REG_BASE + 0x0174) +#define DI_DISP1_DB0_MAP (IPU_REG_BASE + 0x0178) +#define DI_DISP1_DB1_MAP (IPU_REG_BASE + 0x017C) +#define DI_DISP1_DB2_MAP (IPU_REG_BASE + 0x0180) +#define DI_DISP1_CB0_MAP (IPU_REG_BASE + 0x0184) +#define DI_DISP1_CB1_MAP (IPU_REG_BASE + 0x0188) +#define DI_DISP1_CB2_MAP (IPU_REG_BASE + 0x018C) +#define DI_DISP2_DB0_MAP (IPU_REG_BASE + 0x0190) +#define DI_DISP2_DB1_MAP (IPU_REG_BASE + 0x0194) +#define DI_DISP2_DB2_MAP (IPU_REG_BASE + 0x0198) +#define DI_DISP2_CB0_MAP (IPU_REG_BASE + 0x019C) +#define DI_DISP2_CB1_MAP (IPU_REG_BASE + 0x01A0) +#define DI_DISP2_CB2_MAP (IPU_REG_BASE + 0x01A4) +#define DI_DISP3_B0_MAP (IPU_REG_BASE + 0x01A8) +#define DI_DISP3_B1_MAP (IPU_REG_BASE + 0x01AC) +#define DI_DISP3_B2_MAP (IPU_REG_BASE + 0x01B0) +#define DI_DISP_ACC_CC (IPU_REG_BASE + 0x01B4) +#define DI_DISP_LLA_CONF (IPU_REG_BASE + 0x01B8) +#define DI_DISP_LLA_DATA (IPU_REG_BASE + 0x01BC) + +#define IPUIRQ_2_STATREG(int) (IPU_INT_STAT_1 + 4*(int>>5)) +#define IPUIRQ_2_CTRLREG(int) (IPU_INT_CTRL_1 + 4*(int>>5)) +#define IPUIRQ_2_MASK(int) (1UL << (int & 0x1F)) + +enum { + IPU_CONF_CSI_EN = 0x00000001, + IPU_CONF_IC_EN = 0x00000002, + IPU_CONF_ROT_EN = 0x00000004, + IPU_CONF_PF_EN = 0x00000008, + IPU_CONF_SDC_EN = 0x00000010, + IPU_CONF_ADC_EN = 0x00000020, + IPU_CONF_DI_EN = 0x00000040, + IPU_CONF_DU_EN = 0x00000080, + IPU_CONF_PXL_ENDIAN = 0x00000100, + + FS_PRPVF_ROT_SRC_SEL = 0x00000040, + FS_PRPENC_ROT_SRC_SEL = 0x00000020, + FS_PRPENC_DEST_SEL = 0x00000010, + FS_PP_SRC_SEL_MASK = 0x00000300, + FS_PP_SRC_SEL_OFFSET = 8, + FS_PP_ROT_SRC_SEL_MASK = 0x00000C00, + FS_PP_ROT_SRC_SEL_OFFSET = 10, + FS_PF_DEST_SEL_MASK = 0x00003000, + FS_PF_DEST_SEL_OFFSET = 12, + FS_PRPVF_DEST_SEL_MASK = 0x00070000, + FS_PRPVF_DEST_SEL_OFFSET = 16, + FS_PRPVF_ROT_DEST_SEL_MASK = 0x00700000, + FS_PRPVF_ROT_DEST_SEL_OFFSET = 20, + FS_PP_DEST_SEL_MASK = 0x07000000, + FS_PP_DEST_SEL_OFFSET = 24, + FS_PP_ROT_DEST_SEL_MASK = 0x70000000, + FS_PP_ROT_DEST_SEL_OFFSET = 28, + FS_VF_IN_VALID = 0x00000002, + FS_ENC_IN_VALID = 0x00000001, + + FS_SDC_BG_SRC_SEL_MASK = 0x00000007, + FS_SDC_BG_SRC_SEL_OFFSET = 0, + FS_SDC_FG_SRC_SEL_MASK = 0x00000070, + FS_SDC_FG_SRC_SEL_OFFSET = 4, + FS_ADC1_SRC_SEL_MASK = 0x00000700, + FS_ADC1_SRC_SEL_OFFSET = 8, + FS_ADC2_SRC_SEL_MASK = 0x00007000, + FS_ADC2_SRC_SEL_OFFSET = 12, + FS_AUTO_REF_PER_MASK = 0x03FF0000, + FS_AUTO_REF_PER_OFFSET = 16, + + FS_DEST_ARM = 0, + FS_DEST_ROT = 1, + FS_DEST_PP = 1, + FS_DEST_ADC1 = 2, + FS_DEST_ADC2 = 3, + FS_DEST_SDC_BG = 4, + FS_DEST_SDC_FG = 5, + FS_DEST_ADC = 6, + + FS_SRC_ARM = 0, + FS_PP_SRC_PF = 1, + FS_PP_SRC_ROT = 2, + + FS_ROT_SRC_PP = 1, + FS_ROT_SRC_PF = 2, + + FS_PF_DEST_PP = 1, + FS_PF_DEST_ROT = 2, + + FS_SRC_ROT_VF = 1, + FS_SRC_ROT_PP = 2, + FS_SRC_VF = 3, + FS_SRC_PP = 4, + FS_SRC_SNOOP = 5, + FS_SRC_AUTOREF = 6, + FS_SRC_AUTOREF_SNOOP = 7, + + TSTAT_PF_H264_PAUSE = 0x00000001, + TSTAT_CSI2MEM_MASK = 0x0000000C, + TSTAT_CSI2MEM_OFFSET = 2, + TSTAT_VF_MASK = 0x00000600, + TSTAT_VF_OFFSET = 9, + TSTAT_VF_ROT_MASK = 0x000C0000, + TSTAT_VF_ROT_OFFSET = 18, + TSTAT_ENC_MASK = 0x00000180, + TSTAT_ENC_OFFSET = 7, + TSTAT_ENC_ROT_MASK = 0x00030000, + TSTAT_ENC_ROT_OFFSET = 16, + TSTAT_PP_MASK = 0x00001800, + TSTAT_PP_OFFSET = 11, + TSTAT_PP_ROT_MASK = 0x00300000, + TSTAT_PP_ROT_OFFSET = 20, + TSTAT_PF_MASK = 0x00C00000, + TSTAT_PF_OFFSET = 22, + TSTAT_ADCSYS1_MASK = 0x03000000, + TSTAT_ADCSYS1_OFFSET = 24, + TSTAT_ADCSYS2_MASK = 0x0C000000, + TSTAT_ADCSYS2_OFFSET = 26, + + TASK_STAT_IDLE = 0, + TASK_STAT_ACTIVE = 1, + TASK_STAT_WAIT4READY = 2, + + /* Register bits */ + SDC_COM_TFT_COLOR = 0x00000001UL, + SDC_COM_FG_EN = 0x00000010UL, + SDC_COM_GWSEL = 0x00000020UL, + SDC_COM_GLB_A = 0x00000040UL, + SDC_COM_KEY_COLOR_G = 0x00000080UL, + SDC_COM_BG_EN = 0x00000200UL, + SDC_COM_SHARP = 0x00001000UL, + + SDC_V_SYNC_WIDTH_L = 0x00000001UL, + + ADC_CONF_PRP_EN = 0x00000001L, + ADC_CONF_PP_EN = 0x00000002L, + ADC_CONF_MCU_EN = 0x00000004L, + + ADC_DISP_CONF_SL_MASK = 0x00000FFFL, + ADC_DISP_CONF_TYPE_MASK = 0x00003000L, + ADC_DISP_CONF_TYPE_XY = 0x00002000L, + + ADC_DISP_VSYNC_D0_MODE_MASK = 0x00000003L, + ADC_DISP_VSYNC_D0_WIDTH_MASK = 0x003F0000L, + ADC_DISP_VSYNC_D12_MODE_MASK = 0x0000000CL, + ADC_DISP_VSYNC_D12_WIDTH_MASK = 0x3F000000L, + + /* Image Converter Register bits */ + IC_CONF_PRPENC_EN = 0x00000001, + IC_CONF_PRPENC_CSC1 = 0x00000002, + IC_CONF_PRPENC_ROT_EN = 0x00000004, + IC_CONF_PRPVF_EN = 0x00000100, + IC_CONF_PRPVF_CSC1 = 0x00000200, + IC_CONF_PRPVF_CSC2 = 0x00000400, + IC_CONF_PRPVF_CMB = 0x00000800, + IC_CONF_PRPVF_ROT_EN = 0x00001000, + IC_CONF_PP_EN = 0x00010000, + IC_CONF_PP_CSC1 = 0x00020000, + IC_CONF_PP_CSC2 = 0x00040000, + IC_CONF_PP_CMB = 0x00080000, + IC_CONF_PP_ROT_EN = 0x00100000, + IC_CONF_IC_GLB_LOC_A = 0x10000000, + IC_CONF_KEY_COLOR_EN = 0x20000000, + IC_CONF_RWS_EN = 0x40000000, + IC_CONF_CSI_MEM_WR_EN = 0x80000000, + + IDMA_CHAN_INVALID = 0x000000FF, + IDMA_IC_0 = 0x00000001, + IDMA_IC_1 = 0x00000002, + IDMA_IC_2 = 0x00000004, + IDMA_IC_3 = 0x00000008, + IDMA_IC_4 = 0x00000010, + IDMA_IC_5 = 0x00000020, + IDMA_IC_6 = 0x00000040, + IDMA_IC_7 = 0x00000080, + IDMA_IC_8 = 0x00000100, + IDMA_IC_9 = 0x00000200, + IDMA_IC_10 = 0x00000400, + IDMA_IC_11 = 0x00000800, + IDMA_IC_12 = 0x00001000, + IDMA_IC_13 = 0x00002000, + IDMA_SDC_BG = 0x00004000, + IDMA_SDC_FG = 0x00008000, + IDMA_SDC_MASK = 0x00010000, + IDMA_SDC_PARTIAL = 0x00020000, + IDMA_ADC_SYS1_WR = 0x00040000, + IDMA_ADC_SYS2_WR = 0x00080000, + IDMA_ADC_SYS1_CMD = 0x00100000, + IDMA_ADC_SYS2_CMD = 0x00200000, + IDMA_ADC_SYS1_RD = 0x00400000, + IDMA_ADC_SYS2_RD = 0x00800000, + IDMA_PF_QP = 0x01000000, + IDMA_PF_BSP = 0x02000000, + IDMA_PF_Y_IN = 0x04000000, + IDMA_PF_U_IN = 0x08000000, + IDMA_PF_V_IN = 0x10000000, + IDMA_PF_Y_OUT = 0x20000000, + IDMA_PF_U_OUT = 0x40000000, + IDMA_PF_V_OUT = 0x80000000, + + CSI_SENS_CONF_DATA_FMT_SHIFT = 8, + CSI_SENS_CONF_DATA_FMT_RGB_YUV444 = 0x00000000L, + CSI_SENS_CONF_DATA_FMT_YUV422 = 0x00000200L, + CSI_SENS_CONF_DATA_FMT_BAYER = 0x00000300L, + + CSI_SENS_CONF_VSYNC_POL_SHIFT = 0, + CSI_SENS_CONF_HSYNC_POL_SHIFT = 1, + CSI_SENS_CONF_DATA_POL_SHIFT = 2, + CSI_SENS_CONF_PIX_CLK_POL_SHIFT = 3, + CSI_SENS_CONF_SENS_PRTCL_SHIFT = 4, + CSI_SENS_CONF_SENS_CLKSRC_SHIFT = 7, + CSI_SENS_CONF_DATA_WIDTH_SHIFT = 10, + CSI_SENS_CONF_EXT_VSYNC_SHIFT = 15, + CSI_SENS_CONF_DIVRATIO_SHIFT = 16, + + PF_CONF_TYPE_MASK = 0x00000007, + PF_CONF_TYPE_SHIFT = 0, + PF_CONF_PAUSE_EN = 0x00000010, + PF_CONF_RESET = 0x00008000, + PF_CONF_PAUSE_ROW_MASK = 0x00FF0000, + PF_CONF_PAUSE_ROW_SHIFT = 16, + + /* DI_DISP_SIG_POL bits */ + DI_D3_VSYNC_POL_SHIFT = 28, + DI_D3_HSYNC_POL_SHIFT = 27, + DI_D3_DRDY_SHARP_POL_SHIFT = 26, + DI_D3_CLK_POL_SHIFT = 25, + DI_D3_DATA_POL_SHIFT = 24, + + /* DI_DISP_IF_CONF bits */ + DI_D3_CLK_IDLE_SHIFT = 26, + DI_D3_CLK_SEL_SHIFT = 25, + DI_D3_DATAMSK_SHIFT = 24, + + DISPx_IF_CLK_DOWN_OFFSET = 22, + DISPx_IF_CLK_UP_OFFSET = 12, + DISPx_IF_CLK_PER_OFFSET = 0, + DISPx_IF_CLK_READ_EN_OFFSET = 16, + DISPx_PIX_CLK_PER_OFFSET = 0, + + DI_CONF_DISP0_EN = 0x00000001L, + DI_CONF_DISP0_IF_MODE_OFFSET = 1, + DI_CONF_DISP0_BURST_MODE_OFFSET = 3, + DI_CONF_DISP1_EN = 0x00000100L, + DI_CONF_DISP1_IF_MODE_OFFSET = 9, + DI_CONF_DISP1_BURST_MODE_OFFSET = 12, + DI_CONF_DISP2_EN = 0x00010000L, + DI_CONF_DISP2_IF_MODE_OFFSET = 17, + DI_CONF_DISP2_BURST_MODE_OFFSET = 20, + + DI_SER_DISPx_CONF_SER_BIT_NUM_OFFSET = 16, + DI_SER_DISPx_CONF_PREAMBLE_OFFSET = 8, + DI_SER_DISPx_CONF_PREAMBLE_LEN_OFFSET = 4, + DI_SER_DISPx_CONF_RW_CFG_OFFSET = 1, + DI_SER_DISPx_CONF_BURST_MODE_EN = 0x01000000L, + DI_SER_DISPx_CONF_PREAMBLE_EN = 0x00000001L, + + /* DI_DISP_ACC_CC */ + DISP0_IF_CLK_CNT_D_MASK = 0x00000003L, + DISP0_IF_CLK_CNT_D_OFFSET = 0, + DISP0_IF_CLK_CNT_C_MASK = 0x0000000CL, + DISP0_IF_CLK_CNT_C_OFFSET = 2, + DISP1_IF_CLK_CNT_D_MASK = 0x00000030L, + DISP1_IF_CLK_CNT_D_OFFSET = 4, + DISP1_IF_CLK_CNT_C_MASK = 0x000000C0L, + DISP1_IF_CLK_CNT_C_OFFSET = 6, + DISP2_IF_CLK_CNT_D_MASK = 0x00000300L, + DISP2_IF_CLK_CNT_D_OFFSET = 8, + DISP2_IF_CLK_CNT_C_MASK = 0x00000C00L, + DISP2_IF_CLK_CNT_C_OFFSET = 10, + DISP3_IF_CLK_CNT_MASK = 0x00003000L, + DISP3_IF_CLK_CNT_OFFSET = 12, +}; + +#endif diff --git a/drivers/mxc/ipu/ipu_sdc.c b/drivers/mxc/ipu/ipu_sdc.c new file mode 100644 index 000000000000..a36eb6dc6a4e --- /dev/null +++ b/drivers/mxc/ipu/ipu_sdc.c @@ -0,0 +1,357 @@ +/* + * Copyright 2005-2009 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 ipu_sdc.c + * + * @brief IPU SDC submodule API functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +static uint32_t g_h_start_width; +static uint32_t g_v_start_width; + +static const uint32_t di_mappings[] = { + 0x1600AAAA, 0x00E05555, 0x00070000, 3, /* RGB888 */ + 0x0005000F, 0x000B000F, 0x0011000F, 1, /* RGB666 */ + 0x0011000F, 0x000B000F, 0x0005000F, 1, /* BGR666 */ + 0x0004003F, 0x000A000F, 0x000F003F, 1 /* RGB565 */ +}; + +/*! + * This function is called to initialize a synchronous LCD panel. + * + * @param panel The type of panel. + * + * @param pixel_clk Desired pixel clock frequency in Hz. + * + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel + * format is a FOURCC ASCII code. + * + * @param width The width of panel in pixels. + * + * @param height The height of panel in pixels. + * + * @param hStartWidth The number of pixel clocks between the HSYNC + * signal pulse and the start of valid data. + * + * @param hSyncWidth The width of the HSYNC signal in units of pixel + * clocks. + * + * @param hEndWidth The number of pixel clocks between the end of + * valid data and the HSYNC signal for next line. + * + * @param vStartWidth The number of lines between the VSYNC + * signal pulse and the start of valid data. + * + * @param vSyncWidth The width of the VSYNC signal in units of lines + * + * @param vEndWidth The number of lines between the end of valid + * data and the VSYNC signal for next frame. + * + * @param sig Bitfield of signal polarities for LCD interface. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_sdc_init_panel(ipu_panel_t panel, + uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + ipu_di_signal_cfg_t sig) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t old_conf; + uint32_t div; + + dev_dbg(g_ipu_dev, "panel size = %d x %d\n", width, height); + + if ((v_sync_width == 0) || (h_sync_width == 0)) + return EINVAL; + + /* Init panel size and blanking periods */ + reg = + ((uint32_t) (h_sync_width - 1) << 26) | + ((uint32_t) (width + h_sync_width + h_start_width + h_end_width - 1) + << 16); + __raw_writel(reg, SDC_HOR_CONF); + + reg = ((uint32_t) (v_sync_width - 1) << 26) | SDC_V_SYNC_WIDTH_L | + ((uint32_t) + (height + v_sync_width + v_start_width + v_end_width - 1) << 16); + __raw_writel(reg, SDC_VER_CONF); + + g_h_start_width = h_start_width + h_sync_width; + g_v_start_width = v_start_width + v_sync_width; + + switch (panel) { + case IPU_PANEL_SHARP_TFT: + __raw_writel(0x00FD0102L, SDC_SHARP_CONF_1); + __raw_writel(0x00F500F4L, SDC_SHARP_CONF_2); + __raw_writel(SDC_COM_SHARP | SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + case IPU_PANEL_TFT: + __raw_writel(SDC_COM_TFT_COLOR, SDC_COM_CONF); + break; + default: + return EINVAL; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Init clocking */ + + /* Calculate divider */ + /* fractional part is 4 bits so simply multiple by 2^4 to get fractional part */ + dev_dbg(g_ipu_dev, "pixel clk = %d\n", pixel_clk); + div = (clk_get_rate(g_ipu_clk) * 16) / pixel_clk; + if (div < 0x40) { /* Divider less than 4 */ + dev_dbg(g_ipu_dev, + "InitPanel() - Pixel clock divider less than 1\n"); + div = 0x40; + } + /* DISP3_IF_CLK_DOWN_WR is half the divider value and 2 less fraction bits */ + /* Subtract 1 extra from DISP3_IF_CLK_DOWN_WR based on timing debug */ + /* DISP3_IF_CLK_UP_WR is 0 */ + __raw_writel((((div / 8) - 1) << 22) | div, DI_DISP3_TIME_CONF); + + /* DI settings */ + old_conf = __raw_readl(DI_DISP_IF_CONF) & 0x78FFFFFF; + old_conf |= sig.datamask_en << DI_D3_DATAMSK_SHIFT | + sig.clksel_en << DI_D3_CLK_SEL_SHIFT | + sig.clkidle_en << DI_D3_CLK_IDLE_SHIFT; + __raw_writel(old_conf, DI_DISP_IF_CONF); + + old_conf = __raw_readl(DI_DISP_SIG_POL) & 0xE0FFFFFF; + old_conf |= sig.data_pol << DI_D3_DATA_POL_SHIFT | + sig.clk_pol << DI_D3_CLK_POL_SHIFT | + sig.enable_pol << DI_D3_DRDY_SHARP_POL_SHIFT | + sig.Hsync_pol << DI_D3_HSYNC_POL_SHIFT | + sig.Vsync_pol << DI_D3_VSYNC_POL_SHIFT; + __raw_writel(old_conf, DI_DISP_SIG_POL); + + switch (pixel_fmt) { + case IPU_PIX_FMT_RGB24: + __raw_writel(di_mappings[0], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[1], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[2], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[3] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_RGB666: + __raw_writel(di_mappings[4], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[5], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[6], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[7] - 1) << 12), DI_DISP_ACC_CC); + break; + case IPU_PIX_FMT_BGR666: + __raw_writel(di_mappings[8], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[9], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[10], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[11] - 1) << 12), DI_DISP_ACC_CC); + break; + default: + __raw_writel(di_mappings[12], DI_DISP3_B0_MAP); + __raw_writel(di_mappings[13], DI_DISP3_B1_MAP); + __raw_writel(di_mappings[14], DI_DISP3_B2_MAP); + __raw_writel(__raw_readl(DI_DISP_ACC_CC) | + ((di_mappings[15] - 1) << 12), DI_DISP_ACC_CC); + break; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + dev_dbg(g_ipu_dev, "DI_DISP_IF_CONF = 0x%08X\n", + __raw_readl(DI_DISP_IF_CONF)); + dev_dbg(g_ipu_dev, "DI_DISP_SIG_POL = 0x%08X\n", + __raw_readl(DI_DISP_SIG_POL)); + dev_dbg(g_ipu_dev, "DI_DISP3_TIME_CONF = 0x%08X\n", + __raw_readl(DI_DISP3_TIME_CONF)); + + return 0; +} + +/*! + * This function sets the foreground and background plane global alpha blending + * modes. + * + * @param enable Boolean to enable or disable global alpha + * blending. If disabled, per pixel blending is used. + * + * @param alpha Global alpha value. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_global_alpha(bool enable, uint8_t alpha) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0x00FFFFFFL; + __raw_writel(reg | ((uint32_t) alpha << 24), SDC_GW_CTRL); + + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_GLB_A, SDC_COM_CONF); + } else { + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_GLB_A, SDC_COM_CONF); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +/*! + * This function sets the transparent color key for SDC graphic plane. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable color key + * + * @param colorKey 24-bit RGB color to use as transparent color key. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_sdc_set_color_key(ipu_channel_t channel, bool enable, + uint32_t color_key) +{ + uint32_t reg, sdc_conf; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + sdc_conf = __raw_readl(SDC_COM_CONF); + if (channel == MEM_SDC_BG) { + sdc_conf &= ~SDC_COM_GWSEL; + } else { + sdc_conf |= SDC_COM_GWSEL; + } + + if (enable) { + reg = __raw_readl(SDC_GW_CTRL) & 0xFF000000L; + __raw_writel(reg | (color_key & 0x00FFFFFFL), SDC_GW_CTRL); + + sdc_conf |= SDC_COM_KEY_COLOR_G; + } else { + sdc_conf &= ~SDC_COM_KEY_COLOR_G; + } + __raw_writel(sdc_conf, SDC_COM_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} + +int32_t ipu_sdc_set_brightness(uint8_t value) +{ + __raw_writel(0x03000000UL | value << 16, SDC_PWM_CTRL); + return 0; +} + +/*! + * This function sets the window position of the foreground or background plane. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param x_pos The X coordinate position to place window at. + * The position is relative to the top left corner. + * + * @param y_pos The Y coordinate position to place window at. + * The position is relative to the top left corner. + * + * @return This function returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos) +{ + x_pos += g_h_start_width; + y_pos += g_v_start_width; + + if (channel == MEM_SDC_BG) { + __raw_writel((x_pos << 16) | y_pos, SDC_BG_POS); + } else if (channel == MEM_SDC_FG) { + __raw_writel((x_pos << 16) | y_pos, SDC_FG_POS); + } else { + return EINVAL; + } + return 0; +} + +void _ipu_sdc_fg_init(ipu_channel_params_t * params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_FG_EN | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_fg_uninit(void) +{ + uint32_t reg; + + /* Disable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_FG_EN, SDC_COM_CONF); + + return (reg & SDC_COM_FG_EN); +} + +void _ipu_sdc_bg_init(ipu_channel_params_t * params) +{ + uint32_t reg; + (void)params; + + /* Enable FG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg | SDC_COM_BG_EN, SDC_COM_CONF); +} + +uint32_t _ipu_sdc_bg_uninit(void) +{ + uint32_t reg; + + /* Disable BG channel */ + reg = __raw_readl(SDC_COM_CONF); + __raw_writel(reg & ~SDC_COM_BG_EN, SDC_COM_CONF); + + return (reg & SDC_COM_BG_EN); +} + +/* Exported symbols for modules. */ +EXPORT_SYMBOL(ipu_sdc_init_panel); +EXPORT_SYMBOL(ipu_sdc_set_global_alpha); +EXPORT_SYMBOL(ipu_sdc_set_color_key); +EXPORT_SYMBOL(ipu_sdc_set_brightness); +EXPORT_SYMBOL(ipu_disp_set_window_pos); diff --git a/drivers/mxc/ipu/pf/Kconfig b/drivers/mxc/ipu/pf/Kconfig new file mode 100644 index 000000000000..fa5a777cf727 --- /dev/null +++ b/drivers/mxc/ipu/pf/Kconfig @@ -0,0 +1,7 @@ +config MXC_IPU_PF + tristate "MXC MPEG4/H.264 Post Filter Driver" + depends on MXC_IPU_V1 + default y + help + Driver for MPEG4 dering and deblock and H.264 deblock + using MXC IPU h/w diff --git a/drivers/mxc/ipu/pf/Makefile b/drivers/mxc/ipu/pf/Makefile new file mode 100644 index 000000000000..641adf4be4bd --- /dev/null +++ b/drivers/mxc/ipu/pf/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MXC_IPU_PF) += mxc_pf.o diff --git a/drivers/mxc/ipu/pf/mxc_pf.c b/drivers/mxc/ipu/pf/mxc_pf.c new file mode 100644 index 000000000000..744152415e3a --- /dev/null +++ b/drivers/mxc/ipu/pf/mxc_pf.c @@ -0,0 +1,993 @@ +/* + * Copyright 2005-2009 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 mxc_pf.c + * + * @brief MXC IPU MPEG4/H.264 Post-filtering driver + * + * User-level API for IPU Hardware MPEG4/H.264 Post-filtering. + * + * @ingroup MXC_PF + */ + +#include <linux/pagemap.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/ipu.h> +#include <linux/mxc_pf.h> + +struct mxc_pf_data { + pf_operation_t mode; + u32 pf_enabled; + u32 width; + u32 height; + u32 stride; + uint32_t qp_size; + dma_addr_t qp_paddr; + void *qp_vaddr; + pf_buf buf[PF_MAX_BUFFER_CNT]; + void *buf_vaddr[PF_MAX_BUFFER_CNT]; + wait_queue_head_t pf_wait; + volatile int done_mask; + volatile int wait_mask; + volatile int busy_flag; + struct semaphore busy_lock; +}; + +static struct mxc_pf_data pf_data; +static u8 open_count = 0; +static struct class *mxc_pf_class; + +/* + * Function definitions + */ + +static irqreturn_t mxc_pf_irq_handler(int irq, void *dev_id) +{ + struct mxc_pf_data *pf = dev_id; + + if (irq == IPU_IRQ_PF_Y_OUT_EOF) { + pf->done_mask |= PF_WAIT_Y; + } else if (irq == IPU_IRQ_PF_U_OUT_EOF) { + pf->done_mask |= PF_WAIT_U; + } else if (irq == IPU_IRQ_PF_V_OUT_EOF) { + pf->done_mask |= PF_WAIT_V; + } else { + return IRQ_NONE; + } + + if (pf->wait_mask && ((pf->done_mask & pf->wait_mask) == pf->wait_mask)) { + wake_up_interruptible(&pf->pf_wait); + } + return IRQ_HANDLED; +} + +/*! + * This function handles PF_IOCTL_INIT calls. It initializes the PF channels, + * interrupt handlers, and channel buffers. + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_init(pf_init_params * pf_init) +{ + int err; + ipu_channel_params_t params; + u32 w; + u32 stride; + u32 h; + u32 qp_size = 0; + u32 qp_stride; + + if ((pf_init->pf_mode > 4) || (pf_init->width > 1024) || + (pf_init->height > 1024) || (pf_init->stride < pf_init->width)) { + return -EINVAL; + } + + pf_data.mode = pf_init->pf_mode; + w = pf_data.width = pf_init->width; + h = pf_data.height = pf_init->height; + stride = pf_data.stride = pf_init->stride; + pf_data.qp_size = pf_init->qp_size; + + memset(¶ms, 0, sizeof(params)); + params.mem_pf_mem.operation = pf_data.mode; + err = ipu_init_channel(MEM_PF_Y_MEM, ¶ms); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing channel\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error initializing Y output buffer\n"); + goto err0; + } + + w = w / 2; + h = h / 2; + stride = stride / 2; + + if (pf_data.mode != PF_MPEG4_DERING) { + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing U output buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V input buffer\n"); + goto err0; + } + + err = ipu_init_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, stride, + IPU_ROTATE_NONE, 0, 0, 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing V output buffer\n"); + goto err0; + } + } + + /*Setup Channel QF and BSC Params */ + if (pf_data.mode == PF_H264_DEBLOCK) { + w = ((pf_data.width + 15) / 16); + h = (pf_data.height + 15) / 16; + qp_stride = w; + qp_size = 4 * qp_stride * h; + pr_debug("H264 QP width = %d, height = %d\n", w, h); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC_32, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 QP buffer\n"); + goto err0; + } +/* w = (pf_data.width + 3) / 4; */ + w *= 4; + h *= 4; + qp_stride = w; + err = ipu_init_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing H264 BSB buffer\n"); + goto err0; + } + qp_size += qp_stride * h; + } else { /* MPEG4 mode */ + + w = (pf_data.width + 15) / 16; + h = (pf_data.height + 15) / 16; + qp_stride = (w + 3) & ~0x3UL; + pr_debug("MPEG4 QP width = %d, height = %d, stride = %d\n", + w, h, qp_stride); + err = ipu_init_channel_buffer(MEM_PF_Y_MEM, + IPU_SEC_INPUT_BUFFER, + IPU_PIX_FMT_GENERIC, w, h, + qp_stride, IPU_ROTATE_NONE, 0, 0, + 0, 0); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error initializing MPEG4 QP buffer\n"); + goto err0; + } + qp_size = qp_stride * h; + } + + /* Support 2 QP buffers */ + qp_size *= 2; + + if (pf_data.qp_size > qp_size) + qp_size = pf_data.qp_size; + else + pf_data.qp_size = qp_size; + + pf_data.qp_vaddr = dma_alloc_coherent(NULL, pf_data.qp_size, + &pf_data.qp_paddr, + GFP_KERNEL | GFP_DMA); + if (!pf_data.qp_vaddr) + return -ENOMEM; + + pf_init->qp_paddr = pf_data.qp_paddr; + pf_init->qp_size = pf_data.qp_size; + + return 0; + + err0: + return err; +} + +/*! + * This function handles PF_IOCTL_UNINIT calls. It uninitializes the PF + * channels and interrupt handlers. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_uninit(void) +{ + pf_data.pf_enabled = 0; + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + ipu_disable_channel(MEM_PF_Y_MEM, true); + ipu_disable_channel(MEM_PF_U_MEM, true); + ipu_disable_channel(MEM_PF_V_MEM, true); + ipu_uninit_channel(MEM_PF_Y_MEM); + ipu_uninit_channel(MEM_PF_U_MEM); + ipu_uninit_channel(MEM_PF_V_MEM); + + if (pf_data.qp_vaddr) { + dma_free_coherent(NULL, pf_data.qp_size, pf_data.qp_vaddr, + pf_data.qp_paddr); + pf_data.qp_vaddr = NULL; + } + + return 0; +} + +/*! + * This function handles PF_IOCTL_REQBUFS calls. It initializes the PF channels + * and channel buffers. + * + * @param reqbufs Input/Output Structure containing buffer mode, + * type, offset, and size. The offset and size of + * the buffer are returned for PF_MEMORY_MMAP mode. + * + * @return This function returns 0 on success or negative error code + * on error. + */ +static int mxc_pf_reqbufs(pf_reqbufs_params * reqbufs) +{ + int err; + uint32_t buf_size; + int i; + int alloc_cnt = 0; + pf_buf *buf = pf_data.buf; + if (reqbufs->count > PF_MAX_BUFFER_CNT) { + reqbufs->count = PF_MAX_BUFFER_CNT; + } + /* Deallocate mmapped buffers */ + if (reqbufs->count == 0) { + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + if (buf[i].index != -1) { + dma_free_coherent(NULL, buf[i].size, + pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + } + return 0; + } + + buf_size = (pf_data.stride * pf_data.height * 3) / 2; + if (reqbufs->req_size > buf_size) { + buf_size = reqbufs->req_size; + pr_debug("using requested buffer size of %d\n", buf_size); + } else { + reqbufs->req_size = buf_size; + pr_debug("using default buffer size of %d\n", buf_size); + } + + for (i = 0; alloc_cnt < reqbufs->count; i++) { + buf[i].index = i; + buf[i].size = buf_size; + pf_data.buf_vaddr[i] = dma_alloc_coherent(NULL, buf[i].size, + &buf[i].offset, + GFP_KERNEL | GFP_DMA); + if (!pf_data.buf_vaddr[i] || !buf[i].offset) { + printk(KERN_ERR + "mxc_pf: unable to allocate IPU buffers.\n"); + err = -ENOMEM; + goto err0; + } + pr_debug("Allocated buffer %d at paddr 0x%08X, vaddr %p\n", + i, buf[i].offset, pf_data.buf_vaddr[i]); + + alloc_cnt++; + } + + return 0; + err0: + for (i = 0; i < alloc_cnt; i++) { + dma_free_coherent(NULL, buf[i].size, pf_data.buf_vaddr[i], + buf[i].offset); + pf_data.buf_vaddr[i] = NULL; + buf[i].index = -1; + buf[i].size = 0; + } + return err; +} + +/*! + * This function handles PF_IOCTL_START calls. It sets the PF channel buffers + * addresses and starts the channels + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_start(pf_buf * in, pf_buf * out, int qp_buf) +{ + int err; + dma_addr_t y_in_paddr; + dma_addr_t u_in_paddr; + dma_addr_t v_in_paddr; + dma_addr_t p1_in_paddr; + dma_addr_t p2_in_paddr; + dma_addr_t y_out_paddr; + dma_addr_t u_out_paddr; + dma_addr_t v_out_paddr; + + /* H.264 requires output buffer equal to input */ + if (pf_data.mode == PF_H264_DEBLOCK) + out = in; + + y_in_paddr = in->offset + in->y_offset; + if (in->u_offset) + u_in_paddr = in->offset + in->u_offset; + else + u_in_paddr = y_in_paddr + (pf_data.stride * pf_data.height); + if (in->v_offset) + v_in_paddr = in->offset + in->v_offset; + else + v_in_paddr = u_in_paddr + (pf_data.stride * pf_data.height) / 4; + p1_in_paddr = pf_data.qp_paddr; + if (qp_buf) + p1_in_paddr += pf_data.qp_size / 2; + + if (pf_data.mode == PF_H264_DEBLOCK) { + p2_in_paddr = p1_in_paddr + + ((pf_data.width + 15) / 16) * + ((pf_data.height + 15) / 16) * 4; + } else { + p2_in_paddr = 0; + } + + pr_debug("y_in_paddr = 0x%08X\nu_in_paddr = 0x%08X\n" + "v_in_paddr = 0x%08X\n" + "qp_paddr = 0x%08X\nbsb_paddr = 0x%08X\n", + y_in_paddr, u_in_paddr, v_in_paddr, p1_in_paddr, p2_in_paddr); + + y_out_paddr = out->offset + out->y_offset; + if (out->u_offset) + u_out_paddr = out->offset + out->u_offset; + else + u_out_paddr = y_out_paddr + (pf_data.stride * pf_data.height); + if (out->v_offset) + v_out_paddr = out->offset + out->v_offset; + else + v_out_paddr = + u_out_paddr + (pf_data.stride * pf_data.height) / 4; + + pr_debug("y_out_paddr = 0x%08X\nu_out_paddr = 0x%08X\n" + "v_out_paddr = 0x%08X\n", + y_out_paddr, u_out_paddr, v_out_paddr); + + pf_data.done_mask = 0; + + ipu_enable_irq(IPU_IRQ_PF_Y_OUT_EOF); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_enable_irq(IPU_IRQ_PF_V_OUT_EOF); + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0, + y_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y input buffer\n"); + goto err0; + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0, + y_out_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting Y output buffer\n"); + goto err0; + } + + if (pf_data.mode != PF_MPEG4_DERING) { + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0, + u_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, + 0, u_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting U output buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0, + v_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V input buffer\n"); + goto err0; + } + + err = + ipu_update_channel_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, + 0, v_out_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting V output buffer\n"); + goto err0; + } + } + + err = ipu_update_channel_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0, + p1_in_paddr); + if (err < 0) { + printk(KERN_ERR "mxc_pf: error setting QP buffer\n"); + goto err0; + } + + if (pf_data.mode == PF_H264_DEBLOCK) { + + err = ipu_update_channel_buffer(MEM_PF_U_MEM, + IPU_SEC_INPUT_BUFFER, 0, + p2_in_paddr); + if (err < 0) { + printk(KERN_ERR + "mxc_pf: error setting H264 BSB buffer\n"); + goto err0; + } + ipu_select_buffer(MEM_PF_U_MEM, IPU_SEC_INPUT_BUFFER, 0); + } + + ipu_select_buffer(MEM_PF_Y_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_SEC_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_Y_MEM, IPU_INPUT_BUFFER, 0); + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_select_buffer(MEM_PF_U_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_OUTPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_U_MEM, IPU_INPUT_BUFFER, 0); + ipu_select_buffer(MEM_PF_V_MEM, IPU_INPUT_BUFFER, 0); + } + + if (!pf_data.pf_enabled) { + pf_data.pf_enabled = 1; + if (pf_data.mode != PF_MPEG4_DERING) { + ipu_enable_channel(MEM_PF_V_MEM); + ipu_enable_channel(MEM_PF_U_MEM); + } + ipu_enable_channel(MEM_PF_Y_MEM); + } + + return 0; + err0: + return err; +} + +/*! + * Post Filter driver open function. This function implements the Linux + * file_operations.open() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_open(struct inode *inode, struct file *filp) +{ + int i; + + if (open_count) { + return -EBUSY; + } + + open_count++; + + memset(&pf_data, 0, sizeof(pf_data)); + for (i = 0; i < PF_MAX_BUFFER_CNT; i++) { + pf_data.buf[i].index = -1; + } + init_waitqueue_head(&pf_data.pf_wait); + init_MUTEX(&pf_data.busy_lock); + + pf_data.busy_flag = 1; + + ipu_request_irq(IPU_IRQ_PF_Y_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_U_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_request_irq(IPU_IRQ_PF_V_OUT_EOF, mxc_pf_irq_handler, + 0, "mxc_ipu_pf", &pf_data); + + ipu_disable_irq(IPU_IRQ_PF_Y_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_U_OUT_EOF); + ipu_disable_irq(IPU_IRQ_PF_V_OUT_EOF); + + return 0; +} + +/*! + * Post Filter driver release function. This function implements the Linux + * file_operations.release() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_release(struct inode *inode, struct file *filp) +{ + pf_reqbufs_params req_buf; + + if (open_count) { + mxc_pf_uninit(); + + /* Free any allocated buffers */ + req_buf.count = 0; + mxc_pf_reqbufs(&req_buf); + + ipu_free_irq(IPU_IRQ_PF_V_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_U_OUT_EOF, &pf_data); + ipu_free_irq(IPU_IRQ_PF_Y_OUT_EOF, &pf_data); + open_count--; + } + return 0; +} + +/*! + * Post Filter driver ioctl function. This function implements the Linux + * file_operations.ioctl() API function. + * + * @param inode struct inode * + * + * @param filp struct file * + * + * @param cmd IOCTL command to handle + * + * @param arg Pointer to arguments for IOCTL + * + * @return This function returns 0 on success or negative error code on + * error. + */ +static int mxc_pf_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + switch (cmd) { + case PF_IOCTL_INIT: + { + pf_init_params pf_init; + + pr_debug("PF_IOCTL_INIT\n"); + if (copy_from_user(&pf_init, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_init(&pf_init); + if (retval < 0) + break; + pf_init.qp_paddr = pf_data.qp_paddr; + pf_init.qp_size = pf_data.qp_size; + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_init, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + pf_data.busy_flag = 0; + break; + } + case PF_IOCTL_UNINIT: + pr_debug("PF_IOCTL_UNINIT\n"); + retval = mxc_pf_uninit(); + break; + case PF_IOCTL_REQBUFS: + { + pf_reqbufs_params reqbufs; + pr_debug("PF_IOCTL_REQBUFS\n"); + + if (copy_from_user + (&reqbufs, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + retval = mxc_pf_reqbufs(&reqbufs); + + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &reqbufs, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_QUERYBUF: + { + pf_buf buf; + pr_debug("PF_IOCTL_QUERYBUF\n"); + + if (copy_from_user(&buf, (void *)arg, _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if ((buf.index < 0) || + (buf.index >= PF_MAX_BUFFER_CNT) || + (pf_data.buf[buf.index].index != buf.index)) { + retval = -EINVAL; + break; + } + /* Return size of memory allocated */ + if (copy_to_user((void *)arg, &pf_data.buf[buf.index], + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + break; + } + case PF_IOCTL_START: + { + int index; + pf_start_params start_params; + pr_debug("PF_IOCTL_START\n"); + + if (pf_data.busy_flag) { + retval = -EBUSY; + break; + } + + if (copy_from_user(&start_params, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + if (start_params.h264_pause_row >= + ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + pf_data.busy_flag = 1; + + index = start_params.in.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.in.offset) { + retval = -EINVAL; + break; + } + } + + index = start_params.out.index; + if ((index >= 0) && (index < PF_MAX_BUFFER_CNT)) { + if (pf_data.buf[index].offset != + start_params.out.offset) { + retval = -EINVAL; + break; + } + } + + ipu_pf_set_pause_row(start_params.h264_pause_row); + + /*Update y, u, v buffers in DMA Channels */ + if ((retval = + mxc_pf_start(&start_params.in, &start_params.out, + start_params.qp_buf)) + < 0) { + break; + } + + pr_debug("PF_IOCTL_START - processing started\n"); + + if (!start_params.wait) { + break; + } + + pr_debug("PF_IOCTL_START - waiting for completion\n"); + + pf_data.wait_mask = PF_WAIT_ALL; + /* Fall thru to wait */ + } + case PF_IOCTL_WAIT: + { + if (!pf_data.wait_mask) + pf_data.wait_mask = (u32) arg; + + if (pf_data.mode == PF_MPEG4_DERING) + pf_data.wait_mask &= PF_WAIT_Y; + + if (!pf_data.wait_mask) { + retval = -EINVAL; + break; + } + + if (!wait_event_interruptible_timeout(pf_data.pf_wait, + ((pf_data. + done_mask & + pf_data. + wait_mask) == + pf_data. + wait_mask), + 1 * HZ)) { + pr_debug + ("PF_IOCTL_WAIT: timeout, done_mask = %d\n", + pf_data.done_mask); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("PF_IOCTL_WAIT: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + pf_data.busy_flag = 0; + pf_data.wait_mask = 0; + + pr_debug("PF_IOCTL_WAIT - finished\n"); + break; + } + case PF_IOCTL_RESUME: + { + int pause_row; + pr_debug("PF_IOCTL_RESUME\n"); + + if (pf_data.busy_flag == 0) { + retval = -EFAULT; + break; + } + + if (copy_from_user(&pause_row, (void *)arg, + _IOC_SIZE(cmd))) { + retval = -EFAULT; + break; + } + + if (pause_row >= ((pf_data.height + 15) / 16)) { + retval = -EINVAL; + break; + } + + ipu_pf_set_pause_row(pause_row); + break; + } + + default: + printk(KERN_ERR "ipu_pf_ioctl not supported ioctl\n"); + retval = -1; + } + + if (retval < 0) + pr_debug("return = %d\n", retval); + return retval; +} + +/*! + * Post Filter driver mmap function. This function implements the Linux + * file_operations.mmap() API function for mapping driver buffers to user space. + * + * @param file struct file * + * + * @param vma structure vm_area_struct * + * + * @return 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error. + */ +static int mxc_pf_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + + pr_debug("pgoff=0x%lx, start=0x%lx, end=0x%lx\n", + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* make this _really_ smp-safe */ + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, vma->vm_start, + vma->vm_pgoff, size, vma->vm_page_prot)) { + printk(KERN_ERR "mxc_pf: remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mmap_exit; + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + + mmap_exit: + up(&pf_data.busy_lock); + return res; +} + +/*! + * Post Filter driver fsync function. This function implements the Linux + * file_operations.fsync() API function. + * + * The user must call fsync() before reading an output buffer. This + * call flushes the L1 and L2 caches + * + * @param filp structure file * + * + * @param dentry struct dentry * + * + * @param datasync unused + * + * @return status POLLIN | POLLRDNORM + */ +int mxc_pf_fsync(struct file *filp, struct dentry *dentry, int datasync) +{ + flush_cache_all(); + outer_flush_all(); + return 0; +} + +/*! + * Post Filter driver poll function. This function implements the Linux + * file_operations.poll() API function. + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_pf_poll(struct file *file, poll_table * wait) +{ + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&pf_data.busy_lock)) + return -EINTR; + + queue = &pf_data.pf_wait; + poll_wait(file, queue, wait); + + up(&pf_data.busy_lock); + + return res; +} + +/*! + * File operation structure functions pointers. + */ +static struct file_operations mxc_pf_fops = { + .owner = THIS_MODULE, + .open = mxc_pf_open, + .release = mxc_pf_release, + .ioctl = mxc_pf_ioctl, + .poll = mxc_pf_poll, + .mmap = mxc_pf_mmap, + .fsync = mxc_pf_fsync, +}; + +static int mxc_pf_major = 0; + +/*! + * Post Filter driver module initialization function. + */ +int mxc_pf_dev_init(void) +{ + int ret = 0; + struct device *temp_class; + + mxc_pf_major = register_chrdev(0, "mxc_ipu_pf", &mxc_pf_fops); + + if (mxc_pf_major < 0) { + printk(KERN_INFO "Unable to get a major for mxc_ipu_pf"); + return mxc_pf_major; + } + + mxc_pf_class = class_create(THIS_MODULE, "mxc_ipu_pf"); + if (IS_ERR(mxc_pf_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class.\n"); + ret = PTR_ERR(mxc_pf_class); + goto err_out1; + } + + temp_class = device_create(mxc_pf_class, NULL, MKDEV(mxc_pf_major, 0), NULL, + "mxc_ipu_pf"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating mxc_ipu_pf class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + printk(KERN_INFO "IPU Post-filter loading\n"); + + return 0; + + err_out2: + class_destroy(mxc_pf_class); + err_out1: + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + return ret; +} + +/*! + * Post Filter driver module exit function. + */ +static void mxc_pf_exit(void) +{ + if (mxc_pf_major > 0) { + device_destroy(mxc_pf_class, MKDEV(mxc_pf_major, 0)); + class_destroy(mxc_pf_class); + unregister_chrdev(mxc_pf_major, "mxc_ipu_pf"); + } +} + +module_init(mxc_pf_dev_init); +module_exit(mxc_pf_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC MPEG4/H.264 Postfilter Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ipu3/Kconfig b/drivers/mxc/ipu3/Kconfig new file mode 100644 index 000000000000..0ae0ffa9e19d --- /dev/null +++ b/drivers/mxc/ipu3/Kconfig @@ -0,0 +1,5 @@ +config MXC_IPU_V3 + bool + +config MXC_IPU_V3D + bool diff --git a/drivers/mxc/ipu3/Makefile b/drivers/mxc/ipu3/Makefile new file mode 100644 index 000000000000..2d9bc3c8b0c3 --- /dev/null +++ b/drivers/mxc/ipu3/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_MXC_IPU_V3) = mxc_ipu.o + +mxc_ipu-objs := ipu_common.o ipu_ic.o ipu_disp.o ipu_capture.o ipu_device.o diff --git a/drivers/mxc/ipu3/ipu_capture.c b/drivers/mxc/ipu3/ipu_capture.c new file mode 100644 index 000000000000..256fb9aa17e5 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_capture.c @@ -0,0 +1,711 @@ +/* + * Copyright 2008-2009 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 ipu_capture.c + * + * @brief IPU capture dase functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/ipu.h> +#include <linux/clk.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" + +/*! + * ipu_csi_init_interface + * Sets initial values for the CSI registers. + * The width and height of the sensor and the actual frame size will be + * set to the same values. + * @param width Sensor width + * @param height Sensor height + * @param pixel_fmt pixel format + * @param cfg_param ipu_csi_signal_cfg_t structure + * @param csi csi 0 or csi 1 + * + * @return 0 for success, -EINVAL for error + */ +int32_t +ipu_csi_init_interface(uint16_t width, uint16_t height, uint32_t pixel_fmt, + ipu_csi_signal_cfg_t cfg_param) +{ + uint32_t data = 0; + uint32_t csi = cfg_param.csi; + unsigned long lock_flags; + + /* Set SENS_DATA_FORMAT bits (8, 9 and 10) + RGB or YUV444 is 0 which is current value in data so not set + explicitly + This is also the default value if attempts are made to set it to + something invalid. */ + switch (pixel_fmt) { + case IPU_PIX_FMT_YUYV: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_YUYV; + break; + case IPU_PIX_FMT_UYVY: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_YUV422_UYVY; + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR24: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB_YUV444; + break; + case IPU_PIX_FMT_GENERIC: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_BAYER; + break; + case IPU_PIX_FMT_RGB565: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB565; + break; + case IPU_PIX_FMT_RGB555: + cfg_param.data_fmt = CSI_SENS_CONF_DATA_FMT_RGB555; + break; + default: + return -EINVAL; + } + + /* Set the CSI_SENS_CONF register remaining fields */ + data |= cfg_param.data_width << CSI_SENS_CONF_DATA_WIDTH_SHIFT | + cfg_param.data_fmt << CSI_SENS_CONF_DATA_FMT_SHIFT | + cfg_param.data_pol << CSI_SENS_CONF_DATA_POL_SHIFT | + cfg_param.Vsync_pol << CSI_SENS_CONF_VSYNC_POL_SHIFT | + cfg_param.Hsync_pol << CSI_SENS_CONF_HSYNC_POL_SHIFT | + cfg_param.pixclk_pol << CSI_SENS_CONF_PIX_CLK_POL_SHIFT | + cfg_param.ext_vsync << CSI_SENS_CONF_EXT_VSYNC_SHIFT | + cfg_param.clk_mode << CSI_SENS_CONF_SENS_PRTCL_SHIFT | + cfg_param.pack_tight << CSI_SENS_CONF_PACK_TIGHT_SHIFT | + cfg_param.force_eof << CSI_SENS_CONF_FORCE_EOF_SHIFT | + cfg_param.data_en_pol << CSI_SENS_CONF_DATA_EN_POL_SHIFT; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel(data, CSI_SENS_CONF(csi)); + + /* Setup sensor frame size */ + __raw_writel((width - 1) | (height - 1) << 16, CSI_SENS_FRM_SIZE(csi)); + + /* Set CCIR registers */ + if ((cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_PROGRESSIVE) || + (cfg_param.clk_mode == IPU_CSI_CLK_MODE_CCIR656_INTERLACED)) { + _ipu_csi_ccir_err_detection_enable(csi); + __raw_writel(0x40030, CSI_CCIR_CODE_1(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + } else if ((cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_DDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_PROGRESSIVE_SDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_DDR) || + (cfg_param.clk_mode == + IPU_CSI_CLK_MODE_CCIR1120_INTERLACED_SDR)) { + _ipu_csi_ccir_err_detection_enable(csi); + __raw_writel(0x40030, CSI_CCIR_CODE_1(csi)); + __raw_writel(0xFF0000, CSI_CCIR_CODE_3(csi)); + } else if ((cfg_param.clk_mode == IPU_CSI_CLK_MODE_GATED_CLK) || + (cfg_param.clk_mode == IPU_CSI_CLK_MODE_NONGATED_CLK)) { + _ipu_csi_ccir_err_detection_disable(csi); + } + + dev_dbg(g_ipu_dev, "CSI_SENS_CONF = 0x%08X\n", + __raw_readl(CSI_SENS_CONF(csi))); + dev_dbg(g_ipu_dev, "CSI_ACT_FRM_SIZE = 0x%08X\n", + __raw_readl(CSI_ACT_FRM_SIZE(csi))); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_csi_init_interface); + +/*! _ipu_csi_mclk_set + * + * @param pixel_clk desired pixel clock frequency in Hz + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_mclk_set(uint32_t pixel_clk, uint32_t csi) +{ + uint32_t temp; + uint32_t div_ratio; + + div_ratio = (clk_get_rate(g_ipu_clk) / pixel_clk) - 1; + + if (div_ratio > 0xFF || div_ratio < 0) { + dev_dbg(g_ipu_dev, "The value of pixel_clk extends normal range\n"); + return -EINVAL; + } + + temp = __raw_readl(CSI_SENS_CONF(csi)); + temp &= ~CSI_SENS_CONF_DIVRATIO_MASK; + __raw_writel(temp | (div_ratio << CSI_SENS_CONF_DIVRATIO_SHIFT), + CSI_SENS_CONF(csi)); + + return 0; +} + +/*! + * ipu_csi_enable_mclk + * + * @param csi csi 0 or csi 1 + * @param flag true to enable mclk, false to disable mclk + * @param wait true to wait 100ms make clock stable, false not wait + * + * @return Returns 0 on success + */ +int ipu_csi_enable_mclk(int csi, bool flag, bool wait) +{ + if (flag) { + clk_enable(g_csi_clk[csi]); + if (wait == true) + msleep(10); + } else + clk_disable(g_csi_clk[csi]); + + return 0; +} +EXPORT_SYMBOL(ipu_csi_enable_mclk); + +/*! + * ipu_csi_get_window_size + * + * @param width pointer to window width + * @param height pointer to window height + * @param csi csi 0 or csi 1 + */ +void ipu_csi_get_window_size(uint32_t *width, uint32_t *height, uint32_t csi) +{ + uint32_t reg; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(CSI_ACT_FRM_SIZE(csi)); + *width = (reg & 0xFFFF) + 1; + *height = (reg >> 16 & 0xFFFF) + 1; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_get_window_size); + +/*! + * ipu_csi_set_window_size + * + * @param width window width + * @param height window height + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_window_size(uint32_t width, uint32_t height, uint32_t csi) +{ + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel((width - 1) | (height - 1) << 16, CSI_ACT_FRM_SIZE(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_window_size); + +/*! + * ipu_csi_set_window_pos + * + * @param left uint32 window x start + * @param top uint32 window y start + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_window_pos(uint32_t left, uint32_t top, uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~(CSI_HSC_MASK | CSI_VSC_MASK); + temp |= ((top << CSI_VSC_SHIFT) | (left << CSI_HSC_SHIFT)); + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_window_pos); + +/*! + * _ipu_csi_horizontal_downsize_enable + * Enable horizontal downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_horizontal_downsize_enable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp |= CSI_HORI_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_horizontal_downsize_disable + * Disable horizontal downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_horizontal_downsize_disable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~CSI_HORI_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_vertical_downsize_enable + * Enable vertical downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_vertical_downsize_enable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp |= CSI_VERT_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_vertical_downsize_disable + * Disable vertical downsizing(decimation) by 2. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_vertical_downsize_disable(uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_OUT_FRM_CTRL(csi)); + temp &= ~CSI_VERT_DOWNSIZE_EN; + __raw_writel(temp, CSI_OUT_FRM_CTRL(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * ipu_csi_set_test_generator + * + * @param active 1 for active and 0 for inactive + * @param r_value red value for the generated pattern of even pixel + * @param g_value green value for the generated pattern of even + * pixel + * @param b_value blue value for the generated pattern of even pixel + * @param pixel_clk desired pixel clock frequency in Hz + * @param csi csi 0 or csi 1 + */ +void ipu_csi_set_test_generator(bool active, uint32_t r_value, + uint32_t g_value, uint32_t b_value, uint32_t pix_clk, uint32_t csi) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_TST_CTRL(csi)); + + if (active == false) { + temp &= ~CSI_TEST_GEN_MODE_EN; + __raw_writel(temp, CSI_TST_CTRL(csi)); + } else { + /* Set sensb_mclk div_ratio*/ + _ipu_csi_mclk_set(pix_clk, csi); + + temp &= ~(CSI_TEST_GEN_R_MASK | CSI_TEST_GEN_G_MASK | + CSI_TEST_GEN_B_MASK); + temp |= CSI_TEST_GEN_MODE_EN; + temp |= (r_value << CSI_TEST_GEN_R_SHIFT) | + (g_value << CSI_TEST_GEN_G_SHIFT) | + (b_value << CSI_TEST_GEN_B_SHIFT); + __raw_writel(temp, CSI_TST_CTRL(csi)); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} +EXPORT_SYMBOL(ipu_csi_set_test_generator); + +/*! + * _ipu_csi_ccir_err_detection_en + * Enable error detection and correction for + * CCIR interlaced mode with protection bit. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_ccir_err_detection_enable(uint32_t csi) +{ + uint32_t temp; + + temp = __raw_readl(CSI_CCIR_CODE_1(csi)); + temp |= CSI_CCIR_ERR_DET_EN; + __raw_writel(temp, CSI_CCIR_CODE_1(csi)); +} + +/*! + * _ipu_csi_ccir_err_detection_disable + * Disable error detection and correction for + * CCIR interlaced mode with protection bit. + * + * @param csi csi 0 or csi 1 + */ +void _ipu_csi_ccir_err_detection_disable(uint32_t csi) +{ + uint32_t temp; + + temp = __raw_readl(CSI_CCIR_CODE_1(csi)); + temp &= ~CSI_CCIR_ERR_DET_EN; + __raw_writel(temp, CSI_CCIR_CODE_1(csi)); +} + +/*! + * _ipu_csi_set_mipi_di + * + * @param num MIPI data identifier 0-3 handled by CSI + * @param di_val data identifier value + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_mipi_di(uint32_t num, uint32_t di_val, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (di_val > 0xFFL) { + retval = -EINVAL; + goto err; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_MIPI_DI(csi)); + + switch (num) { + case IPU_CSI_MIPI_DI0: + temp &= ~CSI_MIPI_DI0_MASK; + temp |= (di_val << CSI_MIPI_DI0_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI1: + temp &= ~CSI_MIPI_DI1_MASK; + temp |= (di_val << CSI_MIPI_DI1_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI2: + temp &= ~CSI_MIPI_DI2_MASK; + temp |= (di_val << CSI_MIPI_DI2_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + case IPU_CSI_MIPI_DI3: + temp &= ~CSI_MIPI_DI3_MASK; + temp |= (di_val << CSI_MIPI_DI3_SHIFT); + __raw_writel(temp, CSI_MIPI_DI(csi)); + break; + default: + retval = -EINVAL; + goto err; + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_csi_set_skip_isp + * + * @param skip select frames to be skipped and set the + * correspond bits to 1 + * @param max_ratio number of frames in a skipping set and the + * maximum value of max_ratio is 5 + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_skip_isp(uint32_t skip, uint32_t max_ratio, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (max_ratio > 5) { + retval = -EINVAL; + goto err; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_SKIP(csi)); + temp &= ~(CSI_MAX_RATIO_SKIP_ISP_MASK | CSI_SKIP_ISP_MASK); + temp |= (max_ratio << CSI_MAX_RATIO_SKIP_ISP_SHIFT) | + (skip << CSI_SKIP_ISP_SHIFT); + __raw_writel(temp, CSI_SKIP(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_csi_set_skip_smfc + * + * @param skip select frames to be skipped and set the + * correspond bits to 1 + * @param max_ratio number of frames in a skipping set and the + * maximum value of max_ratio is 5 + * @param id csi to smfc skipping id + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_set_skip_smfc(uint32_t skip, uint32_t max_ratio, + uint32_t id, uint32_t csi) +{ + uint32_t temp; + int retval = 0; + unsigned long lock_flags; + + if (max_ratio > 5 || id > 3) { + retval = -EINVAL; + goto err; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(CSI_SKIP(csi)); + temp &= ~(CSI_MAX_RATIO_SKIP_SMFC_MASK | CSI_ID_2_SKIP_MASK | + CSI_SKIP_SMFC_MASK); + temp |= (max_ratio << CSI_MAX_RATIO_SKIP_SMFC_SHIFT) | + (id << CSI_ID_2_SKIP_SHIFT) | + (skip << CSI_SKIP_SMFC_SHIFT); + __raw_writel(temp, CSI_SKIP(csi)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +err: + return retval; +} + +/*! + * _ipu_smfc_init + * Map CSI frames to IDMAC channels. + * + * @param channel IDMAC channel 0-3 + * @param mipi_id mipi id number 0-3 + * @param csi csi0 or csi1 + */ +void _ipu_smfc_init(ipu_channel_t channel, uint32_t mipi_id, uint32_t csi) +{ + uint32_t temp; + + temp = __raw_readl(SMFC_MAP); + + switch (channel) { + case CSI_MEM0: + temp &= ~SMFC_MAP_CH0_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH0_SHIFT; + break; + case CSI_MEM1: + temp &= ~SMFC_MAP_CH1_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH1_SHIFT; + break; + case CSI_MEM2: + temp &= ~SMFC_MAP_CH2_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH2_SHIFT; + break; + case CSI_MEM3: + temp &= ~SMFC_MAP_CH3_MASK; + temp |= ((csi << 2) | mipi_id) << SMFC_MAP_CH3_SHIFT; + break; + default: + return; + } + + __raw_writel(temp, SMFC_MAP); +} + +/*! + * _ipu_smfc_set_wmc + * Caution: The number of required channels, the enabled channels + * and the FIFO size per channel are configured restrictedly. + * + * @param channel IDMAC channel 0-3 + * @param set set 1 or clear 0 + * @param level water mark level when FIFO is on the + * relative size + */ +void _ipu_smfc_set_wmc(ipu_channel_t channel, bool set, uint32_t level) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(SMFC_WMC); + + switch (channel) { + case CSI_MEM0: + if (set == true) { + temp &= ~SMFC_WM0_SET_MASK; + temp |= level << SMFC_WM0_SET_SHIFT; + } else { + temp &= ~SMFC_WM0_CLR_MASK; + temp |= level << SMFC_WM0_CLR_SHIFT; + } + break; + case CSI_MEM1: + if (set == true) { + temp &= ~SMFC_WM1_SET_MASK; + temp |= level << SMFC_WM1_SET_SHIFT; + } else { + temp &= ~SMFC_WM1_CLR_MASK; + temp |= level << SMFC_WM1_CLR_SHIFT; + } + break; + case CSI_MEM2: + if (set == true) { + temp &= ~SMFC_WM2_SET_MASK; + temp |= level << SMFC_WM2_SET_SHIFT; + } else { + temp &= ~SMFC_WM2_CLR_MASK; + temp |= level << SMFC_WM2_CLR_SHIFT; + } + break; + case CSI_MEM3: + if (set == true) { + temp &= ~SMFC_WM3_SET_MASK; + temp |= level << SMFC_WM3_SET_SHIFT; + } else { + temp &= ~SMFC_WM3_CLR_MASK; + temp |= level << SMFC_WM3_CLR_SHIFT; + } + break; + default: + return; + } + + __raw_writel(temp, SMFC_WMC); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_smfc_set_burst_size + * + * @param channel IDMAC channel 0-3 + * @param bs burst size of IDMAC channel, + * the value programmed here shoud be BURST_SIZE-1 + */ +void _ipu_smfc_set_burst_size(ipu_channel_t channel, uint32_t bs) +{ + uint32_t temp; + unsigned long lock_flags; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + temp = __raw_readl(SMFC_BS); + + switch (channel) { + case CSI_MEM0: + temp &= ~SMFC_BS0_MASK; + temp |= bs << SMFC_BS0_SHIFT; + break; + case CSI_MEM1: + temp &= ~SMFC_BS1_MASK; + temp |= bs << SMFC_BS1_SHIFT; + break; + case CSI_MEM2: + temp &= ~SMFC_BS2_MASK; + temp |= bs << SMFC_BS2_SHIFT; + break; + case CSI_MEM3: + temp &= ~SMFC_BS3_MASK; + temp |= bs << SMFC_BS3_SHIFT; + break; + default: + return; + } + + __raw_writel(temp, SMFC_BS); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); +} + +/*! + * _ipu_csi_init + * + * @param channel IDMAC channel + * @param csi csi 0 or csi 1 + * + * @return Returns 0 on success or negative error code on fail + */ +int _ipu_csi_init(ipu_channel_t channel, uint32_t csi) +{ + uint32_t csi_sens_conf, csi_dest; + int retval = 0; + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + csi_dest = CSI_DATA_DEST_IDMAC; + break; + case CSI_PRP_ENC_MEM: + case CSI_PRP_VF_MEM: + csi_dest = CSI_DATA_DEST_IC; + break; + default: + retval = -EINVAL; + goto err; + } + + csi_sens_conf = __raw_readl(CSI_SENS_CONF(csi)); + csi_sens_conf &= ~CSI_SENS_CONF_DATA_DEST_MASK; + __raw_writel(csi_sens_conf | (csi_dest << + CSI_SENS_CONF_DATA_DEST_SHIFT), CSI_SENS_CONF(csi)); +err: + return retval; +} diff --git a/drivers/mxc/ipu3/ipu_common.c b/drivers/mxc/ipu3/ipu_common.c new file mode 100644 index 000000000000..0ad39e46351d --- /dev/null +++ b/drivers/mxc/ipu3/ipu_common.c @@ -0,0 +1,2225 @@ +/* + * Copyright 2005-2009 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 ipu_common.c + * + * @brief This file contains the IPU driver common API functions. + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <linux/clk.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +struct ipu_irq_node { + irqreturn_t(*handler) (int, void *); /*!< the ISR */ + const char *name; /*!< device associated with the interrupt */ + void *dev_id; /*!< some unique information for the ISR */ + __u32 flags; /*!< not used */ +}; + +/* Globals */ +struct clk *g_ipu_clk; +bool g_ipu_clk_enabled; +struct clk *g_di_clk[2]; +struct clk *g_csi_clk[2]; +unsigned char g_dc_di_assignment[10]; +ipu_channel_t g_ipu_csi_channel[2]; +int g_ipu_irq[2]; +int g_ipu_hw_rev; +bool g_sec_chan_en[22]; +bool g_thrd_chan_en[21]; +uint32_t g_channel_init_mask; +uint32_t g_channel_enable_mask; +DEFINE_SPINLOCK(ipu_lock); +struct device *g_ipu_dev; + +static struct ipu_irq_node ipu_irq_list[IPU_IRQ_COUNT]; +static const char driver_name[] = "mxc_ipu"; + +static int ipu_dc_use_count; +static int ipu_dp_use_count; +static int ipu_dmfc_use_count; +static int ipu_smfc_use_count; +static int ipu_ic_use_count; +static int ipu_rot_use_count; +static int ipu_vdi_use_count; +static int ipu_di_use_count[2]; +static int ipu_csi_use_count[2]; +/* Set to the follow using IC direct channel, default non */ +static ipu_channel_t using_ic_dirct_ch; + +/* for power gating */ +static uint32_t ipu_conf_reg; +static uint32_t ic_conf_reg; +static uint32_t ipu_cha_db_mode_reg[4]; +static uint32_t ipu_cha_cur_buf_reg[4]; +static uint32_t idma_enable_reg[2]; +static uint32_t buf_ready_reg[8]; + +u32 *ipu_cm_reg; +u32 *ipu_idmac_reg; +u32 *ipu_dp_reg; +u32 *ipu_ic_reg; +u32 *ipu_dc_reg; +u32 *ipu_dc_tmpl_reg; +u32 *ipu_dmfc_reg; +u32 *ipu_di_reg[2]; +u32 *ipu_smfc_reg; +u32 *ipu_csi_reg[2]; +u32 *ipu_cpmem_base; +u32 *ipu_tpmem_base; +u32 *ipu_disp_base[2]; +u32 *ipu_vdi_reg; + +/* Static functions */ +static irqreturn_t ipu_irq_handler(int irq, void *desc); + +static inline uint32_t channel_2_dma(ipu_channel_t ch, ipu_buffer_t type) +{ + return ((uint32_t) ch >> (6 * type)) & 0x3F; +}; + +static inline int _ipu_is_ic_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 11) && (dma_chan <= 22) && (dma_chan != 17) && (dma_chan != 18)); +} + +static inline int _ipu_is_ic_graphic_chan(uint32_t dma_chan) +{ + return (dma_chan == 14 || dma_chan == 15); +} + +/* Either DP BG or DP FG can be graphic window */ +static inline int _ipu_is_dp_graphic_chan(uint32_t dma_chan) +{ + return (dma_chan == 23 || dma_chan == 27); +} + +static inline int _ipu_is_irt_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 45) && (dma_chan <= 50)); +} + +static inline int _ipu_is_dmfc_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 23) && (dma_chan <= 29)); +} + +static inline int _ipu_is_smfc_chan(uint32_t dma_chan) +{ + return ((dma_chan >= 0) && (dma_chan <= 3)); +} + +#define idma_is_valid(ch) (ch != NO_DMA) +#define idma_mask(ch) (idma_is_valid(ch) ? (1UL << (ch & 0x1F)) : 0) +#define idma_is_set(reg, dma) (__raw_readl(reg(dma)) & idma_mask(dma)) + +/*! + * This function is called by the driver framework to initialize the IPU + * hardware. + * + * @param dev The device structure for the IPU passed in by the + * driver framework. + * + * @return Returns 0 on success or negative error code on error + */ +static int ipu_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mxc_ipu_config *plat_data = pdev->dev.platform_data; + unsigned long ipu_base; + + spin_lock_init(&ipu_lock); + + g_ipu_hw_rev = plat_data->rev; + + g_ipu_dev = &pdev->dev; + + /* Register IPU interrupts */ + g_ipu_irq[0] = platform_get_irq(pdev, 0); + if (g_ipu_irq[0] < 0) + return -EINVAL; + + if (request_irq(g_ipu_irq[0], ipu_irq_handler, 0, pdev->name, 0) != 0) { + dev_err(g_ipu_dev, "request SYNC interrupt failed\n"); + return -EBUSY; + } + /* Some platforms have 2 IPU interrupts */ + g_ipu_irq[1] = platform_get_irq(pdev, 1); + if (g_ipu_irq[1] >= 0) { + if (request_irq + (g_ipu_irq[1], ipu_irq_handler, 0, pdev->name, 0) != 0) { + dev_err(g_ipu_dev, "request ERR interrupt failed\n"); + return -EBUSY; + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (IS_ERR(res)) + return -ENODEV; + + ipu_base = res->start; + ipu_cm_reg = ioremap(ipu_base + IPU_CM_REG_BASE, PAGE_SIZE); + ipu_ic_reg = ioremap(ipu_base + IPU_IC_REG_BASE, PAGE_SIZE); + ipu_idmac_reg = ioremap(ipu_base + IPU_IDMAC_REG_BASE, PAGE_SIZE); + /* DP Registers are accessed thru the SRM */ + ipu_dp_reg = ioremap(ipu_base + IPU_SRM_REG_BASE, PAGE_SIZE); + ipu_dc_reg = ioremap(ipu_base + IPU_DC_REG_BASE, PAGE_SIZE); + ipu_dmfc_reg = ioremap(ipu_base + IPU_DMFC_REG_BASE, PAGE_SIZE); + ipu_di_reg[0] = ioremap(ipu_base + IPU_DI0_REG_BASE, PAGE_SIZE); + ipu_di_reg[1] = ioremap(ipu_base + IPU_DI1_REG_BASE, PAGE_SIZE); + ipu_smfc_reg = ioremap(ipu_base + IPU_SMFC_REG_BASE, PAGE_SIZE); + ipu_csi_reg[0] = ioremap(ipu_base + IPU_CSI0_REG_BASE, PAGE_SIZE); + ipu_csi_reg[1] = ioremap(ipu_base + IPU_CSI1_REG_BASE, PAGE_SIZE); + ipu_cpmem_base = ioremap(ipu_base + IPU_CPMEM_REG_BASE, PAGE_SIZE); + ipu_tpmem_base = ioremap(ipu_base + IPU_TPM_REG_BASE, SZ_64K); + ipu_dc_tmpl_reg = ioremap(ipu_base + IPU_DC_TMPL_REG_BASE, SZ_128K); + ipu_disp_base[1] = ioremap(ipu_base + IPU_DISP1_BASE, SZ_4K); + ipu_vdi_reg = ioremap(ipu_base + IPU_VDI_REG_BASE, PAGE_SIZE); + + dev_dbg(g_ipu_dev, "IPU VDI Regs = %p\n", ipu_vdi_reg); + dev_dbg(g_ipu_dev, "IPU CM Regs = %p\n", ipu_cm_reg); + dev_dbg(g_ipu_dev, "IPU IC Regs = %p\n", ipu_ic_reg); + dev_dbg(g_ipu_dev, "IPU IDMAC Regs = %p\n", ipu_idmac_reg); + dev_dbg(g_ipu_dev, "IPU DP Regs = %p\n", ipu_dp_reg); + dev_dbg(g_ipu_dev, "IPU DC Regs = %p\n", ipu_dc_reg); + dev_dbg(g_ipu_dev, "IPU DMFC Regs = %p\n", ipu_dmfc_reg); + dev_dbg(g_ipu_dev, "IPU DI0 Regs = %p\n", ipu_di_reg[0]); + dev_dbg(g_ipu_dev, "IPU DI1 Regs = %p\n", ipu_di_reg[1]); + dev_dbg(g_ipu_dev, "IPU SMFC Regs = %p\n", ipu_smfc_reg); + dev_dbg(g_ipu_dev, "IPU CSI0 Regs = %p\n", ipu_csi_reg[0]); + dev_dbg(g_ipu_dev, "IPU CSI1 Regs = %p\n", ipu_csi_reg[1]); + dev_dbg(g_ipu_dev, "IPU CPMem = %p\n", ipu_cpmem_base); + dev_dbg(g_ipu_dev, "IPU TPMem = %p\n", ipu_tpmem_base); + dev_dbg(g_ipu_dev, "IPU DC Template Mem = %p\n", ipu_dc_tmpl_reg); + dev_dbg(g_ipu_dev, "IPU Display Region 1 Mem = %p\n", ipu_disp_base[1]); + + /* Enable IPU and CSI clocks */ + /* Get IPU clock freq */ + g_ipu_clk = clk_get(&pdev->dev, "ipu_clk"); + dev_dbg(g_ipu_dev, "ipu_clk = %lu\n", clk_get_rate(g_ipu_clk)); + + clk_enable(g_ipu_clk); + + g_di_clk[0] = plat_data->di_clk[0]; + g_di_clk[1] = plat_data->di_clk[1]; + + g_csi_clk[0] = clk_get(&pdev->dev, "csi_mclk1"); + g_csi_clk[1] = clk_get(&pdev->dev, "csi_mclk2"); + + __raw_writel(0x807FFFFF, IPU_MEM_RST); + while (__raw_readl(IPU_MEM_RST) & 0x80000000) ; + + _ipu_init_dc_mappings(); + + /* Enable error interrupts by default */ + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(5)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(6)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(9)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(10)); + + /* DMFC Init */ + _ipu_dmfc_init(); + + /* Set sync refresh channels as high priority */ + __raw_writel(0x18800000L, IDMAC_CHA_PRI(0)); + + /* Set MCU_T to divide MCU access window into 2 */ + __raw_writel(0x00400000L | (IPU_MCU_T_DEFAULT << 18), IPU_DISP_GEN); + + clk_disable(g_ipu_clk); + + register_ipu_device(); + + return 0; +} + +int ipu_remove(struct platform_device *pdev) +{ + if (g_ipu_irq[0]) + free_irq(g_ipu_irq[0], 0); + if (g_ipu_irq[1]) + free_irq(g_ipu_irq[1], 0); + + clk_put(g_ipu_clk); + + iounmap(ipu_cm_reg); + iounmap(ipu_ic_reg); + iounmap(ipu_idmac_reg); + iounmap(ipu_dc_reg); + iounmap(ipu_dp_reg); + iounmap(ipu_dmfc_reg); + iounmap(ipu_di_reg[0]); + iounmap(ipu_di_reg[1]); + iounmap(ipu_smfc_reg); + iounmap(ipu_csi_reg[0]); + iounmap(ipu_csi_reg[1]); + iounmap(ipu_cpmem_base); + iounmap(ipu_tpmem_base); + iounmap(ipu_dc_tmpl_reg); + iounmap(ipu_disp_base[1]); + iounmap(ipu_vdi_reg); + + return 0; +} + +void ipu_dump_registers(void) +{ + printk(KERN_DEBUG "IPU_CONF = \t0x%08X\n", __raw_readl(IPU_CONF)); + printk(KERN_DEBUG "IDMAC_CONF = \t0x%08X\n", __raw_readl(IDMAC_CONF)); + printk(KERN_DEBUG "IDMAC_CHA_EN1 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_EN(0))); + printk(KERN_DEBUG "IDMAC_CHA_EN2 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_EN(32))); + printk(KERN_DEBUG "IDMAC_CHA_PRI1 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_PRI(0))); + printk(KERN_DEBUG "IDMAC_CHA_PRI2 = \t0x%08X\n", + __raw_readl(IDMAC_CHA_PRI(32))); + printk(KERN_DEBUG "IDMAC_BAND_EN1 = \t0x%08X\n", + __raw_readl(IDMAC_BAND_EN(0))); + printk(KERN_DEBUG "IDMAC_BAND_EN2 = \t0x%08X\n", + __raw_readl(IDMAC_BAND_EN(32))); + printk(KERN_DEBUG "IPU_CHA_DB_MODE_SEL0 = \t0x%08X\n", + __raw_readl(IPU_CHA_DB_MODE_SEL(0))); + printk(KERN_DEBUG "IPU_CHA_DB_MODE_SEL1 = \t0x%08X\n", + __raw_readl(IPU_CHA_DB_MODE_SEL(32))); + printk(KERN_DEBUG "DMFC_WR_CHAN = \t0x%08X\n", + __raw_readl(DMFC_WR_CHAN)); + printk(KERN_DEBUG "DMFC_WR_CHAN_DEF = \t0x%08X\n", + __raw_readl(DMFC_WR_CHAN_DEF)); + printk(KERN_DEBUG "DMFC_DP_CHAN = \t0x%08X\n", + __raw_readl(DMFC_DP_CHAN)); + printk(KERN_DEBUG "DMFC_DP_CHAN_DEF = \t0x%08X\n", + __raw_readl(DMFC_DP_CHAN_DEF)); + printk(KERN_DEBUG "DMFC_IC_CTRL = \t0x%08X\n", + __raw_readl(DMFC_IC_CTRL)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW1 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW1)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW2 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW2)); + printk(KERN_DEBUG "IPU_FS_PROC_FLOW3 = \t0x%08X\n", + __raw_readl(IPU_FS_PROC_FLOW3)); + printk(KERN_DEBUG "IPU_FS_DISP_FLOW1 = \t0x%08X\n", + __raw_readl(IPU_FS_DISP_FLOW1)); +} + +/*! + * This function is called to initialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to init. + * + * @param params Input parameter containing union of channel + * initialization parameters. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel(ipu_channel_t channel, ipu_channel_params_t *params) +{ + int ret = 0; + uint32_t ipu_conf; + uint32_t reg; + unsigned long lock_flags; + + dev_dbg(g_ipu_dev, "init channel = %d\n", IPU_CHAN_ID(channel)); + + /* re-enable error interrupts every time a channel is initialized */ + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(5)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(6)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(9)); + __raw_writel(0xFFFFFFFF, IPU_INT_CTRL(10)); + + if (g_ipu_clk_enabled == false) { + g_ipu_clk_enabled = true; + clk_enable(g_ipu_clk); + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already initialized %d\n", + IPU_CHAN_ID(channel)); + } + + ipu_conf = __raw_readl(IPU_CONF); + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + if (params->csi_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + + ipu_smfc_use_count++; + ipu_csi_use_count[params->csi_mem.csi]++; + g_ipu_csi_channel[params->csi_mem.csi] = channel; + + /*SMFC setting*/ + if (params->csi_mem.mipi_en) { + ipu_conf |= (1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_mem.csi)); + _ipu_smfc_init(channel, params->csi_mem.mipi_id, + params->csi_mem.csi); + } else { + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_mem.csi)); + _ipu_smfc_init(channel, 0, params->csi_mem.csi); + } + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_mem.csi); + break; + case CSI_PRP_ENC_MEM: + if (params->csi_prp_enc_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + if ((using_ic_dirct_ch != 0) && + (using_ic_dirct_ch != MEM_PRP_ENC_MEM)) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = CSI_PRP_ENC_MEM; + + ipu_ic_use_count++; + ipu_csi_use_count[params->csi_prp_enc_mem.csi]++; + g_ipu_csi_channel[params->csi_prp_enc_mem.csi] = channel; + + /*Without SMFC, CSI only support parallel data source*/ + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_prp_enc_mem.csi)); + + /*CSI0/1 feed into IC*/ + ipu_conf &= ~IPU_CONF_IC_INPUT; + if (params->csi_prp_enc_mem.csi) + ipu_conf |= IPU_CONF_CSI_SEL; + else + ipu_conf &= ~IPU_CONF_CSI_SEL; + + /*PRP skip buffer in memory, only valid when RWS_EN is true*/ + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_prp_enc_mem.csi); + _ipu_ic_init_prpenc(params, true); + break; + case CSI_PRP_VF_MEM: + if (params->csi_prp_vf_mem.csi > 1) { + ret = -EINVAL; + goto err; + } + if ((using_ic_dirct_ch != 0) && + (using_ic_dirct_ch != MEM_PRP_VF_MEM)) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = CSI_PRP_VF_MEM; + + ipu_ic_use_count++; + ipu_csi_use_count[params->csi_prp_vf_mem.csi]++; + g_ipu_csi_channel[params->csi_prp_vf_mem.csi] = channel; + + /*Without SMFC, CSI only support parallel data source*/ + ipu_conf &= ~(1 << (IPU_CONF_CSI0_DATA_SOURCE_OFFSET + + params->csi_prp_vf_mem.csi)); + + /*CSI0/1 feed into IC*/ + ipu_conf &= ~IPU_CONF_IC_INPUT; + if (params->csi_prp_vf_mem.csi) + ipu_conf |= IPU_CONF_CSI_SEL; + else + ipu_conf &= ~IPU_CONF_CSI_SEL; + + /*PRP skip buffer in memory, only valid when RWS_EN is true*/ + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + + /*CSI data (include compander) dest*/ + _ipu_csi_init(channel, params->csi_prp_vf_mem.csi); + _ipu_ic_init_prpvf(params, true); + break; + case MEM_PRP_VF_MEM: + ipu_ic_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg | FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + if (params->mem_prp_vf_mem.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + _ipu_ic_init_prpvf(params, false); + break; + case MEM_VDI_PRP_VF_MEM: + if ((using_ic_dirct_ch != 0) && + (using_ic_dirct_ch != MEM_VDI_PRP_VF_MEM)) { + ret = -EINVAL; + goto err; + } + using_ic_dirct_ch = MEM_VDI_PRP_VF_MEM; + ipu_ic_use_count++; + ipu_vdi_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + reg &= ~FS_VDI_SRC_SEL_MASK; + __raw_writel(reg , IPU_FS_PROC_FLOW1); + + if (params->mem_prp_vf_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + _ipu_ic_init_prpvf(params, false); + _ipu_vdi_init(channel, params); + break; + case MEM_VDI_PRP_VF_MEM_P: + _ipu_vdi_init(channel, params); + break; + case MEM_VDI_PRP_VF_MEM_N: + _ipu_vdi_init(channel, params); + break; + case MEM_ROT_VF_MEM: + ipu_ic_use_count++; + ipu_rot_use_count++; + _ipu_ic_init_rotate_vf(params); + break; + case MEM_PRP_ENC_MEM: + ipu_ic_use_count++; + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg | FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + _ipu_ic_init_prpenc(params, false); + break; + case MEM_ROT_ENC_MEM: + ipu_ic_use_count++; + ipu_rot_use_count++; + _ipu_ic_init_rotate_enc(params); + break; + case MEM_PP_MEM: + if (params->mem_pp_mem.graphics_combine_en) + g_sec_chan_en[IPU_CHAN_ID(channel)] = true; + if (params->mem_pp_mem.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + _ipu_ic_init_pp(params); + ipu_ic_use_count++; + break; + case MEM_ROT_PP_MEM: + _ipu_ic_init_rotate_pp(params); + ipu_ic_use_count++; + ipu_rot_use_count++; + break; + case MEM_DC_SYNC: + if (params->mem_dc_sync.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[1] = params->mem_dc_sync.di; + _ipu_dc_init(1, params->mem_dc_sync.di, + params->mem_dc_sync.interlaced); + ipu_di_use_count[params->mem_dc_sync.di]++; + ipu_dc_use_count++; + ipu_dmfc_use_count++; + break; + case MEM_BG_SYNC: + if (params->mem_dp_bg_sync.di > 1) { + ret = -EINVAL; + goto err; + } + + if (params->mem_dp_bg_sync.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + g_dc_di_assignment[5] = params->mem_dp_bg_sync.di; + _ipu_dp_init(channel, params->mem_dp_bg_sync.in_pixel_fmt, + params->mem_dp_bg_sync.out_pixel_fmt); + _ipu_dc_init(5, params->mem_dp_bg_sync.di, + params->mem_dp_bg_sync.interlaced); + ipu_di_use_count[params->mem_dp_bg_sync.di]++; + ipu_dc_use_count++; + ipu_dp_use_count++; + ipu_dmfc_use_count++; + break; + case MEM_FG_SYNC: + _ipu_dp_init(channel, params->mem_dp_fg_sync.in_pixel_fmt, + params->mem_dp_fg_sync.out_pixel_fmt); + + if (params->mem_dp_fg_sync.alpha_chan_en) + g_thrd_chan_en[IPU_CHAN_ID(channel)] = true; + + ipu_dc_use_count++; + ipu_dp_use_count++; + ipu_dmfc_use_count++; + break; + case DIRECT_ASYNC0: + if (params->direct_async.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[8] = params->direct_async.di; + _ipu_dc_init(8, params->direct_async.di, false); + ipu_di_use_count[params->direct_async.di]++; + ipu_dc_use_count++; + break; + case DIRECT_ASYNC1: + if (params->direct_async.di > 1) { + ret = -EINVAL; + goto err; + } + + g_dc_di_assignment[9] = params->direct_async.di; + _ipu_dc_init(9, params->direct_async.di, false); + ipu_di_use_count[params->direct_async.di]++; + ipu_dc_use_count++; + break; + default: + dev_err(g_ipu_dev, "Missing channel initialization\n"); + break; + } + + /* Enable IPU sub module */ + g_channel_init_mask |= 1L << IPU_CHAN_ID(channel); + if (ipu_ic_use_count == 1) + ipu_conf |= IPU_CONF_IC_EN; + if (ipu_vdi_use_count == 1) { + ipu_conf |= IPU_CONF_VDI_EN; + ipu_conf |= IPU_CONF_IC_INPUT; + } + if (ipu_rot_use_count == 1) + ipu_conf |= IPU_CONF_ROT_EN; + if (ipu_dc_use_count == 1) + ipu_conf |= IPU_CONF_DC_EN; + if (ipu_dp_use_count == 1) + ipu_conf |= IPU_CONF_DP_EN; + if (ipu_dmfc_use_count == 1) + ipu_conf |= IPU_CONF_DMFC_EN; + if (ipu_di_use_count[0] == 1) { + ipu_conf |= IPU_CONF_DI0_EN; + clk_enable(g_di_clk[0]); + } + if (ipu_di_use_count[1] == 1) { + ipu_conf |= IPU_CONF_DI1_EN; + clk_enable(g_di_clk[1]); + } + if (ipu_smfc_use_count == 1) + ipu_conf |= IPU_CONF_SMFC_EN; + if (ipu_csi_use_count[0] == 1) + ipu_conf |= IPU_CONF_CSI0_EN; + if (ipu_csi_use_count[1] == 1) + ipu_conf |= IPU_CONF_CSI1_EN; + + __raw_writel(ipu_conf, IPU_CONF); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_init_channel); + +/*! + * This function is called to uninitialize a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID to uninit. + */ +void ipu_uninit_channel(ipu_channel_t channel) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t in_dma, out_dma = 0; + uint32_t ipu_conf; + + if ((g_channel_init_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already uninitialized %d\n", + IPU_CHAN_ID(channel)); + return; + } + + /* Make sure channel is disabled */ + /* Get input and output dma channels */ + in_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + out_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + if (idma_is_set(IDMAC_CHA_EN, in_dma) || + idma_is_set(IDMAC_CHA_EN, out_dma)) { + dev_err(g_ipu_dev, + "Channel %d is not disabled, disable first\n", + IPU_CHAN_ID(channel)); + return; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + ipu_conf = __raw_readl(IPU_CONF); + + /* Reset the double buffer */ + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(in_dma)); + __raw_writel(reg & ~idma_mask(in_dma), IPU_CHA_DB_MODE_SEL(in_dma)); + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(out_dma)); + __raw_writel(reg & ~idma_mask(out_dma), IPU_CHA_DB_MODE_SEL(out_dma)); + + g_sec_chan_en[IPU_CHAN_ID(channel)] = false; + g_thrd_chan_en[IPU_CHAN_ID(channel)] = false; + + switch (channel) { + case CSI_MEM0: + case CSI_MEM1: + case CSI_MEM2: + case CSI_MEM3: + ipu_smfc_use_count--; + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + ipu_csi_use_count[0]--; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + ipu_csi_use_count[1]--; + } + break; + case CSI_PRP_ENC_MEM: + ipu_ic_use_count--; + if (using_ic_dirct_ch == CSI_PRP_ENC_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpenc(); + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + ipu_csi_use_count[0]--; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + ipu_csi_use_count[1]--; + } + break; + case CSI_PRP_VF_MEM: + ipu_ic_use_count--; + if (using_ic_dirct_ch == CSI_PRP_VF_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpvf(); + if (g_ipu_csi_channel[0] == channel) { + g_ipu_csi_channel[0] = CHAN_NONE; + ipu_csi_use_count[0]--; + } else if (g_ipu_csi_channel[1] == channel) { + g_ipu_csi_channel[1] = CHAN_NONE; + ipu_csi_use_count[1]--; + } + break; + case MEM_PRP_VF_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_prpvf(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_VDI_PRP_VF_MEM: + ipu_ic_use_count--; + ipu_vdi_use_count--; + if (using_ic_dirct_ch == MEM_VDI_PRP_VF_MEM) + using_ic_dirct_ch = 0; + _ipu_ic_uninit_prpvf(); + _ipu_vdi_uninit(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_VF_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_ROT_VF_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_vf(); + break; + case MEM_PRP_ENC_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_prpenc(); + reg = __raw_readl(IPU_FS_PROC_FLOW1); + __raw_writel(reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW1); + break; + case MEM_ROT_ENC_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_enc(); + break; + case MEM_PP_MEM: + ipu_ic_use_count--; + _ipu_ic_uninit_pp(); + break; + case MEM_ROT_PP_MEM: + ipu_rot_use_count--; + ipu_ic_use_count--; + _ipu_ic_uninit_rotate_pp(); + break; + case MEM_DC_SYNC: + _ipu_dc_uninit(1); + ipu_di_use_count[g_dc_di_assignment[1]]--; + ipu_dc_use_count--; + ipu_dmfc_use_count--; + break; + case MEM_BG_SYNC: + _ipu_dp_uninit(channel); + _ipu_dc_uninit(5); + ipu_di_use_count[g_dc_di_assignment[5]]--; + ipu_dc_use_count--; + ipu_dp_use_count--; + ipu_dmfc_use_count--; + break; + case MEM_FG_SYNC: + _ipu_dp_uninit(channel); + ipu_dc_use_count--; + ipu_dp_use_count--; + ipu_dmfc_use_count--; + break; + case DIRECT_ASYNC0: + _ipu_dc_uninit(8); + ipu_di_use_count[g_dc_di_assignment[8]]--; + ipu_dc_use_count--; + break; + case DIRECT_ASYNC1: + _ipu_dc_uninit(9); + ipu_di_use_count[g_dc_di_assignment[9]]--; + ipu_dc_use_count--; + break; + default: + break; + } + + g_channel_init_mask &= ~(1L << IPU_CHAN_ID(channel)); + + if (ipu_ic_use_count == 0) + ipu_conf &= ~IPU_CONF_IC_EN; + if (ipu_vdi_use_count == 0) { + ipu_conf &= ~IPU_CONF_VDI_EN; + ipu_conf &= ~IPU_CONF_IC_INPUT; + } + if (ipu_rot_use_count == 0) + ipu_conf &= ~IPU_CONF_ROT_EN; + if (ipu_dc_use_count == 0) + ipu_conf &= ~IPU_CONF_DC_EN; + if (ipu_dp_use_count == 0) + ipu_conf &= ~IPU_CONF_DP_EN; + if (ipu_dmfc_use_count == 0) + ipu_conf &= ~IPU_CONF_DMFC_EN; + if (ipu_di_use_count[0] == 0) { + ipu_conf &= ~IPU_CONF_DI0_EN; + clk_disable(g_di_clk[0]); + } + if (ipu_di_use_count[1] == 0) { + ipu_conf &= ~IPU_CONF_DI1_EN; + clk_disable(g_di_clk[1]); + } + if (ipu_smfc_use_count == 0) + ipu_conf &= ~IPU_CONF_SMFC_EN; + if (ipu_csi_use_count[0] == 0) + ipu_conf &= ~IPU_CONF_CSI0_EN; + if (ipu_csi_use_count[1] == 0) + ipu_conf &= ~IPU_CONF_CSI1_EN; + + __raw_writel(ipu_conf, IPU_CONF); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (ipu_conf == 0) { + clk_disable(g_ipu_clk); + g_ipu_clk_enabled = false; + } + + WARN_ON(ipu_ic_use_count < 0); + WARN_ON(ipu_vdi_use_count < 0); + WARN_ON(ipu_rot_use_count < 0); + WARN_ON(ipu_dc_use_count < 0); + WARN_ON(ipu_dp_use_count < 0); + WARN_ON(ipu_dmfc_use_count < 0); + WARN_ON(ipu_smfc_use_count < 0); +} +EXPORT_SYMBOL(ipu_uninit_channel); + +/*! + * This function is called to initialize a buffer for logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param pixel_fmt Input parameter for pixel format of buffer. + * Pixel format is a FOURCC ASCII code. + * + * @param width Input parameter for width of buffer in pixels. + * + * @param height Input parameter for height of buffer in pixels. + * + * @param stride Input parameter for stride length of buffer + * in pixels. + * + * @param rot_mode Input parameter for rotation setting of buffer. + * A rotation setting other than + * IPU_ROTATE_VERT_FLIP + * should only be used for input buffers of + * rotation channels. + * + * @param phyaddr_0 Input parameter buffer 0 physical address. + * + * @param phyaddr_1 Input parameter buffer 1 physical address. + * Setting this to a value other than NULL enables + * double buffering mode. + * + * @param u private u offset for additional cropping, + * zero if not used. + * + * @param v private v offset for additional cropping, + * zero if not used. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_init_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t pixel_fmt, + uint16_t width, uint16_t height, + uint32_t stride, + ipu_rotate_mode_t rot_mode, + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1, + uint32_t u, uint32_t v) +{ + unsigned long lock_flags; + uint32_t reg; + uint32_t dma_chan; + uint32_t burst_size; + + dma_chan = channel_2_dma(channel, type); + if (!idma_is_valid(dma_chan)) + return -EINVAL; + + if (stride < width * bytes_per_pixel(pixel_fmt)) + stride = width * bytes_per_pixel(pixel_fmt); + + if (stride % 4) { + dev_err(g_ipu_dev, + "Stride not 32-bit aligned, stride = %d\n", stride); + return -EINVAL; + } + /* IC & IRT channels' width must be multiple of 8 pixels */ + if ((_ipu_is_ic_chan(dma_chan) || _ipu_is_irt_chan(dma_chan)) + && (width % 8)) { + dev_err(g_ipu_dev, "Width must be 8 pixel multiple\n"); + return -EINVAL; + } + + /* Build parameter memory data for DMA channel */ + _ipu_ch_param_init(dma_chan, pixel_fmt, width, height, stride, u, v, 0, + phyaddr_0, phyaddr_1); + + /* Set correlative channel parameter of local alpha channel */ + if ((_ipu_is_ic_graphic_chan(dma_chan) || + _ipu_is_dp_graphic_chan(dma_chan)) && + (g_thrd_chan_en[IPU_CHAN_ID(channel)] == true)) { + _ipu_ch_param_set_alpha_use_separate_channel(dma_chan, true); + _ipu_ch_param_set_alpha_buffer_memory(dma_chan); + _ipu_ch_param_set_alpha_condition_read(dma_chan); + /* fix alpha width as 8 and burst size as 16*/ + _ipu_ch_params_set_alpha_width(dma_chan, 8); + _ipu_ch_param_set_burst_size(dma_chan, 16); + } else if (_ipu_is_ic_graphic_chan(dma_chan) && + ipu_pixel_format_has_alpha(pixel_fmt)) + _ipu_ch_param_set_alpha_use_separate_channel(dma_chan, false); + + if (rot_mode) + _ipu_ch_param_set_rotation(dma_chan, rot_mode); + + /* IC and ROT channels have restriction of 8 or 16 pix burst length */ + if (_ipu_is_ic_chan(dma_chan)) { + if ((width % 16) == 0) + _ipu_ch_param_set_burst_size(dma_chan, 16); + else + _ipu_ch_param_set_burst_size(dma_chan, 8); + } else if (_ipu_is_irt_chan(dma_chan)) { + _ipu_ch_param_set_burst_size(dma_chan, 8); + _ipu_ch_param_set_block_mode(dma_chan); + } else if (_ipu_is_dmfc_chan(dma_chan)) + _ipu_dmfc_set_wait4eot(dma_chan, width); + + if (_ipu_chan_is_interlaced(channel)) + _ipu_ch_param_set_interlaced_scan(dma_chan); + + if (_ipu_is_ic_chan(dma_chan) || _ipu_is_irt_chan(dma_chan)) { + burst_size = _ipu_ch_param_get_burst_size(dma_chan); + _ipu_ic_idma_init(dma_chan, width, height, burst_size, + rot_mode); + } else if (_ipu_is_smfc_chan(dma_chan)) { + burst_size = _ipu_ch_param_get_burst_size(dma_chan); + if ((pixel_fmt == IPU_PIX_FMT_GENERIC) && + ((_ipu_ch_param_get_bpp(dma_chan) == 5) || + (_ipu_ch_param_get_bpp(dma_chan) == 3))) + burst_size = burst_size >> 4; + else + burst_size = burst_size >> 2; + _ipu_smfc_set_burst_size(channel, burst_size-1); + } + + if (idma_is_set(IDMAC_CHA_PRI, dma_chan)) + _ipu_ch_param_set_high_priority(dma_chan); + + _ipu_ch_param_dump(dma_chan); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPU_CHA_DB_MODE_SEL(dma_chan)); + if (phyaddr_1) + reg |= idma_mask(dma_chan); + else + reg &= ~idma_mask(dma_chan); + __raw_writel(reg, IPU_CHA_DB_MODE_SEL(dma_chan)); + + /* Reset to buffer 0 */ + __raw_writel(idma_mask(dma_chan), IPU_CHA_CUR_BUF(dma_chan)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_init_channel_buffer); + +/*! + * This function is called to update the physical address of a buffer for + * a logical IPU channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for buffer number to update. + * 0 or 1 are the only valid values. + * + * @param phyaddr Input parameter buffer physical address. + * + * @return This function returns 0 on success or negative error code on + * fail. This function will fail if the buffer is set to ready. + */ +int32_t ipu_update_channel_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum, dma_addr_t phyaddr) +{ + uint32_t reg; + int ret = 0; + unsigned long lock_flags; + uint32_t dma_chan = channel_2_dma(channel, type); + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bufNum == 0) + reg = __raw_readl(IPU_CHA_BUF0_RDY(dma_chan)); + else + reg = __raw_readl(IPU_CHA_BUF1_RDY(dma_chan)); + + if ((reg & idma_mask(dma_chan)) == 0) + _ipu_ch_param_set_buffer(dma_chan, bufNum, phyaddr); + else + ret = -EACCES; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return ret; +} +EXPORT_SYMBOL(ipu_update_channel_buffer); + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param channel Input parameter for the logical channel ID. + * + * @param type Input parameter which buffer to initialize. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_select_buffer(ipu_channel_t channel, ipu_buffer_t type, + uint32_t bufNum) +{ + uint32_t dma_chan = channel_2_dma(channel, type); + + if (dma_chan == IDMA_CHAN_INVALID) + return -EINVAL; + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + __raw_writel(idma_mask(dma_chan), IPU_CHA_BUF0_RDY(dma_chan)); + } else { + /*Mark buffer 1 as ready. */ + __raw_writel(idma_mask(dma_chan), IPU_CHA_BUF1_RDY(dma_chan)); + } + if (channel == MEM_VDI_PRP_VF_MEM) + _ipu_vdi_toggle_top_field_man(); + return 0; +} +EXPORT_SYMBOL(ipu_select_buffer); + +/*! + * This function is called to set a channel's buffer as ready. + * + * @param bufNum Input parameter for which buffer number set to + * ready state. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_select_multi_vdi_buffer(uint32_t bufNum) +{ + + uint32_t dma_chan = channel_2_dma(MEM_VDI_PRP_VF_MEM, IPU_INPUT_BUFFER); + uint32_t mask_bit = + idma_mask(channel_2_dma(MEM_VDI_PRP_VF_MEM_P, IPU_INPUT_BUFFER))| + idma_mask(dma_chan)| + idma_mask(channel_2_dma(MEM_VDI_PRP_VF_MEM_N, IPU_INPUT_BUFFER)); + + if (bufNum == 0) { + /*Mark buffer 0 as ready. */ + __raw_writel(mask_bit, IPU_CHA_BUF0_RDY(dma_chan)); + } else { + /*Mark buffer 1 as ready. */ + __raw_writel(mask_bit, IPU_CHA_BUF1_RDY(dma_chan)); + } + _ipu_vdi_toggle_top_field_man(); + return 0; +} +EXPORT_SYMBOL(ipu_select_multi_vdi_buffer); + +#define NA -1 +static int proc_dest_sel[] = + { 0, 1, 1, 3, 5, 5, 4, 7, 8, 9, 10, 11, 12, 14, 15, 16, + 0, 1, 1, 5, 5, 5, 5, 5, 7, 8, 9, 10, 11, 12, 14, 31 }; +static int proc_src_sel[] = { 0, 6, 7, 6, 7, 8, 5, NA, NA, NA, + NA, NA, NA, NA, NA, 1, 2, 3, 4, 7, 8, NA, NA, NA }; +static int disp_src_sel[] = { 0, 6, 7, 8, 3, 4, 5, NA, NA, NA, + NA, NA, NA, NA, NA, 1, NA, 2, NA, 3, 4, 4, 4, 4 }; + + +/*! + * This function links 2 channels together for automatic frame + * synchronization. The output of the source channel is linked to the input of + * the destination channel. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_link_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + int retval = 0; + unsigned long lock_flags; + uint32_t fs_proc_flow1; + uint32_t fs_proc_flow2; + uint32_t fs_proc_flow3; + uint32_t fs_disp_flow1; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow1 = __raw_readl(IPU_FS_PROC_FLOW1); + fs_proc_flow2 = __raw_readl(IPU_FS_PROC_FLOW2); + fs_proc_flow3 = __raw_readl(IPU_FS_PROC_FLOW3); + fs_disp_flow1 = __raw_readl(IPU_FS_DISP_FLOW1); + + switch (src_ch) { + case CSI_MEM0: + fs_proc_flow3 &= ~FS_SMFC0_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC0_DEST_SEL_OFFSET; + break; + case CSI_MEM1: + fs_proc_flow3 &= ~FS_SMFC1_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC1_DEST_SEL_OFFSET; + break; + case CSI_MEM2: + fs_proc_flow3 &= ~FS_SMFC2_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC2_DEST_SEL_OFFSET; + break; + case CSI_MEM3: + fs_proc_flow3 &= ~FS_SMFC3_DEST_SEL_MASK; + fs_proc_flow3 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_SMFC3_DEST_SEL_OFFSET; + break; + case CSI_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_DEST_SEL_OFFSET; + break; + case CSI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_PP_MEM: + fs_proc_flow2 &= ~FS_PP_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PP_DEST_SEL_OFFSET; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow2 &= ~FS_PP_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PP_ROT_DEST_SEL_OFFSET; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_DEST_SEL_OFFSET; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPENC_ROT_DEST_SEL_OFFSET; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_DEST_SEL_OFFSET; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + fs_proc_flow2 |= + proc_dest_sel[IPU_CHAN_ID(dest_ch)] << + FS_PRPVF_ROT_DEST_SEL_OFFSET; + break; + default: + retval = -EINVAL; + goto err; + } + + switch (dest_ch) { + case MEM_PP_MEM: + fs_proc_flow1 &= ~FS_PP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PP_SRC_SEL_OFFSET; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow1 &= ~FS_PP_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PP_ROT_SRC_SEL_OFFSET; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow1 &= ~FS_PRPENC_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PRPENC_ROT_SRC_SEL_OFFSET; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << FS_PRP_SRC_SEL_OFFSET; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow1 &= ~FS_PRPVF_ROT_SRC_SEL_MASK; + fs_proc_flow1 |= + proc_src_sel[IPU_CHAN_ID(src_ch)] << + FS_PRPVF_ROT_SRC_SEL_OFFSET; + break; + case MEM_DC_SYNC: + fs_disp_flow1 &= ~FS_DC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << FS_DC1_SRC_SEL_OFFSET; + break; + case MEM_BG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC0_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_SYNC0_SRC_SEL_OFFSET; + break; + case MEM_FG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_SYNC1_SRC_SEL_OFFSET; + break; + case MEM_DC_ASYNC: + fs_disp_flow1 &= ~FS_DC2_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << FS_DC2_SRC_SEL_OFFSET; + break; + case MEM_BG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC0_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_ASYNC0_SRC_SEL_OFFSET; + break; + case MEM_FG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC1_SRC_SEL_MASK; + fs_disp_flow1 |= + disp_src_sel[IPU_CHAN_ID(src_ch)] << + FS_DP_ASYNC1_SRC_SEL_OFFSET; + break; + default: + retval = -EINVAL; + goto err; + } + + __raw_writel(fs_proc_flow1, IPU_FS_PROC_FLOW1); + __raw_writel(fs_proc_flow2, IPU_FS_PROC_FLOW2); + __raw_writel(fs_proc_flow3, IPU_FS_PROC_FLOW3); + __raw_writel(fs_disp_flow1, IPU_FS_DISP_FLOW1); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return retval; +} +EXPORT_SYMBOL(ipu_link_channels); + +/*! + * This function unlinks 2 channels and disables automatic frame + * synchronization. + * + * @param src_ch Input parameter for the logical channel ID of + * the source channel. + * + * @param dest_ch Input parameter for the logical channel ID of + * the destination channel. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_unlink_channels(ipu_channel_t src_ch, ipu_channel_t dest_ch) +{ + int retval = 0; + unsigned long lock_flags; + uint32_t fs_proc_flow1; + uint32_t fs_proc_flow2; + uint32_t fs_proc_flow3; + uint32_t fs_disp_flow1; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + fs_proc_flow1 = __raw_readl(IPU_FS_PROC_FLOW1); + fs_proc_flow2 = __raw_readl(IPU_FS_PROC_FLOW2); + fs_proc_flow3 = __raw_readl(IPU_FS_PROC_FLOW3); + fs_disp_flow1 = __raw_readl(IPU_FS_DISP_FLOW1); + + switch (src_ch) { + case CSI_MEM0: + fs_proc_flow3 &= ~FS_SMFC0_DEST_SEL_MASK; + break; + case CSI_MEM1: + fs_proc_flow3 &= ~FS_SMFC1_DEST_SEL_MASK; + break; + case CSI_MEM2: + fs_proc_flow3 &= ~FS_SMFC2_DEST_SEL_MASK; + break; + case CSI_MEM3: + fs_proc_flow3 &= ~FS_SMFC3_DEST_SEL_MASK; + break; + case CSI_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + break; + case CSI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_PP_MEM: + fs_proc_flow2 &= ~FS_PP_DEST_SEL_MASK; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow2 &= ~FS_PP_ROT_DEST_SEL_MASK; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_DEST_SEL_MASK; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow2 &= ~FS_PRPENC_ROT_DEST_SEL_MASK; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_DEST_SEL_MASK; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow2 &= ~FS_PRPVF_ROT_DEST_SEL_MASK; + break; + default: + retval = -EINVAL; + goto err; + } + + switch (dest_ch) { + case MEM_PP_MEM: + fs_proc_flow1 &= ~FS_PP_SRC_SEL_MASK; + break; + case MEM_ROT_PP_MEM: + fs_proc_flow1 &= ~FS_PP_ROT_SRC_SEL_MASK; + break; + case MEM_PRP_ENC_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_ROT_ENC_MEM: + fs_proc_flow1 &= ~FS_PRPENC_ROT_SRC_SEL_MASK; + break; + case MEM_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_VDI_PRP_VF_MEM: + fs_proc_flow1 &= ~FS_PRP_SRC_SEL_MASK; + break; + case MEM_ROT_VF_MEM: + fs_proc_flow1 &= ~FS_PRPVF_ROT_SRC_SEL_MASK; + break; + case MEM_DC_SYNC: + fs_disp_flow1 &= ~FS_DC1_SRC_SEL_MASK; + break; + case MEM_BG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC0_SRC_SEL_MASK; + break; + case MEM_FG_SYNC: + fs_disp_flow1 &= ~FS_DP_SYNC1_SRC_SEL_MASK; + break; + case MEM_DC_ASYNC: + fs_disp_flow1 &= ~FS_DC2_SRC_SEL_MASK; + break; + case MEM_BG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC0_SRC_SEL_MASK; + break; + case MEM_FG_ASYNC0: + fs_disp_flow1 &= ~FS_DP_ASYNC1_SRC_SEL_MASK; + break; + default: + retval = -EINVAL; + goto err; + } + + __raw_writel(fs_proc_flow1, IPU_FS_PROC_FLOW1); + __raw_writel(fs_proc_flow2, IPU_FS_PROC_FLOW2); + __raw_writel(fs_proc_flow3, IPU_FS_PROC_FLOW3); + __raw_writel(fs_disp_flow1, IPU_FS_DISP_FLOW1); + +err: + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return retval; +} +EXPORT_SYMBOL(ipu_unlink_channels); + +/*! + * This function check whether a logical channel was enabled. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 1 while request channel is enabled or + * 0 for not enabled. + */ +int32_t ipu_is_channel_busy(ipu_channel_t channel) +{ + uint32_t reg; + uint32_t in_dma; + uint32_t out_dma; + + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + if (reg & idma_mask(in_dma)) + return 1; + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + if (reg & idma_mask(out_dma)) + return 1; + return 0; +} +EXPORT_SYMBOL(ipu_is_channel_busy); + +/*! + * This function enables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_enable_channel(ipu_channel_t channel) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t out_dma; + uint32_t sec_dma; + uint32_t thrd_dma; + + if (g_channel_enable_mask & (1L << IPU_CHAN_ID(channel))) { + dev_err(g_ipu_dev, "Warning: channel already enabled %d\n", + IPU_CHAN_ID(channel)); + } + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (idma_is_valid(in_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + __raw_writel(reg | idma_mask(in_dma), IDMAC_CHA_EN(in_dma)); + } + if (idma_is_valid(out_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + __raw_writel(reg | idma_mask(out_dma), IDMAC_CHA_EN(out_dma)); + } + + if ((g_sec_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_PP_MEM) || (channel == MEM_PRP_VF_MEM) || + (channel == MEM_VDI_PRP_VF_MEM))) { + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(sec_dma)); + __raw_writel(reg | idma_mask(sec_dma), IDMAC_CHA_EN(sec_dma)); + } + if ((g_thrd_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_PP_MEM) || (channel == MEM_PRP_VF_MEM))) { + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg | idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg | idma_mask(sec_dma), IDMAC_SEP_ALPHA); + } else if ((g_thrd_chan_en[IPU_CHAN_ID(channel)]) && + ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC))) { + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg | idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg | idma_mask(in_dma), IDMAC_SEP_ALPHA); + } + + if ((channel == MEM_DC_SYNC) || (channel == MEM_BG_SYNC) || + (channel == MEM_FG_SYNC)) + _ipu_dp_dc_enable(channel); + + if (_ipu_is_ic_chan(in_dma) || _ipu_is_ic_chan(out_dma) || + _ipu_is_irt_chan(in_dma) || _ipu_is_irt_chan(out_dma)) + _ipu_ic_enable_task(channel); + + g_channel_enable_mask |= 1L << IPU_CHAN_ID(channel); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_enable_channel); + +static irqreturn_t disable_chan_irq_handler(int irq, void *dev_id) +{ + struct completion *comp = dev_id; + + complete(comp); + return IRQ_HANDLED; +} + +/*! + * This function disables a logical channel. + * + * @param channel Input parameter for the logical channel ID. + * + * @param wait_for_stop Flag to set whether to wait for channel end + * of frame or return immediately. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_disable_channel(ipu_channel_t channel, bool wait_for_stop) +{ + uint32_t reg; + unsigned long lock_flags; + uint32_t in_dma; + uint32_t out_dma; + uint32_t sec_dma = NO_DMA; + uint32_t thrd_dma = NO_DMA; + + if ((g_channel_enable_mask & (1L << IPU_CHAN_ID(channel))) == 0) { + dev_err(g_ipu_dev, "Channel already disabled %d\n", + IPU_CHAN_ID(channel)); + return 0; + } + + /* Get input and output dma channels */ + out_dma = channel_2_dma(channel, IPU_OUTPUT_BUFFER); + in_dma = channel_2_dma(channel, IPU_VIDEO_IN_BUFFER); + + if ((idma_is_valid(in_dma) && + !idma_is_set(IDMAC_CHA_EN, in_dma)) + && (idma_is_valid(out_dma) && + !idma_is_set(IDMAC_CHA_EN, out_dma))) + return -EINVAL; + + if (g_sec_chan_en[IPU_CHAN_ID(channel)]) + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + if (g_thrd_chan_en[IPU_CHAN_ID(channel)]) { + sec_dma = channel_2_dma(channel, IPU_GRAPH_IN_BUFFER); + thrd_dma = channel_2_dma(channel, IPU_ALPHA_IN_BUFFER); + } + + if ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC) || + (channel == MEM_DC_SYNC)) { + _ipu_dp_dc_disable(channel, false); + } else if (wait_for_stop) { + if (idma_is_set(IDMAC_CHA_BUSY, in_dma) || + idma_is_set(IDMAC_CHA_BUSY, out_dma) || + (g_sec_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, sec_dma)) || + (g_thrd_chan_en[IPU_CHAN_ID(channel)] && + idma_is_set(IDMAC_CHA_BUSY, thrd_dma)) || + (_ipu_channel_status(channel) == TASK_STAT_ACTIVE)) { + uint32_t ret, irq = out_dma; + DECLARE_COMPLETION_ONSTACK(disable_comp); + + ret = ipu_request_irq(irq, disable_chan_irq_handler, 0, NULL, &disable_comp); + if (ret < 0) { + dev_err(g_ipu_dev, "irq %d in use\n", irq); + } else { + ret = wait_for_completion_timeout(&disable_comp, msecs_to_jiffies(50)); + ipu_free_irq(irq, &disable_comp); + if (ret == msecs_to_jiffies(50)) + ipu_dump_registers(); + } + } + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + + /* Disable IC task */ + if (_ipu_is_ic_chan(in_dma) || _ipu_is_ic_chan(out_dma) || + _ipu_is_irt_chan(in_dma) || _ipu_is_irt_chan(out_dma)) + _ipu_ic_disable_task(channel); + + /* Disable DMA channel(s) */ + if (idma_is_valid(in_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(in_dma)); + __raw_writel(reg & ~idma_mask(in_dma), IDMAC_CHA_EN(in_dma)); + __raw_writel(idma_mask(in_dma), IPU_CHA_CUR_BUF(in_dma)); + } + if (idma_is_valid(out_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(out_dma)); + __raw_writel(reg & ~idma_mask(out_dma), IDMAC_CHA_EN(out_dma)); + __raw_writel(idma_mask(out_dma), IPU_CHA_CUR_BUF(out_dma)); + } + if (g_sec_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(sec_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(sec_dma)); + __raw_writel(reg & ~idma_mask(sec_dma), IDMAC_CHA_EN(sec_dma)); + __raw_writel(idma_mask(sec_dma), IPU_CHA_CUR_BUF(sec_dma)); + } + if (g_thrd_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(thrd_dma)) { + reg = __raw_readl(IDMAC_CHA_EN(thrd_dma)); + __raw_writel(reg & ~idma_mask(thrd_dma), IDMAC_CHA_EN(thrd_dma)); + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) { + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg & ~idma_mask(in_dma), IDMAC_SEP_ALPHA); + } else { + reg = __raw_readl(IDMAC_SEP_ALPHA); + __raw_writel(reg & ~idma_mask(sec_dma), IDMAC_SEP_ALPHA); + } + __raw_writel(idma_mask(thrd_dma), IPU_CHA_CUR_BUF(thrd_dma)); + } + + /* Set channel buffers NOT to be ready */ + __raw_writel(0xF0000000, IPU_GPR); /* write one to clear */ + if (idma_is_valid(in_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, in_dma)) { + __raw_writel(idma_mask(in_dma), + IPU_CHA_BUF0_RDY(in_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, in_dma)) { + __raw_writel(idma_mask(in_dma), + IPU_CHA_BUF1_RDY(in_dma)); + } + } + if (idma_is_valid(out_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, out_dma)) { + __raw_writel(idma_mask(out_dma), + IPU_CHA_BUF0_RDY(out_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, out_dma)) { + __raw_writel(idma_mask(out_dma), + IPU_CHA_BUF1_RDY(out_dma)); + } + } + if (g_sec_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(sec_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, sec_dma)) { + __raw_writel(idma_mask(sec_dma), + IPU_CHA_BUF0_RDY(sec_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, sec_dma)) { + __raw_writel(idma_mask(sec_dma), + IPU_CHA_BUF1_RDY(sec_dma)); + } + } + if (g_thrd_chan_en[IPU_CHAN_ID(channel)] && idma_is_valid(thrd_dma)) { + if (idma_is_set(IPU_CHA_BUF0_RDY, thrd_dma)) { + __raw_writel(idma_mask(thrd_dma), + IPU_CHA_BUF0_RDY(thrd_dma)); + } + if (idma_is_set(IPU_CHA_BUF1_RDY, thrd_dma)) { + __raw_writel(idma_mask(thrd_dma), + IPU_CHA_BUF1_RDY(thrd_dma)); + } + } + __raw_writel(0x0, IPU_GPR); /* write one to set */ + + g_channel_enable_mask &= ~(1L << IPU_CHAN_ID(channel)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_disable_channel); + +static irqreturn_t ipu_irq_handler(int irq, void *desc) +{ + int i; + uint32_t line; + irqreturn_t result = IRQ_NONE; + uint32_t int_stat; + const int err_reg[] = { 5, 6, 9, 10, 0 }; + const int int_reg[] = { 1, 2, 3, 4, 11, 12, 13, 14, 15, 0 }; + + for (i = 0;; i++) { + if (err_reg[i] == 0) + break; + int_stat = __raw_readl(IPU_INT_STAT(err_reg[i])); + int_stat &= __raw_readl(IPU_INT_CTRL(err_reg[i])); + if (int_stat) { + __raw_writel(int_stat, IPU_INT_STAT(err_reg[i])); + dev_err(g_ipu_dev, + "IPU Error - IPU_INT_STAT_%d = 0x%08X\n", + err_reg[i], int_stat); + /* Disable interrupts so we only get error once */ + int_stat = + __raw_readl(IPU_INT_CTRL(err_reg[i])) & ~int_stat; + __raw_writel(int_stat, IPU_INT_CTRL(err_reg[i])); + } + } + + for (i = 0;; i++) { + if (int_reg[i] == 0) + break; + int_stat = __raw_readl(IPU_INT_STAT(int_reg[i])); + int_stat &= __raw_readl(IPU_INT_CTRL(int_reg[i])); + __raw_writel(int_stat, IPU_INT_STAT(int_reg[i])); + while ((line = ffs(int_stat)) != 0) { + line--; + int_stat &= ~(1UL << line); + line += (int_reg[i] - 1) * 32; + result |= + ipu_irq_list[line].handler(line, + ipu_irq_list[line]. + dev_id); + } + } + + return result; +} + +/*! + * This function enables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to enable interrupt for. + * + */ +void ipu_enable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg |= IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_enable_irq); + +/*! + * This function disables the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to disable interrupt for. + * + */ +void ipu_disable_irq(uint32_t irq) +{ + uint32_t reg; + unsigned long lock_flags; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IPUIRQ_2_CTRLREG(irq)); + reg &= ~IPUIRQ_2_MASK(irq); + __raw_writel(reg, IPUIRQ_2_CTRLREG(irq)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_disable_irq); + +/*! + * This function clears the interrupt for the specified interrupt line. + * The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to clear interrupt for. + * + */ +void ipu_clear_irq(uint32_t irq) +{ + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + __raw_writel(IPUIRQ_2_MASK(irq), IPUIRQ_2_STATREG(irq)); + + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); +} +EXPORT_SYMBOL(ipu_clear_irq); + +/*! + * This function returns the current interrupt status for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @return Returns true if the interrupt is pending/asserted or false if + * the interrupt is not pending. + */ +bool ipu_get_irq_status(uint32_t irq) +{ + uint32_t reg; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + reg = __raw_readl(IPUIRQ_2_STATREG(irq)); + + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + if (reg & IPUIRQ_2_MASK(irq)) + return true; + else + return false; +} +EXPORT_SYMBOL(ipu_get_irq_status); + +/*! + * This function registers an interrupt handler function for the specified + * interrupt line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param handler Input parameter for address of the handler + * function. + * + * @param irq_flags Flags for interrupt mode. Currently not used. + * + * @param devname Input parameter for string name of driver + * registering the handler. + * + * @param dev_id Input parameter for pointer of data to be + * passed to the handler. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int ipu_request_irq(uint32_t irq, + irqreturn_t(*handler) (int, void *), + uint32_t irq_flags, const char *devname, void *dev_id) +{ + unsigned long lock_flags; + + BUG_ON(irq >= IPU_IRQ_COUNT); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (ipu_irq_list[irq].handler != NULL) { + dev_err(g_ipu_dev, + "handler already installed on irq %d\n", irq); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + ipu_irq_list[irq].handler = handler; + ipu_irq_list[irq].flags = irq_flags; + ipu_irq_list[irq].dev_id = dev_id; + ipu_irq_list[irq].name = devname; + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + ipu_enable_irq(irq); /* enable the interrupt */ + + return 0; +} +EXPORT_SYMBOL(ipu_request_irq); + +/*! + * This function unregisters an interrupt handler for the specified interrupt + * line. The interrupt lines are defined in \b ipu_irq_line enum. + * + * @param irq Interrupt line to get status for. + * + * @param dev_id Input parameter for pointer of data to be passed + * to the handler. This must match value passed to + * ipu_request_irq(). + * + */ +void ipu_free_irq(uint32_t irq, void *dev_id) +{ + ipu_disable_irq(irq); /* disable the interrupt */ + + if (ipu_irq_list[irq].dev_id == dev_id) + ipu_irq_list[irq].handler = NULL; +} +EXPORT_SYMBOL(ipu_free_irq); + +uint32_t _ipu_channel_status(ipu_channel_t channel) +{ + uint32_t stat = 0; + uint32_t task_stat_reg = __raw_readl(IPU_PROC_TASK_STAT); + + switch (channel) { + case MEM_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_VDI_PRP_VF_MEM: + stat = (task_stat_reg & TSTAT_VF_MASK) >> TSTAT_VF_OFFSET; + break; + case MEM_ROT_VF_MEM: + stat = + (task_stat_reg & TSTAT_VF_ROT_MASK) >> TSTAT_VF_ROT_OFFSET; + break; + case MEM_PRP_ENC_MEM: + stat = (task_stat_reg & TSTAT_ENC_MASK) >> TSTAT_ENC_OFFSET; + break; + case MEM_ROT_ENC_MEM: + stat = + (task_stat_reg & TSTAT_ENC_ROT_MASK) >> + TSTAT_ENC_ROT_OFFSET; + break; + case MEM_PP_MEM: + stat = (task_stat_reg & TSTAT_PP_MASK) >> TSTAT_PP_OFFSET; + break; + case MEM_ROT_PP_MEM: + stat = + (task_stat_reg & TSTAT_PP_ROT_MASK) >> TSTAT_PP_ROT_OFFSET; + break; + + default: + stat = TASK_STAT_IDLE; + break; + } + return stat; +} + +int32_t ipu_swap_channel(ipu_channel_t from_ch, ipu_channel_t to_ch) +{ + uint32_t reg; + unsigned long lock_flags; + + int from_dma = channel_2_dma(from_ch, IPU_INPUT_BUFFER); + int to_dma = channel_2_dma(to_ch, IPU_INPUT_BUFFER); + + /* enable target channel */ + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN(to_dma)); + __raw_writel(reg | idma_mask(to_dma), IDMAC_CHA_EN(to_dma)); + + g_channel_enable_mask |= 1L << IPU_CHAN_ID(to_ch); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + /* switch dp dc */ + _ipu_dp_dc_disable(from_ch, true); + + /* disable source channel */ + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(IDMAC_CHA_EN(from_dma)); + __raw_writel(reg & ~idma_mask(from_dma), IDMAC_CHA_EN(from_dma)); + __raw_writel(idma_mask(from_dma), IPU_CHA_CUR_BUF(from_dma)); + + g_channel_enable_mask &= ~(1L << IPU_CHAN_ID(from_ch)); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_swap_channel); + +uint32_t bytes_per_pixel(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: /*generic data */ + case IPU_PIX_FMT_RGB332: + case IPU_PIX_FMT_YUV420P: + case IPU_PIX_FMT_YUV422P: + return 1; + break; + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + return 2; + break; + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + return 3; + break; + case IPU_PIX_FMT_GENERIC_32: /*generic data */ + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + return 4; + break; + default: + return 1; + break; + } + return 0; +} +EXPORT_SYMBOL(bytes_per_pixel); + +ipu_color_space_t format_to_colorspace(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_BGR32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_RGB32: + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_ABGR32: + case IPU_PIX_FMT_LVDS666: + case IPU_PIX_FMT_LVDS888: + return RGB; + break; + + default: + return YCbCr; + break; + } + return RGB; +} + +bool ipu_pixel_format_has_alpha(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_ABGR32: + return true; + break; + default: + return false; + break; + } + return false; +} + +void ipu_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + _ipu_dp_set_csc_coefficients(channel, param); +} +EXPORT_SYMBOL(ipu_set_csc_coefficients); + +static int ipu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (g_ipu_clk_enabled) { + /* save and disable enabled channels*/ + idma_enable_reg[0] = __raw_readl(IDMAC_CHA_EN(0)); + idma_enable_reg[1] = __raw_readl(IDMAC_CHA_EN(32)); + while ((__raw_readl(IDMAC_CHA_BUSY(0)) & idma_enable_reg[0]) + || (__raw_readl(IDMAC_CHA_BUSY(32)) & + idma_enable_reg[1])) { + /* disable channel not busy already */ + uint32_t chan_should_disable, timeout = 1000, time = 0; + + chan_should_disable = + __raw_readl(IDMAC_CHA_BUSY(0)) + ^ idma_enable_reg[0]; + __raw_writel((~chan_should_disable) & + idma_enable_reg[0], IDMAC_CHA_EN(0)); + chan_should_disable = + __raw_readl(IDMAC_CHA_BUSY(1)) + ^ idma_enable_reg[1]; + __raw_writel((~chan_should_disable) & + idma_enable_reg[1], IDMAC_CHA_EN(32)); + msleep(2); + time += 2; + if (time >= timeout) + return -1; + } + __raw_writel(0, IDMAC_CHA_EN(0)); + __raw_writel(0, IDMAC_CHA_EN(32)); + + /* save double buffer select regs */ + ipu_cha_db_mode_reg[0] = __raw_readl(IPU_CHA_DB_MODE_SEL(0)); + ipu_cha_db_mode_reg[1] = __raw_readl(IPU_CHA_DB_MODE_SEL(32)); + ipu_cha_db_mode_reg[2] = + __raw_readl(IPU_ALT_CHA_DB_MODE_SEL(0)); + ipu_cha_db_mode_reg[3] = + __raw_readl(IPU_ALT_CHA_DB_MODE_SEL(32)); + + /* save current buffer regs */ + ipu_cha_cur_buf_reg[0] = __raw_readl(IPU_CHA_CUR_BUF(0)); + ipu_cha_cur_buf_reg[1] = __raw_readl(IPU_CHA_CUR_BUF(32)); + ipu_cha_cur_buf_reg[2] = __raw_readl(IPU_ALT_CUR_BUF0); + ipu_cha_cur_buf_reg[3] = __raw_readl(IPU_ALT_CUR_BUF1); + + /* save sub-modules status and disable all */ + ic_conf_reg = __raw_readl(IC_CONF); + __raw_writel(0, IC_CONF); + ipu_conf_reg = __raw_readl(IPU_CONF); + __raw_writel(0, IPU_CONF); + + /* save buf ready regs */ + buf_ready_reg[0] = __raw_readl(IPU_CHA_BUF0_RDY(0)); + buf_ready_reg[1] = __raw_readl(IPU_CHA_BUF0_RDY(32)); + buf_ready_reg[2] = __raw_readl(IPU_CHA_BUF1_RDY(0)); + buf_ready_reg[3] = __raw_readl(IPU_CHA_BUF1_RDY(32)); + buf_ready_reg[4] = __raw_readl(IPU_ALT_CHA_BUF0_RDY(0)); + buf_ready_reg[5] = __raw_readl(IPU_ALT_CHA_BUF0_RDY(32)); + buf_ready_reg[6] = __raw_readl(IPU_ALT_CHA_BUF1_RDY(0)); + buf_ready_reg[7] = __raw_readl(IPU_ALT_CHA_BUF1_RDY(32)); + } + + mxc_pg_enable(pdev); + + return 0; +} + +static int ipu_resume(struct platform_device *pdev) +{ + mxc_pg_disable(pdev); + + if (g_ipu_clk_enabled) { + + /* restore buf ready regs */ + __raw_writel(buf_ready_reg[0], IPU_CHA_BUF0_RDY(0)); + __raw_writel(buf_ready_reg[1], IPU_CHA_BUF0_RDY(32)); + __raw_writel(buf_ready_reg[2], IPU_CHA_BUF1_RDY(0)); + __raw_writel(buf_ready_reg[3], IPU_CHA_BUF1_RDY(32)); + __raw_writel(buf_ready_reg[4], IPU_ALT_CHA_BUF0_RDY(0)); + __raw_writel(buf_ready_reg[5], IPU_ALT_CHA_BUF0_RDY(32)); + __raw_writel(buf_ready_reg[6], IPU_ALT_CHA_BUF1_RDY(0)); + __raw_writel(buf_ready_reg[7], IPU_ALT_CHA_BUF1_RDY(32)); + + /* re-enable sub-modules*/ + __raw_writel(ipu_conf_reg, IPU_CONF); + __raw_writel(ic_conf_reg, IC_CONF); + + /* restore double buffer select regs */ + __raw_writel(ipu_cha_db_mode_reg[0], IPU_CHA_DB_MODE_SEL(0)); + __raw_writel(ipu_cha_db_mode_reg[1], IPU_CHA_DB_MODE_SEL(32)); + __raw_writel(ipu_cha_db_mode_reg[2], + IPU_ALT_CHA_DB_MODE_SEL(0)); + __raw_writel(ipu_cha_db_mode_reg[3], + IPU_ALT_CHA_DB_MODE_SEL(32)); + + /* restore current buffer select regs */ + __raw_writel(~(ipu_cha_cur_buf_reg[0]), IPU_CHA_CUR_BUF(0)); + __raw_writel(~(ipu_cha_cur_buf_reg[1]), IPU_CHA_CUR_BUF(32)); + __raw_writel(~(ipu_cha_cur_buf_reg[2]), IPU_ALT_CUR_BUF0); + __raw_writel(~(ipu_cha_cur_buf_reg[3]), IPU_ALT_CUR_BUF1); + + /* restart idma channel*/ + __raw_writel(idma_enable_reg[0], IDMAC_CHA_EN(0)); + __raw_writel(idma_enable_reg[1], IDMAC_CHA_EN(32)); + } else { + clk_enable(g_ipu_clk); + _ipu_dmfc_init(); + _ipu_init_dc_mappings(); + + /* Set sync refresh channels as high priority */ + __raw_writel(0x18800000L, IDMAC_CHA_PRI(0)); + clk_disable(g_ipu_clk); + } + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxcipu_driver = { + .driver = { + .name = "mxc_ipu", + }, + .probe = ipu_probe, + .remove = ipu_remove, + .suspend_late = ipu_suspend, + .resume_early = ipu_resume, +}; + +int32_t __init ipu_gen_init(void) +{ + int32_t ret; + + ret = platform_driver_register(&mxcipu_driver); + return 0; +} + +subsys_initcall(ipu_gen_init); + +static void __exit ipu_gen_uninit(void) +{ + platform_driver_unregister(&mxcipu_driver); +} + +module_exit(ipu_gen_uninit); diff --git a/drivers/mxc/ipu3/ipu_device.c b/drivers/mxc/ipu3/ipu_device.c new file mode 100644 index 000000000000..4c98d4c7edd3 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_device.c @@ -0,0 +1,446 @@ +/* + * Copyright 2005-2009 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 ipu_device.c + * + * @brief This file contains the IPUv3 driver device interface and fops functions. + * + * @ingroup IPU + */ + +#include <linux/types.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <asm/cacheflush.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +/* Strucutures and variables for exporting MXC IPU as device*/ + +static int mxc_ipu_major; +static struct class *mxc_ipu_class; + +DEFINE_SPINLOCK(event_lock); + +struct ipu_dev_irq_info { + wait_queue_head_t waitq; + int irq_pending; +} irq_info[480]; + +int register_ipu_device(void); + +/* Static functions */ + +int get_events(ipu_event_info *p) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&event_lock, flags); + if (irq_info[p->irq].irq_pending > 0) + irq_info[p->irq].irq_pending--; + else + ret = -1; + spin_unlock_irqrestore(&event_lock, flags); + + return ret; +} + +static irqreturn_t mxc_ipu_generic_handler(int irq, void *dev_id) +{ + irq_info[irq].irq_pending++; + + /* Wakeup any blocking user context */ + wake_up_interruptible(&(irq_info[irq].waitq)); + return IRQ_HANDLED; +} + +static int mxc_ipu_open(struct inode *inode, struct file *file) +{ + int ret = 0; + return ret; +} +static int mxc_ipu_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + + switch (cmd) { + case IPU_INIT_CHANNEL: + { + ipu_channel_parm parm; + + if (copy_from_user + (&parm, (ipu_channel_parm *) arg, + sizeof(ipu_channel_parm))) + return -EFAULT; + + if (!parm.flag) { + ret = + ipu_init_channel(parm.channel, + &parm.params); + } else { + ret = ipu_init_channel(parm.channel, NULL); + } + } + break; + case IPU_UNINIT_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_uninit_channel(ch); + } + break; + case IPU_INIT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) + return -EFAULT; + + ret = + ipu_init_channel_buffer( + parm.channel, parm.type, + parm.pixel_fmt, + parm.width, parm.height, + parm.stride, + parm.rot_mode, + parm.phyaddr_0, + parm.phyaddr_1, + parm.u_offset, + parm.v_offset); + + } + break; + case IPU_UPDATE_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) + return -EFAULT; + + if ((parm.phyaddr_0 != (dma_addr_t) NULL) + && (parm.phyaddr_1 == (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer( + parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_0); + } else if ((parm.phyaddr_0 == (dma_addr_t) NULL) + && (parm.phyaddr_1 != (dma_addr_t) NULL)) { + ret = + ipu_update_channel_buffer( + parm.channel, + parm.type, + parm.bufNum, + parm.phyaddr_1); + } else { + ret = -1; + } + + } + break; + case IPU_SELECT_CHANNEL_BUFFER: + { + ipu_channel_buf_parm parm; + if (copy_from_user + (&parm, (ipu_channel_buf_parm *) arg, + sizeof(ipu_channel_buf_parm))) + return -EFAULT; + + ret = + ipu_select_buffer(parm.channel, + parm.type, parm.bufNum); + + } + break; + case IPU_LINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) + return -EFAULT; + + ret = ipu_link_channels(link.src_ch, + link.dest_ch); + + } + break; + case IPU_UNLINK_CHANNELS: + { + ipu_channel_link link; + if (copy_from_user + (&link, (ipu_channel_link *) arg, + sizeof(ipu_channel_link))) + return -EFAULT; + + ret = ipu_unlink_channels(link.src_ch, + link.dest_ch); + + } + break; + case IPU_ENABLE_CHANNEL: + { + ipu_channel_t ch; + int __user *argp = (void __user *)arg; + if (get_user(ch, argp)) + return -EFAULT; + ipu_enable_channel(ch); + } + break; + case IPU_DISABLE_CHANNEL: + { + ipu_channel_info info; + if (copy_from_user + (&info, (ipu_channel_info *) arg, + sizeof(ipu_channel_info))) + return -EFAULT; + + ret = ipu_disable_channel(info.channel, + info.stop); + } + break; + case IPU_ENABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_enable_irq(irq); + } + break; + case IPU_DISABLE_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_disable_irq(irq); + } + break; + case IPU_CLEAR_IRQ: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ipu_clear_irq(irq); + } + break; + case IPU_FREE_IRQ: + { + ipu_irq_info info; + + if (copy_from_user + (&info, (ipu_irq_info *) arg, + sizeof(ipu_irq_info))) + return -EFAULT; + + ipu_free_irq(info.irq, info.dev_id); + irq_info[info.irq].irq_pending = 0; + } + break; + case IPU_REQUEST_IRQ_STATUS: + { + uint32_t irq; + int __user *argp = (void __user *)arg; + if (get_user(irq, argp)) + return -EFAULT; + ret = ipu_get_irq_status(irq); + } + break; + case IPU_REGISTER_GENERIC_ISR: + { + ipu_event_info info; + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + ret = + ipu_request_irq(info.irq, + mxc_ipu_generic_handler, + 0, "video_sink", info.dev); + if (ret == 0) + init_waitqueue_head(&(irq_info[info.irq].waitq)); + } + break; + case IPU_GET_EVENT: + /* User will have to allocate event_type + structure and pass the pointer in arg */ + { + ipu_event_info info; + int r = -1; + + if (copy_from_user + (&info, (ipu_event_info *) arg, + sizeof(ipu_event_info))) + return -EFAULT; + + r = get_events(&info); + if (r == -1) { + if ((file->f_flags & O_NONBLOCK) && + (irq_info[info.irq].irq_pending == 0)) + return -EAGAIN; + wait_event_interruptible_timeout(irq_info[info.irq].waitq, + (irq_info[info.irq].irq_pending != 0), 2 * HZ); + r = get_events(&info); + } + ret = -1; + if (r == 0) { + if (!copy_to_user((ipu_event_info *) arg, + &info, sizeof(ipu_event_info))) + ret = 0; + } + } + break; + case IPU_ALOC_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + info.vaddr = dma_alloc_coherent(0, + PAGE_ALIGN(info.size), + &info.paddr, + GFP_DMA | GFP_KERNEL); + if (info.vaddr == 0) { + printk(KERN_ERR "dma alloc failed!\n"); + return -ENOBUFS; + } + if (copy_to_user((ipu_mem_info *) arg, &info, + sizeof(ipu_mem_info)) > 0) + return -EFAULT; + } + break; + case IPU_FREE_MEM: + { + ipu_mem_info info; + if (copy_from_user + (&info, (ipu_mem_info *) arg, + sizeof(ipu_mem_info))) + return -EFAULT; + + if (info.vaddr) + dma_free_coherent(0, PAGE_ALIGN(info.size), + info.vaddr, info.paddr); + else + return -EFAULT; + } + break; + case IPU_IS_CHAN_BUSY: + { + ipu_channel_t chan; + if (copy_from_user + (&chan, (ipu_channel_t *)arg, + sizeof(ipu_channel_t))) + return -EFAULT; + + if (ipu_is_channel_busy(chan)) + ret = 1; + else + ret = 0; + } + break; + default: + break; + } + return ret; +} + +static int mxc_ipu_mmap(struct file *file, struct vm_area_struct *vma) +{ +// vma->vm_page_prot = pgprot_writethru(vma->vm_page_prot); + + if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) { + printk(KERN_ERR + "mmap failed!\n"); + return -ENOBUFS; + } + return 0; +} + +static int mxc_ipu_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static struct file_operations mxc_ipu_fops = { + .owner = THIS_MODULE, + .open = mxc_ipu_open, + .mmap = mxc_ipu_mmap, + .release = mxc_ipu_release, + .ioctl = mxc_ipu_ioctl +}; + +int register_ipu_device() +{ + int ret = 0; + struct device *temp; + mxc_ipu_major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); + if (mxc_ipu_major < 0) { + printk(KERN_ERR + "Unable to register Mxc Ipu as a char device\n"); + return mxc_ipu_major; + } + + mxc_ipu_class = class_create(THIS_MODULE, "mxc_ipu"); + if (IS_ERR(mxc_ipu_class)) { + printk(KERN_ERR "Unable to create class for Mxc Ipu\n"); + ret = PTR_ERR(mxc_ipu_class); + goto err1; + } + + temp = device_create(mxc_ipu_class, NULL, MKDEV(mxc_ipu_major, 0), + NULL, "mxc_ipu"); + + if (IS_ERR(temp)) { + printk(KERN_ERR "Unable to create class device for Mxc Ipu\n"); + ret = PTR_ERR(temp); + goto err2; + } + spin_lock_init(&event_lock); + + return ret; + +err2: + class_destroy(mxc_ipu_class); +err1: + unregister_chrdev(mxc_ipu_major, "mxc_ipu"); + return ret; + +} diff --git a/drivers/mxc/ipu3/ipu_disp.c b/drivers/mxc/ipu3/ipu_disp.c new file mode 100644 index 000000000000..79b0c2123c22 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_disp.c @@ -0,0 +1,1515 @@ +/* + * Copyright 2005-2009 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 ipu_disp.c + * + * @brief IPU display submodule API functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/ipu.h> +#include <asm/atomic.h> +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum csc_type_t { + RGB2YUV = 0, + YUV2RGB, + RGB2RGB, + YUV2YUV, + CSC_NONE, + CSC_NUM +}; + +struct dp_csc_param_t { + int mode; + void *coeff; +}; + +#define SYNC_WAVE 0 +#define ASYNC_SER_WAVE 6 + +/* DC display ID assignments */ +#define DC_DISP_ID_SYNC(di) (di) +#define DC_DISP_ID_SERIAL 2 +#define DC_DISP_ID_ASYNC 3 + + +/* all value below is determined by fix reg setting in _ipu_dmfc_init*/ +#define DMFC_FIFO_SIZE_28 (128*4) +#define DMFC_FIFO_SIZE_29 (64*4) +#define DMFC_FIFO_SIZE_24 (64*4) +#define DMFC_FIFO_SIZE_27 (128*4) +#define DMFC_FIFO_SIZE_23 (128*4) + +void _ipu_dmfc_init(void) +{ + /* disable DMFC-IC channel*/ + __raw_writel(0x2, DMFC_IC_CTRL); + /* 1 - segment 0 and 1; 2, 1C and 2C unused */ + __raw_writel(0x00000088, DMFC_WR_CHAN); + __raw_writel(0x202020F6, DMFC_WR_CHAN_DEF); + /* 5B - segment 2 and 3; 5F - segment 4 and 5; */ + /* 6B - segment 6; 6F - segment 7 */ + __raw_writel(0x1F1E9694, DMFC_DP_CHAN); + /* Enable chan 5 watermark set at 5 bursts and clear at 7 bursts */ + __raw_writel(0x2020F6F6, DMFC_DP_CHAN_DEF); +} + +void _ipu_dmfc_set_wait4eot(int dma_chan, int width) +{ + u32 dmfc_gen1 = __raw_readl(DMFC_GENERAL1); + + if (dma_chan == 23) { /*5B*/ + if (DMFC_FIFO_SIZE_23/width > 3) + dmfc_gen1 |= 1UL << 20; + else + dmfc_gen1 &= ~(1UL << 20); + } else if (dma_chan == 24) { /*6B*/ + if (DMFC_FIFO_SIZE_24/width > 1) + dmfc_gen1 |= 1UL << 22; + else + dmfc_gen1 &= ~(1UL << 22); + } else if (dma_chan == 27) { /*5F*/ + if (DMFC_FIFO_SIZE_27/width > 2) + dmfc_gen1 |= 1UL << 21; + else + dmfc_gen1 &= ~(1UL << 21); + } else if (dma_chan == 28) { /*1*/ + if (DMFC_FIFO_SIZE_28/width > 2) + dmfc_gen1 |= 1UL << 16; + else + dmfc_gen1 &= ~(1UL << 16); + } else if (dma_chan == 29) { /*6F*/ + if (DMFC_FIFO_SIZE_29/width > 1) + dmfc_gen1 |= 1UL << 23; + else + dmfc_gen1 &= ~(1UL << 23); + } + + __raw_writel(dmfc_gen1, DMFC_GENERAL1); +} + +static void _ipu_di_data_wave_config(int di, + int wave_gen, + int access_size, int component_size) +{ + u32 reg; + reg = (access_size << DI_DW_GEN_ACCESS_SIZE_OFFSET) | + (component_size << DI_DW_GEN_COMPONENT_SIZE_OFFSET); + __raw_writel(reg, DI_DW_GEN(di, wave_gen)); +} + +static void _ipu_di_data_pin_config(int di, int wave_gen, int di_pin, int set, + int up, int down) +{ + u32 reg; + + reg = __raw_readl(DI_DW_GEN(di, wave_gen)); + reg &= ~(0x3 << (di_pin * 2)); + reg |= set << (di_pin * 2); + __raw_writel(reg, DI_DW_GEN(di, wave_gen)); + + __raw_writel((down << 16) | up, DI_DW_SET(di, wave_gen, set)); +} + +static void _ipu_di_sync_config(int di, int wave_gen, + int run_count, int run_src, + int offset_count, int offset_src, + int repeat_count, int cnt_clr_src, + int cnt_polarity_gen_en, + int cnt_polarity_clr_src, + int cnt_polarity_trigger_src, + int cnt_up, int cnt_down) +{ + u32 reg; + + if ((run_count >= 0x1000) || (offset_count >= 0x1000) || (repeat_count >= 0x1000) || + (cnt_up >= 0x400) || (cnt_down >= 0x400)) { + dev_err(g_ipu_dev, "DI%d counters out of range.\n", di); + return; + } + + reg = (run_count << 19) | (++run_src << 16) | + (offset_count << 3) | ++offset_src; + __raw_writel(reg, DI_SW_GEN0(di, wave_gen)); + reg = (cnt_polarity_gen_en << 29) | (++cnt_clr_src << 25) | + (++cnt_polarity_trigger_src << 12) | (++cnt_polarity_clr_src << 9); + reg |= (cnt_down << 16) | cnt_up; + if (repeat_count == 0) { + /* Enable auto reload */ + reg |= 0x10000000; + } + __raw_writel(reg, DI_SW_GEN1(di, wave_gen)); + reg = __raw_readl(DI_STP_REP(di, wave_gen)); + reg &= ~(0xFFFF << (16 * ((wave_gen - 1) & 0x1))); + reg |= repeat_count << (16 * ((wave_gen - 1) & 0x1)); + __raw_writel(reg, DI_STP_REP(di, wave_gen)); +} + +static void _ipu_dc_map_config(int map, int byte_num, int offset, int mask) +{ + int ptr = map * 3 + byte_num; + u32 reg; + + reg = __raw_readl(DC_MAP_CONF_VAL(ptr)); + reg &= ~(0xFFFF << (16 * (ptr & 0x1))); + reg |= ((offset << 8) | mask) << (16 * (ptr & 0x1)); + __raw_writel(reg, DC_MAP_CONF_VAL(ptr)); + + reg = __raw_readl(DC_MAP_CONF_PTR(map)); + reg &= ~(0x1F << ((16 * (map & 0x1)) + (5 * byte_num))); + reg |= ptr << ((16 * (map & 0x1)) + (5 * byte_num)); + __raw_writel(reg, DC_MAP_CONF_PTR(map)); +} + +static void _ipu_dc_map_clear(int map) +{ + u32 reg = __raw_readl(DC_MAP_CONF_PTR(map)); + __raw_writel(reg & ~(0xFFFF << (16 * (map & 0x1))), + DC_MAP_CONF_PTR(map)); +} + +static void _ipu_dc_write_tmpl(int word, u32 opcode, u32 operand, int map, + int wave, int glue, int sync) +{ + u32 reg; + int stop = 1; + + reg = sync; + reg |= (glue << 4); + reg |= (++wave << 11); + reg |= (++map << 15); + reg |= (operand << 20) & 0xFFF00000; + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2); + + reg = (operand >> 12); + reg |= opcode << 4; + reg |= (stop << 9); + __raw_writel(reg, ipu_dc_tmpl_reg + word * 2 + 1); +} + +static void _ipu_dc_link_event(int chan, int event, int addr, int priority) +{ + u32 reg; + + reg = __raw_readl(DC_RL_CH(chan, event)); + reg &= ~(0xFFFF << (16 * (event & 0x1))); + reg |= ((addr << 8) | priority) << (16 * (event & 0x1)); + __raw_writel(reg, DC_RL_CH(chan, event)); +} + +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ +static const int rgb2ycbcr_coeff[5][3] = { + {153, 301, 58}, + {-87, -170, 0x0100}, + {0x100, -215, -42}, + {0x0000, 0x0200, 0x0200}, /* B0, B1, B2 */ + {0x2, 0x2, 0x2}, /* S0, S1, S2 */ +}; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ +static const int ycbcr2rgb_coeff[5][3] = { + {0x095, 0x000, 0x0CC}, + {0x095, 0x3CE, 0x398}, + {0x095, 0x0FF, 0x000}, + {0x3E42, 0x010A, 0x3DD6}, /*B0,B1,B2 */ + {0x1, 0x1, 0x1}, /*S0,S1,S2 */ +}; + +#define mask_a(a) ((u32)(a) & 0x3FF) +#define mask_b(b) ((u32)(b) & 0x3FFF) + +/* Pls keep S0, S1 and S2 as 0x2 by using this convertion */ +static int _rgb_to_yuv(int n, int red, int green, int blue) +{ + int c; + c = red * rgb2ycbcr_coeff[n][0]; + c += green * rgb2ycbcr_coeff[n][1]; + c += blue * rgb2ycbcr_coeff[n][2]; + c /= 16; + c += rgb2ycbcr_coeff[3][n] * 4; + c += 8; + c /= 16; + if (c < 0) + c = 0; + if (c > 255) + c = 255; + return c; +} + +/* + * Row is for BG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + * Column is for FG: RGB2YUV YUV2RGB RGB2RGB YUV2YUV CSC_NONE + */ +static struct dp_csc_param_t dp_csc_array[CSC_NUM][CSC_NUM] = { +{{DP_COM_CONF_CSC_DEF_BOTH, &rgb2ycbcr_coeff}, {0, 0}, {0, 0}, {DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff}, {DP_COM_CONF_CSC_DEF_BG, &rgb2ycbcr_coeff} }, +{{0, 0}, {DP_COM_CONF_CSC_DEF_BOTH, &ycbcr2rgb_coeff}, {DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff}, {0, 0}, {DP_COM_CONF_CSC_DEF_BG, &ycbcr2rgb_coeff} }, +{{0, 0}, {DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff}, {0, 0}, {0, 0}, {0, 0} }, +{{DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff}, {0, 0}, {0, 0}, {0, 0}, {0, 0} }, +{{DP_COM_CONF_CSC_DEF_FG, &rgb2ycbcr_coeff}, {DP_COM_CONF_CSC_DEF_FG, &ycbcr2rgb_coeff}, {0, 0}, {0, 0}, {0, 0} } +}; + +static enum csc_type_t fg_csc_type = CSC_NONE, bg_csc_type = CSC_NONE; +static int color_key_4rgb = 1; + +void __ipu_dp_csc_setup(int dp, struct dp_csc_param_t dp_csc_param, + bool srm_mode_update) +{ + u32 reg; + const int (*coeff)[5][3]; + + if (dp_csc_param.mode >= 0) { + reg = __raw_readl(DP_COM_CONF(dp)); + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + reg |= dp_csc_param.mode; + __raw_writel(reg, DP_COM_CONF(dp)); + } + + coeff = dp_csc_param.coeff; + + if (coeff) { + __raw_writel(mask_a((*coeff)[0][0]) | + (mask_a((*coeff)[0][1]) << 16), DP_CSC_A_0(dp)); + __raw_writel(mask_a((*coeff)[0][2]) | + (mask_a((*coeff)[1][0]) << 16), DP_CSC_A_1(dp)); + __raw_writel(mask_a((*coeff)[1][1]) | + (mask_a((*coeff)[1][2]) << 16), DP_CSC_A_2(dp)); + __raw_writel(mask_a((*coeff)[2][0]) | + (mask_a((*coeff)[2][1]) << 16), DP_CSC_A_3(dp)); + __raw_writel(mask_a((*coeff)[2][2]) | + (mask_b((*coeff)[3][0]) << 16) | + ((*coeff)[4][0] << 30), DP_CSC_0(dp)); + __raw_writel(mask_b((*coeff)[3][1]) | ((*coeff)[4][1] << 14) | + (mask_b((*coeff)[3][2]) << 16) | + ((*coeff)[4][2] << 30), DP_CSC_1(dp)); + } + + if (srm_mode_update) { + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + } +} + +int _ipu_dp_init(ipu_channel_t channel, uint32_t in_pixel_fmt, + uint32_t out_pixel_fmt) +{ + int in_fmt, out_fmt; + int dp; + int partial = false; + uint32_t reg; + + if (channel == MEM_FG_SYNC) { + dp = DP_SYNC; + partial = true; + } else if (channel == MEM_BG_SYNC) { + dp = DP_SYNC; + partial = false; + } else if (channel == MEM_BG_ASYNC0) { + dp = DP_ASYNC0; + partial = false; + } else { + return -EINVAL; + } + + in_fmt = format_to_colorspace(in_pixel_fmt); + out_fmt = format_to_colorspace(out_pixel_fmt); + + if (partial) { + if (in_fmt == RGB) { + if (out_fmt == RGB) + fg_csc_type = RGB2RGB; + else + fg_csc_type = RGB2YUV; + } else { + if (out_fmt == RGB) + fg_csc_type = YUV2RGB; + else + fg_csc_type = YUV2YUV; + } + } else { + if (in_fmt == RGB) { + if (out_fmt == RGB) + bg_csc_type = RGB2RGB; + else + bg_csc_type = RGB2YUV; + } else { + if (out_fmt == RGB) + bg_csc_type = YUV2RGB; + else + bg_csc_type = YUV2YUV; + } + } + + /* Transform color key from rgb to yuv if CSC is enabled */ + reg = __raw_readl(DP_COM_CONF(dp)); + if (color_key_4rgb && (reg & DP_COM_CONF_GWCKE) && + (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB)))) { + int red, green, blue; + int y, u, v; + uint32_t color_key = __raw_readl(DP_GRAPH_WIND_CTRL(dp)) & 0xFFFFFFL; + + dev_dbg(g_ipu_dev, "_ipu_dp_init color key 0x%x need change to yuv fmt!\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + reg = __raw_readl(DP_GRAPH_WIND_CTRL(dp)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(dp)); + color_key_4rgb = 0; + + dev_dbg(g_ipu_dev, "_ipu_dp_init color key change to yuv fmt 0x%x!\n", color_key); + } + + __ipu_dp_csc_setup(dp, dp_csc_array[bg_csc_type][fg_csc_type], true); + + return 0; +} + +void _ipu_dp_uninit(ipu_channel_t channel) +{ + int dp; + int partial = false; + + if (channel == MEM_FG_SYNC) { + dp = DP_SYNC; + partial = true; + } else if (channel == MEM_BG_SYNC) { + dp = DP_SYNC; + partial = false; + } else if (channel == MEM_BG_ASYNC0) { + dp = DP_ASYNC0; + partial = false; + } else { + return; + } + + if (partial) + fg_csc_type = CSC_NONE; + else + bg_csc_type = CSC_NONE; + + __ipu_dp_csc_setup(dp, dp_csc_array[bg_csc_type][fg_csc_type], false); +} + +void _ipu_dc_init(int dc_chan, int di, bool interlaced) +{ + u32 reg = 0; + + if ((dc_chan == 1) || (dc_chan == 5)) { + if (interlaced) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 0, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 0, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 0, 1); + } else { + if (di) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 2, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 3, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 4, 1); + } else { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 5, 3); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 6, 2); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 7, 1); + } + } + _ipu_dc_link_event(dc_chan, DC_EVT_NF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR, 0, 0); + + reg = 0x2; + reg |= DC_DISP_ID_SYNC(di) << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + reg |= di << 2; + if (interlaced) + reg |= DC_WR_CH_CONF_FIELD_MODE; + } else if ((dc_chan == 8) || (dc_chan == 9)) { + /* async channels */ + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_0, 0x64, 1); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_1, 0x64, 1); + + reg = 0x3; + reg |= DC_DISP_ID_SERIAL << DC_WR_CH_CONF_PROG_DISP_ID_OFFSET; + } + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + __raw_writel(0x00000000, DC_WR_CH_ADDR(dc_chan)); + + __raw_writel(0x00000084, DC_GEN); +} + +void _ipu_dc_uninit(int dc_chan) +{ + if ((dc_chan == 1) || (dc_chan == 5)) { + _ipu_dc_link_event(dc_chan, DC_EVT_NL, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOL, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOF, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_EOFIELD, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR, 0, 0); + } else if ((dc_chan == 8) || (dc_chan == 9)) { + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_W_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_ADDR_R_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_CHAN_R_1, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_R_0, 0, 0); + _ipu_dc_link_event(dc_chan, DC_EVT_NEW_DATA_R_1, 0, 0); + } +} + +int _ipu_chan_is_interlaced(ipu_channel_t channel) +{ + if (channel == MEM_DC_SYNC) + return !!(__raw_readl(DC_WR_CH_CONF_1) & + DC_WR_CH_CONF_FIELD_MODE); + else if ((channel == MEM_BG_SYNC) || (channel == MEM_FG_SYNC)) + return !!(__raw_readl(DC_WR_CH_CONF_5) & + DC_WR_CH_CONF_FIELD_MODE); + return 0; +} + +void _ipu_dp_dc_enable(ipu_channel_t channel) +{ + int di; + uint32_t reg; + uint32_t dc_chan; + int irq = 0; + + if (channel == MEM_FG_SYNC) + irq = IPU_IRQ_DP_SF_END; + else if (channel == MEM_DC_SYNC) + dc_chan = 1; + else if (channel == MEM_BG_SYNC) + dc_chan = 5; + else + return; + + if (channel == MEM_FG_SYNC) { + /* Enable FG channel */ + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + __raw_writel(reg | DP_COM_CONF_FG_EN, DP_COM_CONF(DP_SYNC)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + return; + } + + di = g_dc_di_assignment[dc_chan]; + + /* Make sure other DC sync channel is not assigned same DI */ + reg = __raw_readl(DC_WR_CH_CONF(6 - dc_chan)); + if ((di << 2) == (reg & DC_WR_CH_CONF_PROG_DI_ID)) { + reg &= ~DC_WR_CH_CONF_PROG_DI_ID; + reg |= di ? 0 : DC_WR_CH_CONF_PROG_DI_ID; + __raw_writel(reg, DC_WR_CH_CONF(6 - dc_chan)); + } + + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg |= 4 << DC_WR_CH_CONF_PROG_TYPE_OFFSET; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + reg = __raw_readl(IPU_DISP_GEN); + if (di) + reg |= DI1_COUNTER_RELEASE; + else + reg |= DI0_COUNTER_RELEASE; + __raw_writel(reg, IPU_DISP_GEN); +} + +static bool dc_swap; + +static irqreturn_t dc_irq_handler(int irq, void *dev_id) +{ + struct completion *comp = dev_id; + + complete(comp); + return IRQ_HANDLED; +} + +void _ipu_dp_dc_disable(ipu_channel_t channel, bool swap) +{ + int ret; + unsigned long lock_flags; + uint32_t reg; + uint32_t csc; + uint32_t dc_chan; + int irq = 0; + int timeout = 50; + DECLARE_COMPLETION_ONSTACK(dc_comp); + + dc_swap = swap; + + if (channel == MEM_DC_SYNC) { + dc_chan = 1; + irq = IPU_IRQ_DC_FC_1; + } else if (channel == MEM_BG_SYNC) { + dc_chan = 5; + irq = IPU_IRQ_DP_SF_END; + } else if (channel == MEM_FG_SYNC) { + /* Disable FG channel */ + dc_chan = 5; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + reg = __raw_readl(DP_COM_CONF(DP_SYNC)); + csc = reg & DP_COM_CONF_CSC_DEF_MASK; + if (csc == DP_COM_CONF_CSC_DEF_FG) + reg &= ~DP_COM_CONF_CSC_DEF_MASK; + + reg &= ~DP_COM_CONF_FG_EN; + __raw_writel(reg, DP_COM_CONF(DP_SYNC)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_DP_SF_END), + IPUIRQ_2_STATREG(IPU_IRQ_DP_SF_END)); + while ((__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_DP_SF_END)) & + IPUIRQ_2_MASK(IPU_IRQ_DP_SF_END)) == 0) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + + timeout = 50; + + /* + * Wait for DC triple buffer to empty, + * this check is useful for tv overlay. + */ + if (g_dc_di_assignment[dc_chan] == 0) + while ((__raw_readl(DC_STAT) & 0x00000002) + != 0x00000002) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + else if (g_dc_di_assignment[dc_chan] == 1) + while ((__raw_readl(DC_STAT) & 0x00000020) + != 0x00000020) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + return; + } else { + return; + } + + if (!dc_swap) + __raw_writel(IPUIRQ_2_MASK(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan]), + IPUIRQ_2_STATREG(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])); + ipu_clear_irq(irq); + ret = ipu_request_irq(irq, dc_irq_handler, 0, NULL, &dc_comp); + if (ret < 0) { + dev_err(g_ipu_dev, "DC irq %d in use\n", irq); + return; + } + ret = wait_for_completion_timeout(&dc_comp, msecs_to_jiffies(50)); + + dev_dbg(g_ipu_dev, "DC stop timeout - %d * 10ms\n", 5 - ret); + ipu_free_irq(irq, &dc_comp); + + if (dc_swap) { + spin_lock_irqsave(&ipu_lock, lock_flags); + /* Swap DC channel 1 and 5 settings, and disable old dc chan */ + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + __raw_writel(reg, DC_WR_CH_CONF(6 - dc_chan)); + reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + reg ^= DC_WR_CH_CONF_PROG_DI_ID; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + } else { + timeout = 50; + + /* Wait for DC triple buffer to empty */ + if (g_dc_di_assignment[dc_chan] == 0) + while ((__raw_readl(DC_STAT) & 0x00000002) + != 0x00000002) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + else if (g_dc_di_assignment[dc_chan] == 1) + while ((__raw_readl(DC_STAT) & 0x00000020) + != 0x00000020) { + msleep(2); + timeout -= 2; + if (timeout <= 0) + break; + } + + spin_lock_irqsave(&ipu_lock, lock_flags); + reg = __raw_readl(DC_WR_CH_CONF(dc_chan)); + reg &= ~DC_WR_CH_CONF_PROG_TYPE_MASK; + __raw_writel(reg, DC_WR_CH_CONF(dc_chan)); + + reg = __raw_readl(IPU_DISP_GEN); + if (g_dc_di_assignment[dc_chan]) + reg &= ~DI1_COUNTER_RELEASE; + else + reg &= ~DI0_COUNTER_RELEASE; + __raw_writel(reg, IPU_DISP_GEN); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + if (__raw_readl(IPUIRQ_2_STATREG(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])) & + IPUIRQ_2_MASK(IPU_IRQ_VSYNC_PRE_0 + + g_dc_di_assignment[dc_chan])) + dev_dbg(g_ipu_dev, + "VSyncPre occurred before DI%d disable\n", + g_dc_di_assignment[dc_chan]); + } +} + +void _ipu_init_dc_mappings(void) +{ + /* IPU_PIX_FMT_RGB24 */ + _ipu_dc_map_clear(0); + _ipu_dc_map_config(0, 0, 7, 0xFF); + _ipu_dc_map_config(0, 1, 15, 0xFF); + _ipu_dc_map_config(0, 2, 23, 0xFF); + + /* IPU_PIX_FMT_RGB666 */ + _ipu_dc_map_clear(1); + _ipu_dc_map_config(1, 0, 5, 0xFC); + _ipu_dc_map_config(1, 1, 11, 0xFC); + _ipu_dc_map_config(1, 2, 17, 0xFC); + + /* IPU_PIX_FMT_YUV444 */ + _ipu_dc_map_clear(2); + _ipu_dc_map_config(2, 0, 15, 0xFF); + _ipu_dc_map_config(2, 1, 23, 0xFF); + _ipu_dc_map_config(2, 2, 7, 0xFF); + + /* IPU_PIX_FMT_RGB565 */ + _ipu_dc_map_clear(3); + _ipu_dc_map_config(3, 0, 4, 0xF8); + _ipu_dc_map_config(3, 1, 10, 0xFC); + _ipu_dc_map_config(3, 2, 15, 0xF8); + + /* IPU_PIX_FMT_LVDS666 */ + _ipu_dc_map_clear(4); + _ipu_dc_map_config(4, 0, 5, 0xFC); + _ipu_dc_map_config(4, 1, 13, 0xFC); + _ipu_dc_map_config(4, 2, 21, 0xFC); +} + +int _ipu_pixfmt_to_map(uint32_t fmt) +{ + switch (fmt) { + case IPU_PIX_FMT_GENERIC: + case IPU_PIX_FMT_RGB24: + return 0; + case IPU_PIX_FMT_RGB666: + return 1; + case IPU_PIX_FMT_YUV444: + return 2; + case IPU_PIX_FMT_RGB565: + return 3; + case IPU_PIX_FMT_LVDS666: + return 4; + } + + return -1; +} + +/*! + * This function sets the colorspace for of dp. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param param If it's not NULL, update the csc table + * with this parameter. + * + * @return N/A + */ +void _ipu_dp_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]) +{ + int dp; + struct dp_csc_param_t dp_csc_param; + + if (channel == MEM_FG_SYNC) + dp = DP_SYNC; + else if (channel == MEM_BG_SYNC) + dp = DP_SYNC; + else if (channel == MEM_BG_ASYNC0) + dp = DP_ASYNC0; + else + return; + + dp_csc_param.mode = -1; + dp_csc_param.coeff = param; + __ipu_dp_csc_setup(dp, dp_csc_param, true); +} + +/*! + * This function is called to initialize a synchronous LCD panel. + * + * @param disp The DI the panel is attached to. + * + * @param pixel_clk Desired pixel clock frequency in Hz. + * + * @param pixel_fmt Input parameter for pixel format of buffer. + * Pixel format is a FOURCC ASCII code. + * + * @param width The width of panel in pixels. + * + * @param height The height of panel in pixels. + * + * @param hStartWidth The number of pixel clocks between the HSYNC + * signal pulse and the start of valid data. + * + * @param hSyncWidth The width of the HSYNC signal in units of pixel + * clocks. + * + * @param hEndWidth The number of pixel clocks between the end of + * valid data and the HSYNC signal for next line. + * + * @param vStartWidth The number of lines between the VSYNC + * signal pulse and the start of valid data. + * + * @param vSyncWidth The width of the VSYNC signal in units of lines + * + * @param vEndWidth The number of lines between the end of valid + * data and the VSYNC signal for next frame. + * + * @param sig Bitfield of signal polarities for LCD interface. + * + * @return This function returns 0 on success or negative error code on + * fail. + */ +int32_t ipu_init_sync_panel(int disp, uint32_t pixel_clk, + uint16_t width, uint16_t height, + uint32_t pixel_fmt, + uint16_t h_start_width, uint16_t h_sync_width, + uint16_t h_end_width, uint16_t v_start_width, + uint16_t v_sync_width, uint16_t v_end_width, + uint32_t v_to_h_sync, ipu_di_signal_cfg_t sig) +{ + unsigned long lock_flags; + uint32_t field0_offset = 0; + uint32_t field1_offset; + uint32_t reg; + uint32_t disp_gen, di_gen, vsync_cnt; + uint32_t div; + uint32_t h_total, v_total; + int map; + struct clk *di_clk; + + dev_dbg(g_ipu_dev, "panel size = %d x %d\n", width, height); + + if ((v_sync_width == 0) || (h_sync_width == 0)) + return EINVAL; + + h_total = width + h_sync_width + h_start_width + h_end_width; + v_total = height + v_sync_width + v_start_width + v_end_width; + + /* Init clocking */ + dev_dbg(g_ipu_dev, "pixel clk = %d\n", pixel_clk); + if (sig.ext_clk) + di_clk = g_di_clk[disp]; + else + di_clk = g_ipu_clk; + + /* + * Calculate divider + * Fractional part is 4 bits, + * so simply multiply by 2^4 to get fractional part. + */ + div = (clk_get_rate(di_clk) * 16) / pixel_clk; + if (div < 0x10) /* Min DI disp clock divider is 1 */ + div = 0x10; + /* + * DI disp clock offset is zero, + * and fractional part is rounded off to 0.5. + */ + div &= 0xFF8; + + reg = __raw_readl(DI_GENERAL(disp)); + if (sig.ext_clk) + __raw_writel(reg | DI_GEN_DI_CLK_EXT, DI_GENERAL(disp)); + else + __raw_writel(reg & ~DI_GEN_DI_CLK_EXT, DI_GENERAL(disp)); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + disp_gen = __raw_readl(IPU_DISP_GEN); + disp_gen &= disp ? ~DI1_COUNTER_RELEASE : ~DI0_COUNTER_RELEASE; + __raw_writel(disp_gen, IPU_DISP_GEN); + + __raw_writel(div, DI_BS_CLKGEN0(disp)); + + /* Setup pixel clock timing */ + /* FIXME: needs to be more flexible */ + /* Down time is half of period */ + __raw_writel((div / 16) << 16, DI_BS_CLKGEN1(disp)); + + _ipu_di_data_wave_config(disp, SYNC_WAVE, div / 16 - 1, div / 16 - 1); + _ipu_di_data_pin_config(disp, SYNC_WAVE, DI_PIN15, 3, 0, div / 16 * 2); + + div = div / 16; /* Now divider is integer portion */ + + map = _ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) { + dev_dbg(g_ipu_dev, "IPU_DISP: No MAP\n"); + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return -EINVAL; + } + + di_gen = 0; + if (sig.ext_clk) + di_gen |= DI_GEN_DI_CLK_EXT; + + if (sig.interlaced) { + if (cpu_is_mx51_rev(CHIP_REV_2_0)) { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 1, /* counter */ + h_total/2 - 1, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Field 1 VSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 2, /* counter */ + h_total - 1, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 4 /* COUNT DOWN */ + ); + + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 3, /* counter */ + v_total*2 - 1, /* run count */ + DI_SYNC_INT_HSYNC, /* run_resolution */ + 1, /* offset */ + DI_SYNC_INT_HSYNC, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 4 /* COUNT DOWN */ + ); + + /* Active Field ? */ + _ipu_di_sync_config( + disp, /* display */ + 4, /* counter */ + v_total/2 - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + v_start_width, /* offset */ + DI_SYNC_HSYNC, /* offset resolution */ + 2, /* repeat count */ + DI_SYNC_VSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Active Line */ + _ipu_di_sync_config( + disp, /* display */ + 5, /* counter */ + 0, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + height/2, /* repeat count */ + 4, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* Field 0 VSYNC waveform */ + _ipu_di_sync_config( + disp, /* display */ + 6, /* counter */ + v_total - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 0, /* offset */ + DI_SYNC_NONE, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_NONE, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* DC VSYNC waveform */ + vsync_cnt = 7; + _ipu_di_sync_config( + disp, /* display */ + 7, /* counter */ + v_total/2 - 1, /* run count */ + DI_SYNC_HSYNC, /* run_resolution */ + 9, /* offset */ + DI_SYNC_HSYNC, /* offset resolution */ + 2, /* repeat count */ + DI_SYNC_VSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* active pixel waveform */ + _ipu_di_sync_config( + disp, /* display */ + 8, /* counter */ + 0, /* run count */ + DI_SYNC_CLK, /* run_resolution */ + h_start_width, /* offset */ + DI_SYNC_CLK, /* offset resolution */ + width, /* repeat count */ + 5, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 0 /* COUNT DOWN */ + ); + + /* ??? */ + _ipu_di_sync_config( + disp, /* display */ + 9, /* counter */ + v_total - 1, /* run count */ + DI_SYNC_INT_HSYNC, /* run_resolution */ + v_total/2, /* offset */ + DI_SYNC_INT_HSYNC, /* offset resolution */ + 0, /* repeat count */ + DI_SYNC_HSYNC, /* CNT_CLR_SEL */ + 0, /* CNT_POLARITY_GEN_EN */ + DI_SYNC_NONE, /* CNT_POLARITY_CLR_SEL */ + DI_SYNC_NONE, /* CNT_POLARITY_TRIGGER_SEL */ + 0, /* COUNT UP */ + 4 /* COUNT DOWN */ + ); + + /* set gentime select and tag sel */ + reg = __raw_readl(DI_SW_GEN1(disp, 9)); + reg &= 0x1FFFFFFF; + reg |= (3-1)<<29 | 0x00008000; + __raw_writel(reg, DI_SW_GEN1(disp, 9)); + + __raw_writel(v_total / 2 - 1, DI_SCR_CONF(disp)); + + /* set y_sel = 1 */ + di_gen |= 0x10000000; + di_gen |= DI_GEN_POLARITY_5; + di_gen |= DI_GEN_POLARITY_8; + } else { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 1, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + field1_offset = v_sync_width + v_start_width + height / 2 + + v_end_width; + if (sig.odd_field_first) { + field0_offset = field1_offset - 1; + field1_offset = 0; + } + v_total += v_start_width + v_end_width; + + /* Field 1 VSYNC waveform */ + _ipu_di_sync_config(disp, 2, v_total - 1, 1, + field0_offset, + field0_offset ? 1 : DI_SYNC_NONE, + 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, DI_SYNC_NONE, 0, 4); + + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 3, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, DI_SYNC_NONE, 0, 4); + + /* Active Field ? */ + _ipu_di_sync_config(disp, 4, + field0_offset ? + field0_offset : field1_offset - 2, + 1, v_start_width + v_sync_width, 1, 2, 2, + 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, 0); + + /* Active Line */ + _ipu_di_sync_config(disp, 5, 0, 1, + 0, DI_SYNC_NONE, + height / 2, 4, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* Field 0 VSYNC waveform */ + _ipu_di_sync_config(disp, 6, v_total - 1, 1, + 0, DI_SYNC_NONE, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* DC VSYNC waveform */ + vsync_cnt = 7; + _ipu_di_sync_config(disp, 7, 0, 1, + field1_offset, + field1_offset ? 1 : DI_SYNC_NONE, + 1, 2, 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, 0); + + /* active pixel waveform */ + _ipu_di_sync_config(disp, 8, 0, DI_SYNC_CLK, + h_sync_width + h_start_width, DI_SYNC_CLK, + width, 5, 0, DI_SYNC_NONE, DI_SYNC_NONE, + 0, 0); + + /* ??? */ + _ipu_di_sync_config(disp, 9, v_total - 1, 2, + 0, DI_SYNC_NONE, + 0, DI_SYNC_NONE, 6, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + reg = __raw_readl(DI_SW_GEN1(disp, 9)); + reg |= 0x8000; + __raw_writel(reg, DI_SW_GEN1(disp, 9)); + + __raw_writel(v_sync_width + v_start_width + + v_end_width + height / 2 - 1, DI_SCR_CONF(disp)); + } + + /* Init template microcode */ + _ipu_dc_write_tmpl(0, WROD(0), 0, map, SYNC_WAVE, 0, 8); + + if (sig.Hsync_pol) + di_gen |= DI_GEN_POLARITY_3; + if (sig.Vsync_pol) + di_gen |= DI_GEN_POLARITY_2; + } else { + /* Setup internal HSYNC waveform */ + _ipu_di_sync_config(disp, 1, h_total - 1, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + + /* Setup external (delayed) HSYNC waveform */ + _ipu_di_sync_config(disp, DI_SYNC_HSYNC, h_total - 1, + DI_SYNC_CLK, div * v_to_h_sync, DI_SYNC_CLK, + 0, DI_SYNC_NONE, 1, DI_SYNC_NONE, + DI_SYNC_CLK, 0, h_sync_width * 2); + /* Setup VSYNC waveform */ + vsync_cnt = DI_SYNC_VSYNC; + _ipu_di_sync_config(disp, DI_SYNC_VSYNC, v_total - 1, + DI_SYNC_INT_HSYNC, 0, DI_SYNC_NONE, 0, + DI_SYNC_NONE, 1, DI_SYNC_NONE, + DI_SYNC_INT_HSYNC, 0, v_sync_width * 2); + __raw_writel(v_total - 1, DI_SCR_CONF(disp)); + + /* Setup active data waveform to sync with DC */ + _ipu_di_sync_config(disp, 4, 0, DI_SYNC_HSYNC, + v_sync_width + v_start_width, DI_SYNC_HSYNC, height, + DI_SYNC_VSYNC, 0, DI_SYNC_NONE, + DI_SYNC_NONE, 0, 0); + _ipu_di_sync_config(disp, 5, 0, DI_SYNC_CLK, + h_sync_width + h_start_width, DI_SYNC_CLK, + width, 4, 0, DI_SYNC_NONE, DI_SYNC_NONE, 0, + 0); + + /* reset all unused counters */ + __raw_writel(0, DI_SW_GEN0(disp, 6)); + __raw_writel(0, DI_SW_GEN1(disp, 6)); + __raw_writel(0, DI_SW_GEN0(disp, 7)); + __raw_writel(0, DI_SW_GEN1(disp, 7)); + __raw_writel(0, DI_SW_GEN0(disp, 8)); + __raw_writel(0, DI_SW_GEN1(disp, 8)); + __raw_writel(0, DI_SW_GEN0(disp, 9)); + __raw_writel(0, DI_SW_GEN1(disp, 9)); + + reg = __raw_readl(DI_STP_REP(disp, 6)); + reg &= 0x0000FFFF; + __raw_writel(reg, DI_STP_REP(disp, 6)); + __raw_writel(0, DI_STP_REP(disp, 7)); + __raw_writel(0, DI_STP_REP(disp, 9)); + + /* Init template microcode */ + if (disp) { + _ipu_dc_write_tmpl(2, WROD(0), 0, map, SYNC_WAVE, 8, 5); + _ipu_dc_write_tmpl(3, WROD(0), 0, map, SYNC_WAVE, 4, 5); + _ipu_dc_write_tmpl(4, WROD(0), 0, map, SYNC_WAVE, 0, 5); + } else { + _ipu_dc_write_tmpl(5, WROD(0), 0, map, SYNC_WAVE, 8, 5); + _ipu_dc_write_tmpl(6, WROD(0), 0, map, SYNC_WAVE, 4, 5); + _ipu_dc_write_tmpl(7, WROD(0), 0, map, SYNC_WAVE, 0, 5); + } + + if (sig.Hsync_pol) + di_gen |= DI_GEN_POLARITY_2; + if (sig.Vsync_pol) + di_gen |= DI_GEN_POLARITY_3; + } + + __raw_writel(di_gen, DI_GENERAL(disp)); + __raw_writel((--vsync_cnt << DI_VSYNC_SEL_OFFSET) | 0x00000002, + DI_SYNC_AS_GEN(disp)); + + reg = __raw_readl(DI_POL(disp)); + reg &= ~(DI_POL_DRDY_DATA_POLARITY | DI_POL_DRDY_POLARITY_15); + if (sig.enable_pol) + reg |= DI_POL_DRDY_POLARITY_15; + if (sig.data_pol) + reg |= DI_POL_DRDY_DATA_POLARITY; + __raw_writel(reg, DI_POL(disp)); + + __raw_writel(width, DC_DISP_CONF2(DC_DISP_ID_SYNC(disp))); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_init_sync_panel); + + +int ipu_init_async_panel(int disp, int type, uint32_t cycle_time, + uint32_t pixel_fmt, ipu_adc_sig_cfg_t sig) +{ + unsigned long lock_flags; + int map; + u32 ser_conf = 0; + u32 div; + u32 di_clk = clk_get_rate(g_ipu_clk); + + /* round up cycle_time, then calcalate the divider using scaled math */ + cycle_time += (1000000000UL / di_clk) - 1; + div = (cycle_time * (di_clk / 256UL)) / (1000000000UL / 256UL); + + map = _ipu_pixfmt_to_map(pixel_fmt); + if (map < 0) + return -EINVAL; + + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (type == IPU_PANEL_SERIAL) { + __raw_writel((div << 24) | ((sig.ifc_width - 1) << 4), + DI_DW_GEN(disp, ASYNC_SER_WAVE)); + + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_CS, + 0, 0, (div * 2) + 1); + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_SER_CLK, + 1, div, div * 2); + _ipu_di_data_pin_config(disp, ASYNC_SER_WAVE, DI_PIN_SER_RS, + 2, 0, 0); + + _ipu_dc_write_tmpl(0x64, WROD(0), 0, map, ASYNC_SER_WAVE, 0, 0); + + /* Configure DC for serial panel */ + __raw_writel(0x14, DC_DISP_CONF1(DC_DISP_ID_SERIAL)); + + if (sig.clk_pol) + ser_conf |= DI_SER_CONF_SERIAL_CLK_POL; + if (sig.data_pol) + ser_conf |= DI_SER_CONF_SERIAL_DATA_POL; + if (sig.rs_pol) + ser_conf |= DI_SER_CONF_SERIAL_RS_POL; + if (sig.cs_pol) + ser_conf |= DI_SER_CONF_SERIAL_CS_POL; + __raw_writel(ser_conf, DI_SER_CONF(disp)); + } + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + return 0; +} +EXPORT_SYMBOL(ipu_init_async_panel); + +/*! + * This function sets the foreground and background plane global alpha blending + * modes. This function also sets the DP graphic plane according to the + * parameter of IPUv3 DP channel. + * + * @param channel IPUv3 DP channel + * + * @param enable Boolean to enable or disable global alpha + * blending. If disabled, local blending is used. + * + * @param alpha Global alpha value. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_global_alpha(ipu_channel_t channel, bool enable, + uint8_t alpha) +{ + uint32_t reg; + uint32_t flow; + unsigned long lock_flags; + bool bg_chan; + + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_BG_ASYNC0 || channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_BG_ASYNC1 || channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (channel == MEM_BG_SYNC || channel == MEM_BG_ASYNC0 || + channel == MEM_BG_ASYNC1) + bg_chan = true; + else + bg_chan = false; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + spin_lock_irqsave(&ipu_lock, lock_flags); + + if (bg_chan) { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWSEL, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWSEL, DP_COM_CONF(flow)); + } + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(flow)) & 0x00FFFFFFL; + __raw_writel(reg | ((uint32_t) alpha << 24), + DP_GRAPH_WIND_CTRL(flow)); + + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWAM, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWAM, DP_COM_CONF(flow)); + } + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_global_alpha); + +/*! + * This function sets the transparent color key for SDC graphic plane. + * + * @param channel Input parameter for the logical channel ID. + * + * @param enable Boolean to enable or disable color key + * + * @param colorKey 24-bit RGB color for transparent color key. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_color_key(ipu_channel_t channel, bool enable, + uint32_t color_key) +{ + uint32_t reg, flow; + int y, u, v; + int red, green, blue; + unsigned long lock_flags; + + if (channel == MEM_BG_SYNC || channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_BG_ASYNC0 || channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_BG_ASYNC1 || channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + color_key_4rgb = 1; + /* Transform color key from rgb to yuv if CSC is enabled */ + if (((fg_csc_type == RGB2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == RGB2YUV)) || + ((fg_csc_type == YUV2YUV) && (bg_csc_type == YUV2YUV)) || + ((fg_csc_type == YUV2RGB) && (bg_csc_type == YUV2RGB))) { + + dev_dbg(g_ipu_dev, "color key 0x%x need change to yuv fmt\n", color_key); + + red = (color_key >> 16) & 0xFF; + green = (color_key >> 8) & 0xFF; + blue = color_key & 0xFF; + + y = _rgb_to_yuv(0, red, green, blue); + u = _rgb_to_yuv(1, red, green, blue); + v = _rgb_to_yuv(2, red, green, blue); + color_key = (y << 16) | (u << 8) | v; + + color_key_4rgb = 0; + + dev_dbg(g_ipu_dev, "color key change to yuv fmt 0x%x\n", color_key); + } + + if (enable) { + reg = __raw_readl(DP_GRAPH_WIND_CTRL(flow)) & 0xFF000000L; + __raw_writel(reg | color_key, DP_GRAPH_WIND_CTRL(flow)); + + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg | DP_COM_CONF_GWCKE, DP_COM_CONF(flow)); + } else { + reg = __raw_readl(DP_COM_CONF(flow)); + __raw_writel(reg & ~DP_COM_CONF_GWCKE, DP_COM_CONF(flow)); + } + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_color_key); + +/*! + * This function sets the window position of the foreground or background plane. + * modes. + * + * @param channel Input parameter for the logical channel ID. + * + * @param x_pos The X coordinate position to place window at. + * The position is relative to the top left corner. + * + * @param y_pos The Y coordinate position to place window at. + * The position is relative to the top left corner. + * + * @return Returns 0 on success or negative error code on fail + */ +int32_t ipu_disp_set_window_pos(ipu_channel_t channel, int16_t x_pos, + int16_t y_pos) +{ + u32 reg; + unsigned long lock_flags; + uint32_t flow = 0; + + if (channel == MEM_FG_SYNC) + flow = DP_SYNC; + else if (channel == MEM_FG_ASYNC0) + flow = DP_ASYNC0; + else if (channel == MEM_FG_ASYNC1) + flow = DP_ASYNC1; + else + return -EINVAL; + + if (!g_ipu_clk_enabled) + clk_enable(g_ipu_clk); + + spin_lock_irqsave(&ipu_lock, lock_flags); + + __raw_writel((x_pos << 16) | y_pos, DP_FG_POS(flow)); + + reg = __raw_readl(IPU_SRM_PRI2) | 0x8; + __raw_writel(reg, IPU_SRM_PRI2); + + spin_unlock_irqrestore(&ipu_lock, lock_flags); + if (!g_ipu_clk_enabled) + clk_disable(g_ipu_clk); + + return 0; +} +EXPORT_SYMBOL(ipu_disp_set_window_pos); + +void ipu_disp_direct_write(ipu_channel_t channel, u32 value, u32 offset) +{ + if (channel == DIRECT_ASYNC0) + __raw_writel(value, ipu_disp_base[0] + offset); + else if (channel == DIRECT_ASYNC1) + __raw_writel(value, ipu_disp_base[1] + offset); +} +EXPORT_SYMBOL(ipu_disp_direct_write); + +void ipu_reset_disp_panel(void) +{ + uint32_t tmp; + + tmp = __raw_readl(DI_GENERAL(1)); + __raw_writel(tmp | 0x08, DI_GENERAL(1)); + msleep(10); /* tRES >= 100us */ + tmp = __raw_readl(DI_GENERAL(1)); + __raw_writel(tmp & ~0x08, DI_GENERAL(1)); + msleep(60); + + return; +} +EXPORT_SYMBOL(ipu_reset_disp_panel); diff --git a/drivers/mxc/ipu3/ipu_ic.c b/drivers/mxc/ipu3/ipu_ic.c new file mode 100644 index 000000000000..45894265a630 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_ic.c @@ -0,0 +1,822 @@ +/* + * Copyright 2005-2009 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 ipu_ic.c + * + * @brief IPU IC functions + * + * @ingroup IPU + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/videodev2.h> +#include <linux/io.h> +#include <linux/ipu.h> + +#include "ipu_prv.h" +#include "ipu_regs.h" +#include "ipu_param_mem.h" + +enum { + IC_TASK_VIEWFINDER, + IC_TASK_ENCODER, + IC_TASK_POST_PROCESSOR +}; + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format, int csc_index); +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff); + +void _ipu_vdi_set_top_field_man(bool top_field_0) +{ + uint32_t reg; + + reg = __raw_readl(VDI_C); + if (top_field_0) + reg &= ~VDI_C_TOP_FIELD_MAN_1; + else + reg |= VDI_C_TOP_FIELD_MAN_1; + __raw_writel(reg, VDI_C); +} + +void _ipu_vdi_set_motion(ipu_motion_sel motion_sel) +{ + uint32_t reg; + + reg = __raw_readl(VDI_C); + reg &= ~(VDI_C_MOT_SEL_FULL | VDI_C_MOT_SEL_MED | VDI_C_MOT_SEL_LOW); + if (motion_sel == HIGH_MOTION) + reg |= VDI_C_MOT_SEL_FULL; + else if (motion_sel == MED_MOTION) + reg |= VDI_C_MOT_SEL_MED; + else + reg |= VDI_C_MOT_SEL_LOW; + + __raw_writel(reg, VDI_C); +} + +void ic_dump_register(void) +{ + printk(KERN_DEBUG "IC_CONF = \t0x%08X\n", __raw_readl(IC_CONF)); + printk(KERN_DEBUG "IC_PRP_ENC_RSC = \t0x%08X\n", + __raw_readl(IC_PRP_ENC_RSC)); + printk(KERN_DEBUG "IC_PRP_VF_RSC = \t0x%08X\n", + __raw_readl(IC_PRP_VF_RSC)); + printk(KERN_DEBUG "IC_PP_RSC = \t0x%08X\n", __raw_readl(IC_PP_RSC)); + printk(KERN_DEBUG "IC_IDMAC_1 = \t0x%08X\n", __raw_readl(IC_IDMAC_1)); + printk(KERN_DEBUG "IC_IDMAC_2 = \t0x%08X\n", __raw_readl(IC_IDMAC_2)); + printk(KERN_DEBUG "IC_IDMAC_3 = \t0x%08X\n", __raw_readl(IC_IDMAC_3)); +} + +void _ipu_ic_enable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_VDI_PRP_VF_MEM: + ic_conf |= IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf |= IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf |= IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_MEM: + ic_conf |= IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf |= IC_CONF_PP_ROT_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_disable_task(ipu_channel_t channel) +{ + uint32_t ic_conf; + + ic_conf = __raw_readl(IC_CONF); + switch (channel) { + case CSI_PRP_VF_MEM: + case MEM_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_VDI_PRP_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_EN; + break; + case MEM_ROT_VF_MEM: + ic_conf &= ~IC_CONF_PRPVF_ROT_EN; + break; + case CSI_PRP_ENC_MEM: + case MEM_PRP_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_EN; + break; + case MEM_ROT_ENC_MEM: + ic_conf &= ~IC_CONF_PRPENC_ROT_EN; + break; + case MEM_PP_MEM: + ic_conf &= ~IC_CONF_PP_EN; + break; + case MEM_ROT_PP_MEM: + ic_conf &= ~IC_CONF_PP_ROT_EN; + break; + default: + break; + } + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_vdi_init(ipu_channel_t channel, ipu_channel_params_t *params) +{ + uint32_t reg; + uint32_t pixel_fmt; + bool top_field_0; + + reg = ((params->mem_prp_vf_mem.in_height-1) << 16) | + (params->mem_prp_vf_mem.in_width-1); + __raw_writel(reg, VDI_FSIZE); + + /* Full motion, only vertical filter is used + Burst size is 4 accesses */ + pixel_fmt = + (params->mem_prp_vf_mem.in_pixel_fmt == + V4L2_PIX_FMT_YUV422P) ? VDI_C_CH_422 : VDI_C_CH_420; + + reg = __raw_readl(VDI_C); + reg |= pixel_fmt; + switch (channel) { + case MEM_VDI_PRP_VF_MEM: + reg |= VDI_C_BURST_SIZE2_4; + break; + case MEM_VDI_PRP_VF_MEM_P: + reg |= VDI_C_BURST_SIZE1_4 | VDI_C_VWM1_SET_1 | VDI_C_VWM1_CLR_2; + break; + case MEM_VDI_PRP_VF_MEM_N: + reg |= VDI_C_BURST_SIZE3_4 | VDI_C_VWM3_SET_1 | VDI_C_VWM3_CLR_2; + break; + default: + break; + } + __raw_writel(reg, VDI_C); + + /* MED_MOTION and LOW_MOTION algorithm that are using 3 fields + * should start presenting using the 2nd field. + */ + if (((params->mem_prp_vf_mem.field_fmt == V4L2_FIELD_INTERLACED_TB) && + (params->mem_prp_vf_mem.motion_sel != HIGH_MOTION)) || + ((params->mem_prp_vf_mem.field_fmt == V4L2_FIELD_INTERLACED_BT) && + (params->mem_prp_vf_mem.motion_sel == HIGH_MOTION))) + top_field_0 = false; + else + top_field_0 = true; + + /* Buffer selection toggle the value therefore init val is inverted. */ + _ipu_vdi_set_top_field_man(!top_field_0); + + _ipu_vdi_set_motion(params->mem_prp_vf_mem.motion_sel); + + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_RWS_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_vdi_uninit(void) +{ + __raw_writel(0, VDI_FSIZE); + __raw_writel(0, VDI_C); +} + +void _ipu_ic_init_prpvf(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_height, + params->mem_prp_vf_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_vf_mem.in_width, + params->mem_prp_vf_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_VF_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PRPVF_CMB; + + if (!(ic_conf & IC_CONF_PRPVF_CSC1)) { + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_VIEWFINDER, RGB, RGB, 1); + ic_conf |= IC_CONF_PRPVF_CSC1; /* Enable RGB->RGB CSC */ + } + in_fmt = format_to_colorspace(params->mem_prp_vf_mem.in_g_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_vf_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC2 */ + _init_csc(IC_TASK_VIEWFINDER, RGB, out_fmt, 2); + ic_conf |= IC_CONF_PRPVF_CSC2; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC2 */ + _init_csc(IC_TASK_VIEWFINDER, YCbCr, RGB, 2); + ic_conf |= IC_CONF_PRPVF_CSC2; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_prp_vf_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + reg = __raw_readl(IC_CMBP_1); + reg &= ~(0xff); + reg |= params->mem_prp_vf_mem.alpha; + __raw_writel(reg, IC_CMBP_1); + } else + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + + if (params->mem_prp_vf_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + __raw_writel(params->mem_prp_vf_mem.key_color, + IC_CMBP_2); + } else + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_PRPVF_CMB; + } + + if (src_is_csi) + ic_conf &= ~IC_CONF_RWS_EN; + else + ic_conf |= IC_CONF_RWS_EN; + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpvf(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPVF_EN | IC_CONF_PRPVF_CMB | + IC_CONF_PRPVF_CSC2 | IC_CONF_PRPVF_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_vf(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_vf(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PRPVF_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_prpenc(ipu_channel_params_t *params, bool src_is_csi) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_height, + params->mem_prp_enc_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_prp_enc_mem.in_width, + params->mem_prp_enc_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PRP_ENC_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_prp_enc_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_prp_enc_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_ENCODER, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PRPENC_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_ENCODER, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PRPENC_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (src_is_csi) + ic_conf &= ~IC_CONF_RWS_EN; + else + ic_conf |= IC_CONF_RWS_EN; + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_prpenc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_enc(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_enc(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PRPENC_ROT_EN); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_pp(ipu_channel_params_t *params) +{ + uint32_t reg, ic_conf; + uint32_t downsizeCoeff, resizeCoeff; + ipu_color_space_t in_fmt, out_fmt; + + /* Setup vertical resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_height, + params->mem_pp_mem.out_height, + &resizeCoeff, &downsizeCoeff); + reg = (downsizeCoeff << 30) | (resizeCoeff << 16); + + /* Setup horizontal resizing */ + _calc_resize_coeffs(params->mem_pp_mem.in_width, + params->mem_pp_mem.out_width, + &resizeCoeff, &downsizeCoeff); + reg |= (downsizeCoeff << 14) | resizeCoeff; + + __raw_writel(reg, IC_PP_RSC); + + ic_conf = __raw_readl(IC_CONF); + + /* Setup color space conversion */ + in_fmt = format_to_colorspace(params->mem_pp_mem.in_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC1 */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt, 1); + ic_conf |= IC_CONF_PP_CSC1; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC1 */ + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB, 1); + ic_conf |= IC_CONF_PP_CSC1; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.graphics_combine_en) { + ic_conf |= IC_CONF_PP_CMB; + + if (!(ic_conf & IC_CONF_PP_CSC1)) { + /* need transparent CSC1 conversion */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, RGB, 1); + ic_conf |= IC_CONF_PP_CSC1; /* Enable RGB->RGB CSC */ + } + + in_fmt = format_to_colorspace(params->mem_pp_mem.in_g_pixel_fmt); + out_fmt = format_to_colorspace(params->mem_pp_mem.out_pixel_fmt); + if (in_fmt == RGB) { + if ((out_fmt == YCbCr) || (out_fmt == YUV)) { + /* Enable RGB->YCBCR CSC2 */ + _init_csc(IC_TASK_POST_PROCESSOR, RGB, out_fmt, 2); + ic_conf |= IC_CONF_PP_CSC2; + } + } + if ((in_fmt == YCbCr) || (in_fmt == YUV)) { + if (out_fmt == RGB) { + /* Enable YCBCR->RGB CSC2 */ + _init_csc(IC_TASK_POST_PROCESSOR, YCbCr, RGB, 2); + ic_conf |= IC_CONF_PP_CSC2; + } else { + /* TODO: Support YUV<->YCbCr conversion? */ + } + } + + if (params->mem_pp_mem.global_alpha_en) { + ic_conf |= IC_CONF_IC_GLB_LOC_A; + reg = __raw_readl(IC_CMBP_1); + reg &= ~(0xff00); + reg |= (params->mem_pp_mem.alpha << 8); + __raw_writel(reg, IC_CMBP_1); + } else + ic_conf &= ~IC_CONF_IC_GLB_LOC_A; + + if (params->mem_pp_mem.key_color_en) { + ic_conf |= IC_CONF_KEY_COLOR_EN; + __raw_writel(params->mem_pp_mem.key_color, + IC_CMBP_2); + } else + ic_conf &= ~IC_CONF_KEY_COLOR_EN; + } else { + ic_conf &= ~IC_CONF_PP_CMB; + } + + __raw_writel(ic_conf, IC_CONF); +} + +void _ipu_ic_uninit_pp(void) +{ + uint32_t reg; + + reg = __raw_readl(IC_CONF); + reg &= ~(IC_CONF_PP_EN | IC_CONF_PP_CSC1 | IC_CONF_PP_CSC2 | + IC_CONF_PP_CMB); + __raw_writel(reg, IC_CONF); +} + +void _ipu_ic_init_rotate_pp(ipu_channel_params_t *params) +{ +} + +void _ipu_ic_uninit_rotate_pp(void) +{ + uint32_t reg; + reg = __raw_readl(IC_CONF); + reg &= ~IC_CONF_PP_ROT_EN; + __raw_writel(reg, IC_CONF); +} + +int _ipu_ic_idma_init(int dma_chan, uint16_t width, uint16_t height, + int burst_size, ipu_rotate_mode_t rot) +{ + u32 ic_idmac_1, ic_idmac_2, ic_idmac_3; + u32 temp_rot = bitrev8(rot) >> 5; + bool need_hor_flip = false; + + if ((burst_size != 8) && (burst_size != 16)) { + dev_dbg(g_ipu_dev, "Illegal burst length for IC\n"); + return -EINVAL; + } + + width--; + height--; + + if (temp_rot & 0x2) /* Need horizontal flip */ + need_hor_flip = true; + + ic_idmac_1 = __raw_readl(IC_IDMAC_1); + ic_idmac_2 = __raw_readl(IC_IDMAC_2); + ic_idmac_3 = __raw_readl(IC_IDMAC_3); + if (dma_chan == 22) { /* PP output - CB2 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB2_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB2_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PP_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PP_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PP_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PP_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PP_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PP_WIDTH_OFFSET; + + } else if (dma_chan == 11) { /* PP Input - CB5 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB5_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB5_BURST_16; + } else if (dma_chan == 47) { /* PP Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PP_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PP_ROT_OFFSET; + } + + if (dma_chan == 12) { /* PRP Input - CB6 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB6_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB6_BURST_16; + } + + if (dma_chan == 20) { /* PRP ENC output - CB0 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB0_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB0_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PRPENC_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PRPENC_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PRPENC_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PRPENC_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PRPENC_WIDTH_OFFSET; + + } else if (dma_chan == 45) { /* PRP ENC Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PRPENC_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPENC_ROT_OFFSET; + } + + if (dma_chan == 21) { /* PRP VF output - CB1 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB1_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB1_BURST_16; + + if (need_hor_flip) + ic_idmac_1 |= IC_IDMAC_1_PRPVF_FLIP_RS; + else + ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_FLIP_RS; + + ic_idmac_2 &= ~IC_IDMAC_2_PRPVF_HEIGHT_MASK; + ic_idmac_2 |= height << IC_IDMAC_2_PRPVF_HEIGHT_OFFSET; + + ic_idmac_3 &= ~IC_IDMAC_3_PRPVF_WIDTH_MASK; + ic_idmac_3 |= width << IC_IDMAC_3_PRPVF_WIDTH_OFFSET; + + } else if (dma_chan == 46) { /* PRP VF Rot input */ + ic_idmac_1 &= ~IC_IDMAC_1_PRPVF_ROT_MASK; + ic_idmac_1 |= temp_rot << IC_IDMAC_1_PRPVF_ROT_OFFSET; + } + + if (dma_chan == 14) { /* PRP VF graphics combining input - CB3 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB3_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB3_BURST_16; + } else if (dma_chan == 15) { /* PP graphics combining input - CB4 */ + if (burst_size == 16) + ic_idmac_1 |= IC_IDMAC_1_CB4_BURST_16; + else + ic_idmac_1 &= ~IC_IDMAC_1_CB4_BURST_16; + } + + __raw_writel(ic_idmac_1, IC_IDMAC_1); + __raw_writel(ic_idmac_2, IC_IDMAC_2); + __raw_writel(ic_idmac_3, IC_IDMAC_3); + + return 0; +} + +static void _init_csc(uint8_t ic_task, ipu_color_space_t in_format, + ipu_color_space_t out_format, int csc_index) +{ + +/* Y = R * .299 + G * .587 + B * .114; + U = R * -.169 + G * -.332 + B * .500 + 128.; + V = R * .500 + G * -.419 + B * -.0813 + 128.;*/ + static const uint32_t rgb2ycbcr_coeff[4][3] = { + {0x004D, 0x0096, 0x001D}, + {0x01D5, 0x01AB, 0x0080}, + {0x0080, 0x0195, 0x01EB}, + {0x0000, 0x0200, 0x0200}, /* A0, A1, A2 */ + }; + + /* transparent RGB->RGB matrix for combining + */ + static const uint32_t rgb2rgb_coeff[4][3] = { + {0x0080, 0x0000, 0x0000}, + {0x0000, 0x0080, 0x0000}, + {0x0000, 0x0000, 0x0080}, + {0x0000, 0x0000, 0x0000}, /* A0, A1, A2 */ + }; + +/* R = (1.164 * (Y - 16)) + (1.596 * (Cr - 128)); + G = (1.164 * (Y - 16)) - (0.392 * (Cb - 128)) - (0.813 * (Cr - 128)); + B = (1.164 * (Y - 16)) + (2.017 * (Cb - 128); */ + static const uint32_t ycbcr2rgb_coeff[4][3] = { + {149, 0, 204}, + {149, 462, 408}, + {149, 255, 0}, + {8192 - 446, 266, 8192 - 554}, /* A0, A1, A2 */ + }; + + uint32_t param; + uint32_t *base = NULL; + + if (ic_task == IC_TASK_ENCODER) { + base = ipu_tpmem_base + 0x2008 / 4; + } else if (ic_task == IC_TASK_VIEWFINDER) { + if (csc_index == 1) + base = ipu_tpmem_base + 0x4028 / 4; + else + base = ipu_tpmem_base + 0x4040 / 4; + } else if (ic_task == IC_TASK_POST_PROCESSOR) { + if (csc_index == 1) + base = ipu_tpmem_base + 0x6060 / 4; + else + base = ipu_tpmem_base + 0x6078 / 4; + } else { + BUG(); + } + + if ((in_format == YCbCr) && (out_format == RGB)) { + /* Init CSC (YCbCr->RGB) */ + param = (ycbcr2rgb_coeff[3][0] << 27) | + (ycbcr2rgb_coeff[0][0] << 18) | + (ycbcr2rgb_coeff[1][1] << 9) | ycbcr2rgb_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 2, sat = 0 */ + param = (ycbcr2rgb_coeff[3][0] >> 5) | (2L << (40 - 32)); + __raw_writel(param, base++); + + param = (ycbcr2rgb_coeff[3][1] << 27) | + (ycbcr2rgb_coeff[0][1] << 18) | + (ycbcr2rgb_coeff[1][0] << 9) | ycbcr2rgb_coeff[2][0]; + __raw_writel(param, base++); + param = (ycbcr2rgb_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = (ycbcr2rgb_coeff[3][2] << 27) | + (ycbcr2rgb_coeff[0][2] << 18) | + (ycbcr2rgb_coeff[1][2] << 9) | ycbcr2rgb_coeff[2][1]; + __raw_writel(param, base++); + param = (ycbcr2rgb_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else if ((in_format == RGB) && (out_format == YCbCr)) { + /* Init CSC (RGB->YCbCr) */ + param = (rgb2ycbcr_coeff[3][0] << 27) | + (rgb2ycbcr_coeff[0][0] << 18) | + (rgb2ycbcr_coeff[1][1] << 9) | rgb2ycbcr_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 1, sat = 0 */ + param = (rgb2ycbcr_coeff[3][0] >> 5) | (1UL << 8); + __raw_writel(param, base++); + + param = (rgb2ycbcr_coeff[3][1] << 27) | + (rgb2ycbcr_coeff[0][1] << 18) | + (rgb2ycbcr_coeff[1][0] << 9) | rgb2ycbcr_coeff[2][0]; + __raw_writel(param, base++); + param = (rgb2ycbcr_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = (rgb2ycbcr_coeff[3][2] << 27) | + (rgb2ycbcr_coeff[0][2] << 18) | + (rgb2ycbcr_coeff[1][2] << 9) | rgb2ycbcr_coeff[2][1]; + __raw_writel(param, base++); + param = (rgb2ycbcr_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else if ((in_format == RGB) && (out_format == RGB)) { + /* Init CSC */ + param = + (rgb2rgb_coeff[3][0] << 27) | (rgb2rgb_coeff[0][0] << 18) | + (rgb2rgb_coeff[1][1] << 9) | rgb2rgb_coeff[2][2]; + __raw_writel(param, base++); + /* scale = 2, sat = 0 */ + param = (rgb2rgb_coeff[3][0] >> 5) | (2UL << 8); + __raw_writel(param, base++); + + param = + (rgb2rgb_coeff[3][1] << 27) | (rgb2rgb_coeff[0][1] << 18) | + (rgb2rgb_coeff[1][0] << 9) | rgb2rgb_coeff[2][0]; + __raw_writel(param, base++); + param = (rgb2rgb_coeff[3][1] >> 5); + __raw_writel(param, base++); + + param = + (rgb2rgb_coeff[3][2] << 27) | (rgb2rgb_coeff[0][2] << 18) | + (rgb2rgb_coeff[1][2] << 9) | rgb2rgb_coeff[2][1]; + __raw_writel(param, base++); + param = (rgb2rgb_coeff[3][2] >> 5); + __raw_writel(param, base++); + } else { + dev_err(g_ipu_dev, "Unsupported color space conversion\n"); + } +} + +static bool _calc_resize_coeffs(uint32_t inSize, uint32_t outSize, + uint32_t *resizeCoeff, + uint32_t *downsizeCoeff) +{ + uint32_t tempSize; + uint32_t tempDownsize; + + /* Input size cannot be more than 4096 */ + /* Output size cannot be more than 1024 */ + if ((inSize > 4096) || (outSize > 1024)) + return false; + + /* Cannot downsize more than 8:1 */ + if ((outSize << 3) < inSize) + return false; + + /* Compute downsizing coefficient */ + /* Output of downsizing unit cannot be more than 1024 */ + tempDownsize = 0; + tempSize = inSize; + while (((tempSize > 1024) || (tempSize >= outSize * 2)) && + (tempDownsize < 2)) { + tempSize >>= 1; + tempDownsize++; + } + *downsizeCoeff = tempDownsize; + + /* compute resizing coefficient using the following equation: + resizeCoeff = M*(SI -1)/(SO - 1) + where M = 2^13, SI - input size, SO - output size */ + *resizeCoeff = (8192L * (tempSize - 1)) / (outSize - 1); + if (*resizeCoeff >= 16384L) { + dev_err(g_ipu_dev, "Warning! Overflow on resize coeff.\n"); + *resizeCoeff = 0x3FFF; + } + + dev_dbg(g_ipu_dev, "resizing from %u -> %u pixels, " + "downsize=%u, resize=%u.%lu (reg=%u)\n", inSize, outSize, + *downsizeCoeff, (*resizeCoeff >= 8192L) ? 1 : 0, + ((*resizeCoeff & 0x1FFF) * 10000L) / 8192L, *resizeCoeff); + + return true; +} + +void _ipu_vdi_toggle_top_field_man() +{ + uint32_t reg; + uint32_t mask_reg; + + reg = __raw_readl(VDI_C); + mask_reg = reg & VDI_C_TOP_FIELD_MAN_1; + if (mask_reg == VDI_C_TOP_FIELD_MAN_1) + reg &= ~VDI_C_TOP_FIELD_MAN_1; + else + reg |= VDI_C_TOP_FIELD_MAN_1; + + __raw_writel(reg, VDI_C); +} + diff --git a/drivers/mxc/ipu3/ipu_param_mem.h b/drivers/mxc/ipu3/ipu_param_mem.h new file mode 100644 index 000000000000..067ace19563c --- /dev/null +++ b/drivers/mxc/ipu3/ipu_param_mem.h @@ -0,0 +1,391 @@ +/* + * Copyright 2005-2009 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 + */ +#ifndef __INCLUDE_IPU_PARAM_MEM_H__ +#define __INCLUDE_IPU_PARAM_MEM_H__ + +#include <linux/types.h> +#include <linux/bitrev.h> + +extern u32 *ipu_cpmem_base; + +struct ipu_ch_param_word { + uint32_t data[5]; + uint32_t res[3]; +}; + +struct ipu_ch_param { + struct ipu_ch_param_word word[2]; +}; + +#define ipu_ch_param_addr(ch) (((struct ipu_ch_param *)ipu_cpmem_base) + (ch)) + +#define _param_word(base, w) \ + (((struct ipu_ch_param *)(base))->word[(w)].data) + +#define ipu_ch_param_set_field(base, w, bit, size, v) { \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + _param_word(base, w)[i] |= (v) << off; \ + if (((bit)+(size)-1)/32 > i) { \ + _param_word(base, w)[i + 1] |= (v) >> (off ? (32 - off) : 0); \ + } \ +} + +#define ipu_ch_param_mod_field(base, w, bit, size, v) { \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + u32 mask = (1UL << size) - 1; \ + u32 temp = _param_word(base, w)[i]; \ + temp &= ~(mask << off); \ + _param_word(base, w)[i] = temp | (v) << off; \ + if (((bit)+(size)-1)/32 > i) { \ + temp = _param_word(base, w)[i + 1]; \ + temp &= ~(mask >> (32 - off)); \ + _param_word(base, w)[i + 1] = \ + temp | ((v) >> (off ? (32 - off) : 0)); \ + } \ +} + +#define ipu_ch_param_read_field(base, w, bit, size) ({ \ + u32 temp2; \ + int i = (bit) / 32; \ + int off = (bit) % 32; \ + u32 mask = (1UL << size) - 1; \ + u32 temp1 = _param_word(base, w)[i]; \ + temp1 = mask & (temp1 >> off); \ + if (((bit)+(size)-1)/32 > i) { \ + temp2 = _param_word(base, w)[i + 1]; \ + temp2 &= mask >> (off ? (32 - off) : 0); \ + temp1 |= temp2 << (off ? (32 - off) : 0); \ + } \ + temp1; \ +}) + +static inline void _ipu_ch_params_set_packing(struct ipu_ch_param *p, + int red_width, int red_offset, + int green_width, int green_offset, + int blue_width, int blue_offset, + int alpha_width, int alpha_offset) +{ + /* Setup red width and offset */ + ipu_ch_param_set_field(p, 1, 116, 3, red_width - 1); + ipu_ch_param_set_field(p, 1, 128, 5, red_offset); + /* Setup green width and offset */ + ipu_ch_param_set_field(p, 1, 119, 3, green_width - 1); + ipu_ch_param_set_field(p, 1, 133, 5, green_offset); + /* Setup blue width and offset */ + ipu_ch_param_set_field(p, 1, 122, 3, blue_width - 1); + ipu_ch_param_set_field(p, 1, 138, 5, blue_offset); + /* Setup alpha width and offset */ + ipu_ch_param_set_field(p, 1, 125, 3, alpha_width - 1); + ipu_ch_param_set_field(p, 1, 143, 5, alpha_offset); +} + +static inline void _ipu_ch_param_dump(int ch) +{ + struct ipu_ch_param *p = ipu_ch_param_addr(ch); + pr_debug("ch %d word 0 - %08X %08X %08X %08X %08X\n", ch, + p->word[0].data[0], p->word[0].data[1], p->word[0].data[2], + p->word[0].data[3], p->word[0].data[4]); + pr_debug("ch %d word 1 - %08X %08X %08X %08X %08X\n", ch, + p->word[1].data[0], p->word[1].data[1], p->word[1].data[2], + p->word[1].data[3], p->word[1].data[4]); + pr_debug("PFS 0x%x, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 85, 4)); + pr_debug("BPP 0x%x, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 107, 3)); + pr_debug("NPB 0x%x\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 78, 7)); + + pr_debug("FW %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 125, 13)); + pr_debug("FH %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 138, 12)); + pr_debug("Stride %d\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 102, 14)); + + pr_debug("Width0 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 116, 3)); + pr_debug("Width1 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 119, 3)); + pr_debug("Width2 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 122, 3)); + pr_debug("Width3 %d+1, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 125, 3)); + pr_debug("Offset0 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 128, 5)); + pr_debug("Offset1 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 133, 5)); + pr_debug("Offset2 %d, ", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 138, 5)); + pr_debug("Offset3 %d\n", + ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 143, 5)); +} + +static inline void _ipu_ch_param_init(int ch, + uint32_t pixel_fmt, uint32_t width, + uint32_t height, uint32_t stride, + uint32_t u, uint32_t v, + uint32_t uv_stride, dma_addr_t addr0, + dma_addr_t addr1) +{ + uint32_t u_offset = 0; + uint32_t v_offset = 0; + struct ipu_ch_param params; + + memset(¶ms, 0, sizeof(params)); + + ipu_ch_param_set_field(¶ms, 0, 125, 13, width - 1); + + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 0, 138, 12, (height / 2) - 1); + ipu_ch_param_set_field(¶ms, 1, 102, 14, (stride * 2) - 1); + } else { + ipu_ch_param_set_field(¶ms, 0, 138, 12, height - 1); + ipu_ch_param_set_field(¶ms, 1, 102, 14, stride - 1); + } + + ipu_ch_param_set_field(¶ms, 1, 0, 29, addr0 >> 3); + ipu_ch_param_set_field(¶ms, 1, 29, 29, addr1 >> 3); + + switch (pixel_fmt) { + case IPU_PIX_FMT_GENERIC: + /*Represents 8-bit Generic data */ + ipu_ch_param_set_field(¶ms, 0, 107, 3, 5); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 6); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 63); /* burst size */ + + break; + case IPU_PIX_FMT_GENERIC_32: + /*Represents 32-bit Generic data */ + break; + case IPU_PIX_FMT_RGB565: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 5, 0, 6, 5, 5, 11, 8, 16); + break; + case IPU_PIX_FMT_BGR24: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 19); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_YUV444: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 1); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 19); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 16, 8, 8, 8, 0, 8, 24); + break; + case IPU_PIX_FMT_BGRA32: + case IPU_PIX_FMT_BGR32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 8, 8, 16, 8, 24, 8, 0); + break; + case IPU_PIX_FMT_RGBA32: + case IPU_PIX_FMT_RGB32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + + _ipu_ch_params_set_packing(¶ms, 8, 24, 8, 16, 8, 8, 8, 0); + break; + case IPU_PIX_FMT_ABGR32: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 0); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 7); /* pix format */ + + _ipu_ch_params_set_packing(¶ms, 8, 0, 8, 8, 8, 16, 8, 24); + break; + case IPU_PIX_FMT_UYVY: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 0xA); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + break; + case IPU_PIX_FMT_YUYV: + ipu_ch_param_set_field(¶ms, 0, 107, 3, 3); /* bits/pixel */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 0x8); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + ipu_ch_param_set_field(¶ms, 1, 85, 4, 2); /* pix format */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = stride * height; + v_offset = u_offset + (uv_stride * height / 2); + if ((ch == 8) || (ch == 9) || (ch == 10)) { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 15); /* burst size */ + uv_stride = uv_stride*2; + } else { + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + } + break; + case IPU_PIX_FMT_YVU422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + v_offset = (v == 0) ? stride * height : v; + u_offset = (u == 0) ? v_offset + v_offset / 2 : u; + break; + case IPU_PIX_FMT_YUV422P: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 1); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + + if (uv_stride < stride / 2) + uv_stride = stride / 2; + + u_offset = (u == 0) ? stride * height : u; + v_offset = (v == 0) ? u_offset + u_offset / 2 : v; + break; + case IPU_PIX_FMT_NV12: + /* BPP & pixel format */ + ipu_ch_param_set_field(¶ms, 1, 85, 4, 4); /* pix format */ + ipu_ch_param_set_field(¶ms, 1, 78, 7, 31); /* burst size */ + uv_stride = stride; + u_offset = (u == 0) ? stride * height : u; + break; + default: + dev_err(g_ipu_dev, "mxc ipu: unimplemented pixel format\n"); + break; + } + /*set burst size to 16*/ + + + if (uv_stride) + ipu_ch_param_set_field(¶ms, 1, 128, 14, uv_stride - 1); + + if (u > u_offset) + u_offset = u; + + if (v > v_offset) + v_offset = v; + + ipu_ch_param_set_field(¶ms, 0, 46, 22, u_offset / 8); + ipu_ch_param_set_field(¶ms, 0, 68, 22, v_offset / 8); + + pr_debug("initializing idma ch %d @ %p\n", ch, ipu_ch_param_addr(ch)); + memcpy(ipu_ch_param_addr(ch), ¶ms, sizeof(params)); +}; + +static inline void _ipu_ch_param_set_burst_size(uint32_t ch, + uint16_t burst_pixels) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 78, 7, + burst_pixels - 1); +}; + +static inline int _ipu_ch_param_get_burst_size(uint32_t ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 78, 7) + 1; +}; + +static inline int _ipu_ch_param_get_bpp(uint32_t ch) +{ + return ipu_ch_param_read_field(ipu_ch_param_addr(ch), 0, 107, 3); +}; + +static inline void _ipu_ch_param_set_buffer(uint32_t ch, int bufNum, + dma_addr_t phyaddr) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 29 * bufNum, 29, + phyaddr / 8); +}; + +static inline void _ipu_ch_param_set_rotation(uint32_t ch, + ipu_rotate_mode_t rot) +{ + u32 temp_rot = bitrev8(rot) >> 5; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 119, 3, temp_rot); +}; + +static inline void _ipu_ch_param_set_block_mode(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 0, 117, 2, 1); +}; + +static inline void _ipu_ch_param_set_alpha_use_separate_channel(uint32_t ch, + bool option) +{ + if (option) { + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 89, 1, 1); + } else { + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 89, 1, 0); + } +}; + +static inline void _ipu_ch_param_set_alpha_condition_read(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 149, 1, 1); +}; + +static inline void _ipu_ch_param_set_alpha_buffer_memory(uint32_t ch) +{ + int alp_mem_idx; + + switch (ch) { + case 14: /* PRP graphic */ + alp_mem_idx = 0; + break; + case 15: /* PP graphic */ + alp_mem_idx = 1; + break; + case 23: /* DP BG SYNC graphic */ + alp_mem_idx = 4; + break; + case 27: /* DP FG SYNC graphic */ + alp_mem_idx = 2; + break; + default: + dev_err(g_ipu_dev, "unsupported correlative channel of local " + "alpha channel\n"); + return; + } + + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 90, 3, alp_mem_idx); +}; + +static inline void _ipu_ch_param_set_interlaced_scan(uint32_t ch) +{ + u32 stride; + ipu_ch_param_set_field(ipu_ch_param_addr(ch), 0, 113, 1, 1); + stride = ipu_ch_param_read_field(ipu_ch_param_addr(ch), 1, 102, 14) + 1; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 58, 20, stride / 8); + stride *= 2; + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 102, 14, stride - 1); +}; + +static inline void _ipu_ch_param_set_high_priority(uint32_t ch) +{ + ipu_ch_param_mod_field(ipu_ch_param_addr(ch), 1, 93, 2, 1); +}; + +static inline void _ipu_ch_params_set_alpha_width(uint32_t ch, int alpha_width) +{ + ipu_ch_param_set_field(ipu_ch_param_addr(ch), 1, 125, 3, alpha_width - 1); +} + +#endif diff --git a/drivers/mxc/ipu3/ipu_prv.h b/drivers/mxc/ipu3/ipu_prv.h new file mode 100644 index 000000000000..c9884068283f --- /dev/null +++ b/drivers/mxc/ipu3/ipu_prv.h @@ -0,0 +1,92 @@ +/* + * Copyright 2005-2009 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 + */ +#ifndef __INCLUDE_IPU_PRV_H__ +#define __INCLUDE_IPU_PRV_H__ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> + +/* Globals */ +extern struct device *g_ipu_dev; +extern spinlock_t ipu_lock; +extern bool g_ipu_clk_enabled; +extern struct clk *g_ipu_clk; +extern struct clk *g_di_clk[2]; +extern struct clk *g_csi_clk[2]; +extern unsigned char g_dc_di_assignment[]; +extern int g_ipu_hw_rev; + +#define IDMA_CHAN_INVALID 0xFF + +struct ipu_channel { + u8 video_in_dma; + u8 alpha_in_dma; + u8 graph_in_dma; + u8 out_dma; +}; + +int register_ipu_device(void); +ipu_color_space_t format_to_colorspace(uint32_t fmt); +bool ipu_pixel_format_has_alpha(uint32_t fmt); + +void ipu_dump_registers(void); + +uint32_t _ipu_channel_status(ipu_channel_t channel); + +void _ipu_init_dc_mappings(void); +int _ipu_dp_init(ipu_channel_t channel, uint32_t in_pixel_fmt, + uint32_t out_pixel_fmt); +void _ipu_dp_uninit(ipu_channel_t channel); +void _ipu_dc_init(int dc_chan, int di, bool interlaced); +void _ipu_dc_uninit(int dc_chan); +void _ipu_dp_dc_enable(ipu_channel_t channel); +void _ipu_dp_dc_disable(ipu_channel_t channel, bool swap); +void _ipu_dmfc_init(void); +void _ipu_dmfc_set_wait4eot(int dma_chan, int width); +int _ipu_chan_is_interlaced(ipu_channel_t channel); + +void _ipu_ic_enable_task(ipu_channel_t channel); +void _ipu_ic_disable_task(ipu_channel_t channel); +void _ipu_ic_init_prpvf(ipu_channel_params_t *params, bool src_is_csi); +void _ipu_vdi_init(ipu_channel_t channel, ipu_channel_params_t *params); +void _ipu_vdi_uninit(void); +void _ipu_ic_uninit_prpvf(void); +void _ipu_ic_init_rotate_vf(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_vf(void); +void _ipu_ic_init_csi(ipu_channel_params_t *params); +void _ipu_ic_uninit_csi(void); +void _ipu_ic_init_prpenc(ipu_channel_params_t *params, bool src_is_csi); +void _ipu_ic_uninit_prpenc(void); +void _ipu_ic_init_rotate_enc(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_enc(void); +void _ipu_ic_init_pp(ipu_channel_params_t *params); +void _ipu_ic_uninit_pp(void); +void _ipu_ic_init_rotate_pp(ipu_channel_params_t *params); +void _ipu_ic_uninit_rotate_pp(void); +int _ipu_ic_idma_init(int dma_chan, uint16_t width, uint16_t height, + int burst_size, ipu_rotate_mode_t rot); +void _ipu_vdi_toggle_top_field_man(void); +int _ipu_csi_init(ipu_channel_t channel, uint32_t csi); +void ipu_csi_set_test_generator(bool active, uint32_t r_value, + uint32_t g_value, uint32_t b_value, + uint32_t pix_clk, uint32_t csi); +void _ipu_csi_ccir_err_detection_enable(uint32_t csi); +void _ipu_csi_ccir_err_detection_disable(uint32_t csi); +void _ipu_smfc_init(ipu_channel_t channel, uint32_t mipi_id, uint32_t csi); +void _ipu_smfc_set_burst_size(ipu_channel_t channel, uint32_t bs); +void _ipu_dp_set_csc_coefficients(ipu_channel_t channel, int32_t param[][3]); + +#endif /* __INCLUDE_IPU_PRV_H__ */ diff --git a/drivers/mxc/ipu3/ipu_regs.h b/drivers/mxc/ipu3/ipu_regs.h new file mode 100644 index 000000000000..f4b266aef794 --- /dev/null +++ b/drivers/mxc/ipu3/ipu_regs.h @@ -0,0 +1,651 @@ +/* + * Copyright 2005-2009 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 ipu_regs.h + * + * @brief IPU Register definitions + * + * @ingroup IPU + */ +#ifndef __IPU_REGS_INCLUDED__ +#define __IPU_REGS_INCLUDED__ + +#define IPU_DISP0_BASE 0x00000000 +#define IPU_MCU_T_DEFAULT 8 +#define IPU_DISP1_BASE (IPU_MCU_T_DEFAULT << 25) +#define IPU_CM_REG_BASE 0x1E000000 +#define IPU_IDMAC_REG_BASE 0x1E008000 +#define IPU_ISP_REG_BASE 0x1E010000 +#define IPU_DP_REG_BASE 0x1E018000 +#define IPU_IC_REG_BASE 0x1E020000 +#define IPU_IRT_REG_BASE 0x1E028000 +#define IPU_CSI0_REG_BASE 0x1E030000 +#define IPU_CSI1_REG_BASE 0x1E038000 +#define IPU_DI0_REG_BASE 0x1E040000 +#define IPU_DI1_REG_BASE 0x1E048000 +#define IPU_SMFC_REG_BASE 0x1E050000 +#define IPU_DC_REG_BASE 0x1E058000 +#define IPU_DMFC_REG_BASE 0x1E060000 +#define IPU_CPMEM_REG_BASE 0x1F000000 +#define IPU_LUT_REG_BASE 0x1F020000 +#define IPU_SRM_REG_BASE 0x1F040000 +#define IPU_TPM_REG_BASE 0x1F060000 +#define IPU_DC_TMPL_REG_BASE 0x1F080000 +#define IPU_ISP_TBPR_REG_BASE 0x1F0C0000 +#define IPU_VDI_REG_BASE 0x1E068000 + + +extern u32 *ipu_cm_reg; +extern u32 *ipu_idmac_reg; +extern u32 *ipu_dp_reg; +extern u32 *ipu_ic_reg; +extern u32 *ipu_dc_reg; +extern u32 *ipu_dc_tmpl_reg; +extern u32 *ipu_dmfc_reg; +extern u32 *ipu_di_reg[]; +extern u32 *ipu_smfc_reg; +extern u32 *ipu_csi_reg[]; +extern u32 *ipu_tpmem_base; +extern u32 *ipu_disp_base[]; +extern u32 *ipu_vdi_reg; + +/* Register addresses */ +/* IPU Common registers */ +#define IPU_CONF (ipu_cm_reg) + +#define IPU_SRM_PRI1 (ipu_cm_reg + 0x00A0/4) +#define IPU_SRM_PRI2 (ipu_cm_reg + 0x00A4/4) +#define IPU_FS_PROC_FLOW1 (ipu_cm_reg + 0x00A8/4) +#define IPU_FS_PROC_FLOW2 (ipu_cm_reg + 0x00AC/4) +#define IPU_FS_PROC_FLOW3 (ipu_cm_reg + 0x00B0/4) +#define IPU_FS_DISP_FLOW1 (ipu_cm_reg + 0x00B4/4) +#define IPU_FS_DISP_FLOW2 (ipu_cm_reg + 0x00B8/4) +#define IPU_SKIP (ipu_cm_reg + 0x00BC/4) +#define IPU_DISP_ALT_CONF (ipu_cm_reg + 0x00C0/4) +#define IPU_DISP_GEN (ipu_cm_reg + 0x00C4/4) +#define IPU_DISP_ALT1 (ipu_cm_reg + 0x00C8/4) +#define IPU_DISP_ALT2 (ipu_cm_reg + 0x00CC/4) +#define IPU_DISP_ALT3 (ipu_cm_reg + 0x00D0/4) +#define IPU_DISP_ALT4 (ipu_cm_reg + 0x00D4/4) +#define IPU_SNOOP (ipu_cm_reg + 0x00D8/4) +#define IPU_MEM_RST (ipu_cm_reg + 0x00DC/4) +#define IPU_PM (ipu_cm_reg + 0x00E0/4) +#define IPU_GPR (ipu_cm_reg + 0x00E4/4) +#define IPU_CHA_DB_MODE_SEL(ch) (ipu_cm_reg + 0x0150/4 + (ch / 32)) +#define IPU_ALT_CHA_DB_MODE_SEL(ch) (ipu_cm_reg + 0x0168/4 + (ch / 32)) +#define IPU_CHA_CUR_BUF(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x023C/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0124/4 + (ch / 32)); }) +#define IPU_ALT_CUR_BUF0 ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0244/4) : \ + (ipu_cm_reg + 0x012C/4); }) +#define IPU_ALT_CUR_BUF1 ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0248/4) : \ + (ipu_cm_reg + 0x0130/4); }) +#define IPU_SRM_STAT ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x024C/4) : \ + (ipu_cm_reg + 0x0134/4); }) +#define IPU_PROC_TASK_STAT ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0250/4) : \ + (ipu_cm_reg + 0x0138/4); }) +#define IPU_DISP_TASK_STAT ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0254/4) : \ + (ipu_cm_reg + 0x013C/4); }) +#define IPU_CHA_BUF0_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0268/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0140/4 + (ch / 32)); }) +#define IPU_CHA_BUF1_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0270/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0148/4 + (ch / 32)); }) +#define IPU_ALT_CHA_BUF0_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0278/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0158/4 + (ch / 32)); }) +#define IPU_ALT_CHA_BUF1_RDY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0280/4 + (ch / 32)) : \ + (ipu_cm_reg + 0x0160/4 + (ch / 32)); }) + +#define IPU_INT_CTRL(n) (ipu_cm_reg + 0x003C/4 + ((n) - 1)) +#define IPU_INT_CTRL_IRQ(irq) IPU_INT_CTRL(((irq) / 32)) +#define IPU_INT_STAT_IRQ(irq) IPU_INT_STAT(((irq) / 32)) +#define IPU_INT_STAT(n) ({g_ipu_hw_rev == 2 ? \ + (ipu_cm_reg + 0x0200/4 + ((n) - 1)) : \ + (ipu_cm_reg + 0x00E8/4 + ((n) - 1)); }) + +#define IPUIRQ_2_STATREG(irq) (IPU_INT_STAT(1) + ((irq) / 32)) +#define IPUIRQ_2_CTRLREG(irq) (IPU_INT_CTRL(1) + ((irq) / 32)) +#define IPUIRQ_2_MASK(irq) (1UL << ((irq) & 0x1F)) + +#define VDI_FSIZE (ipu_vdi_reg) +#define VDI_C (ipu_vdi_reg + 0x0004/4) + +/* CMOS Sensor Interface Registers */ +#define CSI_SENS_CONF(csi) (ipu_csi_reg[csi]) +#define CSI_SENS_FRM_SIZE(csi) (ipu_csi_reg[csi] + 0x0004/4) +#define CSI_ACT_FRM_SIZE(csi) (ipu_csi_reg[csi] + 0x0008/4) +#define CSI_OUT_FRM_CTRL(csi) (ipu_csi_reg[csi] + 0x000C/4) +#define CSI_TST_CTRL(csi) (ipu_csi_reg[csi] + 0x0010/4) +#define CSI_CCIR_CODE_1(csi) (ipu_csi_reg[csi] + 0x0014/4) +#define CSI_CCIR_CODE_2(csi) (ipu_csi_reg[csi] + 0x0018/4) +#define CSI_CCIR_CODE_3(csi) (ipu_csi_reg[csi] + 0x001C/4) +#define CSI_MIPI_DI(csi) (ipu_csi_reg[csi] + 0x0020/4) +#define CSI_SKIP(csi) (ipu_csi_reg[csi] + 0x0024/4) +#define CSI_CPD_CTRL(csi) (ipu_csi_reg[csi] + 0x0028/4) +#define CSI_CPD_RC(csi, n) (ipu_csi_reg[csi] + 0x002C/4 + n) +#define CSI_CPD_RS(csi, n) (ipu_csi_reg[csi] + 0x004C/4 + n) +#define CSI_CPD_GRC(csi, n) (ipu_csi_reg[csi] + 0x005C/4 + n) +#define CSI_CPD_GRS(csi, n) (ipu_csi_reg[csi] + 0x007C/4 + n) +#define CSI_CPD_GBC(csi, n) (ipu_csi_reg[csi] + 0x008C/4 + n) +#define CSI_CPD_GBS(csi, n) (ipu_csi_reg[csi] + 0x00AC/4 + n) +#define CSI_CPD_BC(csi, n) (ipu_csi_reg[csi] + 0x00BC/4 + n) +#define CSI_CPD_BS(csi, n) (ipu_csi_reg[csi] + 0x00DC/4 + n) +#define CSI_CPD_OFFSET1(csi) (ipu_csi_reg[csi] + 0x00EC/4) +#define CSI_CPD_OFFSET2(csi) (ipu_csi_reg[csi] + 0x00F0/4) + +/*SMFC Registers */ +#define SMFC_MAP (ipu_smfc_reg) +#define SMFC_WMC (ipu_smfc_reg + 0x0004/4) +#define SMFC_BS (ipu_smfc_reg + 0x0008/4) + +/* Image Converter Registers */ +#define IC_CONF (ipu_ic_reg) +#define IC_PRP_ENC_RSC (ipu_ic_reg + 0x0004/4) +#define IC_PRP_VF_RSC (ipu_ic_reg + 0x0008/4) +#define IC_PP_RSC (ipu_ic_reg + 0x000C/4) +#define IC_CMBP_1 (ipu_ic_reg + 0x0010/4) +#define IC_CMBP_2 (ipu_ic_reg + 0x0014/4) +#define IC_IDMAC_1 (ipu_ic_reg + 0x0018/4) +#define IC_IDMAC_2 (ipu_ic_reg + 0x001C/4) +#define IC_IDMAC_3 (ipu_ic_reg + 0x0020/4) +#define IC_IDMAC_4 (ipu_ic_reg + 0x0024/4) + +#define IDMAC_CONF (ipu_idmac_reg + 0x0000) +#define IDMAC_CHA_EN(ch) (ipu_idmac_reg + 0x0004/4 + (ch/32)) +#define IDMAC_SEP_ALPHA (ipu_idmac_reg + 0x000C/4) +#define IDMAC_ALT_SEP_ALPHA (ipu_idmac_reg + 0x0010/4) +#define IDMAC_CHA_PRI(ch) (ipu_idmac_reg + 0x0014/4 + (ch/32)) +#define IDMAC_WM_EN(ch) (ipu_idmac_reg + 0x001C/4 + (ch/32)) +#define IDMAC_CH_LOCK_EN_1 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0024/4) : 0; }) +#define IDMAC_CH_LOCK_EN_2 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0028/4) : \ + (ipu_idmac_reg + 0x0024/4); }) +#define IDMAC_SUB_ADDR_0 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x002C/4) : \ + (ipu_idmac_reg + 0x0028/4); }) +#define IDMAC_SUB_ADDR_1 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0030/4) : \ + (ipu_idmac_reg + 0x002C/4); }) +#define IDMAC_SUB_ADDR_2 ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0034/4) : \ + (ipu_idmac_reg + 0x0030/4); }) +#define IDMAC_BAND_EN(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0040/4 + (ch/32)) : \ + (ipu_idmac_reg + 0x0034/4 + (ch/32)); }) +#define IDMAC_CHA_BUSY(ch) ({g_ipu_hw_rev == 2 ? \ + (ipu_idmac_reg + 0x0100/4 + (ch/32)) : \ + (ipu_idmac_reg + 0x0040/4 + (ch/32)); }) + +#define DI_GENERAL(di) (ipu_di_reg[di]) +#define DI_BS_CLKGEN0(di) (ipu_di_reg[di] + 0x0004/4) +#define DI_BS_CLKGEN1(di) (ipu_di_reg[di] + 0x0008/4) + +#define DI_SW_GEN0(di, gen) (ipu_di_reg[di] + 0x000C/4 + (gen - 1)) +#define DI_SW_GEN1(di, gen) (ipu_di_reg[di] + 0x0030/4 + (gen - 1)) +#define DI_STP_REP(di, gen) (ipu_di_reg[di] + 0x0148/4 + (gen - 1)/2) +#define DI_SYNC_AS_GEN(di) (ipu_di_reg[di] + 0x0054/4) +#define DI_DW_GEN(di, gen) (ipu_di_reg[di] + 0x0058/4 + gen) +#define DI_DW_SET(di, gen, set) (ipu_di_reg[di] + 0x0088/4 + gen + 0xC*set) +#define DI_SER_CONF(di) (ipu_di_reg[di] + 0x015C/4) +#define DI_SSC(di) (ipu_di_reg[di] + 0x0160/4) +#define DI_POL(di) (ipu_di_reg[di] + 0x0164/4) +#define DI_AW0(di) (ipu_di_reg[di] + 0x0168/4) +#define DI_AW1(di) (ipu_di_reg[di] + 0x016C/4) +#define DI_SCR_CONF(di) (ipu_di_reg[di] + 0x0170/4) +#define DI_STAT(di) (ipu_di_reg[di] + 0x0174/4) + +#define DMFC_RD_CHAN (ipu_dmfc_reg) +#define DMFC_WR_CHAN (ipu_dmfc_reg + 0x0004/4) +#define DMFC_WR_CHAN_DEF (ipu_dmfc_reg + 0x0008/4) +#define DMFC_DP_CHAN (ipu_dmfc_reg + 0x000C/4) +#define DMFC_DP_CHAN_DEF (ipu_dmfc_reg + 0x0010/4) +#define DMFC_GENERAL1 (ipu_dmfc_reg + 0x0014/4) +#define DMFC_GENERAL2 (ipu_dmfc_reg + 0x0018/4) +#define DMFC_IC_CTRL (ipu_dmfc_reg + 0x001C/4) +#define DMFC_STAT (ipu_dmfc_reg + 0x0020/4) + +#define DC_MAP_CONF_PTR(n) (ipu_dc_reg + 0x0108/4 + n/2) +#define DC_MAP_CONF_VAL(n) (ipu_dc_reg + 0x0144/4 + n/2) + +#define _RL_CH_2_OFFSET(ch) ((ch == 0) ? 8 : ( \ + (ch == 1) ? 0x24 : ( \ + (ch == 2) ? 0x40 : ( \ + (ch == 5) ? 0x64 : ( \ + (ch == 6) ? 0x80 : ( \ + (ch == 8) ? 0x9C : ( \ + (ch == 9) ? 0xBC : (-1)))))))) +#define DC_RL_CH(ch, evt) (ipu_dc_reg + _RL_CH_2_OFFSET(ch)/4 + evt/2) + +#define DC_EVT_NF 0 +#define DC_EVT_NL 1 +#define DC_EVT_EOF 2 +#define DC_EVT_NFIELD 3 +#define DC_EVT_EOL 4 +#define DC_EVT_EOFIELD 5 +#define DC_EVT_NEW_ADDR 6 +#define DC_EVT_NEW_CHAN 7 +#define DC_EVT_NEW_DATA 8 + +#define DC_EVT_NEW_ADDR_W_0 0 +#define DC_EVT_NEW_ADDR_W_1 1 +#define DC_EVT_NEW_CHAN_W_0 2 +#define DC_EVT_NEW_CHAN_W_1 3 +#define DC_EVT_NEW_DATA_W_0 4 +#define DC_EVT_NEW_DATA_W_1 5 +#define DC_EVT_NEW_ADDR_R_0 6 +#define DC_EVT_NEW_ADDR_R_1 7 +#define DC_EVT_NEW_CHAN_R_0 8 +#define DC_EVT_NEW_CHAN_R_1 9 +#define DC_EVT_NEW_DATA_R_0 10 +#define DC_EVT_NEW_DATA_R_1 11 + +#define dc_ch_offset(ch) \ +({ \ + const u8 _offset[] = { \ + 0, 0x1C, 0x38, 0x54, 0x58, 0x5C, 0x78, 0, 0x94, 0xB4}; \ + _offset[ch]; \ +}) +#define DC_WR_CH_CONF(ch) (ipu_dc_reg + dc_ch_offset(ch)/4) +#define DC_WR_CH_ADDR(ch) (ipu_dc_reg + dc_ch_offset(ch)/4 + 4/4) + +#define DC_WR_CH_CONF_1 (ipu_dc_reg + 0x001C/4) +#define DC_WR_CH_ADDR_1 (ipu_dc_reg + 0x0020/4) +#define DC_WR_CH_CONF_5 (ipu_dc_reg + 0x005C/4) +#define DC_WR_CH_ADDR_5 (ipu_dc_reg + 0x0060/4) +#define DC_GEN (ipu_dc_reg + 0x00D4/4) +#define DC_DISP_CONF1(disp) (ipu_dc_reg + 0x00D8/4 + disp) +#define DC_DISP_CONF2(disp) (ipu_dc_reg + 0x00E8/4 + disp) +#define DC_STAT (ipu_dc_reg + 0x01C8/4) +#define DC_UGDE_0(evt) (ipu_dc_reg + 0x0174/4 + evt*4) +#define DC_UGDE_1(evt) (ipu_dc_reg + 0x0178/4 + evt*4) +#define DC_UGDE_2(evt) (ipu_dc_reg + 0x017C/4 + evt*4) +#define DC_UGDE_3(evt) (ipu_dc_reg + 0x0180/4 + evt*4) + +#define DP_SYNC 0 +#define DP_ASYNC0 0x60 +#define DP_ASYNC1 0xBC +#define DP_COM_CONF(flow) (ipu_dp_reg + flow/4) +#define DP_GRAPH_WIND_CTRL(flow) (ipu_dp_reg + 0x0004/4 + flow/4) +#define DP_FG_POS(flow) (ipu_dp_reg + 0x0008/4 + flow/4) +#define DP_CSC_A_0(flow) (ipu_dp_reg + 0x0044/4 + flow/4) +#define DP_CSC_A_1(flow) (ipu_dp_reg + 0x0048/4 + flow/4) +#define DP_CSC_A_2(flow) (ipu_dp_reg + 0x004C/4 + flow/4) +#define DP_CSC_A_3(flow) (ipu_dp_reg + 0x0050/4 + flow/4) +#define DP_CSC_0(flow) (ipu_dp_reg + 0x0054/4 + flow/4) +#define DP_CSC_1(flow) (ipu_dp_reg + 0x0058/4 + flow/4) + +enum { + IPU_CONF_CSI0_EN = 0x00000001, + IPU_CONF_CSI1_EN = 0x00000002, + IPU_CONF_IC_EN = 0x00000004, + IPU_CONF_ROT_EN = 0x00000008, + IPU_CONF_ISP_EN = 0x00000010, + IPU_CONF_DP_EN = 0x00000020, + IPU_CONF_DI0_EN = 0x00000040, + IPU_CONF_DI1_EN = 0x00000080, + IPU_CONF_DMFC_EN = 0x00000400, + IPU_CONF_SMFC_EN = 0x00000100, + IPU_CONF_DC_EN = 0x00000200, + IPU_CONF_VDI_EN = 0x00001000, + IPU_CONF_IDMAC_DIS = 0x00400000, + IPU_CONF_IC_DMFC_SEL = 0x02000000, + IPU_CONF_IC_DMFC_SYNC = 0x04000000, + IPU_CONF_VDI_DMFC_SYNC = 0x08000000, + IPU_CONF_CSI0_DATA_SOURCE = 0x10000000, + IPU_CONF_CSI0_DATA_SOURCE_OFFSET = 28, + IPU_CONF_CSI1_DATA_SOURCE = 0x20000000, + IPU_CONF_IC_INPUT = 0x40000000, + IPU_CONF_CSI_SEL = 0x80000000, + + DI0_COUNTER_RELEASE = 0x01000000, + DI1_COUNTER_RELEASE = 0x02000000, + + FS_PRPVF_ROT_SRC_SEL_MASK = 0x00000F00, + FS_PRPVF_ROT_SRC_SEL_OFFSET = 8, + FS_PRPENC_ROT_SRC_SEL_MASK = 0x0000000F, + FS_PRPENC_ROT_SRC_SEL_OFFSET = 0, + FS_PP_ROT_SRC_SEL_MASK = 0x000F0000, + FS_PP_ROT_SRC_SEL_OFFSET = 16, + FS_PP_SRC_SEL_MASK = 0x0000F000, + FS_PP_SRC_SEL_OFFSET = 12, + FS_PRP_SRC_SEL_MASK = 0x0F000000, + FS_PRP_SRC_SEL_OFFSET = 24, + FS_VF_IN_VALID = 0x80000000, + FS_ENC_IN_VALID = 0x40000000, + FS_VDI_SRC_SEL_MASK = 0x30000000, + FS_VDI_SRC_SEL_OFFSET = 28, + + + FS_PRPENC_DEST_SEL_MASK = 0x0000000F, + FS_PRPENC_DEST_SEL_OFFSET = 0, + FS_PRPVF_DEST_SEL_MASK = 0x000000F0, + FS_PRPVF_DEST_SEL_OFFSET = 4, + FS_PRPVF_ROT_DEST_SEL_MASK = 0x00000F00, + FS_PRPVF_ROT_DEST_SEL_OFFSET = 8, + FS_PP_DEST_SEL_MASK = 0x0000F000, + FS_PP_DEST_SEL_OFFSET = 12, + FS_PP_ROT_DEST_SEL_MASK = 0x000F0000, + FS_PP_ROT_DEST_SEL_OFFSET = 16, + FS_PRPENC_ROT_DEST_SEL_MASK = 0x00F00000, + FS_PRPENC_ROT_DEST_SEL_OFFSET = 20, + + FS_SMFC0_DEST_SEL_MASK = 0x0000000F, + FS_SMFC0_DEST_SEL_OFFSET = 0, + FS_SMFC1_DEST_SEL_MASK = 0x00000070, + FS_SMFC1_DEST_SEL_OFFSET = 4, + FS_SMFC2_DEST_SEL_MASK = 0x00000780, + FS_SMFC2_DEST_SEL_OFFSET = 7, + FS_SMFC3_DEST_SEL_MASK = 0x00003800, + FS_SMFC3_DEST_SEL_OFFSET = 11, + + FS_DC1_SRC_SEL_MASK = 0x00F00000, + FS_DC1_SRC_SEL_OFFSET = 20, + FS_DC2_SRC_SEL_MASK = 0x000F0000, + FS_DC2_SRC_SEL_OFFSET = 16, + FS_DP_SYNC0_SRC_SEL_MASK = 0x0000000F, + FS_DP_SYNC0_SRC_SEL_OFFSET = 0, + FS_DP_SYNC1_SRC_SEL_MASK = 0x000000F0, + FS_DP_SYNC1_SRC_SEL_OFFSET = 4, + FS_DP_ASYNC0_SRC_SEL_MASK = 0x00000F00, + FS_DP_ASYNC0_SRC_SEL_OFFSET = 8, + FS_DP_ASYNC1_SRC_SEL_MASK = 0x0000F000, + FS_DP_ASYNC1_SRC_SEL_OFFSET = 12, + + FS_AUTO_REF_PER_MASK = 0, + FS_AUTO_REF_PER_OFFSET = 16, + + TSTAT_VF_MASK = 0x0000000C, + TSTAT_VF_OFFSET = 2, + TSTAT_VF_ROT_MASK = 0x00000300, + TSTAT_VF_ROT_OFFSET = 8, + TSTAT_ENC_MASK = 0x00000003, + TSTAT_ENC_OFFSET = 0, + TSTAT_ENC_ROT_MASK = 0x000000C0, + TSTAT_ENC_ROT_OFFSET = 6, + TSTAT_PP_MASK = 0x00000030, + TSTAT_PP_OFFSET = 4, + TSTAT_PP_ROT_MASK = 0x00000C00, + TSTAT_PP_ROT_OFFSET = 10, + + TASK_STAT_IDLE = 0, + TASK_STAT_ACTIVE = 1, + TASK_STAT_WAIT4READY = 2, + + /* Image Converter Register bits */ + IC_CONF_PRPENC_EN = 0x00000001, + IC_CONF_PRPENC_CSC1 = 0x00000002, + IC_CONF_PRPENC_ROT_EN = 0x00000004, + IC_CONF_PRPVF_EN = 0x00000100, + IC_CONF_PRPVF_CSC1 = 0x00000200, + IC_CONF_PRPVF_CSC2 = 0x00000400, + IC_CONF_PRPVF_CMB = 0x00000800, + IC_CONF_PRPVF_ROT_EN = 0x00001000, + IC_CONF_PP_EN = 0x00010000, + IC_CONF_PP_CSC1 = 0x00020000, + IC_CONF_PP_CSC2 = 0x00040000, + IC_CONF_PP_CMB = 0x00080000, + IC_CONF_PP_ROT_EN = 0x00100000, + IC_CONF_IC_GLB_LOC_A = 0x10000000, + IC_CONF_KEY_COLOR_EN = 0x20000000, + IC_CONF_RWS_EN = 0x40000000, + IC_CONF_CSI_MEM_WR_EN = 0x80000000, + + IC_IDMAC_1_CB0_BURST_16 = 0x00000001, + IC_IDMAC_1_CB1_BURST_16 = 0x00000002, + IC_IDMAC_1_CB2_BURST_16 = 0x00000004, + IC_IDMAC_1_CB3_BURST_16 = 0x00000008, + IC_IDMAC_1_CB4_BURST_16 = 0x00000010, + IC_IDMAC_1_CB5_BURST_16 = 0x00000020, + IC_IDMAC_1_CB6_BURST_16 = 0x00000040, + IC_IDMAC_1_CB7_BURST_16 = 0x00000080, + IC_IDMAC_1_PRPENC_ROT_MASK = 0x00003800, + IC_IDMAC_1_PRPENC_ROT_OFFSET = 11, + IC_IDMAC_1_PRPVF_ROT_MASK = 0x0001C000, + IC_IDMAC_1_PRPVF_ROT_OFFSET = 14, + IC_IDMAC_1_PP_ROT_MASK = 0x000E0000, + IC_IDMAC_1_PP_ROT_OFFSET = 17, + IC_IDMAC_1_PP_FLIP_RS = 0x00400000, + IC_IDMAC_1_PRPVF_FLIP_RS = 0x00200000, + IC_IDMAC_1_PRPENC_FLIP_RS = 0x00100000, + + IC_IDMAC_2_PRPENC_HEIGHT_MASK = 0x000003FF, + IC_IDMAC_2_PRPENC_HEIGHT_OFFSET = 0, + IC_IDMAC_2_PRPVF_HEIGHT_MASK = 0x000FFC00, + IC_IDMAC_2_PRPVF_HEIGHT_OFFSET = 10, + IC_IDMAC_2_PP_HEIGHT_MASK = 0x3FF00000, + IC_IDMAC_2_PP_HEIGHT_OFFSET = 20, + + IC_IDMAC_3_PRPENC_WIDTH_MASK = 0x000003FF, + IC_IDMAC_3_PRPENC_WIDTH_OFFSET = 0, + IC_IDMAC_3_PRPVF_WIDTH_MASK = 0x000FFC00, + IC_IDMAC_3_PRPVF_WIDTH_OFFSET = 10, + IC_IDMAC_3_PP_WIDTH_MASK = 0x3FF00000, + IC_IDMAC_3_PP_WIDTH_OFFSET = 20, + + CSI_SENS_CONF_DATA_FMT_SHIFT = 8, + CSI_SENS_CONF_DATA_FMT_MASK = 0x00000700, + CSI_SENS_CONF_DATA_FMT_RGB_YUV444 = 0L, + CSI_SENS_CONF_DATA_FMT_YUV422_YUYV = 1L, + CSI_SENS_CONF_DATA_FMT_YUV422_UYVY = 2L, + CSI_SENS_CONF_DATA_FMT_BAYER = 3L, + CSI_SENS_CONF_DATA_FMT_RGB565 = 4L, + CSI_SENS_CONF_DATA_FMT_RGB555 = 5L, + CSI_SENS_CONF_DATA_FMT_RGB444 = 6L, + CSI_SENS_CONF_DATA_FMT_JPEG = 7L, + + CSI_SENS_CONF_VSYNC_POL_SHIFT = 0, + CSI_SENS_CONF_HSYNC_POL_SHIFT = 1, + CSI_SENS_CONF_DATA_POL_SHIFT = 2, + CSI_SENS_CONF_PIX_CLK_POL_SHIFT = 3, + CSI_SENS_CONF_SENS_PRTCL_SHIFT = 4, + CSI_SENS_CONF_PACK_TIGHT_SHIFT = 7, + CSI_SENS_CONF_DATA_WIDTH_SHIFT = 11, + CSI_SENS_CONF_EXT_VSYNC_SHIFT = 15, + CSI_SENS_CONF_DIVRATIO_SHIFT = 16, + + CSI_SENS_CONF_DIVRATIO_MASK = 0x00FF0000L, + CSI_SENS_CONF_DATA_DEST_SHIFT = 24, + CSI_SENS_CONF_DATA_DEST_MASK = 0x07000000L, + CSI_SENS_CONF_JPEG8_EN_SHIFT = 27, + CSI_SENS_CONF_JPEG_EN_SHIFT = 28, + CSI_SENS_CONF_FORCE_EOF_SHIFT = 29, + CSI_SENS_CONF_DATA_EN_POL_SHIFT = 31, + + CSI_DATA_DEST_ISP = 1L, + CSI_DATA_DEST_IC = 2L, + CSI_DATA_DEST_IDMAC = 4L, + + CSI_CCIR_ERR_DET_EN = 0x01000000L, + CSI_HORI_DOWNSIZE_EN = 0x80000000L, + CSI_VERT_DOWNSIZE_EN = 0x40000000L, + CSI_TEST_GEN_MODE_EN = 0x01000000L, + + CSI_HSC_MASK = 0x1FFF0000, + CSI_HSC_SHIFT = 16, + CSI_VSC_MASK = 0x00000FFF, + CSI_VSC_SHIFT = 0, + + CSI_TEST_GEN_R_MASK = 0x000000FFL, + CSI_TEST_GEN_R_SHIFT = 0, + CSI_TEST_GEN_G_MASK = 0x0000FF00L, + CSI_TEST_GEN_G_SHIFT = 8, + CSI_TEST_GEN_B_MASK = 0x00FF0000L, + CSI_TEST_GEN_B_SHIFT = 16, + + CSI_MIPI_DI0_MASK = 0x000000FFL, + CSI_MIPI_DI0_SHIFT = 0, + CSI_MIPI_DI1_MASK = 0x0000FF00L, + CSI_MIPI_DI1_SHIFT = 8, + CSI_MIPI_DI2_MASK = 0x00FF0000L, + CSI_MIPI_DI2_SHIFT = 16, + CSI_MIPI_DI3_MASK = 0xFF000000L, + CSI_MIPI_DI3_SHIFT = 24, + + CSI_MAX_RATIO_SKIP_ISP_MASK = 0x00070000L, + CSI_MAX_RATIO_SKIP_ISP_SHIFT = 16, + CSI_SKIP_ISP_MASK = 0x00F80000L, + CSI_SKIP_ISP_SHIFT = 19, + CSI_MAX_RATIO_SKIP_SMFC_MASK = 0x00000007L, + CSI_MAX_RATIO_SKIP_SMFC_SHIFT = 0, + CSI_SKIP_SMFC_MASK = 0x000000F8L, + CSI_SKIP_SMFC_SHIFT = 3, + CSI_ID_2_SKIP_MASK = 0x00000300L, + CSI_ID_2_SKIP_SHIFT = 8, + + CSI_COLOR_FIRST_ROW_MASK = 0x00000002L, + CSI_COLOR_FIRST_COMP_MASK = 0x00000001L, + + SMFC_MAP_CH0_MASK = 0x00000007L, + SMFC_MAP_CH0_SHIFT = 0, + SMFC_MAP_CH1_MASK = 0x00000038L, + SMFC_MAP_CH1_SHIFT = 3, + SMFC_MAP_CH2_MASK = 0x000001C0L, + SMFC_MAP_CH2_SHIFT = 6, + SMFC_MAP_CH3_MASK = 0x00000E00L, + SMFC_MAP_CH3_SHIFT = 9, + + SMFC_WM0_SET_MASK = 0x00000007L, + SMFC_WM0_SET_SHIFT = 0, + SMFC_WM1_SET_MASK = 0x000001C0L, + SMFC_WM1_SET_SHIFT = 6, + SMFC_WM2_SET_MASK = 0x00070000L, + SMFC_WM2_SET_SHIFT = 16, + SMFC_WM3_SET_MASK = 0x01C00000L, + SMFC_WM3_SET_SHIFT = 22, + + SMFC_WM0_CLR_MASK = 0x00000038L, + SMFC_WM0_CLR_SHIFT = 3, + SMFC_WM1_CLR_MASK = 0x00000E00L, + SMFC_WM1_CLR_SHIFT = 9, + SMFC_WM2_CLR_MASK = 0x00380000L, + SMFC_WM2_CLR_SHIFT = 19, + SMFC_WM3_CLR_MASK = 0x0E000000L, + SMFC_WM3_CLR_SHIFT = 25, + + SMFC_BS0_MASK = 0x0000000FL, + SMFC_BS0_SHIFT = 0, + SMFC_BS1_MASK = 0x000000F0L, + SMFC_BS1_SHIFT = 4, + SMFC_BS2_MASK = 0x00000F00L, + SMFC_BS2_SHIFT = 8, + SMFC_BS3_MASK = 0x0000F000L, + SMFC_BS3_SHIFT = 12, + + PF_CONF_TYPE_MASK = 0x00000007, + PF_CONF_TYPE_SHIFT = 0, + PF_CONF_PAUSE_EN = 0x00000010, + PF_CONF_RESET = 0x00008000, + PF_CONF_PAUSE_ROW_MASK = 0x00FF0000, + PF_CONF_PAUSE_ROW_SHIFT = 16, + + DI_DW_GEN_ACCESS_SIZE_OFFSET = 24, + DI_DW_GEN_COMPONENT_SIZE_OFFSET = 16, + + DI_GEN_DI_CLK_EXT = 0x100000, + DI_GEN_POLARITY_1 = 0x00000001, + DI_GEN_POLARITY_2 = 0x00000002, + DI_GEN_POLARITY_3 = 0x00000004, + DI_GEN_POLARITY_4 = 0x00000008, + DI_GEN_POLARITY_5 = 0x00000010, + DI_GEN_POLARITY_6 = 0x00000020, + DI_GEN_POLARITY_7 = 0x00000040, + DI_GEN_POLARITY_8 = 0x00000080, + + DI_POL_DRDY_DATA_POLARITY = 0x00000080, + DI_POL_DRDY_POLARITY_15 = 0x00000010, + + DI_VSYNC_SEL_OFFSET = 13, + + DC_WR_CH_CONF_FIELD_MODE = 0x00000200, + DC_WR_CH_CONF_PROG_TYPE_OFFSET = 5, + DC_WR_CH_CONF_PROG_TYPE_MASK = 0x000000E0, + DC_WR_CH_CONF_PROG_DI_ID = 0x00000004, + DC_WR_CH_CONF_PROG_DISP_ID_OFFSET = 3, + DC_WR_CH_CONF_PROG_DISP_ID_MASK = 0x00000018, + + DC_UGDE_0_ODD_EN = 0x02000000, + DC_UGDE_0_ID_CODED_MASK = 0x00000007, + DC_UGDE_0_ID_CODED_OFFSET = 0, + DC_UGDE_0_EV_PRIORITY_MASK = 0x00000078, + DC_UGDE_0_EV_PRIORITY_OFFSET = 3, + + DP_COM_CONF_FG_EN = 0x00000001, + DP_COM_CONF_GWSEL = 0x00000002, + DP_COM_CONF_GWAM = 0x00000004, + DP_COM_CONF_GWCKE = 0x00000008, + DP_COM_CONF_CSC_DEF_MASK = 0x00000300, + DP_COM_CONF_CSC_DEF_OFFSET = 8, + DP_COM_CONF_CSC_DEF_FG = 0x00000300, + DP_COM_CONF_CSC_DEF_BG = 0x00000200, + DP_COM_CONF_CSC_DEF_BOTH = 0x00000100, + + DI_SER_CONF_LLA_SER_ACCESS = 0x00000020, + DI_SER_CONF_SERIAL_CLK_POL = 0x00000010, + DI_SER_CONF_SERIAL_DATA_POL = 0x00000008, + DI_SER_CONF_SERIAL_RS_POL = 0x00000004, + DI_SER_CONF_SERIAL_CS_POL = 0x00000002, + DI_SER_CONF_WAIT4SERIAL = 0x00000001, + + VDI_C_CH_420 = 0x00000000, + VDI_C_CH_422 = 0x00000002, + VDI_C_MOT_SEL_FULL = 0x00000008, + VDI_C_MOT_SEL_LOW = 0x00000004, + VDI_C_MOT_SEL_MED = 0x00000000, + VDI_C_BURST_SIZE1_4 = 0x00000030, + VDI_C_BURST_SIZE2_4 = 0x00000300, + VDI_C_BURST_SIZE3_4 = 0x00003000, + VDI_C_VWM1_SET_1 = 0x00000000, + VDI_C_VWM1_CLR_2 = 0x00080000, + VDI_C_VWM3_SET_1 = 0x00000000, + VDI_C_VWM3_CLR_2 = 0x02000000, + VDI_C_TOP_FIELD_MAN_1 = 0x40000000, + VDI_C_TOP_FIELD_AUTO_1 = 0x80000000, +}; + +enum di_pins { + DI_PIN11 = 0, + DI_PIN12 = 1, + DI_PIN13 = 2, + DI_PIN14 = 3, + DI_PIN15 = 4, + DI_PIN16 = 5, + DI_PIN17 = 6, + DI_PIN_CS = 7, + + DI_PIN_SER_CLK = 0, + DI_PIN_SER_RS = 1, +}; + +enum di_sync_wave { + DI_SYNC_NONE = -1, + DI_SYNC_CLK = 0, + DI_SYNC_INT_HSYNC = 1, + DI_SYNC_HSYNC = 2, + DI_SYNC_VSYNC = 3, + DI_SYNC_DE = 5, +}; + +/* DC template opcodes */ +#define WROD(lf) (0x18 | (lf << 1)) + +#endif diff --git a/drivers/mxc/mcu_pmic/Kconfig b/drivers/mxc/mcu_pmic/Kconfig new file mode 100644 index 000000000000..cb6815e92d86 --- /dev/null +++ b/drivers/mxc/mcu_pmic/Kconfig @@ -0,0 +1,17 @@ +# +# PMIC Modules configuration +# + +config MXC_PMIC_MC9S08DZ60 + tristate "MC9S08DZ60 PMIC" + depends on ARCH_MXC && I2C + ---help--- + This is the MXC MC9S08DZ60(MCU) PMIC support. + +config MXC_MC9SDZ60_RTC + tristate "MC9SDZ60 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC9SDZ60 + ---help--- + This is the MC9SDZ60 RTC module driver. This module provides kernel API + for RTC part of MC9SDZ60. + If you want MC9SDZ60 RTC support, you should say Y here diff --git a/drivers/mxc/mcu_pmic/Makefile b/drivers/mxc/mcu_pmic/Makefile new file mode 100644 index 000000000000..96aae94d5290 --- /dev/null +++ b/drivers/mxc/mcu_pmic/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the mc9sdz60 pmic drivers. +# + +obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += pmic_mc9sdz60_mod.o +pmic_mc9sdz60_mod-objs := mcu_pmic_core.o max8660.o mc9s08dz60.o mcu_pmic_gpio.o diff --git a/drivers/mxc/mcu_pmic/max8660.c b/drivers/mxc/mcu_pmic/max8660.c new file mode 100644 index 000000000000..f48899f212f9 --- /dev/null +++ b/drivers/mxc/mcu_pmic/max8660.c @@ -0,0 +1,154 @@ +/* + * Copyright 2008-2009 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 max8660.c + * @brief Driver for max8660 + * + * @ingroup pmic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <asm/uaccess.h> +#include "mcu_pmic_core.h" +#include "max8660.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MAX8660_I2C_ADDR 0x68 + +static struct i2c_client *max8660_i2c_client; + + /* reg names for max8660 + REG_MAX8660_OUTPUT_ENABLE_1, + REG_MAX8660_OUTPUT_ENABLE_2, + REG_MAX8660_VOLT__CHANGE_1, + REG_MAX8660_V3_TARGET_VOLT_1, + REG_MAX8660_V3_TARGET_VOLT_2, + REG_MAX8660_V4_TARGET_VOLT_1, + REG_MAX8660_V4_TARGET_VOLT_2, + REG_MAX8660_V5_TARGET_VOLT_1, + REG_MAX8660_V5_TARGET_VOLT_2, + REG_MAX8660_V6V7_TARGET_VOLT, + REG_MAX8660_FORCE_PWM + */ + + /* save down the reg values for the device is write only */ +static u8 max8660_reg_value_table[] = + { 0x0, 0x0, 0x0, 0x17, 0x17, 0x1F, 0x1F, 0x04, 0x04, 0x0, 0x0 +}; +static int max8660_dev_present; + +int is_max8660_present(void) +{ + return max8660_dev_present; +} + +int max8660_get_buffered_reg_val(int reg_name, u8 *value) +{ + if (!max8660_dev_present) + return -1; + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) + return -1; + *value = + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1]; + return 0; +} +int max8660_save_buffered_reg_val(int reg_name, u8 value) +{ + + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) + return -1; + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1] = value; + return 0; +} + +int max8660_write_reg(u8 reg, u8 value) +{ + if (max8660_dev_present && (i2c_smbus_write_byte_data( + max8660_i2c_client, reg, value) >= 0)) + return 0; + return -1; +} + +/*! + * max8660 I2C attach function + * + * @param adapter struct i2c_client * + * @return 0 for max8660 successfully detected + */ +static int max8660_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int retval; + max8660_i2c_client = client; + retval = i2c_smbus_write_byte_data(max8660_i2c_client, + MAX8660_OUTPUT_ENABLE_1, 0); + if (retval == 0) { + max8660_dev_present = 1; + pr_info("max8660 probed !\n"); + } else { + max8660_dev_present = 0; + pr_info("max8660 not detected!\n"); + } + return retval; +} + +/*! + * max8660 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int max8660_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id max8660_id[] = { + { "max8660", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, max8660_id); + +static struct i2c_driver max8660_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max8660",}, + .probe = max8660_probe, + .remove = max8660_remove, + .id_table = max8660_id, +}; + +/* called by pmic core when init*/ +int max8660_init(void) +{ + int err; + err = i2c_add_driver(&max8660_i2c_driver); + return err; +} +void max8660_exit(void) +{ + i2c_del_driver(&max8660_i2c_driver); +} diff --git a/drivers/mxc/mcu_pmic/max8660.h b/drivers/mxc/mcu_pmic/max8660.h new file mode 100644 index 000000000000..567784611d80 --- /dev/null +++ b/drivers/mxc/mcu_pmic/max8660.h @@ -0,0 +1,49 @@ +/* + * Copyright 2008 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 max8660.h + * @brief Driver for max8660 + * + * @ingroup pmic + */ +#ifndef _MAX8660_H_ +#define _MAX8660_H_ + +#ifdef __KERNEL__ + +#define MAX8660_OUTPUT_ENABLE_1 0x10 +#define MAX8660_OUTPUT_ENABLE_2 0x12 +#define MAX8660_VOLT_CHANGE_CONTROL 0x20 +#define MAX8660_V3_TARGET_VOLT_1 0x23 +#define MAX8660_V3_TARGET_VOLT_2 0x24 +#define MAX8660_V4_TARGET_VOLT_1 0x29 +#define MAX8660_V4_TARGET_VOLT_2 0x2A +#define MAX8660_V5_TARGET_VOLT_1 0x32 +#define MAX8660_V5_TARGET_VOLT_2 0x33 +#define MAX8660_V6V7_TARGET_VOLT 0x39 +#define MAX8660_FORCE_PWM 0x80 + +int is_max8660_present(void); +int max8660_write_reg(u8 reg, u8 value); +int max8660_save_buffered_reg_val(int reg_name, u8 value); +int max8660_get_buffered_reg_val(int reg_name, u8 *value); +int max8660_init(void); +void max8660_exit(void); + +extern int reg_max8660_probe(void); +extern int reg_max8660_remove(void); + +#endif /* __KERNEL__ */ + +#endif /* _MAX8660_H_ */ diff --git a/drivers/mxc/mcu_pmic/mc9s08dz60.c b/drivers/mxc/mcu_pmic/mc9s08dz60.c new file mode 100644 index 000000000000..1e5da6319b12 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mc9s08dz60.c @@ -0,0 +1,197 @@ +/* + * Copyright 2008-2009 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 mc9s08dz60.c + * @brief Driver for MC9sdz60 + * + * @ingroup pmic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <linux/mfd/mc9s08dz60/core.h> + +#include <mach/clock.h> +#include <linux/uaccess.h> +#include "mc9s08dz60.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MC9S08DZ60_I2C_ADDR 0xD2 /* 7bits I2C address */ +static struct i2c_client *mc9s08dz60_i2c_client; + +int mc9s08dz60_read_reg(u8 reg, u8 *value) +{ + *value = (u8) i2c_smbus_read_byte_data(mc9s08dz60_i2c_client, reg); + return 0; +} + +int mc9s08dz60_write_reg(u8 reg, u8 value) +{ + if (i2c_smbus_write_byte_data(mc9s08dz60_i2c_client, reg, value) < 0) + return -1; + return 0; +} + +static ssize_t mc9s08dz60_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int i; + u8 value; + int offset = 7; + + for (i = 0; i < 7; i++) { + mc9s08dz60_read_reg(i, &value); + pr_info("reg%02x: %02x\t", i, value); + mc9s08dz60_read_reg(i + offset, &value); + pr_info("reg%02x: %02x\t", i + offset, value); + mc9s08dz60_read_reg(i + offset * 2, &value); + pr_info("reg%02x: %02x\t", i + offset * 2, value); + mc9s08dz60_read_reg(i + offset * 3, &value); + pr_info("reg%02x: %02x\t", i + offset * 3, value); + mc9s08dz60_read_reg(i + offset * 4, &value); + pr_info("reg%02x: %02x\t", i + offset * 4, value); + mc9s08dz60_read_reg(i + offset * 5, &value); + pr_info("reg%02x: %02x\n", i + offset * 5, value); + } + + return 0; +} + +static ssize_t mc9s08dz60_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int ret; + unsigned long reg, new_value; + u8 value; + char *p; + + strict_strtoul(buf, 16, ®); + + p = NULL; + p = memchr(buf, ' ', count); + + if (p == NULL) { + mc9s08dz60_read_reg(reg, &value); + pr_info("reg%02lu: %06x\n", reg, value); + return count; + } + + p += 1; + + strict_strtoul(p, 16, &new_value); + value = new_value; + + ret = mc9s08dz60_write_reg((u8)reg, value); + if (ret == 0) + pr_info("write reg%02lx: %06x\n", reg, value); + else + pr_info("register update failed\n"); + + return count; +} + +static struct device_attribute mc9s08dz60_dev_attr = { + .attr = { + .name = "mc9s08dz60_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = mc9s08dz60_show, + .store = mc9s08dz60_store, +}; + + +/*! + * mc9s08dz60 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return 0 + */ +static int mc9s08dz60_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mc9s08dz60 *mc9s08dz60 = NULL; + struct mc9s08dz60_platform_data *plat_data = client->dev.platform_data; + pr_info("mc9s08dz60 probing .... \n"); + + mc9s08dz60 = kzalloc(sizeof(struct mc9s08dz60), GFP_KERNEL); + if (mc9s08dz60 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc9s08dz60); + mc9s08dz60->dev = &client->dev; + mc9s08dz60->i2c_client = client; + + if (plat_data && plat_data->init) { + ret = plat_data->init(mc9s08dz60); + if (ret != 0) + return -1; + } + + ret = device_create_file(&client->dev, &mc9s08dz60_dev_attr); + if (ret) + dev_err(&client->dev, "create device file failed!\n"); + + + mc9s08dz60_i2c_client = client; + + return 0; +} + +/*! + * mc9s08dz60 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int mc9s08dz60_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc9s08dz60_id[] = { + { "mc9s08dz60", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, mc9s08dz60_id); + +static struct i2c_driver mc9s08dz60_i2c_driver = { + .driver = {.owner = THIS_MODULE, + .name = "mc9s08dz60", + }, + .probe = mc9s08dz60_probe, + .remove = mc9s08dz60_remove, + .id_table = mc9s08dz60_id, +}; + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +int mc9s08dz60_init(void) +{ + int err; + err = i2c_add_driver(&mc9s08dz60_i2c_driver); + return err; +} +void mc9s08dz60_exit(void) +{ + i2c_del_driver(&mc9s08dz60_i2c_driver); +} diff --git a/drivers/mxc/mcu_pmic/mc9s08dz60.h b/drivers/mxc/mcu_pmic/mc9s08dz60.h new file mode 100644 index 000000000000..28b8746eeb12 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mc9s08dz60.h @@ -0,0 +1,73 @@ +/* + * Copyright 2008-2009 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 mc9s08dz60.h + * @brief Driver for mc9s08dz60 + * + * @ingroup pmic + */ +#ifndef _MC9SDZ60_H_ +#define _MC9SDZ60_H_ + +#define MCU_VERSION 0x00 +/*#define Reserved 0x01*/ +#define MCU_SECS 0x02 +#define MCU_MINS 0x03 +#define MCU_HRS 0x04 +#define MCU_DAY 0x05 +#define MCU_DATE 0x06 +#define MCU_MONTH 0x07 +#define MCU_YEAR 0x08 + +#define MCU_ALARM_SECS 0x09 +#define MCU_ALARM_MINS 0x0A +#define MCU_ALARM_HRS 0x0B +/* #define Reserved 0x0C*/ +/* #define Reserved 0x0D*/ +#define MCU_TS_CONTROL 0x0E +#define MCU_X_LOW 0x0F +#define MCU_Y_LOW 0x10 +#define MCU_XY_HIGH 0x11 +#define MCU_X_LEFT_LOW 0x12 +#define MCU_X_LEFT_HIGH 0x13 +#define MCU_X_RIGHT 0x14 +#define MCU_Y_TOP_LOW 0x15 +#define MCU_Y_TOP_HIGH 0x16 +#define MCU_Y_BOTTOM 0x17 +/* #define Reserved 0x18*/ +/* #define Reserved 0x19*/ +#define MCU_RESET_1 0x1A +#define MCU_RESET_2 0x1B +#define MCU_POWER_CTL 0x1C +#define MCU_DELAY_CONFIG 0x1D +/* #define Reserved 0x1E */ +/* #define Reserved 0x1F */ +#define MCU_GPIO_1 0x20 +#define MCU_GPIO_2 0x21 +#define MCU_KPD_1 0x22 +#define MCU_KPD_2 0x23 +#define MCU_KPD_CONTROL 0x24 +#define MCU_INT_ENABLE_1 0x25 +#define MCU_INT_ENABLE_2 0x26 +#define MCU_INT_FLAG_1 0x27 +#define MCU_INT_FLAG_2 0x28 +#define MCU_DES_FLAG 0x29 +int mc9s08dz60_read_reg(u8 reg, u8 *value); +int mc9s08dz60_write_reg(u8 reg, u8 value); +int mc9s08dz60_init(void); +void mc9s08dz60_exit(void); + +extern int reg_mc9s08dz60_probe(void); +extern int reg_mc9s08dz60_remove(void); + +#endif /* _MC9SDZ60_H_ */ diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_core.c b/drivers/mxc/mcu_pmic/mcu_pmic_core.c new file mode 100644 index 000000000000..55b2f5fe6e3e --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_core.c @@ -0,0 +1,226 @@ +/* + * Copyright 2008-2009 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 mc9s08dz60/mcu_pmic_core.c + * @brief This is the main file of mc9s08dz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <asm/ioctl.h> +#include <asm/uaccess.h> +#include <mach/gpio.h> + +#include "mcu_pmic_core.h" +#include "mc9s08dz60.h" +#include "max8660.h" + +/* bitfield macros for mcu pmic*/ +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + + +/* map reg names (enum pmic_reg in pmic_external.h) to real addr*/ +const static u8 mcu_pmic_reg_addr_table[] = { + MCU_VERSION, + MCU_SECS, + MCU_MINS, + MCU_HRS, + MCU_DAY, + MCU_DATE, + MCU_MONTH, + MCU_YEAR, + MCU_ALARM_SECS, + MCU_ALARM_MINS, + MCU_ALARM_HRS, + MCU_TS_CONTROL, + MCU_X_LOW, + MCU_Y_LOW, + MCU_XY_HIGH, + MCU_X_LEFT_LOW, + MCU_X_LEFT_HIGH, + MCU_X_RIGHT, + MCU_Y_TOP_LOW, + MCU_Y_TOP_HIGH, + MCU_Y_BOTTOM, + MCU_RESET_1, + MCU_RESET_2, + MCU_POWER_CTL, + MCU_DELAY_CONFIG, + MCU_GPIO_1, + MCU_GPIO_2, + MCU_KPD_1, + MCU_KPD_2, + MCU_KPD_CONTROL, + MCU_INT_ENABLE_1, + MCU_INT_ENABLE_2, + MCU_INT_FLAG_1, + MCU_INT_FLAG_2, + MCU_DES_FLAG, + MAX8660_OUTPUT_ENABLE_1, + MAX8660_OUTPUT_ENABLE_2, + MAX8660_VOLT_CHANGE_CONTROL, + MAX8660_V3_TARGET_VOLT_1, + MAX8660_V3_TARGET_VOLT_2, + MAX8660_V4_TARGET_VOLT_1, + MAX8660_V4_TARGET_VOLT_2, + MAX8660_V5_TARGET_VOLT_1, + MAX8660_V5_TARGET_VOLT_2, + MAX8660_V6V7_TARGET_VOLT, + MAX8660_FORCE_PWM +}; + +static int mcu_pmic_read(int reg_num, unsigned int *reg_val) +{ + int ret; + u8 value = 0; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_DES_FLAG) + ret = mc9s08dz60_read_reg(mcu_pmic_reg_addr_table[reg_num], + &value); + else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) + ret = max8660_get_buffered_reg_val(reg_num, &value); + else + return -1; + + if (ret < 0) + return -1; + *reg_val = value; + + return 0; +} + +static int mcu_pmic_write(int reg_num, const unsigned int reg_val) +{ + int ret; + u8 value = reg_val; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_DES_FLAG) { + + ret = + mc9s08dz60_write_reg( + mcu_pmic_reg_addr_table[reg_num], value); + if (ret < 0) + return -1; + } else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) { + ret = + max8660_write_reg(mcu_pmic_reg_addr_table[reg_num], value); + + if (ret < 0) + return -1; + + ret = max8660_save_buffered_reg_val(reg_num, value); + } else + return -1; + + return 0; +} + +int mcu_pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = mcu_pmic_read(reg, &temp); + if (ret != 0) + return -1; + *reg_value = (temp & reg_mask); + + pr_debug("Read REG[ %d ] = 0x%x\n", reg, *reg_value); + + return ret; +} + + +int mcu_pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = mcu_pmic_read(reg, &temp); + if (ret != 0) + return -1; + temp = (temp & (~reg_mask)) | reg_value; + + ret = mcu_pmic_write(reg, temp); + if (ret != 0) + return -1; + + pr_debug("Write REG[ %d ] = 0x%x\n", reg, reg_value); + + return ret; +} + +/*! + * make max8660 - mc9s08dz60 enter low-power mode + */ +static void pmic_power_off(void) +{ + mcu_pmic_write_reg(REG_MCU_POWER_CTL, 0x10, 0x10); +} + +static int __init mcu_pmic_init(void) +{ + int err; + + /* init chips */ + err = max8660_init(); + if (err) + goto fail1; + + err = mc9s08dz60_init(); + if (err) + goto fail1; + + if (is_max8660_present()) { + pr_info("max8660 is present \n"); + pm_power_off = pmic_power_off; + } else + pr_debug("max8660 is not present\n"); + pr_info("mcu_pmic_init completed!\n"); + return 0; + +fail1: + pr_err("mcu_pmic_init failed!\n"); + return err; +} + +static void __exit mcu_pmic_exit(void) +{ + reg_max8660_remove(); + mc9s08dz60_exit(); + max8660_exit(); +} + +subsys_initcall_sync(mcu_pmic_init); +module_exit(mcu_pmic_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("mcu pmic driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_core.h b/drivers/mxc/mcu_pmic/mcu_pmic_core.h new file mode 100644 index 000000000000..9ab07356f8a8 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_core.h @@ -0,0 +1,43 @@ +/* + * Copyright 2009 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 mcu_pmic_core.h + * @brief Driver for max8660 + * + * @ingroup pmic + */ +#ifndef _MCU_PMIC_CORE_H_ +#define _MCU_PMIC_CORE_H_ + +#include <linux/mfd/mc9s08dz60/pmic.h> + +#define MAX8660_REG_START (REG_MCU_DES_FLAG + 1) +enum { + + /* reg names for max8660 */ + REG_MAX8660_OUTPUT_ENABLE_1 = MAX8660_REG_START, + REG_MAX8660_OUTPUT_ENABLE_2, + REG_MAX8660_VOLT_CHANGE_CONTROL_1, + REG_MAX8660_V3_TARGET_VOLT_1, + REG_MAX8660_V3_TARGET_VOLT_2, + REG_MAX8660_V4_TARGET_VOLT_1, + REG_MAX8660_V4_TARGET_VOLT_2, + REG_MAX8660_V5_TARGET_VOLT_1, + REG_MAX8660_V5_TARGET_VOLT_2, + REG_MAX8660_V6V7_TARGET_VOLT, + REG_MAX8660_FORCE_PWM +}; + + +#endif /* _MCU_PMIC_CORE_H_ */ diff --git a/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c b/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c new file mode 100644 index 000000000000..dae00495e2b2 --- /dev/null +++ b/drivers/mxc/mcu_pmic/mcu_pmic_gpio.c @@ -0,0 +1,131 @@ +/* + * Copyright 2008-2009 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 mc9s08dz60/mcu_pmic_gpio.c + * @brief This is the main file of mc9s08dz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/mfd/mc9s08dz60/pmic.h> +#include <linux/pmic_status.h> +#include <linux/ioctl.h> + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +int pmic_gpio_set_bit_val(int reg, unsigned int bit, + unsigned int val) +{ + int reg_name; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return -1; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + if (0 == val) + CHECK_ERROR(mcu_pmic_write_reg(reg_name, 0, reg_mask)); + else + CHECK_ERROR(mcu_pmic_write_reg(reg_name, reg_mask, reg_mask)); + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_set_bit_val); + +int pmic_gpio_get_bit_val(int reg, unsigned int bit, + unsigned int *val) +{ + int reg_name; + unsigned int reg_read_val; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return -1; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + CHECK_ERROR(mcu_pmic_read_reg(reg_name, ®_read_val, reg_mask)); + if (0 == reg_read_val) + *val = 0; + else + *val = 1; + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_get_bit_val); + +int pmic_gpio_get_designation_bit_val(unsigned int bit, + unsigned int *val) +{ + unsigned int reg_read_val; + u8 reg_mask = 0; + + if (bit > 7) + return -1; + + SET_BIT_IN_BYTE(reg_mask, bit); + CHECK_ERROR( + mcu_pmic_read_reg(REG_MCU_DES_FLAG, ®_read_val, reg_mask)); + if (0 == reg_read_val) + *val = 0; + else + *val = 1; + + return 0; +} +EXPORT_SYMBOL(pmic_gpio_get_designation_bit_val); diff --git a/drivers/mxc/mlb/Kconfig b/drivers/mxc/mlb/Kconfig new file mode 100644 index 000000000000..294c9776fb4d --- /dev/null +++ b/drivers/mxc/mlb/Kconfig @@ -0,0 +1,13 @@ +# +# MLB configuration +# + +menu "MXC Media Local Bus Driver" + +config MXC_MLB + tristate "MLB support" + depends on ARCH_MX35 + ---help--- + Say Y to get the MLB support. + +endmenu diff --git a/drivers/mxc/mlb/Makefile b/drivers/mxc/mlb/Makefile new file mode 100644 index 000000000000..60662eb1c031 --- /dev/null +++ b/drivers/mxc/mlb/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the kernel MLB driver +# + +obj-$(CONFIG_MXC_MLB) += mxc_mlb.o diff --git a/drivers/mxc/mlb/mxc_mlb.c b/drivers/mxc/mlb/mxc_mlb.c new file mode 100644 index 000000000000..233c9d5e731a --- /dev/null +++ b/drivers/mxc/mlb/mxc_mlb.c @@ -0,0 +1,1050 @@ +/* + * linux/drivers/mxc/mlb/mxc_mlb.c + * + * Copyright 2008-2009 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 + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/cdev.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/mxc_mlb.h> +#include <linux/uaccess.h> + +#include <mach/hardware.h> +//#include <asm/irq.h> + +/*! + * MLB module memory map registers define + */ +#define MLB_REG_DCCR 0x0 +#define MLB_REG_SSCR 0x4 +#define MLB_REG_SDCR 0x8 +#define MLB_REG_SMCR 0xC +#define MLB_REG_VCCR 0x1C +#define MLB_REG_SBCR 0x20 +#define MLB_REG_ABCR 0x24 +#define MLB_REG_CBCR 0x28 +#define MLB_REG_IBCR 0x2C +#define MLB_REG_CICR 0x30 +#define MLB_REG_CECRn 0x40 +#define MLB_REG_CSCRn 0x44 +#define MLB_REG_CCBCRn 0x48 +#define MLB_REG_CNBCRn 0x4C +#define MLB_REG_LCBCRn 0x280 + +#define MLB_DCCR_FS_OFFSET 28 +#define MLB_DCCR_EN (1 << 31) +#define MLB_DCCR_LBM_OFFSET 30 +#define MLB_DCCR_RESET (1 << 23) +#define MLB_CECR_CE (1 << 31) +#define MLB_CECR_TR (1 << 30) +#define MLB_CECR_CT_OFFSET 28 +#define MLB_CECR_MBS (1 << 19) +#define MLB_CSCR_CBPE (1 << 0) +#define MLB_CSCR_CBDB (1 << 1) +#define MLB_CSCR_CBD (1 << 2) +#define MLB_CSCR_CBS (1 << 3) +#define MLB_CSCR_BE (1 << 4) +#define MLB_CSCR_ABE (1 << 5) +#define MLB_CSCR_LFS (1 << 6) +#define MLB_CSCR_PBPE (1 << 8) +#define MLB_CSCR_PBDB (1 << 9) +#define MLB_CSCR_PBD (1 << 10) +#define MLB_CSCR_PBS (1 << 11) +#define MLB_CSCR_RDY (1 << 16) +#define MLB_CSCR_BM (1 << 31) +#define MLB_CSCR_BF (1 << 30) +#define MLB_SSCR_SDML (1 << 5) + +#define MLB_CONTROL_TX_CHANN (0 << 4) +#define MLB_CONTROL_RX_CHANN (1 << 4) +#define MLB_ASYNC_TX_CHANN (2 << 4) +#define MLB_ASYNC_RX_CHANN (3 << 4) + +#define MLB_MINOR_DEVICES 2 +#define MLB_CONTROL_DEV_NAME "ctrl" +#define MLB_ASYNC_DEV_NAME "async" + +#define TX_CHANNEL 0 +#define RX_CHANNEL 1 +#define TX_CHANNEL_BUF_SIZE 1024 +#define RX_CHANNEL_BUF_SIZE 2*1024 +/* max package data size */ +#define ASYNC_PACKET_SIZE 1024 +#define CTRL_PACKET_SIZE 64 +#define RX_RING_NODES 10 + +#define _get_txchan(dev) mlb_devinfo[dev].channels[TX_CHANNEL] +#define _get_rxchan(dev) mlb_devinfo[dev].channels[RX_CHANNEL] + +enum { + MLB_CTYPE_SYNC, + MLB_CTYPE_ISOC, + MLB_CTYPE_ASYNC, + MLB_CTYPE_CTRL, +}; + +/*! + * Rx ring buffer + */ +struct mlb_rx_ringnode { + int size; + char *data; +}; + +struct mlb_channel_info { + + /* channel offset in memmap */ + const unsigned int reg_offset; + /* channel address */ + int address; + /*! + * channel buffer start address + * for Rx, buf_head pointer to a loop ring buffer + */ + unsigned long buf_head; + /* physical buffer head address */ + unsigned long phy_head; + /* channel buffer size */ + unsigned int buf_size; + /* channel buffer current ptr */ + unsigned long buf_ptr; + /* buffer spin lock */ + rwlock_t buf_lock; +}; + +struct mlb_dev_info { + + /* device node name */ + const char dev_name[20]; + /* channel type */ + const unsigned int channel_type; + /* channel info for tx/rx */ + struct mlb_channel_info channels[2]; + /* rx ring buffer */ + struct mlb_rx_ringnode rx_bufs[RX_RING_NODES]; + /* rx ring buffer read/write ptr */ + unsigned int rdpos, wtpos; + /* exception event */ + unsigned long ex_event; + /* channel started up or not */ + atomic_t on; + /* device open count */ + atomic_t opencnt; + /* wait queue head for channel */ + wait_queue_head_t rd_wq; + wait_queue_head_t wt_wq; + /* spinlock for event access */ + spinlock_t event_lock; +}; + +static struct mlb_dev_info mlb_devinfo[MLB_MINOR_DEVICES] = { + { + .dev_name = MLB_CONTROL_DEV_NAME, + .channel_type = MLB_CTYPE_CTRL, + .channels = { + [0] = { + .reg_offset = MLB_CONTROL_TX_CHANN, + .buf_size = TX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[0]. + buf_lock), + }, + [1] = { + .reg_offset = MLB_CONTROL_RX_CHANN, + .buf_size = RX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[0].channels[1]. + buf_lock), + }, + }, + .on = ATOMIC_INIT(0), + .opencnt = ATOMIC_INIT(0), + .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].rd_wq), + .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[0].wt_wq), + .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[0].event_lock), + }, + { + .dev_name = MLB_ASYNC_DEV_NAME, + .channel_type = MLB_CTYPE_ASYNC, + .channels = { + [0] = { + .reg_offset = MLB_ASYNC_TX_CHANN, + .buf_size = TX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[0]. + buf_lock), + }, + [1] = { + .reg_offset = MLB_ASYNC_RX_CHANN, + .buf_size = RX_CHANNEL_BUF_SIZE, + .buf_lock = + __RW_LOCK_UNLOCKED(mlb_devinfo[1].channels[1]. + buf_lock), + }, + }, + .on = ATOMIC_INIT(0), + .opencnt = ATOMIC_INIT(0), + .rd_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].rd_wq), + .wt_wq = __WAIT_QUEUE_HEAD_INITIALIZER(mlb_devinfo[1].wt_wq), + .event_lock = __SPIN_LOCK_UNLOCKED(mlb_devinfo[1].event_lock), + }, +}; + +static struct regulator *reg_nvcc; /* NVCC_MLB regulator */ +static struct clk *mlb_clk; +static struct cdev mxc_mlb_dev; /* chareset device */ +static dev_t dev; +static struct class *mlb_class; /* device class */ +static struct device *class_dev; +static unsigned long mlb_base; /* mlb module base address */ +static unsigned int irq; + +/*! + * Initial the MLB module device + */ +static void mlb_dev_init(void) +{ + unsigned long dccr_val; + unsigned long phyaddr; + + /* reset the MLB module */ + __raw_writel(MLB_DCCR_RESET, mlb_base + MLB_REG_DCCR); + while (__raw_readl(mlb_base + MLB_REG_DCCR) + & MLB_DCCR_RESET) ; + + /*! + * Enable MLB device, disable loopback mode, + * set default fps to 512, set mlb device address to 0 + */ + dccr_val = MLB_DCCR_EN; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + + /* disable all the system interrupt */ + __raw_writel(0x5F, mlb_base + MLB_REG_SMCR); + + /* write async, control tx/rx base address */ + phyaddr = _get_txchan(0).phy_head >> 16; + __raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_CBCR); + phyaddr = _get_txchan(1).phy_head >> 16; + __raw_writel(phyaddr << 16 | phyaddr, mlb_base + MLB_REG_ABCR); + +} + +static void mlb_dev_exit(void) +{ + __raw_writel(0, mlb_base + MLB_REG_DCCR); +} + +/*! + * MLB receive start function + * + * load phy_head to next buf register to start next rx + * here use single-packet buffer, set start=end + */ +static void mlb_start_rx(int cdev_id) +{ + struct mlb_channel_info *chinfo = &_get_rxchan(cdev_id); + unsigned long next; + + next = chinfo->phy_head & 0xFFFC; + /* load next buf */ + __raw_writel((next << 16) | next, mlb_base + + MLB_REG_CNBCRn + chinfo->reg_offset); + /* set ready bit to start next rx */ + __raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn + + chinfo->reg_offset); +} + +/*! + * MLB transmit start function + * make sure aquiring the rw buf_lock, when calling this + */ +static void mlb_start_tx(int cdev_id) +{ + struct mlb_channel_info *chinfo = &_get_txchan(cdev_id); + unsigned long begin, end; + + begin = chinfo->phy_head; + end = (chinfo->phy_head + chinfo->buf_ptr - chinfo->buf_head) & 0xFFFC; + /* load next buf */ + __raw_writel((begin << 16) | end, mlb_base + + MLB_REG_CNBCRn + chinfo->reg_offset); + /* set ready bit to start next tx */ + __raw_writel(MLB_CSCR_RDY, mlb_base + MLB_REG_CSCRn + + chinfo->reg_offset); +} + +/*! + * Enable the MLB channel + */ +static void mlb_channel_enable(int chan_dev_id, int on) +{ + unsigned long tx_regval = 0, rx_regval = 0; + /*! + * setup the direction, enable, channel type, + * mode select, channel address and mask buf start + */ + if (on) { + unsigned int ctype = mlb_devinfo[chan_dev_id].channel_type; + tx_regval = MLB_CECR_CE | MLB_CECR_TR | MLB_CECR_MBS | + (ctype << MLB_CECR_CT_OFFSET) | + _get_txchan(chan_dev_id).address; + rx_regval = MLB_CECR_CE | MLB_CECR_MBS | + (ctype << MLB_CECR_CT_OFFSET) | + _get_rxchan(chan_dev_id).address; + + atomic_set(&mlb_devinfo[chan_dev_id].on, 1); + } else { + atomic_set(&mlb_devinfo[chan_dev_id].on, 0); + } + + /* update the rx/tx channel entry config */ + __raw_writel(tx_regval, mlb_base + MLB_REG_CECRn + + _get_txchan(chan_dev_id).reg_offset); + __raw_writel(rx_regval, mlb_base + MLB_REG_CECRn + + _get_rxchan(chan_dev_id).reg_offset); + + if (on) + mlb_start_rx(chan_dev_id); +} + +/*! + * MLB interrupt handler + */ +void mlb_tx_isr(int minor, unsigned int cis) +{ + struct mlb_channel_info *chinfo = &_get_txchan(minor); + + if (cis & MLB_CSCR_CBD) { + /* buffer done, reset the buf_ptr */ + write_lock(&chinfo->buf_lock); + chinfo->buf_ptr = chinfo->buf_head; + write_unlock(&chinfo->buf_lock); + /* wake up the writer */ + wake_up_interruptible(&mlb_devinfo[minor].wt_wq); + } +} + +void mlb_rx_isr(int minor, unsigned int cis) +{ + struct mlb_channel_info *chinfo = &_get_rxchan(minor); + unsigned long end; + unsigned int len; + + if (cis & MLB_CSCR_CBD) { + + int wpos, rpos; + + rpos = mlb_devinfo[minor].rdpos; + wpos = mlb_devinfo[minor].wtpos; + + /* buffer done, get current buffer ptr */ + end = + __raw_readl(mlb_base + MLB_REG_CCBCRn + chinfo->reg_offset); + end >>= 16; /* end here is phy */ + len = end - (chinfo->phy_head & 0xFFFC); + + /*! + * copy packet from IRAM buf to ring buf. + * if the wpos++ == rpos, drop this packet + */ + if (((wpos + 1) % RX_RING_NODES) != rpos) { + +#ifdef DEBUG + if (mlb_devinfo[minor].channel_type == MLB_CTYPE_CTRL) { + if (len > CTRL_PACKET_SIZE) + pr_debug + ("mxc_mlb: ctrl packet" + "overflow\n"); + } else { + if (len > ASYNC_PACKET_SIZE) + pr_debug + ("mxc_mlb: async packet" + "overflow\n"); + } +#endif + memcpy(mlb_devinfo[minor].rx_bufs[wpos].data, + (const void *)chinfo->buf_head, len); + mlb_devinfo[minor].rx_bufs[wpos].size = len; + + /* update the ring wpos */ + mlb_devinfo[minor].wtpos = (wpos + 1) % RX_RING_NODES; + + /* wake up the reader */ + wake_up_interruptible(&mlb_devinfo[minor].rd_wq); + + pr_debug("recv package, len:%d, rdpos: %d, wtpos: %d\n", + len, rpos, mlb_devinfo[minor].wtpos); + } else { + pr_debug + ("drop package, due to no space, (%d,%d)\n", + rpos, mlb_devinfo[minor].wtpos); + } + + /* start next rx */ + mlb_start_rx(minor); + } +} + +static irqreturn_t mlb_isr(int irq, void *dev_id) +{ + unsigned long int_status, sscr, tx_cis, rx_cis; + struct mlb_dev_info *pdev; + int minor; + + sscr = __raw_readl(mlb_base + MLB_REG_SSCR); + pr_debug("mxc_mlb: system interrupt:%lx\n", sscr); + __raw_writel(0x7F, mlb_base + MLB_REG_SSCR); + + int_status = __raw_readl(mlb_base + MLB_REG_CICR) & 0xFFFF; + pr_debug("mxc_mlb: channel interrupt ids: %lx\n", int_status); + + for (minor = 0; minor < MLB_MINOR_DEVICES; minor++) { + + pdev = &mlb_devinfo[minor]; + tx_cis = rx_cis = 0; + + /* get tx channel interrupt status */ + if (int_status & (1 << (_get_txchan(minor).reg_offset >> 4))) + tx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn + + _get_txchan(minor).reg_offset); + /* get rx channel interrupt status */ + if (int_status & (1 << (_get_rxchan(minor).reg_offset >> 4))) + rx_cis = __raw_readl(mlb_base + MLB_REG_CSCRn + + _get_rxchan(minor).reg_offset); + + if (!tx_cis && !rx_cis) + continue; + + pr_debug("tx/rx int status: 0x%08lx/0x%08lx\n", tx_cis, rx_cis); + /* fill exception event */ + spin_lock(&pdev->event_lock); + pdev->ex_event |= tx_cis & 0x303; + pdev->ex_event |= (rx_cis & 0x303) << 16; + spin_unlock(&pdev->event_lock); + + /* clear the interrupt status */ + __raw_writel(tx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn + + _get_txchan(minor).reg_offset); + __raw_writel(rx_cis & 0xFFFF, mlb_base + MLB_REG_CSCRn + + _get_rxchan(minor).reg_offset); + + /* handel tx channel */ + if (tx_cis) + mlb_tx_isr(minor, tx_cis); + /* handle rx channel */ + if (rx_cis) + mlb_rx_isr(minor, rx_cis); + + } + + return IRQ_HANDLED; +} + +static int mxc_mlb_open(struct inode *inode, struct file *filp) +{ + int minor; + + minor = MINOR(inode->i_rdev); + + if (minor < 0 || minor >= MLB_MINOR_DEVICES) + return -ENODEV; + + /* open for each channel device */ + if (atomic_cmpxchg(&mlb_devinfo[minor].opencnt, 0, 1) != 0) + return -EBUSY; + + /* reset the buffer read/write ptr */ + _get_txchan(minor).buf_ptr = _get_txchan(minor).buf_head; + _get_rxchan(minor).buf_ptr = _get_rxchan(minor).buf_head; + mlb_devinfo[minor].rdpos = mlb_devinfo[minor].wtpos = 0; + mlb_devinfo[minor].ex_event = 0; + + return 0; +} + +static int mxc_mlb_release(struct inode *inode, struct file *filp) +{ + int minor; + + minor = MINOR(inode->i_rdev); + + /* clear channel settings and info */ + mlb_channel_enable(minor, 0); + + /* decrease the open count */ + atomic_set(&mlb_devinfo[minor].opencnt, 0); + + return 0; +} + +static int mxc_mlb_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + unsigned long flags, event; + int minor; + + minor = MINOR(inode->i_rdev); + + switch (cmd) { + + case MLB_CHAN_SETADDR: + { + unsigned int caddr; + /* get channel address from user space */ + if (copy_from_user(&caddr, argp, sizeof(caddr))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + _get_txchan(minor).address = (caddr >> 16) & 0xFFFF; + _get_rxchan(minor).address = caddr & 0xFFFF; + break; + } + + case MLB_CHAN_STARTUP: + if (atomic_read(&mlb_devinfo[minor].on)) { + pr_debug("mxc_mlb: channel areadly startup\n"); + break; + } + mlb_channel_enable(minor, 1); + break; + case MLB_CHAN_SHUTDOWN: + if (atomic_read(&mlb_devinfo[minor].on) == 0) { + pr_debug("mxc_mlb: channel areadly shutdown\n"); + break; + } + mlb_channel_enable(minor, 0); + break; + case MLB_CHAN_GETEVENT: + /* get and clear the ex_event */ + spin_lock_irqsave(&mlb_devinfo[minor].event_lock, flags); + event = mlb_devinfo[minor].ex_event; + mlb_devinfo[minor].ex_event = 0; + spin_unlock_irqrestore(&mlb_devinfo[minor].event_lock, flags); + + if (event) { + if (copy_to_user(argp, &event, sizeof(event))) { + pr_err("mxc_mlb: copy to user failed\n"); + return -EFAULT; + } + } else { + pr_debug("mxc_mlb: no exception event now\n"); + return -EAGAIN; + } + break; + case MLB_SET_FPS: + { + unsigned int fps; + unsigned long dccr_val; + + /* get fps from user space */ + if (copy_from_user(&fps, argp, sizeof(fps))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + /* check fps value */ + if (fps != 256 && fps != 512 && fps != 1024) { + pr_debug("mxc_mlb: invalid fps argument\n"); + return -EINVAL; + } + + dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR); + dccr_val &= ~(0x3 << MLB_DCCR_FS_OFFSET); + dccr_val |= (fps >> 9) << MLB_DCCR_FS_OFFSET; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + break; + } + + case MLB_GET_VER: + { + unsigned long version; + + /* get MLB device module version */ + version = __raw_readl(mlb_base + MLB_REG_VCCR); + + if (copy_to_user(argp, &version, sizeof(version))) { + pr_err("mxc_mlb: copy to user failed\n"); + return -EFAULT; + } + break; + } + + case MLB_SET_DEVADDR: + { + unsigned long dccr_val; + unsigned char devaddr; + + /* get MLB device address from user space */ + if (copy_from_user + (&devaddr, argp, sizeof(unsigned char))) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + dccr_val = __raw_readl(mlb_base + MLB_REG_DCCR); + dccr_val &= ~0xFF; + dccr_val |= devaddr; + __raw_writel(dccr_val, mlb_base + MLB_REG_DCCR); + + break; + } + default: + pr_info("mxc_mlb: Invalid ioctl command\n"); + return -EINVAL; + } + + return 0; +} + +/*! + * MLB read routine + * + * Read the current received data from queued buffer, + * and free this buffer for hw to fill ingress data. + */ +static ssize_t mxc_mlb_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + int minor, ret; + int size, rdpos; + struct mlb_rx_ringnode *rxbuf; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + rdpos = mlb_devinfo[minor].rdpos; + rxbuf = mlb_devinfo[minor].rx_bufs; + + /* check the current rx buffer is available or not */ + if (rdpos == mlb_devinfo[minor].wtpos) { + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + /* if !O_NONBLOCK, we wait for recv packet */ + ret = wait_event_interruptible(mlb_devinfo[minor].rd_wq, + (mlb_devinfo[minor].wtpos != + rdpos)); + if (ret < 0) + return ret; + } + + size = rxbuf[rdpos].size; + if (size > count) { + /* the user buffer is too small */ + pr_warning + ("mxc_mlb: received data size is bigger than count\n"); + return -EINVAL; + } + + /* copy rx buffer data to user buffer */ + if (copy_to_user(buf, rxbuf[rdpos].data, size)) { + pr_err("mxc_mlb: copy from user failed\n"); + return -EFAULT; + } + + /* update the read ptr */ + mlb_devinfo[minor].rdpos = (rdpos + 1) % RX_RING_NODES; + + *f_pos = 0; + + return size; +} + +/*! + * MLB write routine + * + * Copy the user data to tx channel buffer, + * and prepare the channel current/next buffer ptr. + */ +static ssize_t mxc_mlb_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + int minor; + unsigned long flags; + DEFINE_WAIT(__wait); + int ret; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + if (count > _get_txchan(minor).buf_size) { + /* too many data to write */ + pr_warning("mxc_mlb: overflow write data\n"); + return -EFBIG; + } + + *f_pos = 0; + + /* check the current tx buffer is used or not */ + write_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr != _get_txchan(minor).buf_head) { + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + + /* there's already some datas being transmit now */ + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + /* if !O_NONBLOCK, we wait for transmit finish */ + for (;;) { + prepare_to_wait(&mlb_devinfo[minor].wt_wq, + &__wait, TASK_INTERRUPTIBLE); + + write_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr == + _get_txchan(minor).buf_head) + break; + + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, + flags); + if (!signal_pending(current)) { + schedule(); + continue; + } + return -ERESTARTSYS; + } + finish_wait(&mlb_devinfo[minor].wt_wq, &__wait); + } + + /* copy user buffer to tx buffer */ + if (copy_from_user((void *)_get_txchan(minor).buf_ptr, buf, count)) { + pr_err("mxc_mlb: copy from user failed\n"); + ret = -EFAULT; + goto out; + } + _get_txchan(minor).buf_ptr += count; + + /* set current/next buffer start/end */ + mlb_start_tx(minor); + + ret = count; + +out: + write_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + return ret; +} + +static unsigned int mxc_mlb_poll(struct file *filp, + struct poll_table_struct *wait) +{ + int minor; + unsigned int ret = 0; + unsigned long flags; + + minor = MINOR(filp->f_dentry->d_inode->i_rdev); + + poll_wait(filp, &mlb_devinfo[minor].rd_wq, wait); + poll_wait(filp, &mlb_devinfo[minor].wt_wq, wait); + + /* check the tx buffer is avaiable or not */ + read_lock_irqsave(&_get_txchan(minor).buf_lock, flags); + if (_get_txchan(minor).buf_ptr == _get_txchan(minor).buf_head) + ret |= POLLOUT | POLLWRNORM; + read_unlock_irqrestore(&_get_txchan(minor).buf_lock, flags); + + /* check the rx buffer filled or not */ + if (mlb_devinfo[minor].rdpos != mlb_devinfo[minor].wtpos) + ret |= POLLIN | POLLRDNORM; + + /* check the exception event */ + if (mlb_devinfo[minor].ex_event) + ret |= POLLIN | POLLRDNORM; + + return ret; +} + +/*! + * char dev file operations structure + */ +static struct file_operations mxc_mlb_fops = { + + .owner = THIS_MODULE, + .open = mxc_mlb_open, + .release = mxc_mlb_release, + .ioctl = mxc_mlb_ioctl, + .poll = mxc_mlb_poll, + .read = mxc_mlb_read, + .write = mxc_mlb_write, +}; + +/*! + * This function is called whenever the MLB device is detected. + */ +static int __devinit mxc_mlb_probe(struct platform_device *pdev) +{ + int ret, mlb_major, i, j; + struct mxc_mlb_platform_data *plat_data; + struct resource *res; + void __iomem *base; + unsigned long bufaddr, phyaddr; + + /* malloc the Rx ring buffer firstly */ + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + char *buf; + int bufsize; + + if (mlb_devinfo[i].channel_type == MLB_CTYPE_ASYNC) + bufsize = ASYNC_PACKET_SIZE; + else + bufsize = CTRL_PACKET_SIZE; + + buf = kmalloc(bufsize * RX_RING_NODES, GFP_KERNEL); + if (buf == NULL) { + ret = -ENOMEM; + dev_err(&pdev->dev, "can not alloc rx buffers\n"); + goto err4; + } + for (j = 0; j < RX_RING_NODES; j++) { + mlb_devinfo[i].rx_bufs[j].data = buf; + buf += bufsize; + } + } + + /** + * Register MLB lld as two character devices + * One for Packet date channel, the other for control data channel + */ + ret = alloc_chrdev_region(&dev, 0, MLB_MINOR_DEVICES, "mxc_mlb"); + mlb_major = MAJOR(dev); + + if (ret < 0) { + dev_err(&pdev->dev, "can't get major %d\n", mlb_major); + goto err3; + } + + cdev_init(&mxc_mlb_dev, &mxc_mlb_fops); + mxc_mlb_dev.owner = THIS_MODULE; + + ret = cdev_add(&mxc_mlb_dev, dev, MLB_MINOR_DEVICES); + if (ret) { + dev_err(&pdev->dev, "can't add cdev\n"); + goto err2; + } + + /* create class and device for udev information */ + mlb_class = class_create(THIS_MODULE, "mlb"); + if (IS_ERR(mlb_class)) { + dev_err(&pdev->dev, "failed to create mlb class\n"); + ret = -ENOMEM; + goto err2; + } + + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + + class_dev = device_create(mlb_class, NULL, MKDEV(mlb_major, i), + NULL, mlb_devinfo[i].dev_name); + if (IS_ERR(class_dev)) { + dev_err(&pdev->dev, "failed to create mlb %s" + " class device\n", mlb_devinfo[i].dev_name); + ret = -ENOMEM; + goto err1; + } + } + + /* get irq line */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No mlb irq line provided\n"); + goto err1; + } + + irq = res->start; + /* request irq */ + if (request_irq(irq, mlb_isr, 0, "mlb", NULL)) { + dev_err(&pdev->dev, "failed to request irq\n"); + ret = -EBUSY; + goto err1; + } + + /* ioremap from phy mlb to kernel space */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No mlb base address provided\n"); + goto err0; + } + + base = ioremap(res->start, res->end - res->start); + dev_dbg(&pdev->dev, "mapped mlb base address: 0x%08x\n", + (unsigned int)base); + + if (base == NULL) { + dev_err(&pdev->dev, "failed to do ioremap with mlb base\n"); + goto err0; + } + mlb_base = (unsigned long)base; + + /*! + * get rx/tx buffer address from platform data + * make sure the buf_address is 4bytes aligned + * + * ------------------- <-- plat_data->buf_address + * | minor 0 tx buf | + * ----------------- + * | minor 0 rx buf | + * ----------------- + * | .... | + * ----------------- + * | minor n tx buf | + * ----------------- + * | minor n rx buf | + * ------------------- + */ + + plat_data = (struct mxc_mlb_platform_data *)pdev->dev.platform_data; + + bufaddr = plat_data->buf_address & ~0x3; + phyaddr = plat_data->phy_address & ~0x3; + + for (i = 0; i < MLB_MINOR_DEVICES; i++) { + /* set the virtual and physical buf head address */ + _get_txchan(i).buf_head = bufaddr; + _get_txchan(i).phy_head = phyaddr; + + bufaddr += TX_CHANNEL_BUF_SIZE; + phyaddr += TX_CHANNEL_BUF_SIZE; + + _get_rxchan(i).buf_head = bufaddr; + _get_rxchan(i).phy_head = phyaddr; + + bufaddr += RX_CHANNEL_BUF_SIZE; + phyaddr += RX_CHANNEL_BUF_SIZE; + + dev_dbg(&pdev->dev, "phy_head: tx(%lx), rx(%lx)\n", + _get_txchan(i).phy_head, _get_rxchan(i).phy_head); + dev_dbg(&pdev->dev, "buf_head: tx(%lx), rx(%lx)\n", + _get_txchan(i).buf_head, _get_rxchan(i).buf_head); + } + + /* enable GPIO */ + gpio_mlb_active(); + + /* power on MLB */ + reg_nvcc = regulator_get(&pdev->dev, plat_data->reg_nvcc); + /* set MAX LDO6 for NVCC to 2.5V */ + regulator_set_voltage(reg_nvcc, 2500000, 2500000); + regulator_enable(reg_nvcc); + + /* enable clock */ + mlb_clk = clk_get(&pdev->dev, plat_data->mlb_clk); + clk_enable(mlb_clk); + + /* initial MLB module */ + mlb_dev_init(); + + return 0; + +err0: + free_irq(irq, NULL); +err1: + for (--i; i >= 0; i--) + device_destroy(mlb_class, MKDEV(mlb_major, i)); + + class_destroy(mlb_class); +err2: + cdev_del(&mxc_mlb_dev); +err3: + unregister_chrdev_region(dev, MLB_MINOR_DEVICES); +err4: + for (i = 0; i < MLB_MINOR_DEVICES; i++) + kfree(mlb_devinfo[i].rx_bufs[0].data); + + return ret; +} + +static int __devexit mxc_mlb_remove(struct platform_device *pdev) +{ + int i; + + mlb_dev_exit(); + + /* disable mlb clock */ + clk_disable(mlb_clk); + clk_put(mlb_clk); + + /* disable mlb power */ + regulator_disable(reg_nvcc); + regulator_put(reg_nvcc); + + /* inactive GPIO */ + gpio_mlb_inactive(); + + /* iounmap */ + iounmap((void *)mlb_base); + + free_irq(irq, NULL); + + /* destroy mlb device class */ + for (i = MLB_MINOR_DEVICES - 1; i >= 0; i--) + device_destroy(mlb_class, MKDEV(MAJOR(dev), i)); + class_destroy(mlb_class); + + /* Unregister the two MLB devices */ + cdev_del(&mxc_mlb_dev); + unregister_chrdev_region(dev, MLB_MINOR_DEVICES); + + for (i = 0; i < MLB_MINOR_DEVICES; i++) + kfree(mlb_devinfo[i].rx_bufs[0].data); + + return 0; +} + +#ifdef CONFIG_PM +static int mxc_mlb_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int mxc_mlb_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define mxc_mlb_suspend NULL +#define mxc_mlb_resume NULL +#endif + +/*! + * platform driver structure for MLB + */ +static struct platform_driver mxc_mlb_driver = { + .driver = { + .name = "mxc_mlb"}, + .probe = mxc_mlb_probe, + .remove = __devexit_p(mxc_mlb_remove), + .suspend = mxc_mlb_suspend, + .resume = mxc_mlb_resume, +}; + +static int __init mxc_mlb_init(void) +{ + return platform_driver_register(&mxc_mlb_driver); +} + +static void __exit mxc_mlb_exit(void) +{ + platform_driver_unregister(&mxc_mlb_driver); +} + +module_init(mxc_mlb_init); +module_exit(mxc_mlb_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MLB low level driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/Kconfig b/drivers/mxc/pmic/Kconfig new file mode 100644 index 000000000000..4ee91110fc03 --- /dev/null +++ b/drivers/mxc/pmic/Kconfig @@ -0,0 +1,64 @@ +# +# PMIC device driver configuration +# + +menu "MXC PMIC support" + +config MXC_PMIC + boolean + +config MXC_PMIC_MC13783 + tristate "MC13783 PMIC" + depends on ARCH_MXC && SPI + select MXC_PMIC + ---help--- + This is the MXC MC13783(PMIC) support. It include + ADC, Audio, Battery, Connectivity, Light, Power and RTC. + +config MXC_PMIC_MC13892 + tristate "MC13892 PMIC" + depends on ARCH_MXC && (I2C || SPI) + select MXC_PMIC + ---help--- + This is the MXC MC13892(PMIC) support. It include + ADC, Battery, Connectivity, Light, Power and RTC. + +config MXC_PMIC_I2C + bool "Support PMIC I2C Interface" + depends on MXC_PMIC_MC13892 && I2C + +config MXC_PMIC_SPI + bool "Support PMIC SPI Interface" + depends on (MXC_PMIC_MC13892 || MXC_PMIC_MC13783) && SPI + +config MXC_PMIC_MC34704 + tristate "MC34704 PMIC" + depends on ARCH_MXC && I2C + select MXC_PMIC + ---help--- + This is the MXC MC34704 PMIC support. + +config MXC_PMIC_MC9SDZ60 + tristate "MC9sDZ60 PMIC" + depends on ARCH_MXC && I2C + select MXC_PMIC + ---help--- + This is the MXC MC9sDZ60(MCU) PMIC support. + +config MXC_PMIC_CHARDEV + tristate "MXC PMIC device interface" + depends on MXC_PMIC + help + Say Y here to use "pmic" device files, found in the /dev directory + on the system. They make it possible to have user-space programs + use or controll PMIC. Mainly its useful for notifying PMIC events + to user-space programs. + +comment "MXC PMIC Client Drivers" + depends on MXC_PMIC + +source "drivers/mxc/pmic/mc13783/Kconfig" + +source "drivers/mxc/pmic/mc13892/Kconfig" + +endmenu diff --git a/drivers/mxc/pmic/Makefile b/drivers/mxc/pmic/Makefile new file mode 100644 index 000000000000..385c07e8509f --- /dev/null +++ b/drivers/mxc/pmic/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the MXC PMIC drivers. +# + +obj-y += core/ +obj-$(CONFIG_MXC_PMIC_MC13783) += mc13783/ +obj-$(CONFIG_MXC_PMIC_MC13892) += mc13892/ diff --git a/drivers/mxc/pmic/core/Makefile b/drivers/mxc/pmic/core/Makefile new file mode 100644 index 000000000000..bb42231e3aa8 --- /dev/null +++ b/drivers/mxc/pmic/core/Makefile @@ -0,0 +1,21 @@ +# +# Makefile for the PMIC core drivers. +# +obj-$(CONFIG_MXC_PMIC_MC13783) += pmic_mc13783_mod.o +pmic_mc13783_mod-objs := pmic_external.o pmic_event.o pmic_common.o pmic_core_spi.o mc13783.o + +obj-$(CONFIG_MXC_PMIC_MC13892) += pmic_mc13892_mod.o +pmic_mc13892_mod-objs := pmic_external.o pmic_event.o pmic_common.o mc13892.o + +ifneq ($(CONFIG_MXC_PMIC_SPI),) +pmic_mc13892_mod-objs += pmic_core_spi.o +endif + +ifneq ($(CONFIG_MXC_PMIC_I2C),) +pmic_mc13892_mod-objs += pmic_core_i2c.o +endif + +obj-$(CONFIG_MXC_PMIC_MC34704) += pmic_mc34704_mod.o +pmic_mc34704_mod-objs := pmic_external.o pmic_event.o mc34704.o + +obj-$(CONFIG_MXC_PMIC_CHARDEV) += pmic-dev.o diff --git a/drivers/mxc/pmic/core/mc13783.c b/drivers/mxc/pmic/core/mc13783.c new file mode 100644 index 000000000000..179cad815ebf --- /dev/null +++ b/drivers/mxc/pmic/core/mc13783.c @@ -0,0 +1,380 @@ +/* + * Copyright 2004-2009 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 pmic/core/mc13783.c + * @brief This file contains MC13783 specific PMIC code. This implementaion + * may differ for each PMIC chip. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include <linux/spi/spi.h> +#include <linux/mfd/mc13783/core.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Defines + */ +#define EVENT_MASK_0 0x697fdf +#define EVENT_MASK_1 0x3efffb +#define MXC_PMIC_FRAME_MASK 0x00FFFFFF +#define MXC_PMIC_MAX_REG_NUM 0x3F +#define MXC_PMIC_REG_NUM_SHIFT 0x19 +#define MXC_PMIC_WRITE_BIT_SHIFT 31 + +static unsigned int events_enabled0 = 0; +static unsigned int events_enabled1 = 0; +struct mxc_pmic pmic_drv_data; + +/*! + * This function is called to read a register on PMIC. + * + * @param reg_num number of the pmic register to be read + * @param reg_val return value of register + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_read(unsigned int reg_num, unsigned int *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) & frame, 1); + + *reg_val = frame & MXC_PMIC_FRAME_MASK; + + return ret; +} + +/*! + * This function is called to write a value to the register on PMIC. + * + * @param reg_num number of the pmic register to be written + * @param reg_val value to be written + * + * @return Returns 0 on success -1 on failure. + */ +int pmic_write(int reg_num, const unsigned int reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= (1 << MXC_PMIC_WRITE_BIT_SHIFT); + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + frame |= reg_val & MXC_PMIC_FRAME_MASK; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) & frame, 1); + + return ret; +} + +void *pmic_alloc_data(struct device *dev) +{ + struct mc13783 *mc13783; + + mc13783 = kzalloc(sizeof(struct mc13783), GFP_KERNEL); + if (mc13783 == NULL) + return NULL; + + mc13783->dev = dev; + + return (void *)mc13783; +} + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi) +{ + /* Setup the SPI slave i.e.PMIC */ + pmic_drv_data.spi = spi; + + spi->mode = SPI_MODE_2 | SPI_CS_HIGH; + spi->bits_per_word = 32; + + return spi_setup(spi); +} + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void) +{ + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_MASK_1, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_0, MXC_PMIC_FRAME_MASK)); + CHECK_ERROR(pmic_write(REG_INTERRUPT_STATUS_1, MXC_PMIC_FRAME_MASK)); + return PMIC_SUCCESS; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t * ver) +{ + int rev_id = 0; + int rev1 = 0; + int rev2 = 0; + int finid = 0; + int icid = 0; + + ver->id = PMIC_MC13783; + pmic_read(REG_REVISION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + /* Ver 0.2 is actually 3.2a. Report as 3.2 */ + if ((rev1 == 0) && (rev2 == 2)) { + rev1 = 3; + } + + if (rev1 == 0 || icid != 2) { + ver->revision = -1; + printk(KERN_NOTICE + "mc13783: Not detected.\tAccess failed\t!!!\n"); + } else { + ver->revision = ((rev1 * 10) + rev2); + printk(KERN_INFO "mc13783 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); + } + + return; + +} + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int status0, status1; + int bit_set; + + pmic_read(REG_INTERRUPT_STATUS_0, &status0); + pmic_read(REG_INTERRUPT_STATUS_1, &status1); + pmic_write(REG_INTERRUPT_STATUS_0, status0); + pmic_write(REG_INTERRUPT_STATUS_1, status1); + status0 &= events_enabled0; + status1 &= events_enabled1; + + while (status0) { + bit_set = ffs(status0) - 1; + *(active_events + count) = bit_set; + count++; + status0 ^= (1 << bit_set); + } + while (status1) { + bit_set = ffs(status1) - 1; + *(active_events + count) = bit_set + 24; + count++; + status1 ^= (1 << bit_set); + } + + return count; +} + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 |= event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 |= event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: unmasking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, 0, event_bit); + + pr_debug("Enable Event : %d\n", event); + + return ret; +} + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_E1HZI) { + mask_reg = REG_INTERRUPT_MASK_0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 &= ~event_bit; + } else { + event -= 24; + mask_reg = REG_INTERRUPT_MASK_1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 &= ~event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: masking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, event_bit, event_bit); + + pr_debug("Disable Event : %d\n", event); + + return ret; +} + +/*! + * This function is called to read all sensor bits of PMIC. + * + * @param sensor Sensor to be checked. + * + * @return This function returns true if the sensor bit is high; + * or returns false if the sensor bit is low. + */ +bool pmic_check_sensor(t_sensor sensor) +{ + unsigned int reg_val = 0; + + CHECK_ERROR(pmic_read_reg + (REG_INTERRUPT_SENSE_0, ®_val, PMIC_ALL_BITS)); + + if ((1 << sensor) & reg_val) + return true; + else + return false; +} + +/*! + * This function checks one sensor of PMIC. + * + * @param sensor_bits structure of all sensor bits. + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ + +PMIC_STATUS pmic_get_sensors(t_sensor_bits * sensor_bits) +{ + int sense_0 = 0; + int sense_1 = 0; + + memset(sensor_bits, 0, sizeof(t_sensor_bits)); + + pmic_read_reg(REG_INTERRUPT_SENSE_0, &sense_0, 0xffffff); + pmic_read_reg(REG_INTERRUPT_SENSE_1, &sense_1, 0xffffff); + + sensor_bits->sense_chgdets = (sense_0 & (1 << 6)) ? true : false; + sensor_bits->sense_chgovs = (sense_0 & (1 << 7)) ? true : false; + sensor_bits->sense_chgrevs = (sense_0 & (1 << 8)) ? true : false; + sensor_bits->sense_chgshorts = (sense_0 & (1 << 9)) ? true : false; + sensor_bits->sense_cccvs = (sense_0 & (1 << 10)) ? true : false; + sensor_bits->sense_chgcurrs = (sense_0 & (1 << 11)) ? true : false; + sensor_bits->sense_bpons = (sense_0 & (1 << 12)) ? true : false; + sensor_bits->sense_lobatls = (sense_0 & (1 << 13)) ? true : false; + sensor_bits->sense_lobaths = (sense_0 & (1 << 14)) ? true : false; + sensor_bits->sense_usb4v4s = (sense_0 & (1 << 16)) ? true : false; + sensor_bits->sense_usb2v0s = (sense_0 & (1 << 17)) ? true : false; + sensor_bits->sense_usb0v8s = (sense_0 & (1 << 18)) ? true : false; + sensor_bits->sense_id_floats = (sense_0 & (1 << 19)) ? true : false; + sensor_bits->sense_id_gnds = (sense_0 & (1 << 20)) ? true : false; + sensor_bits->sense_se1s = (sense_0 & (1 << 21)) ? true : false; + sensor_bits->sense_ckdets = (sense_0 & (1 << 22)) ? true : false; + + sensor_bits->sense_onofd1s = (sense_1 & (1 << 3)) ? true : false; + sensor_bits->sense_onofd2s = (sense_1 & (1 << 4)) ? true : false; + sensor_bits->sense_onofd3s = (sense_1 & (1 << 5)) ? true : false; + sensor_bits->sense_pwrrdys = (sense_1 & (1 << 11)) ? true : false; + sensor_bits->sense_thwarnhs = (sense_1 & (1 << 12)) ? true : false; + sensor_bits->sense_thwarnls = (sense_1 & (1 << 13)) ? true : false; + sensor_bits->sense_clks = (sense_1 & (1 << 14)) ? true : false; + sensor_bits->sense_mc2bs = (sense_1 & (1 << 17)) ? true : false; + sensor_bits->sense_hsdets = (sense_1 & (1 << 18)) ? true : false; + sensor_bits->sense_hsls = (sense_1 & (1 << 19)) ? true : false; + sensor_bits->sense_alspths = (sense_1 & (1 << 20)) ? true : false; + sensor_bits->sense_ahsshorts = (sense_1 & (1 << 21)) ? true : false; + return PMIC_SUCCESS; +} + +EXPORT_SYMBOL(pmic_check_sensor); +EXPORT_SYMBOL(pmic_get_sensors); diff --git a/drivers/mxc/pmic/core/mc13892.c b/drivers/mxc/pmic/core/mc13892.c new file mode 100644 index 000000000000..34ceec59221e --- /dev/null +++ b/drivers/mxc/pmic/core/mc13892.c @@ -0,0 +1,333 @@ +/* + * Copyright 2008-2009 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 pmic/core/mc13892.c + * @brief This file contains MC13892 specific PMIC code. This implementaion + * may differ for each PMIC chip. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include <linux/mfd/mc13892/core.h> + +#include <asm/mach-types.h> +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Defines + */ +#define MC13892_I2C_RETRY_TIMES 10 +#define MXC_PMIC_FRAME_MASK 0x00FFFFFF +#define MXC_PMIC_MAX_REG_NUM 0x3F +#define MXC_PMIC_REG_NUM_SHIFT 0x19 +#define MXC_PMIC_WRITE_BIT_SHIFT 31 + +static unsigned int events_enabled0; +static unsigned int events_enabled1; +static struct mxc_pmic pmic_drv_data; +#ifndef CONFIG_MXC_PMIC_I2C +struct i2c_client *mc13892_client; +#endif + +int pmic_i2c_24bit_read(struct i2c_client *client, unsigned int reg_num, + unsigned int *value) +{ + unsigned char buf[3]; + int ret; + int i; + + memset(buf, 0, 3); + for (i = 0; i < MC13892_I2C_RETRY_TIMES; i++) { + ret = i2c_smbus_read_i2c_block_data(client, reg_num, 3, buf); + if (ret == 3) + break; + msleep(1); + } + + if (ret == 3) { + *value = buf[0] << 16 | buf[1] << 8 | buf[2]; + return ret; + } else { + pr_debug("24bit read error, ret = %d\n", ret); + return -1; /* return -1 on failure */ + } +} + +int pmic_i2c_24bit_write(struct i2c_client *client, + unsigned int reg_num, unsigned int reg_val) +{ + char buf[3]; + int ret; + int i; + + buf[0] = (reg_val >> 16) & 0xff; + buf[1] = (reg_val >> 8) & 0xff; + buf[2] = (reg_val) & 0xff; + + for (i = 0; i < MC13892_I2C_RETRY_TIMES; i++) { + ret = i2c_smbus_write_i2c_block_data(client, reg_num, 3, buf); + if (ret == 0) + break; + msleep(1); + } + + return ret; +} + +int pmic_read(int reg_num, unsigned int *reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (pmic_drv_data.spi != NULL) { + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + *reg_val = frame & MXC_PMIC_FRAME_MASK; + } else { + if (mc13892_client == NULL) + return PMIC_ERROR; + + if (pmic_i2c_24bit_read(mc13892_client, reg_num, reg_val) == -1) + return PMIC_ERROR; + } + + return PMIC_SUCCESS; +} + +int pmic_write(int reg_num, const unsigned int reg_val) +{ + unsigned int frame = 0; + int ret = 0; + + if (pmic_drv_data.spi != NULL) { + if (reg_num > MXC_PMIC_MAX_REG_NUM) + return PMIC_ERROR; + + frame |= (1 << MXC_PMIC_WRITE_BIT_SHIFT); + + frame |= reg_num << MXC_PMIC_REG_NUM_SHIFT; + + frame |= reg_val & MXC_PMIC_FRAME_MASK; + + ret = spi_rw(pmic_drv_data.spi, (u8 *) &frame, 1); + + return ret; + } else { + if (mc13892_client == NULL) + return PMIC_ERROR; + + return pmic_i2c_24bit_write(mc13892_client, reg_num, reg_val); + } +} + +void *pmic_alloc_data(struct device *dev) +{ + struct mc13892 *mc13892; + + mc13892 = kzalloc(sizeof(struct mc13892), GFP_KERNEL); + if (mc13892 == NULL) + return NULL; + + mc13892->dev = dev; + + return (void *)mc13892; +} + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi) +{ + /* Setup the SPI slave i.e.PMIC */ + pmic_drv_data.spi = spi; + + spi->mode = SPI_MODE_0 | SPI_CS_HIGH; + spi->bits_per_word = 32; + + return spi_setup(spi); +} + +int pmic_init_registers(void) +{ + CHECK_ERROR(pmic_write(REG_INT_MASK0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_MASK0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_STATUS0, 0xFFFFFF)); + CHECK_ERROR(pmic_write(REG_INT_STATUS1, 0xFFFFFF)); + /* disable auto charge */ + if (machine_is_mx51_3ds()) + CHECK_ERROR(pmic_write(REG_CHARGE, 0xB40003)); + + pm_power_off = mc13892_power_off; + + return PMIC_SUCCESS; +} + +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int status0, status1; + int bit_set; + + pmic_read(REG_INT_STATUS0, &status0); + pmic_read(REG_INT_STATUS1, &status1); + pmic_write(REG_INT_STATUS0, status0); + pmic_write(REG_INT_STATUS1, status1); + status0 &= events_enabled0; + status1 &= events_enabled1; + + while (status0) { + bit_set = ffs(status0) - 1; + *(active_events + count) = bit_set; + count++; + status0 ^= (1 << bit_set); + } + while (status1) { + bit_set = ffs(status1) - 1; + *(active_events + count) = bit_set + 24; + count++; + status1 ^= (1 << bit_set); + } + + return count; +} + +#define EVENT_MASK_0 0x387fff +#define EVENT_MASK_1 0x1177eb + +int pmic_event_unmask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_1HZI) { + mask_reg = REG_INT_MASK0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 |= event_bit; + } else { + event -= 24; + mask_reg = REG_INT_MASK1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 |= event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: unmasking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, 0, event_bit); + + pr_debug("Enable Event : %d\n", event); + + return ret; +} + +int pmic_event_mask(type_event event) +{ + unsigned int event_mask = 0; + unsigned int mask_reg = 0; + unsigned int event_bit = 0; + int ret; + + if (event < EVENT_1HZI) { + mask_reg = REG_INT_MASK0; + event_mask = EVENT_MASK_0; + event_bit = (1 << event); + events_enabled0 &= ~event_bit; + } else { + event -= 24; + mask_reg = REG_INT_MASK1; + event_mask = EVENT_MASK_1; + event_bit = (1 << event); + events_enabled1 &= ~event_bit; + } + + if ((event_bit & event_mask) == 0) { + pr_debug("Error: masking a reserved/unused event\n"); + return PMIC_ERROR; + } + + ret = pmic_write_reg(mask_reg, event_bit, event_bit); + + pr_debug("Disable Event : %d\n", event); + + return ret; +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t *ver) +{ + int rev_id = 0; + int rev1 = 0; + int rev2 = 0; + int finid = 0; + int icid = 0; + + ver->id = PMIC_MC13892; + pmic_read(REG_IDENTIFICATION, &rev_id); + + rev1 = (rev_id & 0x018) >> 3; + rev2 = (rev_id & 0x007); + icid = (rev_id & 0x01C0) >> 6; + finid = (rev_id & 0x01E00) >> 9; + + ver->revision = ((rev1 * 10) + rev2); + printk(KERN_INFO "mc13892 Rev %d.%d FinVer %x detected\n", rev1, + rev2, finid); +} + +void mc13892_power_off(void) +{ + unsigned int value; + + pmic_read_reg(REG_POWER_CTL0, &value, 0xffffff); + + value |= 0x000008; + + pmic_write_reg(REG_POWER_CTL0, value, 0xffffff); +} diff --git a/drivers/mxc/pmic/core/mc34704.c b/drivers/mxc/pmic/core/mc34704.c new file mode 100644 index 000000000000..f0ec05afe0ab --- /dev/null +++ b/drivers/mxc/pmic/core/mc34704.c @@ -0,0 +1,329 @@ +/* + * Copyright 2008-2009 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 pmic/core/mc34704.c + * @brief This file contains MC34704 specific PMIC code. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/uaccess.h> +#include <linux/mfd/mc34704/core.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include "pmic.h" + +/* + * Globals + */ +static pmic_version_t mxc_pmic_version = { + .id = PMIC_MC34704, + .revision = 0, +}; +static unsigned int events_enabled; +unsigned int active_events[MAX_ACTIVE_EVENTS]; +struct i2c_client *mc34704_client; +static void pmic_trigger_poll(void); + +#define MAX_MC34704_REG 0x59 +static unsigned int mc34704_reg_readonly[MAX_MC34704_REG / 32 + 1] = { + (1 << 0x03) || (1 << 0x05) || (1 << 0x07) || (1 << 0x09) || + (1 << 0x0B) || (1 << 0x0E) || (1 << 0x11) || (1 << 0x14) || + (1 << 0x17) || (1 << 0x18), + 0, +}; +static unsigned int mc34704_reg_written[MAX_MC34704_REG / 32 + 1]; +static unsigned char mc34704_shadow_regs[MAX_MC34704_REG - 1]; +#define IS_READONLY(r) ((1 << ((r) % 32)) & mc34704_reg_readonly[(r) / 32]) +#define WAS_WRITTEN(r) ((1 << ((r) % 32)) & mc34704_reg_written[(r) / 32]) +#define MARK_WRITTEN(r) do { \ + mc34704_reg_written[(r) / 32] |= (1 << ((r) % 32)); \ +} while (0) + +int pmic_read(int reg_nr, unsigned int *reg_val) +{ + int c; + + /* + * Use the shadow register if we've written to it + */ + if (WAS_WRITTEN(reg_nr)) { + *reg_val = mc34704_shadow_regs[reg_nr]; + return PMIC_SUCCESS; + } + + /* + * Otherwise, actually read the real register. + * Write-only registers will read as zero. + */ + c = i2c_smbus_read_byte_data(mc34704_client, reg_nr); + if (c == -1) { + pr_debug("mc34704: error reading register 0x%02x\n", reg_nr); + return PMIC_ERROR; + } else { + *reg_val = c; + return PMIC_SUCCESS; + } +} + +int pmic_write(int reg_nr, const unsigned int reg_val) +{ + int ret; + + ret = i2c_smbus_write_byte_data(mc34704_client, reg_nr, reg_val); + if (ret == -1) { + return PMIC_ERROR; + } else { + /* + * Update our software copy of the register since you + * can't read what you wrote. + */ + if (!IS_READONLY(reg_nr)) { + mc34704_shadow_regs[reg_nr] = reg_val; + MARK_WRITTEN(reg_nr); + } + return PMIC_SUCCESS; + } +} + +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int faults; + int bit_set; + + /* Check for any relevant PMIC faults */ + pmic_read(REG_MC34704_FAULTS, &faults); + faults &= events_enabled; + + /* + * Mask all active events, because there is no way to acknowledge + * or dismiss them in the PMIC -- they're sticky. + */ + events_enabled &= ~faults; + + /* Account for all unmasked faults */ + while (faults) { + bit_set = ffs(faults) - 1; + *(active_events + count) = bit_set; + count++; + faults ^= (1 << bit_set); + } + return count; +} + +int pmic_event_unmask(type_event event) +{ + unsigned int event_bit = 0; + unsigned int prior_events = events_enabled; + + event_bit = (1 << event); + events_enabled |= event_bit; + + pr_debug("Enable Event : %d\n", event); + + /* start the polling task as needed */ + if (events_enabled && prior_events == 0) + pmic_trigger_poll(); + + return 0; +} + +int pmic_event_mask(type_event event) +{ + unsigned int event_bit = 0; + + event_bit = (1 << event); + events_enabled &= ~event_bit; + + pr_debug("Disable Event : %d\n", event); + + return 0; +} + +/*! + * PMIC event polling task. This task is called periodically to poll + * for possible MC34704 events (No interrupt supplied by the hardware). + */ +static void pmic_event_task(struct work_struct *work); +DECLARE_DELAYED_WORK(pmic_ws, pmic_event_task); + +static void pmic_trigger_poll(void) +{ + schedule_delayed_work(&pmic_ws, HZ / 10); +} + +static void pmic_event_task(struct work_struct *work) +{ + unsigned int count = 0; + int i; + + count = pmic_get_active_events(active_events); + pr_debug("active events number %d\n", count); + + /* call handlers for all active events */ + for (i = 0; i < count; i++) + pmic_event_callback(active_events[i]); + + /* re-trigger this task, but only if somebody is watching */ + if (events_enabled) + pmic_trigger_poll(); + + return; +} + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} +EXPORT_SYMBOL(pmic_get_version); + +int __devinit pmic_init_registers(void) +{ + /* + * Set some registers to what they should be, + * if for no other reason than to initialize our + * software register copies. + */ + CHECK_ERROR(pmic_write(REG_MC34704_GENERAL2, 0x09)); + CHECK_ERROR(pmic_write(REG_MC34704_VGSET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG2SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG3SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG4SET1, 0)); + CHECK_ERROR(pmic_write(REG_MC34704_REG5SET1, 0)); + + return PMIC_SUCCESS; +} + +static int __devinit is_chip_onboard(struct i2c_client *client) +{ + int val; + + /* + * This PMIC has no version or ID register, so just see + * if it ACK's and returns 0 on some write-only register as + * evidence of its presence. + */ + val = i2c_smbus_read_byte_data(client, REG_MC34704_GENERAL2); + if (val != 0) + return -1; + + return 0; +} + +static int __devinit pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct mc34704 *mc34704; + struct mc34704_platform_data *plat_data = client->dev.platform_data; + + if (!plat_data || !plat_data->init) + return -ENODEV; + + ret = is_chip_onboard(client); + + if (ret == -1) + return -ENODEV; + + mc34704 = kzalloc(sizeof(struct mc34704), GFP_KERNEL); + if (mc34704 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc34704); + mc34704->dev = &client->dev; + mc34704->i2c_client = client; + + mc34704_client = client; + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize PMI registers */ + if (pmic_init_registers() != PMIC_SUCCESS) + return PMIC_ERROR; + + ret = plat_data->init(mc34704); + if (ret != 0) + return PMIC_ERROR; + + dev_info(&client->dev, "Loaded\n"); + + return PMIC_SUCCESS; +} + +static int pmic_remove(struct i2c_client *client) +{ + return 0; +} + +static int pmic_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int pmic_resume(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc34704_id[] = { + {"mc34704", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mc34704_id); + +static struct i2c_driver pmic_driver = { + .driver = { + .name = "mc34704", + .bus = NULL, + }, + .probe = pmic_probe, + .remove = pmic_remove, + .suspend = pmic_suspend, + .resume = pmic_resume, + .id_table = mc34704_id, +}; + +static int __init pmic_init(void) +{ + return i2c_add_driver(&pmic_driver); +} + +static void __exit pmic_exit(void) +{ + i2c_del_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("MC34704 PMIC driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic-dev.c b/drivers/mxc/pmic/core/pmic-dev.c new file mode 100644 index 000000000000..1cb7ce311338 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic-dev.c @@ -0,0 +1,319 @@ +/* + * Copyright 2005-2009 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 pmic-dev.c + * @brief This provides /dev interface to the user program. They make it + * possible to have user-space programs use or control PMIC. Mainly its + * useful for notifying PMIC events to user-space programs. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/kdev_t.h> +#include <linux/circ_buf.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/signal.h> +#include <linux/pmic_external.h> + +#include <asm/uaccess.h> + +#define PMIC_NAME "pmic" +#define CIRC_BUF_MAX 16 +#define CIRC_ADD(elem,cir_buf,size) \ + down(&event_mutex); \ + if(CIRC_SPACE(cir_buf.head, cir_buf.tail, size)){ \ + cir_buf.buf[cir_buf.head] = (char)elem; \ + cir_buf.head = (cir_buf.head + 1) & (size - 1); \ + } else { \ + pr_info("Failed to notify event to the user\n");\ + } \ + up(&event_mutex); + +#define CIRC_REMOVE(elem,cir_buf,size) \ + down(&event_mutex); \ + if(CIRC_CNT(cir_buf.head, cir_buf.tail, size)){ \ + elem = (int)cir_buf.buf[cir_buf.tail]; \ + cir_buf.tail = (cir_buf.tail + 1) & (size - 1); \ + } else { \ + elem = -1; \ + pr_info("No valid notified event\n"); \ + } \ + up(&event_mutex); + +static int pmic_major; +static struct class *pmic_class; +static struct fasync_struct *pmic_dev_queue; + +static DECLARE_MUTEX(event_mutex); +static struct circ_buf pmic_events; + +static void callbackfn(void *event) +{ + printk(KERN_INFO "\n\n DETECTED PMIC EVENT : %d\n\n", + (unsigned int)event); +} + +static void user_notify_callback(void *event) +{ + CIRC_ADD((int)event, pmic_events, CIRC_BUF_MAX); + kill_fasync(&pmic_dev_queue, SIGIO, POLL_IN); +} + +/*! + * This function implements IOCTL controls on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_dev_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + register_info reg_info; + pmic_event_callback_t event_sub; + type_event event; + int ret = 0; + + if (_IOC_TYPE(cmd) != 'P') + return -ENOTTY; + + switch (cmd) { + case PMIC_READ_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_read_reg(reg_info.reg, &(reg_info.reg_value), + 0x00ffffff); + pr_debug("read reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_WRITE_REG: + if (copy_from_user(®_info, (register_info *) arg, + sizeof(register_info))) { + return -EFAULT; + } + ret = + pmic_write_reg(reg_info.reg, reg_info.reg_value, + 0x00ffffff); + pr_debug("write reg %d %x\n", reg_info.reg, reg_info.reg_value); + if (copy_to_user((register_info *) arg, ®_info, + sizeof(register_info))) { + return -EFAULT; + } + break; + + case PMIC_SUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + pr_debug("subscribe done\n"); + break; + + case PMIC_UNSUBSCRIBE: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = callbackfn; + event_sub.param = (void *)event; + ret = pmic_event_unsubscribe(event, event_sub); + pr_debug("unsubscribe done\n"); + break; + + case PMIC_NOTIFY_USER: + if (get_user(event, (int __user *)arg)) { + return -EFAULT; + } + event_sub.func = user_notify_callback; + event_sub.param = (void *)event; + ret = pmic_event_subscribe(event, event_sub); + break; + + case PMIC_GET_NOTIFY: + CIRC_REMOVE(event, pmic_events, CIRC_BUF_MAX); + if (put_user(event, (int __user *)arg)) { + return -EFAULT; + } + break; + + default: + printk(KERN_ERR "%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + return ret; +} + +/*! + * This function implements the open method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_dev_open(struct inode *inode, struct file *file) +{ + pr_debug("open\n"); + return PMIC_SUCCESS; +} + +/*! + * This function implements the release method on a PMIC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * + * @return This function returns 0. + */ +static int pmic_dev_free(struct inode *inode, struct file *file) +{ + pr_debug("free\n"); + return PMIC_SUCCESS; +} + +static int pmic_dev_fasync(int fd, struct file *filp, int mode) +{ + return fasync_helper(fd, filp, mode, &pmic_dev_queue); +} + +/*! + * This structure defines file operations for a PMIC device. + */ +static struct file_operations pmic_fops = { + /*! + * the owner + */ + .owner = THIS_MODULE, + /*! + * the ioctl operation + */ + .ioctl = pmic_dev_ioctl, + /*! + * the open operation + */ + .open = pmic_dev_open, + /*! + * the release operation + */ + .release = pmic_dev_free, + /*! + * the release operation + */ + .fasync = pmic_dev_fasync, +}; + +/*! + * This function implements the init function of the PMIC char device. + * This function is called when the module is loaded. It registers + * the character device for PMIC to be used by user-space programs. + * + * @return This function returns 0. + */ +static int __init pmic_dev_init(void) +{ + int ret = 0; + struct device *pmic_device; + pmic_version_t pmic_ver; + + pmic_ver = pmic_get_version(); + if (pmic_ver.revision < 0) { + printk(KERN_ERR "No PMIC device found\n"); + return -ENODEV; + } + + pmic_major = register_chrdev(0, PMIC_NAME, &pmic_fops); + if (pmic_major < 0) { + printk(KERN_ERR "unable to get a major for pmic\n"); + return pmic_major; + } + + pmic_class = class_create(THIS_MODULE, PMIC_NAME); + if (IS_ERR(pmic_class)) { + printk(KERN_ERR "Error creating pmic class.\n"); + ret = PMIC_ERROR; + goto err; + } + + pmic_device = device_create(pmic_class, NULL, MKDEV(pmic_major, 0), NULL, + PMIC_NAME); + if (IS_ERR(pmic_device)) { + printk(KERN_ERR "Error creating pmic class device.\n"); + ret = PMIC_ERROR; + goto err1; + } + + pmic_events.buf = kmalloc(CIRC_BUF_MAX * sizeof(char), GFP_KERNEL); + if (NULL == pmic_events.buf) { + ret = -ENOMEM; + goto err2; + } + pmic_events.head = pmic_events.tail = 0; + + printk(KERN_INFO "PMIC Character device: successfully loaded\n"); + return ret; + err2: + device_destroy(pmic_class, MKDEV(pmic_major, 0)); + err1: + class_destroy(pmic_class); + err: + unregister_chrdev(pmic_major, PMIC_NAME); + return ret; + +} + +/*! + * This function implements the exit function of the PMIC character device. + * This function is called when the module is unloaded. It unregisters + * the PMIC character device. + * + */ +static void __exit pmic_dev_exit(void) +{ + device_destroy(pmic_class, MKDEV(pmic_major, 0)); + class_destroy(pmic_class); + + unregister_chrdev(pmic_major, PMIC_NAME); + + printk(KERN_INFO "PMIC Character device: successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_dev_init); +module_exit(pmic_dev_exit); + +MODULE_DESCRIPTION("PMIC Protocol /dev entries driver"); +MODULE_AUTHOR("FreeScale"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic.h b/drivers/mxc/pmic/core/pmic.h new file mode 100644 index 000000000000..b1382a34e850 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic.h @@ -0,0 +1,134 @@ +/* + * Copyright 2004-2009 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 + */ +#ifndef __PMIC_H__ +#define __PMIC_H__ + + /*! + * @file pmic.h + * @brief This file contains prototypes of all the functions to be + * defined for each PMIC chip. The implementation of these may differ + * from PMIC chip to PMIC chip. + * + * @ingroup PMIC_CORE + */ + +#include <linux/spi/spi.h> + +#define MAX_ACTIVE_EVENTS 10 + +/*! + * This structure is a way for the PMIC core driver to define their own + * \b spi_device structure. This structure includes the core \b spi_device + * structure that is provided by Linux SPI Framework/driver as an + * element and may contain other elements that are required by core driver. + */ +struct mxc_pmic { + /*! + * Master side proxy for an SPI slave device(PMIC) + */ + struct spi_device *spi; +}; + +/*! + * This function is called to transfer data to PMIC on SPI. + * + * @param spi the SPI slave device(PMIC) + * @param buf the pointer to the data buffer + * @param len the length of the data to be transferred + * + * @return Returns 0 on success -1 on failure. + */ +static inline int spi_rw(struct spi_device *spi, u8 * buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = (const void *)buf, + .rx_buf = buf, + .len = len, + .cs_change = 0, + .delay_usecs = 0, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + if (spi_sync(spi, &m) != 0 || m.status != 0) + return PMIC_ERROR; + return (len - m.actual_length); +} + +/*! + * This function returns the PMIC version in system. + * + * @param ver pointer to the pmic_version_t structure + * + * @return This function returns PMIC version. + */ +void pmic_get_revision(pmic_version_t * ver); + +/*! + * This function initializes the SPI device parameters for this PMIC. + * + * @param spi the SPI slave device(PMIC) + * + * @return None + */ +int pmic_spi_setup(struct spi_device *spi); + +/*! + * This function initializes the PMIC registers. + * + * @return None + */ +int pmic_init_registers(void); + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events); + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event); + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event); + +#ifdef CONFIG_MXC_PMIC_FIXARB +extern PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi); +#else +static inline PMIC_STATUS pmic_fix_arbitration(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} +#endif + +void *pmic_alloc_data(struct device *dev); + +#endif /* __PMIC_H__ */ diff --git a/drivers/mxc/pmic/core/pmic_common.c b/drivers/mxc/pmic/core/pmic_common.c new file mode 100644 index 000000000000..55f34b29cd93 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_common.c @@ -0,0 +1,98 @@ +/* + * Copyright 2009 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 pmic_common.c + * @brief This is the common file for the PMIC Core/Protocol driver. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Global variables + */ +pmic_version_t mxc_pmic_version; +unsigned int active_events[MAX_ACTIVE_EVENTS]; +struct workqueue_struct *pmic_event_wq; + +void pmic_bh_handler(struct work_struct *work); +/*! + * Bottom half handler of PMIC event handling. + */ +DECLARE_WORK(pmic_ws, pmic_bh_handler); + +/*! + * This function is the bottom half handler of the PMIC interrupt. + * It checks for active events and launches callback for the + * active events. + */ +void pmic_bh_handler(struct work_struct *work) +{ + unsigned int loop; + unsigned int count = 0; + + count = pmic_get_active_events(active_events); + pr_debug("active events number %d\n", count); + + for (loop = 0; loop < count; loop++) + pmic_event_callback(active_events[loop]); + + return; +} + +/*! + * This function is called when pmic interrupt occurs on the processor. + * It is the interrupt handler for the pmic module. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +irqreturn_t pmic_irq_handler(int irq, void *dev_id) +{ + /* prepare a task */ + queue_work(pmic_event_wq, &pmic_ws); + + return IRQ_HANDLED; +} + +/*! + * This function is used to determine the PMIC type and its revision. + * + * @return Returns the PMIC type and its revision. + */ + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} +EXPORT_SYMBOL(pmic_get_version); diff --git a/drivers/mxc/pmic/core/pmic_core_i2c.c b/drivers/mxc/pmic/core/pmic_core_i2c.c new file mode 100644 index 000000000000..d529891973cd --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_core_i2c.c @@ -0,0 +1,346 @@ +/* + * Copyright 2008-2009 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 pmic_core_i2c.c + * @brief This is the main file for the PMIC Core/Protocol driver. i2c + * should be providing the interface between the PMIC and the MCU. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/i2c.h> +#include <linux/mfd/mc13892/core.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +#define MC13892_GENERATION_ID_LSH 6 +#define MC13892_IC_ID_LSH 13 + +#define MC13892_GENERATION_ID_WID 3 +#define MC13892_IC_ID_WID 6 + +#define MC13892_GEN_ID_VALUE 0x7 +#define MC13892_IC_ID_VALUE 1 + +/* + * Global variables + */ +struct i2c_client *mc13892_client; + +extern struct workqueue_struct *pmic_event_wq; +extern pmic_version_t mxc_pmic_version; +extern irqreturn_t pmic_irq_handler(int irq, void *dev_id); + +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device adc_ldm = { + .name = "pmic_adc", + .id = 1, +}; +static struct platform_device battery_ldm = { + .name = "pmic_battery", + .id = 1, +}; +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; +static struct platform_device light_ldm = { + .name = "pmic_light", + .id = 1, +}; +static struct platform_device rleds_ldm = { + .name = "pmic_leds", + .id = 'r', +}; +static struct platform_device gleds_ldm = { + .name = "pmic_leds", + .id = 'g', +}; +static struct platform_device bleds_ldm = { + .name = "pmic_leds", + .id = 'b', +}; + +static void pmic_pdev_register(struct device *dev) +{ + platform_device_register(&adc_ldm); + platform_device_register(&battery_ldm); + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + platform_device_register(&light_ldm); + platform_device_register(&rleds_ldm); + platform_device_register(&gleds_ldm); + platform_device_register(&bleds_ldm); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&adc_ldm); + platform_device_unregister(&battery_ldm); + platform_device_unregister(&rtc_ldm); + platform_device_unregister(&power_ldm); + platform_device_unregister(&light_ldm); +} + +static int __devinit is_chip_onboard(struct i2c_client *client) +{ + unsigned int ret = 0; + + /*bind the right device to the driver */ + if (pmic_i2c_24bit_read(client, REG_IDENTIFICATION, &ret) == -1) + return -1; + + if (MC13892_GEN_ID_VALUE != BITFEXT(ret, MC13892_GENERATION_ID)) { + /*compare the address value */ + dev_err(&client->dev, + "read generation ID 0x%x is not equal to 0x%x!\n", + BITFEXT(ret, MC13892_GENERATION_ID), + MC13892_GEN_ID_VALUE); + return -1; + } + + return 0; +} + +static ssize_t mc13892_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, value; + int offset = (REG_TEST4 + 1) / 4; + + for (i = 0; i < offset; i++) { + pmic_read(i, &value); + pr_info("reg%02d: %06x\t\t", i, value); + pmic_read(i + offset, &value); + pr_info("reg%02d: %06x\t\t", i + offset, value); + pmic_read(i + offset * 2, &value); + pr_info("reg%02d: %06x\t\t", i + offset * 2, value); + pmic_read(i + offset * 3, &value); + pr_info("reg%02d: %06x\n", i + offset * 3, value); + } + + return 0; +} + +static ssize_t mc13892_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int reg, value, ret; + char *p; + + reg = simple_strtoul(buf, NULL, 10); + + p = NULL; + p = memchr(buf, ' ', count); + + if (p == NULL) { + pmic_read(reg, &value); + pr_debug("reg%02d: %06x\n", reg, value); + return count; + } + + p += 1; + + value = simple_strtoul(p, NULL, 16); + + ret = pmic_write(reg, value); + if (ret == 0) + pr_debug("write reg%02d: %06x\n", reg, value); + else + pr_debug("register update failed\n"); + + return count; +} + +static struct device_attribute mc13892_dev_attr = { + .attr = { + .name = "mc13892_ctl", + .mode = S_IRUSR | S_IWUSR, + }, + .show = mc13892_show, + .store = mc13892_store, +}; + +static int __devinit pmic_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + int pmic_irq; + struct mc13892 *mc13892; + struct mc13892_platform_data *plat_data = client->dev.platform_data; + + ret = is_chip_onboard(client); + if (ret == -1) + return -ENODEV; + + mc13892 = kzalloc(sizeof(struct mc13892), GFP_KERNEL); + if (mc13892 == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, mc13892); + mc13892->dev = &client->dev; + mc13892->i2c_client = client; + + /* so far, we got matched chip on board */ + + mc13892_client = client; + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize GPIO for PMIC Interrupt */ + gpio_pmic_active(); + + /* Get the PMIC Version */ + pmic_get_revision(&mxc_pmic_version); + if (mxc_pmic_version.revision < 0) { + dev_err((struct device *)client, + "PMIC not detected!!! Access Failed\n"); + return -ENODEV; + } else { + dev_dbg((struct device *)client, + "Detected pmic core IC version number is %d\n", + mxc_pmic_version.revision); + } + + /* Initialize the PMIC parameters */ + ret = pmic_init_registers(); + if (ret != PMIC_SUCCESS) + return PMIC_ERROR; + + pmic_event_wq = create_workqueue("mc13892"); + if (!pmic_event_wq) { + pr_err("mc13892 pmic driver init: fail to create work queue"); + return -EFAULT; + } + + /* Set and install PMIC IRQ handler */ + pmic_irq = (int)(client->irq); + if (pmic_irq == 0) + return PMIC_ERROR; + + set_irq_type(pmic_irq, IRQF_TRIGGER_RISING); + ret = + request_irq(pmic_irq, pmic_irq_handler, 0, "PMIC_IRQ", + 0); + + if (ret) { + dev_err(&client->dev, "request irq %d error!\n", pmic_irq); + return ret; + } + enable_irq_wake(pmic_irq); + + if (plat_data && plat_data->init) { + ret = plat_data->init(mc13892); + if (ret != 0) + return PMIC_ERROR; + } + + ret = device_create_file(&client->dev, &mc13892_dev_attr); + if (ret) + dev_err(&client->dev, "create device file failed!\n"); + + pmic_pdev_register(&client->dev); + + dev_info(&client->dev, "Loaded\n"); + + return PMIC_SUCCESS; +} + +static int pmic_remove(struct i2c_client *client) +{ + int pmic_irq = (int)(client->irq); + + if (pmic_event_wq) + destroy_workqueue(pmic_event_wq); + + free_irq(pmic_irq, 0); + pmic_pdev_unregister(); + return 0; +} + +static int pmic_suspend(struct i2c_client *client, pm_message_t state) +{ + return 0; +} + +static int pmic_resume(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id mc13892_id[] = { + {"mc13892", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, mc13892_id); + +static struct i2c_driver pmic_driver = { + .driver = { + .name = "mc13892", + .bus = NULL, + }, + .probe = pmic_probe, + .remove = pmic_remove, + .suspend = pmic_suspend, + .resume = pmic_resume, + .id_table = mc13892_id, +}; + +static int __init pmic_init(void) +{ + return i2c_add_driver(&pmic_driver); +} + +static void __exit pmic_exit(void) +{ + i2c_del_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for PMIC"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic_core_spi.c b/drivers/mxc/pmic/core/pmic_core_spi.c new file mode 100644 index 000000000000..4cc9eedec9de --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_core_spi.c @@ -0,0 +1,305 @@ +/* + * Copyright 2004-2009 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 pmic_core_spi.c + * @brief This is the main file for the PMIC Core/Protocol driver. SPI + * should be providing the interface between the PMIC and the MCU. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/spi/spi.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +#include <asm/uaccess.h> + +#include "pmic.h" + +/* + * Static functions + */ +static void pmic_pdev_register(void); +static void pmic_pdev_unregister(void); + +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device adc_ldm = { + .name = "pmic_adc", + .id = 1, +}; +static struct platform_device battery_ldm = { + .name = "pmic_battery", + .id = 1, +}; +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; +static struct platform_device light_ldm = { + .name = "pmic_light", + .id = 1, +}; +static struct platform_device rleds_ldm = { + .name = "pmic_leds", + .id = 'r', +}; +static struct platform_device gleds_ldm = { + .name = "pmic_leds", + .id = 'g', +}; +static struct platform_device bleds_ldm = { + .name = "pmic_leds", + .id = 'b', +}; + +/* + * External functions + */ +extern void pmic_event_list_init(void); +extern void pmic_event_callback(type_event event); +extern void gpio_pmic_active(void); +extern irqreturn_t pmic_irq_handler(int irq, void *dev_id); +extern pmic_version_t mxc_pmic_version; +extern struct workqueue_struct *pmic_event_wq; + +/*! + * This function registers platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_register(void) +{ + platform_device_register(&adc_ldm); + platform_device_register(&battery_ldm); + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + platform_device_register(&light_ldm); + platform_device_register(&rleds_ldm); + platform_device_register(&gleds_ldm); + platform_device_register(&bleds_ldm); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&adc_ldm); + platform_device_unregister(&battery_ldm); + platform_device_unregister(&rtc_ldm); + platform_device_unregister(&power_ldm); + platform_device_unregister(&light_ldm); +} + +/*! + * This function puts the SPI slave device in low-power mode/state. + * + * @param spi the SPI slave device + * @param message the power state to enter + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_suspend(struct spi_device *spi, pm_message_t message) +{ + return PMIC_SUCCESS; +} + +/*! + * This function brings the SPI slave device back from low-power mode/state. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int pmic_resume(struct spi_device *spi) +{ + return PMIC_SUCCESS; +} + +static struct spi_driver pmic_driver; + +/*! + * This function is called whenever the SPI slave device is detected. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devinit pmic_probe(struct spi_device *spi) +{ + int ret = 0; + struct pmic_platform_data *plat_data = spi->dev.platform_data; + + /* Initialize the PMIC parameters */ + ret = pmic_spi_setup(spi); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Initialize GPIO for PMIC Interrupt */ + gpio_pmic_active(); + + /* Get the PMIC Version */ + pmic_get_revision(&mxc_pmic_version); + if (mxc_pmic_version.revision < 0) { + dev_err((struct device *)spi, + "PMIC not detected!!! Access Failed\n"); + return -ENODEV; + } else { + dev_dbg((struct device *)spi, + "Detected pmic core IC version number is %d\n", + mxc_pmic_version.revision); + } + + spi_set_drvdata(spi, pmic_alloc_data(&(spi->dev))); + + /* Initialize the PMIC parameters */ + ret = pmic_init_registers(); + if (ret != PMIC_SUCCESS) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return PMIC_ERROR; + } + + pmic_event_wq = create_workqueue("pmic_spi"); + if (!pmic_event_wq) { + pr_err("pmic driver init: fail to create work queue"); + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return -EFAULT; + } + + /* Set and install PMIC IRQ handler */ + set_irq_type(spi->irq, IRQF_TRIGGER_RISING); + ret = request_irq(spi->irq, pmic_irq_handler, 0, "PMIC_IRQ", 0); + if (ret) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + dev_err((struct device *)spi, "gpio1: irq%d error.", spi->irq); + return ret; + } + + enable_irq_wake(spi->irq); + + if (plat_data && plat_data->init) { + ret = plat_data->init(spi_get_drvdata(spi)); + if (ret != 0) { + kfree(spi_get_drvdata(spi)); + spi_set_drvdata(spi, NULL); + return PMIC_ERROR; + } + } + + power_ldm.dev.platform_data = spi->dev.platform_data; + + pmic_pdev_register(); + + printk(KERN_INFO "Device %s probed\n", dev_name(&spi->dev)); + + return PMIC_SUCCESS; +} + +/*! + * This function is called whenever the SPI slave device is removed. + * + * @param spi the SPI slave device + * + * @return Returns 0 on SUCCESS and error on FAILURE. + */ +static int __devexit pmic_remove(struct spi_device *spi) +{ + if (pmic_event_wq) + destroy_workqueue(pmic_event_wq); + + free_irq(spi->irq, 0); + + pmic_pdev_unregister(); + + printk(KERN_INFO "Device %s removed\n", dev_name(&spi->dev)); + + return PMIC_SUCCESS; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct spi_driver pmic_driver = { + .driver = { + .name = "pmic_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = pmic_probe, + .remove = __devexit_p(pmic_remove), + .suspend = pmic_suspend, + .resume = pmic_resume, +}; + +/* + * Initialization and Exit + */ + +/*! + * This function implements the init function of the PMIC device. + * This function is called when the module is loaded. It registers + * the PMIC Protocol driver. + * + * @return This function returns 0. + */ +static int __init pmic_init(void) +{ + return spi_register_driver(&pmic_driver); +} + +/*! + * This function implements the exit function of the PMIC device. + * This function is called when the module is unloaded. It unregisters + * the PMIC Protocol driver. + * + */ +static void __exit pmic_exit(void) +{ + pr_debug("Unregistering the PMIC Protocol Driver\n"); + spi_unregister_driver(&pmic_driver); +} + +/* + * Module entry points + */ +subsys_initcall_sync(pmic_init); +module_exit(pmic_exit); + +MODULE_DESCRIPTION("Core/Protocol driver for PMIC"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/core/pmic_event.c b/drivers/mxc/pmic/core/pmic_event.c new file mode 100644 index 000000000000..2d1bebfef448 --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_event.c @@ -0,0 +1,235 @@ +/* + * Copyright 2004-2009 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 pmic_event.c + * @brief This file manage all event of PMIC component. + * + * It contains event subscription, unsubscription and callback + * launch methods implemeted. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include "pmic.h" + +/*! + * This structure is used to keep a list of subscribed + * callbacks for an event. + */ +typedef struct { + /*! + * Keeps a list of subscribed clients to an event. + */ + struct list_head list; + + /*! + * Callback function with parameter, called when event occurs + */ + pmic_event_callback_t callback; +} pmic_event_callback_list_t; + +/* Create a mutex to be used to prevent concurrent access to the event list */ +static DECLARE_MUTEX(event_mutex); + +/* This is a pointer to the event handler array. It defines the currently + * active set of events and user-defined callback functions. + */ +static struct list_head pmic_events[PMIC_MAX_EVENTS]; + +/*! + * This function initializes event list for PMIC event handling. + * + */ +void pmic_event_list_init(void) +{ + int i; + + for (i = 0; i < PMIC_MAX_EVENTS; i++) { + INIT_LIST_HEAD(&pmic_events[i]); + } + + sema_init(&event_mutex, 1); + return; +} + +/*! + * This function is used to subscribe on an event. + * + * @param event the event number to be subscribed + * @param callback the callback funtion to be subscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_subscribe(type_event event, + pmic_event_callback_t callback) +{ + pmic_event_callback_list_t *new = NULL; + + pr_debug("Event:%d Subscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Create a new linked list entry */ + new = kmalloc(sizeof(pmic_event_callback_list_t), GFP_KERNEL); + if (NULL == new) { + return -ENOMEM; + } + /* Initialize the list node fields */ + new->callback.func = callback.func; + new->callback.param = callback.param; + INIT_LIST_HEAD(&new->list); + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + kfree(new); + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_unmask(event) != PMIC_SUCCESS) { + kfree(new); + up(&event_mutex); + return PMIC_ERROR; + } + } + + /* Add this entry to the event list */ + list_add_tail(&new->list, &pmic_events[event]); + + /* Release the lock */ + up(&event_mutex); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to unsubscribe on an event. + * + * @param event the event number to be unsubscribed + * @param callback the callback funtion to be unsubscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_unsubscribe(type_event event, + pmic_event_callback_t callback) +{ + struct list_head *p; + struct list_head *n; + pmic_event_callback_list_t *temp = NULL; + int ret = PMIC_EVENT_NOT_SUBSCRIBED; + + pr_debug("Event:%d Unsubscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Find the entry in the list */ + list_for_each_safe(p, n, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + if (temp->callback.func == callback.func + && temp->callback.param == callback.param) { + /* Remove the entry from the list */ + list_del(p); + kfree(temp); + ret = PMIC_SUCCESS; + break; + } + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_mask(event) != PMIC_SUCCESS) { + ret = PMIC_UNSUBSCRIBE_ERROR; + } + } + + /* Release the lock */ + up(&event_mutex); + + return ret; +} + +/*! + * This function calls all callback of a specific event. + * + * @param event the active event number + * + * @return None + */ +void pmic_event_callback(type_event event) +{ + struct list_head *p; + pmic_event_callback_list_t *temp = NULL; + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + return; + } + + if (list_empty(&pmic_events[event])) { + pr_debug("PMIC Event:%d detected. No callback subscribed\n", + event); + up(&event_mutex); + return; + } + + list_for_each(p, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + temp->callback.func(temp->callback.param); + } + + /* Release the lock */ + up(&event_mutex); + + return; + +} + +EXPORT_SYMBOL(pmic_event_subscribe); +EXPORT_SYMBOL(pmic_event_unsubscribe); diff --git a/drivers/mxc/pmic/core/pmic_external.c b/drivers/mxc/pmic/core/pmic_external.c new file mode 100644 index 000000000000..bea23af3207d --- /dev/null +++ b/drivers/mxc/pmic/core/pmic_external.c @@ -0,0 +1,100 @@ +/* + * Copyright 2004-2009 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 pmic_external.c + * @brief This file contains all external functions of PMIC drivers. + * + * @ingroup PMIC_CORE + */ + +/* + * Includes + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/init.h> +#include <linux/errno.h> + +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> + +/* + * External Functions + */ +extern int pmic_read(int reg_num, unsigned int *reg_val); +extern int pmic_write(int reg_num, const unsigned int reg_val); + +/*! + * This function is called by PMIC clients to read a register on PMIC. + * + * @param reg number of register + * @param reg_value return value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_read_reg(int reg, unsigned int *reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + *reg_value = (temp & reg_mask); + + pr_debug("Read REG[ %d ] = 0x%x\n", reg, *reg_value); + + return ret; +} + +/*! + * This function is called by PMIC clients to write a register on PMIC. + * + * @param reg number of register + * @param reg_value New value of register + * @param reg_mask Bitmap mask indicating which bits to modify + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_write_reg(int reg, unsigned int reg_value, + unsigned int reg_mask) +{ + int ret = 0; + unsigned int temp = 0; + + ret = pmic_read(reg, &temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + temp = (temp & (~reg_mask)) | reg_value; +#ifdef CONFIG_MXC_PMIC_MC13783 + if (reg == REG_POWER_MISCELLANEOUS) + temp &= 0xFFFE7FFF; +#endif + ret = pmic_write(reg, temp); + if (ret != PMIC_SUCCESS) { + return PMIC_ERROR; + } + + pr_debug("Write REG[ %d ] = 0x%x\n", reg, reg_value); + + return ret; +} + +EXPORT_SYMBOL(pmic_read_reg); +EXPORT_SYMBOL(pmic_write_reg); diff --git a/drivers/mxc/pmic/mc13783/Kconfig b/drivers/mxc/pmic/mc13783/Kconfig new file mode 100644 index 000000000000..02496c624e2e --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Kconfig @@ -0,0 +1,55 @@ +# +# PMIC Modules configuration +# + +config MXC_MC13783_ADC + tristate "MC13783 ADC support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 ADC module driver. This module provides kernel API + for the ADC system of MC13783. + It controls also the touch screen interface. + If you want MC13783 ADC support, you should say Y here + +config MXC_MC13783_AUDIO + tristate "MC13783 Audio support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 audio module driver. This module provides kernel API + for audio part of MC13783. + If you want MC13783 audio support, you should say Y here +config MXC_MC13783_RTC + tristate "MC13783 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 RTC module driver. This module provides kernel API + for RTC part of MC13783. + If you want MC13783 RTC support, you should say Y here +config MXC_MC13783_LIGHT + tristate "MC13783 Light and Backlight support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 Light module driver. This module provides kernel API + for led and backlight control part of MC13783. + If you want MC13783 Light support, you should say Y here +config MXC_MC13783_BATTERY + tristate "MC13783 Battery API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 battery module driver. This module provides kernel API + for battery control part of MC13783. + If you want MC13783 battery support, you should say Y here +config MXC_MC13783_CONNECTIVITY + tristate "MC13783 Connectivity API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 connectivity module driver. This module provides kernel API + for USB/RS232 connectivity control part of MC13783. + If you want MC13783 connectivity support, you should say Y here +config MXC_MC13783_POWER + tristate "MC13783 Power API support" + depends on MXC_PMIC_MC13783 + ---help--- + This is the MC13783 power and supplies module driver. This module provides kernel API + for power and regulator control part of MC13783. + If you want MC13783 power support, you should say Y here diff --git a/drivers/mxc/pmic/mc13783/Makefile b/drivers/mxc/pmic/mc13783/Makefile new file mode 100644 index 000000000000..7bbba23f5ab9 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the mc13783 pmic drivers. +# + +obj-$(CONFIG_MXC_MC13783_ADC) += pmic_adc-mod.o +obj-$(CONFIG_MXC_MC13783_AUDIO) += pmic_audio-mod.o +obj-$(CONFIG_MXC_MC13783_RTC) += pmic_rtc-mod.o +obj-$(CONFIG_MXC_MC13783_LIGHT) += pmic_light-mod.o +obj-$(CONFIG_MXC_MC13783_BATTERY) += pmic_battery-mod.o +obj-$(CONFIG_MXC_MC13783_CONNECTIVITY) += pmic_convity-mod.o +obj-$(CONFIG_MXC_MC13783_POWER) += pmic_power-mod.o +pmic_adc-mod-objs := pmic_adc.o +pmic_audio-mod-objs := pmic_audio.o +pmic_rtc-mod-objs := pmic_rtc.o +pmic_light-mod-objs := pmic_light.o +pmic_battery-mod-objs := pmic_battery.o +pmic_convity-mod-objs := pmic_convity.o +pmic_power-mod-objs := pmic_power.o diff --git a/drivers/mxc/pmic/mc13783/pmic_adc.c b/drivers/mxc/pmic/mc13783/pmic_adc.c new file mode 100644 index 000000000000..1554faffd288 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc.c @@ -0,0 +1,1542 @@ +/* + * Copyright 2004-2009 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 mc13783/pmic_adc.c + * @brief This is the main file of PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +/* + * Includes + */ + +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#include "../core/pmic.h" +#include "pmic_adc_defs.h" + +#define NB_ADC_REG 5 + +static int pmic_adc_major; + +/* internal function */ +static void callback_tsi(void *); +static void callback_adcdone(void *); +static void callback_adcbisdone(void *); +static void callback_adc_comp_high(void *); + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the adc devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used by blocking application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_adc_class; + +/* + * ADC mc13783 API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_adc_init); +EXPORT_SYMBOL(pmic_adc_deinit); +EXPORT_SYMBOL(pmic_adc_convert); +EXPORT_SYMBOL(pmic_adc_convert_8x); +EXPORT_SYMBOL(pmic_adc_convert_multichnnel); +EXPORT_SYMBOL(pmic_adc_set_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_sample); +EXPORT_SYMBOL(pmic_adc_get_battery_current); +EXPORT_SYMBOL(pmic_adc_active_comparator); +EXPORT_SYMBOL(pmic_adc_deactive_comparator); + +static DECLARE_COMPLETION(adcdone_it); +static DECLARE_COMPLETION(adcbisdone_it); +static DECLARE_COMPLETION(adc_tsi); +static pmic_event_callback_t tsi_event; +static pmic_event_callback_t event_adc; +static pmic_event_callback_t event_adc_bis; +static pmic_event_callback_t adc_comp_h; +static bool data_ready_adc_1; +static bool data_ready_adc_2; +static bool adc_ts; +static bool wait_ts; +static bool monitor_en; +static bool monitor_adc; +static t_check_mode wcomp_mode; +static DECLARE_MUTEX(convert_mutex); + +void (*monitoring_cb) (void); /*call back to be called when event is detected. */ + +static DECLARE_WAIT_QUEUE_HEAD(queue_adc_busy); +static t_adc_state adc_dev[2]; + +static unsigned channel_num[] = { + 0, + 1, + 3, + 4, + 2, + 12, + 13, + 14, + 15, + -1, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 7, + 6, + -1, + -1, + -1, + -1, + 5, + 7 +}; + +static bool pmic_adc_ready; + +int is_pmic_adc_ready() +{ + return pmic_adc_ready; +} +EXPORT_SYMBOL(is_pmic_adc_ready); + + +/*! + * This is the suspend of power management for the mc13783 ADC API. + * It supports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + unsigned int reg_value = 0; + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the mc13783 adc API. + * It supports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_adc_resume(struct platform_device *pdev) +{ + /* nothing for mc13783 adc */ + unsigned int adc_0_reg, adc_1_reg; + suspend_flag = 0; + + /* let interrupt of TSI again */ + adc_0_reg = ADC_WAIT_TSI_0; + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + adc_1_reg = ADC_WAIT_TSI_1 | (ADC_BIS * adc_ts); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/* + * Call back functions + */ + +/*! + * This is the callback function called on TSI mc13783 event, used in synchronous call. + */ +static void callback_tsi(void *unused) +{ + pr_debug("*** TSI IT mc13783 PMIC_ADC_GET_TOUCH_SAMPLE ***\n"); + if (wait_ts) { + complete(&adc_tsi); + pmic_event_mask(EVENT_TSI); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcdone(void *unused) +{ + if (data_ready_adc_1) { + complete(&adcdone_it); + } +} + +/*! + * This is the callback function called on ADCDone mc13783 event. + */ +static void callback_adcbisdone(void *unused) +{ + pr_debug("* adcdone bis it callback *\n"); + if (data_ready_adc_2) { + complete(&adcbisdone_it); + } +} + +/*! + * This is the callback function called on mc13783 event. + */ +static void callback_adc_comp_high(void *unused) +{ + pr_debug("* adc comp it high *\n"); + if (wcomp_mode == CHECK_HIGH || wcomp_mode == CHECK_LOW_OR_HIGH) { + /* launch callback */ + if (monitoring_cb != NULL) { + monitoring_cb(); + } + } +} + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen * ts_curr) +{ + unsigned int ydiff1, ydiff2, ydiff3, xdiff1, xdiff2, xdiff3; + unsigned int sample_sumx, sample_sumy; + static unsigned int prev_x[FILTLEN], prev_y[FILTLEN]; + int index = 0; + unsigned int y_curr, x_curr; + static int filt_count = 0; + /* Added a variable filt_type to decide filtering at run-time */ + unsigned int filt_type = 0; + + if (ts_curr->contact_resistance == 0) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + filt_count = 0; + return 0; + } + + ydiff1 = abs(ts_curr->y_position1 - ts_curr->y_position2); + ydiff2 = abs(ts_curr->y_position2 - ts_curr->y_position3); + ydiff3 = abs(ts_curr->y_position1 - ts_curr->y_position3); + if ((ydiff1 > DELTA_Y_MAX) || + (ydiff2 > DELTA_Y_MAX) || (ydiff3 > DELTA_Y_MAX)) { + pr_debug("pmic_adc_filter: Ret pos 1\n"); + return -1; + } + + xdiff1 = abs(ts_curr->x_position1 - ts_curr->x_position2); + xdiff2 = abs(ts_curr->x_position2 - ts_curr->x_position3); + xdiff3 = abs(ts_curr->x_position1 - ts_curr->x_position3); + + if ((xdiff1 > DELTA_X_MAX) || + (xdiff2 > DELTA_X_MAX) || (xdiff3 > DELTA_X_MAX)) { + pr_debug("mc13783_adc_filter: Ret pos 2\n"); + return -1; + } + /* Compute two closer values among the three available Y readouts */ + + if (ydiff1 < ydiff2) { + if (ydiff1 < ydiff3) { + // Sample 0 & 1 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position2; + } else { + // Sample 0 & 2 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } else { + if (ydiff2 < ydiff3) { + // Sample 1 & 2 closest together + sample_sumy = ts_curr->y_position2 + + ts_curr->y_position3; + } else { + // Sample 0 & 2 closest together + sample_sumy = ts_curr->y_position1 + + ts_curr->y_position3; + } + } + + /* + * Compute two closer values among the three available X + * readouts + */ + if (xdiff1 < xdiff2) { + if (xdiff1 < xdiff3) { + // Sample 0 & 1 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position2; + } else { + // Sample 0 & 2 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } else { + if (xdiff2 < xdiff3) { + // Sample 1 & 2 closest together + sample_sumx = ts_curr->x_position2 + + ts_curr->x_position3; + } else { + // Sample 0 & 2 closest together + sample_sumx = ts_curr->x_position1 + + ts_curr->x_position3; + } + } + /* + * Wait FILTER_MIN_DELAY number of samples to restart + * filtering + */ + if (filt_count < FILTER_MIN_DELAY) { + /* + * Current output is the average of the two closer + * values and no filtering is used + */ + y_curr = (sample_sumy / 2); + x_curr = (sample_sumx / 2); + ts_curr->y_position = y_curr; + ts_curr->x_position = x_curr; + filt_count++; + } else { + if (abs(sample_sumx - (prev_x[0] + prev_x[1])) > + (DELTA_X_MAX * 16)) { + pr_debug("pmic_adc_filter: : Ret pos 3\n"); + return -1; + } + if (abs(sample_sumy - (prev_y[0] + prev_y[1])) > + (DELTA_Y_MAX * 16)) { + return -1; + } + sample_sumy /= 2; + sample_sumx /= 2; + /* Use hard filtering if the sample difference < 10 */ + if ((abs(sample_sumy - prev_y[0]) > 10) || + (abs(sample_sumx - prev_x[0]) > 10)) { + filt_type = 1; + } + + /* + * Current outputs are the average of three previous + * values and the present readout + */ + y_curr = sample_sumy; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + y_curr = y_curr + (prev_y[index]); + } else { + y_curr = y_curr + (prev_y[index] / 3); + } + } + if (filt_type == 0) { + y_curr = y_curr >> 2; + } else { + y_curr = y_curr >> 1; + } + ts_curr->y_position = y_curr; + + x_curr = sample_sumx; + for (index = 0; index < FILTLEN; index++) { + if (filt_type == 0) { + x_curr = x_curr + (prev_x[index]); + } else { + x_curr = x_curr + (prev_x[index] / 3); + } + } + if (filt_type == 0) { + x_curr = x_curr >> 2; + } else { + x_curr = x_curr >> 1; + } + ts_curr->x_position = x_curr; + + } + + /* Update previous X and Y values */ + for (index = (FILTLEN - 1); index > 0; index--) { + prev_x[index] = prev_x[index - 1]; + prev_y[index] = prev_y[index - 1]; + } + + /* + * Current output will be the most recent past for the + * next sample + */ + prev_y[0] = y_curr; + prev_x[0] = x_curr; + + return 0; +} + +/*! + * This function implements the open method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pr_debug("mc13783_adc : mc13783_adc_open()\n"); + return 0; +} + +/*! + * This function implements the release method on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_adc_free(struct inode *inode, struct file *file) +{ + pr_debug("mc13783_adc : mc13783_adc_free()\n"); + return 0; +} + +/*! + * This function initializes all ADC registers with default values. This + * function also registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +int pmic_adc_init(void) +{ + unsigned int reg_value = 0, i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + for (i = 0; i < ADC_NB_AVAILABLE; i++) { + adc_dev[i] = ADC_FREE; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_2, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_4, reg_value, PMIC_ALL_BITS)); + reg_value = 0x001000; + CHECK_ERROR(pmic_write_reg(REG_ARBITRATION_PERIPHERAL_AUDIO, reg_value, + 0xFFFFFF)); + + data_ready_adc_1 = false; + data_ready_adc_2 = false; + adc_ts = false; + wait_ts = false; + monitor_en = false; + monitor_adc = false; + wcomp_mode = CHECK_LOW; + monitoring_cb = NULL; + /* sub to ADCDone IT */ + event_adc.param = NULL; + event_adc.func = callback_adcdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCDONEI, event_adc)); + + /* sub to ADCDoneBis IT */ + event_adc_bis.param = NULL; + event_adc_bis.func = callback_adcbisdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCBISDONEI, event_adc_bis)); + + /* sub to Touch Screen IT */ + tsi_event.param = NULL; + tsi_event.func = callback_tsi; + CHECK_ERROR(pmic_event_subscribe(EVENT_TSI, tsi_event)); + + /* ADC reading above high limit */ + adc_comp_h.param = NULL; + adc_comp_h.func = callback_adc_comp_high; + CHECK_ERROR(pmic_event_subscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables the ADC, de-registers the interrupt events. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deinit(void) +{ + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCDONEI, event_adc)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCBISDONEI, event_adc_bis)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TSI, tsi_event)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_WHIGHI, adc_comp_h)); + + return PMIC_SUCCESS; +} + +/*! + * This function initializes adc_param structure. + * + * @param adc_param Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_param(t_adc_param * adc_param) +{ + int i = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + adc_param->delay = 0; + adc_param->conv_delay = false; + adc_param->single_channel = false; + adc_param->group = false; + adc_param->channel_0 = BATTERY_VOLTAGE; + adc_param->channel_1 = BATTERY_VOLTAGE; + adc_param->read_mode = 0; + adc_param->wait_tsi = 0; + adc_param->chrgraw_devide_5 = true; + adc_param->read_ts = false; + adc_param->ts_value.x_position = 0; + adc_param->ts_value.y_position = 0; + adc_param->ts_value.contact_resistance = 0; + for (i = 0; i <= MAX_CHANNEL; i++) { + adc_param->value[i] = 0; + } + return 0; +} + +/*! + * This function starts the convert. + * + * @param adc_param contains all adc configuration and return value. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_adc_convert(t_adc_param * adc_param) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, reg_1 = 0, result_reg = + 0, i = 0; + unsigned int result = 0, temp = 0; + pmic_version_t mc13783_ver; + pr_debug("mc13783 ADC - mc13783_adc_convert ....\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + + if (adc_param->wait_tsi) { + /* we need to set ADCEN 1 for TSI interrupt on mc13783 1.x */ + /* configure adc to wait tsi interrupt */ + INIT_COMPLETION(adc_tsi); + pr_debug("mc13783 ADC - pmic_write_reg ....\n"); + /*for ts don't use bis */ + adc_0_reg = 0x001c00 | (ADC_BIS * 0); + pmic_event_unmask(EVENT_TSI); + CHECK_ERROR(pmic_write_reg + (REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + /*for ts don't use bis */ + adc_1_reg = 0x200001 | (ADC_BIS * 0); + CHECK_ERROR(pmic_write_reg + (REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + pr_debug("wait tsi ....\n"); + wait_ts = true; + wait_for_completion_interruptible(&adc_tsi); + wait_ts = false; + } + if (adc_param->read_ts == false) + down(&convert_mutex); + use_bis = mc13783_adc_request(adc_param->read_ts); + if (use_bis < 0) { + pr_debug("process has received a signal and got interrupted\n"); + return -EINTR; + } + + /* CONFIGURE ADC REG 0 */ + adc_0_reg = 0; + adc_1_reg = 0; + if (adc_param->read_ts == false) { + adc_0_reg = adc_param->read_mode & 0x00003F; + /* add auto inc */ + adc_0_reg |= ADC_INC; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (adc_param->chrgraw_devide_5) { + adc_0_reg |= ADC_CHRGRAW_D5; + } + } + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + if (adc_param->conv_delay) { + adc_1_reg |= ADC_ATO; + } + + if (adc_param->group) { + adc_1_reg |= ADC_ADSEL; + } + + if (adc_param->single_channel) { + adc_1_reg |= ADC_SGL_CH; + } + + adc_1_reg |= (adc_param->channel_0 << ADC_CH_0_POS) & + ADC_CH_0_MASK; + adc_1_reg |= (adc_param->channel_1 << ADC_CH_1_POS) & + ADC_CH_1_MASK; + } else { + adc_0_reg = 0x003c00 | (ADC_BIS * use_bis) | ADC_INC; + } + pr_debug("Write Reg %i = %x\n", REG_ADC_0, adc_0_reg); + /*Change has been made here */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, + ADC_INC | ADC_BIS | ADC_CHRGRAW_D5 | + 0xfff00ff)); + /* CONFIGURE ADC REG 1 */ + if (adc_param->read_ts == false) { + adc_1_reg |= ADC_NO_ADTRIG; + adc_1_reg |= ADC_EN; + adc_1_reg |= (adc_param->delay << ADC_DELAY_POS) & + ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + } else { + /* configure and start convert to read x and y position */ + /* configure to read 2 value in channel selection 1 & 2 */ + adc_1_reg = 0x100409 | (ADC_BIS * use_bis) | ADC_NO_ADTRIG; + } + reg_1 = adc_1_reg; + if (use_bis == 0) { + data_ready_adc_1 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_1 = true; + pr_debug("Write Reg %i = %x\n", REG_ADC_1, adc_1_reg); + INIT_COMPLETION(adcdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, + ADC_SGL_CH | ADC_ATO | ADC_ADSEL + | ADC_CH_0_MASK | ADC_CH_1_MASK | + ADC_NO_ADTRIG | ADC_EN | + ADC_DELAY_MASK | ASC_ADC | ADC_BIS)); + pr_debug("wait adc done \n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC_3, temp, 0xFFFFFF)); + temp = 0x001000; + pmic_write_reg(REG_ARBITRATION_PERIPHERAL_AUDIO, temp, + 0xFFFFFF); + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + /* read result and store in adc_param */ + result = 0; + if (use_bis == 0) { + result_reg = REG_ADC_2; + } else { + result_reg = REG_ADC_4; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_1, 4 << ADC_CH_1_POS, + ADC_CH_0_MASK | ADC_CH_1_MASK)); + + for (i = 0; i <= 3; i++) { + CHECK_ERROR(pmic_read_reg(result_reg, &result, PMIC_ALL_BITS)); + pr_debug("result %i = %x\n", result_reg, result); + adc_param->value[i] = ((result & ADD1_RESULT_MASK) >> 2); + adc_param->value[i + 4] = ((result & ADD2_RESULT_MASK) >> 14); + } + if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.x_position1 = adc_param->value[0]; + adc_param->ts_value.x_position2 = adc_param->value[1]; + adc_param->ts_value.x_position3 = adc_param->value[2]; + adc_param->ts_value.y_position1 = adc_param->value[3]; + adc_param->ts_value.y_position2 = adc_param->value[4]; + adc_param->ts_value.y_position3 = adc_param->value[5]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + + } + + /*if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + } */ + mc13783_adc_release(use_bis); + if (adc_param->read_ts == false) + up(&convert_mutex); + + return PMIC_SUCCESS; +} + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel) +{ + t_reading_mode read_mode = 0; + + switch (channel) { + case LICELL: + read_mode = M_LITHIUM_CELL; + break; + case CHARGE_CURRENT: + read_mode = M_CHARGE_CURRENT; + break; + case BATTERY_CURRENT: + read_mode = M_BATTERY_CURRENT; + break; + case THERMISTOR: + read_mode = M_THERMISTOR; + break; + case DIE_TEMP: + read_mode = M_DIE_TEMPERATURE; + break; + case USB_ID: + read_mode = M_UID; + break; + default: + read_mode = 0; + } + + return read_mode; +} + +/*! + * This function triggers a conversion and returns one sampling result of one + * channel. + * + * @param channel The channel to be sampled + * @param result The pointer to the conversion result. The memory + * should be allocated by the caller of this function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + PMIC_STATUS ret; + + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert\n"); + adc_param.read_ts = false; + adc_param.read_mode = mc13783_set_read_mode(channel); + + adc_param.single_channel = true; + /* Find the group */ + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + ret = mc13783_adc_convert(&adc_param); + *result = adc_param.value[0]; + return ret; +} + +/*! + * This function triggers a conversion and returns eight sampling results of + * one channel. + * + * @param channel The channel to be sampled + * @param result The pointer to array to store eight sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + + channel = channel_num[channel]; + + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_8x\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13783_set_read_mode(channel); + if ((channel >= 0) && (channel <= 7)) { + adc_param.channel_0 = channel; + adc_param.channel_1 = channel; + adc_param.group = false; + } else if ((channel >= 8) && (channel <= 15)) { + adc_param.channel_0 = channel & 0x07; + adc_param.channel_1 = channel & 0x07; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + + ret = mc13783_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function triggers a conversion and returns sampling results of each + * specified channel. + * + * @param channels This input parameter is bitmap to specify channels + * to be sampled. + * @param result The pointer to array to store sampling results. + * The memory should be allocated by the caller of this + * function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_convert_multichnnel(t_channel channels, + unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) { + return -EBUSY; + } + mc13783_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_multichnnel\n"); + + channels = channel_num[channels]; + + if (channels == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + + adc_param.read_ts = false; + adc_param.single_channel = false; + if ((channels >= 0) && (channels <= 7)) { + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = false; + } else if ((channels >= 8) && (channels <= 15)) { + channels = channels & 0x07; + adc_param.channel_0 = channels; + adc_param.channel_1 = ((channels + 4) % 4) + 4; + adc_param.group = true; + } else { + return PMIC_PARAMETER_ERROR; + } + adc_param.read_mode = 0x00003f; + adc_param.read_ts = false; + ret = mc13783_adc_convert(&adc_param); + + for (i = 0; i <= 7; i++) { + result[i] = adc_param.value[i]; + } + return ret; +} + +/*! + * This function sets touch screen operation mode. + * + * @param touch_mode Touch screen operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode) +{ + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_write_reg(REG_ADC_0, + BITFVAL(MC13783_ADC0_TS_M, touch_mode), + BITFMASK(MC13783_ADC0_TS_M))); + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen operation mode. + * + * @param touch_mode Pointer to the retrieved touch screen operation + * mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode * touch_mode) +{ + unsigned int value; + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(REG_ADC_0, &value, PMIC_ALL_BITS)); + + *touch_mode = BITFEXT(value, MC13783_ADC0_TS_M); + + return PMIC_SUCCESS; +} + +/*! + * This function retrieves the current touch screen (X,Y) coordinates. + * + * @param touch_sample Pointer to touch sample. + * @param wait indicates whether this call must block or not. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen * touch_sample, int wait) +{ + if (mc13783_adc_read_ts(touch_sample, wait) != 0) + return PMIC_ERROR; + if (0 == pmic_adc_filter(touch_sample)) + return PMIC_SUCCESS; + else + return PMIC_ERROR; +} + +/*! + * This function read the touch screen value. + * + * @param ts_value return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen * ts_value, int wait_tsi) +{ + t_adc_param param; + pr_debug("mc13783_adc : mc13783_adc_read_ts\n"); + if (suspend_flag == 1) { + return -EBUSY; + } + if (wait_ts) { + pr_debug("mc13783_adc : error TS busy \n"); + return PMIC_ERROR; + } + mc13783_adc_init_param(¶m); + param.wait_tsi = wait_tsi; + param.read_ts = true; + if (mc13783_adc_convert(¶m) != 0) + return PMIC_ERROR; + /* check if x-y is ok */ + if ((param.ts_value.x_position1 < TS_X_MAX) && + (param.ts_value.x_position1 >= TS_X_MIN) && + (param.ts_value.y_position1 < TS_Y_MAX) && + (param.ts_value.y_position1 >= TS_Y_MIN) && + (param.ts_value.x_position2 < TS_X_MAX) && + (param.ts_value.x_position2 >= TS_X_MIN) && + (param.ts_value.y_position2 < TS_Y_MAX) && + (param.ts_value.y_position2 >= TS_Y_MIN) && + (param.ts_value.x_position3 < TS_X_MAX) && + (param.ts_value.x_position3 >= TS_X_MIN) && + (param.ts_value.y_position3 < TS_Y_MAX) && + (param.ts_value.y_position3 >= TS_Y_MIN)) { + ts_value->x_position = param.ts_value.x_position; + ts_value->x_position1 = param.ts_value.x_position1; + ts_value->x_position2 = param.ts_value.x_position2; + ts_value->x_position3 = param.ts_value.x_position3; + ts_value->y_position = param.ts_value.y_position; + ts_value->y_position1 = param.ts_value.y_position1; + ts_value->y_position2 = param.ts_value.y_position2; + ts_value->y_position3 = param.ts_value.y_position3; + ts_value->contact_resistance = + param.ts_value.contact_resistance + 1; + + } else { + ts_value->x_position = 0; + ts_value->y_position = 0; + ts_value->contact_resistance = 0; + + } + return PMIC_SUCCESS; +} + +/*! + * This function starts a Battery Current mode conversion. + * + * @param mode Conversion mode. + * @param result Battery Current measurement result. + * if \a mode = ADC_8CHAN_1X, the result is \n + * result[0] = (BATTP - BATT_I) \n + * if \a mode = ADC_1CHAN_8X, the result is \n + * result[0] = BATTP \n + * result[1] = BATT_I \n + * result[2] = BATTP \n + * result[3] = BATT_I \n + * result[4] = BATTP \n + * result[5] = BATT_I \n + * result[6] = BATTP \n + * result[7] = BATT_I + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_get_battery_current(t_conversion_mode mode, + unsigned short *result) +{ + PMIC_STATUS ret; + t_channel channel; + if (suspend_flag == 1) { + return -EBUSY; + } + channel = BATTERY_CURRENT; + if (mode == ADC_8CHAN_1X) { + ret = pmic_adc_convert(channel, result); + } else { + ret = pmic_adc_convert_8x(channel, result); + } + return ret; +} + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + return -1 if error. + */ +int mc13783_adc_request(bool read_ts) +{ + int adc_index = -1; + if (read_ts != 0) { + /*for ts we use bis=0 */ + if (adc_dev[0] == ADC_USED) + return -1; + /*no wait here */ + adc_dev[0] = ADC_USED; + adc_index = 0; + } else { + /*for other adc use bis = 1 */ + if (adc_dev[1] == ADC_USED) { + return -1; + /*no wait here */ + } + adc_dev[1] = ADC_USED; + adc_index = 1; + } + pr_debug("mc13783_adc : request ADC %d\n", adc_index); + return adc_index; +} + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + pr_debug("mc13783_adc : release ADC %d\n", adc_index); + if ((adc_dev[adc_index] == ADC_MONITORING) || + (adc_dev[adc_index] == ADC_USED)) { + adc_dev[adc_index] = ADC_FREE; + wake_up(&queue_adc_busy); + return 0; + } + return -1; +} + +/*! + * This function initializes monitoring structure. + * + * @param monitor Structure to be initialized. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_init_monitor_param(t_monitoring_param * monitor) +{ + pr_debug("mc13783_adc : init monitor\n"); + monitor->delay = 0; + monitor->conv_delay = false; + monitor->channel = BATTERY_VOLTAGE; + monitor->read_mode = 0; + monitor->comp_low = 0; + monitor->comp_high = 0; + monitor->group = 0; + monitor->check_mode = CHECK_LOW_OR_HIGH; + monitor->callback = NULL; + return 0; +} + +/*! + * This function actives the comparator. When comparator is active and ADC + * is enabled, the 8th converted value will be digitally compared against the + * window defined by WLOW and WHIGH registers. + * + * @param low Comparison window low threshold (WLOW). + * @param high Comparison window high threshold (WHIGH). + * @param channel The channel to be sampled + * @param callback Callback function to be called when the converted + * value is beyond the comparison window. The callback + * function will pass a parameter of type + * \b t_comp_expection to indicate the reason of + * comparator exception. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_active_comparator(unsigned char low, + unsigned char high, + t_channel channel, + t_comparator_cb callback) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, adc_3_reg = 0; + t_monitoring_param monitoring; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (monitor_en) { + pr_debug("mc13783_adc : monitoring already configured\n"); + return PMIC_ERROR; + } + monitor_en = true; + mc13783_adc_init_monitor_param(&monitoring); + monitoring.comp_low = low; + monitoring.comp_high = high; + monitoring.channel = channel; + monitoring.callback = (void *)callback; + + use_bis = mc13783_adc_request(false); + if (use_bis < 0) { + pr_debug("mc13783_adc : request error\n"); + return PMIC_ERROR; + } + monitor_adc = use_bis; + + adc_0_reg = 0; + + /* TO DO ADOUT CONFIGURE */ + adc_0_reg = monitoring.read_mode & ADC_MODE_MASK; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + adc_0_reg |= ADC_WCOMP; + + /* CONFIGURE ADC REG 1 */ + adc_1_reg = 0; + adc_1_reg |= ADC_EN; + if (monitoring.conv_delay) { + adc_1_reg |= ADC_ATO; + } + if (monitoring.group) { + adc_1_reg |= ADC_ADSEL; + } + adc_1_reg |= (monitoring.channel << ADC_CH_0_POS) & ADC_CH_0_MASK; + adc_1_reg |= (monitoring.delay << ADC_DELAY_POS) & ADC_DELAY_MASK; + if (use_bis) { + adc_1_reg |= ADC_BIS; + } + + adc_3_reg |= (monitoring.comp_high << ADC_WCOMP_H_POS) & + ADC_WCOMP_H_MASK; + adc_3_reg |= (monitoring.comp_low << ADC_WCOMP_L_POS) & + ADC_WCOMP_L_MASK; + if (use_bis) { + adc_3_reg |= ADC_BIS; + } + + wcomp_mode = monitoring.check_mode; + /* call back to be called when event is detected. */ + monitoring_cb = monitoring.callback; + + CHECK_ERROR(pmic_write_reg(REG_ADC_0, adc_0_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, adc_1_reg, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, adc_3_reg, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function deactivates the comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_adc_deactive_comparator(void) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (!monitor_en) { + pr_debug("mc13783_adc : adc monitoring free\n"); + return PMIC_ERROR; + } + + if (monitor_en) { + reg_value = ADC_BIS; + } + + /* clear all reg value */ + CHECK_ERROR(pmic_write_reg(REG_ADC_0, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_1, reg_value, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC_3, reg_value, PMIC_ALL_BITS)); + + reg_value = 0; + + if (monitor_adc) { + CHECK_ERROR(pmic_write_reg + (REG_ADC_4, reg_value, PMIC_ALL_BITS)); + } else { + CHECK_ERROR(pmic_write_reg + (REG_ADC_2, reg_value, PMIC_ALL_BITS)); + } + + mc13783_adc_release(monitor_adc); + monitor_en = false; + return PMIC_SUCCESS; +} + +/*! + * This function implements IOCTL controls on a MC13783 ADC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_adc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_adc_convert_param *convert_param; + t_touch_mode touch_mode; + t_touch_screen touch_sample; + unsigned short b_current; + t_adc_comp_param *comp_param; + if ((_IOC_TYPE(cmd) != 'p') && (_IOC_TYPE(cmd) != 'D')) + return -ENOTTY; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + switch (cmd) { + case PMIC_ADC_INIT: + pr_debug("init adc\n"); + CHECK_ERROR(pmic_adc_init()); + break; + + case PMIC_ADC_DEINIT: + pr_debug("deinit adc\n"); + CHECK_ERROR(pmic_adc_deinit()); + break; + + case PMIC_ADC_CONVERT: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_8X: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_convert_8x(convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_CONVERT_MULTICHANNEL: + if ((convert_param = kmalloc(sizeof(t_adc_convert_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(convert_param, (t_adc_convert_param *) arg, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_adc_convert_multichnnel + (convert_param->channel, + convert_param->result), + (kfree(convert_param))); + + if (copy_to_user((t_adc_convert_param *) arg, convert_param, + sizeof(t_adc_convert_param))) { + kfree(convert_param); + return -EFAULT; + } + kfree(convert_param); + break; + + case PMIC_ADC_SET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_set_touch_mode((t_touch_mode) arg)); + break; + + case PMIC_ADC_GET_TOUCH_MODE: + CHECK_ERROR(pmic_adc_get_touch_mode(&touch_mode)); + if (copy_to_user((t_touch_mode *) arg, &touch_mode, + sizeof(t_touch_mode))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_TOUCH_SAMPLE: + pr_debug("pmic_adc_ioctl: " "PMIC_ADC_GET_TOUCH_SAMPLE\n"); + CHECK_ERROR(pmic_adc_get_touch_sample(&touch_sample, 1)); + if (copy_to_user((t_touch_screen *) arg, &touch_sample, + sizeof(t_touch_screen))) { + return -EFAULT; + } + break; + + case PMIC_ADC_GET_BATTERY_CURRENT: + CHECK_ERROR(pmic_adc_get_battery_current(ADC_8CHAN_1X, + &b_current)); + if (copy_to_user((unsigned short *)arg, &b_current, + sizeof(unsigned short))) { + + return -EFAULT; + } + break; + + case PMIC_ADC_ACTIVATE_COMPARATOR: + if ((comp_param = kmalloc(sizeof(t_adc_comp_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(comp_param, (t_adc_comp_param *) arg, + sizeof(t_adc_comp_param))) { + kfree(comp_param); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_adc_active_comparator(comp_param->wlow, + comp_param->whigh, + comp_param-> + channel, + comp_param-> + callback), + (kfree(comp_param))); + break; + + case PMIC_ADC_DEACTIVE_COMPARATOR: + CHECK_ERROR(pmic_adc_deactive_comparator()); + break; + + default: + pr_debug("pmic_adc_ioctl: unsupported ioctl command 0x%x\n", + cmd); + return -EINVAL; + } + return 0; +} + +static struct file_operations mc13783_adc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_adc_ioctl, + .open = pmic_adc_open, + .release = pmic_adc_free, +}; + +static int pmic_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_adc_major = register_chrdev(0, "pmic_adc", &mc13783_adc_fops); + + if (pmic_adc_major < 0) { + pr_debug(KERN_ERR "Unable to get a major for pmic_adc\n"); + return pmic_adc_major; + } + init_waitqueue_head(&suspendq); + + pmic_adc_class = class_create(THIS_MODULE, "pmic_adc"); + if (IS_ERR(pmic_adc_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class.\n"); + ret = PTR_ERR(pmic_adc_class); + goto err_out1; + } + + temp_class = device_create(pmic_adc_class, NULL, + MKDEV(pmic_adc_major, 0), NULL, "pmic_adc"); + if (IS_ERR(temp_class)) { + pr_debug(KERN_ERR "Error creating pmic_adc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_adc_init(); + if (ret != PMIC_SUCCESS) { + pr_debug(KERN_ERR "Error in pmic_adc_init.\n"); + goto err_out4; + } + + pmic_adc_ready = 1; + pr_debug(KERN_INFO "PMIC ADC successfully probed\n"); + return ret; + + err_out4: + device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + err_out2: + class_destroy(pmic_adc_class); + err_out1: + unregister_chrdev(pmic_adc_major, "pmic_adc"); + return ret; +} + +static int pmic_adc_module_remove(struct platform_device *pdev) +{ + pmic_adc_ready = 0; + pmic_adc_deinit(); + device_destroy(pmic_adc_class, MKDEV(pmic_adc_major, 0)); + class_destroy(pmic_adc_class); + unregister_chrdev(pmic_adc_major, "pmic_adc"); + pr_debug(KERN_INFO "PMIC ADC successfully removed\n"); + return 0; +} + +static struct platform_driver pmic_adc_driver_ldm = { + .driver = { + .name = "pmic_adc", + }, + .suspend = pmic_adc_suspend, + .resume = pmic_adc_resume, + .probe = pmic_adc_module_probe, + .remove = pmic_adc_module_remove, +}; + +/* + * Initialization and Exit + */ +static int __init pmic_adc_module_init(void) +{ + pr_debug("PMIC ADC driver loading...\n"); + return platform_driver_register(&pmic_adc_driver_ldm); +} + +static void __exit pmic_adc_module_exit(void) +{ + platform_driver_unregister(&pmic_adc_driver_ldm); + pr_debug("PMIC ADC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_adc_module_init); +module_exit(pmic_adc_module_exit); + +MODULE_DESCRIPTION("PMIC ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_adc_defs.h b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h new file mode 100644 index 000000000000..db1b08232c77 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_adc_defs.h @@ -0,0 +1,321 @@ +/* + * Copyright 2004-2008 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 mc13783/pmic_adc_defs.h + * @brief This header contains all defines for PMIC(mc13783) ADC driver. + * + * @ingroup PMIC_ADC + */ + +#ifndef __MC13783_ADC__DEFS_H__ +#define __MC13783_ADC__DEFS_H__ + +#define MC13783_ADC_DEVICE "/dev/mc13783_adc" + +#define DEF_ADC_0 0x008000 +#define DEF_ADC_3 0x000080 + +#define ADC_NB_AVAILABLE 2 + +#define MAX_CHANNEL 7 + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch-screen + */ +#define DELTA_Y_MAX 50 +#define DELTA_X_MAX 50 + +/* Upon clearing the filter, this is the delay in restarting the filter */ +#define FILTER_MIN_DELAY 4 + +/* Length of X and Y Touch screen filters */ +#define FILTLEN 3 + +#define TS_X_MAX 1000 +#define TS_Y_MAX 1000 + +#define TS_X_MIN 80 +#define TS_Y_MIN 80 + +#define MC13783_ADC0_TS_M_LSH 14 +#define MC13783_ADC0_TS_M_WID 3 +/* + * ADC 0 + */ +#define ADC_WAIT_TSI_0 0x001C00 + +/* + * ADC 1 + */ + +#define ADC_EN 0x000001 +#define ADC_SGL_CH 0x000002 +#define ADC_ADSEL 0x000008 +#define ADC_CH_0_POS 5 +#define ADC_CH_0_MASK 0x0000E0 +#define ADC_CH_1_POS 8 +#define ADC_CH_1_MASK 0x000700 +#define ADC_DELAY_POS 11 +#define ADC_DELAY_MASK 0x07F800 +#define ADC_ATO 0x080000 +#define ASC_ADC 0x100000 +#define ADC_WAIT_TSI_1 0x300001 +#define ADC_CHRGRAW_D5 0x008000 + +/* + * ADC 2 - 4 + */ +#define ADD1_RESULT_MASK 0x00000FFC +#define ADD2_RESULT_MASK 0x00FFC000 +#define ADC_TS_MASK 0x00FFCFFC + +/* + * ADC 3 + */ +#define ADC_INC 0x030000 +#define ADC_BIS 0x800000 + +/* + * ADC 3 + */ +#define ADC_NO_ADTRIG 0x200000 +#define ADC_WCOMP 0x040000 +#define ADC_WCOMP_H_POS 0 +#define ADC_WCOMP_L_POS 9 +#define ADC_WCOMP_H_MASK 0x00003F +#define ADC_WCOMP_L_MASK 0x007E00 + +#define ADC_MODE_MASK 0x00003F + +/* + * Interrupt Status 0 + */ +#define ADC_INT_BISDONEI 0x02 + +/*! + * Define state mode of ADC. + */ +typedef enum adc_state { + /*! + * Free. + */ + ADC_FREE, + /*! + * Used. + */ + ADC_USED, + /*! + * Monitoring + */ + ADC_MONITORING, +} t_adc_state; + +/*! + * This enumeration, is used to configure the mode of ADC. + */ +typedef enum reading_mode { + /*! + * Enables lithium cell reading + */ + M_LITHIUM_CELL = 0x000001, + /*! + * Enables charge current reading + */ + M_CHARGE_CURRENT = 0x000002, + /*! + * Enables battery current reading + */ + M_BATTERY_CURRENT = 0x000004, + /*! + * Enables thermistor reading + */ + M_THERMISTOR = 0x000008, + /*! + * Enables die temperature reading + */ + M_DIE_TEMPERATURE = 0x000010, + /*! + * Enables UID reading + */ + M_UID = 0x000020, +} t_reading_mode; + +/*! + * This enumeration, is used to configure the monitoring mode. + */ +typedef enum check_mode { + /*! + * Comparator low level + */ + CHECK_LOW, + /*! + * Comparator high level + */ + CHECK_HIGH, + /*! + * Comparator low or high level + */ + CHECK_LOW_OR_HIGH, +} t_check_mode; + +/*! + * This structure is used to configure and report adc value. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Sets the single channel mode + */ + bool single_channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Channel selection 1 + */ + t_channel channel_0; + /*! + * Channel selection 2 + */ + t_channel channel_1; + /*! + * Used to configure ADC mode with t_reading_mode + */ + t_reading_mode read_mode; + /*! + * Sets the Touch screen mode + */ + bool read_ts; + /*! + * Wait TSI event before touch screen reading + */ + bool wait_tsi; + /*! + * Sets CHRGRAW scaling to divide by 5 + * Only supported on 2.0 and higher + */ + bool chrgraw_devide_5; + /*! + * Return ADC values + */ + unsigned int value[8]; + /*! + * Return touch screen values + */ + t_touch_screen ts_value; +} t_adc_param; + +/*! + * This structure is used to configure the monitoring mode of ADC. + */ +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Channel selection 1 + */ + t_channel channel; + /*! + * Selects the set of inputs + */ + bool group; + /*! + * Used to configure ADC mode with t_reading_mode + */ + unsigned int read_mode; + /*! + * Comparator low level in WCOMP mode + */ + unsigned int comp_low; + /*! + * Comparator high level in WCOMP mode + */ + unsigned int comp_high; + /*! + * Sets type of monitoring (low, high or both) + */ + t_check_mode check_mode; + /*! + * Callback to be launched when event is detected + */ + void (*callback) (void); +} t_monitoring_param; + +/*! + * This function performs filtering and rejection of excessive noise prone + * samples. + * + * @param ts_curr Touch screen value + * + * @return This function returns 0 on success, -1 otherwise. + */ +static int pmic_adc_filter(t_touch_screen * ts_curr); + +/*! + * This function request a ADC. + * + * @return This function returns index of ADC to be used (0 or 1) if successful. + return -1 if error. + */ +int mc13783_adc_request(bool read_ts); + +/*! + * This function is used to update buffer of touch screen value in read mode. + */ +void update_buffer(void); + +/*! + * This function release an ADC. + * + * @param adc_index index of ADC to be released. + * + * @return This function returns 0 if successful. + */ +int mc13783_adc_release(int adc_index); + +/*! + * This function select the required read_mode for a specific channel. + * + * @param channel The channel to be sampled + * + * @return This function returns the requires read_mode + */ +t_reading_mode mc13783_set_read_mode(t_channel channel); + +/*! + * This function read the touch screen value. + * + * @param touch_sample return value of touch screen + * @param wait_tsi if true, this function is synchronous (wait in TSI event). + * + * @return This function returns 0. + */ +PMIC_STATUS mc13783_adc_read_ts(t_touch_screen * touch_sample, int wait_tsi); + +#endif /* __MC13783_ADC__DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_audio.c b/drivers/mxc/pmic/mc13783/pmic_audio.c new file mode 100644 index 000000000000..6aab4b83f6ea --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_audio.c @@ -0,0 +1,5876 @@ +/* + * Copyright 2004-2009 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 mc13783/pmic_audio.c + * @brief Implementation of the PMIC(mc13783) Audio driver APIs. + * + * The PMIC Audio driver and this API were developed to support the + * audio playback, recording, and mixing capabilities of the power + * management ICs that are available from Freescale Semiconductor, Inc. + * + * The following operating modes are supported: + * + * @verbatim + Operating Mode mc13783 + ---------------------------- ------- + Stereo DAC Playback Yes + Stereo DAC Input Mixing Yes + Voice CODEC Playback Yes + Voice CODEC Input Mixing Yes + Voice CODEC Mono Recording Yes + Voice CODEC Stereo Recording Yes + Microphone Bias Control Yes + Output Amplifier Control Yes + Output Mixing Control Yes + Input Amplifier Control Yes + Master/Slave Mode Select Yes + Anti Pop Bias Circuit Control Yes + @endverbatim + * + * Note that the Voice CODEC may also be referred to as the Telephone + * CODEC in the PMIC DTS documentation. Also note that, while the power + * management ICs do provide similar audio capabilities, each PMIC may + * support additional configuration settings and features. Therefore, it + * is highly recommended that the appropriate power management IC DTS + * documents be used in conjunction with this API interface. + * + * @ingroup PMIC_AUDIO + */ + +#include <linux/module.h> +#include <linux/interrupt.h> /* For tasklet interface. */ +#include <linux/platform_device.h> /* For kernel module interface. */ +#include <linux/init.h> +#include <linux/spinlock.h> /* For spinlock interface. */ +#include <linux/pmic_adc.h> /* For PMIC ADC driver interface. */ +#include <linux/pmic_status.h> +#include <mach/pmic_audio.h> /* For PMIC Audio driver interface. */ + +/* + * mc13783 PMIC Audio API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(MIN_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(MAX_STDAC_SAMPLING_RATE_HZ); +EXPORT_SYMBOL(pmic_audio_open); +EXPORT_SYMBOL(pmic_audio_close); +EXPORT_SYMBOL(pmic_audio_set_protocol); +EXPORT_SYMBOL(pmic_audio_get_protocol); +EXPORT_SYMBOL(pmic_audio_enable); +EXPORT_SYMBOL(pmic_audio_disable); +EXPORT_SYMBOL(pmic_audio_reset); +EXPORT_SYMBOL(pmic_audio_reset_all); +EXPORT_SYMBOL(pmic_audio_set_callback); +EXPORT_SYMBOL(pmic_audio_clear_callback); +EXPORT_SYMBOL(pmic_audio_get_callback); +EXPORT_SYMBOL(pmic_audio_antipop_enable); +EXPORT_SYMBOL(pmic_audio_antipop_disable); +EXPORT_SYMBOL(pmic_audio_digital_filter_reset); +EXPORT_SYMBOL(pmic_audio_vcodec_set_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_get_clock); +EXPORT_SYMBOL(pmic_audio_vcodec_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_get_secondary_txslot); +EXPORT_SYMBOL(pmic_audio_vcodec_set_config); +EXPORT_SYMBOL(pmic_audio_vcodec_clear_config); +EXPORT_SYMBOL(pmic_audio_vcodec_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_bypass); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_bypass); +EXPORT_SYMBOL(pmic_audio_stdac_set_clock); +EXPORT_SYMBOL(pmic_audio_stdac_get_clock); +EXPORT_SYMBOL(pmic_audio_stdac_set_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_get_rxtx_timeslot); +EXPORT_SYMBOL(pmic_audio_stdac_set_config); +EXPORT_SYMBOL(pmic_audio_stdac_clear_config); +EXPORT_SYMBOL(pmic_audio_stdac_get_config); +EXPORT_SYMBOL(pmic_audio_input_set_config); +EXPORT_SYMBOL(pmic_audio_input_clear_config); +EXPORT_SYMBOL(pmic_audio_input_get_config); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic); +EXPORT_SYMBOL(pmic_audio_vcodec_set_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_get_mic_on_off); +EXPORT_SYMBOL(pmic_audio_vcodec_set_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_get_record_gain); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_micbias); +EXPORT_SYMBOL(pmic_audio_vcodec_enable_mixer); +EXPORT_SYMBOL(pmic_audio_vcodec_disable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_enable_mixer); +EXPORT_SYMBOL(pmic_audio_stdac_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_port); +EXPORT_SYMBOL(pmic_audio_output_get_port); +EXPORT_SYMBOL(pmic_audio_output_clear_port); +EXPORT_SYMBOL(pmic_audio_output_set_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_get_stereo_in_gain); +EXPORT_SYMBOL(pmic_audio_output_set_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_get_pgaGain); +EXPORT_SYMBOL(pmic_audio_output_enable_mixer); +EXPORT_SYMBOL(pmic_audio_output_disable_mixer); +EXPORT_SYMBOL(pmic_audio_output_set_balance); +EXPORT_SYMBOL(pmic_audio_output_get_balance); +EXPORT_SYMBOL(pmic_audio_output_enable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_disable_mono_adder); +EXPORT_SYMBOL(pmic_audio_output_set_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_get_mono_adder_gain); +EXPORT_SYMBOL(pmic_audio_output_set_config); +EXPORT_SYMBOL(pmic_audio_output_clear_config); +EXPORT_SYMBOL(pmic_audio_output_get_config); +EXPORT_SYMBOL(pmic_audio_output_enable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_output_disable_phantom_ground); +EXPORT_SYMBOL(pmic_audio_set_autodetect); +#ifdef DEBUG_AUDIO +EXPORT_SYMBOL(pmic_audio_dump_registers); +#endif /* DEBUG_AUDIO */ +/*! + * Define the minimum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MIN_STDAC_SAMPLING_RATE_HZ = 8000; + +/*! + * Define the maximum sampling rate (in Hz) that is supported by the + * Stereo DAC. + */ +const unsigned MAX_STDAC_SAMPLING_RATE_HZ = 96000; + +/*! @def SET_BITS + * Set a register field to a given value. + */ +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, field, value) (((value) & reg.field.mask) >> \ + reg.field.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_MC2BI, /*!< Microphone Bias 2 detect. */ + CORE_EVENT_HSDETI, /*!< Detect Headset attach */ + CORE_EVENT_HSLI, /*!< Detect Stereo Headset */ + CORE_EVENT_ALSPTHI, /*!< Detect Thermal shutdown of ALSP */ + CORE_EVENT_AHSSHORTI /*!< Detect Short circuit on AHS outputs */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure is used to track the state of a microphone input. + */ +typedef struct { + PMIC_AUDIO_INPUT_PORT mic; /*!< Microphone input port. */ + PMIC_AUDIO_INPUT_MIC_STATE micOnOff; /*!< Microphone On/Off state. */ + PMIC_AUDIO_MIC_AMP_MODE ampMode; /*!< Input amplifier mode. */ + PMIC_AUDIO_MIC_GAIN gain; /*!< Input amplifier gain level. */ +} PMIC_MICROPHONE_STATE; + +/*! + * @brief Tracks whether a headset is currently attached or not. + */ +typedef enum { + NO_HEADSET, /*!< No headset currently attached. */ + HEADSET_ON /*!< Headset has been attached. */ +} HEADSET_STATUS; + +/*! + * @brief mc13783 only enum that indicates the path to output taken + * by the voice codec output + */ +typedef enum { + VCODEC_DIRECT_OUT, /*!< Vcodec signal out direct */ + VCODEC_MIXER_OUT /*!< Output via the mixer */ +} PMIC_AUDIO_VCODEC_OUTPUT_PATH; + +/*! + * @brief This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /*!< Offset of LSB of register field. */ + const unsigned int mask; /*!< Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure lists all fields of the AUD_CODEC hardware register. + */ +typedef struct { + REGFIELD CDCSSISEL; /*!< codec SSI bus select */ + REGFIELD CDCCLKSEL; /*!< Codec clock input select */ + REGFIELD CDCSM; /*!< Codec slave / master select */ + REGFIELD CDCBCLINV; /*!< Codec bitclock inversion */ + REGFIELD CDCFSINV; /*!< Codec framesync inversion */ + REGFIELD CDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD CDCCLK; /*!< Codec clock setting - 3 bits */ + REGFIELD CDCFS8K16K; /*!< Codec framesync select */ + REGFIELD CDCEN; /*!< Codec enable */ + REGFIELD CDCCLKEN; /*!< Codec clocking enable */ + REGFIELD CDCTS; /*!< Codec SSI tristate */ + REGFIELD CDCDITH; /*!< Codec dithering */ + REGFIELD CDCRESET; /*!< Codec filter reset */ + REGFIELD CDCBYP; /*!< Codec bypass */ + REGFIELD CDCALM; /*!< Codec analog loopback */ + REGFIELD CDCDLM; /*!< Codec digital loopback */ + REGFIELD AUDIHPF; /*!< Transmit high pass filter enable */ + REGFIELD AUDOHPF; /*!< Receive high pass filter enable */ +} REGISTER_AUD_CODEC; + +/*! + * @brief This variable is used to access the AUD_CODEC hardware register. + * + * This variable defines how to access all of the fields within the + * AUD_CODEC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUD_CODEC regAUD_CODEC = { + {0, 0x000001}, /* CDCSSISEL */ + {1, 0x000002}, /* CDCCLKSEL */ + {2, 0x000004}, /* CDCSM */ + {3, 0x000008}, /* CDCBCLINV */ + {4, 0x000010}, /* CDCFSINV */ + {5, 0x000060}, /* CDCFS */ + {7, 0x000380}, /* CDCCLK */ + {10, 0x000400}, /* CDCFS8K16K */ + {11, 0x000800}, /* CDCEN */ + {12, 0x001000}, /* CDCCLKEN */ + {13, 0x002000}, /* CDCTS */ + {14, 0x004000}, /* CDCDITH */ + {15, 0x008000}, /* CDCRESET */ + {16, 0x010000}, /* CDCBYP */ + {17, 0x020000}, /* CDCALM */ + {18, 0x040000}, /* CDCDLM */ + {19, 0x080000}, /* AUDIHPF */ + {20, 0x100000} /* AUDOHPF */ + /* Unused */ + /* Unused */ + /* Unused */ + +}; + +/*! + * @brief This structure lists all fields of the ST_DAC hardware register. + */ + /* VVV */ +typedef struct { + REGFIELD STDCSSISEL; /*!< Stereo DAC SSI bus select */ + REGFIELD STDCCLKSEL; /*!< Stereo DAC clock input select */ + REGFIELD STDCSM; /*!< Stereo DAC slave / master select */ + REGFIELD STDCBCLINV; /*!< Stereo DAC bitclock inversion */ + REGFIELD STDCFSINV; /*!< Stereo DAC framesync inversion */ + REGFIELD STDCFS; /*!< Bus protocol selection - 2 bits */ + REGFIELD STDCCLK; /*!< Stereo DAC clock setting - 3 bits */ + REGFIELD STDCFSDLYB; /*!< Stereo DAC framesync delay bar */ + REGFIELD STDCEN; /*!< Stereo DAC enable */ + REGFIELD STDCCLKEN; /*!< Stereo DAC clocking enable */ + REGFIELD STDCRESET; /*!< Stereo DAC filter reset */ + REGFIELD SPDIF; /*!< Stereo DAC SSI SPDIF mode. Mode no longer available. */ + REGFIELD SR; /*!< Stereo DAC sample rate - 4 bits */ +} REGISTER_ST_DAC; + +/*! + * @brief This variable is used to access the ST_DAC hardware register. + * + * This variable defines how to access all of the fields within the + * ST_DAC hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_ST_DAC regST_DAC = { + {0, 0x000001}, /* STDCSSISEL */ + {1, 0x000002}, /* STDCCLKSEL */ + {2, 0x000004}, /* STDCSM */ + {3, 0x000008}, /* STDCBCLINV */ + {4, 0x000010}, /* STDCFSINV */ + {5, 0x000060}, /* STDCFS */ + {7, 0x000380}, /* STDCCLK */ + {10, 0x000400}, /* STDCFSDLYB */ + {11, 0x000800}, /* STDCEN */ + {12, 0x001000}, /* STDCCLKEN */ + {15, 0x008000}, /* STDCRESET */ + {16, 0x010000}, /* SPDIF */ + {17, 0x1E0000} /* SR */ +}; + +/*! + * @brief This structure lists all of the fields in the SSI_NETWORK hardware register. + */ +typedef struct { + REGFIELD CDCTXRXSLOT; /*!< Codec timeslot assignment - 2 bits */ + REGFIELD CDCTXSECSLOT; /*!< Codec secondary transmit timeslot - 2 bits */ + REGFIELD CDCRXSECSLOT; /*!< Codec secondary receive timeslot - 2 bits */ + REGFIELD CDCRXSECGAIN; /*!< Codec secondary receive channel gain setting - 2 bits */ + REGFIELD CDCSUMGAIN; /*!< Codec summed receive signal gain setting */ + REGFIELD CDCFSDLY; /*!< Codec framesync delay */ + REGFIELD STDCSLOTS; /*!< Stereo DAC number of timeslots select - 2 bits */ + REGFIELD STDCRXSLOT; /*!< Stereo DAC timeslot assignment - 2 bits */ + REGFIELD STDCRXSECSLOT; /*!< Stereo DAC secondary receive timeslot - 2 bits */ + REGFIELD STDCRXSECGAIN; /*!< Stereo DAC secondary receive channel gain setting - 2 bits */ + REGFIELD STDCSUMGAIN; /*!< Stereo DAC summed receive signal gain setting */ +} REGISTER_SSI_NETWORK; + +/*! + * @brief This variable is used to access the SSI_NETWORK hardware register. + * + * This variable defines how to access all of the fields within the + * SSI_NETWORK hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_SSI_NETWORK regSSI_NETWORK = { + {2, 0x00000c}, /* CDCTXRXSLOT */ + {4, 0x000030}, /* CDCTXSECSLOT */ + {6, 0x0000c0}, /* CDCRXSECSLOT */ + {8, 0x000300}, /* CDCRXSECGAIN */ + {10, 0x000400}, /* CDCSUMGAIN */ + {11, 0x000800}, /* CDCFSDLY */ + {12, 0x003000}, /* STDCSLOTS */ + {14, 0x00c000}, /* STDCRXSLOT */ + {16, 0x030000}, /* STDCRXSECSLOT */ + {18, 0x0c0000}, /* STDCRXSECGAIN */ + {20, 0x100000} /* STDCSUMGAIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_TX hardware register. + * + * + */ +typedef struct { + REGFIELD MC1BEN; /*!< Microphone bias 1 enable */ + REGFIELD MC2BEN; /*!< Microphone bias 2 enable */ + REGFIELD MC2BDETDBNC; /*!< Microphone bias detect debounce setting */ + REGFIELD MC2BDETEN; /*!< Microphone bias 2 detect enable */ + REGFIELD AMC1REN; /*!< Amplifier Amc1R enable */ + REGFIELD AMC1RITOV; /*!< Amplifier Amc1R current to voltage mode enable */ + REGFIELD AMC1LEN; /*!< Amplifier Amc1L enable */ + REGFIELD AMC1LITOV; /*!< Amplifier Amc1L current to voltage mode enable */ + REGFIELD AMC2EN; /*!< Amplifier Amc2 enable */ + REGFIELD AMC2ITOV; /*!< Amplifier Amc2 current to voltage mode enable */ + REGFIELD ATXINEN; /*!< Amplifier Atxin enable */ + REGFIELD ATXOUTEN; /*!< Reserved for output TXOUT enable, currently not used */ + REGFIELD RXINREC; /*!< RXINR/RXINL to voice CODEC ADC routing enable */ + REGFIELD PGATXR; /*!< Transmit gain setting right - 5 bits */ + REGFIELD PGATXL; /*!< Transmit gain setting left - 5 bits */ +} REGISTER_AUDIO_TX; + +/*! + * @brief This variable is used to access the AUDIO_TX hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_TX hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_TX regAUDIO_TX = { + {0, 0x000001}, /* MC1BEN */ + {1, 0x000002}, /* MC2BEN */ + {2, 0x000004}, /* MC2BDETDBNC */ + {3, 0x000008}, /* MC2BDETEN */ + {5, 0x000020}, /* AMC1REN */ + {6, 0x000040}, /* AMC1RITOV */ + {7, 0x000080}, /* AMC1LEN */ + {8, 0x000100}, /* AMC1LITOV */ + {9, 0x000200}, /* AMC2EN */ + {10, 0x000400}, /* AMC2ITOV */ + {11, 0x000800}, /* ATXINEN */ + {12, 0x001000}, /* ATXOUTEN */ + {13, 0x002000}, /* RXINREC */ + {14, 0x07c000}, /* PGATXR */ + {19, 0xf80000} /* PGATXL */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_0 hardware register. + */ +typedef struct { + REGFIELD VAUDIOON; /*!< Forces VAUDIO in active on mode */ + REGFIELD BIASEN; /*!< Audio bias enable */ + REGFIELD BIASSPEED; /*!< Turn on ramp speed of the audio bias */ + REGFIELD ASPEN; /*!< Amplifier Asp enable */ + REGFIELD ASPSEL; /*!< Asp input selector */ + REGFIELD ALSPEN; /*!< Amplifier Alsp enable */ + REGFIELD ALSPREF; /*!< Bias Alsp at common audio reference */ + REGFIELD ALSPSEL; /*!< Alsp input selector */ + REGFIELD LSPLEN; /*!< Output LSPL enable */ + REGFIELD AHSREN; /*!< Amplifier AhsR enable */ + REGFIELD AHSLEN; /*!< Amplifier AhsL enable */ + REGFIELD AHSSEL; /*!< Ahsr and Ahsl input selector */ + REGFIELD HSPGDIS; /*!< Phantom ground disable */ + REGFIELD HSDETEN; /*!< Headset detect enable */ + REGFIELD HSDETAUTOB; /*!< Amplifier state determined by headset detect */ + REGFIELD ARXOUTREN; /*!< Output RXOUTR enable */ + REGFIELD ARXOUTLEN; /*!< Output RXOUTL enable */ + REGFIELD ARXOUTSEL; /*!< Arxout input selector */ + REGFIELD CDCOUTEN; /*!< Output CDCOUT enable */ + REGFIELD HSLDETEN; /*!< Headset left channel detect enable */ + REGFIELD ADDCDC; /*!< Adder channel codec selection */ + REGFIELD ADDSTDC; /*!< Adder channel stereo DAC selection */ + REGFIELD ADDRXIN; /*!< Adder channel line in selection */ +} REGISTER_AUDIO_RX_0; + +/*! + * @brief This variable is used to access the AUDIO_RX_0 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_0 regAUDIO_RX_0 = { + {0, 0x000001}, /* VAUDIOON */ + {1, 0x000002}, /* BIASEN */ + {2, 0x000004}, /* BIASSPEED */ + {3, 0x000008}, /* ASPEN */ + {4, 0x000010}, /* ASPSEL */ + {5, 0x000020}, /* ALSPEN */ + {6, 0x000040}, /* ALSPREF */ + {7, 0x000080}, /* ALSPSEL */ + {8, 0x000100}, /* LSPLEN */ + {9, 0x000200}, /* AHSREN */ + {10, 0x000400}, /* AHSLEN */ + {11, 0x000800}, /* AHSSEL */ + {12, 0x001000}, /* HSPGDIS */ + {13, 0x002000}, /* HSDETEN */ + {14, 0x004000}, /* HSDETAUTOB */ + {15, 0x008000}, /* ARXOUTREN */ + {16, 0x010000}, /* ARXOUTLEN */ + {17, 0x020000}, /* ARXOUTSEL */ + {18, 0x040000}, /* CDCOUTEN */ + {19, 0x080000}, /* HSLDETEN */ + {21, 0x200000}, /* ADDCDC */ + {22, 0x400000}, /* ADDSTDC */ + {23, 0x800000} /* ADDRXIN */ +}; + +/*! + * @brief This structure lists all fields of the AUDIO_RX_1 hardware register. + */ +typedef struct { + REGFIELD PGARXEN; /*!< Codec receive PGA enable */ + REGFIELD PGARX; /*!< Codec receive gain setting - 4 bits */ + REGFIELD PGASTEN; /*!< Stereo DAC PGA enable */ + REGFIELD PGAST; /*!< Stereo DAC gain setting - 4 bits */ + REGFIELD ARXINEN; /*!< Amplifier Arx enable */ + REGFIELD ARXIN; /*!< Amplifier Arx additional gain setting */ + REGFIELD PGARXIN; /*!< PGArxin gain setting - 4 bits */ + REGFIELD MONO; /*!< Mono adder setting - 2 bits */ + REGFIELD BAL; /*!< Balance control - 3 bits */ + REGFIELD BALLR; /*!< Left / right balance */ +} REGISTER_AUDIO_RX_1; + +/*! + * @brief This variable is used to access the AUDIO_RX_1 hardware register. + * + * This variable defines how to access all of the fields within the + * AUDIO_RX_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const REGISTER_AUDIO_RX_1 regAUDIO_RX_1 = { + {0, 0x000001}, /* PGARXEN */ + {1, 0x00001e}, /* PGARX */ + {5, 0x000020}, /* PGASTEN */ + {6, 0x0003c0}, /* PGAST */ + {10, 0x000400}, /* ARXINEN */ + {11, 0x000800}, /* ARXIN */ + {12, 0x00f000}, /* PGARXIN */ + {16, 0x030000}, /* MONO */ + {18, 0x1c0000}, /* BAL */ + {21, 0x200000} /* BALLR */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Reset value for the AUD_CODEC register. */ +static const unsigned int RESET_AUD_CODEC = 0x180027; + +/*! Reset value for the ST_DAC register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_ST_DAC = 0x0E0004; + +/*! Reset value for the SSI_NETWORK register. */ +static const unsigned int RESET_SSI_NETWORK = 0x013060; + +/*! Reset value for the AUDIO_TX register. + * + * Note that we avoid resetting any of the arbitration bits. + */ +static const unsigned int RESET_AUDIO_TX = 0x420000; + +/*! Reset value for the AUDIO_RX_0 register. */ +static const unsigned int RESET_AUDIO_RX_0 = 0x001000; + +/*! Reset value for the AUDIO_RX_1 register. */ +static const unsigned int RESET_AUDIO_RX_1 = 0x00D35A; + +/*! Reset mask for the SSI network Vcodec part. first 12 bits + * 0 - 11 */ +static const unsigned int REG_SSI_VCODEC_MASK = 0x000fff; + +/*! Reset mask for the SSI network STDAC part. last 12 bits + * 12 - 24 */ +static const unsigned int REG_SSI_STDAC_MASK = 0xfff000; + +/*! Constant NULL value for initializing/reseting the audio handles. */ +static const PMIC_AUDIO_HANDLE AUDIO_HANDLE_NULL = (PMIC_AUDIO_HANDLE) NULL; + +/*! + * @brief This structure maintains the current state of the Stereo DAC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Stereo DAC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Stereo DAC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Stereo DAC clock input + source select. */ + PMIC_AUDIO_STDAC_SAMPLING_RATE samplingRate; /*!< Stereo DAC sampling rate + select. */ + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ clockFreq; /*!< Stereo DAC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Stereo DAC clock signal + invert select. */ + PMIC_AUDIO_STDAC_TIMESLOTS timeslot; /*!< Stereo DAC data + timeslots select. */ + PMIC_AUDIO_STDAC_CONFIG config; /*!< Stereo DAC configuration + options. */ +} PMIC_AUDIO_STDAC_STATE; + +/*! + * @brief This variable maintains the current state of the Stereo DAC. + * + * This variable tracks the current state of the Stereo DAC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Stereo DAC. + */ +static PMIC_AUDIO_STDAC_STATE stDAC = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_1, /* busID */ + false, + NORMAL_MSB_JUSTIFIED_MODE, /* protocol */ + BUS_MASTER_MODE, /* masterSlave */ + USE_2_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIA, /* clockIn */ + STDAC_RATE_44_1_KHZ, /* samplingRate */ + STDAC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0_TS1, /* timeslot */ + (PMIC_AUDIO_STDAC_CONFIG) 0 /* config */ +}; + +/*! + * @brief This structure maintains the current state of the Voice CODEC. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access + the Voice CODEC. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_DATA_BUS busID; /*!< Data bus used to access + the Voice CODEC. */ + bool protocol_set; + PMIC_AUDIO_BUS_PROTOCOL protocol; /*!< Data bus protocol. */ + PMIC_AUDIO_BUS_MODE masterSlave; /*!< Master/Slave mode + select. */ + PMIC_AUDIO_NUMSLOTS numSlots; /*!< Number of timeslots + used. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification + callback function + pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification + mask. */ + PMIC_AUDIO_CLOCK_IN_SOURCE clockIn; /*!< Voice CODEC clock input + source select. */ + PMIC_AUDIO_VCODEC_SAMPLING_RATE samplingRate; /*!< Voice CODEC sampling + rate select. */ + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ clockFreq; /*!< Voice CODEC clock input + frequency. */ + PMIC_AUDIO_CLOCK_INVERT invert; /*!< Voice CODEC clock + signal invert select. */ + PMIC_AUDIO_VCODEC_TIMESLOT timeslot; /*!< Voice CODEC data + timeslot select. */ + PMIC_AUDIO_VCODEC_TIMESLOT secondaryTXtimeslot; + + PMIC_AUDIO_VCODEC_CONFIG config; /*!< Voice CODEC + configuration + options. */ + PMIC_MICROPHONE_STATE leftChannelMic; /*!< Left channel + microphone + configuration. */ + PMIC_MICROPHONE_STATE rightChannelMic; /*!< Right channel + microphone + configuration. */ +} PMIC_AUDIO_VCODEC_STATE; + +/*! + * @brief This variable maintains the current state of the Voice CODEC. + * + * This variable tracks the current state of the Voice CODEC audio hardware + * along with any information that is required by the device driver to + * manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the Voice CODEC. + */ +static PMIC_AUDIO_VCODEC_STATE vCodec = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + AUDIO_DATA_BUS_2, /* busID */ + false, + NETWORK_MODE, /* protocol */ + BUS_SLAVE_MODE, /* masterSlave */ + USE_4_TIMESLOTS, /* numSlots */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + CLOCK_IN_CLIB, /* clockIn */ + VCODEC_RATE_8_KHZ, /* samplingRate */ + VCODEC_CLI_13MHZ, /* clockFreq */ + NO_INVERT, /* invert */ + USE_TS0, /* timeslot pri */ + USE_TS2, /* timeslot sec TX */ + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER, /* config */ + /* leftChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + }, + /* rightChannelMic */ + {NO_MIC, /* mic */ + MICROPHONE_OFF, /* micOnOff */ + AMP_OFF, /* ampMode */ + MIC_GAIN_0DB /* gain */ + } +}; + +/*! + * @brief This maintains the current state of the External Stereo Input. + */ +typedef struct { + PMIC_AUDIO_HANDLE handle; /*!< Handle used to access the + External Stereo Inputs. */ + HANDLE_STATE handleState; /*!< Current handle state. */ + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ + PMIC_AUDIO_STEREO_IN_GAIN inputGain; /*!< External Stereo Input + amplifier gain level. */ +} PMIC_AUDIO_EXT_STEREO_IN_STATE; + +/*! + * @brief This maintains the current state of the External Stereo Input. + * + * This variable tracks the current state of the External Stereo Input audio + * hardware along with any information that is required by the device driver + * to manage the hardware (e.g., callback functions and event notification + * masks). + * + * The initial values represent the reset/power on state of the External + * Stereo Input. + */ +static PMIC_AUDIO_EXT_STEREO_IN_STATE extStereoIn = { + (PMIC_AUDIO_HANDLE) NULL, /* handle */ + HANDLE_FREE, /* handleState */ + (PMIC_AUDIO_CALLBACK) NULL, /* callback */ + (PMIC_AUDIO_EVENTS) NULL, /* eventMask */ + STEREO_IN_GAIN_0DB /* inputGain */ +}; + +/*! + * @brief This maintains the current state of the callback & Eventmask. + */ +typedef struct { + PMIC_AUDIO_CALLBACK callback; /*!< Event notification callback + function pointer. */ + PMIC_AUDIO_EVENTS eventMask; /*!< Event notification mask. */ +} PMIC_AUDIO_EVENT_STATE; + +static PMIC_AUDIO_EVENT_STATE event_state = { + (PMIC_AUDIO_CALLBACK) NULL, /*Callback */ + (PMIC_AUDIO_EVENTS) NULL, /* EventMask */ + +}; + +/*! + * @brief This maintains the current state of the Audio Output Section. + */ +typedef struct { + PMIC_AUDIO_OUTPUT_PORT outputPort; /*!< Current audio + output port. */ + PMIC_AUDIO_OUTPUT_PGA_GAIN vCodecoutputPGAGain; /*!< Output PGA gain + level codec */ + PMIC_AUDIO_OUTPUT_PGA_GAIN stDacoutputPGAGain; /*!< Output PGA gain + level stDAC */ + PMIC_AUDIO_OUTPUT_PGA_GAIN extStereooutputPGAGain; /*!< Output PGA gain + level stereo ext */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceLeftGain; /*!< Left channel + balance gain + level. */ + PMIC_AUDIO_OUTPUT_BALANCE_GAIN balanceRightGain; /*!< Right channel + balance gain + level. */ + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN monoAdderGain; /*!< Mono adder gain + level. */ + PMIC_AUDIO_OUTPUT_CONFIG config; /*!< Audio output + section config + options. */ + PMIC_AUDIO_VCODEC_OUTPUT_PATH vCodecOut; + +} PMIC_AUDIO_AUDIO_OUTPUT_STATE; + +/*! + * @brief This variable maintains the current state of the Audio Output Section. + * + * This variable tracks the current state of the Audio Output Section. + * + * The initial values represent the reset/power on state of the Audio + * Output Section. + */ +static PMIC_AUDIO_AUDIO_OUTPUT_STATE audioOutput = { + (PMIC_AUDIO_OUTPUT_PORT) NULL, /* outputPort */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + OUTPGA_GAIN_0DB, /* outputPGAGain */ + BAL_GAIN_0DB, /* balanceLeftGain */ + BAL_GAIN_0DB, /* balanceRightGain */ + MONOADD_GAIN_0DB, /* monoAdderGain */ + (PMIC_AUDIO_OUTPUT_CONFIG) 0, /* config */ + VCODEC_DIRECT_OUT +}; + +/*! The current headset status. */ +static HEADSET_STATUS headsetState = NO_HEADSET; + +/* Removed PTT variable */ +/*! Define a 1 ms wait interval that is needed to ensure that certain + * hardware operations are successfully completed. + */ +static const unsigned long delay_1ms = (HZ / 1000); + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock whenever we do need to provide mutual + * exclusion while possibly executing in a hardware interrupt context. + * Spinlocks should be held for the minimum time that is necessary + * because hardware interrupts are disabled while a spinlock is held. + * + */ + +static spinlock_t lock = SPIN_LOCK_UNLOCKED; +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the audio data structures are consistent at all times + * when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the audio driver. Note that access to this + * variable may occur while within an interrupt context and, therefore, + * must be guarded by using a spinlock. + */ +/* static PMIC_CORE_EVENT eventID = 0; */ + +/* Prototypes for all static audio driver functions. */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void); +static PMIC_STATUS pmic_audio_mic_boost_disable(void);*/ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle); +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle); + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask); + +/************************************************************************* + * Audio device access APIs. + ************************************************************************* + */ + +/*! + * @name General Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Audio + * hardware. + */ +/*@{*/ + +PMIC_STATUS pmic_audio_set_autodetect(int val) +{ + PMIC_STATUS status; + unsigned int reg_mask = 0, reg_write = 0; + reg_mask = SET_BITS(regAUDIO_RX_0, VAUDIOON, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_mask, reg_mask); + if (status != PMIC_SUCCESS) + return status; + reg_mask = 0; + if (val == 1) { + reg_write = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + } else { + reg_write = 0; + } + reg_mask = + SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | SET_BITS(regAUDIO_RX_0, + HSDETAUTOB, 1); + status = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return status; +} + +/*! + * @brief Request exclusive access to the PMIC Audio hardware. + * + * Attempt to open and gain exclusive access to a key PMIC audio hardware + * component (e.g., the Stereo DAC or the Voice CODEC). Depending upon the + * type of audio operation that is desired and the nature of the audio data + * stream, the Stereo DAC and/or the Voice CODEC will be a required hardware + * component and needs to be acquired by calling this function. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls to complete + * the configuration of either the Stereo DAC or the Voice CODEC and along + * with any other associated audio hardware components that will be needed. + * + * The same handle must also be used in the close call when use of the PMIC + * audio hardware is no longer required. + * + * The open request will fail if the requested audio hardware component has + * already been acquired by a previous open call but not yet closed. + * + * @param handle Device handle to be used for subsequent PMIC + * audio API calls. + * @param device The required PMIC audio hardware component. + * + * @retval PMIC_SUCCESS If the open request was successful + * @retval PMIC_PARAMETER_ERROR If the handle argument is NULL. + * @retval PMIC_ERROR If the audio hardware component is + * unavailable. + */ +PMIC_STATUS pmic_audio_open(PMIC_AUDIO_HANDLE * const handle, + const PMIC_AUDIO_SOURCE device) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_AUDIO_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_PARAMETER_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_audio_open() are made since we can only allow + * one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + + if ((device == STEREO_DAC) && (stDAC.handleState == HANDLE_FREE)) { + stDAC.handle = (PMIC_AUDIO_HANDLE) (&stDAC); + stDAC.handleState = HANDLE_IN_USE; + *handle = stDAC.handle; + rc = PMIC_SUCCESS; + } else if ((device == VOICE_CODEC) + && (vCodec.handleState == HANDLE_FREE)) { + vCodec.handle = (PMIC_AUDIO_HANDLE) (&vCodec); + vCodec.handleState = HANDLE_IN_USE; + *handle = vCodec.handle; + rc = PMIC_SUCCESS; + } else if ((device == EXTERNAL_STEREO_IN) && + (extStereoIn.handleState == HANDLE_FREE)) { + extStereoIn.handle = (PMIC_AUDIO_HANDLE) (&extStereoIn); + extStereoIn.handleState = HANDLE_IN_USE; + *handle = extStereoIn.handle; + rc = PMIC_SUCCESS; + } else { + *handle = AUDIO_HANDLE_NULL; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Terminate further access to the PMIC audio hardware. + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_close(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* We can now call pmic_audio_close_handle() to actually do the work. */ + rc = pmic_audio_close_handle(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure the data bus protocol to be used. + * + * Provide the parameters needed to properly configure the audio data bus + * protocol so that data can be read/written to either the Stereo DAC or + * the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID Select data bus to be used. + * @param protocol Select the data bus protocol. + * @param masterSlave Select the data bus timing mode. + * @param numSlots Define the number of timeslots (only if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful configured. + * @retval PMIC_PARAMETER_ERROR If the handle or the protocol parameters + * are invalid. + */ +PMIC_STATUS pmic_audio_set_protocol(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_DATA_BUS busID, + const PMIC_AUDIO_BUS_PROTOCOL protocol, + const PMIC_AUDIO_BUS_MODE masterSlave, + const PMIC_AUDIO_NUMSLOTS numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int ST_DAC_MASK = SET_BITS(regST_DAC, STDCSSISEL, 1) | + SET_BITS(regST_DAC, STDCFS, 3) | SET_BITS(regST_DAC, STDCSM, 1); + + unsigned int reg_mask; + /*unsigned int VCODEC_MASK = SET_BITS(regAUD_CODEC, CDCSSISEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS, 3) | SET_BITS(regAUD_CODEC, CDCSM, 1); */ + + unsigned int SSI_NW_MASK = SET_BITS(regSSI_NETWORK, STDCSLOTS, 1); + unsigned int reg_value = 0; + unsigned int ssi_nw_value = 0; + + /* Enter a critical section so that we can ensure only one + * state change request is completed at a time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else { + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((stDAC.handleState == HANDLE_IN_USE) && + (stDAC.busID == busID) && (stDAC.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((masterSlave == BUS_MASTER_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("mc13783 supports only 4 slots in Master mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if ((masterSlave == BUS_SLAVE_MODE) + && (numSlots != USE_4_TIMESLOTS)) { + pr_debug + ("Driver currently supports only 4 slots in Slave mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else if (!((protocol == NETWORK_MODE) || + (protocol == I2S_MODE))) { + pr_debug + ("mc13783 Voice codec works only in Network and I2S modes\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Voice Codec\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSSISEL, + 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSSISEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (masterSlave == BUS_MASTER_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCSM, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCSM, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + if (protocol == NETWORK_MODE) { + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 1); + } else { /* protocol == I2S, other options have been already eliminated */ + reg_value = + SET_BITS(regAUD_CODEC, CDCFS, 2); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS, 3); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + ssi_nw_value = + SET_BITS(regSSI_NETWORK, CDCFSDLY, 1); + /*if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + VCODEC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { */ + vCodec.busID = busID; + vCodec.protocol = protocol; + vCodec.masterSlave = masterSlave; + vCodec.numSlots = numSlots; + vCodec.protocol_set = true; + //pmic_write_reg(REG_AUDIO_SSI_NETWORK, ssi_nw_value, ssi_nw_value); + + pr_debug + ("mc13783 Voice codec successfully configured\n"); + rc = PMIC_SUCCESS; + //} + + } + + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((vCodec.handleState == HANDLE_IN_USE) && + (vCodec.busID == busID) && (vCodec.protocol_set)) { + pr_debug("The requested bus already in USE\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (((protocol == NORMAL_MSB_JUSTIFIED_MODE) || + (protocol == I2S_MODE)) + && (numSlots != USE_2_TIMESLOTS)) { + pr_debug + ("STDAC uses only 2 slots in Normal and I2S modes\n"); + rc = PMIC_PARAMETER_ERROR; + } else if ((protocol == NETWORK_MODE) && + !((numSlots == USE_2_TIMESLOTS) || + (numSlots == USE_4_TIMESLOTS) || + (numSlots == USE_8_TIMESLOTS))) { + pr_debug + ("STDAC uses only 2,4 or 8 slots in Network mode\n"); + rc = PMIC_PARAMETER_ERROR; + } else if (protocol == SPD_IF_MODE) { + pr_debug + ("STDAC driver currently does not support SPD IF mode\n"); + rc = PMIC_NOT_SUPPORTED; + } else { + pr_debug + ("Proceeding to configure Stereo DAC\n"); + if (busID == AUDIO_DATA_BUS_1) { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 0); + } else { + reg_value = + SET_BITS(regST_DAC, STDCSSISEL, 1); + } + if (masterSlave == BUS_MASTER_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 0); + } else { + reg_value |= + SET_BITS(regST_DAC, STDCSM, 1); + } + if (protocol == NETWORK_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 1); + } else if (protocol == + NORMAL_MSB_JUSTIFIED_MODE) { + reg_value |= + SET_BITS(regST_DAC, STDCFS, 0); + } else { /* I2S mode as the other option has already been eliminated */ + reg_value |= + SET_BITS(regST_DAC, STDCFS, 2); + } + + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, + reg_value, ST_DAC_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + if (numSlots == USE_2_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 3); + } else if (numSlots == USE_4_TIMESLOTS) { + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 2); + } else { /* Use 8 timeslots - L , R and 6 other */ + reg_value = + SET_BITS(regSSI_NETWORK, + STDCSLOTS, 1); + } + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, + reg_value, + SSI_NW_MASK) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + stDAC.busID = busID; + stDAC.protocol = protocol; + stDAC.protocol_set = true; + stDAC.masterSlave = masterSlave; + stDAC.numSlots = numSlots; + pr_debug + ("mc13783 Stereo DAC successfully configured\n"); + rc = PMIC_SUCCESS; + } + } + + } + } else { + rc = PMIC_PARAMETER_ERROR; + /* Handle can only be Voice Codec or Stereo DAC */ + pr_debug("Handles only STDAC and VCODEC\n"); + } + + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Retrieve the current data bus protocol configuration. + * + * Retrieve the parameters that define the current audio data bus protocol. + * + * @param handle Device handle from pmic_audio_open() call. + * @param busID The data bus being used. + * @param protocol The data bus protocol being used. + * @param masterSlave The data bus timing mode being used. + * @param numSlots The number of timeslots being used (if in + * master mode). + * + * @retval PMIC_SUCCESS If the protocol was successful retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_protocol(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_DATA_BUS * const busID, + PMIC_AUDIO_BUS_PROTOCOL * const protocol, + PMIC_AUDIO_BUS_MODE * const masterSlave, + PMIC_AUDIO_NUMSLOTS * const numSlots) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + if ((busID != (PMIC_AUDIO_DATA_BUS *) NULL) && + (protocol != (PMIC_AUDIO_BUS_PROTOCOL *) NULL) && + (masterSlave != (PMIC_AUDIO_BUS_MODE *) NULL) && + (numSlots != (PMIC_AUDIO_NUMSLOTS *) NULL)) { + /* Enter a critical section so that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *busID = stDAC.busID; + *protocol = stDAC.protocol; + *masterSlave = stDAC.masterSlave; + *numSlots = stDAC.numSlots; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *busID = vCodec.busID; + *protocol = vCodec.protocol; + *masterSlave = vCodec.masterSlave; + *numSlots = vCodec.numSlots; + rc = PMIC_SUCCESS; + } + + /* Exit critical section. */ + up(&mutex); + } + + return rc; +} + +/*! + * @brief Enable the Stereo DAC or the Voice CODEC. + * + * Explicitly enable the Stereo DAC or the Voice CODEC to begin audio + * playback or recording as required. This should only be done after + * successfully configuring all of the associated audio components (e.g., + * microphones, amplifiers, etc.). + * + * Note that the timed delays used in this function are necessary to + * ensure reliable operation of the Voice CODEC and Stereo DAC. The + * Stereo DAC seems to be particularly sensitive and it has been observed + * to fail to generate the required master mode clock signals if it is + * not allowed enough time to initialize properly. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful enabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be enabled. + */ +PMIC_STATUS pmic_audio_enable(const PMIC_AUDIO_HANDLE handle) +{ + const unsigned int AUDIO_BIAS_ENABLE = SET_BITS(regAUDIO_RX_0, + VAUDIOON, 1); + const unsigned int STDAC_ENABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_ENABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + pmic_write_reg(REG_AUDIO_RX_0, AUDIO_BIAS_ENABLE, + AUDIO_BIAS_ENABLE); + reg_mask = + SET_BITS(regAUDIO_RX_0, HSDETEN, + 1) | SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write = + SET_BITS(regAUDIO_RX_0, HSDETEN, + 1) | SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + if (rc == PMIC_SUCCESS) + pr_debug("pmic_audio_enable\n"); + /* We can enable the Stereo DAC. */ + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + STDAC_ENABLE, STDAC_ENABLE); + /*pmic_read_reg(REG_AUDIO_STEREO_DAC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Stereo DAC\n"); + rc = PMIC_ERROR; + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Must first set the audio bias bit to power up the audio circuits. */ + pmic_write_reg(REG_AUDIO_RX_0, AUDIO_BIAS_ENABLE, + AUDIO_BIAS_ENABLE); + reg_mask = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write = SET_BITS(regAUDIO_RX_0, HSDETEN, 1) | + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + /* Then we can enable the Voice CODEC. */ + rc = pmic_write_reg(REG_AUDIO_CODEC, VCODEC_ENABLE, + VCODEC_ENABLE); + + /* pmic_read_reg(REG_AUDIO_CODEC, ®_value); */ + if (rc != PMIC_SUCCESS) { + pr_debug("Failed to enable the Voice codec\n"); + rc = PMIC_ERROR; + } + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Disable the Stereo DAC or the Voice CODEC. + * + * Explicitly disable the Stereo DAC or the Voice CODEC to end audio + * playback or recording as required. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the device was successful disabled. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the device could not be disabled. + */ +PMIC_STATUS pmic_audio_disable(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int STDAC_DISABLE = SET_BITS(regST_DAC, STDCEN, 1); + const unsigned int VCODEC_DISABLE = SET_BITS(regAUD_CODEC, CDCEN, 1); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, 0, STDAC_DISABLE); + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, 0, VCODEC_DISABLE); + } + if (rc == PMIC_SUCCESS) { + pr_debug("Disabled successfully\n"); + } + /* Exit critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = pmic_audio_reset_device(handle); + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Reset all audio hardware control registers to their power on state. + * + * This resets all of the audio hardware control registers back to their + * power on states. Use this function with care since it also invalidates + * (i.e., automatically closes) all currently opened device handles. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +PMIC_STATUS pmic_audio_reset_all(void) +{ + PMIC_STATUS rc = PMIC_SUCCESS; + unsigned int audio_ssi_reset = 0; + unsigned int audio_rx1_reset = 0; + /* We need a critical section here to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* First close all opened device handles, also deregisters callbacks. */ + pmic_audio_close_handle(stDAC.handle); + pmic_audio_close_handle(vCodec.handle); + pmic_audio_close_handle(extStereoIn.handle); + + if (pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_rx1_reset = 1; + } + if (pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + audio_ssi_reset = 1; + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, RESET_ST_DAC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + /* better to check if SSI is also reset as some fields are represennted in SSI reg */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.protocol_set = false; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + } + } + + if (pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + if (audio_ssi_reset) { + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.protocol_set = false; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | OUTPUT_HIGHPASS_FILTER; + } + } + + if (pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. */ + audioOutput.outputPort = (PMIC_AUDIO_OUTPUT_PORT) NULL; + audioOutput.vCodecoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.stDacoutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.extStereooutputPGAGain = OUTPGA_GAIN_0DB; + audioOutput.balanceLeftGain = BAL_GAIN_0DB; + audioOutput.balanceRightGain = BAL_GAIN_0DB; + audioOutput.monoAdderGain = MONOADD_GAIN_0DB; + audioOutput.config = (PMIC_AUDIO_OUTPUT_CONFIG) 0; + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + } + + if (pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, + PMIC_ALL_BITS) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + /* Also reset the driver state information to match. Note that we + * reset the vCodec fields since all of the input/recording + * devices are only connected to the Voice CODEC and are managed + * as part of the Voice CODEC state. + */ + if (audio_rx1_reset) { + vCodec.leftChannelMic.mic = NO_MIC; + vCodec.leftChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.leftChannelMic.ampMode = CURRENT_TO_VOLTAGE; + vCodec.leftChannelMic.gain = MIC_GAIN_0DB; + vCodec.rightChannelMic.mic = NO_MIC; + vCodec.rightChannelMic.micOnOff = MICROPHONE_OFF; + vCodec.rightChannelMic.ampMode = AMP_OFF; + vCodec.rightChannelMic.gain = MIC_GAIN_0DB; + } + } + /* Finally, also reset any global state variables. */ + headsetState = NO_HEADSET; + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set the Audio callback function. + * + * Register a callback function that will be used to signal PMIC audio + * events. For example, the OSS audio driver should register a callback + * function in order to be notified of headset connect/disconnect events. + * + * @param func A pointer to the callback function. + * @param eventMask A mask selecting events to be notified. + * @param hs_state To know the headset state. + * + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * registered. + * @retval PMIC_PARAMETER_ERROR If the handle or the eventMask is invalid. + */ +PMIC_STATUS pmic_audio_set_callback(void *func, + const PMIC_AUDIO_EVENTS eventMask, + PMIC_HS_STATE * hs_state) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + pmic_event_callback_t eventNotify; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_audio_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + rc = PMIC_ERROR; + /* Register for PMIC events from the core protocol driver. */ + if (eventMask & MICROPHONE_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_MC2BI); + rc = pmic_event_subscribe(EVENT_MC2BI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto End; + } + } + + if (eventMask & HEADSET_DETECTED) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + rc = pmic_event_subscribe(EVENT_HSDETI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSDETI " + "failed\n", __FILE__); + goto Cleanup_HDT; + } + + } + if (eventMask & HEADSET_STEREO) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + rc = pmic_event_subscribe(EVENT_HSLI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_HSLI " + "failed\n", __FILE__); + goto Cleanup_HST; + } + } + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + rc = pmic_event_subscribe(EVENT_ALSPTHI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_ALSPTHI " + "failed\n", __FILE__); + goto Cleanup_TSD; + } + pr_debug("Registered for EVENT_ALSPTHI\n"); + } + if (eventMask & HEADSET_SHORT_CIRCUIT) { + /* We need to register for the A1 amplifier interrupt. */ + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + rc = pmic_event_subscribe(EVENT_AHSSHORTI, eventNotify); + + if (rc != PMIC_SUCCESS) { + pr_debug + ("%s: pmic_event_subscribe() for EVENT_AHSSHORTI " + "failed\n", __FILE__); + goto Cleanup_HShort; + } + pr_debug("Registered for EVENT_AHSSHORTI\n"); + } + + /* We also need the spinlock here to avoid possible problems + * with the interrupt handler when we update the + * "callback" and "eventMask" state variables. + */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + event_state.callback = func; + event_state.eventMask = eventMask; + + /* The spinlock is no longer needed now that we've finished + * updating the "callback" and "eventMask" state variables. + */ + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_HShort: + + if (eventMask & HEADSET_SHORT_CIRCUIT) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify); + } + + Cleanup_TSD: + + if (eventMask & HEADSET_THERMAL_SHUTDOWN) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify); + } + + Cleanup_HST: + + if (eventMask & HEADSET_STEREO) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + pmic_event_unsubscribe(EVENT_HSLI, eventNotify); + } + + Cleanup_HDT: + + if (eventMask & HEADSET_DETECTED) { + eventNotify.func = func; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + pmic_event_unsubscribe(EVENT_HSDETI, eventNotify); + } + + End: + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Deregisters the existing audio callback function. + * + * Deregister the callback function that was previously registered by calling + * pmic_audio_set_callback(). + * + * + * @retval PMIC_SUCCESS If the callback was successfully + * deregistered. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_clear_callback(void) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We need a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (event_state.callback != (PMIC_AUDIO_CALLBACK) NULL) { + rc = pmic_audio_deregister(&(event_state.callback), + &(event_state.eventMask)); + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Get the current audio callback function settings. + * + * Get the current callback function and event mask. + * + * @param func The current callback function. + * @param eventMask The current event selection mask. + * + * @retval PMIC_SUCCESS If the callback information was + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +PMIC_STATUS pmic_audio_get_callback(PMIC_AUDIO_CALLBACK * const func, + PMIC_AUDIO_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* We only need to acquire the mutex here because we will not be updating + * anything that may affect the interrupt handler. We just need to ensure + * that the callback fields are not changed while we are in the critical + * section by calling either pmic_audio_set_callback() or + * pmic_audio_clear_callback(). + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((func != (PMIC_AUDIO_CALLBACK *) NULL) && + (eventMask != (PMIC_AUDIO_EVENTS *) NULL)) { + + *func = event_state.callback; + *eventMask = event_state.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable the anti-pop circuitry to avoid extra noise when inserting + * or removing a external device (e.g., a headset). + * + * Enable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. A slow ramp speed may be needed to avoid extra noise. + * + * @param rampSpeed The desired anti-pop circuitry ramp speed. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * enabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * enabled. + */ +PMIC_STATUS pmic_audio_antipop_enable(const PMIC_AUDIO_ANTI_POP_RAMP_SPEED + rampSpeed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASEN, 1) | + SET_BITS(regAUDIO_RX_0, BIASSPEED, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is enabled by enabling the BIAS (BIASEN) and setting the + * BIASSPEED . + * BIASEN is just to make sure that BIAS is enabled + */ + reg_value = SET_BITS(regAUDIO_RX_0, BIASEN, 1) + | SET_BITS(regAUDIO_RX_0, BIASSPEED, 0) | SET_BITS(regAUDIO_RX_0, + HSLDETEN, 1); + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_value, reg_mask); + return rc; +} + +/*! + * @brief Disable the anti-pop circuitry. + * + * Disable the use of the built-in anti-pop circuitry to prevent noise from + * being generated when an external audio device is inserted or removed + * from an audio plug. + * + * @retval PMIC_SUCCESS If the anti-pop circuitry was successfully + * disabled. + * @retval PMIC_ERROR If the anti-pop circuitry could not be + * disabled. + */ +PMIC_STATUS pmic_audio_antipop_disable(void) +{ + PMIC_STATUS rc = PMIC_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 1); + const unsigned int reg_write = SET_BITS(regAUDIO_RX_0, BIASSPEED, 1) | + SET_BITS(regAUDIO_RX_0, BIASEN, 0); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* + * Antipop is disabled by setting BIASSPEED = 0. BIASEN bit remains set + * as only antipop needs to be disabled + */ + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + + return rc; +} + +/*! + * @brief Performs a reset of the Voice CODEC/Stereo DAC digital filter. + * + * The digital filter should be reset whenever the clock or sampling rate + * configuration has been changed. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the digital filter was successfully + * reset. + * @retval PMIC_ERROR If the digital filter could not be reset. + */ +PMIC_STATUS pmic_audio_digital_filter_reset(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regST_DAC, STDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC filter reset\n"); + } + + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUD_CODEC, CDCRESET, 1); + if (pmic_write_reg(REG_AUDIO_CODEC, reg_mask, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC filter reset\n"); + } + } + return rc; +} + +/*! + * @brief Get the most recent PTT button voltage reading. + * + * This feature is not supported by mc13783 + * @param level PTT button level. + * + * @retval PMIC_SUCCESS If the most recent PTT button voltage was + * returned. + * @retval PMIC_PARAMETER_ERROR If a NULL pointer argument was given. + */ +PMIC_STATUS pmic_audio_get_ptt_button_level(unsigned int *const level) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +#ifdef DEBUG_AUDIO + +/*! + * @brief Provide a hexadecimal dump of all PMIC audio registers (DEBUG only) + * + * This function is intended strictly for debugging purposes only and will + * print the current values of the following PMIC registers: + * + * - AUD_CODEC + * - ST_DAC + * - AUDIO_RX_0 + * - AUDIO_RX_1 + * - AUDIO_TX + * - AUDIO_SSI_NW + * + * The register fields will not be decoded. + * + * Note that we don't dump any of the arbitration bits because we cannot + * access the true arbitration bit settings when reading the registers + * from the secondary SPI bus. + * + * Also note that we must not call this function with interrupts disabled, + * for example, while holding a spinlock, because calls to pmic_read_reg() + * eventually end up in the SPI driver which will want to perform a + * schedule() operation. If schedule() is called with interrupts disabled, + * then you will see messages like the following: + * + * BUG: scheduling while atomic: ... + * + */ +void pmic_audio_dump_registers(void) +{ + unsigned int reg_value = 0; + + /* Dump the AUD_CODEC (Voice CODEC) register. */ + if (pmic_read_reg(REG_AUDIO_CODEC, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio Codec = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio codec\n"); + } + + /* Dump the ST DAC (Stereo DAC) register. */ + if (pmic_read_reg + (REG_AUDIO_STEREO_DAC, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("Stereo DAC = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read Stereo DAC\n"); + } + + /* Dump the SSI NW register. */ + if (pmic_read_reg + (REG_AUDIO_SSI_NETWORK, ®_value, REG_FULLMASK) == PMIC_SUCCESS) { + pr_debug("SSI Network = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read SSI network\n"); + } + + /* Dump the Audio RX 0 register. */ + if (pmic_read_reg(REG_AUDIO_RX_0, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 0 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 0\n"); + } + + /* Dump the Audio RX 1 register. */ + if (pmic_read_reg(REG_AUDIO_RX_1, ®_value, REG_FULLMASK) + == PMIC_SUCCESS) { + pr_debug("Audio RX 1 = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio RX 1\n"); + } + /* Dump the Audio TX register. */ + if (pmic_read_reg(REG_AUDIO_TX, ®_value, REG_FULLMASK) == + PMIC_SUCCESS) { + pr_debug("Audio Tx = 0x%x\n", reg_value); + } else { + pr_debug("Failed to read audio TX\n"); + } + +} + +#endif /* DEBUG_AUDIO */ + +/*@}*/ + +/************************************************************************* + * General Voice CODEC configuration. + ************************************************************************* + */ + +/*! + * @name General Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice + * CODEC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Voice CODEC clock source and operating characteristics. + * + * Define the Voice CODEC clock source and operating characteristics. This + * must be done before the Voice CODEC is enabled. + * + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE + clockIn, + const PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_VCODEC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((clockFreq >= VCODEC_CLI_13MHZ) + && (clockFreq <= VCODEC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((samplingRate != VCODEC_RATE_8_KHZ) + && (samplingRate != VCODEC_RATE_16_KHZ)) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((invert >= NO_INVERT) + && (invert <= INVERT_FRAMESYNC))) { + rc = PMIC_PARAMETER_ERROR; + } else { + /*reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7) | + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1) | + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1) | + SET_BITS(regAUD_CODEC, CDCBCLINV, 1) | + SET_BITS(regAUD_CODEC, CDCFSINV, 1); */ + if (clockIn == CLOCK_IN_CLIA) { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 0); + } else { + reg_value = + SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLKSEL, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + if (clockFreq == VCODEC_CLI_13MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 0); + } else if (clockFreq == VCODEC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 1); + } else if (clockFreq == VCODEC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 2); + } else if (clockFreq == VCODEC_CLI_26MHZ) { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 4); + } else { + reg_value |= SET_BITS(regAUD_CODEC, CDCCLK, 7); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCCLK, 7); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + + reg_value = 0; + reg_mask = 0; + + if (samplingRate == VCODEC_RATE_8_KHZ) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 0); + } else { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + } + reg_mask = SET_BITS(regAUD_CODEC, CDCFS8K16K, 1); + if (PMIC_SUCCESS != + pmic_write_reg(REG_AUDIO_CODEC, + reg_value, reg_mask)) + return PMIC_ERROR; + reg_value = 0; + reg_mask = + SET_BITS(regAUD_CODEC, CDCBCLINV, + 1) | SET_BITS(regAUD_CODEC, CDCFSINV, 1); + + if (invert & INVERT_BITCLOCK) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= + SET_BITS(regAUD_CODEC, CDCBCLINV, 0); + reg_value |= + SET_BITS(regAUD_CODEC, CDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_CODEC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("CODEC clock set\n"); + vCodec.clockIn = clockIn; + vCodec.clockFreq = clockFreq; + vCodec.samplingRate = samplingRate; + vCodec.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC clock source and operating characteristics. + * + * Get the current Voice CODEC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Voice CODEC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_VCODEC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure that we return a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (clockFreq != (PMIC_AUDIO_VCODEC_CLOCK_IN_FREQ *) NULL) && + (samplingRate != (PMIC_AUDIO_VCODEC_SAMPLING_RATE *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = vCodec.clockIn; + *clockFreq = vCodec.clockFreq; + *samplingRate = vCodec.samplingRate; + *invert = vCodec.invert; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC primary audio channel timeslot. + * + * Set the Voice CODEC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + ((timeslot == USE_TS0) || (timeslot == USE_TS1) || + (timeslot == USE_TS2) || (timeslot == USE_TS3))) { + reg_write = SET_BITS(regSSI_NETWORK, CDCTXRXSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.timeslot = timeslot; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC primary audio channel timeslot. + * + * Get the current Voice CODEC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + *timeslot = vCodec.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Voice CODEC secondary recording audio channel timeslot. + * + * Set the Voice CODEC secondary audio channel timeslot. This function must be + * used if the default timeslot for the secondary audio channel is to be + * changed. The secondary audio channel timeslot is used to transmit the audio + * data that was recorded by the Voice CODEC from the secondary audio input + * channel. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_vcodec_set_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_VCODEC_TIMESLOT + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, 3); + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + /* How to handle primary slot and secondary slot being the same */ + if ((timeslot >= USE_TS0) && (timeslot <= USE_TS3) + && (timeslot != vCodec.timeslot)) { + reg_write = + SET_BITS(regSSI_NETWORK, CDCTXSECSLOT, timeslot); + + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + vCodec.secondaryTXtimeslot = timeslot; + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Voice CODEC secondary recording audio channel timeslot. + * + * Get the Voice CODEC secondary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The secondary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Voice CODEC secondary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC secondary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_secondary_txslot(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_VCODEC_TIMESLOT * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_VCODEC_TIMESLOT *) NULL)) { + rc = PMIC_SUCCESS; + *timeslot = vCodec.secondaryTXtimeslot; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Set/Enable the Voice CODEC options. + * + * Set or enable various Voice CODEC options. The available options include + * the use of dithering, highpass digital filters, and loopback modes. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to enable. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 0); + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_write |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_write |= SET_BITS(regAUD_CODEC, CDCALM, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 0); + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1) | + SET_BITS(regAUD_CODEC, CDCTS, 1); + + } + + if (config & TRISTATE_TS) { + reg_write |= SET_BITS(regAUD_CODEC, CDCTS, 1); + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Voice CODEC options. + * + * Clear or disable various Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Voice CODEC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Voice CODEC options + * were invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_vcodec_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (config & DITHERING) { + reg_mask = SET_BITS(regAUD_CODEC, CDCDITH, 1); + reg_write = SET_BITS(regAUD_CODEC, CDCDITH, 1); + } + + if (config & INPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDIHPF, 1); + } + + if (config & OUTPUT_HIGHPASS_FILTER) { + reg_mask |= SET_BITS(regAUD_CODEC, AUDOHPF, 1); + } + + if (config & DIGITAL_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCDLM, 1); + } + + if (config & ANALOG_LOOPBACK) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCALM, 1); + } + + if (config & VCODEC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCCLKEN, 1); + } + + if (config & TRISTATE_TS) { + reg_mask |= SET_BITS(regAUD_CODEC, CDCTS, 1); + } + + if (reg_mask == 0) { + /* We should not reach this point without having to configure + * anything so we flag it as an error. + */ + rc = PMIC_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_CODEC, + reg_write, reg_mask); + } + + if (rc == PMIC_SUCCESS) { + vCodec.config |= config; + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Voice CODEC options. + * + * Get the current Voice CODEC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Voice CODEC options. + * + * @retval PMIC_SUCCESS If the Voice CODEC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_VCODEC_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_VCODEC_CONFIG *) NULL)) { + *config = vCodec.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the Voice CODEC bypass audio pathway. + * + * Enables the Voice CODEC bypass pathway for audio data. This allows direct + * output of the voltages on the TX data bus line to the output amplifiers + * (bypassing the digital-to-analog converters within the Voice CODEC). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = SET_BITS(regAUD_CODEC, CDCBYP, 1); + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC bypass audio pathway. + * + * Disables the Voice CODEC bypass pathway for audio data. This means that + * the TX data bus line will deliver digital data to the digital-to-analog + * converters within the Voice CODEC. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC bypass was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC bypass could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_bypass(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUD_CODEC, CDCBYP, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_CODEC, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * General Stereo DAC configuration. + ************************************************************************* + */ + +/*! + * @name General Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo + * DAC hardware. + */ +/*@{*/ + +/*! + * @brief Set the Stereo DAC clock source and operating characteristics. + * + * Define the Stereo DAC clock source and operating characteristics. This + * must be done before the Stereo DAC is enabled. + * + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn Select the clock signal source. + * @param clockFreq Select the clock signal frequency. + * @param samplingRate Select the audio data sampling rate. + * @param invert Enable inversion of the frame sync and/or + * bit clock inputs. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or clock configuration was + * invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_clock(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_CLOCK_IN_SOURCE clockIn, + const PMIC_AUDIO_STDAC_CLOCK_IN_FREQ + clockFreq, + const PMIC_AUDIO_STDAC_SAMPLING_RATE + samplingRate, + const PMIC_AUDIO_CLOCK_INVERT invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + /* Validate all of the calling parameters. */ + if (handle == (PMIC_AUDIO_HANDLE) NULL) { + rc = PMIC_PARAMETER_ERROR; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + if ((clockIn != CLOCK_IN_CLIA) && (clockIn != CLOCK_IN_CLIB)) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_MASTER_MODE) + && !((clockFreq >= STDAC_CLI_3_36864MHZ) + && (clockFreq <= STDAC_CLI_33_6MHZ))) { + rc = PMIC_PARAMETER_ERROR; + } else if ((stDAC.masterSlave == BUS_SLAVE_MODE) + && !((clockFreq >= STDAC_MCLK_PLL_DISABLED) + && (clockFreq <= STDAC_BCLK_IN_PLL))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((samplingRate >= STDAC_RATE_8_KHZ) + && (samplingRate <= STDAC_RATE_96_KHZ))) { + rc = PMIC_PARAMETER_ERROR; + } + /* + else if(!((invert >= NO_INVERT) && (invert <= INVERT_FRAMESYNC))) + { + rc = PMIC_PARAMETER_ERROR; + } */ + else { + reg_mask = SET_BITS(regST_DAC, STDCCLK, 7) | + SET_BITS(regST_DAC, STDCCLKSEL, 1) | + SET_BITS(regST_DAC, SR, 15) | + SET_BITS(regST_DAC, STDCBCLINV, 1) | + SET_BITS(regST_DAC, STDCFSINV, 1); + if (clockIn == CLOCK_IN_CLIA) { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 0); + } else { + reg_value = SET_BITS(regST_DAC, STDCCLKSEL, 1); + } + /* How to take care of sample rates in SLAVE mode */ + if ((clockFreq == STDAC_CLI_3_36864MHZ) + || ((clockFreq == STDAC_FSYNC_IN_PLL))) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 6); + } else if ((clockFreq == STDAC_CLI_12MHZ) + || (clockFreq == STDAC_MCLK_PLL_DISABLED)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 5); + } else if (clockFreq == STDAC_CLI_13MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 0); + } else if (clockFreq == STDAC_CLI_15_36MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 1); + } else if (clockFreq == STDAC_CLI_16_8MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 2); + } else if (clockFreq == STDAC_CLI_26MHZ) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 4); + } else if ((clockFreq == STDAC_CLI_33_6MHZ) + || (clockFreq == STDAC_BCLK_IN_PLL)) { + reg_value |= SET_BITS(regST_DAC, STDCCLK, 7); + } + + reg_value |= SET_BITS(regST_DAC, SR, samplingRate); + + if (invert & INVERT_BITCLOCK) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 1); + } + if (invert & INVERT_FRAMESYNC) { + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 1); + } + if (invert & NO_INVERT) { + reg_value |= SET_BITS(regST_DAC, STDCBCLINV, 0); + reg_value |= SET_BITS(regST_DAC, STDCFSINV, 0); + } + if (pmic_write_reg + (REG_AUDIO_STEREO_DAC, reg_value, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC clock set\n"); + rc = PMIC_SUCCESS; + stDAC.clockIn = clockIn; + stDAC.clockFreq = clockFreq; + stDAC.samplingRate = samplingRate; + stDAC.invert = invert; + } + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the Stereo DAC clock source and operating characteristics. + * + * Get the current Stereo DAC clock source and operating characteristics. + * + * @param handle Device handle from pmic_audio_open() call. + * @param clockIn The clock signal source. + * @param clockFreq The clock signal frequency. + * @param samplingRate The audio data sampling rate. + * @param invert Inversion of the frame sync and/or + * bit clock inputs is enabled/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC clock settings were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle invalid. + * @retval PMIC_ERROR If the Stereo DAC clock configuration + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_clock(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_CLOCK_IN_SOURCE * + const clockIn, + PMIC_AUDIO_STDAC_SAMPLING_RATE * + const samplingRate, + PMIC_AUDIO_STDAC_CLOCK_IN_FREQ * + const clockFreq, + PMIC_AUDIO_CLOCK_INVERT * const invert) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (clockIn != (PMIC_AUDIO_CLOCK_IN_SOURCE *) NULL) && + (samplingRate != (PMIC_AUDIO_STDAC_SAMPLING_RATE *) NULL) && + (clockFreq != (PMIC_AUDIO_STDAC_CLOCK_IN_FREQ *) NULL) && + (invert != (PMIC_AUDIO_CLOCK_INVERT *) NULL)) { + *clockIn = stDAC.clockIn; + *samplingRate = stDAC.samplingRate; + *clockFreq = stDAC.clockFreq; + *invert = stDAC.invert; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the Stereo DAC primary audio channel timeslot. + * + * Set the Stereo DAC primary audio channel timeslot. This function must be + * used if the default timeslot for the primary audio channel is to be changed. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot Select the primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio channel timeslot + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be set. + */ +PMIC_STATUS pmic_audio_stdac_set_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSLOT, 3); + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if ((timeslot == USE_TS0_TS1) || (timeslot == USE_TS2_TS3) + || (timeslot == USE_TS4_TS5) || (timeslot == USE_TS6_TS7)) { + if (pmic_write_reg + (REG_AUDIO_SSI_NETWORK, timeslot, + reg_mask) != PMIC_SUCCESS) { + rc = PMIC_ERROR; + } else { + pr_debug("STDAC primary timeslot set\n"); + stDAC.timeslot = timeslot; + rc = PMIC_SUCCESS; + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC primary audio channel timeslot. + * + * Get the current Stereo DAC primary audio channel timeslot. + * + * @param handle Device handle from pmic_audio_open() call. + * @param timeslot The primary audio channel timeslot. + * + * @retval PMIC_SUCCESS If the Stereo DAC primary audio channel + * timeslot was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC primary audio channel + * timeslot could not be retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_rxtx_timeslot(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_TIMESLOTS * + const timeslot) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (timeslot != (PMIC_AUDIO_STDAC_TIMESLOTS *) NULL)) { + *timeslot = stDAC.timeslot; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set/Enable the Stereo DAC options. + * + * Set or enable various Stereo DAC options. The available options include + * resetting the digital filter and enabling the bus master clock outputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to enable. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * successfully set/enabled. + */ +PMIC_STATUS pmic_audio_stdac_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_write |= SET_BITS(regST_DAC, STDCCLKEN, 1); + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config |= config; + pr_debug("STDAC config set\n"); + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear/Disable the Stereo DAC options. + * + * Clear or disable various Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The Stereo DAC options to be cleared/disabled. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the Stereo DAC options + * were invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * cleared/disabled. + */ +PMIC_STATUS pmic_audio_stdac_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + + if (config & STDAC_MASTER_CLOCK_OUTPUTS) { + reg_mask |= SET_BITS(regST_DAC, STDCCLKEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + stDAC.config &= ~config; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current Stereo DAC options. + * + * Get the current Stereo DAC options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current set of Stereo DAC options. + * + * @retval PMIC_SUCCESS If the Stereo DAC options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC options could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_stdac_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STDAC_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE) && + (config != (PMIC_AUDIO_STDAC_CONFIG *) NULL)) { + *config = stDAC.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio input section configuration. + ************************************************************************* + */ + +/*! + * @name Audio Input Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio + * input hardware. + */ +/*@{*/ + +/*! + * @brief Set/Enable the audio input section options. + * + * Set or enable various audio input section options. The only available + * option right now is to enable the automatic disabling of the microphone + * input amplifiers when a microphone/headset is inserted or removed. + * NOT SUPPORTED BY MC13783 + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to enable. + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully configured. + * @retval PMIC_PARAMETER_ERROR If the handle or audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be successfully set/enabled. + */ +PMIC_STATUS pmic_audio_input_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Clear/Disable the audio input section options. + * + * Clear or disable various audio input section options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The audio input section options to be + * cleared/disabled. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully cleared/disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or the audio input section + * options were invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be cleared/disabled. + */ +PMIC_STATUS pmic_audio_input_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; + +} + +/*! + * @brief Get the current audio input section options. + * + * Get the current audio input section options. + * + * @param[in] handle Device handle from pmic_audio_open() call. + * @param[out] config The current set of audio input section options. + * NOT SUPPORTED BY MC13783 + * + * @retval PMIC_SUCCESS If the audio input section options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio input section options could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_input_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_CONFIG * const config) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio recording using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Recording Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio recording. + */ +/*@{*/ + +/*! + * @brief Select the microphone inputs to be used for Voice CODEC recording. + * + * Select left (mc13783-only) and right microphone inputs for Voice CODEC + * recording. It is possible to disable or not use a particular microphone + * input channel by specifying NO_MIC as a parameter. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel Select the left microphone input channel. + * @param rightChannel Select the right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input ports + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be successfully enabled. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_PORT leftChannel, + const PMIC_AUDIO_INPUT_PORT rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((leftChannel == NO_MIC) || (leftChannel == MIC1_LEFT))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightChannel == NO_MIC) + || (rightChannel == MIC1_RIGHT_MIC_MONO) + || (rightChannel == TXIN_EXT) + || (rightChannel == MIC2_AUX))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (leftChannel == NO_MIC) { + } else { /* Left channel MIC enable */ + reg_mask = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1); + reg_write = SET_BITS(regAUDIO_TX, AMC1LEN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0); + } + /*For right channel enable one and clear the other two as well as RXINREC */ + if (rightChannel == NO_MIC) { + } else if (rightChannel == MIC1_RIGHT_MIC_MONO) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else if (rightChannel == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 0); + } else { /* TX line in */ + reg_mask |= SET_BITS(regAUDIO_TX, AMC1REN, 1) | + SET_BITS(regAUDIO_TX, RXINREC, 1) | + SET_BITS(regAUDIO_TX, AMC2EN, 1) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + reg_write |= SET_BITS(regAUDIO_TX, AMC1REN, 0) | + SET_BITS(regAUDIO_TX, RXINREC, 0) | + SET_BITS(regAUDIO_TX, AMC2EN, 0) | + SET_BITS(regAUDIO_TX, ATXINEN, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC inputs configured successfully\n"); + vCodec.leftChannelMic.mic = leftChannel; + vCodec.rightChannelMic.mic = + rightChannel; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone inputs being used for Voice CODEC + * recording. + * + * Get the left (mc13783-only) and right microphone inputs currently being + * used for Voice CODEC recording. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel. + * @param rightChannel The right microphone input channel. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_PORT * const leftChannel, + PMIC_AUDIO_INPUT_PORT * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_PORT *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_PORT *) NULL)) { + *leftChannel = vCodec.leftChannelMic.mic; + *rightChannel = vCodec.rightChannelMic.mic; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + return rc; +} + +/*! + * @brief Enable/disable the microphone input. + * + * This function enables/disables the current microphone input channel. The + * input amplifier is automatically turned off when the microphone input is + * disabled. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The left microphone input channel state. + * @param rightChannel the right microphone input channel state. + * + * @retval PMIC_SUCCESS If the microphone input channels were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input states + * were invalid. + * @retval PMIC_ERROR If the microphone input channels could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_mic_on_off(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_INPUT_MIC_STATE + leftChannel, + const PMIC_AUDIO_INPUT_MIC_STATE + rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int curr_left = 0; + unsigned int curr_right = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + curr_left = vCodec.leftChannelMic.mic; + curr_right = vCodec.rightChannelMic.mic; + if ((curr_left == NO_MIC) && (curr_right == NO_MIC)) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (curr_left == MIC1_LEFT) { + if ((leftChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else if ((leftChannel == MICROPHONE_OFF) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1LEN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, 0); + + } else { + /* Both are in same state . Nothing to be done */ + } + + } + if (curr_right == MIC1_RIGHT_MIC_MONO) { + if ((rightChannel == MICROPHONE_ON) && + (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == MIC2_AUX) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } else if (curr_right == TXIN_EXT) { + if ((rightChannel == MICROPHONE_ON) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_OFF)) { + /* Enable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + } else if ((rightChannel == MICROPHONE_OFF) + && (vCodec.leftChannelMic.micOnOff == + MICROPHONE_ON)) { + /* Disable the microphone */ + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1REN, + 1) | SET_BITS(regAUDIO_TX, + RXINREC, + 1) | + SET_BITS(regAUDIO_TX, AMC2EN, + 1) | SET_BITS(regAUDIO_TX, + ATXINEN, 1); + reg_write |= + SET_BITS(regAUDIO_TX, AMC1REN, + 0) | SET_BITS(regAUDIO_TX, + RXINREC, + 0) | + SET_BITS(regAUDIO_TX, AMC2EN, + 0) | SET_BITS(regAUDIO_TX, + ATXINEN, 0); + } else { + /* Both are in same state . Nothing to be done */ + } + } + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("MIC states configured successfully\n"); + vCodec.leftChannelMic.micOnOff = + leftChannel; + vCodec.rightChannelMic.micOnOff = + rightChannel; + } + } + } + + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Return the current state of the microphone inputs. + * + * This function returns the current state (on/off) of the microphone + * input channels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannel The current left microphone input channel + * state. + * @param rightChannel the current right microphone input channel + * state. + * + * @retval PMIC_SUCCESS If the microphone input channel states + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input channel states + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_mic_on_off(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_INPUT_MIC_STATE * + const leftChannel, + PMIC_AUDIO_INPUT_MIC_STATE * + const rightChannel) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL) && + (rightChannel != (PMIC_AUDIO_INPUT_MIC_STATE *) NULL)) { + *leftChannel = vCodec.leftChannelMic.micOnOff; + *rightChannel = vCodec.rightChannelMic.micOnOff; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the microphone input amplifier mode and gain level. + * + * This function sets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifiers were + * successfully reconfigured. + * @retval PMIC_PARAMETER_ERROR If the handle or microphone input amplifier + * modes or gain levels were invalid. + * @retval PMIC_ERROR If the microphone input amplifiers could + * not be reconfigured. + */ +PMIC_STATUS pmic_audio_vcodec_set_record_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_AMP_MODE + leftChannelMode, + const PMIC_AUDIO_MIC_GAIN + leftChannelGain, + const PMIC_AUDIO_MIC_AMP_MODE + rightChannelMode, + const PMIC_AUDIO_MIC_GAIN + rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!(((leftChannelGain >= MIC_GAIN_MINUS_8DB) + && (leftChannelGain <= MIC_GAIN_PLUS_23DB)) + && ((rightChannelGain >= MIC_GAIN_MINUS_8DB) + && (rightChannelGain <= MIC_GAIN_PLUS_23DB)))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong gain value\n"); + } else if (((leftChannelMode != AMP_OFF) + && (leftChannelMode != VOLTAGE_TO_VOLTAGE) + && (leftChannelMode != CURRENT_TO_VOLTAGE)) + || ((rightChannelMode != VOLTAGE_TO_VOLTAGE) + && (rightChannelMode != CURRENT_TO_VOLTAGE) + && (rightChannelMode != AMP_OFF))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("VCODEC set record gain - wrong amp mode\n"); + } else { + if (vCodec.leftChannelMic.mic == MIC1_LEFT) { + reg_mask = SET_BITS(regAUDIO_TX, AMC1LITOV, 1) | + SET_BITS(regAUDIO_TX, PGATXL, 31); + if (leftChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 0); + } else { + reg_write = + SET_BITS(regAUDIO_TX, AMC1LITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + } + if (vCodec.rightChannelMic.mic == MIC1_RIGHT_MIC_MONO) { + reg_mask |= + SET_BITS(regAUDIO_TX, AMC1RITOV, + 1) | SET_BITS(regAUDIO_TX, PGATXR, + 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC1RITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == MIC2_AUX) { + reg_mask |= SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + if (rightChannelMode == VOLTAGE_TO_VOLTAGE) { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 0); + } else { + reg_write |= + SET_BITS(regAUDIO_TX, AMC2ITOV, 1); + } + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } else if (vCodec.rightChannelMic.mic == TXIN_EXT) { + reg_mask |= SET_BITS(regAUDIO_TX, PGATXR, 31); + /* No current to voltage option for TX IN amplifier */ + reg_write |= + SET_BITS(regAUDIO_TX, PGATXR, + rightChannelGain); + } + + if (reg_mask == 0) { + } else { + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + reg_write = + SET_BITS(regAUDIO_TX, PGATXL, + leftChannelGain); + reg_mask = SET_BITS(regAUDIO_TX, PGATXL, 31); + rc = pmic_write_reg(REG_AUDIO_TX, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("MIC amp mode and gain set\n"); + vCodec.leftChannelMic.ampMode = + leftChannelMode; + vCodec.leftChannelMic.gain = + leftChannelGain; + vCodec.rightChannelMic.ampMode = + rightChannelMode; + vCodec.rightChannelMic.gain = + rightChannelGain; + + } + } + } + } + + /* Exit critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current microphone input amplifier mode and gain level. + * + * This function gets the current microphone input amplifier operating mode + * and gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftChannelMode The left microphone input amplifier mode. + * @param leftChannelGain The left microphone input amplifier gain level. + * @param rightChannelMode The right microphone input amplifier mode. + * @param rightChannelGain The right microphone input amplifier gain + * level. + * + * @retval PMIC_SUCCESS If the microphone input amplifier modes + * and gain levels were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the microphone input amplifier modes + * and gain levels could not be retrieved. + */ +PMIC_STATUS pmic_audio_vcodec_get_record_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_MIC_AMP_MODE * + const leftChannelMode, + PMIC_AUDIO_MIC_GAIN * + const leftChannelGain, + PMIC_AUDIO_MIC_AMP_MODE * + const rightChannelMode, + PMIC_AUDIO_MIC_GAIN * + const rightChannelGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE) && + (leftChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (leftChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL) && + (rightChannelMode != (PMIC_AUDIO_MIC_AMP_MODE *) NULL) && + (rightChannelGain != (PMIC_AUDIO_MIC_GAIN *) NULL)) { + *leftChannelMode = vCodec.leftChannelMic.ampMode; + *leftChannelGain = vCodec.leftChannelMic.gain; + *rightChannelMode = vCodec.rightChannelMic.ampMode; + *rightChannelGain = vCodec.rightChannelMic.gain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable a microphone bias circuit. + * + * This function enables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be enabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_write = SET_BITS(regAUDIO_TX, MC1BEN, 1); + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + if (biasCircuit & MIC_BIAS2) { + reg_write |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*! + * @brief Disable a microphone bias circuit. + * + * This function disables one of the available microphone bias circuits. + * + * @param handle Device handle from pmic_audio_open() call. + * @param biasCircuit The microphone bias circuit to be disabled. + * + * @retval PMIC_SUCCESS If the microphone bias circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or selected microphone bias + * circuit was invalid. + * @retval PMIC_ERROR If the microphone bias circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_micbias(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MIC_BIAS + biasCircuit) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (biasCircuit & MIC_BIAS1) { + reg_mask = SET_BITS(regAUDIO_TX, MC1BEN, 1); + } + + if (biasCircuit & MIC_BIAS2) { + reg_mask |= SET_BITS(regAUDIO_TX, MC2BEN, 1); + } + + if (reg_mask != 0) { + rc = pmic_write_reg(REG_AUDIO_TX, reg_write, reg_mask); + } + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Voice CODEC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Voice CODEC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Voice CODEC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Voice CODEC mixer. + * + * This function configures and enables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_vcodec_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_VCODEC_TIMESLOT + rxSecondaryTimeslot, + const PMIC_AUDIO_VCODEC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_VCODEC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0) + && (rxSecondaryTimeslot <= USE_TS3))) { + pr_debug + ("VCODEC enable mixer - wrong sec rx timeslot\n"); + } else if (!((gainIn >= VCODEC_NO_MIX) + && (gainIn <= VCODEC_MIX_IN_MINUS_12DB))) { + pr_debug("VCODEC enable mixer - wrong mix in gain\n"); + + } else if (!((gainOut >= VCODEC_MIX_OUT_0DB) + && (gainOut <= VCODEC_MIX_OUT_MINUS_6DB))) { + pr_debug("VCODEC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, CDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, CDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + CDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer enabled\n"); + } + } + } + + return rc; +} + +/*! + * @brief Disable the Voice CODEC mixer. + * + * This function disables the Voice CODEC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Voice CODEC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Voice CODEC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_vcodec_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask; + + if ((handle == vCodec.handle) && (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regSSI_NETWORK, CDCRXSECGAIN, 1); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + VCODEC_NO_MIX, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Vcodec mixer disabled\n"); + } + + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Playback Using the Stereo DAC. + ************************************************************************* + */ + +/*! + * @name Audio Playback Using the Stereo DAC Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC Stereo DAC + * to perform audio playback. + */ +/*@{*/ + +/*! + * @brief Configure and enable the Stereo DAC mixer. + * + * This function configures and enables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * @param rxSecondaryTimeslot The timeslot used for the secondary audio + * channel. + * @param gainIn The secondary audio channel gain level. + * @param gainOut The mixer output gain level. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mixer configuration + * was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_stdac_enable_mixer(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STDAC_TIMESLOTS + rxSecondaryTimeslot, + const PMIC_AUDIO_STDAC_MIX_IN_GAIN + gainIn, + const PMIC_AUDIO_STDAC_MIX_OUT_GAIN + gainOut) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + if (!((rxSecondaryTimeslot >= USE_TS0_TS1) + && (rxSecondaryTimeslot <= USE_TS6_TS7))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong sec timeslot\n"); + } else if (!((gainIn >= STDAC_NO_MIX) + && (gainIn <= STDAC_MIX_IN_MINUS_12DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix in gain\n"); + } else if (!((gainOut >= STDAC_MIX_OUT_0DB) + && (gainOut <= STDAC_MIX_OUT_MINUS_6DB))) { + rc = PMIC_PARAMETER_ERROR; + pr_debug("STDAC enable mixer - wrong mix out gain\n"); + } else { + + reg_mask = SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, 3) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 3) | + SET_BITS(regSSI_NETWORK, STDCSUMGAIN, 1); + reg_write = + SET_BITS(regSSI_NETWORK, STDCRXSECSLOT, + rxSecondaryTimeslot) | + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, + gainIn) | SET_BITS(regSSI_NETWORK, + STDCSUMGAIN, gainOut); + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("STDAC mixer enabled\n"); + } + } + + } + + return rc; +} + +/*! + * @brief Disable the Stereo DAC mixer. + * + * This function disables the Stereo DAC mixer. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the Stereo DAC mixer was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the Stereo DAC mixer could not be + * disabled. + */ +PMIC_STATUS pmic_audio_stdac_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = + SET_BITS(regSSI_NETWORK, STDCRXSECGAIN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, reg_write, reg_mask); + } + + return rc; +} + +/*@}*/ + +/************************************************************************* + * Audio Output Control + ************************************************************************* + */ + +/*! + * @name Audio Output Section Setup and Configuration APIs + * Functions for general setup and configuration of the PMIC audio output + * section to support playback. + */ +/*@{*/ + +/*! + * @brief Select the audio output ports. + * + * This function selects the audio output ports to be used. This also enables + * the appropriate output amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * acquired. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * acquired. + */ +PMIC_STATUS pmic_audio_output_set_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut == VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port + / Avoid Codec direct out */ + + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 1); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 1) | + SET_BITS(regAUDIO_RX_0, ASPSEL, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 1) | SET_BITS(regAUDIO_RX_0, + ALSPREF, + 1) | + SET_BITS(regAUDIO_RX_0, ALSPSEL, 0); + } + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSLEN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1) | + SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, AHSREN, + 1) | SET_BITS(regAUDIO_RX_0, + AHSSEL, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, + 1) | SET_BITS(regAUDIO_RX_0, + ARXOUTSEL, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + } + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("output ports enabled\n"); + audioOutput.outputPort = port; + + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Deselect/disable the audio output ports. + * + * This function disables the audio output ports that were previously enabled + * by calling pmic_audio_output_set_port(). + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports to be disabled. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle or output ports were + * invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * disabled. + */ +PMIC_STATUS pmic_audio_output_clear_port(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PORT port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((port == MONO_ALERT) || (port == MONO_EXTOUT)) { + rc = PMIC_NOT_SUPPORTED; + } else { + if (((handle == stDAC.handle) + && (stDAC.handleState == HANDLE_IN_USE)) + || ((handle == extStereoIn.handle) + && (extStereoIn.handleState == HANDLE_IN_USE)) + || ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_MIXER_OUT))) { + /* Stereo signal and MIXER source needs to be routed to the port / + Avoid Codec direct out */ + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & STEREO_LEFT_LOW_POWER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, LSPLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, LSPLEN, 0); + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE) + && (audioOutput.vCodecOut = VCODEC_DIRECT_OUT)) { + if (port & MONO_SPEAKER) { + reg_mask = SET_BITS(regAUDIO_RX_0, ASPEN, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ASPEN, 0); + } + if (port & MONO_LOUDSPEAKER) { + reg_mask |= SET_BITS(regAUDIO_RX_0, ALSPEN, 1) | + SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ALSPEN, + 0) | SET_BITS(regAUDIO_RX_0, + ALSPREF, 0); + } + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } + if (port & STEREO_OUT_LEFT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTLEN, 0); + } + if (port & STEREO_OUT_RIGHT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, ARXOUTREN, 0); + } + if (port & MONO_CDCOUT) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, CDCOUTEN, 0); + } + } +#ifdef CONFIG_HEADSET_DETECT_ENABLE + + if (port & STEREO_HEADSET_LEFT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 0); + } + if (port & STEREO_HEADSET_RIGHT) { + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 0); + } +#endif + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("output ports disabled\n"); + audioOutput.outputPort &= ~port; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output ports. + * + * This function retrieves the audio output ports that are currently being + * used. + * + * @param handle Device handle from pmic_audio_open() call. + * @param port The audio output ports currently being used. + * + * @retval PMIC_SUCCESS If the audio output ports were successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the audio output ports could not be + * retrieved. + */ +PMIC_STATUS pmic_audio_output_get_port(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PORT * const port) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (port != (PMIC_AUDIO_OUTPUT_PORT *) NULL)) { + *port = audioOutput.outputPort; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the gain level for the external stereo inputs. + * + * This function sets the gain levels for the external stereo inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The external stereo input gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_STEREO_IN_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1) | + SET_BITS(regAUDIO_RX_1, ARXIN, 1); + unsigned int reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + /* The ARX amplifier for stereo is also enabled over here */ + + if ((gain == STEREO_IN_GAIN_0DB) || (gain == STEREO_IN_GAIN_PLUS_18DB)) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + if (gain == STEREO_IN_GAIN_0DB) { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 1); + } else { + reg_write |= SET_BITS(regAUDIO_RX_1, ARXIN, 0); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Ext stereo gain set\n"); + extStereoIn.inputGain = gain; + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } + + return rc; +} + +/*! + * @brief Get the current gain level for the external stereo inputs. + * + * This function retrieves the current gain levels for the external stereo + * inputs. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current external stereo input gain + * level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_stereo_in_gain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_STEREO_IN_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE) && + (gain != (PMIC_AUDIO_STEREO_IN_GAIN *) NULL)) { + *gain = extStereoIn.inputGain; + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Set the output PGA gain level. + * + * This function sets the audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the gain level could not be set. + */ +PMIC_STATUS pmic_audio_output_set_pgaGain(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_PGA_GAIN gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = 0; + unsigned int reg_gain; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (!((gain >= OUTPGA_GAIN_MINUS_33DB) + && (gain <= OUTPGA_GAIN_PLUS_6DB))) { + rc = PMIC_NOT_SUPPORTED; + pr_debug("output set PGA gain - wrong gain value\n"); + } else { + reg_gain = gain + 2; + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXIN, 15) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXIN, reg_gain) | + SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARX, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGARX, reg_gain); + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGAST, 15); + reg_write = SET_BITS(regAUDIO_RX_1, PGAST, reg_gain); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA gains set\n"); + + if (handle == stDAC.handle) { + audioOutput.stDacoutputPGAGain = gain; + } else if (handle == vCodec.handle) { + audioOutput.vCodecoutputPGAGain = gain; + } else { + audioOutput.extStereooutputPGAGain = + gain; + } + } else { + pr_debug + ("Error writing PGA gains to register\n"); + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the output PGA gain level. + * + * This function retrieves the current audio output PGA gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output PGA gain level. + * + * @retval PMIC_SUCCESS If the gain level was successfully + * retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the gain level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_pgaGain(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_PGA_GAIN * + const gain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (gain != (PMIC_AUDIO_OUTPUT_PGA_GAIN *) NULL) { + if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.extStereooutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.vCodecoutputPGAGain; + rc = PMIC_SUCCESS; + } else if ((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) { + *gain = audioOutput.stDacoutputPGAGain; + rc = PMIC_SUCCESS; + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the output mixer. + * + * This function enables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + audioOutput.vCodecOut = VCODEC_MIXER_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers enabled\n"); + rc = PMIC_SUCCESS; + } + + } else { + pr_debug("Error writing mixer enable to register\n"); + } + + } + + return rc; +} + +/*! + * @brief Disable the output mixer. + * + * This function disables the output mixer for the audio stream that + * corresponds to the current handle (i.e., the Voice CODEC, Stereo DAC, or + * the external stereo inputs). + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mixer was successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mixer could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mixer(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + unsigned int reg_mask_mix = 0; + unsigned int reg_write_mix = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + if (((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE))) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, PGASTEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGASTEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDSTDC, 0); + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + reg_mask = SET_BITS(regAUDIO_RX_1, PGARXEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, PGARXEN, 0); + audioOutput.vCodecOut = VCODEC_DIRECT_OUT; + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDCDC, 0); + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /*reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 0); */ + + reg_mask_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write_mix = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + } + + if (reg_mask == 0) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write_mix, reg_mask_mix); + if (rc == PMIC_SUCCESS) { + pr_debug("Output PGA mixers disabled\n"); + } + } + } + return rc; +} + +/*! + * @brief Configure and enable the output balance amplifiers. + * + * This function configures and enables the output balance amplifiers. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The desired left channel gain level. + * @param rightGain The desired right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifiers were + * successfully configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or gain levels were invalid. + * @retval PMIC_ERROR If the output balance amplifiers could not + * be reconfigured or enabled. + */ +PMIC_STATUS pmic_audio_output_set_balance(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + leftGain, + const PMIC_AUDIO_OUTPUT_BALANCE_GAIN + rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + unsigned int reg_mask_ch = 0; + unsigned int reg_write_ch = 0; + + /* No critical section required here since we are not updating any + * global data. + */ + + if (!((leftGain >= BAL_GAIN_MINUS_21DB) && (leftGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else if (!((rightGain >= BAL_GAIN_MINUS_21DB) + && (rightGain <= BAL_GAIN_0DB))) { + rc = PMIC_PARAMETER_ERROR; + } else { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + /* In mc13783 only one channel can be attenuated wrt the other. + * It is not possible to specify attenuation for both + * This function will return an error if both channels + * are required to be attenuated + * The BALLR bit is set/reset depending on whether leftGain + * or rightGain is specified*/ + if ((rightGain == BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Nothing to be done */ + } else if ((rightGain != BAL_GAIN_0DB) + && (leftGain == BAL_GAIN_0DB)) { + /* Attenuate right channel */ + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - rightGain)); + /* The enum and the register values are reversed in order .. */ + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 0); + /* BALLR = 0 selects right channel for atten */ + } else if ((rightGain == BAL_GAIN_0DB) + && (leftGain != BAL_GAIN_0DB)) { + /* Attenuate left channel */ + + reg_mask = SET_BITS(regAUDIO_RX_1, BAL, 7); + reg_mask_ch = SET_BITS(regAUDIO_RX_1, BALLR, 1); + reg_write = + SET_BITS(regAUDIO_RX_1, BAL, + (BAL_GAIN_0DB - leftGain)); + reg_write_ch = + SET_BITS(regAUDIO_RX_1, BALLR, 1); + /* BALLR = 1 selects left channel for atten */ + } else { + rc = PMIC_PARAMETER_ERROR; + } + + if ((reg_mask == 0) || (reg_mask_ch == 0)) { + + } else { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write_ch, reg_mask_ch); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, + reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug + ("Output balance attenuation set\n"); + audioOutput.balanceLeftGain = + leftGain; + audioOutput.balanceRightGain = + rightGain; + } + } + } + } + } + return rc; +} + +/*! + * @brief Get the current output balance amplifier gain levels. + * + * This function retrieves the current output balance amplifier gain levels. + * + * @param handle Device handle from pmic_audio_open() call. + * @param leftGain The current left channel gain level. + * @param rightGain The current right channel gain level. + * + * @retval PMIC_SUCCESS If the output balance amplifier gain levels + * were successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the output balance amplifier gain levels + * could be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_balance(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const leftGain, + PMIC_AUDIO_OUTPUT_BALANCE_GAIN * + const rightGain) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + ((leftGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL) && + (rightGain != (PMIC_AUDIO_OUTPUT_BALANCE_GAIN *) NULL))) { + *leftGain = audioOutput.balanceLeftGain; + *rightGain = audioOutput.balanceRightGain; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Configure and enable the output mono adder. + * + * This function configures and enables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * @param mode The desired mono adder operating mode. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * configured and enabled. + * @retval PMIC_PARAMETER_ERROR If the handle or mono adder mode was + * invalid. + * @retval PMIC_ERROR If the mono adder could not be reconfigured + * or enabled. + */ +PMIC_STATUS pmic_audio_output_enable_mono_adder(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_MONO_ADDER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_write = 0; + unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if ((mode >= MONO_ADDER_OFF) && (mode <= STEREO_OPPOSITE_PHASE)) { + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (mode == MONO_ADDER_OFF) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 0); + } else if (mode == MONO_ADD_LEFT_RIGHT) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 2); + } else if (mode == MONO_ADD_OPPOSITE_PHASE) { + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 3); + } else { /* stereo opposite */ + + reg_write = SET_BITS(regAUDIO_RX_1, MONO, 1); + } + + rc = pmic_write_reg(REG_AUDIO_RX_1, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output mono adder mode set\n"); + + } + + } else { + rc = PMIC_PARAMETER_ERROR; + } + } else { + rc = PMIC_PARAMETER_ERROR; + } + return rc; +} + +/*! + * @brief Disable the output mono adder. + * + * This function disables the output mono adder. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the mono adder was successfully + * disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder could not be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_mono_adder(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_write = 0; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_1, MONO, 3); + + /* No critical section required here since we are not updating any + * global data. + */ + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + } + + return rc; +} + +/*! + * @brief Configure the mono adder output gain level. + * + * This function configures the mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The desired output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or gain level was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be reconfigured. + */ +PMIC_STATUS pmic_audio_output_set_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + const + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Get the current mono adder output gain level. + * + * This function retrieves the current mono adder output amplifier gain level. + * + * @param handle Device handle from pmic_audio_open() call. + * @param gain The current output gain level. + * + * @retval PMIC_SUCCESS If the mono adder output amplifier gain + * level was successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the mono adder output amplifier gain + * level could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_mono_adder_gain(const PMIC_AUDIO_HANDLE + handle, + PMIC_AUDIO_MONO_ADDER_OUTPUT_GAIN + * const gain) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + return rc; +} + +/*! + * @brief Set various audio output section options. + * + * This function sets one or more audio output section configuration + * options. The currently supported options include whether to disable + * the non-inverting mono speaker output, enabling the loudspeaker common + * bias circuit, enabling detection of headset insertion/removal, and + * whether to automatically disable the headset amplifiers when a headset + * insertion/removal has been detected. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be set. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully set. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be set. + */ +PMIC_STATUS pmic_audio_output_set_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + } + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + } + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config set\n"); + audioOutput.config |= config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Clear various audio output section options. + * + * This function clears one or more audio output section configuration + * options. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The desired audio output section + * configuration options to be cleared. + * + * @retval PMIC_SUCCESS If the desired configuration options were + * all successfully cleared. + * @retval PMIC_PARAMETER_ERROR If the handle or configuration options + * were invalid. + * @retval PMIC_ERROR If the desired configuration options + * could not be cleared. + */ +PMIC_STATUS pmic_audio_output_clear_config(const PMIC_AUDIO_HANDLE handle, + const PMIC_AUDIO_OUTPUT_CONFIG + config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + /*unsigned int reg_write_RX = 0; + unsigned int reg_mask_RX = 0; + unsigned int reg_write_TX = 0; + unsigned int reg_mask_TX = 0; */ + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) { + if (config & MONO_SPEAKER_INVERT_OUT_ONLY) { + /* If this is one of the parameters */ + rc = PMIC_NOT_SUPPORTED; + } else { + if (config & MONO_LOUDSPEAKER_COMMON_BIAS) { + reg_mask = SET_BITS(regAUDIO_RX_0, ALSPREF, 1); + reg_write = SET_BITS(regAUDIO_RX_0, ALSPREF, 0); + } + + if (config & HEADSET_DETECT_ENABLE) { + reg_mask |= SET_BITS(regAUDIO_RX_0, HSDETEN, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETEN, 0); + } + + if (config & STEREO_HEADSET_AMP_AUTO_DISABLE) { + reg_mask |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 1); + reg_write |= + SET_BITS(regAUDIO_RX_0, HSDETAUTOB, 0); + } + + if (reg_mask == 0) { + rc = PMIC_PARAMETER_ERROR; + } else { + rc = pmic_write_reg(REG_AUDIO_RX_0, + reg_write, reg_mask); + + if (rc == PMIC_SUCCESS) { + pr_debug("Output config cleared\n"); + audioOutput.config &= ~config; + + } + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Get the current audio output section options. + * + * This function retrieves the current audio output section configuration + * option settings. + * + * @param handle Device handle from pmic_audio_open() call. + * @param config The current audio output section + * configuration option settings. + * + * @retval PMIC_SUCCESS If the current configuration options were + * successfully retrieved. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the current configuration options + * could not be retrieved. + */ +PMIC_STATUS pmic_audio_output_get_config(const PMIC_AUDIO_HANDLE handle, + PMIC_AUDIO_OUTPUT_CONFIG * + const config) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Use a critical section to ensure a consistent hardware state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == stDAC.handle) && + (stDAC.handleState == HANDLE_IN_USE)) || + ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) || + ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE))) && + (config != (PMIC_AUDIO_OUTPUT_CONFIG *) NULL)) { + *config = audioOutput.config; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * @brief Enable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function enables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully enabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be enabled. + */ +PMIC_STATUS pmic_audio_output_enable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground enabled\n"); + + } + return rc; +} + +/*! + * @brief Disable the phantom ground circuit that is used to help identify + * the type of headset that has been inserted. + * + * This function disables the phantom ground circuit that is used to help + * identify the type of headset (e.g., stereo or mono) that has been inserted. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the phantom ground circuit was + * successfully disabled. + * @retval PMIC_PARAMETER_ERROR If the handle was invalid. + * @retval PMIC_ERROR If the phantom ground circuit could not + * be disabled. + */ +PMIC_STATUS pmic_audio_output_disable_phantom_ground() +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + const unsigned int reg_mask = SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + + /* No critical section required here since we are not updating any + * global data. + */ + + rc = pmic_write_reg(REG_AUDIO_RX_0, 1, reg_mask); + if (rc == PMIC_SUCCESS) { + pr_debug("Phantom ground disabled\n"); + + } + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Audio Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported audio APIs. + */ +/*@{*/ + +/*! + * @brief Enables the 5.6V boost for the microphone bias 2 circuit. + * + * This function enables the switching regulator SW3 and configures it to + * provide the 5.6V boost that is required for driving the microphone bias 2 + * circuit when using a 5-pole jack configuration (which is the case for the + * Sphinx board). + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully enabled. + * @retval PMIC_ERROR Failed to enable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_enable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ +/*! + * @brief Disables the 5.6V boost for the microphone bias 2 circuit. + * + * This function disables the switching regulator SW3 to turn off the 5.6V + * boost for the microphone bias 2 circuit. + * + * @retval PMIC_SUCCESS The 5.6V boost was successfully disabled. + * @retval PMIC_ERROR Failed to disable the 5.6V boost. + */ +/* +static PMIC_STATUS pmic_audio_mic_boost_disable(void) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + return rc; +} +*/ + +/*! + * @brief Free a device handle previously acquired by calling pmic_audio_open(). + * + * Terminate further access to the PMIC audio hardware that was previously + * acquired by calling pmic_audio_open(). This now allows another thread to + * successfully call pmic_audio_open() to gain access. + * + * Note that we will shutdown/reset the Voice CODEC or Stereo DAC as well as + * any associated audio input/output components that are no longer required. + * + * Also note that this function should only be called with the mutex already + * acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the close request was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + */ +static PMIC_STATUS pmic_audio_close_handle(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + + /* Match up the handle to the audio device and then close it. */ + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Stereo DAC hardware. The simplest way to + * do this is to simply call pmic_audio_reset_device() which will + * restore the ST_DAC register to it's initial power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(stDAC.handle); + + if (rc == PMIC_SUCCESS) { + stDAC.handle = AUDIO_HANDLE_NULL; + stDAC.handleState = HANDLE_FREE; + } + } else if ((handle == vCodec.handle) && + (vCodec.handleState == HANDLE_IN_USE)) { + /* Also shutdown the Voice CODEC and audio input hardware. The + * simplest way to do this is to simply call pmic_audio_reset_device() + * which will restore the AUD_CODEC register to it's initial + * power-on state. + * + * This will also shutdown the audio output section if no one + * else is still using it. + */ + rc = pmic_audio_reset_device(vCodec.handle); + if (rc == PMIC_SUCCESS) { + vCodec.handle = AUDIO_HANDLE_NULL; + vCodec.handleState = HANDLE_FREE; + } + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + + /* Call pmic_audio_reset_device() here to shutdown the audio output + * section if no one else is still using it. + */ + rc = pmic_audio_reset_device(extStereoIn.handle); + + if (rc == PMIC_SUCCESS) { + extStereoIn.handle = AUDIO_HANDLE_NULL; + extStereoIn.handleState = HANDLE_FREE; + } + } + + return rc; +} + +/*! + * @brief Reset the selected audio hardware control registers to their + * power on state. + * + * This resets all of the audio hardware control registers currently + * associated with the device handle back to their power on states. For + * example, if the handle is associated with the Stereo DAC and a + * specific output port and output amplifiers, then this function will + * reset all of those components to their initial power on state. + * + * This function can only be called if the mutex has already been acquired. + * + * @param handle Device handle from pmic_audio_open() call. + * + * @retval PMIC_SUCCESS If the reset operation was successful. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_ERROR If the reset was unsuccessful. + */ +static PMIC_STATUS pmic_audio_reset_device(const PMIC_AUDIO_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + unsigned int reg_mask = 0; + + if ((handle == stDAC.handle) && (stDAC.handleState == HANDLE_IN_USE)) { + /* Also shutdown the audio output section if nobody else is using it. + if ((vCodec.handleState == HANDLE_FREE) && + (extStereoIn.handleState == HANDLE_FREE)) + { + pmic_write_reg(REG_RX_AUD_AMPS, RESET_RX_AUD_AMPS, + REG_FULLMASK); + } */ + + rc = pmic_write_reg(REG_AUDIO_STEREO_DAC, + RESET_ST_DAC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_STDAC_MASK); + if (rc == PMIC_SUCCESS) { + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + stDAC.busID = AUDIO_DATA_BUS_1; + stDAC.protocol = NORMAL_MSB_JUSTIFIED_MODE; + stDAC.protocol_set = false; + stDAC.masterSlave = BUS_MASTER_MODE; + stDAC.numSlots = USE_2_TIMESLOTS; + stDAC.clockIn = CLOCK_IN_CLIA; + stDAC.samplingRate = STDAC_RATE_44_1_KHZ; + stDAC.clockFreq = STDAC_CLI_13MHZ; + stDAC.invert = NO_INVERT; + stDAC.timeslot = USE_TS0_TS1; + stDAC.config = (PMIC_AUDIO_STDAC_CONFIG) 0; + + } + } + } else if ((handle == vCodec.handle) + && (vCodec.handleState == HANDLE_IN_USE)) { + /* Disable the audio input section when disabling the Voice CODEC. */ + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + + rc = pmic_write_reg(REG_AUDIO_CODEC, + RESET_AUD_CODEC, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + rc = pmic_write_reg(REG_AUDIO_SSI_NETWORK, + RESET_SSI_NETWORK, + REG_SSI_VCODEC_MASK); + if (rc == PMIC_SUCCESS) { + + /* Also reset the driver state information to match. Note that we + * keep the device handle and event callback settings unchanged + * since these don't affect the actual hardware and we rely on + * the user to explicitly close the handle or deregister callbacks + */ + vCodec.busID = AUDIO_DATA_BUS_2; + vCodec.protocol = NETWORK_MODE; + vCodec.protocol_set = false; + vCodec.masterSlave = BUS_SLAVE_MODE; + vCodec.numSlots = USE_4_TIMESLOTS; + vCodec.clockIn = CLOCK_IN_CLIB; + vCodec.samplingRate = VCODEC_RATE_8_KHZ; + vCodec.clockFreq = VCODEC_CLI_13MHZ; + vCodec.invert = NO_INVERT; + vCodec.timeslot = USE_TS0; + vCodec.config = + INPUT_HIGHPASS_FILTER | + OUTPUT_HIGHPASS_FILTER; + + } + } + + } else if ((handle == extStereoIn.handle) && + (extStereoIn.handleState == HANDLE_IN_USE)) { + /* Disable the Ext stereo Amplifier and disable it as analog mixer input */ + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + pmic_write_reg(REG_AUDIO_RX_1, 0, reg_mask); + + reg_mask = SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + pmic_write_reg(REG_AUDIO_RX_0, 0, reg_mask); + + /* We don't need to reset any other registers for this case. */ + rc = PMIC_SUCCESS; + } + + return rc; +} + +/*! + * @brief Deregister the callback function and event mask currently associated + * with an audio device handle. + * + * This function deregisters any existing callback function and event mask for + * the given audio device handle. This is done by either calling the + * pmic_audio_clear_callback() API or by closing the device handle. + * + * Note that this function should only be called with the mutex already + * acquired. We will also acquire the spinlock here to prevent possible + * race conditions with the interrupt handler. + * + * @param[in] callback The current event callback function pointer. + * @param[in] eventMask The current audio event mask. + * + * @retval PMIC_SUCCESS If the callback function and event mask + * were both successfully deregistered. + * @retval PMIC_ERROR If either the callback function or the + * event mask was not successfully + * deregistered. + */ + +static PMIC_STATUS pmic_audio_deregister(void *callback, + PMIC_AUDIO_EVENTS * const eventMask) +{ + unsigned long flags; + pmic_event_callback_t eventNotify; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by calling pmic_event_subscribe(). + */ + if (*eventMask & (HEADSET_DETECTED)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSDETI); + if (pmic_event_unsubscribe(EVENT_HSDETI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_DETECTED); + pr_debug("Deregistered for EVENT_HSDETI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (*eventMask & (HEADSET_STEREO)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_HSLI); + if (pmic_event_unsubscribe(EVENT_HSLI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_STEREO); + pr_debug("Deregistered for EVENT_HSLI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_THERMAL_SHUTDOWN)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_ALSPTHI); + if (pmic_event_unsubscribe(EVENT_ALSPTHI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_THERMAL_SHUTDOWN); + pr_debug("Deregistered for EVENT_ALSPTHI\n"); + } else { + rc = PMIC_ERROR; + } + } + if (*eventMask & (HEADSET_SHORT_CIRCUIT)) { + /* We need to deregister for the A1 amplifier interrupt. */ + eventNotify.func = callback; + eventNotify.param = (void *)(CORE_EVENT_AHSSHORTI); + if (pmic_event_unsubscribe(EVENT_AHSSHORTI, eventNotify) == + PMIC_SUCCESS) { + *eventMask &= ~(HEADSET_SHORT_CIRCUIT); + pr_debug("Deregistered for EVENT_AHSSHORTI\n"); + } else { + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* We need to grab the spinlock here to create a critical section to + * avoid any possible race conditions with the interrupt handler + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + */ + callback = NULL; + *eventMask = 0; + + /* Exit the critical section. */ + spin_unlock_irqrestore(&lock, flags); + } + + return rc; +} + +/*! + * @brief enable/disable fm output. + * + * @param[in] enable true to enable false to disable + */ +PMIC_STATUS pmic_audio_fm_output_enable(bool enable) +{ + unsigned int reg_mask = 0; + unsigned int reg_write = 0; + PMIC_STATUS rc = PMIC_PARAMETER_ERROR; + if (enable) { + pmic_audio_antipop_enable(ANTI_POP_RAMP_FAST); + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSLEN, 1); + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSREN, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, AHSSEL, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + + reg_mask |= SET_BITS(regAUDIO_RX_0, HSPGDIS, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, HSPGDIS, 0); + } else { + reg_mask |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 1); + reg_write |= SET_BITS(regAUDIO_RX_0, ADDRXIN, 0); + } + rc = pmic_write_reg(REG_AUDIO_RX_0, reg_write, reg_mask); + if (rc != PMIC_SUCCESS) + return rc; + if (enable) { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + } else { + reg_mask = SET_BITS(regAUDIO_RX_1, ARXINEN, 1); + reg_write = SET_BITS(regAUDIO_RX_1, ARXINEN, 0); + } + rc = pmic_write_reg(REG_AUDIO_RX_1, reg_write, reg_mask); + return rc; +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Audio Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the audio + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the audio device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_audio_init(void) +{ + printk(KERN_INFO "PMIC Audio driver loading...\n"); + + return 0; +} + +/*! + * @brief This is the audio device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_audio_exit(void) +{ + printk(KERN_INFO "PMIC Audio driver unloading...\n"); + + /* Close all device handles that are still open. This will also + * deregister any callbacks that may still be active. + */ + if (stDAC.handleState == HANDLE_IN_USE) { + pmic_audio_close(stDAC.handle); + } + if (vCodec.handleState == HANDLE_IN_USE) { + pmic_audio_close(vCodec.handle); + } + if (extStereoIn.handleState == HANDLE_IN_USE) { + pmic_audio_close(extStereoIn.handle); + } + + /* Explicitly reset all of the audio registers so that there is no + * possibility of leaving the audio hardware in a state + * where it can cause problems if there is no device driver loaded. + */ + pmic_write_reg(REG_AUDIO_STEREO_DAC, RESET_ST_DAC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_CODEC, RESET_AUD_CODEC, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_TX, RESET_AUDIO_TX, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_SSI_NETWORK, RESET_SSI_NETWORK, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_0, RESET_AUDIO_RX_0, REG_FULLMASK); + pmic_write_reg(REG_AUDIO_RX_1, RESET_AUDIO_RX_1, REG_FULLMASK); +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_audio_init); +module_exit(mc13783_pmic_audio_exit); + +MODULE_DESCRIPTION("PMIC - mc13783 ADC driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery.c b/drivers/mxc/pmic/mc13783/pmic_battery.c new file mode 100644 index 000000000000..775ebe77a5c0 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery.c @@ -0,0 +1,1221 @@ +/* + * Copyright 2004-2009 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 mc13783/pmic_battery.c + * @brief This is the main file of PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> + +#include <linux/pmic_battery.h> +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#include "pmic_battery_defs.h" + +#include <mach/pmic_power.h> +#ifdef CONFIG_MXC_HWEVENT +#include <mach/hw_events.h> +#endif + +static int pmic_battery_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the battery devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_battery_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_batt_enable_charger); +EXPORT_SYMBOL(pmic_batt_disable_charger); +EXPORT_SYMBOL(pmic_batt_set_charger); +EXPORT_SYMBOL(pmic_batt_get_charger_setting); +EXPORT_SYMBOL(pmic_batt_get_charge_current); +EXPORT_SYMBOL(pmic_batt_enable_eol); +EXPORT_SYMBOL(pmic_batt_bp_enable_eol); +EXPORT_SYMBOL(pmic_batt_disable_eol); +EXPORT_SYMBOL(pmic_batt_set_out_control); +EXPORT_SYMBOL(pmic_batt_set_threshold); +EXPORT_SYMBOL(pmic_batt_led_control); +EXPORT_SYMBOL(pmic_batt_set_reverse_supply); +EXPORT_SYMBOL(pmic_batt_set_unregulated); +EXPORT_SYMBOL(pmic_batt_set_5k_pull); +EXPORT_SYMBOL(pmic_batt_event_subscribe); +EXPORT_SYMBOL(pmic_batt_event_unsubscribe); + +static DECLARE_MUTEX(count_mutex); /* open count mutex */ +static int open_count; /* open count for device file */ + +/*! + * Callback function for events, we want on MGN board + */ +static void callback_chg_detect(void) +{ +#ifdef CONFIG_MXC_HWEVENT + t_sensor_bits sensor; + struct mxc_hw_event event = { HWE_BAT_CHARGER_PLUG, 0 }; + + pr_debug("In callback_chg_detect\n"); + + /* get sensor values */ + pmic_get_sensors(&sensor); + + pr_debug("Callback, charger detect:%d\n", sensor.sense_chgdets); + + if (sensor.sense_chgdets) + event.args = 1; + else + event.args = 0; + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_low_battery(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_BATTERY_LOW, 0 }; + + pr_debug("In callback_low_battery\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_power_fail(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_POWER_FAILED, 0 }; + + pr_debug("In callback_power_fail\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_chg_overvoltage(void) +{ +#ifdef CONFIG_MXC_HWEVENT + struct mxc_hw_event event = { HWE_BAT_CHARGER_OVERVOLTAGE, 0 }; + + pr_debug("In callback_chg_overvoltage\n"); + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static void callback_chg_full(void) +{ +#ifdef CONFIG_MXC_HWEVENT + t_sensor_bits sensor; + struct mxc_hw_event event = { HWE_BAT_CHARGER_FULL, 0 }; + + pr_debug("In callback_chg_full\n"); + + /* disable charge function */ + pmic_batt_disable_charger(BATT_MAIN_CHGR); + + /* get charger sensor */ + pmic_get_sensors(&sensor); + + /* if did not detect the charger */ + if (sensor.sense_chgdets) + return; + /* send hardware event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +/*! + * This is the suspend of power management for the pmic battery API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + unsigned int reg_value = 0; + + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_CHARGER, reg_value, PMIC_ALL_BITS)); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic battery API. + * It suports RESTORE state. + * + * @param pdev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_battery_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function is used to start charging a battery. For different charger, + * different voltage and current range are supported. \n + * + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_enable_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage) | + BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_ENABLED); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN) | + BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a charger. + * + * @param chgr Charger as defined in \b t_batt_charger. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_charger(t_batt_charger chgr) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, 0) | + BITFVAL(MC13783_BATT_DAC_V_DAC, 0); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_COIN_CH_EN, + MC13783_BATT_DAC_COIN_CH_EN_DISABLED); + mask = BITFMASK(MC13783_BATT_DAC_COIN_CH_EN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, 0); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to change the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Charging voltage. + * @param c_current Charging current. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_charger(t_batt_charger chgr, + unsigned char c_voltage, + unsigned char c_current) +{ + unsigned int val, mask, reg; + + val = 0; + mask = 0; + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + val = BITFVAL(MC13783_BATT_DAC_DAC, c_current) | + BITFVAL(MC13783_BATT_DAC_V_DAC, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_DAC) | + BITFMASK(MC13783_BATT_DAC_V_DAC); + reg = REG_CHARGER; + break; + + case BATT_CELL_CHGR: + val = BITFVAL(MC13783_BATT_DAC_V_COIN, c_voltage); + mask = BITFMASK(MC13783_BATT_DAC_V_COIN); + reg = REG_POWER_CONTROL_0; + break; + + case BATT_TRCKLE_CHGR: + val = BITFVAL(MC13783_BATT_DAC_TRCKLE, c_current); + mask = BITFMASK(MC13783_BATT_DAC_TRCKLE); + reg = REG_CHARGER; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to retrive the charger setting. + * + * @param chgr Charger as defined in \b t_batt_charger. + * @param c_voltage Output parameter for charging voltage setting. + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charger_setting(t_batt_charger chgr, + unsigned char *c_voltage, + unsigned char *c_current) +{ + unsigned int val, reg; + + reg = 0; + + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (chgr) { + case BATT_MAIN_CHGR: + case BATT_TRCKLE_CHGR: + reg = REG_CHARGER; + break; + case BATT_CELL_CHGR: + reg = REG_POWER_CONTROL_0; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, &val, PMIC_ALL_BITS)); + + switch (chgr) { + case BATT_MAIN_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_DAC);; + *c_current = BITFEXT(val, MC13783_BATT_DAC_DAC); + break; + + case BATT_CELL_CHGR: + *c_voltage = BITFEXT(val, MC13783_BATT_DAC_V_COIN); + *c_current = 0; + break; + + case BATT_TRCKLE_CHGR: + *c_voltage = 0; + *c_current = BITFEXT(val, MC13783_BATT_DAC_TRCKLE); + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery voltage. + * + * @param b_voltage Output parameter for voltage setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_voltage(unsigned short *b_voltage) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + channel = BATTERY_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_voltage = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery current. + * + * @param b_current Output parameter for current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_current(unsigned short *b_current) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = BATTERY_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_current = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery temperature. + * + * @param b_temper Output parameter for temperature setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_batt_temperature(unsigned short *b_temper) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = GEN_PURPOSE_AD5; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *b_temper = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery charging voltage. + * + * @param c_voltage Output parameter for charging voltage setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charge_voltage(unsigned short *c_voltage) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = CHARGE_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *c_voltage = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function is retrives the main battery charging current. + * + * @param c_current Output parameter for charging current setting. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_get_charge_current(unsigned short *c_current) +{ + t_channel channel; + unsigned short result[8]; + + if (suspend_flag == 1) + return PMIC_ERROR; + + channel = CHARGE_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *c_current = result[0]; + + return PMIC_SUCCESS; +} + +/*! + * This function enables End-of-Life comparator. Not supported on + * mc13783. Use pmic_batt_bp_enable_eol function. + * + * @param threshold End-of-Life threshold. + * + * @return This function returns PMIC_UNSUPPORTED + */ +PMIC_STATUS pmic_batt_enable_eol(unsigned char threshold) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables End-of-Life comparator. + * + * @param typical Falling Edge Threshold threshold. + * @verbatim + BPDET UVDET LOBATL + ____ _____ ___________ + 0 2.6 UVDET + 0.2 + 1 2.6 UVDET + 0.3 + 2 2.6 UVDET + 0.4 + 3 2.6 UVDET + 0.5 + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_bp_enable_eol(t_bp_threshold typical) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_ENABLE) | + BITFVAL(MC13783_BATT_DAC_EOL_SEL, typical); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN) | + BITFMASK(MC13783_BATT_DAC_EOL_SEL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables End-of-Life comparator. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_disable_eol(void) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_EOL_CMP_EN, + MC13783_BATT_DAC_EOL_CMP_EN_DISABLE); + mask = BITFMASK(MC13783_BATT_DAC_EOL_CMP_EN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the output controls. + * It sets the FETOVRD and FETCTRL bits of mc13783 + * + * @param control type of control. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_out_control(t_control control) +{ + unsigned int val, mask; + if (suspend_flag == 1) + return PMIC_ERROR; + + switch (control) { + case CONTROL_HARDWARE: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 0) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_LOW: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 0); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + case CONTROL_BPFET_HIGH: + val = BITFVAL(MC13783_BATT_DAC_FETOVRD_EN, 1) | + BITFVAL(MC13783_BATT_DAC_FETCTRL_EN, 1); + mask = BITFMASK(MC13783_BATT_DAC_FETOVRD_EN) | + BITFMASK(MC13783_BATT_DAC_FETCTRL_EN); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function sets over voltage threshold. + * + * @param threshold value of over voltage threshold. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_set_threshold(int threshold) +{ + unsigned int val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + if (threshold > BAT_THRESHOLD_MAX) + return PMIC_PARAMETER_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_OVCTRL, threshold); + mask = BITFMASK(MC13783_BATT_DAC_OVCTRL); + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function controls charge LED. + * + * @param on If on is ture, LED will be turned on, + * or otherwise, LED will be turned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_led_control(bool on) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_LED_EN, on); + mask = BITFMASK(MC13783_BATT_DAC_LED_EN); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets reverse supply mode. + * + * @param enable If enable is ture, reverse supply mode is enable, + * or otherwise, reverse supply mode is disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_reverse_supply(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_REVERSE_SUPPLY, enable); + mask = BITFMASK(MC13783_BATT_DAC_REVERSE_SUPPLY); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets unregulatored charging mode on main battery. + * + * @param enable If enable is ture, unregulated charging mode is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_unregulated(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_UNREGULATED, enable); + mask = BITFMASK(MC13783_BATT_DAC_UNREGULATED); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a 5K pull down at CHRGRAW. + * To be used in the dual path charging configuration. + * + * @param enable If enable is true, 5k pull down is + * enable, or otherwise, disabled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_batt_set_5k_pull(bool enable) +{ + unsigned val, mask; + + if (suspend_flag == 1) + return PMIC_ERROR; + + val = BITFVAL(MC13783_BATT_DAC_5K, enable); + mask = BITFMASK(MC13783_BATT_DAC_5K); + + CHECK_ERROR(pmic_write_reg(REG_CHARGER, val, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS mc13783_battery_event(t_batt_event event, void *callback, bool sub) +{ + pmic_event_callback_t bat_callback; + type_event bat_event; + + bat_callback.func = callback; + bat_callback.param = NULL; + switch (event) { + case BAT_IT_CHG_DET: + bat_event = EVENT_CHGDETI; + break; + case BAT_IT_CHG_OVERVOLT: + bat_event = EVENT_CHGOVI; + break; + case BAT_IT_CHG_REVERSE: + bat_event = EVENT_CHGREVI; + break; + case BAT_IT_CHG_SHORT_CIRCUIT: + bat_event = EVENT_CHGSHORTI; + break; + case BAT_IT_CCCV: + bat_event = EVENT_CCCVI; + break; + case BAT_IT_BELOW_THRESHOLD: + bat_event = EVENT_CHRGCURRI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(bat_event, bat_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(bat_event, bat_callback)); + } + return 0; +} + +/*! + * This function is used to subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_subscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) + return PMIC_ERROR; + + return mc13783_battery_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on battery event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_batt_event_unsubscribe(t_batt_event event, void *callback) +{ + if (suspend_flag == 1) + return PMIC_ERROR; + + return mc13783_battery_event(event, callback, false); +} + +/*! + * This function implements IOCTL controls on a PMIC Battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_battery_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_charger_setting *chgr_setting = NULL; + unsigned short c_current; + unsigned int bc_info; + t_eol_setting *eol_setting; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + switch (cmd) { + case PMIC_BATT_CHARGER_CONTROL: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + if (chgr_setting->on != false) { + CHECK_ERROR_KFREE(pmic_batt_enable_charger + (chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting->c_current), + (kfree(chgr_setting))); + } else { + CHECK_ERROR(pmic_batt_disable_charger + (chgr_setting->chgr)); + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_SET_CHARGER: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_set_charger(chgr_setting->chgr, + chgr_setting->c_voltage, + chgr_setting-> + c_current), + (kfree(chgr_setting))); + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER: + if ((chgr_setting = kmalloc(sizeof(t_charger_setting), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(chgr_setting, (t_charger_setting *) arg, + sizeof(t_charger_setting))) { + kfree(chgr_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_batt_get_charger_setting + (chgr_setting->chgr, &chgr_setting->c_voltage, + &chgr_setting->c_current), + (kfree(chgr_setting))); + if (copy_to_user + ((t_charger_setting *) arg, chgr_setting, + sizeof(t_charger_setting))) { + return -EFAULT; + } + + kfree(chgr_setting); + break; + + case PMIC_BATT_GET_CHARGER_SENSOR: + { + t_sensor_bits sensor; + pmic_get_sensors(&sensor); + if (copy_to_user + ((unsigned int *)arg, &sensor.sense_chgdets, + sizeof(unsigned int))) + return -EFAULT; + + break; + } + case PMIC_BATT_GET_BATTERY_VOLTAGE: + CHECK_ERROR(pmic_batt_get_batt_voltage(&c_current)); + bc_info = (unsigned int)c_current * 2300 / 1023 + 2400; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_BATTERY_CURRENT: + CHECK_ERROR(pmic_batt_get_batt_current(&c_current)); + bc_info = (unsigned int)c_current * 5750 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + break; + + case PMIC_BATT_GET_BATTERY_TEMPERATURE: + CHECK_ERROR(pmic_batt_get_batt_temperature(&c_current)); + bc_info = (unsigned int)c_current; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_CHARGER_VOLTAGE: + CHECK_ERROR(pmic_batt_get_charge_voltage(&c_current)); + bc_info = (unsigned int)c_current * 23000 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_GET_CHARGER_CURRENT: + CHECK_ERROR(pmic_batt_get_charge_current(&c_current)); + bc_info = (unsigned int)c_current * 5750 / 1023; + if (copy_to_user((unsigned int *)arg, &bc_info, + sizeof(unsigned int))) + return -EFAULT; + + break; + + case PMIC_BATT_EOL_CONTROL: + if ((eol_setting = kmalloc(sizeof(t_eol_setting), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(eol_setting, (t_eol_setting *) arg, + sizeof(t_eol_setting))) { + kfree(eol_setting); + return -EFAULT; + } + + if (eol_setting->enable != false) { + CHECK_ERROR_KFREE(pmic_batt_bp_enable_eol + (eol_setting->typical), + (kfree(chgr_setting))); + } else { + CHECK_ERROR_KFREE(pmic_batt_disable_eol(), + (kfree(chgr_setting))); + } + + kfree(eol_setting); + break; + + case PMIC_BATT_SET_OUT_CONTROL: + CHECK_ERROR(pmic_batt_set_out_control((t_control) arg)); + break; + + case PMIC_BATT_SET_THRESHOLD: + CHECK_ERROR(pmic_batt_set_threshold((int)arg)); + break; + + case PMIC_BATT_LED_CONTROL: + CHECK_ERROR(pmic_batt_led_control((bool) arg)); + break; + + case PMIC_BATT_REV_SUPP_CONTROL: + CHECK_ERROR(pmic_batt_set_reverse_supply((bool) arg)); + break; + + case PMIC_BATT_UNREG_CONTROL: + CHECK_ERROR(pmic_batt_set_unregulated((bool) arg)); + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function implements the open method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + /* check open count, if open firstly, register callbacks */ + down(&count_mutex); + if (open_count++ > 0) { + up(&count_mutex); + return 0; + } + + pr_debug("Subscribe the callbacks\n"); + /* register battery event callback */ + if (pmic_batt_event_subscribe(BAT_IT_CHG_DET, callback_chg_detect)) { + pr_debug("Failed to subscribe the charger detect callback\n"); + goto event_err1; + } + if (pmic_power_event_sub(PWR_IT_LOBATLI, callback_power_fail)) { + pr_debug("Failed to subscribe the power failed callback\n"); + goto event_err2; + } + if (pmic_power_event_sub(PWR_IT_LOBATHI, callback_low_battery)) { + pr_debug("Failed to subscribe the low battery callback\n"); + goto event_err3; + } + if (pmic_batt_event_subscribe + (BAT_IT_CHG_OVERVOLT, callback_chg_overvoltage)) { + pr_debug("Failed to subscribe the low battery callback\n"); + goto event_err4; + } + if (pmic_batt_event_subscribe + (BAT_IT_BELOW_THRESHOLD, callback_chg_full)) { + pr_debug("Failed to subscribe the charge full callback\n"); + goto event_err5; + } + + up(&count_mutex); + + return 0; + + /* un-subscribe the event callbacks */ +event_err5: + pmic_batt_event_unsubscribe(BAT_IT_CHG_OVERVOLT, + callback_chg_overvoltage); +event_err4: + pmic_power_event_unsub(PWR_IT_LOBATHI, callback_low_battery); +event_err3: + pmic_power_event_unsub(PWR_IT_LOBATLI, callback_power_fail); +event_err2: + pmic_batt_event_unsubscribe(BAT_IT_CHG_DET, callback_chg_detect); +event_err1: + + open_count--; + up(&count_mutex); + + return -EFAULT; + +} + +/*! + * This function implements the release method on a Pmic battery device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_battery_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + + /* check open count, if open firstly, register callbacks */ + down(&count_mutex); + if (--open_count == 0) { + /* unregister these event callback */ + pr_debug("Unsubscribe the callbacks\n"); + pmic_batt_event_unsubscribe(BAT_IT_BELOW_THRESHOLD, + callback_chg_full); + pmic_batt_event_unsubscribe(BAT_IT_CHG_OVERVOLT, + callback_chg_overvoltage); + pmic_power_event_unsub(PWR_IT_LOBATHI, callback_low_battery); + pmic_power_event_unsub(PWR_IT_LOBATLI, callback_power_fail); + pmic_batt_event_unsubscribe(BAT_IT_CHG_DET, + callback_chg_detect); + } + up(&count_mutex); + + return 0; +} + +static struct file_operations pmic_battery_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_battery_ioctl, + .open = pmic_battery_open, + .release = pmic_battery_release, +}; + +static int pmic_battery_remove(struct platform_device *pdev) +{ + device_destroy(pmic_battery_class, MKDEV(pmic_battery_major, 0)); + class_destroy(pmic_battery_class); + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return 0; +} + +static int pmic_battery_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_battery_major = register_chrdev(0, PMIC_BATTERY_STRING, + &pmic_battery_fops); + + if (pmic_battery_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_battery\n"); + return pmic_battery_major; + } + init_waitqueue_head(&suspendq); + + pmic_battery_class = class_create(THIS_MODULE, PMIC_BATTERY_STRING); + if (IS_ERR(pmic_battery_class)) { + printk(KERN_ERR "Error creating PMIC battery class.\n"); + ret = PTR_ERR(pmic_battery_class); + goto err_out1; + } + + temp_class = device_create(pmic_battery_class, NULL, + MKDEV(pmic_battery_major, 0), NULL, + PMIC_BATTERY_STRING); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating PMIC battery class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_batt_led_control(true); + pmic_batt_set_5k_pull(true); + + printk(KERN_INFO "PMIC Battery successfully probed\n"); + + return ret; + + err_out2: + class_destroy(pmic_battery_class); + err_out1: + unregister_chrdev(pmic_battery_major, PMIC_BATTERY_STRING); + return ret; +} + +static struct platform_driver pmic_battery_driver_ldm = { + .driver = { + .name = "pmic_battery", + .bus = &platform_bus_type, + }, + .suspend = pmic_battery_suspend, + .resume = pmic_battery_resume, + .probe = pmic_battery_probe, + .remove = pmic_battery_remove, +}; + +/* + * Init and Exit + */ + +static int __init pmic_battery_init(void) +{ + pr_debug("PMIC Battery driver loading...\n"); + return platform_driver_register(&pmic_battery_driver_ldm); +} + +static void __exit pmic_battery_exit(void) +{ + platform_driver_unregister(&pmic_battery_driver_ldm); + pr_debug("PMIC Battery driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +module_init(pmic_battery_init); +module_exit(pmic_battery_exit); + +MODULE_DESCRIPTION("pmic_battery driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_battery_defs.h b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h new file mode 100644 index 000000000000..9f66a185cd2f --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_battery_defs.h @@ -0,0 +1,81 @@ +/* + * 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 mc13783/pmic_battery_defs.h + * @brief This is the internal header for PMIC(mc13783) Battery driver. + * + * @ingroup PMIC_BATTERY + */ + +#ifndef __PMIC_BATTERY_DEFS_H__ +#define __PMIC_BATTERY_DEFS_H__ + +#define PMIC_BATTERY_STRING "pmic_battery" + +/* REG_CHARGE */ +#define MC13783_BATT_DAC_V_DAC_LSH 0 +#define MC13783_BATT_DAC_V_DAC_WID 3 +#define MC13783_BATT_DAC_DAC_LSH 3 +#define MC13783_BATT_DAC_DAC_WID 4 +#define MC13783_BATT_DAC_TRCKLE_LSH 7 +#define MC13783_BATT_DAC_TRCKLE_WID 3 +#define MC13783_BATT_DAC_FETOVRD_EN_LSH 10 +#define MC13783_BATT_DAC_FETOVRD_EN_WID 1 +#define MC13783_BATT_DAC_FETCTRL_EN_LSH 11 +#define MC13783_BATT_DAC_FETCTRL_EN_WID 1 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_LSH 13 +#define MC13783_BATT_DAC_REVERSE_SUPPLY_WID 1 +#define MC13783_BATT_DAC_OVCTRL_LSH 15 +#define MC13783_BATT_DAC_OVCTRL_WID 2 +#define MC13783_BATT_DAC_UNREGULATED_LSH 17 +#define MC13783_BATT_DAC_UNREGULATED_WID 1 +#define MC13783_BATT_DAC_LED_EN_LSH 18 +#define MC13783_BATT_DAC_LED_EN_WID 1 +#define MC13783_BATT_DAC_5K_LSH 19 +#define MC13783_BATT_DAC_5K_WID 1 + +#define BITS_OUT_VOLTAGE 0 +#define LONG_OUT_VOLTAGE 3 +#define BITS_CURRENT_MAIN 3 +#define LONG_CURRENT_MAIN 4 +#define BITS_CURRENT_TRICKLE 7 +#define LONG_CURRENT_TRICKLE 3 +#define BIT_FETOVRD 10 +#define BIT_FETCTRL 11 +#define BIT_RVRSMODE 13 +#define BITS_OVERVOLTAGE 15 +#define LONG_OVERVOLTAGE 2 +#define BIT_UNREGULATED 17 +#define BIT_CHRG_LED 18 +#define BIT_CHRGRAWPDEN 19 + +/* REG_POWXER_CONTROL_0 */ +#define MC13783_BATT_DAC_V_COIN_LSH 20 +#define MC13783_BATT_DAC_V_COIN_WID 3 +#define MC13783_BATT_DAC_COIN_CH_EN_LSH 23 +#define MC13783_BATT_DAC_COIN_CH_EN_WID 1 +#define MC13783_BATT_DAC_COIN_CH_EN_ENABLED 1 +#define MC13783_BATT_DAC_COIN_CH_EN_DISABLED 0 +#define MC13783_BATT_DAC_EOL_CMP_EN_LSH 18 +#define MC13783_BATT_DAC_EOL_CMP_EN_WID 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_ENABLE 1 +#define MC13783_BATT_DAC_EOL_CMP_EN_DISABLE 0 +#define MC13783_BATT_DAC_EOL_SEL_LSH 16 +#define MC13783_BATT_DAC_EOL_SEL_WID 2 + +#define DEF_VALUE 0 + +#define BAT_THRESHOLD_MAX 3 + +#endif /* __PMIC_BATTERY_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_convity.c b/drivers/mxc/pmic/mc13783/pmic_convity.c new file mode 100644 index 000000000000..5d634ebebacf --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_convity.c @@ -0,0 +1,2482 @@ +/* + * Copyright 2004-2009 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 mc13783/pmic_convity.c + * @brief Implementation of the PMIC Connectivity driver APIs. + * + * The PMIC connectivity device driver and this API were developed to support + * the external connectivity capabilities of several power management ICs that + * are available from Freescale Semiconductor, Inc. + * + * The following operating modes, in terms of external connectivity, are + * supported: + * + * @verbatim + Operating Mode mc13783 + --------------- ------- + USB (incl. OTG) Yes + RS-232 Yes + CEA-936 Yes + + @endverbatim + * + * @ingroup PMIC_CONNECTIVITY + */ + +#include <linux/interrupt.h> /* For tasklet interface. */ +#include <linux/platform_device.h> /* For kernel module interface. */ +#include <linux/spinlock.h> /* For spinlock interface. */ +#include <linux/pmic_adc.h> /* For PMIC ADC driver interface. */ +#include <linux/pmic_status.h> +#include <mach/pmic_convity.h> /* For PMIC Connectivity driver interface. */ + +/* + * mc13783 Connectivity API + */ +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_convity_open); +EXPORT_SYMBOL(pmic_convity_close); +EXPORT_SYMBOL(pmic_convity_set_mode); +EXPORT_SYMBOL(pmic_convity_get_mode); +EXPORT_SYMBOL(pmic_convity_reset); +EXPORT_SYMBOL(pmic_convity_set_callback); +EXPORT_SYMBOL(pmic_convity_clear_callback); +EXPORT_SYMBOL(pmic_convity_get_callback); +EXPORT_SYMBOL(pmic_convity_usb_set_speed); +EXPORT_SYMBOL(pmic_convity_usb_get_speed); +EXPORT_SYMBOL(pmic_convity_usb_set_power_source); +EXPORT_SYMBOL(pmic_convity_usb_get_power_source); +EXPORT_SYMBOL(pmic_convity_usb_set_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_get_xcvr); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_dlp_duration); +EXPORT_SYMBOL(pmic_convity_usb_otg_set_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_clear_config); +EXPORT_SYMBOL(pmic_convity_usb_otg_get_config); +EXPORT_SYMBOL(pmic_convity_set_output); +EXPORT_SYMBOL(pmic_convity_rs232_set_config); +EXPORT_SYMBOL(pmic_convity_rs232_get_config); +EXPORT_SYMBOL(pmic_convity_cea936_exit_signal); + +/*! @def SET_BITS + * Set a register field to a given value. + */ + +#define SET_BITS(reg, field, value) (((value) << reg.field.offset) & \ + reg.field.mask) + +/*! @def GET_BITS + * Get the current value of a given register field. + */ +#define GET_BITS(reg, value) (((value) & reg.mask) >> \ + reg.offset) + +/*! + * @brief Define the possible states for a device handle. + * + * This enumeration is used to track the current state of each device handle. + */ +typedef enum { + HANDLE_FREE, /*!< Handle is available for use. */ + HANDLE_IN_USE /*!< Handle is currently in use. */ +} HANDLE_STATE; + +/* + * This structure is used to define a specific hardware register field. + * + * All hardware register fields are defined using an offset to the LSB + * and a mask. The offset is used to right shift a register value before + * applying the mask to actually obtain the value of the field. + */ +typedef struct { + const unsigned char offset; /* Offset of LSB of register field. */ + const unsigned int mask; /* Mask value used to isolate register field. */ +} REGFIELD; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_0 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_0 hardware + * register. + */ +typedef struct { + REGFIELD FSENB; /*!< USB Full Speed Enable */ + REGFIELD USB_SUSPEND; /*!< USB Suspend Mode Enable */ + REGFIELD USB_PU; /*!< USB Pullup Enable */ + REGFIELD UDP_PD; /*!< USB Data Plus Pulldown Enable */ + REGFIELD UDM_PD; /*!< USB 150K UDP Pullup Enable */ + REGFIELD DP150K_PU; /*!< USB Pullup/Pulldown Override Enable */ + REGFIELD VBUSPDENB; /*!< USB VBUS Pulldown NMOS Switch Enable */ + REGFIELD CURRENT_LIMIT; /*!< USB Regulator Current Limit Setting-3 bits */ + REGFIELD DLP_SRP; /*!< USB Data Line Pulsing Timer Enable */ + REGFIELD SE0_CONN; /*!< USB Pullup Connect When SE0 Detected */ + REGFIELD USBXCVREN; /*!< USB Transceiver Enabled When INTERFACE_MODE[2:0]=000 and RESETB=high */ + REGFIELD PULLOVR; /*!< 1K5 Pullup and UDP/UDM Pulldown Disable When UTXENB=Low */ + REGFIELD INTERFACE_MODE; /*!< Connectivity Interface Mode Select-3 Bits */ + REGFIELD DATSE0; /*!< USB Single or Differential Mode Select */ + REGFIELD BIDIR; /*!< USB Unidirectional/Bidirectional Transmission */ + REGFIELD USBCNTRL; /*!< USB Mode of Operation controlled By USBEN/SPI Pin */ + REGFIELD IDPD; /*!< USB UID Pulldown Enable */ + REGFIELD IDPULSE; /*!< USB Pulse to Gnd on UID Line Generated */ + REGFIELD IDPUCNTRL; /*!< USB UID Pin pulled high By 5ua Curr Source */ + REGFIELD DMPULSE; /*!< USB Positive pulse on the UDM Line Generated */ +} USBCNTRL_REG_0; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_0 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_0 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_0 regUSB0 = { + {0, 0x000001}, /*!< FSENB */ + {1, 0x000002}, /*!< USB_SUSPEND */ + {2, 0x000004}, /*!< USB_PU */ + {3, 0x000008}, /*!< UDP_PD */ + {4, 0x000010}, /*!< UDM_PD */ + {5, 0x000020}, /*!< DP150K_PU */ + {6, 0x000040}, /*!< VBUSPDENB */ + {7, 0x000380}, /*!< CURRENT_LIMIT */ + {10, 0x000400}, /*!< DLP_SRP */ + {11, 0x000800}, /*!< SE0_CONN */ + {12, 0x001000}, /*!< USBXCVREN */ + {13, 0x002000}, /*!< PULLOVR */ + {14, 0x01c000}, /*!< INTERFACE_MODE */ + {17, 0x020000}, /*!< DATSE0 */ + {18, 0x040000}, /*!< BIDIR */ + {19, 0x080000}, /*!< USBCNTRL */ + {20, 0x100000}, /*!< IDPD */ + {21, 0x200000}, /*!< IDPULSE */ + {22, 0x400000}, /*!< IDPUCNTRL */ + {23, 0x800000} /*!< DMPULSE */ + +}; + +/*! + * @brief This structure is used to identify the fields in the USBCNTRL_REG_1 hardware register. + * + * This structure lists all of the fields within the USBCNTRL_REG_1 hardware + * register. + */ +typedef struct { + REGFIELD VUSBIN; /*!< Controls The Input Source For VUSB */ + REGFIELD VUSB; /*!< VUSB Output Voltage Select-High=3.3V Low=2.775V */ + REGFIELD VUSBEN; /*!< VUSB Output Enable- */ + REGFIELD VBUSEN; /*!< VBUS Output Enable- */ + REGFIELD RSPOL; /*!< Low=RS232 TX on UDM, RX on UDP + High= RS232 TX on UDP, RX on UDM */ + REGFIELD RSTRI; /*!< TX Forced To Tristate in RS232 Mode Only */ + REGFIELD ID100kPU; /*!< 100k UID Pullup Enabled */ +} USBCNTRL_REG_1; + +/*! + * @brief This variable is used to access the USBCNTRL_REG_1 hardware register. + * + * This variable defines how to access all of the fields within the + * USBCNTRL_REG_1 hardware register. The initial values consist of the offset + * and mask values needed to access each of the register fields. + */ +static const USBCNTRL_REG_1 regUSB1 = { + {0, 0x000003}, /*!< VUSBIN-2 Bits */ + {2, 0x000004}, /*!< VUSB */ + {3, 0x000008}, /*!< VUSBEN */ + /*{4, 0x000010} *//*!< Reserved */ + {5, 0x000020}, /*!< VBUSEN */ + {6, 0x000040}, /*!< RSPOL */ + {7, 0x000080}, /*!< RSTRI */ + {8, 0x000100} /*!< ID100kPU */ + /*!< 9-23 Unused */ +}; + +/*! Define a mask to access the entire hardware register. */ +static const unsigned int REG_FULLMASK = 0xffffff; + +/*! Define the mc13783 USBCNTRL_REG_0 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_0 = 0x080060; + +/*! Define the mc13783 USBCNTRL_REG_1 register power on reset state. */ +static const unsigned int RESET_USBCNTRL_REG_1 = 0x000006; + +static pmic_event_callback_t eventNotify; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the connectivity driver. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_state_struct; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in USB mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_USB_SPEED usbSpeed; /*!< USB connection + speed. */ + PMIC_CONVITY_USB_MODE usbMode; /*!< USB connection + mode. */ + PMIC_CONVITY_USB_POWER_IN usbPowerIn; /*!< USB transceiver + power source. */ + PMIC_CONVITY_USB_POWER_OUT usbPowerOut; /*!< USB transceiver + power output + level. */ + PMIC_CONVITY_USB_TRANSCEIVER_MODE usbXcvrMode; /*!< USB transceiver + mode. */ + unsigned int usbDlpDuration; /*!< USB Data Line + Pulsing duration. */ + PMIC_CONVITY_USB_OTG_CONFIG usbOtgCfg; /*!< USB OTG + configuration + options. */ +} pmic_convity_usb_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in RS_232 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + PMIC_CONVITY_RS232_INTERNAL rs232CfgInternal; /*!< RS-232 internal + connections. */ + PMIC_CONVITY_RS232_EXTERNAL rs232CfgExternal; /*!< RS-232 external + connections. */ +} pmic_convity_rs232_state; + +/*! + * @brief This structure is used to maintain the current device driver state. + * + * This structure maintains the current state of the driver in cea-936 mode. This + * includes both the PMIC hardware state as well as the device handle and + * callback states. + */ + +typedef struct { + PMIC_CONVITY_HANDLE handle; /*!< Device handle. */ + HANDLE_STATE handle_state; /*!< Device handle + state. */ + PMIC_CONVITY_MODE mode; /*!< Device mode. */ + PMIC_CONVITY_CALLBACK callback; /*!< Event callback function pointer. */ + PMIC_CONVITY_EVENTS eventMask; /*!< Event mask. */ + +} pmic_convity_cea936_state; + +/*! + * @brief Identifies the hardware interrupt source. + * + * This enumeration identifies which of the possible hardware interrupt + * sources actually caused the current interrupt handler to be called. + */ +typedef enum { + CORE_EVENT_4V4 = 1, /*!< Detected USB 4.4 V event. */ + CORE_EVENT_2V0 = 2, /*!< Detected USB 2.0 V event. */ + CORE_EVENT_0V8 = 4, /*!< Detected USB 0.8 V event. */ + CORE_EVENT_ABDET = 8 /*!< Detected USB mini A-B connector event. */ +} PMIC_CORE_EVENT; + +/*! + * @brief This structure defines the reset/power on state for the Connectivity driver. + */ +static const pmic_convity_state_struct reset = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_usb_state usb = { + 0, + HANDLE_FREE, + USB, + NULL, + 0, + USB_FULL_SPEED, + USB_PERIPHERAL, + USB_POWER_INTERNAL, + USB_POWER_3V3, + USB_TRANSCEIVER_OFF, + 0, + USB_PULL_OVERRIDE | USB_VBUS_CURRENT_LIMIT_HIGH, +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_rs232_state rs_232 = { + 0, + HANDLE_FREE, + RS232_1, + NULL, + 0, + RS232_TX_USE0VM_RX_UDATVP, + RS232_TX_UDM_RX_UDP +}; + +/*! + * @brief This structure maintains the current state of the Connectivity driver. + * + * The initial values must be identical to the reset state defined by the + * #reset variable. + */ +static pmic_convity_cea936_state cea_936 = { + 0, + HANDLE_FREE, + CEA936_MONO, + NULL, + 0, +}; + +/*! + * @brief This spinlock is used to provide mutual exclusion. + * + * Create a spinlock that can be used to provide mutually exclusive + * read/write access to the globally accessible "convity" data structure + * that was defined above. Mutually exclusive access is required to + * ensure that the convity data structure is consistent at all times + * when possibly accessed by multiple threads of execution (for example, + * while simultaneously handling a user request and an interrupt event). + * + * We need to use a spinlock sometimes because we need to provide mutual + * exclusion while handling a hardware interrupt. + */ +static spinlock_t lock = SPIN_LOCK_UNLOCKED; + +/*! + * @brief This mutex is used to provide mutual exclusion. + * + * Create a mutex that can be used to provide mutually exclusive + * read/write access to the globally accessible data structures + * that were defined above. Mutually exclusive access is required to + * ensure that the Connectivity data structures are consistent at all + * times when possibly accessed by multiple threads of execution. + * + * Note that we use a mutex instead of the spinlock whenever disabling + * interrupts while in the critical section is not required. This helps + * to minimize kernel interrupt handling latency. + */ +static DECLARE_MUTEX(mutex); + +/* Prototype for the connectivity driver tasklet function. */ +static void pmic_convity_tasklet(struct work_struct *work); + +/*! + * @brief Tasklet handler for the connectivity driver. + * + * Declare a tasklet that will do most of the processing for all of the + * connectivity-related interrupt events (USB4.4VI, USB2.0VI, USB0.8VI, + * and AB_DETI). Note that we cannot do all of the required processing + * within the interrupt handler itself because we may need to call the + * ADC driver to measure voltages as well as calling any user-registered + * callback functions. + */ +DECLARE_WORK(convityTasklet, pmic_convity_tasklet); + +/*! + * @brief Global variable to track currently active interrupt events. + * + * This global variable is used to keep track of all of the currently + * active interrupt events for the connectivity driver. Note that access + * to this variable may occur while within an interrupt context and, + * therefore, must be guarded by using a spinlock. + */ +static PMIC_CORE_EVENT eventID = 0; + +/* Prototypes for all static connectivity driver functions. */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode); +static PMIC_STATUS pmic_convity_deregister_all(void); +static void pmic_convity_event_handler(void *param); + +/************************************************************************** + * General setup and configuration functions. + ************************************************************************** + */ + +/*! + * @name General Setup and Configuration Connectivity APIs + * Functions for setting up and configuring the connectivity hardware. + */ +/*@{*/ + +/*! + * Attempt to open and gain exclusive access to the PMIC connectivity + * hardware. An initial operating mode must also be specified. + * + * If the open request is successful, then a numeric handle is returned + * and this handle must be used in all subsequent function calls. The + * same handle must also be used in the pmic_convity_close() call when use + * of the PMIC connectivity hardware is no longer required. + * + * @param handle device handle from open() call + * @param mode initial connectivity operating mode + * + * @return PMIC_SUCCESS if the open request was successful + */ +PMIC_STATUS pmic_convity_open(PMIC_CONVITY_HANDLE * const handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + if (handle == (PMIC_CONVITY_HANDLE *) NULL) { + /* Do not dereference a NULL pointer. */ + return PMIC_ERROR; + } + + /* We only need to acquire a mutex here because the interrupt handler + * never modifies the device handle or device handle state. Therefore, + * we don't need to worry about conflicts with the interrupt handler + * or the need to execute in an interrupt context. + * + * But we do need a critical section here to avoid problems in case + * multiple calls to pmic_convity_open() are made since we can only + * allow one of them to succeed. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Check the current device handle state and acquire the handle if + * it is available. + */ + if ((usb.handle_state != HANDLE_FREE) + && (rs_232.handle_state != HANDLE_FREE) + && (cea_936.handle_state != HANDLE_FREE)) { + + /* Cannot open the PMIC connectivity hardware at this time or an invalid + * mode was requested. + */ + *handle = reset.handle; + } else { + + if (mode == USB) { + usb.handle = (PMIC_CONVITY_HANDLE) (&usb); + usb.handle_state = HANDLE_IN_USE; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = (PMIC_CONVITY_HANDLE) (&rs_232); + rs_232.handle_state = HANDLE_IN_USE; + } else if ((mode == CEA936_STEREO) || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = (PMIC_CONVITY_HANDLE) (&cea_936); + cea_936.handle_state = HANDLE_IN_USE; + + } + /* Let's begin by acquiring the connectivity device handle. */ + /* Then we can try to set the desired operating mode. */ + rc = pmic_convity_set_mode_internal(mode); + + if (rc == PMIC_SUCCESS) { + /* Successfully set the desired operating mode, now return the + * handle to the caller. + */ + if (mode == USB) { + *handle = usb.handle; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + *handle = rs_232.handle; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + *handle = cea_936.handle; + } + } else { + /* Failed to set the desired mode, return the handle to an unused + * state. + */ + if (mode == USB) { + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + } else if ((mode == RS232_1) || (mode == RS232_2)) { + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + } else if ((mode == CEA936_STEREO) + || (mode == CEA936_MONO) + || (mode == CEA936_TEST_LEFT) + || (mode == CEA936_TEST_RIGHT)) { + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + } + + *handle = reset.handle; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Terminate further access to the PMIC connectivity hardware. Also allows + * another process to call pmic_convity_open() to gain access. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the close request was successful + */ +PMIC_STATUS pmic_convity_close(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Begin a critical section here to avoid the possibility of race + * conditions if multiple threads happen to call this function and + * pmic_convity_open() at the same time. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Confirm that the device handle matches the one assigned in the + * pmic_convity_open() call and then close the connection. + */ + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = PMIC_SUCCESS; + + /* Deregister for all existing callbacks if necessary and make sure + * that the event handling settings are consistent following the + * close operation. + */ + if ((usb.callback != reset.callback) + || (rs_232.callback != reset.callback) + || (cea_936.callback != reset.callback)) { + /* Deregister the existing callback function and all registered + * events before we completely close the handle. + */ + rc = pmic_convity_deregister_all(); + if (rc == PMIC_SUCCESS) { + + } else if (usb.eventMask != reset.eventMask) { + /* Having a non-zero eventMask without a callback function being + * defined should never occur but let's just make sure here that + * we keep things consistent. + */ + usb.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + usb.handle = reset.handle; + usb.handle_state = reset.handle_state; + + } else if (rs_232.eventMask != reset.eventMask) { + + rs_232.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + rs_232.handle = reset.handle; + rs_232.handle_state = reset.handle_state; + + } else if (cea_936.eventMask != reset.eventMask) { + cea_936.eventMask = reset.eventMask; + /* Mark the connectivity device handle as being closed. */ + cea_936.handle = reset.handle; + cea_936.handle_state = reset.handle_state; + + } + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Change the current operating mode of the PMIC connectivity hardware. + * The available connectivity operating modes is hardware dependent and + * consists of one or more of the following: USB (including USB On-the-Go), + * RS-232, and CEA-936. Requesting an operating mode that is not supported + * by the PMIC hardware will return PMIC_NOT_SUPPORTED. + * + * @param handle device handle from + open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_set_mode(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_MODE mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + rc = pmic_convity_set_mode_internal(mode); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current operating mode for the PMIC connectivity hardware. + * + * @param handle device handle from open() call + * @param mode the current PMIC connectivity operating mode + * + * @return PMIC_SUCCESS if the requested mode was successfully set + */ +PMIC_STATUS pmic_convity_get_mode(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (mode != (PMIC_CONVITY_MODE *) NULL)) { + + *mode = usb.mode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Restore all registers to the initial power-on/reset state. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the reset was successful + */ +PMIC_STATUS pmic_convity_reset(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + /* Reset the PMIC Connectivity register to it's power on state. */ + rc = pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, + REG_FULLMASK); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + RESET_USBCNTRL_REG_1, REG_FULLMASK); + + if (rc == PMIC_SUCCESS) { + /* Also reset the device driver state data structure. */ + + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Register a callback function that will be used to signal PMIC connectivity + * events. For example, the USB subsystem should register a callback function + * in order to be notified of device connect/disconnect events. Note, however, + * that non-USB events may also be signalled depending upon the PMIC hardware + * capabilities. Therefore, the callback function must be able to properly + * handle all of the possible events if support for non-USB peripherals is + * also to be included. + * + * @param handle device handle from open() call + * @param func a pointer to the callback function + * @param eventMask a mask selecting events to be notified + * + * @return PMIC_SUCCESS if the callback was successful registered + */ +PMIC_STATUS pmic_convity_set_callback(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_CALLBACK func, + const PMIC_CONVITY_EVENTS eventMask) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_ERROR; + + /* We need to start a critical section here to ensure a consistent state + * in case simultaneous calls to pmic_convity_set_callback() are made. In + * that case, we must serialize the calls to ensure that the "callback" + * and "eventMask" state variables are always consistent. + * + * Note that we don't actually need to acquire the spinlock until later + * when we are finally ready to update the "callback" and "eventMask" + * state variables which are shared with the interrupt handler. + */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + /* Return an error if either the callback function or event mask + * is not properly defined. + * + * It is also considered an error if a callback function has already + * been defined. If you wish to register for a new set of events, + * then you must first call pmic_convity_clear_callback() to + * deregister the existing callback function and list of events + * before trying to register a new callback function. + */ + if ((func == NULL) || (eventMask == 0) + || (usb.callback != NULL)) { + rc = PMIC_ERROR; + + /* Register for PMIC events from the core protocol driver. */ + } else { + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + /* We need to register for the 4.4V interrupt. */ + //EVENT_USBI or EVENT_USB_44VI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + return rc; + } + } + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + /* We need to register for the 2.0V interrupt. */ + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_4V4; + } + } + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + /* We need to register for the 0.8V interrupt. */ + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + rc = pmic_event_subscribe(EVENT_USBI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_2V0; + } + } + + if ((eventMask & USB_DETECT_MINI_A) || + (eventMask & USB_DETECT_MINI_B) + || (eventMask & USB_DETECT_NON_USB_ACCESSORY) + || (eventMask & USB_DETECT_FACTORY_MODE)) { + /* We need to register for the AB_DET interrupt. */ + //EVENT_AB_DETI or EVENT_IDI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + rc = pmic_event_subscribe(EVENT_IDI, + eventNotify); + + if (rc != PMIC_SUCCESS) { + goto Cleanup_0V8; + } + } + + /* Use a critical section to maintain a consistent state. */ + spin_lock_irqsave(&lock, flags); + + /* Successfully registered for all events. */ + usb.callback = func; + usb.eventMask = eventMask; + spin_unlock_irqrestore(&lock, flags); + + goto End; + + /* This section unregisters any already registered events if we should + * encounter an error partway through the registration process. Note + * that we don't check the return status here since it is already set + * to PMIC_ERROR before we get here. + */ + Cleanup_0V8: + + if ((eventMask & USB_DETECT_0V8_RISE) || + (eventMask & USB_DETECT_0V8_FALL)) { + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_2V0: + + if ((eventMask & USB_DETECT_2V0_RISE) || + (eventMask & USB_DETECT_2V0_FALL)) { + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + goto End; + } + + Cleanup_4V4: + + if ((eventMask & USB_DETECT_4V4_RISE) || + (eventMask & USB_DETECT_4V4_FALL)) { + //EVENT_USB_44VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + pmic_event_unsubscribe(EVENT_USBI, eventNotify); + } + } + /* Exit the critical section. */ + + } + End:up(&mutex); + return rc; + +} + +/*! + * Clears the current callback function. If this function returns successfully + * then all future Connectivity events will only be handled by the default + * handler within the Connectivity driver. + * + * @param handle device handle from open() call + * + * @return PMIC_SUCCESS if the callback was successful cleared + */ +PMIC_STATUS pmic_convity_clear_callback(const PMIC_CONVITY_HANDLE handle) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if (((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232.handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) { + + rc = pmic_convity_deregister_all(); + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current callback function and event mask. + * + * @param handle device handle from open() call + * @param func the current callback function + * @param eventMask the current event selection mask + * + * @return PMIC_SUCCESS if the callback information was successful + * retrieved + */ +PMIC_STATUS pmic_convity_get_callback(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_CALLBACK * const func, + PMIC_CONVITY_EVENTS * const eventMask) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + if ((((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) || ((handle == rs_232.handle) + && (rs_232. + handle_state == + HANDLE_IN_USE)) + || ((handle == cea_936.handle) + && (cea_936.handle_state == HANDLE_IN_USE))) + && (func != (PMIC_CONVITY_CALLBACK *) NULL) + && (eventMask != (PMIC_CONVITY_EVENTS *) NULL)) { + *func = usb.callback; + *eventMask = usb.eventMask; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + + up(&mutex); + + return rc; +} + +/*@*/ + +/************************************************************************** + * USB-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name USB and USB-OTG Connectivity APIs + * Functions for controlling USB and USB-OTG connectivity. + */ +/*@{*/ + +/*! + * Set the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the desired USB transceiver speed + * + * @return PMIC_SUCCESS if the transceiver speed was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_speed(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_SPEED speed) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = SET_BITS(regUSB0, FSENB, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if (handle == (rs_232.handle || cea_936.handle)) { + return PMIC_PARAMETER_ERROR; + } else { + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE)) { + /* Validate the function parameters and if they are valid, then + * configure the pull-up and pull-down resistors as required for + * the desired operating mode. + */ + if ((speed == USB_HIGH_SPEED)) { + /* + * The USB transceiver also does not support the high speed mode + * (which is also optional under the USB OTG specification). + */ + rc = PMIC_NOT_SUPPORTED; + } else if ((speed != USB_LOW_SPEED) + && (speed != USB_FULL_SPEED)) { + /* Final validity check on the speed parameter. */ + rc = PMIC_ERROR;; + } else { + /* First configure the D+ and D- pull-up/pull-down resistors as + * per the USB OTG specification. + */ + if (speed == USB_FULL_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = + SET_BITS(regUSB0, UDM_PD, 1); + } else if (speed == USB_LOW_SPEED) { + /* Activate pull-up on D+ and pull-down on D-. */ + reg_value = SET_BITS(regUSB0, FSENB, 1); + } + + /* Now set the desired USB transceiver speed. Note that + * USB_FULL_SPEED simply requires FSENB=0 (which it + * already is). + */ + + rc = pmic_write_reg(REG_USB, reg_value, + reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbSpeed = speed; + } + } + } + } + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver speed. + * + * @param handle device handle from open() call + * @param speed the current USB transceiver speed + * @param mode the current USB transceiver mode + * + * @return PMIC_SUCCESS if the transceiver speed was successfully + * obtained + */ +PMIC_STATUS pmic_convity_usb_get_speed(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_SPEED * const speed, + PMIC_CONVITY_USB_MODE * const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (speed != (PMIC_CONVITY_USB_SPEED *) NULL) && + (mode != (PMIC_CONVITY_USB_MODE *) NULL)) { + *speed = usb.usbSpeed; + *mode = usb.usbMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * This function enables/disables VUSB and VBUS output. + * This API configures the VUSBEN and VBUSEN bits of USB register + * + * @param handle device handle from open() call + * @param out_type true, for VBUS + * false, for VUSB + * @param out if true, output is enabled + * if false, output is disabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_convity_set_output(const PMIC_CONVITY_HANDLE handle, + bool out_type, bool out) +{ + + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if ((out_type == 0) && (out == 1)) { + + reg_value = SET_BITS(regUSB1, VUSBEN, 1); + reg_mask = SET_BITS(regUSB1, VUSBEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 0 && out == 0) { + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } else if (out_type == 1 && out == 1) { + + reg_value = SET_BITS(regUSB1, VBUSEN, 1); + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + else if (out_type == 1 && out == 0) { + + reg_mask = SET_BITS(regUSB1, VBUSEN, 1); + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } + + /*else { + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value, reg_mask); + } */ + } + + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_set_power_source(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_POWER_IN + pwrin, + const PMIC_CONVITY_USB_POWER_OUT + pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + /* SET_BITS(regUSB1, VUSBEN, 1) | SET_BITS(regUSB1, VBUSEN, + 1) | SET_BITS(regUSB1, + VUSBIN, + 2) | */ + // SET_BITS(regUSB1, VUSB, 1); +/* SET_BITS(regUSB1, VUSBIN, 1) | SET_BITS(regUSB1, VUSBEN, + 1) | SET_BITS(regUSB1, + VBUSEN, 1);*/ + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (pwrin == USB_POWER_INTERNAL_BOOST) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 0); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } else if (pwrin == USB_POWER_VBUS) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 1); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + else if (pwrin == USB_POWER_INTERNAL) { + reg_value |= SET_BITS(regUSB1, VUSBIN, 2); + reg_mask = SET_BITS(regUSB1, VUSBIN, 1); + } + + if (pwrout == USB_POWER_3V3) { + reg_value |= SET_BITS(regUSB1, VUSB, 1); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + + else if (pwrout == USB_POWER_2V775) { + reg_value |= SET_BITS(regUSB1, VUSB, 0); + reg_mask |= SET_BITS(regUSB1, VUSB, 1); + } + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbPowerIn = pwrin; + usb.usbPowerOut = pwrout; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current power supply configuration. + * + * @param handle device handle from open() call + * @param pwrin USB transceiver regulator input power source + * @param pwrout USB transceiver regulator output power level + * + * @return PMIC_SUCCESS if the USB transceiver's power supply + * configuration was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_power_source(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_POWER_IN * + const pwrin, + PMIC_CONVITY_USB_POWER_OUT * + const pwrout) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (pwrin != (PMIC_CONVITY_USB_POWER_IN *) NULL) && + (pwrout != (PMIC_CONVITY_USB_POWER_OUT *) NULL)) { + *pwrin = usb.usbPowerIn; + *pwrout = usb.usbPowerOut; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the USB transceiver's operating mode. + * + * @param handle device handle from open() call + * @param mode desired operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully configured + */ +PMIC_STATUS pmic_convity_usb_set_xcvr(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_TRANSCEIVER_MODE + mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + + if (mode == USB_TRANSCEIVER_OFF) { + reg_value = SET_BITS(regUSB0, USBXCVREN, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + } + + if (mode == USB_SINGLE_ENDED_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_SINGLE_ENDED_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 1) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_UNIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 0); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } else if (mode == USB_DIFFERENTIAL_BIDIR) { + reg_value |= + SET_BITS(regUSB0, DATSE0, 0) | SET_BITS(regUSB0, + BIDIR, 1); + reg_mask |= + SET_BITS(regUSB0, USB_SUSPEND, + 1) | SET_BITS(regUSB0, DATSE0, + 1) | SET_BITS(regUSB0, BIDIR, + 1); + } + + if (mode == USB_SUSPEND_ON) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 1); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_SUSPEND_OFF) { + reg_value |= SET_BITS(regUSB0, USB_SUSPEND, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } + + if (mode == USB_OTG_SRP_DLP_START) { + reg_value |= SET_BITS(regUSB0, USB_PU, 0); + reg_mask |= SET_BITS(regUSB0, USB_SUSPEND, 1); + } else if (mode == USB_OTG_SRP_DLP_STOP) { + reg_value &= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbXcvrMode = mode; + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the USB transceiver's current operating mode. + * + * @param handle device handle from open() call + * @param mode current operating mode + * + * @return PMIC_SUCCESS if the USB transceiver's operating mode + * was successfully retrieved + */ +PMIC_STATUS pmic_convity_usb_get_xcvr(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_TRANSCEIVER_MODE * + const mode) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (mode != (PMIC_CONVITY_USB_TRANSCEIVER_MODE *) NULL)) { + *mode = usb.usbXcvrMode; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Set the Data Line Pulse duration (in milliseconds) for the USB OTG + * Session Request Protocol. + * + * For mc13783, this feature is not supported.So return PMIC_NOT_SUPPORTED + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + const unsigned int duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of the dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Get the current Data Line Pulse duration (in milliseconds) for the USB + * OTG Session Request Protocol. + * + * @param handle device handle from open() call + * @param duration the data line pulse duration (ms) + * + * @return PMIC_SUCCESS if the pulse duration was successfully obtained + */ +PMIC_STATUS pmic_convity_usb_otg_get_dlp_duration(const PMIC_CONVITY_HANDLE + handle, + unsigned int *const duration) +{ + PMIC_STATUS rc = PMIC_NOT_SUPPORTED; + + /* The setting of dlp duration is not supported by the mc13783 PMIC hardware. */ + + /* No critical section is required. */ + + if ((handle != usb.handle) + || (usb.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } + + return rc; +} + +/*! + * Set the USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg desired USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully set + */ +PMIC_STATUS pmic_convity_usb_otg_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + if (cfg & USB_OTG_SE0CONN) { + reg_value = SET_BITS(regUSB0, SE0_CONN, 1); + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + if (cfg & USBXCVREN) { + reg_value |= SET_BITS(regUSB0, USBXCVREN, 1); + reg_mask |= SET_BITS(regUSB0, USBXCVREN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_value |= SET_BITS(regUSB0, DLP_SRP, 1); + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_value |= SET_BITS(regUSB0, PULLOVR, 1); + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + reg_value |= SET_BITS(regUSB0, USB_PU, 1); + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + reg_value |= SET_BITS(regUSB0, UDM_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + reg_value |= SET_BITS(regUSB0, UDP_PD, 1); + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_value |= SET_BITS(regUSB0, DP150K_PU, 1); + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_value |= SET_BITS(regUSB0, USBCNTRL, 1); + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + if (cfg & USB_VBUS_CURRENT_LIMIT_LOW) { + reg_value |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 7); + } + + if (cfg & USB_VBUS_PULLDOWN) { + reg_value |= SET_BITS(regUSB0, VBUSPDENB, 1); + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + if ((cfg & USB_VBUS_CURRENT_LIMIT_HIGH) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) { + /* Make sure that the VBUS current limit state is + * correctly set to either USB_VBUS_CURRENT_LIMIT_HIGH + * or USB_VBUS_CURRENT_LIMIT_LOW but never both at the + * same time. + * + * We guarantee this by first clearing both of the + * status bits and then resetting the correct one. + */ + usb.usbOtgCfg &= + ~(USB_VBUS_CURRENT_LIMIT_HIGH | + USB_VBUS_CURRENT_LIMIT_LOW | + USB_VBUS_CURRENT_LIMIT_LOW_10MS | + USB_VBUS_CURRENT_LIMIT_LOW_20MS | + USB_VBUS_CURRENT_LIMIT_LOW_30MS | + USB_VBUS_CURRENT_LIMIT_LOW_40MS | + USB_VBUS_CURRENT_LIMIT_LOW_50MS | + USB_VBUS_CURRENT_LIMIT_LOW_60MS); + } + + usb.usbOtgCfg |= cfg; + } + } + //} + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Clears the USB On-The-Go (OTG) configuration. Multiple configuration settings + * may be OR'd together in a single call. However, selecting conflicting + * settings (e.g., multiple VBUS current limits) will result in undefined + * behavior. + * + * @param handle Device handle from open() call. + * @param cfg USB OTG configuration settings to be cleared. + * + * @retval PMIC_SUCCESS If the OTG configuration was successfully + * cleared. + * @retval PMIC_PARAMETER_ERROR If the handle is invalid. + * @retval PMIC_NOT_SUPPORTED If the desired USB OTG configuration is + * not supported by the PMIC hardware. + */ +PMIC_STATUS pmic_convity_usb_otg_clear_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_USB_OTG_CONFIG + cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = 0; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && (usb.handle_state == HANDLE_IN_USE)) { + /* if ((cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) || + (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS)) + { + rc = PMIC_NOT_SUPPORTED; + } */ + //else + + if (cfg & USB_OTG_SE0CONN) { + reg_mask = SET_BITS(regUSB0, SE0_CONN, 1); + } + + if (cfg & USB_OTG_DLP_SRP) { + reg_mask |= SET_BITS(regUSB0, DLP_SRP, 1); + } + + if (cfg & USB_DP150K_PU) { + reg_mask |= SET_BITS(regUSB0, DP150K_PU, 1); + } + + if (cfg & USB_PULL_OVERRIDE) { + reg_mask |= SET_BITS(regUSB0, PULLOVR, 1); + } + + if (cfg & USB_PU) { + + reg_mask |= SET_BITS(regUSB0, USB_PU, 1); + } + + if (cfg & USB_UDM_PD) { + + reg_mask |= SET_BITS(regUSB0, UDM_PD, 1); + } + + if (cfg & USB_UDP_PD) { + + reg_mask |= SET_BITS(regUSB0, UDP_PD, 1); + } + + if (cfg & USB_USBCNTRL) { + reg_mask |= SET_BITS(regUSB0, USBCNTRL, 1); + } + + if (cfg & USB_VBUS_CURRENT_LIMIT_HIGH) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 0); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_10MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 1); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_20MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 2); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_30MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 3); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_40MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 4); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_50MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 5); + } else if (cfg & USB_VBUS_CURRENT_LIMIT_LOW_60MS) { + reg_mask |= SET_BITS(regUSB0, CURRENT_LIMIT, 6); + } + + if (cfg & USB_VBUS_PULLDOWN) { + // reg_value |= SET_BITS(regUSB0, VBUSPDENB, 1); + reg_mask |= SET_BITS(regUSB0, VBUSPDENB, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + if (rc == PMIC_SUCCESS) { + usb.usbOtgCfg &= ~cfg; + } + } + //} + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the current USB On-The-Go (OTG) configuration. + * + * @param handle device handle from open() call + * @param cfg the current USB OTG configuration + * + * @return PMIC_SUCCESS if the OTG configuration was successfully + * retrieved + */ +PMIC_STATUS pmic_convity_usb_otg_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_USB_OTG_CONFIG * + const cfg) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == usb.handle) && + (usb.handle_state == HANDLE_IN_USE) && + (cfg != (PMIC_CONVITY_USB_OTG_CONFIG *) NULL)) { + *cfg = usb.usbOtgCfg; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * RS-232-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name RS-232 Serial Connectivity APIs + * Functions for controlling RS-232 serial connectivity. + */ +/*@{*/ + +/*! + * Set the connectivity interface to the selected RS-232 operating + * configuration. Note that the RS-232 operating mode will be automatically + * overridden if the USB_EN is asserted at any time (e.g., when a USB device + * is attached). However, we will also automatically return to the RS-232 + * mode once the USB device is detached. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was set + */ +PMIC_STATUS pmic_convity_rs232_set_config(const PMIC_CONVITY_HANDLE handle, + const PMIC_CONVITY_RS232_INTERNAL + cfgInternal, + const PMIC_CONVITY_RS232_EXTERNAL + cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask = SET_BITS(regUSB1, RSPOL, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && (rs_232.handle_state == HANDLE_IN_USE)) { + rc = PMIC_SUCCESS; + + /* Validate the calling parameters. */ + /*if ((cfgInternal != RS232_TX_USE0VM_RX_UDATVP) && + (cfgInternal != RS232_TX_RX_INTERNAL_DEFAULT) && (cfgInternal != RS232_TX_UDATVP_RX_URXVM)) + { + + rc = PMIC_NOT_SUPPORTED; + } */ + if (cfgInternal == RS232_TX_USE0VM_RX_UDATVP) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + + } else if (cfgInternal == RS232_TX_RX_INTERNAL_DEFAULT) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_mask |= SET_BITS(regUSB1, RSPOL, 1); + + } else if (cfgInternal == RS232_TX_UDATVP_RX_URXVM) { + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 2); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 1); + + } else if ((cfgExternal == RS232_TX_UDM_RX_UDP) || + (cfgExternal == RS232_TX_RX_EXTERNAL_DEFAULT)) { + /* Configure for TX on D+ and RX on D-. */ + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1); + reg_value1 |= SET_BITS(regUSB1, RSPOL, 0); + } else if (cfgExternal != RS232_TX_UDM_RX_UDP) { + /* Any other RS-232 configuration is an error. */ + rc = PMIC_ERROR; + } + + if (rc == PMIC_SUCCESS) { + /* Configure for TX on D- and RX on D+. */ + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask); + + rc = pmic_write_reg(REG_CHARGE_USB_SPARE, + reg_value1, reg_mask); + + if (rc == PMIC_SUCCESS) { + rs_232.rs232CfgInternal = cfgInternal; + rs_232.rs232CfgExternal = cfgExternal; + } + } + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*! + * Get the connectivity interface's current RS-232 operating configuration. + * + * @param handle device handle from open() call + * @param cfgInternal RS-232 transceiver internal connections + * @param cfgExternal RS-232 transceiver external connections + * + * @return PMIC_SUCCESS if the requested mode was retrieved + */ +PMIC_STATUS pmic_convity_rs232_get_config(const PMIC_CONVITY_HANDLE handle, + PMIC_CONVITY_RS232_INTERNAL * + const cfgInternal, + PMIC_CONVITY_RS232_EXTERNAL * + const cfgExternal) +{ + PMIC_STATUS rc = PMIC_ERROR; + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle == rs_232.handle) && + (rs_232.handle_state == HANDLE_IN_USE) && + (cfgInternal != (PMIC_CONVITY_RS232_INTERNAL *) NULL) && + (cfgExternal != (PMIC_CONVITY_RS232_EXTERNAL *) NULL)) { + *cfgInternal = rs_232.rs232CfgInternal; + *cfgExternal = rs_232.rs232CfgExternal; + + rc = PMIC_SUCCESS; + } + + /* Exit the critical section. */ + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * CEA-936-specific configuration and setup functions. + ************************************************************************** + */ + +/*! + * @name CEA-936 Connectivity APIs + * Functions for controlling CEA-936 connectivity. + */ +/*@{*/ + +/*! + * Signal the attached device to exit the current CEA-936 operating mode. + * Returns an error if the current operating mode is not CEA-936. + * + * @param handle device handle from open() call + * @param signal type of exit signal to be sent + * + * @return PMIC_SUCCESS if exit signal was sent + */ +PMIC_STATUS pmic_convity_cea936_exit_signal(const PMIC_CONVITY_HANDLE handle, + const + PMIC_CONVITY_CEA936_EXIT_SIGNAL + signal) +{ + PMIC_STATUS rc = PMIC_ERROR; + unsigned int reg_value = 0; + unsigned int reg_mask = + SET_BITS(regUSB0, IDPD, 1) | SET_BITS(regUSB0, IDPULSE, 1); + + /* Use a critical section to maintain a consistent state. */ + if (down_interruptible(&mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + if ((handle != cea_936.handle) + || (cea_936.handle_state != HANDLE_IN_USE)) { + /* Must return error indication for invalid handle parameter to be + * consistent with other APIs. + */ + rc = PMIC_ERROR; + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = + SET_BITS(regUSB0, IDPULSE, 0) | SET_BITS(regUSB0, IDPD, 0); + } else if (signal == CEA936_UID_PULLDOWN_6MS) { + reg_value = SET_BITS(regUSB0, IDPULSE, 1); + } else if (signal == CEA936_UID_PULLDOWN) { + reg_value = SET_BITS(regUSB0, IDPD, 1); + } else if (signal == CEA936_UDMPULSE) { + reg_value = SET_BITS(regUSB0, DMPULSE, 1); + } + + rc = pmic_write_reg(REG_USB, reg_value, reg_mask); + + up(&mutex); + + return rc; +} + +/*@}*/ + +/************************************************************************** + * Static functions. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Internal Support Functions + * These non-exported internal functions are used to support the functionality + * of the exported connectivity APIs. + */ +/*@{*/ + +/*! + * This internal helper function sets the desired operating mode (either USB + * OTG or RS-232). It must be called with the mutex already acquired. + * + * @param mode the desired operating mode (USB or RS232) + * + * @return PMIC_SUCCESS if the desired operating mode was set + * @return PMIC_NOT_SUPPORTED if the desired operating mode is invalid + */ +static PMIC_STATUS pmic_convity_set_mode_internal(const PMIC_CONVITY_MODE mode) +{ + unsigned int reg_value0 = 0, reg_value1 = 0; + unsigned int reg_mask0 = 0, reg_mask1 = 0; + + //unsigned int reg_mask1 = SET_BITS(regUSB1, VBUSEN, 1); + + PMIC_STATUS rc = PMIC_SUCCESS; + + switch (mode) { + case USB: + /* For the USB mode, we start by tri-stating the USB bus (by + * setting VBUSEN = 0) until a device is connected (i.e., + * until we receive a 4.4V rising edge event). All pull-up + * and pull-down resistors are also disabled until a USB + * device is actually connected and we have determined which + * device is the host and the desired USB bus speed. + * + * Also tri-state the RS-232 buffers (by setting RSTRI = 1). + * This prevents the hardware from automatically returning to + * the RS-232 mode when the USB device is detached. + */ + + reg_value0 = SET_BITS(regUSB0, INTERFACE_MODE, 0); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + /*reg_value1 = SET_BITS(regUSB1, RSTRI, 1); */ + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + /* if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } */ + + break; + + case RS232_1: + /* For the RS-232 mode, we tri-state the USB bus (by setting + * VBUSEN = 0) and enable the RS-232 transceiver (by setting + * RS232ENB = 0). + * + * Note that even in the RS-232 mode, if a USB device is + * plugged in, we will receive a 4.4V rising edge event which + * will automatically disable the RS-232 transceiver and + * tri-state the RS-232 buffers. This allows us to temporarily + * switch over to USB mode while the USB device is attached. + * The RS-232 transceiver and buffers will be automatically + * re-enabled when the USB device is detached. + */ + + /* Explicitly disconnect all of the USB pull-down resistors + * and the VUSB power regulator here just to be safe. + * + * But we do connect the internal pull-up resistor on USB_D+ + * to avoid having an extra load on the USB_D+ line when in + * RS-232 mode. + */ + + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 1) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 0); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case RS232_2: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 2) | + SET_BITS(regUSB0, VBUSPDENB, 1) | + SET_BITS(regUSB0, USB_PU, 1); + reg_mask0 = + SET_BITS(regUSB0, INTERFACE_MODE, 7) | SET_BITS(regUSB0, + VBUSPDENB, + 1) | + SET_BITS(regUSB0, USB_PU, 1); + + reg_value1 = SET_BITS(regUSB1, RSPOL, 1); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + + if (rc == PMIC_SUCCESS) { + CHECK_ERROR(pmic_write_reg + (REG_CHARGE_USB_SPARE, + reg_value1, reg_mask1)); + } + break; + + case CEA936_MONO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 4); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_STEREO: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 5); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_RIGHT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 6); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + case CEA936_TEST_LEFT: + reg_value0 |= SET_BITS(regUSB0, INTERFACE_MODE, 7); + reg_mask0 = SET_BITS(regUSB0, INTERFACE_MODE, 7); + + rc = pmic_write_reg(REG_USB, reg_value0, reg_mask0); + break; + + default: + rc = PMIC_NOT_SUPPORTED; + } + + if (rc == PMIC_SUCCESS) { + if (mode == USB) { + usb.mode = mode; + } else if ((mode == RS232_1) || (mode == RS232_1)) { + rs_232.mode = mode; + } else if ((mode == CEA936_MONO) || (mode == CEA936_STEREO) || + (mode == CEA936_TEST_RIGHT) + || (mode == CEA936_TEST_LEFT)) { + cea_936.mode = mode; + } + } + + return rc; +} + +/*! + * This internal helper function deregisters all of the currently registered + * callback events. It must be called with the mutual exclusion spinlock + * already acquired. + * + * We've defined the event and callback deregistration code here as a separate + * function because it can be called by either the pmic_convity_close() or the + * pmic_convity_clear_callback() APIs. We also wanted to avoid any possible + * issues with having the same thread calling spin_lock_irq() twice. + * + * Note that the mutex must have already been acquired. We will also acquire + * the spinlock here to avoid any possible race conditions with the interrupt + * handler. + * + * @return PMIC_SUCCESS if all of the callback events were cleared + */ +static PMIC_STATUS pmic_convity_deregister_all(void) +{ + unsigned long flags; + PMIC_STATUS rc = PMIC_SUCCESS; + + /* Deregister each of the PMIC events that we had previously + * registered for by using pmic_event_subscribe(). + */ + + if ((usb.eventMask & USB_DETECT_MINI_A) || + (usb.eventMask & USB_DETECT_MINI_B) || + (usb.eventMask & USB_DETECT_NON_USB_ACCESSORY) || + (usb.eventMask & USB_DETECT_FACTORY_MODE)) { + //EVENT_AB_DETI or EVENT_IDI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_ABDET); + + if (pmic_event_unsubscribe(EVENT_IDI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_MINI_A | + USB_DETECT_MINI_B | + USB_DETECT_NON_USB_ACCESSORY | + USB_DETECT_FACTORY_MODE); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_AB_DETI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_0V8_RISE) || + (usb.eventMask & USB_DETECT_0V8_FALL)) { + //EVENT_USB_08VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_0V8); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_0V8_RISE | + USB_DETECT_0V8_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_08VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + + } + + else if ((usb.eventMask & USB_DETECT_2V0_RISE) || + (usb.eventMask & USB_DETECT_2V0_FALL)) { + //EVENT_USB_20VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_2V0); + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_2V0_RISE | + USB_DETECT_2V0_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_20VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + else if ((usb.eventMask & USB_DETECT_4V4_RISE) || + (usb.eventMask & USB_DETECT_4V4_FALL)) { + + //EVENT_USB_44VI or EVENT_USBI + eventNotify.func = pmic_convity_event_handler; + eventNotify.param = (void *)(CORE_EVENT_4V4); + + if (pmic_event_unsubscribe(EVENT_USBI, eventNotify) == + PMIC_SUCCESS) { + + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + usb.eventMask &= ~(USB_DETECT_4V4_RISE | + USB_DETECT_4V4_FALL); + + spin_unlock_irqrestore(&lock, flags); + } else { + pr_debug + ("%s: pmic_event_unsubscribe() for EVENT_USB_44VI failed\n", + __FILE__); + rc = PMIC_ERROR; + } + } + + if (rc == PMIC_SUCCESS) { + /* Also acquire the spinlock here to avoid any possible race + * conditions with the interrupt handler. + */ + spin_lock_irqsave(&lock, flags); + + /* Restore the initial reset values for the callback function + * and event mask parameters. This should be NULL and zero, + * respectively. + * + * Note that we wait until the end here to fully reset everything + * just in case some of the pmic_event_unsubscribe() calls above + * failed for some reason (which normally shouldn't happen). + */ + usb.callback = reset.callback; + usb.eventMask = reset.eventMask; + + spin_unlock_irqrestore(&lock, flags); + } + return rc; +} + +/*! + * This is the default event handler for all connectivity-related events + * and hardware interrupts. + * + * @param param event ID + */ +static void pmic_convity_event_handler(void *param) +{ + unsigned long flags; + + /* Update the global list of active interrupt events. */ + spin_lock_irqsave(&lock, flags); + eventID |= (PMIC_CORE_EVENT) (param); + spin_unlock_irqrestore(&lock, flags); + + /* Schedule the tasklet to be run as soon as it is convenient to do so. */ + schedule_work(&convityTasklet); +} + +/*! + * @brief This is the connectivity driver tasklet that handles interrupt events. + * + * This function is scheduled by the connectivity driver interrupt handler + * pmic_convity_event_handler() to complete the processing of all of the + * connectivity-related interrupt events. + * + * Since this tasklet runs with interrupts enabled, we can safely call + * the ADC driver, if necessary, to properly detect the type of USB connection + * that is being made and to call any user-registered callback functions. + * + * @param arg The parameter that was provided above in + * the DECLARE_TASKLET() macro (unused). + */ +static void pmic_convity_tasklet(struct work_struct *work) +{ + + PMIC_CONVITY_EVENTS activeEvents = 0; + unsigned long flags = 0; + + /* Check the interrupt sense bits to determine exactly what + * event just occurred. + */ + if (eventID & CORE_EVENT_4V4) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_4V4; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB4V4S) ? + USB_DETECT_4V4_RISE : USB_DETECT_4V4_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 4.4 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_2V0) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_2V0; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB2V0S) ? + USB_DETECT_2V0_RISE : USB_DETECT_2V0_FALL; + if (activeEvents & ~usb.eventMask) { + /* The default handler for 2.0 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_0V8) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_0V8; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_USB0V8S) ? + USB_DETECT_0V8_RISE : USB_DETECT_0V8_FALL; + + if (activeEvents & ~usb.eventMask) { + /* The default handler for 0.8 V rising/falling edge detection + * is to simply ignore the event. + */ + ; + } + } + if (eventID & CORE_EVENT_ABDET) { + spin_lock_irqsave(&lock, flags); + eventID &= ~CORE_EVENT_ABDET; + spin_unlock_irqrestore(&lock, flags); + + activeEvents |= pmic_check_sensor(SENSE_ID_GNDS) ? + USB_DETECT_MINI_A : 0; + + activeEvents |= pmic_check_sensor(SENSE_ID_FLOATS) ? + USB_DETECT_MINI_B : 0; + } + + /* Begin a critical section here so that we don't register/deregister + * for events or open/close the connectivity driver while the existing + * event handler (if it is currently defined) is in the middle of handling + * the current event. + */ + spin_lock_irqsave(&lock, flags); + + /* Finally, call the user-defined callback function if required. */ + if ((usb.handle_state == HANDLE_IN_USE) && + (usb.callback != NULL) && (activeEvents & usb.eventMask)) { + (*usb.callback) (activeEvents); + } + + spin_unlock_irqrestore(&lock, flags); +} + +/*@}*/ + +/************************************************************************** + * Module initialization and termination functions. + * + * Note that if this code is compiled into the kernel, then the + * module_init() function will be called within the device_initcall() + * group. + ************************************************************************** + */ + +/*! + * @name Connectivity Driver Loading/Unloading Functions + * These non-exported internal functions are used to support the connectivity + * device driver initialization and de-initialization operations. + */ +/*@{*/ + +/*! + * @brief This is the connectivity device driver initialization function. + * + * This function is called by the kernel when this device driver is first + * loaded. + */ +static int __init mc13783_pmic_convity_init(void) +{ + printk(KERN_INFO "PMIC Connectivity driver loading..\n"); + + return 0; +} + +/*! + * @brief This is the Connectivity device driver de-initialization function. + * + * This function is called by the kernel when this device driver is about + * to be unloaded. + */ +static void __exit mc13783_pmic_convity_exit(void) +{ + printk(KERN_INFO "PMIC Connectivity driver unloading\n"); + + /* Close the device handle if it is still open. This will also + * deregister any callbacks that may still be active. + */ + if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(usb.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(rs_232.handle); + } else if (usb.handle_state == HANDLE_IN_USE) { + pmic_convity_close(cea_936.handle); + } + + /* Reset the PMIC Connectivity register to it's power on state. + * We should do this when unloading the module so that we don't + * leave the hardware in a state which could cause problems when + * no device driver is loaded. + */ + pmic_write_reg(REG_USB, RESET_USBCNTRL_REG_0, REG_FULLMASK); + pmic_write_reg(REG_CHARGE_USB_SPARE, RESET_USBCNTRL_REG_1, + REG_FULLMASK); + /* Note that there is no need to reset the "convity" device driver + * state structure to the reset state since we are in the final + * stage of unloading the device driver. The device driver state + * structure will be automatically and properly reinitialized if + * this device driver is reloaded. + */ +} + +/*@}*/ + +/* + * Module entry points and description information. + */ + +module_init(mc13783_pmic_convity_init); +module_exit(mc13783_pmic_convity_exit); + +MODULE_DESCRIPTION("mc13783 Connectivity device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light.c b/drivers/mxc/pmic/mc13783/pmic_light.c new file mode 100644 index 000000000000..c28cbc3386c3 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light.c @@ -0,0 +1,2769 @@ +/* + * Copyright 2004-2009 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 mc13783/pmic_light.c + * @brief This is the main file of PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/pmic_light.h> +#include <linux/pmic_status.h> +#include "pmic_light_defs.h" + +#define NB_LIGHT_REG 6 + +static int pmic_light_major; + +/*! + * Number of users waiting in suspendq + */ +static int swait = 0; + +/*! + * To indicate whether any of the light devices are suspending + */ +static int suspend_flag = 0; + +/*! + * The suspendq is used to block application calls + */ +static wait_queue_head_t suspendq; + +static struct class *pmic_light_class; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_bklit_tcled_master_enable); +EXPORT_SYMBOL(pmic_bklit_tcled_master_disable); +EXPORT_SYMBOL(pmic_bklit_master_enable); +EXPORT_SYMBOL(pmic_bklit_master_disable); +EXPORT_SYMBOL(pmic_bklit_set_current); +EXPORT_SYMBOL(pmic_bklit_get_current); +EXPORT_SYMBOL(pmic_bklit_set_dutycycle); +EXPORT_SYMBOL(pmic_bklit_get_dutycycle); +EXPORT_SYMBOL(pmic_bklit_set_cycle_time); +EXPORT_SYMBOL(pmic_bklit_get_cycle_time); +EXPORT_SYMBOL(pmic_bklit_set_mode); +EXPORT_SYMBOL(pmic_bklit_get_mode); +EXPORT_SYMBOL(pmic_bklit_rampup); +EXPORT_SYMBOL(pmic_bklit_off_rampup); +EXPORT_SYMBOL(pmic_bklit_rampdown); +EXPORT_SYMBOL(pmic_bklit_off_rampdown); +EXPORT_SYMBOL(pmic_bklit_enable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_disable_edge_slow); +EXPORT_SYMBOL(pmic_bklit_get_edge_slow); +EXPORT_SYMBOL(pmic_bklit_set_strobemode); +EXPORT_SYMBOL(pmic_tcled_enable); +EXPORT_SYMBOL(pmic_tcled_disable); +EXPORT_SYMBOL(pmic_tcled_get_mode); +EXPORT_SYMBOL(pmic_tcled_ind_set_current); +EXPORT_SYMBOL(pmic_tcled_ind_get_current); +EXPORT_SYMBOL(pmic_tcled_ind_set_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_ind_get_blink_pattern); +EXPORT_SYMBOL(pmic_tcled_fun_set_current); +EXPORT_SYMBOL(pmic_tcled_fun_get_current); +EXPORT_SYMBOL(pmic_tcled_fun_set_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_get_cycletime); +EXPORT_SYMBOL(pmic_tcled_fun_set_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_get_dutycycle); +EXPORT_SYMBOL(pmic_tcled_fun_blendedramps); +EXPORT_SYMBOL(pmic_tcled_fun_sawramps); +EXPORT_SYMBOL(pmic_tcled_fun_blendedbowtie); +EXPORT_SYMBOL(pmic_tcled_fun_chasinglightspattern); +EXPORT_SYMBOL(pmic_tcled_fun_strobe); +EXPORT_SYMBOL(pmic_tcled_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampup); +EXPORT_SYMBOL(pmic_tcled_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_get_fun_rampdown); +EXPORT_SYMBOL(pmic_tcled_fun_triode_on); +EXPORT_SYMBOL(pmic_tcled_fun_triode_off); +EXPORT_SYMBOL(pmic_tcled_enable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_disable_edge_slow); +EXPORT_SYMBOL(pmic_tcled_enable_half_current); +EXPORT_SYMBOL(pmic_tcled_disable_half_current); +EXPORT_SYMBOL(pmic_tcled_enable_audio_modulation); +EXPORT_SYMBOL(pmic_tcled_disable_audio_modulation); +EXPORT_SYMBOL(pmic_bklit_set_boost_mode); +EXPORT_SYMBOL(pmic_bklit_get_boost_mode); +EXPORT_SYMBOL(pmic_bklit_config_boost_mode); +EXPORT_SYMBOL(pmic_bklit_gets_boost_mode); + +/*! + * This is the suspend of power management for the pmic light API. + * It suports SAVE and POWER_DOWN state. + * + * @param pdev the device + * @param state the state + * + * @return This function returns 0 if successful. + */ +static int pmic_light_suspend(struct platform_device *dev, pm_message_t state) +{ + suspend_flag = 1; + /* switch off all leds and backlights */ + CHECK_ERROR(pmic_light_init_reg()); + + return 0; +}; + +/*! + * This is the resume of power management for the pmic light API. + * It suports RESTORE state. + * + * @param dev the device + * + * @return This function returns 0 if successful. + */ +static int pmic_light_resume(struct platform_device *pdev) +{ + suspend_flag = 0; + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +/*! + * This function enables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_tcled_master_enable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 1); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight & tcled. + * + * @return This function returns PMIC_SUCCESS if successful + */ +PMIC_STATUS pmic_bklit_tcled_master_disable(void) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + reg_value = BITFVAL(BIT_LEDEN, 0); + mask = BITFMASK(BIT_LEDEN); + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_enable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight. Not supported on mc13783 + * Use pmic_bklit_tcled_master_enable. + * + * @return This function returns PMIC_NOT_SUPPORTED + */ +PMIC_STATUS pmic_bklit_master_disable(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function sets backlight current level. + * + * @param channel Backlight channel + * @param level Backlight current level, as the following table. + * @verbatim + level main & aux keyboard + ------ ----------- -------- + 0 0 mA 0 mA + 1 3 mA 12 mA + 2 6 mA 24 mA + 3 9 mA 36 mA + 4 12 mA 48 mA + 5 15 mA 60 mA + 6 18 mA 72 mA + 7 21 mA 84 mA + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_current(t_bklit_channel channel, unsigned char level) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + value = BITFVAL(BIT_CL_MAIN, level); + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + value = BITFVAL(BIT_CL_AUX, level); + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + value = BITFVAL(BIT_CL_KEY, level); + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives backlight current level. + * The channels are not individually adjustable, hence + * the channel parameter is ignored. + * + * @param channel Backlight channel (Ignored because the + * channels are not individually adjustable) + * @param level Pointer to store backlight current level result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_current(t_bklit_channel channel, + unsigned char *level) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *level = BITFEXT(reg_value, BIT_CL_MAIN); + break; + case BACKLIGHT_LED2: + *level = BITFEXT(reg_value, BIT_CL_AUX); + break; + case BACKLIGHT_LED3: + *level = BITFEXT(reg_value, BIT_CL_KEY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel duty cycle. + * LED perceived brightness for each zone may be individually set by setting + * duty cycle. The default setting is for 0% duty cycle; this keeps all zone + * drivers turned off even after the master enable command. Each LED current + * sink can be turned on and adjusted for brightness with an independent 4 bit + * word for a duty cycle ranging from 0% to 100% in approximately 6.7% steps. + * + * @param channel Backlight channel. + * @param dc Backlight duty cycle, as the following table. + * @verbatim + dc Duty Cycle (% On-time over Cycle Time) + ------ --------------------------------------- + 0 0% + 1 6.7% + 2 13.3% + 3 20% + 4 26.7% + 5 33.3% + 6 40% + 7 46.7% + 8 53.3% + 9 60% + 10 66.7% + 11 73.3% + 12 80% + 13 86.7% + 14 93.3% + 15 100% + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_dutycycle(t_bklit_channel channel, unsigned char dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (dc > 15) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + reg_value = reg_value & (~MASK_DUTY_CYCLE); + reg_value = reg_value | (dc << BIT_DUTY_CYCLE); + break; + case BACKLIGHT_LED2: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_AUX)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + reg_value = reg_value & (~(MASK_DUTY_CYCLE << INDEX_KYD)); + reg_value = reg_value | (dc << (BIT_DUTY_CYCLE + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(LREG_2, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; + +} + +/*! + * This function retrives a backlight channel duty cycle. + * + * @param channel Backlight channel. + * @param dc Pointer to backlight duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_dutycycle(t_bklit_channel channel, unsigned char *dc) +{ + unsigned int reg_value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + CHECK_ERROR(pmic_read_reg(LREG_2, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE)) + >> BIT_DUTY_CYCLE); + + break; + case BACKLIGHT_LED2: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << INDEX_AUX)) + >> (BIT_DUTY_CYCLE + INDEX_AUX)); + break; + case BACKLIGHT_LED3: + *dc = (int)((reg_value & (MASK_DUTY_CYCLE << + INDEX_KYD)) >> (BIT_DUTY_CYCLE + + INDEX_KYD)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a backlight channel cycle time. + * Cycle Time is defined as the period of a complete cycle of + * Time_on + Time_off. The default Cycle Time is set to 0.01 seconds such that + * the 100 Hz on-off cycling is averaged out by the eye to eliminate + * flickering. Additionally, the Cycle Time can be programmed to intentionally + * extend the period of on-off cycles for a visual pulsating or blinking effect. + * + * @param period Backlight cycle time, as the following table. + * @verbatim + period Cycle Time + -------- ------------ + 0 0.01 seconds + 1 0.1 seconds + 2 0.5 seconds + 3 2 seconds + @endverbatim + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_cycle_time(unsigned char period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + if (period > 3) { + return PMIC_PARAMETER_ERROR; + } + mask = BITFMASK(BIT_PERIOD); + value = BITFVAL(BIT_PERIOD, period); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives a backlight channel cycle time setting. + * + * @param period Pointer to save backlight cycle time setting result. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_cycle_time(unsigned char *period) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *period = BITFEXT(value, BIT_PERIOD); + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_set_mode(t_bklit_channel channel, t_bklit_mode mode) +{ + unsigned int reg_value = 0; + unsigned int clear_val = 0; + unsigned int triode_val = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, PMIC_ALL_BITS)); + + switch (channel) { + case BACKLIGHT_LED1: + clear_val = ~(MASK_TRIODE_MAIN_BL); + triode_val = MASK_TRIODE_MAIN_BL; + break; + case BACKLIGHT_LED2: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_AUXILIARY); + break; + case BACKLIGHT_LED3: + clear_val = ~(MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + triode_val = (MASK_TRIODE_MAIN_BL << INDEX_KEYPAD); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + reg_value = (reg_value & clear_val); + + if (mode == BACKLIGHT_TRIODE_MODE) { + reg_value = (reg_value | triode_val); + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, PMIC_ALL_BITS)); + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight operation mode. There are two modes of + * operations: current control and triode mode. + * The Duty Cycle/Cycle Time control is retained in Triode Mode. Audio + * coupling is not available in Triode Mode. + * + * @param channel Backlight channel. + * @param mode Backlight operation mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_mode(t_bklit_channel channel, t_bklit_mode * mode) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, ®_value, mask)); + + switch (channel) { + case BACKLIGHT_LED1: + *mode = BITFEXT(reg_value, BIT_TRIODE_MAIN_BL); + break; + case BACKLIGHT_LED2: + *mode = BITFEXT(reg_value, BIT_TRIODE_AUX_BL); + break; + case BACKLIGHT_LED3: + *mode = BITFEXT(reg_value, BIT_TRIODE_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp up function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + reg_value = BITFVAL(BIT_UP_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + reg_value = BITFVAL(BIT_UP_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + reg_value = BITFVAL(BIT_UP_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp up function; + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampup(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_UP_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_UP_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_UP_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function starts backlight brightness ramp down function; ramp time is + * fixed at 0.5 seconds. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + reg_value = BITFVAL(BIT_DOWN_MAIN_BL, 1); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + reg_value = BITFVAL(BIT_DOWN_AUX_BL, 1); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + reg_value = BITFVAL(BIT_DOWN_KEY_BL, 1); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function stops backlight brightness ramp down function. + * + * @param channel Backlight channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_off_rampdown(t_bklit_channel channel) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (channel) { + case BACKLIGHT_LED1: + mask = BITFMASK(BIT_DOWN_MAIN_BL); + break; + case BACKLIGHT_LED2: + mask = BITFMASK(BIT_DOWN_AUX_BL); + break; + case BACKLIGHT_LED3: + mask = BITFMASK(BIT_DOWN_KEY_BL); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, reg_value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight analog edge slowing mode. Analog Edge + * Slowing slows down the transient edges to reduce the chance of coupling LED + * modulation activity into other circuits. Rise and fall times will be targeted + * for approximately 50usec. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_enable_edge_slow(void) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + value = BITFVAL(BIT_SLEWLIMBL, 1); + CHECK_ERROR(pmic_write_reg(LREG_2, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables backlight analog edge slowing mode. The backlight + * drivers will default to an <93>Instant On<94> mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_disable_edge_slow(void) +{ + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets backlight analog edge slowing mode. DThe backlight + * + * @param edge Edge slowing mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_bklit_get_edge_slow(bool * edge) +{ + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_SLEWLIMBL); + CHECK_ERROR(pmic_read_reg(LREG_2, &value, mask)); + *edge = (bool) BITFEXT(value, BIT_SLEWLIMBL); + + return PMIC_SUCCESS; +} + +/*! + * This function sets backlight Strobe Light Pulsing mode. + * + * @param channel Backlight channel. + * @param mode Strobe Light Pulsing mode. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_bklit_set_strobemode(t_bklit_channel channel, + t_bklit_strobe_mode mode) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables tri-color LED. + * + * @param mode Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable(t_tcled_mode mode, t_funlight_bank bank) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (mode) { + case TCLED_FUN_MODE: + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + break; + case TCLED_IND_MODE: + mask = MASK_BK1_FL | MASK_BK2_FL | MASK_BK3_FL; + break; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables tri-color LED. + * + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + * + */ +PMIC_STATUS pmic_tcled_disable(t_funlight_bank bank) +{ + unsigned int mask = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, 0, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED operation mode. + * + * @param mode Pointer to Tri-color LED operation mode. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_mode(t_tcled_mode * mode, t_funlight_bank bank) +{ + unsigned int val; + unsigned int mask; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_0, &val, mask)); + + if (val) { + *mode = TCLED_FUN_MODE; + } else { + *mode = TCLED_IND_MODE; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_current(t_ind_channel channel, + t_tcled_cur_level level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in indicator mode. + * + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_current(t_ind_channel channel, + t_tcled_cur_level * level, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_IND_GREEN: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_IND_BLUE: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel blinking pattern in indication + * mode. + * + * @param channel Tri-color LED channel. + * @param pattern Blinking pattern. + * @param skip If true, skip a cycle after each cycle. + * @param bank Selected tri-color bank + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_set_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern pattern, + bool skip, t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (skip == true) { + return PMIC_NOT_SUPPORTED; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + value = BITFVAL(BITS_DC_RED, pattern); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + value = BITFVAL(BITS_DC_GREEN, pattern); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + value = BITFVAL(BITS_DC_BLUE, pattern); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel blinking pattern in + * indication mode. + * + * @param channel Tri-color LED channel. + * @param pattern Pointer to Blinking pattern. + * @param skip Pointer to a boolean varible indicating if skip + * @param bank Selected tri-color bank + * a cycle after each cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_ind_get_blink_pattern(t_ind_channel channel, + t_tcled_ind_blink_pattern * + pattern, bool * skip, + t_funlight_bank bank) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_IND_RED: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_IND_GREEN: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_IND_RED: + *pattern = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_IND_GREEN: + *pattern = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_IND_BLUE: + *pattern = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel current level in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level level) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (level > TCLED_CUR_LEVEL_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_CL_RED, level); + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_CL_GREEN, level); + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_CL_BLUE, level); + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel current level + * in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param level Pointer to current level. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_current(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_cur_level * level) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *level = BITFEXT(value, BITS_CL_RED); + break; + case TCLED_FUN_CHANNEL2: + *level = BITFEXT(value, BITS_CL_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *level = BITFEXT(value, BITS_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets tri-color LED cycle time. + * + * @param bank Tri-color LED bank + * @param ct Cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + value = BITFVAL(BIT_PERIOD, ct); + mask = BITFMASK(BIT_PERIOD); + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function retrives tri-color LED cycle time in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param ct Pointer to cycle time. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_cycletime(t_funlight_bank bank, + t_tcled_fun_cycle_time * ct) +{ + unsigned int reg_conf = 0; + unsigned int mask; + unsigned int value; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (*ct > TC_CYCLE_TIME_4) { + return PMIC_PARAMETER_ERROR; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BIT_PERIOD); + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + *ct = BITFVAL(BIT_PERIOD, value); + + return PMIC_SUCCESS; +} + +/*! + * This function sets a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_set_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + value = BITFVAL(BITS_DC_RED, dc); + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + value = BITFVAL(BITS_DC_GREEN, dc); + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + value = BITFVAL(BITS_DC_BLUE, dc); + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_conf, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives a tri-color LED channel duty cycle in Fun Light mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param dc Pointer to duty cycle. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_get_dutycycle(t_funlight_bank bank, + t_funlight_channel channel, + unsigned char *dc) +{ + unsigned int reg_conf = 0; + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + reg_conf = LREG_3; + break; + case TCLED_FUN_BANK2: + reg_conf = LREG_4; + break; + case TCLED_FUN_BANK3: + reg_conf = LREG_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = BITFMASK(BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + mask = BITFMASK(BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + mask = BITFMASK(BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg_conf, &value, mask)); + + switch (channel) { + case TCLED_FUN_CHANNEL1: + *dc = BITFEXT(value, BITS_DC_RED); + break; + case TCLED_FUN_CHANNEL2: + *dc = BITFEXT(value, BITS_DC_GREEN); + break; + case TCLED_FUN_CHANNEL3: + *dc = BITFEXT(value, BITS_DC_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Saw Ramp fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_sawramps(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, SAW_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Blended Bowtie fun light pattern. + * + * @param bank Tri-color LED bank + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_blendedbowtie(t_funlight_bank bank, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_SLOW); + break; + case TC_FAST: + value = BITFVAL(BITS_FUN_LIGHT, BLENDED_INVERSE_RAMPS_FAST); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Chasing Lights fun light pattern. + * + * @param bank Tri-color LED bank + * @param pattern Chasing light pattern mode. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_chasinglightspattern(t_funlight_bank bank, + t_chaselight_pattern pattern, + t_tcled_fun_speed speed) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + if (pattern > BGR) { + return PMIC_PARAMETER_ERROR; + } + + switch (speed) { + case TC_OFF: + value = BITFVAL(BITS_FUN_LIGHT, FUN_LIGHTS_OFF); + break; + case TC_SLOW: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_SLOW); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_SLOW); + } + break; + case TC_FAST: + if (pattern == PMIC_RGB) { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_RGB_FAST); + } else { + value = + BITFVAL(BITS_FUN_LIGHT, CHASING_LIGHTS_BGR_FAST); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + mask = BITFMASK(BITS_FUN_LIGHT); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Strobe Mode fun light pattern. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param speed Speed of pattern. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_strobe(t_funlight_bank bank, + t_funlight_channel channel, + t_tcled_fun_strobe_speed speed) +{ + /* not supported on mc13783 */ + + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + value = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + value = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + value = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampup) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets Tri-color LED brightness Ramp Up function; Ramp time + * is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampup Ramp-up configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampup(t_funlight_bank bank, + t_funlight_channel channel, bool * rampup) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPUP; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPUP; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPUP; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampup = true; + } else { + *rampup = false; + } + + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, bool rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + value = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + value = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + value = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + value = value; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + value = value * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + value = value * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (!rampdown) { + value = 0; + } + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + return PMIC_SUCCESS; +} + +/*! + * This function initiates Tri-color LED brightness Ramp Down function; Ramp + * time is fixed at 1 second. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * @param rampdown Ramp-down configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_get_fun_rampdown(t_funlight_bank bank, + t_funlight_channel channel, + bool * rampdown) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = LEDR1RAMPDOWN; + break; + case TCLED_FUN_BANK2: + mask = LEDR2RAMPDOWN; + break; + case TCLED_FUN_BANK3: + mask = LEDR3RAMPDOWN; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + switch (channel) { + case TCLED_FUN_CHANNEL1: + mask = mask; + break; + case TCLED_FUN_CHANNEL2: + mask = mask * 2; + break; + case TCLED_FUN_CHANNEL3: + mask = mask * 4; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(LREG_1, &value, mask)); + if (value) { + *rampdown = true; + } else { + *rampdown = false; + } + return PMIC_SUCCESS; +} + +/*! + * This function enables a Tri-color channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_on(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + value = ENABLE_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + value = ENABLE_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + value = ENABLE_BK2_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables a Tri-color LED channel triode mode. + * + * @param bank Tri-color LED bank + * @param channel Tri-color LED channel. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_fun_triode_off(t_funlight_bank bank, + t_funlight_channel channel) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + switch (bank) { + case TCLED_FUN_BANK1: + mask = MASK_BK1_FL; + break; + case TCLED_FUN_BANK2: + mask = MASK_BK2_FL; + break; + case TCLED_FUN_BANK3: + mask = MASK_BK3_FL; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 1); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED edge slowing. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_edge_slow(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_SLEWLIMTC, 0); + mask = BITFMASK(BIT_SLEWLIMTC); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_enable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 1); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function disables Tri-color LED half current mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_tcled_disable_half_current(void) +{ + unsigned int mask = 0; + unsigned int value = 0; + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_TC1HALF, 0); + mask = BITFMASK(BIT_TC1HALF); + + CHECK_ERROR(pmic_write_reg(LREG_1, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function enables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_enable_audio_modulation(t_led_channel channel, + t_aud_path path, + t_aud_gain gain, bool lpf_bypass) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function disables backlight or Tri-color LED audio modulation. + * + * @return This function returns PMIC_NOT_SUPPORTED. + */ +PMIC_STATUS pmic_tcled_disable_audio_modulation(void) +{ + return PMIC_NOT_SUPPORTED; +} + +/*! + * This function enables the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_set_boost_mode(bool en_dis) +{ + + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + + value = BITFVAL(BIT_BOOSTEN, en_dis); + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets the boost mode. + * Only on mc13783 2.0 or higher + * + * @param en_dis Enable or disable the boost mode + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_get_boost_mode(bool * en_dis) +{ + pmic_version_t mc13783_ver; + unsigned int mask; + unsigned int value; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + + if (suspend_flag == 1) { + return -EBUSY; + } + mask = BITFMASK(BIT_BOOSTEN); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *en_dis = BITFEXT(value, BIT_BOOSTEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function sets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_config_boost_mode(unsigned int abms, unsigned int abr) +{ + unsigned int conf_boost = 0; + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + if (abms > MAX_BOOST_ABMS) { + return PMIC_PARAMETER_ERROR; + } + + if (abr > MAX_BOOST_ABR) { + return PMIC_PARAMETER_ERROR; + } + + conf_boost = abms | (abr << 3); + + value = BITFVAL(BITS_BOOST, conf_boost); + mask = BITFMASK(BITS_BOOST); + CHECK_ERROR(pmic_write_reg(LREG_0, value, mask)); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets boost mode configuration + * Only on mc13783 2.0 or higher + * + * @param abms Define adaptive boost mode selection + * @param abr Define adaptive boost reference + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_bklit_gets_boost_mode(unsigned int *abms, unsigned int *abr) +{ + unsigned int mask; + unsigned int value; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + if (suspend_flag == 1) { + return -EBUSY; + } + + mask = BITFMASK(BITS_BOOST_ABMS); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abms = BITFEXT(value, BITS_BOOST_ABMS); + + mask = BITFMASK(BITS_BOOST_ABR); + CHECK_ERROR(pmic_read_reg(LREG_0, &value, mask)); + *abr = BITFEXT(value, BITS_BOOST_ABR); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function implements IOCTL controls on a PMIC Light device. + * + + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_light_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + t_bklit_setting_param *bklit_setting = NULL; + t_tcled_enable_param *tcled_setting; + t_fun_param *fun_param; + t_tcled_ind_param *tcled_ind; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + switch (cmd) { + case PMIC_BKLIT_TCLED_ENABLE: + pmic_bklit_tcled_master_enable(); + break; + + case PMIC_BKLIT_TCLED_DISABLE: + pmic_bklit_tcled_master_disable(); + break; + + case PMIC_BKLIT_ENABLE: + pmic_bklit_master_enable(); + break; + + case PMIC_BKLIT_DISABLE: + pmic_bklit_master_disable(); + break; + + case PMIC_SET_BKLIT: + if ((bklit_setting = kmalloc(sizeof(t_bklit_setting_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_set_mode(bklit_setting->channel, + bklit_setting->mode), + (kfree(bklit_setting))); + + CHECK_ERROR_KFREE(pmic_bklit_set_current(bklit_setting->channel, + bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_dutycycle + (bklit_setting->channel, + bklit_setting->duty_cycle), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_cycle_time + (bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_set_boost_mode + (bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_config_boost_mode + (bklit_setting->abms, bklit_setting->abr), + (kfree(bklit_setting))); + if (bklit_setting->edge_slow != false) { + CHECK_ERROR_KFREE(pmic_bklit_enable_edge_slow(), + (kfree(bklit_setting))); + } else { + CHECK_ERROR_KFREE(pmic_bklit_disable_edge_slow(), + (kfree(bklit_setting))); + } + + kfree(bklit_setting); + break; + + case PMIC_GET_BKLIT: + if ((bklit_setting = kmalloc(sizeof(t_bklit_setting_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + + if (copy_from_user(bklit_setting, (t_bklit_setting_param *) arg, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + + CHECK_ERROR_KFREE(pmic_bklit_get_current(bklit_setting->channel, + &bklit_setting-> + current_level), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_cycle_time + (&bklit_setting->cycle_time), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_dutycycle + (bklit_setting->channel, + &bklit_setting->duty_cycle), + (kfree(bklit_setting))); + bklit_setting->strobe = BACKLIGHT_STROBE_NONE; + CHECK_ERROR_KFREE(pmic_bklit_get_mode(bklit_setting->channel, + &bklit_setting->mode), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_edge_slow + (&bklit_setting->edge_slow), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_get_boost_mode + (&bklit_setting->en_dis), + (kfree(bklit_setting))); + CHECK_ERROR_KFREE(pmic_bklit_gets_boost_mode + (&bklit_setting->abms, &bklit_setting->abr), + (kfree(bklit_setting))); + + if (copy_to_user((t_bklit_setting_param *) arg, bklit_setting, + sizeof(t_bklit_setting_param))) { + kfree(bklit_setting); + return -EFAULT; + } + kfree(bklit_setting); + break; + + case PMIC_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_rampup((t_bklit_channel) arg)); + break; + + case PMIC_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPUP_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampup((t_bklit_channel) arg)); + break; + + case PMIC_OFF_RAMPDOWN_BKLIT: + CHECK_ERROR(pmic_bklit_off_rampdown((t_bklit_channel) arg)); + break; + + case PMIC_TCLED_ENABLE: + if ((tcled_setting = kmalloc(sizeof(t_tcled_enable_param), + GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + + if (copy_from_user(tcled_setting, (t_tcled_enable_param *) arg, + sizeof(t_tcled_enable_param))) { + kfree(tcled_setting); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_enable(tcled_setting->mode, + tcled_setting->bank), + (kfree(bklit_setting))); + break; + + case PMIC_TCLED_DISABLE: + CHECK_ERROR(pmic_tcled_disable((t_funlight_bank) arg)); + break; + + case PMIC_TCLED_PATTERN: + if ((fun_param = kmalloc(sizeof(t_fun_param), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + if (copy_from_user(fun_param, + (t_fun_param *) arg, sizeof(t_fun_param))) { + kfree(fun_param); + return -EFAULT; + } + + switch (fun_param->pattern) { + case BLENDED_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case SAW_RAMPS_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case SAW_RAMPS_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_sawramps + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_SLOW), + (kfree(fun_param))); + break; + + case BLENDED_BOWTIE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_blendedbowtie + (fun_param->bank, TC_FAST), + (kfree(fun_param))); + break; + + case STROBE_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, fun_param->channel, + TC_STROBE_SLOW), (kfree(fun_param))); + break; + + case STROBE_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_strobe + (fun_param->bank, + fun_param->channel, TC_STROBE_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_RGB_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, PMIC_RGB, TC_FAST), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_SLOW: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_SLOW), + (kfree(fun_param))); + break; + + case CHASING_LIGHT_BGR_FAST: + CHECK_ERROR_KFREE(pmic_tcled_fun_chasinglightspattern + (fun_param->bank, BGR, TC_FAST), + (kfree(fun_param))); + break; + } + + kfree(fun_param); + break; + + case PMIC_SET_TCLED: + if ((tcled_ind = kmalloc(sizeof(t_tcled_ind_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_set_current(tcled_ind->channel, + tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_set_blink_pattern + (tcled_ind->channel, tcled_ind->pattern, + tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + tcled_ind->rampdown), (kfree(tcled_ind))); + if (tcled_ind->half_current) { + CHECK_ERROR_KFREE(pmic_tcled_enable_half_current(), + (kfree(tcled_ind))); + } else { + CHECK_ERROR_KFREE(pmic_tcled_disable_half_current(), + (kfree(tcled_ind))); + } + + kfree(tcled_ind); + break; + + case PMIC_GET_TCLED: + if ((tcled_ind = kmalloc(sizeof(t_tcled_ind_param), GFP_KERNEL)) + == NULL) { + return -ENOMEM; + } + if (copy_from_user(tcled_ind, (t_tcled_ind_param *) arg, + sizeof(t_tcled_ind_param))) { + kfree(tcled_ind); + return -EFAULT; + } + CHECK_ERROR_KFREE(pmic_tcled_ind_get_current(tcled_ind->channel, + &tcled_ind->level, + tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_ind_get_blink_pattern + (tcled_ind->channel, &tcled_ind->pattern, + &tcled_ind->skip, tcled_ind->bank), + (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampup + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampup), (kfree(tcled_ind))); + CHECK_ERROR_KFREE(pmic_tcled_get_fun_rampdown + (tcled_ind->bank, tcled_ind->channel, + &tcled_ind->rampdown), (kfree(tcled_ind))); + if (copy_to_user + ((t_tcled_ind_param *) arg, tcled_ind, + sizeof(t_tcled_ind_param))) { + return -EFAULT; + } + kfree(tcled_ind); + + break; + + default: + return -EINVAL; + } + return 0; +} + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void) +{ + CHECK_ERROR(pmic_write_reg(LREG_0, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_3, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_4, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(LREG_5, 0, PMIC_ALL_BITS)); + return 0; +} + +/*! + * This function implements the open method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_open(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +/*! + * This function implements the release method on a mc13783 light device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_light_release(struct inode *inode, struct file *file) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + return 0; +} + +static struct file_operations pmic_light_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_light_ioctl, + .open = pmic_light_open, + .release = pmic_light_release, +}; + +static int pmic_light_remove(struct platform_device *pdev) +{ + device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + class_destroy(pmic_light_class); + unregister_chrdev(pmic_light_major, "pmic_light"); + return 0; +} + +static int pmic_light_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) { + return -ERESTARTSYS; + } + } + pmic_light_major = register_chrdev(0, "pmic_light", &pmic_light_fops); + + if (pmic_light_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_light\n"); + return pmic_light_major; + } + init_waitqueue_head(&suspendq); + + pmic_light_class = class_create(THIS_MODULE, "pmic_light"); + if (IS_ERR(pmic_light_class)) { + printk(KERN_ERR "Error creating pmic_light class.\n"); + ret = PTR_ERR(pmic_light_class); + goto err_out1; + } + + temp_class = device_create(pmic_light_class, NULL, + MKDEV(pmic_light_major, 0), NULL, + "pmic_light"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic_light class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + ret = pmic_light_init_reg(); + if (ret != PMIC_SUCCESS) { + goto err_out3; + } + + printk(KERN_INFO "PMIC Light successfully loaded\n"); + return ret; + + err_out3: + device_destroy(pmic_light_class, MKDEV(pmic_light_major, 0)); + err_out2: + class_destroy(pmic_light_class); + err_out1: + unregister_chrdev(pmic_light_major, "pmic_light"); + return ret; +} + +static struct platform_driver pmic_light_driver_ldm = { + .driver = { + .name = "pmic_light", + }, + .suspend = pmic_light_suspend, + .resume = pmic_light_resume, + .probe = pmic_light_probe, + .remove = pmic_light_remove, +}; + +/* + * Initialization and Exit + */ + +static int __init pmic_light_init(void) +{ + pr_debug("PMIC Light driver loading...\n"); + return platform_driver_register(&pmic_light_driver_ldm); +} +static void __exit pmic_light_exit(void) +{ + platform_driver_unregister(&pmic_light_driver_ldm); + pr_debug("PMIC Light driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_light_init); +module_exit(pmic_light_exit); + +MODULE_DESCRIPTION("PMIC_LIGHT"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_light_defs.h b/drivers/mxc/pmic/mc13783/pmic_light_defs.h new file mode 100644 index 000000000000..80082363d9ed --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_light_defs.h @@ -0,0 +1,144 @@ +/* + * 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 mc13783/pmic_light_defs.h + * @brief This is the internal header PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +#ifndef __MC13783_LIGHT_DEFS_H__ +#define __MC13783_LIGHT_DEFS_H__ + +#define LREG_0 REG_LED_CONTROL_0 +#define LREG_1 REG_LED_CONTROL_1 +#define LREG_2 REG_LED_CONTROL_2 +#define LREG_3 REG_LED_CONTROL_3 +#define LREG_4 REG_LED_CONTROL_4 +#define LREG_5 REG_LED_CONTROL_5 + +/* REG_LED_CONTROL_0 */ + +#define BIT_LEDEN_LSH 0 +#define BIT_LEDEN_WID 1 +#define MASK_TRIODE_MAIN_BL 0x080 +#define INDEX_AUXILIARY 1 +#define INDEX_KEYPAD 2 +#define BITS_FUN_LIGHT_LSH 17 +#define BITS_FUN_LIGHT_WID 4 +#define MASK_FUN_LIGHT 0x1E0000 +#define MASK_BK1_FL 0x200000 +#define ENABLE_BK1_FL 0x200000 +#define MASK_BK2_FL 0x400000 +#define ENABLE_BK2_FL 0x400000 +#define MASK_BK3_FL 0x800000 +#define ENABLE_BK3_FL 0x800000 +#define BIT_UP_MAIN_BL_LSH 1 +#define BIT_UP_MAIN_BL_WID 1 +#define BIT_UP_AUX_BL_LSH 2 +#define BIT_UP_AUX_BL_WID 1 +#define BIT_UP_KEY_BL_LSH 3 +#define BIT_UP_KEY_BL_WID 1 +#define BIT_DOWN_MAIN_BL_LSH 4 +#define BIT_DOWN_MAIN_BL_WID 1 +#define BIT_DOWN_AUX_BL_LSH 5 +#define BIT_DOWN_AUX_BL_WID 1 +#define BIT_DOWN_KEY_BL_LSH 6 +#define BIT_DOWN_KEY_BL_WID 1 +#define BIT_TRIODE_MAIN_BL_LSH 7 +#define BIT_TRIODE_MAIN_BL_WID 1 +#define BIT_TRIODE_AUX_BL_LSH 8 +#define BIT_TRIODE_AUX_BL_WID 1 +#define BIT_TRIODE_KEY_BL_LSH 9 +#define BIT_TRIODE_KEY_BL_WID 1 + +#define BIT_BOOSTEN_LSH 10 +#define BIT_BOOSTEN_WID 1 +#define BITS_BOOST_LSH 11 +#define BITS_BOOST_WID 5 +#define BITS_BOOST_ABMS_LSH 11 +#define BITS_BOOST_ABMS_WID 3 +#define BITS_BOOST_ABR_LSH 14 +#define BITS_BOOST_ABR_WID 2 + +#define MAX_BOOST_ABMS 7 +#define MAX_BOOST_ABR 3 + +/* REG_LED_CONTROL_1 */ + +#define BIT_SLEWLIMTC_LSH 23 +#define BIT_SLEWLIMTC_WID 1 +#define BIT_TC1HALF_LSH 18 +#define BIT_TC1HALF_WID 1 +#define LEDR1RAMPUP 0x000001 +#define LEDR2RAMPUP 0x000040 +#define LEDR3RAMPUP 0x001000 +#define LEDR1RAMPDOWN 0x000008 +#define LEDR2RAMPDOWN 0x000200 +#define LEDR3RAMPDOWN 0x008000 + +/* REG_LED_CONTROL_2 */ + +#define BIT_SLEWLIMBL_LSH 23 +#define BIT_SLEWLIMBL_WID 1 +#define BIT_DUTY_CYCLE 9 +#define MASK_DUTY_CYCLE 0x001E00 +#define INDEX_AUX 4 +#define INDEX_KYD 8 +#define BIT_CL_MAIN_LSH 0 +#define BIT_CL_MAIN_WID 3 +#define BIT_CL_AUX_LSH 3 +#define BIT_CL_AUX_WID 3 +#define BIT_CL_KEY_LSH 6 +#define BIT_CL_KEY_WID 3 + +/* REG_LED_CONTROL_3 4 5 */ +#define BITS_CL_RED_LSH 0 +#define BITS_CL_RED_WID 2 +#define BITS_CL_GREEN_LSH 2 +#define BITS_CL_GREEN_WID 2 +#define BITS_CL_BLUE_LSH 4 +#define BITS_CL_BLUE_WID 2 +#define BITS_DC_RED_LSH 6 +#define BITS_DC_RED_WID 5 +#define BITS_DC_GREEN_LSH 11 +#define BITS_DC_GREEN_WID 5 +#define BITS_DC_BLUE_LSH 16 +#define BITS_DC_BLUE_WID 5 +#define BIT_PERIOD_LSH 21 +#define BIT_PERIOD_WID 2 + +#define DUTY_CYCLE_MAX 31 + +/* Fun light pattern */ +#define BLENDED_RAMPS_SLOW 0 +#define BLENDED_RAMPS_FAST 1 +#define SAW_RAMPS_SLOW 2 +#define SAW_RAMPS_FAST 3 +#define BLENDED_INVERSE_RAMPS_SLOW 4 +#define BLENDED_INVERSE_RAMPS_FAST 5 +#define CHASING_LIGHTS_RGB_SLOW 6 +#define CHASING_LIGHTS_RGB_FAST 7 +#define CHASING_LIGHTS_BGR_SLOW 8 +#define CHASING_LIGHTS_BGR_FAST 9 +#define FUN_LIGHTS_OFF 15 + +/*! + * This function initialize Light registers of mc13783 with 0. + * + * @return This function returns 0 if successful. + */ +int pmic_light_init_reg(void); + +#endif /* __MC13783_LIGHT_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_power.c b/drivers/mxc/pmic/mc13783/pmic_power.c new file mode 100644 index 000000000000..8f877b887e16 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power.c @@ -0,0 +1,3146 @@ +/* + * Copyright 2004-2009 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 mc13783/pmic_power.c + * @brief This is the main file of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#include <linux/platform_device.h> +#include <linux/ioctl.h> +#include <linux/pmic_status.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <mach/pmic_power.h> + +#include "pmic_power_defs.h" + +#ifdef CONFIG_MXC_HWEVENT +#include <mach/hw_events.h> +#endif + +#include <asm/mach-types.h> + +#define MC13783_REGCTRL_GPOx_MASK 0x18000 + +static bool VBKUP1_EN = false; +static bool VBKUP2_EN = false; + +/* + * Power Pmic API + */ + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_power_off); +EXPORT_SYMBOL(pmic_power_set_pc_config); +EXPORT_SYMBOL(pmic_power_get_pc_config); +EXPORT_SYMBOL(pmic_power_regulator_on); +EXPORT_SYMBOL(pmic_power_regulator_off); +EXPORT_SYMBOL(pmic_power_regulator_set_voltage); +EXPORT_SYMBOL(pmic_power_regulator_get_voltage); +EXPORT_SYMBOL(pmic_power_regulator_set_config); +EXPORT_SYMBOL(pmic_power_regulator_get_config); +EXPORT_SYMBOL(pmic_power_vbkup2_auto_en); +EXPORT_SYMBOL(pmic_power_get_vbkup2_auto_state); +EXPORT_SYMBOL(pmic_power_bat_det_en); +EXPORT_SYMBOL(pmic_power_get_bat_det_state); +EXPORT_SYMBOL(pmic_power_vib_pin_en); +EXPORT_SYMBOL(pmic_power_gets_vib_pin_state); +EXPORT_SYMBOL(pmic_power_get_power_mode_sense); +EXPORT_SYMBOL(pmic_power_set_regen_assig); +EXPORT_SYMBOL(pmic_power_get_regen_assig); +EXPORT_SYMBOL(pmic_power_set_regen_inv); +EXPORT_SYMBOL(pmic_power_get_regen_inv); +EXPORT_SYMBOL(pmic_power_esim_v_en); +EXPORT_SYMBOL(pmic_power_gets_esim_v_state); +EXPORT_SYMBOL(pmic_power_set_auto_reset_en); +EXPORT_SYMBOL(pmic_power_get_auto_reset_en); +EXPORT_SYMBOL(pmic_power_set_conf_button); +EXPORT_SYMBOL(pmic_power_get_conf_button); +EXPORT_SYMBOL(pmic_power_event_sub); +EXPORT_SYMBOL(pmic_power_event_unsub); + +/*! + * This function is called to put the power in a low power state. + * Switching off the platform cannot be decided by + * the power module. It has to be handled by the + * client application. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_power_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +}; + +/*! + * This function is called to resume the power from a low power state. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_power_resume(struct platform_device *pdev) +{ + return 0; +}; + +/*! + * This function sets user power off in power control register and thus powers + * off the phone. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +void pmic_power_off(void) +{ + unsigned int mask, value; + + mask = BITFMASK(MC13783_PWRCTRL_USER_OFF_SPI); + value = BITFVAL(MC13783_PWRCTRL_USER_OFF_SPI, + MC13783_PWRCTRL_USER_OFF_SPI_ENABLE); + + pmic_write_reg(REG_POWER_CONTROL_0, value, mask); +} + +/*! + * This function sets the power control configuration. + * + * @param pc_config power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_set_pc_config(t_pc_config * pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + unsigned int pwrctrl_mask_reg0 = 0; + unsigned int pwrctrl_mask_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + if (pc_config->pc_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PCT, + pc_config->pc_timer); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PCEN, + MC13783_PWRCTRL_PCEN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PCEN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PCT); + + if (pc_config->pc_count_enable != false) { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT, + pc_config->pc_count); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_PC_MAX_CNT, + pc_config->pc_max_count); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_PC_COUNT_EN, + MC13783_PWRCTRL_PC_COUNT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_PC_COUNT_EN); + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_PC_MAX_CNT) | + BITFMASK(MC13783_PWRCTRL_PC_COUNT); + + if (pc_config->warm_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_WARM_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_WARM_EN); + + if (pc_config->user_off_pc != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_USER_OFF_PC, + MC13783_PWRCTRL_USER_OFF_PC_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_WARM_EN, + MC13783_PWRCTRL_USER_OFF_PC_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_USER_OFF_PC); + + if (pc_config->clk_32k_user_off != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_USER_OFF, + MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_USER_OFF); + + if (pc_config->clk_32k_enable != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_32OUT_EN, + MC13783_PWRCTRL_32OUT_EN_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_32OUT_EN); + + if (pc_config->en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP1_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP1_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_EN); + + if (pc_config->en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + VBKUP2_EN = true; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + VBKUP2_EN = false; + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_EN); + + if (pc_config->auto_en_vbkup1 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1_AUTO_EN); + + if (pc_config->auto_en_vbkup2 != false) { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_ENABLE); + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2_AUTO_EN, + MC13783_PWRCTRL_VBKUP_DISABLE); + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP2_AUTO_EN); + + if (VBKUP1_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP1, + pc_config->vhold_voltage); + } + } + if (VBKUP2_EN != false) { + if (pc_config->vhold_voltage > 3 + || pc_config->vhold_voltage < 0) { + return PMIC_PARAMETER_ERROR; + } else { + pwrctrl_val_reg0 |= BITFVAL(MC13783_PWRCTRL_VBKUP2, + pc_config->vhold_voltage2); + } + } + pwrctrl_mask_reg0 |= BITFMASK(MC13783_PWRCTRL_VBKUP1) | + BITFMASK(MC13783_PWRCTRL_VBKUP2); + + if (pc_config->mem_allon != false) { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_ENABLE); + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_TMR, + pc_config->mem_timer); + } else { + pwrctrl_val_reg1 |= BITFVAL(MC13783_PWRCTRL_MEM_ALLON, + MC13783_PWRCTRL_MEM_ALLON_DISABLE); + } + pwrctrl_mask_reg1 |= BITFMASK(MC13783_PWRCTRL_MEM_ALLON) | + BITFMASK(MC13783_PWRCTRL_MEM_TMR); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + pwrctrl_val_reg0, pwrctrl_mask_reg0)); + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_1, + pwrctrl_val_reg1, pwrctrl_mask_reg1)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the power control configuration. + * + * @param pc_config pointer to power control configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_pc_config(t_pc_config * pc_config) +{ + unsigned int pwrctrl_val_reg0 = 0; + unsigned int pwrctrl_val_reg1 = 0; + + if (pc_config == NULL) { + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + &pwrctrl_val_reg0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_1, + &pwrctrl_val_reg1, PMIC_ALL_BITS)); + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PCEN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_enable = true; + pc_config->pc_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PCT); + + } else { + pc_config->pc_enable = false; + pc_config->pc_timer = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_PC_COUNT_EN) + == MC13783_PWRCTRL_PCEN_ENABLE) { + pc_config->pc_count_enable = true; + pc_config->pc_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_COUNT); + pc_config->pc_max_count = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_PC_MAX_CNT); + } else { + pc_config->pc_count_enable = false; + pc_config->pc_count = 0; + pc_config->pc_max_count = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_WARM_EN) + == MC13783_PWRCTRL_WARM_EN_ENABLE) { + pc_config->warm_enable = true; + } else { + pc_config->warm_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_USER_OFF_PC) + == MC13783_PWRCTRL_USER_OFF_PC_ENABLE) { + pc_config->user_off_pc = true; + } else { + pc_config->user_off_pc = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_USER_OFF) + == MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE) { + pc_config->clk_32k_user_off = true; + } else { + pc_config->clk_32k_user_off = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_32OUT_EN) + == MC13783_PWRCTRL_32OUT_EN_ENABLE) { + pc_config->clk_32k_enable = true; + } else { + pc_config->clk_32k_enable = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup1 = true; + } else { + pc_config->auto_en_vbkup1 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_AUTO_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->auto_en_vbkup2 = true; + } else { + pc_config->auto_en_vbkup2 = false; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP1_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup1 = true; + pc_config->vhold_voltage = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP1); + } else { + pc_config->en_vbkup1 = false; + pc_config->vhold_voltage = 0; + } + + if (BITFEXT(pwrctrl_val_reg0, MC13783_PWRCTRL_VBKUP2_EN) + == MC13783_PWRCTRL_VBKUP_ENABLE) { + pc_config->en_vbkup2 = true; + pc_config->vhold_voltage2 = BITFEXT(pwrctrl_val_reg0, + MC13783_PWRCTRL_VBKUP2); + } else { + pc_config->en_vbkup2 = false; + pc_config->vhold_voltage2 = 0; + } + + if (BITFEXT(pwrctrl_val_reg1, MC13783_PWRCTRL_MEM_ALLON) == + MC13783_PWRCTRL_MEM_ALLON_ENABLE) { + pc_config->mem_allon = true; + pc_config->mem_timer = BITFEXT(pwrctrl_val_reg1, + MC13783_PWRCTRL_MEM_TMR); + } else { + pc_config->mem_allon = false; + pc_config->mem_timer = 0; + } + + return PMIC_SUCCESS; +} + +/*! + * This function turns on a regulator. + * + * @param regulator The regulator to be truned on. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_on(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_ENABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function turns off a regulator. + * + * @param regulator The regulator to be truned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_off(t_pmic_regulator regulator) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_PLL: + reg_val = BITFVAL(MC13783_SWCTRL_PLL_EN, + MC13783_SWCTRL_PLL_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_EN); + reg = REG_SWITCHERS_4; + break; + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_EN, + MC13783_SWCTRL_SW3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_EN); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_EN, + MC13783_REGCTRL_VAUDIO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_EN, + MC13783_REGCTRL_VIOHI_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_EN, + MC13783_REGCTRL_VIOLO_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_EN, + MC13783_REGCTRL_VDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_EN, + MC13783_REGCTRL_VGEN_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_EN, + MC13783_REGCTRL_VRFDIG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_EN, + MC13783_REGCTRL_VRFREF_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_EN, + MC13783_REGCTRL_VRFCP_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_EN); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_EN, + MC13783_REGCTRL_VSIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_EN, + MC13783_REGCTRL_VESIM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_EN, + MC13783_REGCTRL_VCAM_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_EN, + MC13783_REGCTRL_VRFBG_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VVIB: + reg_val = BITFVAL(MC13783_REGCTRL_VVIB_EN, + MC13783_REGCTRL_VVIB_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VVIB_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_EN, + MC13783_REGCTRL_VRF1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_EN, + MC13783_REGCTRL_VRF2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_EN, + MC13783_REGCTRL_VMMC1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_EN, + MC13783_REGCTRL_VMMC2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_EN); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGCTRL_GPO1_EN, + MC13783_REGCTRL_GPO1_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO1_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGCTRL_GPO2_EN, + MC13783_REGCTRL_GPO2_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO2_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGCTRL_GPO3_EN, + MC13783_REGCTRL_GPO3_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO3_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGCTRL_GPO4_EN, + MC13783_REGCTRL_GPO4_EN_DISABLE); + reg_mask = BITFMASK(MC13783_REGCTRL_GPO4_EN); + reg = REG_POWER_MISCELLANEOUS; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator output voltage. + * + * @param regulator The regulator to be configured. + * @param voltage The regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_voltage(t_pmic_regulator regulator, + t_regulator_voltage voltage) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((voltage.sw1a < SW1A_0_9V) || (voltage.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((voltage.sw1b < SW1B_0_9V) || (voltage.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B, voltage.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((voltage.sw2a < SW2A_0_9V) || (voltage.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A, voltage.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((voltage.sw2b < SW2B_0_9V) || (voltage.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B, voltage.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW1A); + reg = REG_SWITCHERS_3; + break; + case SW_SW3: + if (voltage.sw3 != SW3_5V) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW3, voltage.sw3); + reg_mask = BITFMASK(MC13783_SWSET_SW3); + reg = REG_SWITCHERS_5; + break; + case REGU_VIOLO: + if ((voltage.violo < VIOLO_1_2V) || + (voltage.violo > VIOLO_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VIOLO, voltage.violo); + reg_mask = BITFMASK(MC13783_REGSET_VIOLO); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VDIG: + if ((voltage.vdig < VDIG_1_2V) || (voltage.vdig > VDIG_1_8V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VDIG, voltage.vdig); + reg_mask = BITFMASK(MC13783_REGSET_VDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VGEN: + if ((voltage.vgen < VGEN_1_2V) || (voltage.vgen > VGEN_2_4V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VGEN, voltage.vgen); + reg_mask = BITFMASK(MC13783_REGSET_VGEN); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFDIG: + if ((voltage.vrfdig < VRFDIG_1_2V) || + (voltage.vrfdig > VRFDIG_1_875V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFDIG, voltage.vrfdig); + reg_mask = BITFMASK(MC13783_REGSET_VRFDIG); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFREF: + if ((voltage.vrfref < VRFREF_2_475V) || + (voltage.vrfref > VRFREF_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFREF, voltage.vrfref); + reg_mask = BITFMASK(MC13783_REGSET_VRFREF); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VRFCP: + if ((voltage.vrfcp < VRFCP_2_7V) || + (voltage.vrfcp > VRFCP_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRFCP, voltage.vrfcp); + reg_mask = BITFMASK(MC13783_REGSET_VRFCP); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VSIM: + if ((voltage.vsim < VSIM_1_8V) || (voltage.vsim > VSIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VSIM, voltage.vsim); + reg_mask = BITFMASK(MC13783_REGSET_VSIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VESIM: + if ((voltage.vesim < VESIM_1_8V) || + (voltage.vesim > VESIM_2_9V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VESIM, voltage.vesim); + reg_mask = BITFMASK(MC13783_REGSET_VESIM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VCAM: + if ((voltage.vcam < VCAM_1_5V) || (voltage.vcam > VCAM_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VCAM, voltage.vcam); + reg_mask = BITFMASK(MC13783_REGSET_VCAM); + reg = REG_REGULATOR_SETTING_0; + break; + case REGU_VVIB: + if ((voltage.vvib < VVIB_1_3V) || (voltage.vvib > VVIB_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VVIB, voltage.vvib); + reg_mask = BITFMASK(MC13783_REGSET_VVIB); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF1: + if ((voltage.vrf1 < VRF1_1_5V) || (voltage.vrf1 > VRF1_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF1, voltage.vrf1); + reg_mask = BITFMASK(MC13783_REGSET_VRF1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VRF2: + if ((voltage.vrf2 < VRF2_1_5V) || (voltage.vrf2 > VRF2_2_775V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VRF2, voltage.vrf2); + reg_mask = BITFMASK(MC13783_REGSET_VRF2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC1: + if ((voltage.vmmc1 < VMMC1_1_6V) || (voltage.vmmc1 > VMMC1_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC1, voltage.vmmc1); + reg_mask = BITFMASK(MC13783_REGSET_VMMC1); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VMMC2: + if ((voltage.vmmc2 < VMMC2_1_6V) || (voltage.vmmc2 > VMMC2_3V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGSET_VMMC2, voltage.vmmc2); + reg_mask = BITFMASK(MC13783_REGSET_VMMC2); + reg = REG_REGULATOR_SETTING_1; + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + case REGU_GPO1: + case REGU_GPO2: + case REGU_GPO3: + case REGU_GPO4: + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output voltage. + * + * @param regulator The regulator to be truned off. + * @param voltage Pointer to regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_voltage(t_pmic_regulator regulator, + t_regulator_voltage * voltage) +{ + unsigned int reg_val = 0; + + if (regulator == SW_SW1A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_0, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW1B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_1, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2A) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_2, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW2B) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_3, + ®_val, PMIC_ALL_BITS)); + } else if (regulator == SW_SW3) { + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_5, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VIOLO) || (regulator == REGU_VDIG) || + (regulator == REGU_VGEN) || + (regulator == REGU_VRFDIG) || + (regulator == REGU_VRFREF) || + (regulator == REGU_VRFCP) || + (regulator == REGU_VSIM) || + (regulator == REGU_VESIM) || (regulator == REGU_VCAM)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_0, + ®_val, PMIC_ALL_BITS)); + } else if ((regulator == REGU_VVIB) || (regulator == REGU_VRF1) || + (regulator == REGU_VRF2) || + (regulator == REGU_VMMC1) || (regulator == REGU_VMMC2)) { + CHECK_ERROR(pmic_read_reg(REG_REGULATOR_SETTING_1, + ®_val, PMIC_ALL_BITS)); + } + + switch (regulator) { + case SW_SW1A: + voltage->sw1a = BITFEXT(reg_val, MC13783_SWSET_SW1A); + break; + case SW_SW1B: + voltage->sw1b = BITFEXT(reg_val, MC13783_SWSET_SW1B); + break; + case SW_SW2A: + voltage->sw2a = BITFEXT(reg_val, MC13783_SWSET_SW2A); + break; + case SW_SW2B: + voltage->sw2b = BITFEXT(reg_val, MC13783_SWSET_SW2B); + break; + case SW_SW3: + voltage->sw3 = BITFEXT(reg_val, MC13783_SWSET_SW3); + break; + case REGU_VIOLO: + voltage->violo = BITFEXT(reg_val, MC13783_REGSET_VIOLO); + break; + case REGU_VDIG: + voltage->vdig = BITFEXT(reg_val, MC13783_REGSET_VDIG); + break; + case REGU_VGEN: + voltage->vgen = BITFEXT(reg_val, MC13783_REGSET_VGEN); + break; + case REGU_VRFDIG: + voltage->vrfdig = BITFEXT(reg_val, MC13783_REGSET_VRFDIG); + break; + case REGU_VRFREF: + voltage->vrfref = BITFEXT(reg_val, MC13783_REGSET_VRFREF); + break; + case REGU_VRFCP: + voltage->vrfcp = BITFEXT(reg_val, MC13783_REGSET_VRFCP); + break; + case REGU_VSIM: + voltage->vsim = BITFEXT(reg_val, MC13783_REGSET_VSIM); + break; + case REGU_VESIM: + voltage->vesim = BITFEXT(reg_val, MC13783_REGSET_VESIM); + break; + case REGU_VCAM: + voltage->vcam = BITFEXT(reg_val, MC13783_REGSET_VCAM); + break; + case REGU_VVIB: + voltage->vvib = BITFEXT(reg_val, MC13783_REGSET_VVIB); + break; + case REGU_VRF1: + voltage->vrf1 = BITFEXT(reg_val, MC13783_REGSET_VRF1); + break; + case REGU_VRF2: + voltage->vrf2 = BITFEXT(reg_val, MC13783_REGSET_VRF2); + break; + case REGU_VMMC1: + voltage->vmmc1 = BITFEXT(reg_val, MC13783_REGSET_VMMC1); + break; + case REGU_VMMC2: + voltage->vmmc2 = BITFEXT(reg_val, MC13783_REGSET_VMMC2); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the DVS voltage + * + * @param regulator The regulator to be configured. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs(t_pmic_regulator regulator, + t_regulator_voltage dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((dvs.sw1a < SW1A_0_9V) || (dvs.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_DVS, dvs.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((dvs.sw1b < SW1B_0_9V) || (dvs.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_DVS, dvs.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((dvs.sw2a < SW2A_0_9V) || (dvs.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_DVS, dvs.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((dvs.sw2b < SW2B_0_9V) || (dvs.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_DVS, dvs.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the DVS voltage + * + * @param regulator The regulator to be handled. + * @param dvs The switch Dynamic Voltage Scaling + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs(t_pmic_regulator regulator, + t_regulator_voltage * dvs) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_DVS); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_DVS); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_DVS); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_DVS); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_DVS); + break; + case SW_SW1B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_DVS); + break; + case SW_SW2A: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_DVS); + break; + case SW_SW2B: + *dvs = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_DVS); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the standiby voltage + * + * @param regulator The regulator to be configured. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_stby(t_pmic_regulator regulator, + t_regulator_voltage stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + if ((stby.sw1a < SW1A_0_9V) || (stby.sw1a > SW1A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1A_STDBY, stby.sw1a); + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + if ((stby.sw1b < SW1B_0_9V) || (stby.sw1b > SW1B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW1B_STDBY, stby.sw1b); + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + if ((stby.sw2a < SW2A_0_9V) || (stby.sw2a > SW2A_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2A_STDBY, stby.sw2a); + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + if ((stby.sw2b < SW2B_0_9V) || (stby.sw2b > SW2B_2_2V)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWSET_SW2B_STDBY, stby.sw2b); + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the standiby voltage + * + * @param regulator The regulator to be handled. + * @param stby The switch standby voltage + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_stby(t_pmic_regulator regulator, + t_regulator_voltage * stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWSET_SW1A_STDBY); + reg = REG_SWITCHERS_0; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWSET_SW1B_STDBY); + reg = REG_SWITCHERS_1; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWSET_SW2A_STDBY); + reg = REG_SWITCHERS_2; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWSET_SW2B_STDBY); + reg = REG_SWITCHERS_3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1A_STDBY); + break; + case SW_SW1B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW1B_STDBY); + break; + case SW_SW2A: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2A_STDBY); + break; + case SW_SW2B: + *stby = (t_regulator_voltage) BITFEXT(reg_val, + MC13783_SWSET_SW2B_STDBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switchers mode. + * + * @param regulator The regulator to be configured. + * @param mode The switcher mode + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_mode(t_pmic_regulator regulator, + t_regulator_sw_mode mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode; + + if (mode == SYNC_RECT) { + l_mode = MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN; + } else if (mode == NO_PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN; + } else if (mode == PULSE_SKIP) { + l_mode = MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN; + } else if (mode == LOW_POWER) { + l_mode = MC13783_SWCTRL_SW_MODE_LOW_POWER_EN; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW1B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW1B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2A_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_val = + BITFVAL(MC13783_SWCTRL_SW2B_STBY_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_MODE, l_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switchers mode. + * + * @param regulator The regulator to be handled. + * @param mode The switcher mode. + * @param stby Switch between main and standby. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_mode(t_pmic_regulator regulator, + t_regulator_sw_mode * mode, bool stby) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg = 0; + unsigned int l_mode = 0; + + switch (regulator) { + case SW_SW1A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_MODE); + } + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_MODE); + } + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + if (stby) { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_MODE); + } + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_MODE); + } + break; + case SW_SW1B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW1B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_MODE); + } + break; + case SW_SW2A: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2A_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_MODE); + } + break; + case SW_SW2B: + if (stby) { + l_mode = + BITFEXT(reg_val, MC13783_SWCTRL_SW2B_STBY_MODE); + } else { + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_MODE); + } + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (l_mode == MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN) { + *mode = SYNC_RECT; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN) { + *mode = NO_PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN) { + *mode = PULSE_SKIP; + } else if (l_mode == MC13783_SWCTRL_SW_MODE_LOW_POWER_EN) { + *mode = LOW_POWER; + } else { + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch dvs speed + * + * @param regulator The regulator to be configured. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + if (speed > 3 || speed < 0) { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_DVS_SPEED, speed); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch dvs speed + * + * @param regulator The regulator to be handled. + * @param speed The dvs speed. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_dvs_speed(t_pmic_regulator regulator, + t_switcher_dvs_speed * speed) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_DVS_SPEED); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_DVS_SPEED); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_DVS_SPEED); + break; + case SW_SW1B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_DVS_SPEED); + break; + case SW_SW2A: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_DVS_SPEED); + break; + case SW_SW2B: + *speed = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_DVS_SPEED); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch panic mode + * + * @param regulator The regulator to be configured. + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_panic_mode(t_pmic_regulator regulator, + bool panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_PANIC_MODE, panic_mode); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch panic mode + * + * @param regulator The regulator to be handled + * @param panic_mode Enable or disable panic mode + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_panic_mode(t_pmic_regulator regulator, + bool * panic_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_PANIC_MODE); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_PANIC_MODE); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_PANIC_MODE); + break; + case SW_SW1B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW1B_PANIC_MODE); + break; + case SW_SW2A: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_PANIC_MODE); + break; + case SW_SW2B: + *panic_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_PANIC_MODE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the switch softstart mode + * + * @param regulator The regulator to be configured. + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_softstart(t_pmic_regulator regulator, + bool softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_val = BITFVAL(MC13783_SWCTRL_SW1A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_val = BITFVAL(MC13783_SWCTRL_SW2A_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_val = BITFVAL(MC13783_SWCTRL_SW2B_SOFTSTART, softstart); + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the switch softstart mode + * + * @param regulator The regulator to be handled + * @param softstart Enable or disable softstart. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_softstart(t_pmic_regulator regulator, + bool * softstart) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + + switch (regulator) { + case SW_SW1A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1A_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW1B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW1B_SOFTSTART); + reg = REG_SWITCHERS_4; + break; + case SW_SW2A: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2A_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + case SW_SW2B: + reg_mask = BITFMASK(MC13783_SWCTRL_SW2B_SOFTSTART); + reg = REG_SWITCHERS_5; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW1A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW1A_SOFTSTART); + break; + case SW_SW1B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + case SW_SW2A: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2A_SOFTSTART); + break; + case SW_SW2B: + *softstart = BITFEXT(reg_val, MC13783_SWCTRL_SW2B_SOFTSTART); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the PLL multiplication factor + * + * @param regulator The regulator to be configured. + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_set_factor(t_pmic_regulator regulator, + t_switcher_factor factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + if (factor < FACTOR_28 || factor > FACTOR_35) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_SWCTRL_PLL_FACTOR, factor); + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_write_reg(REG_SWITCHERS_4, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the PLL multiplication factor + * + * @param regulator The regulator to be handled + * @param factor The multiplication factor. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_switcher_get_factor(t_pmic_regulator regulator, + t_switcher_factor * factor) +{ + unsigned int reg_val = 0, reg_mask = 0; + + if (regulator != SW_PLL) { + return PMIC_PARAMETER_ERROR; + } + reg_mask = BITFMASK(MC13783_SWCTRL_PLL_FACTOR); + + CHECK_ERROR(pmic_read_reg(REG_SWITCHERS_4, ®_val, reg_mask)); + + *factor = BITFEXT(reg_val, MC13783_SWCTRL_PLL_FACTOR); + + return PMIC_SUCCESS; +} + +/*! + * This function enables or disables low power mode. + * + * @param regulator The regulator to be configured. + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + if (lp_mode == LOW_POWER_DISABLED) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else if (lp_mode == LOW_POWER_EN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_DISABLE; + } else if (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN) { + l_mode = MC13783_REGTRL_LP_MODE_ENABLE; + l_stby = MC13783_REGTRL_STBY_MODE_ENABLE; + } else { + return PMIC_PARAMETER_ERROR; + } + + switch (regulator) { + case SW_SW3: + reg_val = BITFVAL(MC13783_SWCTRL_SW3_MODE, l_mode) | + BITFVAL(MC13783_SWCTRL_SW3_STBY, l_stby); + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGCTRL_VAUDIO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VAUDIO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGCTRL_VIOHI_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOHI_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGCTRL_VIOLO_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VIOLO_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGCTRL_VGEN_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VGEN_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGCTRL_VRFDIG_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFDIG_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGCTRL_VRFREF_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFREF_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGCTRL_VRFCP_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRFCP_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_val = BITFVAL(MC13783_REGCTRL_VSIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VSIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_val = BITFVAL(MC13783_REGCTRL_VESIM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VESIM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGCTRL_VCAM_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VCAM_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + if ((lp_mode == LOW_POWER) || + (lp_mode == LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN)) { + return PMIC_PARAMETER_ERROR; + } + reg_val = BITFVAL(MC13783_REGCTRL_VRFBG_STBY, l_mode); + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGCTRL_VRF1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGCTRL_VRF2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VRF2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC1_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC1_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGCTRL_VMMC2_MODE, l_mode) | + BITFVAL(MC13783_REGCTRL_VMMC2_STBY, l_stby); + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets low power mode. + * + * @param regulator The regulator to be handled + * @param lp_mode Select nominal or low power mode. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_lp_mode(t_pmic_regulator regulator, + t_regulator_lp_mode * lp_mode) +{ + unsigned int reg_val = 0, reg_mask = 0; + unsigned int reg; + unsigned int l_mode, l_stby; + + switch (regulator) { + case SW_SW3: + reg_mask = BITFMASK(MC13783_SWCTRL_SW3_MODE) | + BITFMASK(MC13783_SWCTRL_SW3_STBY); + reg = REG_SWITCHERS_5; + break; + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGCTRL_VAUDIO_MODE) | + BITFMASK(MC13783_REGCTRL_VAUDIO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOHI_MODE) | + BITFMASK(MC13783_REGCTRL_VIOHI_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGCTRL_VIOLO_MODE) | + BITFMASK(MC13783_REGCTRL_VIOLO_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGCTRL_VGEN_MODE) | + BITFMASK(MC13783_REGCTRL_VGEN_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFDIG_MODE) | + BITFMASK(MC13783_REGCTRL_VRFDIG_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFREF_MODE) | + BITFMASK(MC13783_REGCTRL_VRFREF_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFCP_MODE) | + BITFMASK(MC13783_REGCTRL_VRFCP_STBY); + reg = REG_REGULATOR_MODE_0; + break; + case REGU_VSIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VSIM_MODE) | + BITFMASK(MC13783_REGCTRL_VSIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VESIM: + reg_mask = BITFMASK(MC13783_REGCTRL_VESIM_MODE) | + BITFMASK(MC13783_REGCTRL_VESIM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGCTRL_VCAM_MODE) | + BITFMASK(MC13783_REGCTRL_VCAM_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGCTRL_VRFBG_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF1_MODE) | + BITFMASK(MC13783_REGCTRL_VRF1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGCTRL_VRF2_MODE) | + BITFMASK(MC13783_REGCTRL_VRF2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC1_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC1_STBY); + reg = REG_REGULATOR_MODE_1; + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGCTRL_VMMC2_MODE) | + BITFMASK(MC13783_REGCTRL_VMMC2_STBY); + reg = REG_REGULATOR_MODE_1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_val, reg_mask)); + + switch (regulator) { + case SW_SW3: + l_mode = BITFEXT(reg_val, MC13783_SWCTRL_SW3_MODE); + l_stby = BITFEXT(reg_val, MC13783_SWCTRL_SW3_STBY); + break; + case REGU_VAUDIO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VAUDIO_STBY); + break; + case REGU_VIOHI: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOHI_STBY); + break; + case REGU_VIOLO: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VIOLO_STBY); + break; + case REGU_VDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VDIG_STBY); + break; + case REGU_VGEN: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VGEN_STBY); + break; + case REGU_VRFDIG: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFDIG_STBY); + break; + case REGU_VRFREF: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFREF_STBY); + break; + case REGU_VRFCP: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFCP_STBY); + break; + case REGU_VSIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VSIM_STBY); + break; + case REGU_VESIM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VESIM_STBY); + break; + case REGU_VCAM: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VCAM_STBY); + break; + case REGU_VRFBG: + l_mode = MC13783_REGTRL_LP_MODE_DISABLE; + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRFBG_STBY); + break; + case REGU_VRF1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF1_STBY); + break; + case REGU_VRF2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VRF2_STBY); + break; + case REGU_VMMC1: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC1_STBY); + break; + case REGU_VMMC2: + l_mode = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_MODE); + l_stby = BITFEXT(reg_val, MC13783_REGCTRL_VMMC2_STBY); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_DISABLED; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_DISABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_ENABLE)) { + *lp_mode = LOW_POWER_CTRL_BY_PIN; + } else if ((l_mode == MC13783_REGTRL_LP_MODE_ENABLE) && + (l_stby == MC13783_REGTRL_STBY_MODE_DISABLE)) { + *lp_mode = LOW_POWER_EN; + } else { + *lp_mode = LOW_POWER_AND_LOW_POWER_CTRL_BY_PIN; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the regulator configuration. + * + * @param regulator The regulator to be configured. + * @param config The regulator output configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_config(t_pmic_regulator regulator, + t_regulator_config * config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_switcher_set_dvs + (regulator, config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_set_stby + (regulator, config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->mode, false)); + CHECK_ERROR(pmic_power_switcher_set_mode + (regulator, config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_set_dvs_speed + (regulator, config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_set_panic_mode + (regulator, config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_set_softstart + (regulator, config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_set_factor + (regulator, config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_set_voltage + (regulator, config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_set_lp_mode + (regulator, config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function retrives the regulator output configuration. + * + * @param regulator The regulator to be truned off. + * @param config Pointer to regulator configuration. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_config(t_pmic_regulator regulator, + t_regulator_config * config) +{ + if (config == NULL) { + return PMIC_ERROR; + } + + switch (regulator) { + case SW_SW1A: + case SW_SW1B: + case SW_SW2A: + case SW_SW2B: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_switcher_get_dvs + (regulator, &config->voltage_lvs)); + CHECK_ERROR(pmic_power_switcher_get_stby + (regulator, &config->voltage_stby)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->mode, false)); + CHECK_ERROR(pmic_power_switcher_get_mode + (regulator, &config->stby_mode, true)); + CHECK_ERROR(pmic_power_switcher_get_dvs_speed + (regulator, &config->dvs_speed)); + CHECK_ERROR(pmic_power_switcher_get_panic_mode + (regulator, &config->panic_mode)); + CHECK_ERROR(pmic_power_switcher_get_softstart + (regulator, &config->softstart)); + break; + case SW_PLL: + CHECK_ERROR(pmic_power_switcher_get_factor + (regulator, &config->factor)); + break; + case SW_SW3: + case REGU_VIOLO: + case REGU_VDIG: + case REGU_VGEN: + case REGU_VRFDIG: + case REGU_VRFREF: + case REGU_VRFCP: + case REGU_VSIM: + case REGU_VESIM: + case REGU_VCAM: + case REGU_VRF1: + case REGU_VRF2: + case REGU_VMMC1: + case REGU_VMMC2: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + case REGU_VVIB: + CHECK_ERROR(pmic_power_regulator_get_voltage + (regulator, &config->voltage)); + break; + case REGU_VAUDIO: + case REGU_VIOHI: + case REGU_VRFBG: + CHECK_ERROR(pmic_power_regulator_get_lp_mode + (regulator, &config->lp_mode)); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} + +/*! + * This function enables automatically VBKUP2 in the memory hold modes. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VBKUP2AUTOMH + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vbkup2_auto_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VBKUP2AUTOMH, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of automatically VBKUP2. + * Only on mc13783 2.0 or higher + * + * @param en if true, VBKUP2AUTOMH is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_vbkup2_auto_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VBKUP2AUTOMH); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VBKUP2AUTOMH); + + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable BATTDETEN + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_bat_det_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_BATTDETEN, en); + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_0, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of battery detect function. + * Only on mc13783 2.0 or higher + * + * @param en if true, BATTDETEN is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_bat_det_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_BATTDETEN); + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_0, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_BATTDETEN); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * + * @param en if true, enable VIBPINCTRL + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_vib_pin_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGCTRL_VIBPINCTRL, en); + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + + CHECK_ERROR(pmic_write_reg(REG_POWER_MISCELLANEOUS, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets state of control of VVIB by VIBEN pin. + * Only on mc13783 2.0 or higher + * @param en if true, VIBPINCTRL is enabled + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_gets_vib_pin_state(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGCTRL_VIBPINCTRL); + CHECK_ERROR(pmic_read_reg(REG_POWER_MISCELLANEOUS, + ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_REGCTRL_VIBPINCTRL); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function returns power up sense value + * + * @param p_up_sense value of power up sense + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_get_power_mode_sense(struct t_p_up_sense * p_up_sense) +{ + unsigned int reg_value = 0; + CHECK_ERROR(pmic_read_reg(REG_POWER_UP_MODE_SENSE, + ®_value, PMIC_ALL_BITS)); + p_up_sense->state_ictest = (STATE_ICTEST_MASK & reg_value); + p_up_sense->state_clksel = ((STATE_CLKSEL_MASK & reg_value) + >> STATE_CLKSEL_BIT); + p_up_sense->state_pums1 = ((STATE_PUMS1_MASK & reg_value) + >> STATE_PUMS1_BITS); + p_up_sense->state_pums2 = ((STATE_PUMS2_MASK & reg_value) + >> STATE_PUMS2_BITS); + p_up_sense->state_pums3 = ((STATE_PUMS3_MASK & reg_value) + >> STATE_PUMS3_BITS); + p_up_sense->state_chrgmode0 = ((STATE_CHRGM1_MASK & reg_value) + >> STATE_CHRGM1_BITS); + p_up_sense->state_chrgmode1 = ((STATE_CHRGM2_MASK & reg_value) + >> STATE_CHRGM2_BITS); + p_up_sense->state_umod = ((STATE_UMOD_MASK & reg_value) + >> STATE_UMOD_BITS); + p_up_sense->state_usben = ((STATE_USBEN_MASK & reg_value) + >> STATE_USBEN_BIT); + p_up_sense->state_sw_1a1b_joined = ((STATE_SW1A_J_B_MASK & reg_value) + >> STATE_SW1A_J_B_BIT); + p_up_sense->state_sw_2a2b_joined = ((STATE_SW2A_J_B_MASK & reg_value) + >> STATE_SW2A_J_B_BIT); + return PMIC_SUCCESS; +} + +/*! + * This function configures the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis if true, the regulator is enabled by regen. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_assig(t_pmic_regulator regulator, bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_val = BITFVAL(MC13783_REGGEN_VAUDIO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_val = BITFVAL(MC13783_REGGEN_VIOHI, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_val = BITFVAL(MC13783_REGGEN_VIOLO, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_val = BITFVAL(MC13783_REGGEN_VDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_val = BITFVAL(MC13783_REGGEN_VGEN, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_val = BITFVAL(MC13783_REGGEN_VRFDIG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_val = BITFVAL(MC13783_REGGEN_VRFREF, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_val = BITFVAL(MC13783_REGGEN_VRFCP, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_val = BITFVAL(MC13783_REGGEN_VCAM, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_val = BITFVAL(MC13783_REGGEN_VRFBG, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_val = BITFVAL(MC13783_REGGEN_VRF1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_val = BITFVAL(MC13783_REGGEN_VRF2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_val = BITFVAL(MC13783_REGGEN_VMMC1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_val = BITFVAL(MC13783_REGGEN_VMMC2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_val = BITFVAL(MC13783_REGGEN_GPO1, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_val = BITFVAL(MC13783_REGGEN_GPO2, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_val = BITFVAL(MC13783_REGGEN_GPO3, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_val = BITFVAL(MC13783_REGGEN_GPO4, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen assignment for all regulator + * + * @param regulator type of regulator + * @param en_dis return value, if true : + * the regulator is enabled by regen. + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_assig(t_pmic_regulator regulator, + bool * en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (regulator) { + case REGU_VAUDIO: + reg_mask = BITFMASK(MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + reg_mask = BITFMASK(MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + reg_mask = BITFMASK(MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + reg_mask = BITFMASK(MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + reg_mask = BITFMASK(MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + reg_mask = BITFMASK(MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + reg_mask = BITFMASK(MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + reg_mask = BITFMASK(MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + reg_mask = BITFMASK(MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + reg_mask = BITFMASK(MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + reg_mask = BITFMASK(MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + reg_mask = BITFMASK(MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + reg_mask = BITFMASK(MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + reg_mask = BITFMASK(MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + reg_mask = BITFMASK(MC13783_REGGEN_GPO4); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + + switch (regulator) { + case REGU_VAUDIO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VAUDIO); + break; + case REGU_VIOHI: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOHI); + break; + case REGU_VIOLO: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VIOLO); + break; + case REGU_VDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VDIG); + break; + case REGU_VGEN: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VGEN); + break; + case REGU_VRFDIG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFDIG); + break; + case REGU_VRFREF: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFREF); + break; + case REGU_VRFCP: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFCP); + break; + case REGU_VCAM: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VCAM); + break; + case REGU_VRFBG: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRFBG); + break; + case REGU_VRF1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF1); + break; + case REGU_VRF2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VRF2); + break; + case REGU_VMMC1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC1); + break; + case REGU_VMMC2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_VMMC2); + break; + case REGU_GPO1: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO1); + break; + case REGU_GPO2: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO2); + break; + case REGU_GPO3: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO3); + break; + case REGU_GPO4: + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_GPO4); + break; + default: + break; + } + + return PMIC_SUCCESS; +} + +/*! + * This function sets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_regen_inv(bool en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_REGGEN_INV, en_dis); + reg_mask = BITFMASK(MC13783_REGGEN_INV); + + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets the Regen polarity. + * + * @param en_dis If true regen is inverted. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_regen_inv(bool * en_dis) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_REGGEN_INV); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, ®_val, reg_mask)); + *en_dis = BITFEXT(reg_val, MC13783_REGGEN_INV); + return PMIC_SUCCESS; +} + +/*! + * This function enables esim control voltage. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_esim_v_en(bool vesim, bool vmmc1, bool vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_val = BITFVAL(MC13783_REGGEN_VESIMESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC1ESIM, vesim) | + BITFVAL(MC13783_REGGEN_VMMC2ESIM, vesim); + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_write_reg(REG_REGEN_ASSIGNMENT, + reg_val, reg_mask)); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function gets esim control voltage values. + * Only on mc13783 2.0 or higher + * + * @param vesim if true, enable VESIMESIMEN + * @param vmmc1 if true, enable VMMC1ESIMEN + * @param vmmc2 if true, enable VMMC2ESIMEN + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_gets_esim_v_state(bool * vesim, bool * vmmc1, + bool * vmmc2) +{ + unsigned int reg_val = 0, reg_mask = 0; + pmic_version_t mc13783_ver; + mc13783_ver = pmic_get_version(); + if (mc13783_ver.revision >= 20) { + reg_mask = BITFMASK(MC13783_REGGEN_VESIMESIM) | + BITFMASK(MC13783_REGGEN_VMMC1ESIM) | + BITFMASK(MC13783_REGGEN_VMMC2ESIM); + CHECK_ERROR(pmic_read_reg(REG_REGEN_ASSIGNMENT, + ®_val, reg_mask)); + *vesim = BITFEXT(reg_val, MC13783_REGGEN_VESIMESIM); + *vmmc1 = BITFEXT(reg_val, MC13783_REGGEN_VMMC1ESIM); + *vmmc2 = BITFEXT(reg_val, MC13783_REGGEN_VMMC2ESIM); + return PMIC_SUCCESS; + } else { + return PMIC_NOT_SUPPORTED; + } +} + +/*! + * This function enables auto reset after a system reset. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_auto_reset_en(bool en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_val = BITFVAL(MC13783_AUTO_RESTART, en); + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + return PMIC_SUCCESS; +} + +/*! + * This function gets auto reset configuration. + * + * @param en if true, the auto reset is enabled + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_auto_reset_en(bool * en) +{ + unsigned int reg_val = 0, reg_mask = 0; + + reg_mask = BITFMASK(MC13783_AUTO_RESTART); + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + *en = BITFEXT(reg_val, MC13783_AUTO_RESTART); + return PMIC_SUCCESS; +} + +/*! + * This function configures a system reset on a button. + * + * @param bt type of button. + * @param sys_rst if true, enable the system reset on this button + * @param deb_time sets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_set_conf_button(t_button bt, bool sys_rst, int deb_time) +{ + int max_val = 0; + unsigned int reg_val = 0, reg_mask = 0; + + max_val = (1 << MC13783_DEB_BT_ON1B_WID) - 1; + if (deb_time > max_val) { + return PMIC_PARAMETER_ERROR; + } + + switch (bt) { + case BT_ON1B: + reg_val = BITFVAL(MC13783_EN_BT_ON1B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON1B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_val = BITFVAL(MC13783_EN_BT_ON2B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON2B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_val = BITFVAL(MC13783_EN_BT_ON3B, sys_rst) | + BITFVAL(MC13783_DEB_BT_ON3B, deb_time); + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_POWER_CONTROL_2, reg_val, reg_mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function gets configuration of a button. + * + * @param bt type of button. + * @param sys_rst if true, the system reset is enabled on this button + * @param deb_time gets the debounce time on this button pin + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_get_conf_button(t_button bt, + bool * sys_rst, int *deb_time) +{ + unsigned int reg_val = 0, reg_mask = 0; + + switch (bt) { + case BT_ON1B: + reg_mask = BITFMASK(MC13783_EN_BT_ON1B) | + BITFMASK(MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + reg_mask = BITFMASK(MC13783_EN_BT_ON2B) | + BITFMASK(MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + reg_mask = BITFMASK(MC13783_EN_BT_ON3B) | + BITFMASK(MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(REG_POWER_CONTROL_2, ®_val, reg_mask)); + + switch (bt) { + case BT_ON1B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON1B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON1B); + break; + case BT_ON2B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON2B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON2B); + break; + case BT_ON3B: + *sys_rst = BITFEXT(reg_val, MC13783_EN_BT_ON3B); + *deb_time = BITFEXT(reg_val, MC13783_DEB_BT_ON3B); + break; + default: + return PMIC_PARAMETER_ERROR; + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event(t_pwr_int event, void *callback, bool sub) +{ + pmic_event_callback_t power_callback; + type_event power_event; + + power_callback.func = callback; + power_callback.param = NULL; + switch (event) { + case PWR_IT_BPONI: + power_event = EVENT_BPONI; + break; + case PWR_IT_LOBATLI: + power_event = EVENT_LOBATLI; + break; + case PWR_IT_LOBATHI: + power_event = EVENT_LOBATHI; + break; + case PWR_IT_ONOFD1I: + power_event = EVENT_ONOFD1I; + break; + case PWR_IT_ONOFD2I: + power_event = EVENT_ONOFD2I; + break; + case PWR_IT_ONOFD3I: + power_event = EVENT_ONOFD3I; + break; + case PWR_IT_SYSRSTI: + power_event = EVENT_SYSRSTI; + break; + case PWR_IT_PWRRDYI: + power_event = EVENT_PWRRDYI; + break; + case PWR_IT_PCI: + power_event = EVENT_PCI; + break; + case PWR_IT_WARMI: + power_event = EVENT_WARMI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(power_event, power_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe + (power_event, power_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_sub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, true); +} + +/*! + * This function is used to un subscribe on power event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns 0 if successful. + */ +PMIC_STATUS pmic_power_event_unsub(t_pwr_int event, void *callback) +{ + return pmic_power_event(event, callback, false); +} + +void pmic_power_key_callback(void) +{ +#ifdef CONFIG_MXC_HWEVENT + /*read the power key is pressed or up */ + t_sensor_bits sense; + struct mxc_hw_event event = { HWE_POWER_KEY, 0 }; + + pmic_get_sensors(&sense); + if (sense.sense_onofd1s) { + pr_debug("PMIC Power key up\n"); + event.args = PWRK_UNPRESS; + } else { + pr_debug("PMIC Power key pressed\n"); + event.args = PWRK_PRESS; + } + /* send hw event */ + hw_event_send(HWE_DEF_PRIORITY, &event); +#endif +} + +static irqreturn_t power_key_int(int irq, void *dev_id) +{ + pr_info(KERN_INFO "on-off key pressed\n"); + + return 0; +} + +extern void gpio_power_key_active(void); + +/* + * Init and Exit + */ + +static int pmic_power_probe(struct platform_device *pdev) +{ + int irq, ret; + struct pmic_platform_data *ppd; + + /* configure on/off button */ + gpio_power_key_active(); + + ppd = pdev->dev.platform_data; + if (ppd) + irq = ppd->power_key_irq; + else + goto done; + + if (irq == 0) { + pr_info(KERN_INFO "PMIC Power has no platform data\n"); + goto done; + } + set_irq_type(irq, IRQF_TRIGGER_RISING); + + ret = request_irq(irq, power_key_int, 0, "power_key", 0); + if (ret) + pr_info(KERN_ERR "register on-off key interrupt failed\n"); + + set_irq_wake(irq, 1); + + done: + pr_info(KERN_INFO "PMIC Power successfully probed\n"); + return 0; +} + +static struct platform_driver pmic_power_driver_ldm = { + .driver = { + .name = "pmic_power", + }, + .suspend = pmic_power_suspend, + .resume = pmic_power_resume, + .probe = pmic_power_probe, + .remove = NULL, +}; + +static int __init pmic_power_init(void) +{ + pr_debug("PMIC Power driver loading..\n"); + pmic_power_event_sub(PWR_IT_ONOFD1I, pmic_power_key_callback); + /* set power off hook to mc13783 power off */ + pm_power_off = pmic_power_off; + return platform_driver_register(&pmic_power_driver_ldm); +} +static void __exit pmic_power_exit(void) +{ + pmic_power_event_unsub(PWR_IT_ONOFD1I, pmic_power_key_callback); + platform_driver_unregister(&pmic_power_driver_ldm); + pr_debug("PMIC Power driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall_sync(pmic_power_init); +module_exit(pmic_power_exit); + +MODULE_DESCRIPTION("pmic_power driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_power_defs.h b/drivers/mxc/pmic/mc13783/pmic_power_defs.h new file mode 100644 index 000000000000..38e554146a70 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_power_defs.h @@ -0,0 +1,509 @@ +/* + * 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 mc13783/pmic_power_defs.h + * @brief This is the internal header define of PMIC(mc13783) Power driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ + +#ifndef __MC13783_POWER_DEFS_H__ +#define __MC13783_POWER_DEFS_H__ + +/* + * Power Up Mode Sense bits + */ + +#define STATE_ICTEST_MASK 0x000001 + +#define STATE_CLKSEL_BIT 1 +#define STATE_CLKSEL_MASK 0x000002 + +#define STATE_PUMS1_BITS 2 +#define STATE_PUMS1_MASK 0x00000C + +#define STATE_PUMS2_BITS 4 +#define STATE_PUMS2_MASK 0x000030 + +#define STATE_PUMS3_BITS 6 +#define STATE_PUMS3_MASK 0x0000C0 + +#define STATE_CHRGM1_BITS 8 +#define STATE_CHRGM1_MASK 0x000300 + +#define STATE_CHRGM2_BITS 10 +#define STATE_CHRGM2_MASK 0x000C00 + +#define STATE_UMOD_BITS 12 +#define STATE_UMOD_MASK 0x003000 + +#define STATE_USBEN_BIT 14 +#define STATE_USBEN_MASK 0x004000 + +#define STATE_SW1A_J_B_BIT 15 +#define STATE_SW1A_J_B_MASK 0x008000 + +#define STATE_SW2A_J_B_BIT 16 +#define STATE_SW2A_J_B_MASK 0x010000 + +#define PC_COUNT_MAX 3 +#define PC_COUNT_MIN 0 +/* + * Reg Regen + */ +#define MC13783_REGGEN_VAUDIO_LSH 0 +#define MC13783_REGGEN_VAUDIO_WID 1 +#define MC13783_REGGEN_VIOHI_LSH 1 +#define MC13783_REGGEN_VIOHI_WID 1 +#define MC13783_REGGEN_VIOLO_LSH 2 +#define MC13783_REGGEN_VIOLO_WID 1 +#define MC13783_REGGEN_VDIG_LSH 3 +#define MC13783_REGGEN_VDIG_WID 1 +#define MC13783_REGGEN_VGEN_LSH 4 +#define MC13783_REGGEN_VGEN_WID 1 +#define MC13783_REGGEN_VRFDIG_LSH 5 +#define MC13783_REGGEN_VRFDIG_WID 1 +#define MC13783_REGGEN_VRFREF_LSH 6 +#define MC13783_REGGEN_VRFREF_WID 1 +#define MC13783_REGGEN_VRFCP_LSH 7 +#define MC13783_REGGEN_VRFCP_WID 1 +#define MC13783_REGGEN_VCAM_LSH 8 +#define MC13783_REGGEN_VCAM_WID 1 +#define MC13783_REGGEN_VRFBG_LSH 9 +#define MC13783_REGGEN_VRFBG_WID 1 +#define MC13783_REGGEN_VRF1_LSH 10 +#define MC13783_REGGEN_VRF1_WID 1 +#define MC13783_REGGEN_VRF2_LSH 11 +#define MC13783_REGGEN_VRF2_WID 1 +#define MC13783_REGGEN_VMMC1_LSH 12 +#define MC13783_REGGEN_VMMC1_WID 1 +#define MC13783_REGGEN_VMMC2_LSH 13 +#define MC13783_REGGEN_VMMC2_WID 1 +#define MC13783_REGGEN_GPO1_LSH 16 +#define MC13783_REGGEN_GPO1_WID 1 +#define MC13783_REGGEN_GPO2_LSH 17 +#define MC13783_REGGEN_GPO2_WID 1 +#define MC13783_REGGEN_GPO3_LSH 18 +#define MC13783_REGGEN_GPO3_WID 1 +#define MC13783_REGGEN_GPO4_LSH 19 +#define MC13783_REGGEN_GPO4_WID 1 +#define MC13783_REGGEN_INV_LSH 20 +#define MC13783_REGGEN_INV_WID 1 +#define MC13783_REGGEN_VESIMESIM_LSH 21 +#define MC13783_REGGEN_VESIMESIM_WID 1 +#define MC13783_REGGEN_VMMC1ESIM_LSH 22 +#define MC13783_REGGEN_VMMC1ESIM_WID 1 +#define MC13783_REGGEN_VMMC2ESIM_LSH 23 +#define MC13783_REGGEN_VMMC2ESIM_WID 1 + +/* + * Reg Power Control 0 + */ +#define MC13783_PWRCTRL_PCEN_LSH 0 +#define MC13783_PWRCTRL_PCEN_WID 1 +#define MC13783_PWRCTRL_PCEN_ENABLE 1 +#define MC13783_PWRCTRL_PCEN_DISABLE 0 +#define MC13783_PWRCTRL_PC_COUNT_EN_LSH 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_WID 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_ENABLE 1 +#define MC13783_PWRCTRL_PC_COUNT_EN_DISABLE 0 +#define MC13783_PWRCTRL_WARM_EN_LSH 2 +#define MC13783_PWRCTRL_WARM_EN_WID 1 +#define MC13783_PWRCTRL_WARM_EN_ENABLE 1 +#define MC13783_PWRCTRL_WARM_EN_DISABLE 0 +#define MC13783_PWRCTRL_USER_OFF_SPI_LSH 3 +#define MC13783_PWRCTRL_USER_OFF_SPI_WID 1 +#define MC13783_PWRCTRL_USER_OFF_SPI_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_LSH 4 +#define MC13783_PWRCTRL_USER_OFF_PC_WID 1 +#define MC13783_PWRCTRL_USER_OFF_PC_ENABLE 1 +#define MC13783_PWRCTRL_USER_OFF_PC_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_USER_OFF_LSH 5 +#define MC13783_PWRCTRL_32OUT_USER_OFF_WID 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_USER_OFF_DISABLE 0 +#define MC13783_PWRCTRL_32OUT_EN_LSH 6 +#define MC13783_PWRCTRL_32OUT_EN_WID 1 +#define MC13783_PWRCTRL_32OUT_EN_ENABLE 1 +#define MC13783_PWRCTRL_32OUT_EN_DISABLE 0 +#define MC13783_REGCTRL_VBKUP2AUTOMH_LSH 7 +#define MC13783_REGCTRL_VBKUP2AUTOMH_WID 1 +#define MC13783_PWRCTRL_VBKUP1_EN_LSH 8 +#define MC13783_PWRCTRL_VBKUP1_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP_ENABLE 1 +#define MC13783_PWRCTRL_VBKUP_DISABLE 0 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_LSH 9 +#define MC13783_PWRCTRL_VBKUP1_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP1_LSH 10 +#define MC13783_PWRCTRL_VBKUP1_WID 2 +#define MC13783_PWRCTRL_VBKUP2_EN_LSH 12 +#define MC13783_PWRCTRL_VBKUP2_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_LSH 13 +#define MC13783_PWRCTRL_VBKUP2_AUTO_EN_WID 1 +#define MC13783_PWRCTRL_VBKUP2_LSH 14 +#define MC13783_PWRCTRL_VBKUP2_WID 2 +#define MC13783_REGCTRL_BATTDETEN_LSH 19 +#define MC13783_REGCTRL_BATTDETEN_WID 1 + +/* + * Reg Power Control 1 + */ +#define MC13783_PWRCTRL_PCT_LSH 0 +#define MC13783_PWRCTRL_PCT_WID 8 +#define MC13783_PWRCTRL_PC_COUNT_LSH 8 +#define MC13783_PWRCTRL_PC_COUNT_WID 4 +#define MC13783_PWRCTRL_PC_MAX_CNT_LSH 12 +#define MC13783_PWRCTRL_PC_MAX_CNT_WID 4 +#define MC13783_PWRCTRL_MEM_TMR_LSH 16 +#define MC13783_PWRCTRL_MEM_TMR_WID 4 +#define MC13783_PWRCTRL_MEM_ALLON_LSH 20 +#define MC13783_PWRCTRL_MEM_ALLON_WID 1 +#define MC13783_PWRCTRL_MEM_ALLON_ENABLE 1 +#define MC13783_PWRCTRL_MEM_ALLON_DISABLE 0 + +/* + * Reg Power Control 2 + */ +#define MC13783_AUTO_RESTART_LSH 0 +#define MC13783_AUTO_RESTART_WID 1 +#define MC13783_EN_BT_ON1B_LSH 1 +#define MC13783_EN_BT_ON1B_WID 1 +#define MC13783_EN_BT_ON2B_LSH 2 +#define MC13783_EN_BT_ON2B_WID 1 +#define MC13783_EN_BT_ON3B_LSH 3 +#define MC13783_EN_BT_ON3B_WID 1 +#define MC13783_DEB_BT_ON1B_LSH 4 +#define MC13783_DEB_BT_ON1B_WID 2 +#define MC13783_DEB_BT_ON2B_LSH 6 +#define MC13783_DEB_BT_ON2B_WID 2 +#define MC13783_DEB_BT_ON3B_LSH 8 +#define MC13783_DEB_BT_ON3B_WID 2 + +/* + * Reg Regulator Mode 0 + */ +#define MC13783_REGCTRL_VAUDIO_EN_LSH 0 +#define MC13783_REGCTRL_VAUDIO_EN_WID 1 +#define MC13783_REGCTRL_VAUDIO_EN_ENABLE 1 +#define MC13783_REGCTRL_VAUDIO_EN_DISABLE 0 +#define MC13783_REGCTRL_VAUDIO_STBY_LSH 1 +#define MC13783_REGCTRL_VAUDIO_STBY_WID 1 +#define MC13783_REGCTRL_VAUDIO_MODE_LSH 2 +#define MC13783_REGCTRL_VAUDIO_MODE_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_LSH 3 +#define MC13783_REGCTRL_VIOHI_EN_WID 1 +#define MC13783_REGCTRL_VIOHI_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOHI_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOHI_STBY_LSH 4 +#define MC13783_REGCTRL_VIOHI_STBY_WID 1 +#define MC13783_REGCTRL_VIOHI_MODE_LSH 5 +#define MC13783_REGCTRL_VIOHI_MODE_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_LSH 6 +#define MC13783_REGCTRL_VIOLO_EN_WID 1 +#define MC13783_REGCTRL_VIOLO_EN_ENABLE 1 +#define MC13783_REGCTRL_VIOLO_EN_DISABLE 0 +#define MC13783_REGCTRL_VIOLO_STBY_LSH 7 +#define MC13783_REGCTRL_VIOLO_STBY_WID 1 +#define MC13783_REGCTRL_VIOLO_MODE_LSH 8 +#define MC13783_REGCTRL_VIOLO_MODE_WID 1 +#define MC13783_REGCTRL_VDIG_EN_LSH 9 +#define MC13783_REGCTRL_VDIG_EN_WID 1 +#define MC13783_REGCTRL_VDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VDIG_STBY_LSH 10 +#define MC13783_REGCTRL_VDIG_STBY_WID 1 +#define MC13783_REGCTRL_VDIG_MODE_LSH 11 +#define MC13783_REGCTRL_VDIG_MODE_WID 1 +#define MC13783_REGCTRL_VGEN_EN_LSH 12 +#define MC13783_REGCTRL_VGEN_EN_WID 1 +#define MC13783_REGCTRL_VGEN_EN_ENABLE 1 +#define MC13783_REGCTRL_VGEN_EN_DISABLE 0 +#define MC13783_REGCTRL_VGEN_STBY_LSH 13 +#define MC13783_REGCTRL_VGEN_STBY_WID 1 +#define MC13783_REGCTRL_VGEN_MODE_LSH 14 +#define MC13783_REGCTRL_VGEN_MODE_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_LSH 15 +#define MC13783_REGCTRL_VRFDIG_EN_WID 1 +#define MC13783_REGCTRL_VRFDIG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFDIG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFDIG_STBY_LSH 16 +#define MC13783_REGCTRL_VRFDIG_STBY_WID 1 +#define MC13783_REGCTRL_VRFDIG_MODE_LSH 17 +#define MC13783_REGCTRL_VRFDIG_MODE_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_LSH 18 +#define MC13783_REGCTRL_VRFREF_EN_WID 1 +#define MC13783_REGCTRL_VRFREF_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFREF_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFREF_STBY_LSH 19 +#define MC13783_REGCTRL_VRFREF_STBY_WID 1 +#define MC13783_REGCTRL_VRFREF_MODE_LSH 20 +#define MC13783_REGCTRL_VRFREF_MODE_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_LSH 21 +#define MC13783_REGCTRL_VRFCP_EN_WID 1 +#define MC13783_REGCTRL_VRFCP_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFCP_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFCP_STBY_LSH 22 +#define MC13783_REGCTRL_VRFCP_STBY_WID 1 +#define MC13783_REGCTRL_VRFCP_MODE_LSH 23 +#define MC13783_REGCTRL_VRFCP_MODE_WID 1 + +/* + * Reg Regulator Mode 1 + */ +#define MC13783_REGCTRL_VSIM_EN_LSH 0 +#define MC13783_REGCTRL_VSIM_EN_WID 1 +#define MC13783_REGCTRL_VSIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VSIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VSIM_STBY_LSH 1 +#define MC13783_REGCTRL_VSIM_STBY_WID 1 +#define MC13783_REGCTRL_VSIM_MODE_LSH 2 +#define MC13783_REGCTRL_VSIM_MODE_WID 1 +#define MC13783_REGCTRL_VESIM_EN_LSH 3 +#define MC13783_REGCTRL_VESIM_EN_WID 1 +#define MC13783_REGCTRL_VESIM_EN_ENABLE 1 +#define MC13783_REGCTRL_VESIM_EN_DISABLE 0 +#define MC13783_REGCTRL_VESIM_STBY_LSH 4 +#define MC13783_REGCTRL_VESIM_STBY_WID 1 +#define MC13783_REGCTRL_VESIM_MODE_LSH 5 +#define MC13783_REGCTRL_VESIM_MODE_WID 1 +#define MC13783_REGCTRL_VCAM_EN_LSH 6 +#define MC13783_REGCTRL_VCAM_EN_WID 1 +#define MC13783_REGCTRL_VCAM_EN_ENABLE 1 +#define MC13783_REGCTRL_VCAM_EN_DISABLE 0 +#define MC13783_REGCTRL_VCAM_STBY_LSH 7 +#define MC13783_REGCTRL_VCAM_STBY_WID 1 +#define MC13783_REGCTRL_VCAM_MODE_LSH 8 +#define MC13783_REGCTRL_VCAM_MODE_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_LSH 9 +#define MC13783_REGCTRL_VRFBG_EN_WID 1 +#define MC13783_REGCTRL_VRFBG_EN_ENABLE 1 +#define MC13783_REGCTRL_VRFBG_EN_DISABLE 0 +#define MC13783_REGCTRL_VRFBG_STBY_LSH 10 +#define MC13783_REGCTRL_VRFBG_STBY_WID 1 +#define MC13783_REGCTRL_VVIB_EN_LSH 11 +#define MC13783_REGCTRL_VVIB_EN_WID 1 +#define MC13783_REGCTRL_VVIB_EN_ENABLE 1 +#define MC13783_REGCTRL_VVIB_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_EN_LSH 12 +#define MC13783_REGCTRL_VRF1_EN_WID 1 +#define MC13783_REGCTRL_VRF1_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF1_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF1_STBY_LSH 13 +#define MC13783_REGCTRL_VRF1_STBY_WID 1 +#define MC13783_REGCTRL_VRF1_MODE_LSH 14 +#define MC13783_REGCTRL_VRF1_MODE_WID 1 +#define MC13783_REGCTRL_VRF2_EN_LSH 15 +#define MC13783_REGCTRL_VRF2_EN_WID 1 +#define MC13783_REGCTRL_VRF2_EN_ENABLE 1 +#define MC13783_REGCTRL_VRF2_EN_DISABLE 0 +#define MC13783_REGCTRL_VRF2_STBY_LSH 16 +#define MC13783_REGCTRL_VRF2_STBY_WID 1 +#define MC13783_REGCTRL_VRF2_MODE_LSH 17 +#define MC13783_REGCTRL_VRF2_MODE_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_LSH 18 +#define MC13783_REGCTRL_VMMC1_EN_WID 1 +#define MC13783_REGCTRL_VMMC1_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC1_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC1_STBY_LSH 19 +#define MC13783_REGCTRL_VMMC1_STBY_WID 1 +#define MC13783_REGCTRL_VMMC1_MODE_LSH 20 +#define MC13783_REGCTRL_VMMC1_MODE_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_LSH 21 +#define MC13783_REGCTRL_VMMC2_EN_WID 1 +#define MC13783_REGCTRL_VMMC2_EN_ENABLE 1 +#define MC13783_REGCTRL_VMMC2_EN_DISABLE 0 +#define MC13783_REGCTRL_VMMC2_STBY_LSH 22 +#define MC13783_REGCTRL_VMMC2_STBY_WID 1 +#define MC13783_REGCTRL_VMMC2_MODE_LSH 23 +#define MC13783_REGCTRL_VMMC2_MODE_WID 1 + +/* + * Reg Regulator Misc. + */ +#define MC13783_REGCTRL_GPO1_EN_LSH 6 +#define MC13783_REGCTRL_GPO1_EN_WID 1 +#define MC13783_REGCTRL_GPO1_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO1_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO2_EN_LSH 8 +#define MC13783_REGCTRL_GPO2_EN_WID 1 +#define MC13783_REGCTRL_GPO2_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO2_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO3_EN_LSH 10 +#define MC13783_REGCTRL_GPO3_EN_WID 1 +#define MC13783_REGCTRL_GPO3_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO3_EN_DISABLE 0 +#define MC13783_REGCTRL_GPO4_EN_LSH 12 +#define MC13783_REGCTRL_GPO4_EN_WID 1 +#define MC13783_REGCTRL_GPO4_EN_ENABLE 1 +#define MC13783_REGCTRL_GPO4_EN_DISABLE 0 +#define MC13783_REGCTRL_VIBPINCTRL_LSH 14 +#define MC13783_REGCTRL_VIBPINCTRL_WID 1 + +/* + * Reg Regulator Setting 0 + */ +#define MC13783_REGSET_VIOLO_LSH 2 +#define MC13783_REGSET_VIOLO_WID 2 +#define MC13783_REGSET_VDIG_LSH 4 +#define MC13783_REGSET_VDIG_WID 2 +#define MC13783_REGSET_VGEN_LSH 6 +#define MC13783_REGSET_VGEN_WID 3 +#define MC13783_REGSET_VRFDIG_LSH 9 +#define MC13783_REGSET_VRFDIG_WID 2 +#define MC13783_REGSET_VRFREF_LSH 11 +#define MC13783_REGSET_VRFREF_WID 2 +#define MC13783_REGSET_VRFCP_LSH 13 +#define MC13783_REGSET_VRFCP_WID 1 +#define MC13783_REGSET_VSIM_LSH 14 +#define MC13783_REGSET_VSIM_WID 1 +#define MC13783_REGSET_VESIM_LSH 15 +#define MC13783_REGSET_VESIM_WID 1 +#define MC13783_REGSET_VCAM_LSH 16 +#define MC13783_REGSET_VCAM_WID 3 + +/* + * Reg Regulator Setting 1 + */ +#define MC13783_REGSET_VVIB_LSH 0 +#define MC13783_REGSET_VVIB_WID 2 +#define MC13783_REGSET_VRF1_LSH 2 +#define MC13783_REGSET_VRF1_WID 2 +#define MC13783_REGSET_VRF2_LSH 4 +#define MC13783_REGSET_VRF2_WID 2 +#define MC13783_REGSET_VMMC1_LSH 6 +#define MC13783_REGSET_VMMC1_WID 3 +#define MC13783_REGSET_VMMC2_LSH 9 +#define MC13783_REGSET_VMMC2_WID 3 + +/* + * Reg Switcher 0 + */ +#define MC13783_SWSET_SW1A_LSH 0 +#define MC13783_SWSET_SW1A_WID 6 +#define MC13783_SWSET_SW1A_DVS_LSH 6 +#define MC13783_SWSET_SW1A_DVS_WID 6 +#define MC13783_SWSET_SW1A_STDBY_LSH 12 +#define MC13783_SWSET_SW1A_STDBY_WID 6 + +/* + * Reg Switcher 1 + */ +#define MC13783_SWSET_SW1B_LSH 0 +#define MC13783_SWSET_SW1B_WID 6 +#define MC13783_SWSET_SW1B_DVS_LSH 6 +#define MC13783_SWSET_SW1B_DVS_WID 6 +#define MC13783_SWSET_SW1B_STDBY_LSH 12 +#define MC13783_SWSET_SW1B_STDBY_WID 6 + +/* + * Reg Switcher 2 + */ +#define MC13783_SWSET_SW2A_LSH 0 +#define MC13783_SWSET_SW2A_WID 6 +#define MC13783_SWSET_SW2A_DVS_LSH 6 +#define MC13783_SWSET_SW2A_DVS_WID 6 +#define MC13783_SWSET_SW2A_STDBY_LSH 12 +#define MC13783_SWSET_SW2A_STDBY_WID 6 + +/* + * Reg Switcher 3 + */ +#define MC13783_SWSET_SW2B_LSH 0 +#define MC13783_SWSET_SW2B_WID 6 +#define MC13783_SWSET_SW2B_DVS_LSH 6 +#define MC13783_SWSET_SW2B_DVS_WID 6 +#define MC13783_SWSET_SW2B_STDBY_LSH 12 +#define MC13783_SWSET_SW2B_STDBY_WID 6 + +/* + * Reg Switcher 4 + */ +#define MC13783_SWCTRL_SW1A_MODE_LSH 0 +#define MC13783_SWCTRL_SW1A_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW1A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW1A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW1A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW1A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW1B_MODE_LSH 10 +#define MC13783_SWCTRL_SW1B_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW1B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW1B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW1B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW1B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW1B_SOFTSTART_WID 1 +#define MC13783_SWCTRL_PLL_EN_LSH 18 +#define MC13783_SWCTRL_PLL_EN_WID 1 +#define MC13783_SWCTRL_PLL_EN_ENABLE 1 +#define MC13783_SWCTRL_PLL_EN_DISABLE 0 +#define MC13783_SWCTRL_PLL_FACTOR_LSH 19 +#define MC13783_SWCTRL_PLL_FACTOR_WID 3 + +/* + * Reg Switcher 5 + */ +#define MC13783_SWCTRL_SW2A_MODE_LSH 0 +#define MC13783_SWCTRL_SW2A_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_LSH 2 +#define MC13783_SWCTRL_SW2A_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_LSH 6 +#define MC13783_SWCTRL_SW2A_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_LSH 8 +#define MC13783_SWCTRL_SW2A_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2A_SOFTSTART_LSH 9 +#define MC13783_SWCTRL_SW2A_SOFTSTART_WID 1 +#define MC13783_SWCTRL_SW2B_MODE_LSH 10 +#define MC13783_SWCTRL_SW2B_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_STBY_MODE_LSH 12 +#define MC13783_SWCTRL_SW2B_STBY_MODE_WID 2 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_LSH 14 +#define MC13783_SWCTRL_SW2B_DVS_SPEED_WID 2 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_LSH 16 +#define MC13783_SWCTRL_SW2B_PANIC_MODE_WID 1 +#define MC13783_SWCTRL_SW2B_SOFTSTART_LSH 17 +#define MC13783_SWCTRL_SW2B_SOFTSTART_WID 1 +#define MC13783_SWSET_SW3_LSH 18 +#define MC13783_SWSET_SW3_WID 2 +#define MC13783_SWCTRL_SW3_EN_LSH 20 +#define MC13783_SWCTRL_SW3_EN_WID 2 +#define MC13783_SWCTRL_SW3_EN_ENABLE 1 +#define MC13783_SWCTRL_SW3_EN_DISABLE 0 +#define MC13783_SWCTRL_SW3_STBY_LSH 21 +#define MC13783_SWCTRL_SW3_STBY_WID 1 +#define MC13783_SWCTRL_SW3_MODE_LSH 22 +#define MC13783_SWCTRL_SW3_MODE_WID 1 + +/* + * Switcher configuration + */ +#define MC13783_SWCTRL_SW_MODE_SYNC_RECT_EN 0 +#define MC13783_SWCTRL_SW_MODE_PULSE_NO_SKIP_EN 1 +#define MC13783_SWCTRL_SW_MODE_PULSE_SKIP_EN 2 +#define MC13783_SWCTRL_SW_MODE_LOW_POWER_EN 3 +#define MC13783_REGTRL_LP_MODE_ENABLE 1 +#define MC13783_REGTRL_LP_MODE_DISABLE 0 +#define MC13783_REGTRL_STBY_MODE_ENABLE 1 +#define MC13783_REGTRL_STBY_MODE_DISABLE 0 + +#endif /* __MC13783_POWER_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc.c b/drivers/mxc/pmic/mc13783/pmic_rtc.c new file mode 100644 index 000000000000..50f086f87022 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc.c @@ -0,0 +1,552 @@ +/* + * Copyright 2004-2009 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 mc13783/pmic_rtc.c + * @brief This is the main file of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * Includes + */ +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <linux/pmic_rtc.h> +#include <linux/pmic_status.h> + +#include "pmic_rtc_defs.h" + +#define PMIC_LOAD_ERROR_MSG \ +"PMIC card was not correctly detected. Stop loading PMIC RTC driver\n" + +/* + * Global variables + */ +static int pmic_rtc_major; +static void callback_alarm_asynchronous(void *); +static void callback_alarm_synchronous(void *); +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait); +static DECLARE_WAIT_QUEUE_HEAD(queue_alarm); +static DECLARE_WAIT_QUEUE_HEAD(pmic_rtc_wait); +static pmic_event_callback_t alarm_callback; +static pmic_event_callback_t rtc_callback; +static int pmic_rtc_detected = 0; +static bool pmic_rtc_done = 0; +static struct class *pmic_rtc_class; + +static DECLARE_MUTEX(mutex); + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_rtc_set_time); +EXPORT_SYMBOL(pmic_rtc_get_time); +EXPORT_SYMBOL(pmic_rtc_set_time_alarm); +EXPORT_SYMBOL(pmic_rtc_get_time_alarm); +EXPORT_SYMBOL(pmic_rtc_wait_alarm); +EXPORT_SYMBOL(pmic_rtc_event_sub); +EXPORT_SYMBOL(pmic_rtc_event_unsub); +EXPORT_SYMBOL(pmic_rtc_loaded); + +/* + * Real Time Clock Pmic API + */ + +/*! + * This is the callback function called on TSI Pmic event, used in asynchronous + * call. + */ +static void callback_alarm_asynchronous(void *unused) +{ + pmic_rtc_done = true; +} + +/*! + * This is the callback function is used in test code for (un)sub. + */ +static void callback_test_sub(void) +{ + printk(KERN_INFO "*****************************************\n"); + printk(KERN_INFO "***** PMIC RTC 'Alarm IT CallBack' ******\n"); + printk(KERN_INFO "*****************************************\n"); +} + +/*! + * This is the callback function called on TSI Pmic event, used in synchronous + * call. + */ +static void callback_alarm_synchronous(void *unused) +{ + printk(KERN_INFO "*** Alarm IT Pmic ***\n"); + wake_up(&queue_alarm); +} + +/*! + * This function wait the Alarm event + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_wait_alarm(void) +{ + DEFINE_WAIT(wait); + alarm_callback.func = callback_alarm_synchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + prepare_to_wait(&queue_alarm, &wait, TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&queue_alarm, &wait); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TODAI, alarm_callback)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + value = BITFVAL(MC13783_RTCTIME_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_TIME, value, mask)); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + value = BITFVAL(MC13783_RTCDAY_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY, value, mask)); + + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCTIME_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_TIME, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCTIME_TIME); + + mask = BITFMASK(MC13783_RTCDAY_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCDAY_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + return PMIC_SUCCESS; +} + +/*! + * This function set the real time clock alarm of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time_alarm(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + int ret; + + if ((ret = down_interruptible(&mutex)) < 0) + return ret; + + tod_reg_val = pmic_time->tv_sec % 86400; + day_reg_val = pmic_time->tv_sec / 86400; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + value = BITFVAL(MC13783_RTCALARM_TIME, tod_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_ALARM, value, mask)); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + value = BITFVAL(MC13783_RTCALARM_DAY, day_reg_val); + CHECK_ERROR(pmic_write_reg(REG_RTC_DAY_ALARM, value, mask)); + up(&mutex); + return PMIC_SUCCESS; +} + +/*! + * This function get the real time clock alarm of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time_alarm(struct timeval * pmic_time) +{ + unsigned int tod_reg_val = 0; + unsigned int day_reg_val = 0; + unsigned int mask, value; + + mask = BITFMASK(MC13783_RTCALARM_TIME); + CHECK_ERROR(pmic_read_reg(REG_RTC_ALARM, &value, mask)); + tod_reg_val = BITFEXT(value, MC13783_RTCALARM_TIME); + + mask = BITFMASK(MC13783_RTCALARM_DAY); + CHECK_ERROR(pmic_read_reg(REG_RTC_DAY_ALARM, &value, mask)); + day_reg_val = BITFEXT(value, MC13783_RTCALARM_DAY); + + pmic_time->tv_sec = (unsigned long)((unsigned long)(tod_reg_val & + 0x0001FFFF) + + (unsigned long)(day_reg_val * + 86400)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to un/subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event(t_rtc_int event, void *callback, bool sub) +{ + type_event rtc_event; + if (callback == NULL) { + return PMIC_ERROR; + } else { + rtc_callback.func = callback; + rtc_callback.param = NULL; + } + switch (event) { + case RTC_IT_ALARM: + rtc_event = EVENT_TODAI; + break; + case RTC_IT_1HZ: + rtc_event = EVENT_E1HZI; + break; + case RTC_IT_RST: + rtc_event = EVENT_RTCRSTI; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub == true) { + CHECK_ERROR(pmic_event_subscribe(rtc_event, rtc_callback)); + } else { + CHECK_ERROR(pmic_event_unsubscribe(rtc_event, rtc_callback)); + } + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_sub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, true)); + return PMIC_SUCCESS; +} + +/*! + * This function is used to un subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_unsub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, false)); + return PMIC_SUCCESS; +} + +/* Called without the kernel lock - fine */ +static unsigned int pmic_rtc_poll(struct file *file, poll_table * wait) +{ + /*poll_wait(file, &pmic_rtc_wait, wait); */ + + if (pmic_rtc_done) + return POLLIN | POLLRDNORM; + return 0; +} + +/*! + * This function implements IOCTL controls on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct timeval *pmic_time = NULL; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + if (arg) { + if ((pmic_time = kmalloc(sizeof(struct timeval), + GFP_KERNEL)) == NULL) { + return -ENOMEM; + } + /* if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } */ + } + + switch (cmd) { + case PMIC_RTC_SET_TIME: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC\n"); + CHECK_ERROR(pmic_rtc_set_time(pmic_time)); + break; + case PMIC_RTC_GET_TIME: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC\n"); + CHECK_ERROR(pmic_rtc_get_time(pmic_time)); + break; + case PMIC_RTC_SET_ALARM: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("SET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_set_time_alarm(pmic_time)); + break; + case PMIC_RTC_GET_ALARM: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + pr_debug("GET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_get_time_alarm(pmic_time)); + break; + case PMIC_RTC_WAIT_ALARM: + printk(KERN_INFO "WAIT ALARM...\n"); + CHECK_ERROR(pmic_rtc_event_sub(RTC_IT_ALARM, + callback_test_sub)); + CHECK_ERROR(pmic_rtc_wait_alarm()); + printk(KERN_INFO "ALARM DONE\n"); + CHECK_ERROR(pmic_rtc_event_unsub(RTC_IT_ALARM, + callback_test_sub)); + break; + case PMIC_RTC_ALARM_REGISTER: + printk(KERN_INFO "PMIC RTC ALARM REGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_TODAI, alarm_callback)); + break; + case PMIC_RTC_ALARM_UNREGISTER: + printk(KERN_INFO "PMIC RTC ALARM UNREGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_unsubscribe + (EVENT_TODAI, alarm_callback)); + pmic_rtc_done = false; + break; + default: + pr_debug("%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + if (arg) { + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) { + return -EFAULT; + } + kfree(pmic_time); + } + + return 0; +} + +/*! + * This function implements the open method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function implements the release method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function is called to put the RTC in a low power state. + * There is no need for power handlers for the RTC device. + * The RTC cannot be suspended. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +/*! + * This function is called to resume the RTC from a low power state. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_rtc_resume(struct platform_device *pdev) +{ + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ + +static struct file_operations pmic_rtc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_rtc_ioctl, + .poll = pmic_rtc_poll, + .open = pmic_rtc_open, + .release = pmic_rtc_release, +}; + +int pmic_rtc_loaded(void) +{ + return pmic_rtc_detected; +} + +static int pmic_rtc_remove(struct platform_device *pdev) +{ + device_destroy(pmic_rtc_class, MKDEV(pmic_rtc_major, 0)); + class_destroy(pmic_rtc_class); + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return 0; +} + +static int pmic_rtc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct device *temp_class; + + pmic_rtc_major = register_chrdev(0, "pmic_rtc", &pmic_rtc_fops); + if (pmic_rtc_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_rtc\n"); + return pmic_rtc_major; + } + + pmic_rtc_class = class_create(THIS_MODULE, "pmic_rtc"); + if (IS_ERR(pmic_rtc_class)) { + printk(KERN_ERR "Error creating pmic rtc class.\n"); + ret = PTR_ERR(pmic_rtc_class); + goto err_out1; + } + + temp_class = device_create(pmic_rtc_class, NULL, + MKDEV(pmic_rtc_major, 0), NULL, + "pmic_rtc"); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic rtc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_rtc_detected = 1; + printk(KERN_INFO "PMIC RTC successfully probed\n"); + return ret; + + err_out2: + class_destroy(pmic_rtc_class); + err_out1: + unregister_chrdev(pmic_rtc_major, "pmic_rtc"); + return ret; +} + +static struct platform_driver pmic_rtc_driver_ldm = { + .driver = { + .name = "pmic_rtc", + .owner = THIS_MODULE, + }, + .suspend = pmic_rtc_suspend, + .resume = pmic_rtc_resume, + .probe = pmic_rtc_probe, + .remove = pmic_rtc_remove, +}; + +static int __init pmic_rtc_init(void) +{ + pr_debug("PMIC RTC driver loading...\n"); + return platform_driver_register(&pmic_rtc_driver_ldm); +} +static void __exit pmic_rtc_exit(void) +{ + platform_driver_unregister(&pmic_rtc_driver_ldm); + pr_debug("PMIC RTC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_rtc_init); +module_exit(pmic_rtc_exit); + +MODULE_DESCRIPTION("Pmic_rtc driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h new file mode 100644 index 000000000000..16e968dd9977 --- /dev/null +++ b/drivers/mxc/pmic/mc13783/pmic_rtc_defs.h @@ -0,0 +1,47 @@ +/* + * 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 + */ +#ifndef __MC13783_RTC_DEFS_H__ +#define __MC13783_RTC_DEFS_H__ + +/*! + * @file mc13783/pmic_rtc_defs.h + * @brief This is the internal header of PMIC(mc13783) RTC driver. + * + * @ingroup PMIC_RTC + */ + +/* + * RTC Time + */ +#define MC13783_RTCTIME_TIME_LSH 0 +#define MC13783_RTCTIME_TIME_WID 17 + +/* + * RTC Alarm + */ +#define MC13783_RTCALARM_TIME_LSH 0 +#define MC13783_RTCALARM_TIME_WID 17 + +/* + * RTC Day + */ +#define MC13783_RTCDAY_DAY_LSH 0 +#define MC13783_RTCDAY_DAY_WID 15 + +/* + * RTC Day alarm + */ +#define MC13783_RTCALARM_DAY_LSH 0 +#define MC13783_RTCALARM_DAY_WID 15 + +#endif /* __MC13783_RTC_DEFS_H__ */ diff --git a/drivers/mxc/pmic/mc13892/Kconfig b/drivers/mxc/pmic/mc13892/Kconfig new file mode 100644 index 000000000000..930e06ab2282 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/Kconfig @@ -0,0 +1,48 @@ +# +# PMIC Modules configuration +# + +config MXC_MC13892_ADC + tristate "MC13892 ADC support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 ADC module driver. This module provides kernel API + for the ADC system of MC13892. + It controls also the touch screen interface. + If you want MC13892 ADC support, you should say Y here + +config MXC_MC13892_RTC + tristate "MC13892 Real Time Clock (RTC) support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 RTC module driver. This module provides kernel API + for RTC part of MC13892. + If you want MC13892 RTC support, you should say Y here +config MXC_MC13892_LIGHT + tristate "MC13892 Light and Backlight support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 Light module driver. This module provides kernel API + for led and backlight control part of MC13892. + If you want MC13892 Light support, you should say Y here +config MXC_MC13892_BATTERY + tristate "MC13892 Battery API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 battery module driver. This module provides kernel API + for battery control part of MC13892. + If you want MC13892 battery support, you should say Y here +config MXC_MC13892_CONNECTIVITY + tristate "MC13892 Connectivity API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 connectivity module driver. This module provides kernel API + for USB/RS232 connectivity control part of MC13892. + If you want MC13892 connectivity support, you should say Y here +config MXC_MC13892_POWER + tristate "MC13892 Power API support" + depends on MXC_PMIC_MC13892 + ---help--- + This is the MC13892 power and supplies module driver. This module provides kernel API + for power and regulator control part of MC13892. + If you want MC13892 power support, you should say Y here diff --git a/drivers/mxc/pmic/mc13892/Makefile b/drivers/mxc/pmic/mc13892/Makefile new file mode 100644 index 000000000000..0ed2b7eb4c11 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the mc13783 pmic drivers. +# + +obj-$(CONFIG_MXC_MC13892_ADC) += pmic_adc.o +#obj-$(CONFIG_MXC_MC13892_RTC) += pmic_rtc.o +obj-$(CONFIG_MXC_MC13892_LIGHT) += pmic_light.o +obj-$(CONFIG_MXC_MC13892_BATTERY) += pmic_battery.o +#obj-$(CONFIG_MXC_MC13892_CONNECTIVITY) += pmic_convity.o +#obj-$(CONFIG_MXC_MC13892_POWER) += pmic_power.o diff --git a/drivers/mxc/pmic/mc13892/pmic_adc.c b/drivers/mxc/pmic/mc13892/pmic_adc.c new file mode 100644 index 000000000000..cec4d6045974 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_adc.c @@ -0,0 +1,984 @@ +/* + * Copyright 2008-2009 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 + */ +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/device.h> + +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#include "../core/pmic.h" + +#define DEF_ADC_0 0x008000 +#define DEF_ADC_3 0x0001c0 + +#define ADC_NB_AVAILABLE 2 + +#define MAX_CHANNEL 7 + +#define MC13892_ADC0_TS_M_LSH 14 +#define MC13892_ADC0_TS_M_WID 3 + +/* + * Maximun allowed variation in the three X/Y co-ordinates acquired from + * touch-screen + */ +#define DELTA_Y_MAX 50 +#define DELTA_X_MAX 50 + +/* + * ADC 0 + */ +#define ADC_WAIT_TSI_0 0x001400 + +#define ADC_INC 0x030000 +#define ADC_BIS 0x800000 +#define ADC_CHRGRAW_D5 0x008000 + +/* + * ADC 1 + */ + +#define ADC_EN 0x000001 +#define ADC_SGL_CH 0x000002 +#define ADC_ADSEL 0x000008 +#define ADC_CH_0_POS 5 +#define ADC_CH_0_MASK 0x0000E0 +#define ADC_CH_1_POS 8 +#define ADC_CH_1_MASK 0x000700 +#define ADC_DELAY_POS 11 +#define ADC_DELAY_MASK 0x07F800 +#define ADC_ATO 0x080000 +#define ASC_ADC 0x100000 +#define ADC_WAIT_TSI_1 0x200001 +#define ADC_NO_ADTRIG 0x200000 + +/* + * ADC 2 - 4 + */ +#define ADD1_RESULT_MASK 0x00000FFC +#define ADD2_RESULT_MASK 0x00FFC000 +#define ADC_TS_MASK 0x00FFCFFC + +#define ADC_WCOMP 0x040000 +#define ADC_WCOMP_H_POS 0 +#define ADC_WCOMP_L_POS 9 +#define ADC_WCOMP_H_MASK 0x00003F +#define ADC_WCOMP_L_MASK 0x007E00 + +#define ADC_MODE_MASK 0x00003F + +#define ADC_INT_BISDONEI 0x02 +#define ADC_TSMODE_MASK 0x007000 + +typedef enum adc_state { + ADC_FREE, + ADC_USED, + ADC_MONITORING, +} t_adc_state; + +typedef enum reading_mode { + /*! + * Enables lithium cell reading + */ + M_LITHIUM_CELL = 0x000001, + /*! + * Enables charge current reading + */ + M_CHARGE_CURRENT = 0x000002, + /*! + * Enables battery current reading + */ + M_BATTERY_CURRENT = 0x000004, +} t_reading_mode; + +typedef struct { + /*! + * Delay before first conversion + */ + unsigned int delay; + /*! + * sets the ATX bit for delay on all conversion + */ + bool conv_delay; + /*! + * Sets the single channel mode + */ + bool single_channel; + /*! + * Channel selection 1 + */ + t_channel channel_0; + /*! + * Channel selection 2 + */ + t_channel channel_1; + /*! + * Used to configure ADC mode with t_reading_mode + */ + t_reading_mode read_mode; + /*! + * Sets the Touch screen mode + */ + bool read_ts; + /*! + * Wait TSI event before touch screen reading + */ + bool wait_tsi; + /*! + * Sets CHRGRAW scaling to divide by 5 + * Only supported on 2.0 and higher + */ + bool chrgraw_devide_5; + /*! + * Return ADC values + */ + unsigned int value[8]; + /*! + * Return touch screen values + */ + t_touch_screen ts_value; +} t_adc_param; + +static int pmic_adc_filter(t_touch_screen *ts_curr); +int mc13892_adc_request(bool read_ts); +int mc13892_adc_release(int adc_index); +t_reading_mode mc13892_set_read_mode(t_channel channel); +PMIC_STATUS mc13892_adc_read_ts(t_touch_screen *touch_sample, int wait_tsi); + +/* internal function */ +static void callback_tsi(void *); +static void callback_adcdone(void *); +static void callback_adcbisdone(void *); + +static int swait; + +static int suspend_flag; + +static wait_queue_head_t suspendq; + +/* EXPORTED FUNCTIONS */ +EXPORT_SYMBOL(pmic_adc_init); +EXPORT_SYMBOL(pmic_adc_deinit); +EXPORT_SYMBOL(pmic_adc_convert); +EXPORT_SYMBOL(pmic_adc_convert_8x); +EXPORT_SYMBOL(pmic_adc_set_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_mode); +EXPORT_SYMBOL(pmic_adc_get_touch_sample); + +static DECLARE_COMPLETION(adcdone_it); +static DECLARE_COMPLETION(adcbisdone_it); +static DECLARE_COMPLETION(adc_tsi); +static pmic_event_callback_t tsi_event; +static pmic_event_callback_t event_adc; +static pmic_event_callback_t event_adc_bis; +static bool data_ready_adc_1; +static bool data_ready_adc_2; +static bool adc_ts; +static bool wait_ts; +static bool monitor_en; +static bool monitor_adc; +static DECLARE_MUTEX(convert_mutex); + +static DECLARE_WAIT_QUEUE_HEAD(queue_adc_busy); +static t_adc_state adc_dev[2]; + +static unsigned channel_num[] = { + 0, + 1, + 3, + 4, + 2, + 0, + 1, + 3, + 4, + -1, + 5, + 6, + 7, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1 +}; + +static bool pmic_adc_ready; + +int is_pmic_adc_ready() +{ + return pmic_adc_ready; +} +EXPORT_SYMBOL(is_pmic_adc_ready); + + +static int pmic_adc_suspend(struct platform_device *pdev, pm_message_t state) +{ + suspend_flag = 1; + CHECK_ERROR(pmic_write_reg(REG_ADC0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC4, 0, PMIC_ALL_BITS)); + + return 0; +}; + +static int pmic_adc_resume(struct platform_device *pdev) +{ + /* nothing for mc13892 adc */ + unsigned int adc_0_reg, adc_1_reg, reg_mask; + suspend_flag = 0; + + /* let interrupt of TSI again */ + adc_0_reg = ADC_WAIT_TSI_0; + reg_mask = ADC_WAIT_TSI_0; + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, reg_mask)); + adc_1_reg = ADC_WAIT_TSI_1 | (ADC_BIS * adc_ts); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, PMIC_ALL_BITS)); + + while (swait > 0) { + swait--; + wake_up_interruptible(&suspendq); + } + + return 0; +}; + +static void callback_tsi(void *unused) +{ + pr_debug("*** TSI IT mc13892 PMIC_ADC_GET_TOUCH_SAMPLE ***\n"); + if (wait_ts) { + complete(&adc_tsi); + pmic_event_mask(EVENT_TSI); + } +} + +static void callback_adcdone(void *unused) +{ + if (data_ready_adc_1) + complete(&adcdone_it); +} + +static void callback_adcbisdone(void *unused) +{ + pr_debug("* adcdone bis it callback *\n"); + if (data_ready_adc_2) + complete(&adcbisdone_it); +} + +static int pmic_adc_filter(t_touch_screen *ts_curr) +{ + unsigned int ydiff, xdiff; + unsigned int sample_sumx, sample_sumy; + + if (ts_curr->contact_resistance == 0) { + ts_curr->x_position = 0; + ts_curr->y_position = 0; + return 0; + } + + ydiff = abs(ts_curr->y_position1 - ts_curr->y_position2); + if (ydiff > DELTA_Y_MAX) { + pr_debug("pmic_adc_filter: Ret pos y\n"); + return -1; + } + + xdiff = abs(ts_curr->x_position1 - ts_curr->x_position2); + if (xdiff > DELTA_X_MAX) { + pr_debug("mc13892_adc_filter: Ret pos x\n"); + return -1; + } + + sample_sumx = ts_curr->x_position1 + ts_curr->x_position2; + sample_sumy = ts_curr->y_position1 + ts_curr->y_position2; + + ts_curr->y_position = sample_sumy / 2; + ts_curr->x_position = sample_sumx / 2; + + return 0; +} + +int pmic_adc_init(void) +{ + unsigned int reg_value = 0, i = 0; + + if (suspend_flag == 1) + return -EBUSY; + + for (i = 0; i < ADC_NB_AVAILABLE; i++) + adc_dev[i] = ADC_FREE; + + CHECK_ERROR(pmic_write_reg(REG_ADC0, DEF_ADC_0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC3, DEF_ADC_3, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_ADC4, 0, PMIC_ALL_BITS)); + reg_value = 0x001000; + + data_ready_adc_1 = false; + data_ready_adc_2 = false; + adc_ts = false; + wait_ts = false; + monitor_en = false; + monitor_adc = false; + + /* sub to ADCDone IT */ + event_adc.param = NULL; + event_adc.func = callback_adcdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCDONEI, event_adc)); + + /* sub to ADCDoneBis IT */ + event_adc_bis.param = NULL; + event_adc_bis.func = callback_adcbisdone; + CHECK_ERROR(pmic_event_subscribe(EVENT_ADCBISDONEI, event_adc_bis)); + + /* sub to Touch Screen IT */ + tsi_event.param = NULL; + tsi_event.func = callback_tsi; + CHECK_ERROR(pmic_event_subscribe(EVENT_TSI, tsi_event)); + + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_deinit(void) +{ + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCDONEI, event_adc)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_ADCBISDONEI, event_adc_bis)); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_TSI, tsi_event)); + + return PMIC_SUCCESS; +} + +int mc13892_adc_init_param(t_adc_param * adc_param) +{ + int i = 0; + + if (suspend_flag == 1) + return -EBUSY; + + adc_param->delay = 0; + adc_param->conv_delay = false; + adc_param->single_channel = false; + adc_param->channel_0 = BATTERY_VOLTAGE; + adc_param->channel_1 = BATTERY_VOLTAGE; + adc_param->read_mode = 0; + adc_param->wait_tsi = 0; + adc_param->chrgraw_devide_5 = true; + adc_param->read_ts = false; + adc_param->ts_value.x_position = 0; + adc_param->ts_value.y_position = 0; + adc_param->ts_value.contact_resistance = 0; + for (i = 0; i <= MAX_CHANNEL; i++) + adc_param->value[i] = 0; + + return 0; +} + +PMIC_STATUS mc13892_adc_convert(t_adc_param * adc_param) +{ + bool use_bis = false; + unsigned int adc_0_reg = 0, adc_1_reg = 0, reg_1 = 0, result_reg = + 0, i = 0; + unsigned int result = 0, temp = 0; + pmic_version_t mc13892_ver; + pr_debug("mc13892 ADC - mc13892_adc_convert ....\n"); + if (suspend_flag == 1) + return -EBUSY; + + if (adc_param->wait_tsi) { + /* configure adc to wait tsi interrupt */ + INIT_COMPLETION(adc_tsi); + + /*for ts don't use bis */ + /*put ts in interrupt mode */ + /* still kep reference? */ + adc_0_reg = 0x001400 | (ADC_BIS * 0); + pmic_event_unmask(EVENT_TSI); + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, PMIC_ALL_BITS)); + /*for ts don't use bis */ + adc_1_reg = 0x200001 | (ADC_BIS * 0); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, PMIC_ALL_BITS)); + pr_debug("wait tsi ....\n"); + wait_ts = true; + wait_for_completion_interruptible(&adc_tsi); + wait_ts = false; + } + down(&convert_mutex); + use_bis = mc13892_adc_request(adc_param->read_ts); + if (use_bis < 0) { + pr_debug("process has received a signal and got interrupted\n"); + return -EINTR; + } + + /* CONFIGURE ADC REG 0 */ + adc_0_reg = 0; + adc_1_reg = 0; + if (adc_param->read_ts == false) { + adc_0_reg = adc_param->read_mode & 0x00003F; + /* add auto inc */ + adc_0_reg |= ADC_INC; + if (use_bis) { + /* add adc bis */ + adc_0_reg |= ADC_BIS; + } + mc13892_ver = pmic_get_version(); + if (mc13892_ver.revision >= 20) + if (adc_param->chrgraw_devide_5) + adc_0_reg |= ADC_CHRGRAW_D5; + + if (adc_param->single_channel) + adc_1_reg |= ADC_SGL_CH; + + if (adc_param->conv_delay) + adc_1_reg |= ADC_ATO; + + if (adc_param->single_channel) + adc_1_reg |= ADC_SGL_CH; + + adc_1_reg |= (adc_param->channel_0 << ADC_CH_0_POS) & + ADC_CH_0_MASK; + adc_1_reg |= (adc_param->channel_1 << ADC_CH_1_POS) & + ADC_CH_1_MASK; + } else { + adc_0_reg = 0x002400 | (ADC_BIS * use_bis) | ADC_INC; + } + pr_debug("Write Reg %i = %x\n", REG_ADC0, adc_0_reg); + /*Change has been made here */ + CHECK_ERROR(pmic_write_reg(REG_ADC0, adc_0_reg, + ADC_INC | ADC_BIS | ADC_CHRGRAW_D5 | + 0xfff00ff)); + /* CONFIGURE ADC REG 1 */ + if (adc_param->read_ts == false) { + adc_1_reg |= ADC_NO_ADTRIG; + adc_1_reg |= ADC_EN; + adc_1_reg |= (adc_param->delay << ADC_DELAY_POS) & + ADC_DELAY_MASK; + if (use_bis) + adc_1_reg |= ADC_BIS; + } else { + /* configure and start convert to read x and y position */ + /* configure to read 2 value in channel selection 1 & 2 */ + adc_1_reg = 0x100409 | (ADC_BIS * use_bis) | ADC_NO_ADTRIG; + /* set ATOx = 5, it could be better for ts ADC */ + adc_1_reg |= 0x002800; + } + reg_1 = adc_1_reg; + if (use_bis == 0) { + data_ready_adc_1 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_1 = true; + pr_debug("Write Reg %i = %x\n", REG_ADC1, adc_1_reg); + INIT_COMPLETION(adcdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, + ADC_SGL_CH | ADC_ATO | ADC_ADSEL + | ADC_CH_0_MASK | ADC_CH_1_MASK | + ADC_NO_ADTRIG | ADC_EN | + ADC_DELAY_MASK | ASC_ADC | ADC_BIS)); + pr_debug("wait adc done \n"); + wait_for_completion_interruptible(&adcdone_it); + data_ready_adc_1 = false; + } else { + data_ready_adc_2 = false; + adc_1_reg |= ASC_ADC; + data_ready_adc_2 = true; + INIT_COMPLETION(adcbisdone_it); + CHECK_ERROR(pmic_write_reg(REG_ADC1, adc_1_reg, 0xFFFFFF)); + temp = 0x800000; + CHECK_ERROR(pmic_write_reg(REG_ADC3, temp, 0xFFFFFF)); + pr_debug("wait adc done bis\n"); + wait_for_completion_interruptible(&adcbisdone_it); + data_ready_adc_2 = false; + } + /* read result and store in adc_param */ + result = 0; + if (use_bis == 0) + result_reg = REG_ADC2; + else + result_reg = REG_ADC4; + + CHECK_ERROR(pmic_write_reg(REG_ADC1, 4 << ADC_CH_1_POS, + ADC_CH_0_MASK | ADC_CH_1_MASK)); + + for (i = 0; i <= 3; i++) { + CHECK_ERROR(pmic_read_reg(result_reg, &result, PMIC_ALL_BITS)); + adc_param->value[i] = ((result & ADD1_RESULT_MASK) >> 2); + adc_param->value[i + 4] = ((result & ADD2_RESULT_MASK) >> 14); + pr_debug("value[%d] = %d, value[%d] = %d\n", + i, adc_param->value[i], + i + 4, adc_param->value[i + 4]); + } + if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[0]; + adc_param->ts_value.x_position1 = adc_param->value[0]; + adc_param->ts_value.x_position2 = adc_param->value[1]; + adc_param->ts_value.y_position = adc_param->value[3]; + adc_param->ts_value.y_position1 = adc_param->value[3]; + adc_param->ts_value.y_position2 = adc_param->value[4]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + CHECK_ERROR(pmic_write_reg(REG_ADC0, 0x0, + ADC_TSMODE_MASK)); + } + + /*if (adc_param->read_ts) { + adc_param->ts_value.x_position = adc_param->value[2]; + adc_param->ts_value.y_position = adc_param->value[5]; + adc_param->ts_value.contact_resistance = adc_param->value[6]; + } */ + mc13892_adc_release(use_bis); + up(&convert_mutex); + + return PMIC_SUCCESS; +} + +t_reading_mode mc13892_set_read_mode(t_channel channel) +{ + t_reading_mode read_mode = 0; + + switch (channel) { + case CHARGE_CURRENT: + read_mode = M_CHARGE_CURRENT; + break; + case BATTERY_CURRENT: + read_mode = M_BATTERY_CURRENT; + break; + default: + read_mode = 0; + } + + return read_mode; +} + +PMIC_STATUS pmic_adc_convert(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + PMIC_STATUS ret; + + if (suspend_flag == 1) + return -EBUSY; + + channel = channel_num[channel]; + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13892_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13892_set_read_mode(channel); + + /* Find the group */ + if (channel <= 7) + adc_param.channel_0 = channel; + else + return PMIC_PARAMETER_ERROR; + + ret = mc13892_adc_convert(&adc_param); + *result = adc_param.value[0]; + return ret; +} + +PMIC_STATUS pmic_adc_convert_8x(t_channel channel, unsigned short *result) +{ + t_adc_param adc_param; + int i; + PMIC_STATUS ret; + if (suspend_flag == 1) + return -EBUSY; + + channel = channel_num[channel]; + + if (channel == -1) { + pr_debug("Wrong channel ID\n"); + return PMIC_PARAMETER_ERROR; + } + mc13892_adc_init_param(&adc_param); + pr_debug("pmic_adc_convert_8x\n"); + adc_param.read_ts = false; + adc_param.single_channel = true; + adc_param.read_mode = mc13892_set_read_mode(channel); + + if (channel <= 7) { + adc_param.channel_0 = channel; + adc_param.channel_1 = channel; + } else + return PMIC_PARAMETER_ERROR; + + ret = mc13892_adc_convert(&adc_param); + for (i = 0; i <= 7; i++) + result[i] = adc_param.value[i]; + + return ret; +} + +PMIC_STATUS pmic_adc_set_touch_mode(t_touch_mode touch_mode) +{ + if (suspend_flag == 1) + return -EBUSY; + + CHECK_ERROR(pmic_write_reg(REG_ADC0, + BITFVAL(MC13892_ADC0_TS_M, touch_mode), + BITFMASK(MC13892_ADC0_TS_M))); + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_get_touch_mode(t_touch_mode * touch_mode) +{ + unsigned int value; + if (suspend_flag == 1) + return -EBUSY; + + CHECK_ERROR(pmic_read_reg(REG_ADC0, &value, PMIC_ALL_BITS)); + + *touch_mode = BITFEXT(value, MC13892_ADC0_TS_M); + + return PMIC_SUCCESS; +} + +PMIC_STATUS pmic_adc_get_touch_sample(t_touch_screen *touch_sample, int wait) +{ + if (mc13892_adc_read_ts(touch_sample, wait) != 0) + return PMIC_ERROR; + if (0 == pmic_adc_filter(touch_sample)) + return PMIC_SUCCESS; + else + return PMIC_ERROR; +} + +PMIC_STATUS mc13892_adc_read_ts(t_touch_screen *ts_value, int wait_tsi) +{ + t_adc_param param; + pr_debug("mc13892_adc : mc13892_adc_read_ts\n"); + if (suspend_flag == 1) + return -EBUSY; + + if (wait_ts) { + pr_debug("mc13892_adc : error TS busy \n"); + return PMIC_ERROR; + } + mc13892_adc_init_param(¶m); + param.wait_tsi = wait_tsi; + param.read_ts = true; + if (mc13892_adc_convert(¶m) != 0) + return PMIC_ERROR; + /* check if x-y is ok */ + if (param.ts_value.contact_resistance < 1000) { + ts_value->x_position = param.ts_value.x_position; + ts_value->x_position1 = param.ts_value.x_position1; + ts_value->x_position2 = param.ts_value.x_position2; + + ts_value->y_position = param.ts_value.y_position; + ts_value->y_position1 = param.ts_value.y_position1; + ts_value->y_position2 = param.ts_value.y_position2; + + ts_value->contact_resistance = + param.ts_value.contact_resistance + 1; + + } else { + ts_value->x_position = 0; + ts_value->y_position = 0; + ts_value->contact_resistance = 0; + + } + return PMIC_SUCCESS; +} + +int mc13892_adc_request(bool read_ts) +{ + int adc_index = -1; + if (read_ts != 0) { + /*for ts we use bis=0 */ + if (adc_dev[0] == ADC_USED) + return -1; + /*no wait here */ + adc_dev[0] = ADC_USED; + adc_index = 0; + } else { + /*for other adc use bis = 1 */ + if (adc_dev[1] == ADC_USED) { + return -1; + /*no wait here */ + } + adc_dev[1] = ADC_USED; + adc_index = 1; + } + pr_debug("mc13892_adc : request ADC %d\n", adc_index); + return adc_index; +} + +int mc13892_adc_release(int adc_index) +{ + while (suspend_flag == 1) { + swait++; + /* Block if the device is suspended */ + if (wait_event_interruptible(suspendq, (suspend_flag == 0))) + return -ERESTARTSYS; + } + + pr_debug("mc13892_adc : release ADC %d\n", adc_index); + if ((adc_dev[adc_index] == ADC_MONITORING) || + (adc_dev[adc_index] == ADC_USED)) { + adc_dev[adc_index] = ADC_FREE; + wake_up(&queue_adc_busy); + return 0; + } + return -1; +} + +#ifdef DEBUG +static t_adc_param adc_param_db; + +static ssize_t adc_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int *value = adc_param_db.value; + + pr_debug("adc_info\n"); + + pr_debug("ch0\t\t%d\n", adc_param_db.channel_0); + pr_debug("ch1\t\t%d\n", adc_param_db.channel_1); + pr_debug("d5\t\t%d\n", adc_param_db.chrgraw_devide_5); + pr_debug("conv delay\t%d\n", adc_param_db.conv_delay); + pr_debug("delay\t\t%d\n", adc_param_db.delay); + pr_debug("read mode\t%d\n", adc_param_db.read_mode); + pr_debug("read ts\t\t%d\n", adc_param_db.read_ts); + pr_debug("single ch\t%d\n", adc_param_db.single_channel); + pr_debug("wait ts int\t%d\n", adc_param_db.wait_tsi); + pr_debug("value0-3:\t%d\t%d\t%d\t%d\n", value[0], value[1], + value[2], value[3]); + pr_debug("value4-7:\t%d\t%d\t%d\t%d\n", value[4], value[5], + value[6], value[7]); + + return 0; +} + +enum { + ADC_SET_CH0 = 0, + ADC_SET_CH1, + ADC_SET_DV5, + ADC_SET_CON_DELAY, + ADC_SET_DELAY, + ADC_SET_RM, + ADC_SET_RT, + ADC_SET_S_CH, + ADC_SET_WAIT_TS, + ADC_INIT_P, + ADC_START, + ADC_TS, + ADC_TS_READ, + ADC_TS_CAL, + ADC_CMD_MAX +}; + +static const char *const adc_cmd[ADC_CMD_MAX] = { + [ADC_SET_CH0] = "ch0", + [ADC_SET_CH1] = "ch1", + [ADC_SET_DV5] = "dv5", + [ADC_SET_CON_DELAY] = "cd", + [ADC_SET_DELAY] = "dl", + [ADC_SET_RM] = "rm", + [ADC_SET_RT] = "rt", + [ADC_SET_S_CH] = "sch", + [ADC_SET_WAIT_TS] = "wt", + [ADC_INIT_P] = "init", + [ADC_START] = "start", + [ADC_TS] = "touch", + [ADC_TS_READ] = "touchr", + [ADC_TS_CAL] = "cal" +}; + +static int cmd(unsigned int index, int value) +{ + t_touch_screen ts; + + switch (index) { + case ADC_SET_CH0: + adc_param_db.channel_0 = value; + break; + case ADC_SET_CH1: + adc_param_db.channel_1 = value; + break; + case ADC_SET_DV5: + adc_param_db.chrgraw_devide_5 = value; + break; + case ADC_SET_CON_DELAY: + adc_param_db.conv_delay = value; + break; + case ADC_SET_RM: + adc_param_db.read_mode = value; + break; + case ADC_SET_RT: + adc_param_db.read_ts = value; + break; + case ADC_SET_S_CH: + adc_param_db.single_channel = value; + break; + case ADC_SET_WAIT_TS: + adc_param_db.wait_tsi = value; + break; + case ADC_INIT_P: + mc13892_adc_init_param(&adc_param_db); + break; + case ADC_START: + mc13892_adc_convert(&adc_param_db); + break; + case ADC_TS: + pmic_adc_get_touch_sample(&ts, 1); + pr_debug("x = %d\n", ts.x_position); + pr_debug("y = %d\n", ts.y_position); + pr_debug("p = %d\n", ts.contact_resistance); + break; + case ADC_TS_READ: + pmic_adc_get_touch_sample(&ts, 0); + pr_debug("x = %d\n", ts.x_position); + pr_debug("y = %d\n", ts.y_position); + pr_debug("p = %d\n", ts.contact_resistance); + break; + case ADC_TS_CAL: + break; + default: + pr_debug("error command\n"); + break; + } + return 0; +} + +static ssize_t adc_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state = 0; + const char *const *s; + char *p, *q; + int error; + int len, value = 0; + + pr_debug("adc_ctl\n"); + + q = NULL; + q = memchr(buf, ' ', count); + + if (q != NULL) { + len = q - buf; + q += 1; + value = simple_strtoul(q, NULL, 10); + } else { + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + } + + for (s = &adc_cmd[state]; state < ADC_CMD_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) + break; + } + if (state < ADC_CMD_MAX && *s) + error = cmd(state, value); + else + error = -EINVAL; + + return count; +} + +#else +static ssize_t adc_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t adc_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +#endif + +static DEVICE_ATTR(adc, 0644, adc_info, adc_ctl); + +static int pmic_adc_module_probe(struct platform_device *pdev) +{ + int ret = 0; + + pr_debug("PMIC ADC start probe\n"); + ret = device_create_file(&(pdev->dev), &dev_attr_adc); + if (ret) { + pr_debug("Can't create device file!\n"); + return -ENODEV; + } + + init_waitqueue_head(&suspendq); + + ret = pmic_adc_init(); + if (ret != PMIC_SUCCESS) { + pr_debug("Error in pmic_adc_init.\n"); + goto rm_dev_file; + } + + pmic_adc_ready = 1; + pr_debug("PMIC ADC successfully probed\n"); + return 0; + + rm_dev_file: + device_remove_file(&(pdev->dev), &dev_attr_adc); + return ret; +} + +static int pmic_adc_module_remove(struct platform_device *pdev) +{ + pmic_adc_deinit(); + pmic_adc_ready = 0; + pr_debug("PMIC ADC successfully removed\n"); + return 0; +} + +static struct platform_driver pmic_adc_driver_ldm = { + .driver = { + .name = "pmic_adc", + }, + .suspend = pmic_adc_suspend, + .resume = pmic_adc_resume, + .probe = pmic_adc_module_probe, + .remove = pmic_adc_module_remove, +}; + +static int __init pmic_adc_module_init(void) +{ + pr_debug("PMIC ADC driver loading...\n"); + return platform_driver_register(&pmic_adc_driver_ldm); +} + +static void __exit pmic_adc_module_exit(void) +{ + platform_driver_unregister(&pmic_adc_driver_ldm); + pr_debug("PMIC ADC driver successfully unloaded\n"); +} + +module_init(pmic_adc_module_init); +module_exit(pmic_adc_module_exit); + +MODULE_DESCRIPTION("PMIC ADC device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13892/pmic_battery.c b/drivers/mxc/pmic/mc13892/pmic_battery.c new file mode 100644 index 000000000000..8535eb0a34e4 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_battery.c @@ -0,0 +1,634 @@ +/* + * Copyright 2009 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 + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#include <linux/delay.h> +#include <asm/mach-types.h> +#include <linux/pmic_battery.h> +#include <linux/pmic_adc.h> +#include <linux/pmic_status.h> + +#define BIT_CHG_VOL_LSH 0 +#define BIT_CHG_VOL_WID 3 + +#define BIT_CHG_CURR_LSH 3 +#define BIT_CHG_CURR_WID 4 + +#define BIT_CHG_PLIM_LSH 15 +#define BIT_CHG_PLIM_WID 2 + +#define BIT_CHG_DETS_LSH 6 +#define BIT_CHG_DETS_WID 1 +#define BIT_CHG_CURRS_LSH 11 +#define BIT_CHG_CURRS_WID 1 + +#define TRICKLE_CHG_EN_LSH 7 +#define LOW_POWER_BOOT_ACK_LSH 8 +#define BAT_TH_CHECK_DIS_LSH 9 +#define BATTFET_CTL_EN_LSH 10 +#define BATTFET_CTL_LSH 11 +#define REV_MOD_EN_LSH 13 +#define PLIM_DIS_LSH 17 +#define CHG_LED_EN_LSH 18 +#define RESTART_CHG_STAT_LSH 20 +#define AUTO_CHG_DIS_LSH 21 +#define CYCLING_DIS_LSH 22 +#define VI_PROGRAM_EN_LSH 23 + +#define TRICKLE_CHG_EN_WID 1 +#define LOW_POWER_BOOT_ACK_WID 1 +#define BAT_TH_CHECK_DIS_WID 1 +#define BATTFET_CTL_EN_WID 1 +#define BATTFET_CTL_WID 1 +#define REV_MOD_EN_WID 1 +#define PLIM_DIS_WID 1 +#define CHG_LED_EN_WID 1 +#define RESTART_CHG_STAT_WID 1 +#define AUTO_CHG_DIS_WID 1 +#define CYCLING_DIS_WID 1 +#define VI_PROGRAM_EN_WID 1 + +#define ACC_STARTCC_LSH 0 +#define ACC_STARTCC_WID 1 +#define ACC_RSTCC_LSH 1 +#define ACC_RSTCC_WID 1 +#define ACC_CCFAULT_LSH 7 +#define ACC_CCFAULT_WID 7 +#define ACC_CCOUT_LSH 8 +#define ACC_CCOUT_WID 16 +#define ACC1_ONEC_LSH 0 +#define ACC1_ONEC_WID 15 + +#define ACC_CALIBRATION 0x17 +#define ACC_START_COUNTER 0x07 +#define ACC_STOP_COUNTER 0x2 +#define ACC_CONTROL_BIT_MASK 0x1f +#define ACC_ONEC_VALUE 2621 +#define ACC_COULOMB_PER_LSB 1 +#define ACC_CALIBRATION_DURATION_MSECS 20 + +#define BAT_VOLTAGE_UNIT_UV 4692 +#define BAT_CURRENT_UNIT_UA 5870 +#define CHG_VOLTAGE_UINT_UV 23474 +#define CHG_MIN_CURRENT_UA 3500 + +#define COULOMB_TO_UAH(c) (10000 * c / 36) + +enum chg_setting { + TRICKLE_CHG_EN, + LOW_POWER_BOOT_ACK, + BAT_TH_CHECK_DIS, + BATTFET_CTL_EN, + BATTFET_CTL, + REV_MOD_EN, + PLIM_DIS, + CHG_LED_EN, + RESTART_CHG_STAT, + AUTO_CHG_DIS, + CYCLING_DIS, + VI_PROGRAM_EN +}; + +static int pmic_set_chg_current(unsigned short curr) +{ + unsigned int mask; + unsigned int value; + + value = BITFVAL(BIT_CHG_CURR, curr); + mask = BITFMASK(BIT_CHG_CURR); + CHECK_ERROR(pmic_write_reg(REG_CHARGE, value, mask)); + + return 0; +} + +static int pmic_set_chg_misc(enum chg_setting type, unsigned short flag) +{ + + unsigned int reg_value = 0; + unsigned int mask = 0; + + switch (type) { + case TRICKLE_CHG_EN: + reg_value = BITFVAL(TRICKLE_CHG_EN, flag); + mask = BITFMASK(TRICKLE_CHG_EN); + break; + case LOW_POWER_BOOT_ACK: + reg_value = BITFVAL(LOW_POWER_BOOT_ACK, flag); + mask = BITFMASK(LOW_POWER_BOOT_ACK); + break; + case BAT_TH_CHECK_DIS: + reg_value = BITFVAL(BAT_TH_CHECK_DIS, flag); + mask = BITFMASK(BAT_TH_CHECK_DIS); + break; + case BATTFET_CTL_EN: + reg_value = BITFVAL(BATTFET_CTL_EN, flag); + mask = BITFMASK(BATTFET_CTL_EN); + break; + case BATTFET_CTL: + reg_value = BITFVAL(BATTFET_CTL, flag); + mask = BITFMASK(BATTFET_CTL); + break; + case REV_MOD_EN: + reg_value = BITFVAL(REV_MOD_EN, flag); + mask = BITFMASK(REV_MOD_EN); + break; + case PLIM_DIS: + reg_value = BITFVAL(PLIM_DIS, flag); + mask = BITFMASK(PLIM_DIS); + break; + case CHG_LED_EN: + reg_value = BITFVAL(CHG_LED_EN, flag); + mask = BITFMASK(CHG_LED_EN); + break; + case RESTART_CHG_STAT: + reg_value = BITFVAL(RESTART_CHG_STAT, flag); + mask = BITFMASK(RESTART_CHG_STAT); + break; + case AUTO_CHG_DIS: + reg_value = BITFVAL(AUTO_CHG_DIS, flag); + mask = BITFMASK(AUTO_CHG_DIS); + break; + case CYCLING_DIS: + reg_value = BITFVAL(CYCLING_DIS, flag); + mask = BITFMASK(CYCLING_DIS); + break; + case VI_PROGRAM_EN: + reg_value = BITFVAL(VI_PROGRAM_EN, flag); + mask = BITFMASK(VI_PROGRAM_EN); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(REG_CHARGE, reg_value, mask)); + + return 0; +} + +static int pmic_get_batt_voltage(unsigned short *voltage) +{ + t_channel channel; + unsigned short result[8]; + + channel = BATTERY_VOLTAGE; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *voltage = result[0]; + + return 0; +} + +static int pmic_get_batt_current(unsigned short *curr) +{ + t_channel channel; + unsigned short result[8]; + + channel = BATTERY_CURRENT; + CHECK_ERROR(pmic_adc_convert(channel, result)); + *curr = result[0]; + + return 0; +} + +static int coulomb_counter_calibration; +static unsigned int coulomb_counter_start_time_msecs; + +static int pmic_start_coulomb_counter(void) +{ + /* set scaler */ + CHECK_ERROR(pmic_write_reg(REG_ACC1, + ACC_COULOMB_PER_LSB * ACC_ONEC_VALUE, BITFMASK(ACC1_ONEC))); + + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_START_COUNTER, ACC_CONTROL_BIT_MASK)); + coulomb_counter_start_time_msecs = jiffies_to_msecs(jiffies); + pr_debug("coulomb counter start time %u\n", + coulomb_counter_start_time_msecs); + return 0; +} + +static int pmic_stop_coulomb_counter(void) +{ + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_STOP_COUNTER, ACC_CONTROL_BIT_MASK)); + return 0; +} + +static int pmic_calibrate_coulomb_counter(void) +{ + int ret; + unsigned int value; + + /* set scaler */ + CHECK_ERROR(pmic_write_reg(REG_ACC1, + 0x1, BITFMASK(ACC1_ONEC))); + + CHECK_ERROR(pmic_write_reg( + REG_ACC0, ACC_CALIBRATION, ACC_CONTROL_BIT_MASK)); + msleep(ACC_CALIBRATION_DURATION_MSECS); + + ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); + if (ret != 0) + return -1; + value = BITFEXT(value, ACC_CCOUT); + pr_debug("calibrate value = %x\n", value); + coulomb_counter_calibration = (int)((s16)((u16) value)); + pr_debug("coulomb_counter_calibration = %d\n", + coulomb_counter_calibration); + + return 0; + +} + +static int pmic_get_charger_coulomb(int *coulomb) +{ + int ret; + unsigned int value; + int calibration; + unsigned int time_diff_msec; + + ret = pmic_read_reg(REG_ACC0, &value, BITFMASK(ACC_CCOUT)); + if (ret != 0) + return -1; + value = BITFEXT(value, ACC_CCOUT); + pr_debug("counter value = %x\n", value); + *coulomb = ((s16)((u16)value)) * ACC_COULOMB_PER_LSB; + + if (abs(*coulomb) >= ACC_COULOMB_PER_LSB) { + /* calibrate */ + time_diff_msec = jiffies_to_msecs(jiffies); + time_diff_msec = + (time_diff_msec > coulomb_counter_start_time_msecs) ? + (time_diff_msec - coulomb_counter_start_time_msecs) : + (0xffffffff - coulomb_counter_start_time_msecs + + time_diff_msec); + calibration = coulomb_counter_calibration * (int)time_diff_msec + / (ACC_ONEC_VALUE * ACC_CALIBRATION_DURATION_MSECS); + *coulomb -= calibration; + } + + return 0; +} + +static int pmic_restart_charging(void) +{ + pmic_set_chg_misc(BAT_TH_CHECK_DIS, 1); + pmic_set_chg_misc(AUTO_CHG_DIS, 0); + pmic_set_chg_misc(VI_PROGRAM_EN, 1); + pmic_set_chg_current(0x8); + pmic_set_chg_misc(RESTART_CHG_STAT, 1); + return 0; +} + +struct mc13892_dev_info { + struct device *dev; + + unsigned short voltage_raw; + int voltage_uV; + unsigned short current_raw; + int current_uA; + int battery_status; + int full_counter; + int charger_online; + int charger_voltage_uV; + int accum_current_uAh; + + struct power_supply bat; + struct power_supply charger; + + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; +}; + +#define mc13892_SENSER 25 +#define to_mc13892_dev_info(x) container_of((x), struct mc13892_dev_info, \ + bat); + +static enum power_supply_property mc13892_battery_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_STATUS, +}; + +static enum power_supply_property mc13892_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int mc13892_charger_update_status(struct mc13892_dev_info *di) +{ + int ret; + unsigned int value; + int online; + + ret = pmic_read_reg(REG_INT_SENSE0, &value, BITFMASK(BIT_CHG_DETS)); + + if (ret == 0) { + online = BITFEXT(value, BIT_CHG_DETS); + if (online != di->charger_online) { + di->charger_online = online; + dev_info(di->charger.dev, "charger status: %s\n", + online ? "online" : "offline"); + power_supply_changed(&di->charger); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, + &di->monitor_work, HZ / 10); + if (online) { + pmic_start_coulomb_counter(); + pmic_restart_charging(); + } else + pmic_stop_coulomb_counter(); + } + } + + return ret; +} + +static int mc13892_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mc13892_dev_info *di = + container_of((psy), struct mc13892_dev_info, charger); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->charger_online; + return 0; + default: + break; + } + return -EINVAL; +} + +static int mc13892_battery_read_status(struct mc13892_dev_info *di) +{ + int retval; + int coulomb; + retval = pmic_get_batt_voltage(&(di->voltage_raw)); + if (retval == 0) + di->voltage_uV = di->voltage_raw * BAT_VOLTAGE_UNIT_UV; + + retval = pmic_get_batt_current(&(di->current_raw)); + if (retval == 0) { + if (di->current_raw & 0x200) + di->current_uA = + (0x1FF - (di->current_raw & 0x1FF)) * + BAT_CURRENT_UNIT_UA * (-1); + else + di->current_uA = + (di->current_raw & 0x1FF) * BAT_CURRENT_UNIT_UA; + } + retval = pmic_get_charger_coulomb(&coulomb); + if (retval == 0) + di->accum_current_uAh = COULOMB_TO_UAH(coulomb); + + return retval; +} + +static void mc13892_battery_update_status(struct mc13892_dev_info *di) +{ + unsigned int value; + int retval; + int old_battery_status = di->battery_status; + + if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) + di->full_counter = 0; + + if (di->charger_online) { + retval = pmic_read_reg(REG_INT_SENSE0, + &value, BITFMASK(BIT_CHG_CURRS)); + + if (retval == 0) { + value = BITFEXT(value, BIT_CHG_CURRS); + if (value) + di->battery_status = + POWER_SUPPLY_STATUS_CHARGING; + else + di->battery_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + if (di->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING) + di->full_counter++; + else + di->full_counter = 0; + } else { + di->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + di->full_counter = 0; + } + + dev_dbg(di->bat.dev, "bat status: %d\n", + di->battery_status); + + if (di->battery_status != old_battery_status) + power_supply_changed(&di->bat); +} + +static void mc13892_battery_work(struct work_struct *work) +{ + struct mc13892_dev_info *di = container_of(work, + struct mc13892_dev_info, + monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->dev, "%s\n", __func__); + + mc13892_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); +} + +static void charger_online_event_callback(void *para) +{ + struct mc13892_dev_info *di = (struct mc13892_dev_info *) para; + pr_info("\n\n DETECTED charger plug/unplug event\n"); + mc13892_charger_update_status(di); +} + + +static int mc13892_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mc13892_dev_info *di = to_mc13892_dev_info(psy); + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (di->battery_status == POWER_SUPPLY_STATUS_UNKNOWN) { + mc13892_charger_update_status(di); + mc13892_battery_update_status(di); + } + val->intval = di->battery_status; + return 0; + default: + break; + } + + mc13892_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = 3800000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = 3300000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pmic_battery_remove(struct platform_device *pdev) +{ + pmic_event_callback_t bat_event_callback; + struct mc13892_dev_info *di = platform_get_drvdata(pdev); + + bat_event_callback.func = charger_online_event_callback; + bat_event_callback.param = (void *) di; + pmic_event_unsubscribe(EVENT_CHGDETI, bat_event_callback); + + cancel_rearming_delayed_workqueue(di->monitor_wqueue, + &di->monitor_work); + destroy_workqueue(di->monitor_wqueue); + power_supply_unregister(&di->bat); + power_supply_unregister(&di->charger); + + kfree(di); + + return 0; +} + +static int pmic_battery_probe(struct platform_device *pdev) +{ + int retval = 0; + struct mc13892_dev_info *di; + pmic_event_callback_t bat_event_callback; + pmic_version_t pmic_version; + + /* Only apply battery driver for MC13892 V2.0 due to ENGR108085 */ + pmic_version = pmic_get_version(); + if (pmic_version.revision < 20) { + pr_debug("Battery driver is only applied for MC13892 V2.0\n"); + return -1; + } + if (machine_is_mx51_babbage()) { + pr_debug("mc13892 charger is not used for this platform\n"); + return -1; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + di->dev = &pdev->dev; + di->bat.name = "mc13892_bat"; + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = mc13892_battery_props; + di->bat.num_properties = ARRAY_SIZE(mc13892_battery_props); + di->bat.get_property = mc13892_battery_get_property; + di->bat.use_for_apm = 1; + + di->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(di->dev, "failed to register battery\n"); + goto batt_failed; + } + di->charger.name = "mc13892_charger"; + di->charger.type = POWER_SUPPLY_TYPE_MAINS; + di->charger.properties = mc13892_charger_props; + di->charger.num_properties = ARRAY_SIZE(mc13892_charger_props); + di->charger.get_property = mc13892_charger_get_property; + retval = power_supply_register(&pdev->dev, &di->charger); + if (retval) { + dev_err(di->dev, "failed to register charger\n"); + goto charger_failed; + } + INIT_DELAYED_WORK(&di->monitor_work, mc13892_battery_work); + di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 10); + + bat_event_callback.func = charger_online_event_callback; + bat_event_callback.param = (void *) di; + pmic_event_subscribe(EVENT_CHGDETI, bat_event_callback); + + pmic_stop_coulomb_counter(); + pmic_calibrate_coulomb_counter(); + goto success; + +workqueue_failed: + power_supply_unregister(&di->charger); +charger_failed: + power_supply_unregister(&di->bat); +batt_failed: + kfree(di); +di_alloc_failed: +success: + dev_dbg(di->dev, "%s battery probed!\n", __func__); + return retval; + + + return 0; +} + +static struct platform_driver pmic_battery_driver_ldm = { + .driver = { + .name = "pmic_battery", + .bus = &platform_bus_type, + }, + .probe = pmic_battery_probe, + .remove = pmic_battery_remove, +}; + +static int __init pmic_battery_init(void) +{ + pr_debug("PMIC Battery driver loading...\n"); + return platform_driver_register(&pmic_battery_driver_ldm); +} + +static void __exit pmic_battery_exit(void) +{ + platform_driver_unregister(&pmic_battery_driver_ldm); + pr_debug("PMIC Battery driver successfully unloaded\n"); +} + +module_init(pmic_battery_init); +module_exit(pmic_battery_exit); + +MODULE_DESCRIPTION("pmic_battery driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc13892/pmic_light.c b/drivers/mxc/pmic/mc13892/pmic_light.c new file mode 100644 index 000000000000..ae02430a1981 --- /dev/null +++ b/drivers/mxc/pmic/mc13892/pmic_light.c @@ -0,0 +1,685 @@ +/* + * Copyright 2004-2009 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 mc13892/pmic_light.c + * @brief This is the main file of PMIC(mc13783) Light and Backlight driver. + * + * @ingroup PMIC_LIGHT + */ + +/* + * Includes + */ +#define DEBUG +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/pmic_light.h> +#include <linux/pmic_status.h> + +#define BIT_CL_MAIN_LSH 9 +#define BIT_CL_AUX_LSH 21 +#define BIT_CL_KEY_LSH 9 +#define BIT_CL_RED_LSH 9 +#define BIT_CL_GREEN_LSH 21 +#define BIT_CL_BLUE_LSH 9 + +#define BIT_CL_MAIN_WID 3 +#define BIT_CL_AUX_WID 3 +#define BIT_CL_KEY_WID 3 +#define BIT_CL_RED_WID 3 +#define BIT_CL_GREEN_WID 3 +#define BIT_CL_BLUE_WID 3 + +#define BIT_DC_MAIN_LSH 3 +#define BIT_DC_AUX_LSH 15 +#define BIT_DC_KEY_LSH 3 +#define BIT_DC_RED_LSH 3 +#define BIT_DC_GREEN_LSH 15 +#define BIT_DC_BLUE_LSH 3 + +#define BIT_DC_MAIN_WID 6 +#define BIT_DC_AUX_WID 6 +#define BIT_DC_KEY_WID 6 +#define BIT_DC_RED_WID 6 +#define BIT_DC_GREEN_WID 6 +#define BIT_DC_BLUE_WID 6 + +#define BIT_RP_MAIN_LSH 2 +#define BIT_RP_AUX_LSH 14 +#define BIT_RP_KEY_LSH 2 +#define BIT_RP_RED_LSH 2 +#define BIT_RP_GREEN_LSH 14 +#define BIT_RP_BLUE_LSH 2 + +#define BIT_RP_MAIN_WID 1 +#define BIT_RP_AUX_WID 1 +#define BIT_RP_KEY_WID 1 +#define BIT_RP_RED_WID 1 +#define BIT_RP_GREEN_WID 1 +#define BIT_RP_BLUE_WID 1 + +#define BIT_HC_MAIN_LSH 1 +#define BIT_HC_AUX_LSH 13 +#define BIT_HC_KEY_LSH 1 + +#define BIT_HC_MAIN_WID 1 +#define BIT_HC_AUX_WID 1 +#define BIT_HC_KEY_WID 1 + +#define BIT_BP_RED_LSH 0 +#define BIT_BP_GREEN_LSH 12 +#define BIT_BP_BLUE_LSH 0 + +#define BIT_BP_RED_WID 2 +#define BIT_BP_GREEN_WID 2 +#define BIT_BP_BLUE_WID 2 + +int pmic_light_init_reg(void) +{ + CHECK_ERROR(pmic_write_reg(REG_LED_CTL0, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL1, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL2, 0, PMIC_ALL_BITS)); + CHECK_ERROR(pmic_write_reg(REG_LED_CTL3, 0, PMIC_ALL_BITS)); + + return 0; +} + +static int pmic_light_suspend(struct platform_device *dev, pm_message_t state) +{ + return 0; +}; + +static int pmic_light_resume(struct platform_device *pdev) +{ + return 0; +}; + +PMIC_STATUS mc13892_bklit_set_hi_current(enum lit_channel channel, int mode) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_HC_MAIN, mode); + mask = BITFMASK(BIT_HC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_HC_AUX, mode); + mask = BITFMASK(BIT_HC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_HC_KEY, mode); + mask = BITFMASK(BIT_HC_KEY); + reg = REG_LED_CTL1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_hi_current(enum lit_channel channel, int *mode) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_HC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_HC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_HC_KEY); + reg = REG_LED_CTL1; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, mode, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_current(enum lit_channel channel, + unsigned char level) +{ + unsigned int mask; + unsigned int value; + int reg; + + if (level > LIT_CURR_HI_42) + return PMIC_PARAMETER_ERROR; + else if (level >= LIT_CURR_HI_0) { + CHECK_ERROR(mc13892_bklit_set_hi_current(channel, 1)); + level -= LIT_CURR_HI_0; + } + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_CL_MAIN, level); + mask = BITFMASK(BIT_CL_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_CL_AUX, level); + mask = BITFMASK(BIT_CL_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_CL_KEY, level); + mask = BITFMASK(BIT_CL_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_CL_RED, level); + mask = BITFMASK(BIT_CL_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_CL_GREEN, level); + mask = BITFMASK(BIT_CL_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_CL_BLUE, level); + mask = BITFMASK(BIT_CL_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_current(enum lit_channel channel, + unsigned char *level) +{ + unsigned int reg_value = 0; + unsigned int mask = 0; + int reg, mode; + + CHECK_ERROR(mc13892_bklit_get_hi_current(channel, &mode)); + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_CL_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_CL_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_CL_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_CL_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_CL_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_CL_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_value, mask)); + + switch (channel) { + case LIT_MAIN: + *level = BITFEXT(reg_value, BIT_CL_MAIN); + break; + case LIT_AUX: + *level = BITFEXT(reg_value, BIT_CL_AUX); + break; + case LIT_KEY: + *level = BITFEXT(reg_value, BIT_CL_KEY); + break; + case LIT_RED: + *level = BITFEXT(reg_value, BIT_CL_RED); + break; + case LIT_GREEN: + *level = BITFEXT(reg_value, BIT_CL_GREEN); + break; + case LIT_BLUE: + *level = BITFEXT(reg_value, BIT_CL_BLUE); + break; + default: + return PMIC_PARAMETER_ERROR; + } + + if (mode == 1) + *level += LIT_CURR_HI_0; + + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_dutycycle(enum lit_channel channel, + unsigned char dc) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_DC_MAIN, dc); + mask = BITFMASK(BIT_DC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_DC_AUX, dc); + mask = BITFMASK(BIT_DC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_DC_KEY, dc); + mask = BITFMASK(BIT_DC_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_DC_RED, dc); + mask = BITFMASK(BIT_DC_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_DC_GREEN, dc); + mask = BITFMASK(BIT_DC_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_DC_BLUE, dc); + mask = BITFMASK(BIT_DC_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_dutycycle(enum lit_channel channel, + unsigned char *dc) +{ + unsigned int mask; + int reg; + unsigned int reg_value = 0; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_DC_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_DC_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_DC_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_DC_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_DC_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_DC_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, ®_value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_ramp(enum lit_channel channel, int flag) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_MAIN: + value = BITFVAL(BIT_RP_MAIN, flag); + mask = BITFMASK(BIT_RP_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + value = BITFVAL(BIT_RP_AUX, flag); + mask = BITFMASK(BIT_RP_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + value = BITFVAL(BIT_RP_KEY, flag); + mask = BITFMASK(BIT_RP_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + value = BITFVAL(BIT_RP_RED, flag); + mask = BITFMASK(BIT_RP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_RP_GREEN, flag); + mask = BITFMASK(BIT_RP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_RP_BLUE, flag); + mask = BITFMASK(BIT_RP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_ramp(enum lit_channel channel, int *flag) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_MAIN: + mask = BITFMASK(BIT_RP_MAIN); + reg = REG_LED_CTL0; + break; + case LIT_AUX: + mask = BITFMASK(BIT_RP_AUX); + reg = REG_LED_CTL0; + break; + case LIT_KEY: + mask = BITFMASK(BIT_RP_KEY); + reg = REG_LED_CTL1; + break; + case LIT_RED: + mask = BITFMASK(BIT_RP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_RP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_RP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, flag, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_set_blink_p(enum lit_channel channel, int period) +{ + unsigned int mask; + unsigned int value; + int reg; + + switch (channel) { + case LIT_RED: + value = BITFVAL(BIT_BP_RED, period); + mask = BITFMASK(BIT_BP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + value = BITFVAL(BIT_BP_GREEN, period); + mask = BITFMASK(BIT_BP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + value = BITFVAL(BIT_BP_BLUE, period); + mask = BITFMASK(BIT_BP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + CHECK_ERROR(pmic_write_reg(reg, value, mask)); + return PMIC_SUCCESS; +} + +PMIC_STATUS mc13892_bklit_get_blink_p(enum lit_channel channel, int *period) +{ + unsigned int mask; + int reg; + + switch (channel) { + case LIT_RED: + mask = BITFMASK(BIT_BP_RED); + reg = REG_LED_CTL2; + break; + case LIT_GREEN: + mask = BITFMASK(BIT_BP_GREEN); + reg = REG_LED_CTL2; + break; + case LIT_BLUE: + mask = BITFMASK(BIT_BP_BLUE); + reg = REG_LED_CTL3; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_read_reg(reg, period, mask)); + return PMIC_SUCCESS; +} + +EXPORT_SYMBOL(mc13892_bklit_set_current); +EXPORT_SYMBOL(mc13892_bklit_get_current); +EXPORT_SYMBOL(mc13892_bklit_set_dutycycle); +EXPORT_SYMBOL(mc13892_bklit_get_dutycycle); +EXPORT_SYMBOL(mc13892_bklit_set_ramp); +EXPORT_SYMBOL(mc13892_bklit_get_ramp); +EXPORT_SYMBOL(mc13892_bklit_set_blink_p); +EXPORT_SYMBOL(mc13892_bklit_get_blink_p); + +static int pmic_light_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef DEBUG +static ssize_t lit_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +enum { + SET_CURR = 0, + SET_DC, + SET_RAMP, + SET_BP, + SET_CH, + LIT_CMD_MAX +}; + +static const char *const lit_cmd[LIT_CMD_MAX] = { + [SET_CURR] = "cur", + [SET_DC] = "dc", + [SET_RAMP] = "ra", + [SET_BP] = "bp", + [SET_CH] = "ch" +}; + +static int cmd(unsigned int index, int value) +{ + static int ch = LIT_MAIN; + int ret = 0; + + switch (index) { + case SET_CH: + ch = value; + break; + case SET_CURR: + pr_debug("set %d cur %d\n", ch, value); + ret = mc13892_bklit_set_current(ch, value); + break; + case SET_DC: + pr_debug("set %d dc %d\n", ch, value); + ret = mc13892_bklit_set_dutycycle(ch, value); + break; + case SET_RAMP: + pr_debug("set %d ramp %d\n", ch, value); + ret = mc13892_bklit_set_ramp(ch, value); + break; + case SET_BP: + pr_debug("set %d bp %d\n", ch, value); + ret = mc13892_bklit_set_blink_p(ch, value); + break; + default: + pr_debug("error command\n"); + break; + } + + if (ret == PMIC_SUCCESS) + pr_debug("command exec successfully!\n"); + + return 0; +} + +static ssize_t lit_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int state = 0; + const char *const *s; + char *p, *q; + int error; + int len, value = 0; + + pr_debug("lit_ctl\n"); + + q = NULL; + q = memchr(buf, ' ', count); + + if (q != NULL) { + len = q - buf; + q += 1; + value = simple_strtoul(q, NULL, 10); + } else { + p = memchr(buf, '\n', count); + len = p ? p - buf : count; + } + + for (s = &lit_cmd[state]; state < LIT_CMD_MAX; s++, state++) { + if (*s && !strncmp(buf, *s, len)) + break; + } + if (state < LIT_CMD_MAX && *s) + error = cmd(state, value); + else + error = -EINVAL; + + return count; +} + +#else +static ssize_t lit_info(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return 0; +} + +static ssize_t lit_ctl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return count; +} + +#endif + +static DEVICE_ATTR(lit, 0644, lit_info, lit_ctl); + +static int pmic_light_probe(struct platform_device *pdev) +{ + int ret = 0; + + pr_debug("PMIC ADC start probe\n"); + ret = device_create_file(&(pdev->dev), &dev_attr_lit); + if (ret) { + pr_debug("Can't create device file!\n"); + return -ENODEV; + } + + pmic_light_init_reg(); + + pr_debug("PMIC Light successfully loaded\n"); + return 0; +} + +static struct platform_driver pmic_light_driver_ldm = { + .driver = { + .name = "pmic_light", + }, + .suspend = pmic_light_suspend, + .resume = pmic_light_resume, + .probe = pmic_light_probe, + .remove = pmic_light_remove, +}; + +/* + * Initialization and Exit + */ + +static int __init pmic_light_init(void) +{ + pr_debug("PMIC Light driver loading...\n"); + return platform_driver_register(&pmic_light_driver_ldm); +} +static void __exit pmic_light_exit(void) +{ + platform_driver_unregister(&pmic_light_driver_ldm); + pr_debug("PMIC Light driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_light_init); +module_exit(pmic_light_exit); + +MODULE_DESCRIPTION("PMIC_LIGHT"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/security/Kconfig b/drivers/mxc/security/Kconfig new file mode 100644 index 000000000000..83d856d85dc0 --- /dev/null +++ b/drivers/mxc/security/Kconfig @@ -0,0 +1,64 @@ +menu "MXC Security Drivers" + +config MXC_SECURITY_SCC + tristate "MXC SCC Driver" + default n + ---help--- + This module contains the core API's for accessing the SCC module. + If you are unsure about this, say N here. + +config MXC_SECURITY_SCC2 + tristate "MXC SCC2 Driver" + depends on ARCH_MX37 || ARCH_MX51 + default n + ---help--- + This module contains the core API's for accessing the SCC2 module. + If you are unsure about this, say N here. + +config SCC_DEBUG + bool "MXC SCC Module debugging" + depends on MXC_SECURITY_SCC || MXC_SECURITY_SCC2 + ---help--- + This is an option for use by developers; most people should + say N here. This enables SCC module debugging. + +config MXC_SECURITY_RNG + tristate "MXC RNG Driver" + depends on ARCH_MXC + depends on !ARCH_MXC91321 + depends on !ARCH_MX27 + default n + select MXC_SECURITY_CORE + ---help--- + This module contains the core API's for accessing the RNG module. + If you are unsure about this, say N here. + +config MXC_RNG_TEST_DRIVER + bool "MXC RNG debug register" + depends on MXC_SECURITY_RNG + default n + ---help--- + This option enables the RNG kcore driver to provide peek-poke facility + into the RNG device registers. Enable this, only for development and + testing purposes. +config MXC_RNG_DEBUG + bool "MXC RNG Module Dubugging" + depends on MXC_SECURITY_RNG + default n + ---help--- + This is an option for use by developers; most people should + say N here. This enables RNG module debugging. + +config MXC_DRYICE + tristate "MXC DryIce Driver" + depends on ARCH_MX25 + default n + ---help--- + This module contains the core API's for accessing the DryIce module. + If you are unsure about this, say N here. + +if (ARCH_MX37 || ARCH_MX51 || ARCH_MX27) +source "drivers/mxc/security/sahara2/Kconfig" +endif + +endmenu diff --git a/drivers/mxc/security/Makefile b/drivers/mxc/security/Makefile new file mode 100644 index 000000000000..f643a09a423d --- /dev/null +++ b/drivers/mxc/security/Makefile @@ -0,0 +1,11 @@ +# Makefile for the Linux MXC Security API +ifeq ($( SCC_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif +EXTRA_CFLAGS += -DMXC -DLINUX_KERNEL + +obj-$(CONFIG_MXC_SECURITY_SCC2) += scc2_driver.o +obj-$(CONFIG_MXC_SECURITY_SCC) += mxc_scc.o +obj-$(CONFIG_MXC_SECURITY_RNG) += rng/ +obj-$(CONFIG_MXC_SAHARA) += sahara2/ +obj-$(CONFIG_MXC_DRYICE) += dryice.o diff --git a/drivers/mxc/security/dryice-regs.h b/drivers/mxc/security/dryice-regs.h new file mode 100644 index 000000000000..b8d3858bdb7d --- /dev/null +++ b/drivers/mxc/security/dryice-regs.h @@ -0,0 +1,207 @@ +/* + * Copyright 2009 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 + */ + + +#ifndef __DRYICE_REGS_H__ +#define __DRYICE_REGS_H__ + +/*********************************************************************** + * DryIce Register Definitions + ***********************************************************************/ + +/* DryIce Time Counter MSB Reg */ +#define DTCMR 0x00 + +/* DryIce Time Counter LSB Reg */ +#define DTCLR 0x04 + +/* DryIce Clock Alarm MSB Reg */ +#define DCAMR 0x08 + +/* DryIce Clock Alarm LSB Reg */ +#define DCALR 0x0c + +/* DryIce Control Reg */ +#define DCR 0x10 +#define DCR_TDCHL (1 << 30) /* Tamper Detect Config Hard Lock */ +#define DCR_TDCSL (1 << 29) /* Tamper Detect COnfig Soft Lock */ +#define DCR_KSHL (1 << 28) /* Key Select Hard Lock */ +#define DCR_KSSL (1 << 27) /* Key Select Soft Lock */ +#define DCR_RKHL (1 << 26) /* Random Key Hard Lock */ +#define DCR_RKSL (1 << 25) /* Random Key Soft Lock */ +#define DCR_PKRHL (1 << 24) /* Programmed Key Read Hard Lock */ +#define DCR_PKRSL (1 << 23) /* Programmed Key Read Soft Lock */ +#define DCR_PKWHL (1 << 22) /* Programmed Key Write Hard Lock */ +#define DCR_PKWSL (1 << 21) /* Programmed Key Write Soft Lock */ +#define DCR_MCHL (1 << 20) /* Monotonic Counter Hard Lock */ +#define DCR_MCSL (1 << 19) /* Monotonic Counter Soft Lock */ +#define DCR_TCHL (1 << 18) /* Time Counter Hard Lock */ +#define DCR_TCSL (1 << 17) /* Time Counter Soft Lock */ +#define DCR_FSHL (1 << 16) /* Failure State Hard Lock */ +#define DCR_NSA (1 << 15) /* Non-Secure Access */ +#define DCR_OSCB (1 << 14) /* Oscillator Bypass */ +#define DCR_APE (1 << 4) /* Alarm Pin Enable */ +#define DCR_TCE (1 << 3) /* Time Counter Enable */ +#define DCR_MCE (1 << 2) /* Monotonic Counter Enable */ +#define DCR_SWR (1 << 0) /* Software Reset (w/o) */ + +/* DryIce Status Reg */ +#define DSR 0x14 +#define DSR_WTD (1 << 23) /* Wire-mesh Tampering Detected */ +#define DSR_ETBD (1 << 22) /* External Tampering B Detected */ +#define DSR_ETAD (1 << 21) /* External Tampering A Detected */ +#define DSR_EBD (1 << 20) /* External Boot Detected */ +#define DSR_SAD (1 << 19) /* Security Alarm Detected */ +#define DSR_TTD (1 << 18) /* Temperature Tampering Detected */ +#define DSR_CTD (1 << 17) /* Clock Tampering Detected */ +#define DSR_VTD (1 << 16) /* Voltage Tampering Detected */ +#define DSR_KBF (1 << 11) /* Key Busy Flag */ +#define DSR_WBF (1 << 10) /* Write Busy Flag */ +#define DSR_WNF (1 << 9) /* Write Next Flag */ +#define DSR_WCF (1 << 8) /* Write Complete Flag */ +#define DSR_WEF (1 << 7) /* Write Error Flag */ +#define DSR_RKE (1 << 6) /* Random Key Error */ +#define DSR_RKV (1 << 5) /* Random Key Valid */ +#define DSR_CAF (1 << 4) /* Clock Alarm Flag */ +#define DSR_MCO (1 << 3) /* Monotonic Counter Overflow */ +#define DSR_TCO (1 << 2) /* Time Counter Overflow */ +#define DSR_NVF (1 << 1) /* Non-Valid Flag */ +#define DSR_SVF (1 << 0) /* Security Violation Flag */ + +#define DSR_TAMPER_BITS (DSR_WTD | DSR_ETBD | DSR_ETAD | DSR_EBD | DSR_SAD | \ + DSR_TTD | DSR_CTD | DSR_VTD | DSR_MCO | DSR_TCO) + +/* ensure that external tamper defs match register bits */ +#if DSR_WTD != DI_TAMPER_EVENT_WTD +#error "Mismatch between DSR_WTD and DI_TAMPER_EVENT_WTD" +#endif +#if DSR_ETBD != DI_TAMPER_EVENT_ETBD +#error "Mismatch between DSR_ETBD and DI_TAMPER_EVENT_ETBD" +#endif +#if DSR_ETAD != DI_TAMPER_EVENT_ETAD +#error "Mismatch between DSR_ETAD and DI_TAMPER_EVENT_ETAD" +#endif +#if DSR_EBD != DI_TAMPER_EVENT_EBD +#error "Mismatch between DSR_EBD and DI_TAMPER_EVENT_EBD" +#endif +#if DSR_SAD != DI_TAMPER_EVENT_SAD +#error "Mismatch between DSR_SAD and DI_TAMPER_EVENT_SAD" +#endif +#if DSR_TTD != DI_TAMPER_EVENT_TTD +#error "Mismatch between DSR_TTD and DI_TAMPER_EVENT_TTD" +#endif +#if DSR_CTD != DI_TAMPER_EVENT_CTD +#error "Mismatch between DSR_CTD and DI_TAMPER_EVENT_CTD" +#endif +#if DSR_VTD != DI_TAMPER_EVENT_VTD +#error "Mismatch between DSR_VTD and DI_TAMPER_EVENT_VTD" +#endif +#if DSR_MCO != DI_TAMPER_EVENT_MCO +#error "Mismatch between DSR_MCO and DI_TAMPER_EVENT_MCO" +#endif +#if DSR_TCO != DI_TAMPER_EVENT_TCO +#error "Mismatch between DSR_TCO and DI_TAMPER_EVENT_TCO" +#endif + +/* DryIce Interrupt Enable Reg */ +#define DIER 0x18 +#define DIER_WNIE (1 << 9) /* Write Next Interrupt Enable */ +#define DIER_WCIE (1 << 8) /* Write Complete Interrupt Enable */ +#define DIER_WEIE (1 << 7) /* Write Error Interrupt Enable */ +#define DIER_RKIE (1 << 5) /* Random Key Interrupt Enable */ +#define DIER_CAIE (1 << 4) /* Clock Alarm Interrupt Enable */ +#define DIER_MOIE (1 << 3) /* Monotonic Overflow Interrupt En */ +#define DIER_TOIE (1 << 2) /* Time Overflow Interrupt Enable */ +#define DIER_SVIE (1 << 0) /* Security Violation Interrupt En */ + +/* DryIce Monotonic Counter Reg */ +#define DMCR 0x1c + +/* DryIce Key Select Reg */ +#define DKSR 0x20 +#define DKSR_IIM_KEY 0x0 +#define DKSR_PROG_KEY 0x4 +#define DKSR_RAND_KEY 0x5 +#define DKSR_PROG_XOR_IIM_KEY 0x6 +#define DKSR_RAND_XOR_IIM_KEY 0x7 + +/* DryIce Key Control Reg */ +#define DKCR 0x24 +#define DKCR_LRK (1 << 0) /* Load Random Key */ + +/* DryIce Tamper Configuration Reg */ +#define DTCR 0x28 +#define DTCR_ETGFB_SHIFT 27 /* Ext Tamper Glitch Filter B */ +#define DTCR_ETGFB_MASK 0xf8000000 +#define DTCR_ETGFA_SHIFT 22 /* Ext Tamper Glitch Filter A */ +#define DTCR_ETGFA_MASK 0x07c00000 +#define DTCR_WTGF_SHIFT 17 /* Wire-mesh Tamper Glitch Filter */ +#define DTCR_WTGF_MASK 0x003e0000 +#define DTCR_WGFE (1 << 16) /* Wire-mesh Glitch Filter Enable */ +#define DTCR_SAOE (1 << 15) /* Security Alarm Output Enable */ +#define DTCR_MOE (1 << 9) /* Monotonic Overflow Enable */ +#define DTCR_TOE (1 << 8) /* Time Overflow Enable */ +#define DTCR_WTE (1 << 7) /* Wire-mesh Tampering Enable */ +#define DTCR_ETBE (1 << 6) /* External Tampering B Enable */ +#define DTCR_ETAE (1 << 5) /* External Tampering A Enable */ +#define DTCR_EBE (1 << 4) /* External Boot Enable */ +#define DTCR_SAIE (1 << 3) /* Security Alarm Input Enable */ +#define DTCR_TTE (1 << 2) /* Temperature Tamper Enable */ +#define DTCR_CTE (1 << 1) /* Clock Tamper Enable */ +#define DTCR_VTE (1 << 0) /* Voltage Tamper Enable */ + +/* DryIce Analog Configuration Reg */ +#define DACR 0x2c +#define DACR_VRC_SHIFT 6 /* Voltage Reference Configuration */ +#define DACR_VRC_MASK 0x000001c0 +#define DACR_HTDC_SHIFT 3 /* High Temperature Detect Configuration */ +#define DACR_HTDC_MASK 0x00000038 +#define DACR_LTDC_SHIFT 0 /* Low Temperature Detect Configuration */ +#define DACR_LTDC_MASK 0x00000007 + +/* DryIce General Purpose Reg */ +#define DGPR 0x3c + +/* DryIce Programmed Key0-7 Regs */ +#define DPKR0 0x40 +#define DPKR1 0x44 +#define DPKR2 0x48 +#define DPKR3 0x4c +#define DPKR4 0x50 +#define DPKR5 0x54 +#define DPKR6 0x58 +#define DPKR7 0x5c + +/* DryIce Random Key0-7 Regs */ +#define DRKR0 0x60 +#define DRKR1 0x64 +#define DRKR2 0x68 +#define DRKR3 0x6c +#define DRKR4 0x70 +#define DRKR5 0x74 +#define DRKR6 0x78 +#define DRKR7 0x7c + +#define DI_ADDRESS_RANGE (DRKR7 + 4) + +/* + * this doesn't really belong here but the + * portability layer doesn't include it + */ +#ifdef LINUX_KERNEL +#define EXTERN_SYMBOL(symbol) EXPORT_SYMBOL(symbol) +#else +#define EXTERN_SYMBOL(symbol) do {} while (0) +#endif + +#endif /* __DRYICE_REGS_H__ */ diff --git a/drivers/mxc/security/dryice.c b/drivers/mxc/security/dryice.c new file mode 100644 index 000000000000..f7cefb6ae599 --- /dev/null +++ b/drivers/mxc/security/dryice.c @@ -0,0 +1,1123 @@ +/* + * Copyright 2009 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 + */ + + +#undef DI_DEBUG /* enable debug messages */ +#undef DI_DEBUG_REGIO /* show register read/write */ +#undef DI_TESTING /* include test code */ + +#ifdef DI_DEBUG +#define di_debug(fmt, arg...) os_printk(KERN_INFO fmt, ##arg) +#else +#define di_debug(fmt, arg...) do {} while (0) +#endif + +#define di_info(fmt, arg...) os_printk(KERN_INFO fmt, ##arg) +#define di_warn(fmt, arg...) os_printk(KERN_WARNING fmt, ##arg) + +#include "sahara2/include/portable_os.h" +#include "dryice.h" +#include "dryice-regs.h" + +/* mask of the lock-related function flags */ +#define DI_FUNC_LOCK_FLAGS (DI_FUNC_FLAG_READ_LOCK | \ + DI_FUNC_FLAG_WRITE_LOCK | \ + DI_FUNC_FLAG_HARD_LOCK) + +/* + * dryice hardware states + */ +enum di_states { + DI_STATE_VALID = 0, + DI_STATE_NON_VALID, + DI_STATE_FAILURE, +}; + +/* + * todo list actions + */ +enum todo_actions { + TODO_ACT_WRITE_VAL, + TODO_ACT_WRITE_PTR, + TODO_ACT_WRITE_PTR32, + TODO_ACT_ASSIGN, + TODO_ACT_WAIT_RKG, +}; + +/* + * todo list status + */ +enum todo_status { + TODO_ST_LOADING, + TODO_ST_READY, + TODO_ST_PEND_WCF, + TODO_ST_PEND_RKG, + TODO_ST_DONE, +}; + +OS_DEV_INIT_DCL(dryice_init) +OS_DEV_SHUTDOWN_DCL(dryice_exit) +OS_DEV_ISR_DCL(dryice_norm_irq) +OS_WAIT_OBJECT(done_queue); +OS_WAIT_OBJECT(exit_queue); + +struct dryice_data { + int busy; /* enforce exclusive access */ + os_lock_t busy_lock; + int exit_flag; /* don't start new operations */ + + uint32_t baseaddr; /* physical base address */ + void *ioaddr; /* virtual base address */ + + /* interrupt handling */ + struct irq_struct { + os_interrupt_id_t irq; + int set; + } irq_norm, irq_sec; + + struct clk *clk; /* clock control */ + + int key_programmed; /* key has been programmed */ + int key_selected; /* key has been selected */ + + /* callback function and cookie */ + void (*cb_func)(di_return_t rc, unsigned long cookie); + unsigned long cb_cookie; +} *di = NULL; + +#define TODO_LIST_LEN 12 +static struct { + struct td { + enum todo_actions action; + uint32_t src; + uint32_t dst; + int num; + } list[TODO_LIST_LEN]; + int cur; /* current todo pointer */ + int num; /* number of todo's on the list */ + int async; /* non-zero if list is async */ + int status; /* current status of the list */ + di_return_t rc; /* return code generated by the list */ +} todo; + +/* + * dryice register read/write functions + */ +#ifdef DI_DEBUG_REGIO +static uint32_t di_read(int reg) +{ + uint32_t val = os_read32(di->ioaddr + (reg)); + di_info("di_read(0x%02x) = 0x%08x\n", reg, val); + + return val; +} + +static void di_write(uint32_t val, int reg) +{ + di_info("dryice_write_reg(0x%08x, 0x%02x)\n", val, reg); + os_write32(di->ioaddr + (reg), val); +} +#else +#define di_read(reg) os_read32(di->ioaddr + (reg)) +#define di_write(val, reg) os_write32(di->ioaddr + (reg), val); +#endif + +/* + * set the dryice busy flag atomically, allowing + * for case where the driver is trying to exit. + */ +static int di_busy_set(void) +{ + os_lock_context_t context; + int rc = 0; + + os_lock_save_context(di->busy_lock, context); + if (di->exit_flag || di->busy) + rc = 1; + else + di->busy = 1; + os_unlock_restore_context(di->busy_lock, context); + + return rc; +} + +/* + * clear the dryice busy flag + */ +static inline void di_busy_clear(void) +{ + /* don't acquire the lock because the race is benign */ + di->busy = 0; + + if (di->exit_flag) + os_wake_sleepers(exit_queue); +} + +/* + * return the current state of dryice + * (valid, non-valid, or failure) + */ +static enum di_states di_state(void) +{ + enum di_states state = DI_STATE_VALID; + uint32_t dsr = di_read(DSR); + + if (dsr & DSR_NVF) + state = DI_STATE_NON_VALID; + else if (dsr & DSR_SVF) + state = DI_STATE_FAILURE; + + return state; +} + +#define DI_WRITE_LOOP_CNT 0x1000 +/* + * the write-error flag is something that shouldn't get set + * during normal operation. if it's set something is terribly + * wrong. the best we can do is try to clear the bit and hope + * that dryice will recover. this situation is similar to an + * unexpected bus fault in terms of severity. + */ +static void try_to_clear_wef(void) +{ + int cnt; + + while (1) { + di_write(DSR_WEF, DSR); + for (cnt = 0; cnt < DI_WRITE_LOOP_CNT; cnt++) { + if ((di_read(DSR) & DSR_WEF) == 0) + break; + } + di_warn("WARNING: DryIce cannot clear DSR_WEF " + "(Write Error Flag)!\n"); + } +} + +/* + * write a dryice register and loop, waiting for it + * to complete. use only during driver initialization. + * returns 0 on success or 1 on write failure. + */ +static int di_write_loop(uint32_t val, int reg) +{ + int rc = 0; + int cnt; + + di_debug("FUNC: %s\n", __func__); + di_write(val, reg); + + for (cnt = 0; cnt < DI_WRITE_LOOP_CNT; cnt++) { + uint32_t dsr = di_read(DSR); + if (dsr & DSR_WEF) { + try_to_clear_wef(); + rc = 1; + } + if (dsr & DSR_WCF) + break; + } + di_debug("wait_write_loop looped %d times\n", cnt); + if (cnt == DI_WRITE_LOOP_CNT) + rc = 1; + + if (rc) + di_warn("DryIce wait_write_done: WRITE ERROR!\n"); + return rc; +} + +/* + * initialize the todo list. must be called + * before adding items to the list. + */ +static void todo_init(int async_flag) +{ + di_debug("FUNC: %s\n", __func__); + todo.cur = 0; + todo.num = 0; + todo.async = async_flag; + todo.rc = 0; + todo.status = TODO_ST_LOADING; +} + +/* + * perform the current action on the todo list + */ +#define TC todo.list[todo.cur] +void todo_cur(void) +{ + di_debug("FUNC: %s[%d]\n", __func__, todo.cur); + switch (TC.action) { + case TODO_ACT_WRITE_VAL: + di_debug(" TODO_ACT_WRITE_VAL\n"); + /* enable the write-completion interrupt */ + todo.status = TODO_ST_PEND_WCF; + di_write(di_read(DIER) | DIER_WCIE, DIER); + + di_write(TC.src, TC.dst); + break; + + case TODO_ACT_WRITE_PTR32: + di_debug(" TODO_ACT_WRITE_PTR32\n"); + /* enable the write-completion interrupt */ + todo.status = TODO_ST_PEND_WCF; + di_write(di_read(DIER) | DIER_WCIE, DIER); + + di_write(*(uint32_t *)TC.src, TC.dst); + break; + + case TODO_ACT_WRITE_PTR: + { + uint8_t *p = (uint8_t *)TC.src; + uint32_t val = 0; + int num = TC.num; + + di_debug(" TODO_ACT_WRITE_PTR\n"); + while (num--) + val = (val << 8) | *p++; + + /* enable the write-completion interrupt */ + todo.status = TODO_ST_PEND_WCF; + di_write(di_read(DIER) | DIER_WCIE, DIER); + + di_write(val, TC.dst); + } + break; + + case TODO_ACT_ASSIGN: + di_debug(" TODO_ACT_ASSIGN\n"); + switch (TC.num) { + case 1: + *(uint8_t *)TC.dst = TC.src; + break; + case 2: + *(uint16_t *)TC.dst = TC.src; + break; + case 4: + *(uint32_t *)TC.dst = TC.src; + break; + default: + di_warn("Unexpected size in TODO_ACT_ASSIGN\n"); + break; + } + break; + + case TODO_ACT_WAIT_RKG: + di_debug(" TODO_ACT_WAIT_RKG\n"); + /* enable the random-key interrupt */ + todo.status = TODO_ST_PEND_RKG; + di_write(di_read(DIER) | DIER_RKIE, DIER); + break; + + default: + di_debug(" TODO_ACT_NOOP\n"); + break; + } +} + +/* + * called when done with the todo list. + * if async, it does the callback. + * if blocking, it wakes up the caller. + */ +static void todo_done(di_return_t rc) +{ + todo.rc = rc; + todo.status = TODO_ST_DONE; + if (todo.async) { + di_busy_clear(); + if (di->cb_func) + di->cb_func(rc, di->cb_cookie); + } else + os_wake_sleepers(done_queue); +} + +/* + * performs the actions sequentially from the todo list + * until it encounters an item that isn't ready. + */ +static void todo_run(void) +{ + di_debug("FUNC: %s\n", __func__); + while (todo.status == TODO_ST_READY) { + if (todo.cur == todo.num) { + todo_done(0); + break; + } + todo_cur(); + if (todo.status != TODO_ST_READY) + break; + todo.cur++; + } +} + +/* + * kick off the todo list by making it ready + */ +static void todo_start(void) +{ + di_debug("FUNC: %s\n", __func__); + todo.status = TODO_ST_READY; + todo_run(); +} + +/* + * blocking callers sleep here until the todo list is done + */ +static int todo_wait_done(void) +{ + di_debug("FUNC: %s\n", __func__); + os_sleep(done_queue, todo.status == TODO_ST_DONE, 0); + + return todo.rc; +} + +/* + * add a dryice register write to the todo list. + * the value to be written is supplied. + */ +#define todo_write_val(val, reg) \ + todo_add(TODO_ACT_WRITE_VAL, val, reg, 0) + +/* + * add a dryice register write to the todo list. + * "size" bytes pointed to by addr will be written. + */ +#define todo_write_ptr(addr, reg, size) \ + todo_add(TODO_ACT_WRITE_PTR, (uint32_t)addr, reg, size) + +/* + * add a dryice register write to the todo list. + * the word pointed to by addr will be written. + */ +#define todo_write_ptr32(addr, reg) \ + todo_add(TODO_ACT_WRITE_PTR32, (uint32_t)addr, reg, 0) + +/* + * add a dryice memory write to the todo list. + * object can only have a size of 1, 2, or 4 bytes. + */ +#define todo_assign(var, val) \ + todo_add(TODO_ACT_ASSIGN, val, (uint32_t)&(var), sizeof(var)) + +#define todo_wait_rkg() \ + todo_add(TODO_ACT_WAIT_RKG, 0, 0, 0) + +static void todo_add(int action, uint32_t src, uint32_t dst, int num) +{ + struct td *p = &todo.list[todo.num]; + + di_debug("FUNC: %s\n", __func__); + if (todo.num == TODO_LIST_LEN) { + di_warn("WARNING: DryIce todo-list overflow!\n"); + return; + } + p->action = action; + p->src = src; + p->dst = dst; + p->num = num; + todo.num++; +} + +#if defined(DI_DEBUG) || defined(DI_TESTING) +/* + * print out the contents of the dryice status register + * with all the bits decoded + */ +static void show_dsr(const char *heading) +{ + uint32_t dsr = di_read(DSR); + + di_info("%s\n", heading); + if (dsr & DSR_TAMPER_BITS) { + if (dsr & DSR_WTD) + di_info("Wire-mesh Tampering Detected\n"); + if (dsr & DSR_ETBD) + di_info("External Tampering B Detected\n"); + if (dsr & DSR_ETAD) + di_info("External Tampering A Detected\n"); + if (dsr & DSR_EBD) + di_info("External Boot Detected\n"); + if (dsr & DSR_SAD) + di_info("Security Alarm Detected\n"); + if (dsr & DSR_TTD) + di_info("Temperature Tampering Detected\n"); + if (dsr & DSR_CTD) + di_info("Clock Tampering Detected\n"); + if (dsr & DSR_VTD) + di_info("Voltage Tampering Detected\n"); + if (dsr & DSR_MCO) + di_info("Monotonic Counter Overflow\n"); + if (dsr & DSR_TCO) + di_info("Time Counter Overflow\n"); + } else + di_info("No Tamper Events Detected\n"); + + di_info("%d Key Busy Flag\n", !!(dsr & DSR_KBF)); + di_info("%d Write Busy Flag\n", !!(dsr & DSR_WBF)); + di_info("%d Write Next Flag\n", !!(dsr & DSR_WNF)); + di_info("%d Write Complete Flag\n", !!(dsr & DSR_WCF)); + di_info("%d Write Error Flag\n", !!(dsr & DSR_WEF)); + di_info("%d Random Key Error\n", !!(dsr & DSR_RKE)); + di_info("%d Random Key Valid\n", !!(dsr & DSR_RKV)); + di_info("%d Clock Alarm Flag\n", !!(dsr & DSR_CAF)); + di_info("%d Non-Valid Flag\n", !!(dsr & DSR_NVF)); + di_info("%d Security Violation Flag\n", !!(dsr & DSR_SVF)); +} + +/* + * print out a key in hex + */ +static void print_key(const char *tag, uint8_t *key, int bits) +{ + int bytes = (bits + 7) / 8; + + di_info("%s", tag); + while (bytes--) + os_printk("%02x", *key++); + os_printk("\n"); +} +#endif /* defined(DI_DEBUG) || defined(DI_TESTING) */ + +/* + * dryice normal interrupt service routine + */ +OS_DEV_ISR(dryice_norm_irq) +{ + /* save dryice status register */ + uint32_t dsr = di_read(DSR); + + if (dsr & DSR_WCF) { + /* disable the write-completion interrupt */ + di_write(di_read(DIER) & ~DIER_WCIE, DIER); + + if (todo.status == TODO_ST_PEND_WCF) { + if (dsr & DSR_WEF) { + try_to_clear_wef(); + todo_done(DI_ERR_WRITE); + } else { + todo.cur++; + todo.status = TODO_ST_READY; + todo_run(); + } + } + } else if (dsr & (DSR_RKV | DSR_RKE)) { + /* disable the random-key-gen interrupt */ + di_write(di_read(DIER) & ~DIER_RKIE, DIER); + + if (todo.status == TODO_ST_PEND_RKG) { + if (dsr & DSR_RKE) + todo_done(DI_ERR_FAIL); + else { + todo.cur++; + todo.status = TODO_ST_READY; + todo_run(); + } + } + } + + os_dev_isr_return(1); +} + +/* write loop with error handling -- for init only */ +#define di_write_loop_goto(val, reg, rc, label) \ + do {if (di_write_loop(val, reg)) \ + {rc = OS_ERROR_FAIL_S; goto label; } } while (0) + +/* + * dryice driver initialization + */ +OS_DEV_INIT(dryice_init) +{ + di_return_t rc = 0; + + di_info("MXC DryIce driver\n"); + + /* allocate memory */ + di = os_alloc_memory(sizeof(*di), GFP_KERNEL); + if (di == NULL) { + rc = OS_ERROR_NO_MEMORY_S; + goto err_alloc; + } + memset(di, 0, sizeof(*di)); + di->baseaddr = DRYICE_BASE_ADDR; + di->irq_norm.irq = MXC_INT_DRYICE_NORM; + di->irq_sec.irq = MXC_INT_DRYICE_SEC; + + /* map i/o registers */ + di->ioaddr = os_map_device(di->baseaddr, DI_ADDRESS_RANGE); + if (di->ioaddr == NULL) { + rc = OS_ERROR_FAIL_S; + goto err_iomap; + } + + /* allocate locks */ + di->busy_lock = os_lock_alloc_init(); + if (di->busy_lock == NULL) { + rc = OS_ERROR_NO_MEMORY_S; + goto err_locks; + } + + /* enable clocks (is there a portable way to do this?) */ + di->clk = clk_get(NULL, "dryice_clk"); + clk_enable(di->clk); + + /* register for interrupts */ + /* os_register_interrupt() dosen't support an option to make the + interrupt as shared. Replaced it with request_irq().*/ + rc = request_irq(di->irq_norm.irq, dryice_norm_irq, IRQF_SHARED, + "dry_ice", di); + if (rc) + goto err_irqs; + else + di->irq_norm.set = 1; + + /* + * DRYICE HARDWARE INIT + */ + +#ifdef DI_DEBUG + show_dsr("DSR Pre-Initialization State"); +#endif + + if (di_state() == DI_STATE_NON_VALID) { + uint32_t dsr = di_read(DSR); + + di_debug("initializing from non-valid state\n"); + + /* clear security violation flag */ + if (dsr & DSR_SVF) + di_write_loop_goto(DSR_SVF, DSR, rc, err_write); + + /* clear tamper detect flags */ + if (dsr & DSR_TAMPER_BITS) + di_write_loop_goto(DSR_TAMPER_BITS, DSR, rc, err_write); + + /* initialize timers */ + di_write_loop_goto(0, DTCLR, rc, err_write); + di_write_loop_goto(0, DTCMR, rc, err_write); + di_write_loop_goto(0, DMCR, rc, err_write); + + /* clear non-valid flag */ + di_write_loop_goto(DSR_NVF, DSR, rc, err_write); + } + + /* set tamper events we are interested in watching */ + di_write_loop_goto(DTCR_WTE | DTCR_ETBE | DTCR_ETAE, DTCR, rc, + err_write); +#ifdef DI_DEBUG + show_dsr("DSR Post-Initialization State"); +#endif + os_dev_init_return(OS_ERROR_OK_S); + +err_write: + /* unregister interrupts */ + if (di->irq_norm.set) + os_deregister_interrupt(di->irq_norm.irq); + if (di->irq_sec.set) + os_deregister_interrupt(di->irq_sec.irq); + + /* turn off clocks (is there a portable way to do this?) */ + clk_disable(di->clk); + clk_put(di->clk); + +err_irqs: + /* unallocate locks */ + os_lock_deallocate(di->busy_lock); + +err_locks: + /* unmap i/o registers */ + os_unmap_device(di->ioaddr, DI_ADDRESS_RANGE); + +err_iomap: + /* free the dryice struct */ + os_free_memory(di); + +err_alloc: + os_dev_init_return(rc); +} + +/* + * dryice driver exit routine + */ +OS_DEV_SHUTDOWN(dryice_exit) +{ + /* don't allow new operations */ + di->exit_flag = 1; + + /* wait for the current operation to complete */ + os_sleep(exit_queue, di->busy == 0, 0); + + /* unregister interrupts */ + if (di->irq_norm.set) + os_deregister_interrupt(di->irq_norm.irq); + if (di->irq_sec.set) + os_deregister_interrupt(di->irq_sec.irq); + + /* turn off clocks (is there a portable way to do this?) */ + clk_disable(di->clk); + clk_put(di->clk); + + /* unallocate locks */ + os_lock_deallocate(di->busy_lock); + + /* unmap i/o registers */ + os_unmap_device(di->ioaddr, DI_ADDRESS_RANGE); + + /* free the dryice struct */ + os_free_memory(di); + + os_dev_shutdown_return(OS_ERROR_OK_S); +} + +di_return_t dryice_set_programmed_key(const void *key_data, int key_bits, + int flags) +{ + uint32_t dcr; + int key_bytes, reg; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (key_data == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + if (key_bits < 0 || key_bits > MAX_KEY_LEN || key_bits % 8) { + rc = DI_ERR_INVAL; + goto err; + } + if (flags & DI_FUNC_FLAG_WORD_KEY) { + if (key_bits % 32 || (uint32_t)key_data & 0x3) { + rc = DI_ERR_INVAL; + goto err; + } + } + if (di->key_programmed) { + rc = DI_ERR_INUSE; + goto err; + } + if (di_state() == DI_STATE_FAILURE) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_PKWHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_PKWSL) { + rc = DI_ERR_SLOCK; + goto err; + } + key_bytes = key_bits / 8; + + todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0); + + /* accomodate busses that can only do 32-bit transfers */ + if (flags & DI_FUNC_FLAG_WORD_KEY) { + uint32_t *keyp = (void *)key_data; + + for (reg = 0; reg < MAX_KEY_WORDS; reg++) { + if (reg < MAX_KEY_WORDS - key_bytes / 4) + todo_write_val(0, DPKR7 - reg * 4); + else { + todo_write_ptr32(keyp, DPKR7 - reg * 4); + keyp++; + } + } + } else { + uint8_t *keyp = (void *)key_data; + + for (reg = 0; reg < MAX_KEY_WORDS; reg++) { + int size = key_bytes - (MAX_KEY_WORDS - reg - 1) * 4; + if (size <= 0) + todo_write_val(0, DPKR7 - reg * 4); + else { + if (size > 4) + size = 4; + todo_write_ptr(keyp, DPKR7 - reg * 4, size); + keyp += size; + } + } + } + todo_assign(di->key_programmed, 1); + + if (flags & DI_FUNC_LOCK_FLAGS) { + dcr = di_read(DCR); + if (flags & DI_FUNC_FLAG_READ_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_PKRHL; + else + dcr |= DCR_PKRSL; + } + if (flags & DI_FUNC_FLAG_WRITE_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_PKWHL; + else + dcr |= DCR_PKWSL; + } + todo_write_val(dcr, DCR); + } + todo_start(); + + if (flags & DI_FUNC_FLAG_ASYNC) + return 0; + + rc = todo_wait_done(); +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_set_programmed_key); + +di_return_t dryice_get_programmed_key(uint8_t *key_data, int key_bits) +{ + int reg, byte, key_bytes; + uint32_t dcr, dpkr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (key_data == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + if (key_bits < 0 || key_bits > MAX_KEY_LEN || key_bits % 8) { + rc = DI_ERR_INVAL; + goto err; + } + #if 0 + if (!di->key_programmed) { + rc = DI_ERR_UNSET; + goto err; + } + #endif + if (di_state() == DI_STATE_FAILURE) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_PKRHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_PKRSL) { + rc = DI_ERR_SLOCK; + goto err; + } + key_bytes = key_bits / 8; + + /* read key */ + for (reg = 0; reg < MAX_KEY_WORDS; reg++) { + if (reg < (MAX_KEY_BYTES - key_bytes) / 4) + continue; + dpkr = di_read(DPKR7 - reg * 4); + + for (byte = 0; byte < 4; byte++) { + if (reg * 4 + byte >= MAX_KEY_BYTES - key_bytes) { + int shift = 24 - byte * 8; + *key_data++ = (dpkr >> shift) & 0xff; + } + } + dpkr = 0; /* cleared for security */ + } +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_get_programmed_key); + +di_return_t dryice_release_programmed_key(void) +{ + uint32_t dcr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (!di->key_programmed) { + rc = DI_ERR_UNSET; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_PKWHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_PKWSL) { + rc = DI_ERR_SLOCK; + goto err; + } + di->key_programmed = 0; + +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_release_programmed_key); + +di_return_t dryice_set_random_key(int flags) +{ + uint32_t dcr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (di_state() == DI_STATE_FAILURE) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_RKHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_RKSL) { + rc = DI_ERR_SLOCK; + goto err; + } + todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0); + + /* clear Random Key Error bit, if set */ + if (di_read(DSR) & DSR_RKE) + todo_write_val(DSR_RKE, DCR); + + /* load random key */ + todo_write_val(DKCR_LRK, DKCR); + + /* wait for RKV (valid) or RKE (error) */ + todo_wait_rkg(); + + if (flags & DI_FUNC_LOCK_FLAGS) { + dcr = di_read(DCR); + if (flags & DI_FUNC_FLAG_WRITE_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_RKHL; + else + dcr |= DCR_RKSL; + } + todo_write_val(dcr, DCR); + } + todo_start(); + + if (flags & DI_FUNC_FLAG_ASYNC) + return 0; + + rc = todo_wait_done(); +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_set_random_key); + +di_return_t dryice_select_key(di_key_t key, int flags) +{ + uint32_t dcr, dksr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + switch (key) { + case DI_KEY_FK: + dksr = DKSR_IIM_KEY; + break; + case DI_KEY_PK: + dksr = DKSR_PROG_KEY; + break; + case DI_KEY_RK: + dksr = DKSR_RAND_KEY; + break; + case DI_KEY_FPK: + dksr = DKSR_PROG_XOR_IIM_KEY; + break; + case DI_KEY_FRK: + dksr = DKSR_RAND_XOR_IIM_KEY; + break; + default: + rc = DI_ERR_INVAL; + goto err; + } + if (di->key_selected) { + rc = DI_ERR_INUSE; + goto err; + } + if (di_state() != DI_STATE_VALID) { + rc = DI_ERR_STATE; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_KSHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_KSSL) { + rc = DI_ERR_SLOCK; + goto err; + } + todo_init((flags & DI_FUNC_FLAG_ASYNC) != 0); + + /* select key */ + todo_write_val(dksr, DKSR); + + todo_assign(di->key_selected, 1); + + if (flags & DI_FUNC_LOCK_FLAGS) { + dcr = di_read(DCR); + if (flags & DI_FUNC_FLAG_WRITE_LOCK) { + if (flags & DI_FUNC_FLAG_HARD_LOCK) + dcr |= DCR_KSHL; + else + dcr |= DCR_KSSL; + } + todo_write_val(dcr, DCR); + } + todo_start(); + + if (flags & DI_FUNC_FLAG_ASYNC) + return 0; + + rc = todo_wait_done(); +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_select_key); + +di_return_t dryice_check_key(di_key_t *key) +{ + uint32_t dksr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (key == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + + dksr = di_read(DKSR); + + if (di_state() != DI_STATE_VALID) { + dksr = DKSR_IIM_KEY; + rc = DI_ERR_STATE; + } else if (dksr == DI_KEY_RK || dksr == DI_KEY_FRK) { + if (!(di_read(DSR) & DSR_RKV)) { + dksr = DKSR_IIM_KEY; + rc = DI_ERR_UNSET; + } + } + switch (dksr) { + case DKSR_IIM_KEY: + *key = DI_KEY_FK; + break; + case DKSR_PROG_KEY: + *key = DI_KEY_PK; + break; + case DKSR_RAND_KEY: + *key = DI_KEY_RK; + break; + case DKSR_PROG_XOR_IIM_KEY: + *key = DI_KEY_FPK; + break; + case DKSR_RAND_XOR_IIM_KEY: + *key = DI_KEY_FRK; + break; + } +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_check_key); + +di_return_t dryice_release_key_selection(void) +{ + uint32_t dcr; + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (!di->key_selected) { + rc = DI_ERR_UNSET; + goto err; + } + dcr = di_read(DCR); + if (dcr & DCR_KSHL) { + rc = DI_ERR_HLOCK; + goto err; + } + if (dcr & DCR_KSSL) { + rc = DI_ERR_SLOCK; + goto err; + } + di->key_selected = 0; + +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_release_key_selection); + +di_return_t dryice_get_tamper_event(uint32_t *events, uint32_t *timestamp, + int flags) +{ + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + if (di_state() == DI_STATE_VALID) { + rc = DI_ERR_STATE; + goto err; + } + if (events == NULL) { + rc = DI_ERR_INVAL; + goto err; + } + *events = di_read(DSR) & DSR_TAMPER_BITS; + if (timestamp) { + if (di_state() == DI_STATE_NON_VALID) + *timestamp = di_read(DTCMR); + else + *timestamp = 0; + } +err: + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_get_tamper_event); + +di_return_t dryice_register_callback(void (*func)(di_return_t, + unsigned long cookie), + unsigned long cookie) +{ + di_return_t rc = 0; + + if (di_busy_set()) + return DI_ERR_BUSY; + + di->cb_func = func; + di->cb_cookie = cookie; + + di_busy_clear(); + return rc; +} +EXTERN_SYMBOL(dryice_register_callback); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("DryIce"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/security/dryice.h b/drivers/mxc/security/dryice.h new file mode 100644 index 000000000000..8334b5098d31 --- /dev/null +++ b/drivers/mxc/security/dryice.h @@ -0,0 +1,287 @@ +/* + * Copyright 2009 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 + */ + + +#ifndef __DRYICE_H__ +#define __DRYICE_H__ + + +/*! + * @file dryice.h + * @brief Definition of DryIce API. + */ + +/*! @page dryice_api DryIce API + * + * Definition of the DryIce API. + * + * The DryIce API implements a software interface to the DryIce hardware + * block. Methods are provided to store, retrieve, generate, and manage + * cryptographic keys and to monitor security tamper events. + * + * See @ref dryice_api for the DryIce API. + */ + +/*! + * This defines the SCC key length (in bits) + */ +#define SCC_KEY_LEN 168 + +/*! + * This defines the maximum key length (in bits) + */ +#define MAX_KEY_LEN 256 +#define MAX_KEY_BYTES ((MAX_KEY_LEN) / 8) +#define MAX_KEY_WORDS ((MAX_KEY_LEN) / 32) + +/*! + * @name DryIce Function Flags + */ +/*@{*/ +#define DI_FUNC_FLAG_ASYNC 0x01 /*!< do not block */ +#define DI_FUNC_FLAG_READ_LOCK 0x02 /*!< set read lock for this resource */ +#define DI_FUNC_FLAG_WRITE_LOCK 0x04 /*!< set write lock for resource */ +#define DI_FUNC_FLAG_HARD_LOCK 0x08 /*!< locks will be hard (default soft) */ +#define DI_FUNC_FLAG_WORD_KEY 0x10 /*!< key provided as 32-bit words */ +/*@}*/ + +/*! + * @name DryIce Tamper Events + */ +/*@{*/ +#define DI_TAMPER_EVENT_WTD (1 << 23) /*!< wire-mesh tampering det */ +#define DI_TAMPER_EVENT_ETBD (1 << 22) /*!< ext tampering det: input B */ +#define DI_TAMPER_EVENT_ETAD (1 << 21) /*!< ext tampering det: input A */ +#define DI_TAMPER_EVENT_EBD (1 << 20) /*!< external boot detected */ +#define DI_TAMPER_EVENT_SAD (1 << 19) /*!< security alarm detected */ +#define DI_TAMPER_EVENT_TTD (1 << 18) /*!< temperature tampering det */ +#define DI_TAMPER_EVENT_CTD (1 << 17) /*!< clock tampering det */ +#define DI_TAMPER_EVENT_VTD (1 << 16) /*!< voltage tampering det */ +#define DI_TAMPER_EVENT_MCO (1 << 3) /*!< monotonic counter overflow */ +#define DI_TAMPER_EVENT_TCO (1 << 2) /*!< time counter overflow */ +/*@}*/ + +/*! + * DryIce Key Sources + */ +typedef enum di_key { + DI_KEY_FK, /*!< the fused (IIM) key */ + DI_KEY_PK, /*!< the programmed key */ + DI_KEY_RK, /*!< the random key */ + DI_KEY_FPK, /*!< the programmed key XORed with the fused key */ + DI_KEY_FRK, /*!< the random key XORed with the fused key */ +} di_key_t; + +/*! + * DryIce Error Codes + */ +typedef enum dryice_return { + DI_SUCCESS = 0, /*!< operation was successful */ + DI_ERR_BUSY, /*!< device or resource busy */ + DI_ERR_STATE, /*!< dryice is in incompatible state */ + DI_ERR_INUSE, /*!< resource is already in use */ + DI_ERR_UNSET, /*!< resource has not been initialized */ + DI_ERR_WRITE, /*!< error occurred during register write */ + DI_ERR_INVAL, /*!< invalid argument */ + DI_ERR_FAIL, /*!< operation failed */ + DI_ERR_HLOCK, /*!< resource is hard locked */ + DI_ERR_SLOCK, /*!< resource is soft locked */ + DI_ERR_NOMEM, /*!< out of memory */ +} di_return_t; + +/*! + * These functions define the DryIce API. + */ + +/*! + * Write a given key to the Programmed Key registers in DryIce, and + * optionally lock the Programmed Key against either reading or further + * writing. The value is held until a call to the release_programmed_key + * interface is made, or until the appropriate HW reset if the write-lock + * flags are used. Unused key bits will be zeroed. + * + * @param[in] key_data A pointer to the key data to be programmed, with + * the most significant byte or word first. This + * will be interpreted as a byte pointer unless the + * WORD_KEY flag is set, in which case it will be + * treated as a word pointer and the key data will be + * read a word at a time, starting with the MSW. + * When called asynchronously, the data pointed to by + * key_data must persist until the operation completes. + * + * @param[in] key_bits The number of bits in the key to be stored. + * This must be a multiple of 8 and within the + * range of 0 and MAX_KEY_LEN. + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags can include: + * ASYNC, READ_LOCK, WRITE_LOCK, HARD_LOCK, and + * WORD_KEY. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, INVAL + * on invalid arguments, INUSE if key has already been + * programmed, STATE if DryIce is in the wrong state, + * HLOCK or SLOCK if the key registers are locked for + * writing, and WRITE if a write error occurs + * (See #di_return_t). + */ +extern di_return_t dryice_set_programmed_key(const void *key_data, int key_bits, + int flags); + +/*! + * Read the Programmed Key registers and write the contents into a buffer. + * + * @param[out] key_data A byte pointer to where the key data will be written, + * with the most significant byte being written first. + * + * @param[in] key_bits The number of bits of the key to be retrieved. + * This must be a multiple of 8 and within the + * range of 0 and MAX_KEY_LEN. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, INVAL + * on invalid arguments, UNSET if key has not been + * programmed, STATE if DryIce is in the wrong state, + * and HLOCK or SLOCK if the key registers are locked for + * reading (See #di_return_t). + */ +extern di_return_t dryice_get_programmed_key(uint8_t *key_data, int key_bits); + +/*! + * Allow the set_programmed_key interface to be used to write a new + * Programmed Key to DryIce. Note that this interface does not overwrite + * the value in the Programmed Key registers. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, + * UNSET if the key has not been previously set, and + * HLOCK or SLOCK if the key registers are locked for + * writing (See #di_return_t). + */ +extern di_return_t dryice_release_programmed_key(void); + +/*! + * Generate and load a new Random Key in DryIce, and optionally lock the + * Random Key against further change. + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags can include: + * ASYNC, READ_LOCK, WRITE_LOCK, and HARD_LOCK. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, STATE + * if DryIce is in the wrong state, FAIL if the key gen + * failed, HLOCK or SLOCK if the key registers are + * locked, and WRITE if a write error occurs + * (See #di_return_t). + */ +extern di_return_t dryice_set_random_key(int flags); + +/*! + * Set the key selection in DryIce to determine the key used by an + * encryption module such as SCC. The selection is held until a call to the + * Release Selected Key interface is made, or until the appropriate HW + * reset if the LOCK flags are used. + * + * @param[in] key The source of the key to be used by the SCC + * (See #di_key_t). + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags can include: + * ASYNC, WRITE_LOCK, and HARD_LOCK. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, INVAL + * on invalid arguments, INUSE if a selection has already + * been made, STATE if DryIce is in the wrong state, + * HLOCK or SLOCK if the selection register is locked, + * and WRITE if a write error occurs + */ +extern di_return_t dryice_select_key(di_key_t key, int flags); + +/*! + * Check which key will be used in the SCC. This is needed because in some + * DryIce states, the Key Select Register is overridden by a default value + * (the Fused/IIM key). + * + * @param[out] key The source of the key that is currently selected for + * use by the SCC. This may be different from the key + * specified by the dryice_select_key function + * (See #di_key_t). This value is set even if an error + * code (except for BUSY) is returned. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, STATE if + * DryIce is in the wrong state, INVAL on invalid + * arguments, or UNSET if no key has been selected + * (See #di_return_t). + */ +extern di_return_t dryice_check_key(di_key_t *key); + +/*! + * Allow the dryice_select_key interface to be used to set a new key selection + * in DryIce. Note that this interface does not overwrite the value in DryIce. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, UNSET + * if the no selection has been made previously, and + * HLOCK or SLOCK if the selection register is locked + * (See #di_return_t). + */ +extern di_return_t dryice_release_key_selection(void); + +/*! + * Returns tamper-detection status bits. Also an optional timestamp when + * DryIce is in the Non-valid state. If DryIce is not in Failure or Non-valid + * state, this interface returns a failure code. + * + * @param[out] events This is a bit-wise OR of the following events: + * WTD (Wire Mesh), ETBD (External Tamper B), + * ETAD (External Tamper A), EBD (External Boot), + * SAD (Security Alarm), TTD (Temperature Tamper), + * CTD (Clock Tamper), VTD (Voltage Tamper), + * MCO (Monolithic Counter Overflow), and + * TCO (Time Counter Overflow). + * + * @param[out] timestamp This is the value of the time counter in seconds + * when the tamper occurred. A timestamp will not be + * returned if a NULL pointer is specified. If DryIce + * is not in the Non-valid state the time cannot be + * read, so a timestamp of 0 will be returned. + * + * @param[in] flags This is a bit-wise OR of the flags to be passed + * to the function. Flags is ignored currently by + * this function. + * + * @return Returns SUCCESS (0), BUSY if DryIce is busy, and + * INVAL on invalid arguments (See #di_return_t). + */ +extern di_return_t +dryice_get_tamper_event(uint32_t *events, uint32_t *timestamp, int flags); + +/*! + * Provide a callback function to be called upon the completion of DryIce calls + * that are executed asynchronously. + * + * @param[in] func This is a pointer to a function of type: + * void callback(di_return_t rc, unsigned long cookie) + * The return code of the async function is passed + * back in "rc" along with the cookie provided when + * registering the callback. + * + * @param[in] cookie This is an "opaque" cookie of type unsigned long that + * is returned on subsequent callbacks. It may be of any + * value. + * + * @return Returns SUCCESS (0), or BUSY if DryIce is busy + * (See #di_return_t). + */ +extern di_return_t dryice_register_callback(void (*func)(di_return_t rc, + unsigned long cookie), + unsigned long cookie); + +#endif /* __DRYICE_H__ */ diff --git a/drivers/mxc/security/mxc_scc.c b/drivers/mxc/security/mxc_scc.c new file mode 100644 index 000000000000..8a6b0c2419b5 --- /dev/null +++ b/drivers/mxc/security/mxc_scc.c @@ -0,0 +1,2386 @@ +/* + * Copyright 2004-2009 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 mxc_scc.c + * + * This is the driver code for the Security Controller (SCC). It has no device + * driver interface, so no user programs may access it. Its interaction with + * the Linux kernel is from calls to #scc_init() when the driver is loaded, and + * #scc_cleanup() should the driver be unloaded. The driver uses locking and + * (task-sleep/task-wakeup) functions of the kernel. It also registers itself + * to handle the interrupt line(s) from the SCC. + * + * Other drivers in the kernel may use the remaining API functions to get at + * the services of the SCC. The main service provided is the Secure Memory, + * which allows encoding and decoding of secrets with a per-chip secret key. + * + * The SCC is single-threaded, and so is this module. When the scc_crypt() + * routine is called, it will lock out other accesses to the function. If + * another task is already in the module, the subsequent caller will spin on a + * lock waiting for the other access to finish. + * + * Note that long crypto operations could cause a task to spin for a while, + * preventing other kernel work (other than interrupt processing) to get done. + * + * The external (kernel module) interface is through the following functions: + * @li scc_get_configuration() + * @li scc_crypt() + * @li scc_zeroize_memories() + * @li scc_monitor_security_failure() + * @li scc_stop_monitoring_security_failure() + * @li scc_set_sw_alarm() + * @li scc_read_register() + * @li scc_write_register() + * + * All other functions are internal to the driver. + * + * @ingroup MXCSCC +*/ +#include "sahara2/include/fsl_platform.h" +#include "sahara2/include/portable_os.h" +#include "mxc_scc_internals.h" + +#include <linux/delay.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + +#include <linux/device.h> +#include <mach/clock.h> +#include <linux/device.h> + +#else +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#endif + +/*! + * This is the set of errors which signal that access to the SCM RAM has + * failed or will fail. + */ +#define SCM_ACCESS_ERRORS \ + (SCM_ERR_USER_ACCESS | SCM_ERR_ILLEGAL_ADDRESS | \ + SCM_ERR_ILLEGAL_MASTER | SCM_ERR_CACHEABLE_ACCESS | \ + SCM_ERR_UNALIGNED_ACCESS | SCM_ERR_BYTE_ACCESS | \ + SCM_ERR_INTERNAL_ERROR | SCM_ERR_SMN_BLOCKING_ACCESS | \ + SCM_ERR_CIPHERING | SCM_ERR_ZEROIZING | SCM_ERR_BUSY) +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * This is type void* so that a) it cannot directly be dereferenced, + * and b) pointer arithmetic on it will function in a 'normal way' for + * the offsets in scc_defines.h + * + * scc_base is the location in the iomap where the SCC's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the SCC, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #SCC_READ_REGISTER and + * #SCC_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *scc_base; + +/*! Array to hold function pointers registered by + #scc_monitor_security_failure() and processed by + #scc_perform_callbacks() */ +static void (*scc_callbacks[SCC_CALLBACK_SIZE]) (void); + +/*! Structure returned by #scc_get_configuration() */ +static scc_config_t scc_configuration = { + .driver_major_version = SCC_DRIVER_MAJOR_VERSION_1, + .driver_minor_version = SCC_DRIVER_MINOR_VERSION_8, + .scm_version = -1, + .smn_version = -1, + .block_size_bytes = -1, + .black_ram_size_blocks = -1, + .red_ram_size_blocks = -1 +}; + +/*! Key Control Information. Integrity is controlled by use of + #scc_crypto_lock. */ +static struct scc_key_slot scc_key_info[SCC_KEY_SLOTS]; + +/*! Internal flag to know whether SCC is in Failed state (and thus many + * registers are unavailable). Once it goes failed, it never leaves it. */ +static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL; + +/*! Flag to say whether interrupt handler has been registered for + * SMN interrupt */ +static int smn_irq_set = 0; + +/*! Flag to say whether interrupt handler has been registered for + * SCM interrupt */ +static int scm_irq_set = 0; + +/*! This lock protects the #scc_callbacks list as well as the @c + * callbacks_performed flag in #scc_perform_callbacks. Since the data this + * protects may be read or written from either interrupt or base level, all + * operations should use the irqsave/irqrestore or similar to make sure that + * interrupts are inhibited when locking from base level. + */ +static spinlock_t scc_callbacks_lock = SPIN_LOCK_UNLOCKED; + +/*! + * Ownership of this lock prevents conflicts on the crypto operation in the SCC + * and the integrity of the #scc_key_info. + */ +static spinlock_t scc_crypto_lock = SPIN_LOCK_UNLOCKED; + +/*! Calculated once for quick reference to size of the unreserved space in one + * RAM in SCM. + */ +static uint32_t scc_memory_size_bytes; + +/*! Calculated once for quick reference to size of SCM address space */ +static uint32_t scm_highest_memory_address; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)) +#ifndef SCC_CLOCK_NOT_GATED +/*! Pointer to SCC's clock information. Initialized during scc_init(). */ +static struct clk *scc_clk = NULL; +#endif +#endif + +/*! The lookup table for an 8-bit value. Calculated once + * by #scc_init_ccitt_crc(). + */ +static uint16_t scc_crc_lookup_table[256]; + +/*! Fixed padding for appending to plaintext to fill out a block */ +static uint8_t scc_block_padding[8] = + { SCC_DRIVER_PAD_CHAR, 0, 0, 0, 0, 0, 0, 0 }; + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_init() */ +/*****************************************************************************/ +/*! + * Initialize the driver at boot time or module load time. + * + * Register with the kernel as the interrupt handler for the SCC interrupt + * line(s). + * + * Map the SCC's register space into the driver's memory space. + * + * Query the SCC for its configuration and status. Save the configuration in + * #scc_configuration and save the status in #scc_availability. Called by the + * kernel. + * + * Do any locking/wait queue initialization which may be necessary. + * + * The availability fuse may be checked, depending on platform. + */ +static int scc_init(void) +{ + uint32_t smn_status; + int i; + int return_value = -EIO; /* assume error */ + if (scc_availability == SCC_STATUS_INITIAL) { + + /* Set this until we get an initial reading */ + scc_availability = SCC_STATUS_CHECKING; + + /* Initialize the constant for the CRC function */ + scc_init_ccitt_crc(); + + /* initialize the callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* Initialize key slots */ + for (i = 0; i < SCC_KEY_SLOTS; i++) { + scc_key_info[i].offset = i * SCC_KEY_SLOT_SIZE; + scc_key_info[i].status = 0; /* unassigned */ + } + + /* Enable the SCC clock on platforms where it is gated */ +#ifndef SCC_CLOCK_NOT_GATED + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SCC_CLK); +#else + + scc_clk = clk_get(NULL, "scc_clk"); + if (scc_clk != ERR_PTR(ENOENT)) { + clk_enable(scc_clk); + } +#endif /* LINUX_VERSION_CODE */ + +#endif /* SCC_CLOCK_NOT_GATED */ + /* See whether there is an SCC available */ + if (0 && !SCC_ENABLED()) { + os_printk(KERN_ERR + "SCC: Fuse for SCC is set to disabled. Exiting.\n"); + } else { + /* Map the SCC (SCM and SMN) memory on the internal bus into + kernel address space */ + scc_base = (void *)IO_ADDRESS(SCC_BASE); + + /* If that worked, we can try to use the SCC */ + if (scc_base == NULL) { + os_printk(KERN_ERR + "SCC: Register mapping failed. Exiting.\n"); + } else { + /* Get SCM into 'clean' condition w/interrupts cleared & + disabled */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* Clear error status register (any write will do it) */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + + /* + * There is an SCC. Determine its current state. Side effect + * is to populate scc_config and scc_availability + */ + smn_status = scc_grab_config_values(); + + /* Try to set up interrupt handler(s) */ + if (scc_availability == SCC_STATUS_OK) { + if (setup_interrupt_handling() != 0) { + unsigned condition; + /*! + * The error could be only that the SCM interrupt was + * not set up. This interrupt is always masked, so + * that is not an issue. + * + * The SMN's interrupt may be shared on that line, it + * may be separate, or it may not be wired. Do what + * is necessary to check its status. + * + * Although the driver is coded for possibility of not + * having SMN interrupt, the fact that there is one + * means it should be available and used. + */ +#ifdef USE_SMN_INTERRUPT + condition = !smn_irq_set; /* Separate. Check SMN binding */ +#elif !defined(NO_SMN_INTERRUPT) + condition = !scm_irq_set; /* Shared. Check SCM binding */ +#else + condition = FALSE; /* SMN not wired at all. Ignore. */ +#endif + /* setup was not able to set up SMN interrupt */ + scc_availability = + SCC_STATUS_UNIMPLEMENTED; + } /* interrupt handling returned non-zero */ + } /* availability is OK */ + if (scc_availability == SCC_STATUS_OK) { + /* Get SMN into 'clean' condition w/interrupts cleared & + enabled */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT + | + SMN_COMMAND_ENABLE_INTERRUPT); + } + /* availability is still OK */ + } /* if scc_base != NULL */ + + } /* if SCC_ENABLED() */ + + /* + * If status is SCC_STATUS_UNIMPLEMENTED or is still + * SCC_STATUS_CHECKING, could be leaving here with the driver partially + * initialized. In either case, cleanup (which will mark the SCC as + * UNIMPLEMENTED). + */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_UNIMPLEMENTED) { + scc_cleanup(); + } else { + return_value = 0; /* All is well */ + } + } + /* ! STATUS_INITIAL */ + pr_debug("SCC: Driver Status is %s\n", + (scc_availability == SCC_STATUS_INITIAL) ? "INITIAL" : + (scc_availability == SCC_STATUS_CHECKING) ? "CHECKING" : + (scc_availability == + SCC_STATUS_UNIMPLEMENTED) ? "UNIMPLEMENTED" + : (scc_availability == + SCC_STATUS_OK) ? "OK" : (scc_availability == + SCC_STATUS_FAILED) ? "FAILED" : + "UNKNOWN"); + + return return_value; +} /* scc_init */ + +/*****************************************************************************/ +/* fn scc_cleanup() */ +/*****************************************************************************/ +/*! + * Perform cleanup before driver/module is unloaded by setting the machine + * state close to what it was when the driver was loaded. This function is + * called when the kernel is shutting down or when this driver is being + * unloaded. + * + * A driver like this should probably never be unloaded, especially if there + * are other module relying upon the callback feature for monitoring the SCC + * status. + * + * In any case, cleanup the callback table (by clearing out all of the + * pointers). Deregister the interrupt handler(s). Unmap SCC registers. + * + */ +static void scc_cleanup(void) +{ + int i; + + /* Mark the driver / SCC as unusable. */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + + /* Clear out callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* If SCC has been mapped in, clean it up and unmap it */ + if (scc_base) { + /* For the SCM, disable interrupts, zeroize RAMs. Interrupt + * status will appear because zeroize will complete. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS | + SCM_INTERRUPT_CTRL_ZEROIZE_MEMORY); + + /* For the SMN, clear and disable interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_CLEAR_INTERRUPT); + + /* remove virtual mapping */ + iounmap((void *)scc_base); + } + + /* Now that interrupts cannot occur, disassociate driver from the interrupt + * lines. + */ + + /* Deregister SCM interrupt handler */ + if (scm_irq_set) { + os_deregister_interrupt(INT_SCC_SCM); + } + + /* Deregister SMN interrupt handler */ + if (smn_irq_set) { +#ifdef USE_SMN_INTERRUPT + os_deregister_interrupt(INT_SCC_SMN); +#endif + } + pr_debug("SCC driver cleaned up.\n"); + +} /* scc_cleanup */ + +/*****************************************************************************/ +/* fn scc_get_configuration() */ +/*****************************************************************************/ +scc_config_t *scc_get_configuration(void) +{ + /* + * If some other driver calls scc before the kernel does, make sure that + * this driver's initialization is performed. + */ + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /*! + * If there is no SCC, yet the driver exists, the value -1 will be in + * the #scc_config_t fields for other than the driver versions. + */ + return &scc_configuration; +} /* scc_get_configuration */ + +/*****************************************************************************/ +/* fn scc_zeroize_memories() */ +/*****************************************************************************/ +scc_return_t scc_zeroize_memories(void) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + if (scc_availability == SCC_STATUS_OK) { + unsigned long irq_flags; /* for IRQ save/restore */ + + /* Lock access to crypto memory of the SCC */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + /* Start the Zeroize by setting a bit in the SCM_INTERRUPT_CTRL + * register */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_MASK_INTERRUPTS + | SCM_INTERRUPT_CTRL_ZEROIZE_MEMORY); + + scc_wait_completion(); + + /* Get any error info */ + status = SCC_READ_REGISTER(SCM_ERROR_STATUS); + + /* unlock the SCC */ + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + if (!(status & SCM_ERR_ZEROIZE_FAILED)) { + return_status = SCC_RET_OK; + } else { + pr_debug + ("SCC: Zeroize failed. SCM Error Status is 0x%08x\n", + status); + } + + /* Clear out any status. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* and any error status */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + } + + return return_status; +} /* scc_zeroize_memories */ + +/*****************************************************************************/ +/* fn scc_crypt() */ +/*****************************************************************************/ +scc_return_t +scc_crypt(unsigned long count_in_bytes, const uint8_t * data_in, + const uint8_t * init_vector, + scc_enc_dec_t direction, scc_crypto_mode_t crypto_mode, + scc_verify_t check_mode, uint8_t * data_out, + unsigned long *count_out_bytes) + +{ + scc_return_t return_code = SCC_RET_FAIL; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + (void)scc_update_state(); /* in case no interrupt line from SMN */ + + /* make initial error checks */ + if (scc_availability != SCC_STATUS_OK + || count_in_bytes == 0 + || data_in == 0 + || data_out == 0 + || (crypto_mode != SCC_CBC_MODE && crypto_mode != SCC_ECB_MODE) + || (crypto_mode == SCC_CBC_MODE && init_vector == NULL) + || (direction != SCC_ENCRYPT && direction != SCC_DECRYPT) + || (check_mode == SCC_VERIFY_MODE_NONE && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (direction == SCC_DECRYPT && + count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (check_mode != SCC_VERIFY_MODE_NONE && + check_mode != SCC_VERIFY_MODE_CCITT_CRC)) { + pr_debug + ("SCC: scc_crypt() count_in_bytes_ok = %d; data_in_ok = %d;" + " data_out_ok = %d; iv_ok = %d\n", !(count_in_bytes == 0), + !(data_in == 0), !(data_out == 0), + !(crypto_mode == SCC_CBC_MODE && init_vector == NULL)); + pr_debug("SCC: scc_crypt() mode_ok=%d; direction_ok=%d;" + " size_ok=%d, check_mode_ok=%d\n", + !(crypto_mode != SCC_CBC_MODE + && crypto_mode != SCC_ECB_MODE), + !(direction != SCC_ENCRYPT + && direction != SCC_DECRYPT), + !((check_mode == SCC_VERIFY_MODE_NONE + && count_in_bytes % SCC_BLOCK_SIZE_BYTES() != 0) + || (direction == SCC_DECRYPT + && count_in_bytes % SCC_BLOCK_SIZE_BYTES() != + 0)), !(check_mode != SCC_VERIFY_MODE_NONE + && check_mode != + SCC_VERIFY_MODE_CCITT_CRC)); + pr_debug("SCC: scc_crypt() detected bad argument\n"); + } else { + /* Start settings for write to SCM_CONTROL register */ + uint32_t scc_control = SCM_CONTROL_START_CIPHER; + unsigned long irq_flags; /* for IRQ save/restore */ + + /* Lock access to crypto memory of the SCC */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + /* Special needs for CBC Mode */ + if (crypto_mode == SCC_CBC_MODE) { + scc_control |= SCM_CBC_MODE; /* change default of ECB */ + /* Put in Initial Context. Vector registers are contiguous */ + copy_to_scc(init_vector, SCM_INIT_VECTOR_0, + SCC_BLOCK_SIZE_BYTES(), NULL); + } + + /* Fill the RED_START register */ + SCC_WRITE_REGISTER(SCM_RED_START, + SCM_NON_RESERVED_OFFSET / + SCC_BLOCK_SIZE_BYTES()); + + /* Fill the BLACK_START register */ + SCC_WRITE_REGISTER(SCM_BLACK_START, + SCM_NON_RESERVED_OFFSET / + SCC_BLOCK_SIZE_BYTES()); + + if (direction == SCC_ENCRYPT) { + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Calculate extra bytes needed for crc (2) and block + padding */ + int padding_needed = + CRC_SIZE_BYTES + SCC_BLOCK_SIZE_BYTES() - + ((count_in_bytes + CRC_SIZE_BYTES) + % SCC_BLOCK_SIZE_BYTES()); + + /* Verify space is available */ + if (*count_out_bytes < + count_in_bytes + padding_needed) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } + /* If did not detect space error, do the encryption */ + if (return_code != SCC_RET_INSUFFICIENT_SPACE) { + return_code = + scc_encrypt(count_in_bytes, data_in, + scc_control, data_out, + check_mode == + SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } + + } + /* direction == SCC_ENCRYPT */ + else { /* SCC_DECRYPT */ + /* Check for sufficient space in data_out */ + if (check_mode == SCC_VERIFY_MODE_NONE) { + if (*count_out_bytes < count_in_bytes) { + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } else { /* SCC_VERIFY_MODE_CCITT_CRC */ + /* Do initial check. Assume last block (of padding) and CRC + * will get stripped. After decipher is done and padding is + * removed, will know exact value. + */ + int possible_size = + (int)count_in_bytes - CRC_SIZE_BYTES - + SCC_BLOCK_SIZE_BYTES(); + if ((int)*count_out_bytes < possible_size) { + pr_debug + ("SCC: insufficient decrypt space %ld/%d.\n", + *count_out_bytes, possible_size); + return_code = + SCC_RET_INSUFFICIENT_SPACE; + } + } + + /* If did not detect space error, do the decryption */ + if (return_code != SCC_RET_INSUFFICIENT_SPACE) { + return_code = + scc_decrypt(count_in_bytes, data_in, + scc_control, data_out, + check_mode == + SCC_VERIFY_MODE_CCITT_CRC, + count_out_bytes); + } + + } /* SCC_DECRYPT */ + + /* unlock the SCC */ + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + } /* else no initial error */ + + return return_code; +} /* scc_crypt */ + +/*****************************************************************************/ +/* fn scc_set_sw_alarm() */ +/*****************************************************************************/ +void scc_set_sw_alarm(void) +{ + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Update scc_availability based on current SMN status. This might + * perform callbacks. + */ + (void)scc_update_state(); + + /* if everything is OK, make it fail */ + if (scc_availability == SCC_STATUS_OK) { + + /* sound the alarm (and disable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_SET_SOFTWARE_ALARM); + + scc_availability = SCC_STATUS_FAILED; /* Remember what we've done */ + + /* In case SMN interrupt is not available, tell the world */ + scc_perform_callbacks(); + } + + return; +} /* scc_set_sw_alarm */ + +/*****************************************************************************/ +/* fn scc_monitor_security_failure() */ +/*****************************************************************************/ +scc_return_t scc_monitor_security_failure(void callback_func(void)) +{ + int i; + unsigned long irq_flags; /* for IRQ save/restore */ + scc_return_t return_status = SCC_RET_TOO_MANY_FUNCTIONS; + int function_stored = FALSE; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search through table looking for empty slot */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + if (function_stored) { + /* Saved duplicate earlier. Clear this later one. */ + scc_callbacks[i] = NULL; + } + /* Exactly one copy is now stored */ + return_status = SCC_RET_OK; + break; + } else if (scc_callbacks[i] == NULL && !function_stored) { + /* Found open slot. Save it and remember */ + scc_callbacks[i] = callback_func; + return_status = SCC_RET_OK; + function_stored = TRUE; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return return_status; +} /* scc_monitor_security_failure */ + +/*****************************************************************************/ +/* fn scc_stop_monitoring_security_failure() */ +/*****************************************************************************/ +void scc_stop_monitoring_security_failure(void callback_func(void)) +{ + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + /* Search every entry of the table for this function */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + scc_callbacks[i] = NULL; /* found instance - clear it out */ + break; + } + } + + /* Free the lock */ + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} /* scc_stop_monitoring_security_failure */ + +/*****************************************************************************/ +/* fn scc_read_register() */ +/*****************************************************************************/ +scc_return_t scc_read_register(int register_offset, uint32_t * value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (register_offset != SMN_BITBANK_DECREMENT && /* write only! */ + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if ((return_status = + check_register_accessible(register_offset, + smn_status, + scm_status)) == + SCC_RET_OK) { + *value = SCC_READ_REGISTER(register_offset); + } + } + } + + return return_status; +} /* scc_read_register */ + +/*****************************************************************************/ +/* fn scc_write_register() */ +/*****************************************************************************/ +scc_return_t scc_write_register(int register_offset, uint32_t value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (!(register_offset == SCM_STATUS || /* These registers are */ + register_offset == SCM_CONFIGURATION || /* Read Only */ + register_offset == SMN_BIT_COUNT || + register_offset == SMN_TIMER) && + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if (check_register_accessible + (register_offset, smn_status, scm_status) == 0) { + SCC_WRITE_REGISTER(register_offset, value); + return_status = SCC_RET_OK; + } + } + } + + return return_status; +} /* scc_write_register() */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_irq() */ +/*****************************************************************************/ +/*! + * This is the interrupt handler for the SCC. + * + * This function checks the SMN Status register to see whether it + * generated the interrupt, then it checks the SCM Status register to + * see whether it needs attention. + * + * If an SMN Interrupt is active, then the SCC state set to failure, and + * #scc_perform_callbacks() is invoked to notify any interested parties. + * + * The SCM Interrupt should be masked, as this driver uses polling to determine + * when the SCM has completed a crypto or zeroing operation. Therefore, if the + * interrupt is active, the driver will just clear the interrupt and (re)mask. + * + */ +OS_DEV_ISR(scc_irq) +{ + uint32_t smn_status; + uint32_t scm_status; + int handled = 0; /* assume interrupt isn't from SMN */ +#if defined(USE_SMN_INTERRUPT) + int smn_irq = INT_SCC_SMN; /* SMN interrupt is on a line by itself */ +#elif defined (NO_SMN_INTERRUPT) + int smn_irq = -1; /* not wired to CPU at all */ +#else + int smn_irq = INT_SCC_SCM; /* SMN interrupt shares a line with SCM */ +#endif + + /* Update current state... This will perform callbacks... */ + smn_status = scc_update_state(); + + /* SMN is on its own interrupt line. Verify the IRQ was triggered + * before clearing the interrupt and marking it handled. */ + if ((os_dev_get_irq() == smn_irq) && + (smn_status & SMN_STATUS_SMN_STATUS_IRQ)) { + SCC_WRITE_REGISTER(SMN_COMMAND, SMN_COMMAND_CLEAR_INTERRUPT); + handled++; /* tell kernel that interrupt was handled */ + } + + /* Check on the health of the SCM */ + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* The driver masks interrupts, so this should never happen. */ + if (os_dev_get_irq() == INT_SCC_SCM) { + /* but if it does, try to prevent it in the future */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + handled++; + } + + /* Any non-zero value of handled lets kernel know we got something */ + return IRQ_RETVAL(handled); +} + +/*****************************************************************************/ +/* fn scc_perform_callbacks() */ +/*****************************************************************************/ +/*! Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void) +{ + static int callbacks_performed = 0; + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + /* Acquire lock of callbacks table and callbacks_performed flag */ + spin_lock_irqsave(&scc_callbacks_lock, irq_flags); + + if (!callbacks_performed) { + callbacks_performed = 1; + + /* Loop over all of the entries in the table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + /* If not null, ... */ + if (scc_callbacks[i]) { + scc_callbacks[i] (); /* invoke the callback routine */ + } + } + } + + spin_unlock_irqrestore(&scc_callbacks_lock, irq_flags); + + return; +} + +/*****************************************************************************/ +/* fn copy_to_scc() */ +/*****************************************************************************/ +/*! + * Move data from possibly unaligned source and realign for SCC, possibly + * while calculating CRC. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_from_scc(), as long as the sum total of bytes copied is a multiple of + * four (SCC native word size). + * + * @param[in] from Location in memory + * @param[out] to Location in SCC + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, no crc is calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_to_scc(const uint8_t * from, uint32_t to, unsigned long count_bytes, + uint16_t * crc) +{ + int i; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + uint32_t status; + + pr_debug("SCC: copying %ld bytes to 0x%0x.\n", count_bytes, to); + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug + ("SCC copy_to_scc(): Error status detected (before copy):" + " %08x\n", status); + /* clear out errors left behind by somebody else */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + if (crc) { + current_crc = *crc; + } + + /* Initialize value being built for SCM. If we are starting 'clean', + * set it to zero. Otherwise pick up partial value which had been saved + * earlier. */ + if (SCC_BYTE_OFFSET(to) == 0) { + scm_word = 0; + } else { + scm_word = SCC_READ_REGISTER(SCC_WORD_PTR(to)); /* recover */ + } + + /* Now build up SCM words and write them out when each is full */ + for (i = 0; i < count_bytes; i++) { + uint8_t byte = *from++; /* value from plaintext */ + +#if defined(__BIG_ENDIAN) || defined(FSL_HAVE_DRYICE) + scm_word = (scm_word << 8) | byte; /* add byte to SCM word */ +#else + scm_word = (byte << 24) | (scm_word >> 8); +#endif + /* now calculate CCITT CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + to++; /* bump location in SCM */ + + /* check for full word */ + if (SCC_BYTE_OFFSET(to) == 0) { + SCC_WRITE_REGISTER((uint32_t) (to - 4), scm_word); /* write it out */ + } + } + + /* If at partial word after previous loop, save it in SCM memory for + next time. */ + if (SCC_BYTE_OFFSET(to) != 0) { + SCC_WRITE_REGISTER(SCC_WORD_PTR(to), scm_word); /* save */ + } + + /* Copy CRC back */ + if (crc) { + *crc = current_crc; + } + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug("SCC copy_to_scc(): Error status detected: %08x\n", + status); + /* Clear any/all bits. */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + return status; +} + +/*****************************************************************************/ +/* fn copy_from_scc() */ +/*****************************************************************************/ +/*! + * Move data from aligned 32-bit source and place in (possibly unaligned) + * target, and maybe calculate CRC at the same time. + * + * Multiple calls can be made to this routine (without intervening calls to + * #copy_to_scc(), as long as the sum total of bytes copied is be a multiple + * of four. + * + * @param[in] from Location in SCC + * @param[out] to Location in memory + * @param[in] count_bytes Number of bytes to copy + * @param[in,out] crc Pointer to CRC. Initial value must be + * #CRC_CCITT_START if this is the start of + * message. Output is the resulting (maybe + * partial) CRC. If NULL, crc is not calculated. + * + * @return Zero - success. Non-zero - SCM status bits defining failure. + */ +static uint32_t +copy_from_scc(const uint32_t from, uint8_t * to, unsigned long count_bytes, + uint16_t * crc) +{ + uint32_t running_from = from; + uint32_t scm_word; + uint16_t current_crc = 0; /* local copy for fast access */ + uint32_t status; + pr_debug("SCC: copying %ld bytes from 0x%x.\n", count_bytes, from); + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug + ("SCC copy_from_scc(): Error status detected (before copy):" + " %08x\n", status); + /* clear out errors left behind by somebody else */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + if (crc) { + current_crc = *crc; + } + + /* Read word which is sitting in SCM memory. Ignore byte offset */ + scm_word = SCC_READ_REGISTER(SCC_WORD_PTR(running_from)); + + /* If necessary, move the 'first' byte into place */ + if (SCC_BYTE_OFFSET(running_from) != 0) { +#if defined(__BIG_ENDIAN) || defined(FSL_HAVE_DRYICE) + scm_word <<= 8 * SCC_BYTE_OFFSET(running_from); +#else + scm_word >>= 8 * SCC_BYTE_OFFSET(running_from); +#endif + } + + /* Now build up SCM words and write them out when each is full */ + while (count_bytes--) { + uint8_t byte; /* value from plaintext */ + +#if defined(__BIG_ENDIAN) || defined(FSL_HAVE_DRYICE) + byte = (scm_word & 0xff000000) >> 24; /* pull byte out of SCM word */ + scm_word <<= 8; /* shift over to remove the just-pulled byte */ +#else + byte = (scm_word & 0xff); + scm_word >>= 8; /* shift over to remove the just-pulled byte */ +#endif + *to++ = byte; /* send byte to memory */ + + /* now calculate CRC */ + if (crc) { + CALC_CRC(byte, current_crc); + } + + running_from++; + /* check for empty word */ + if (count_bytes && SCC_BYTE_OFFSET(running_from) == 0) { + /* read one in */ + scm_word = SCC_READ_REGISTER((uint32_t) running_from); + } + } + + if (crc) { + *crc = current_crc; + } + + status = SCC_READ_REGISTER(SCM_ERROR_STATUS) & SCM_ACCESS_ERRORS; + if (status != 0) { + pr_debug("SCC copy_from_scc(): Error status detected: %08x\n", + status); + /* Clear any/all bits. */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, status); + } + + return status; +} + +/*****************************************************************************/ +/* fn scc_strip_padding() */ +/*****************************************************************************/ +/*! + * Remove padding from plaintext. Search backwards for #SCC_DRIVER_PAD_CHAR, + * verifying that each byte passed over is zero (0). Maximum number of bytes + * to examine is 8. + * + * @param[in] from Pointer to byte after end of message + * @param[out] count_bytes_stripped Number of padding bytes removed by this + * function. + * + * @return #SCC_RET_OK if all goes, well, #SCC_RET_FAIL if padding was + * not present. +*/ +static scc_return_t +scc_strip_padding(uint8_t * from, unsigned *count_bytes_stripped) +{ + int i = SCC_BLOCK_SIZE_BYTES(); + scc_return_t return_code = SCC_RET_VERIFICATION_FAILED; + + /* + * Search backwards looking for the magic marker. If it isn't found, + * make sure that a 0 byte is there in its place. Stop after the maximum + * amount of padding (8 bytes) has been searched); + */ + while (i-- > 0) { + if (*--from == SCC_DRIVER_PAD_CHAR) { + *count_bytes_stripped = SCC_BLOCK_SIZE_BYTES() - i; + return_code = SCC_RET_OK; + break; + } else if (*from != 0) { /* if not marker, check for 0 */ + pr_debug("SCC: Found non-zero interim pad: 0x%x\n", + *from); + break; + } + } + + return return_code; +} + +/*****************************************************************************/ +/* fn scc_update_state() */ +/*****************************************************************************/ +/*! + * Make certain SCC is still running. + * + * Side effect is to update #scc_availability and, if the state goes to failed, + * run #scc_perform_callbacks(). + * + * (If #SCC_BRINGUP is defined, bring SCC to secure state if it is found to be + * in health check state) + * + * @return Current value of #SMN_STATUS register. + */ +static uint32_t scc_update_state(void) +{ + uint32_t smn_status_register = SMN_STATE_FAIL; + int smn_state; + + /* if FAIL or UNIMPLEMENTED, don't bother */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_OK) { + + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + +#ifdef SCC_BRINGUP + /* If in Health Check while booting, try to 'bringup' to Secure mode */ + if (scc_availability == SCC_STATUS_CHECKING && + smn_state == SMN_STATE_HEALTH_CHECK) { + /* Code up a simple algorithm for the ASC */ + SCC_WRITE_REGISTER(SMN_SEQUENCE_START, 0xaaaa); + SCC_WRITE_REGISTER(SMN_SEQUENCE_END, 0x5555); + SCC_WRITE_REGISTER(SMN_SEQUENCE_CHECK, 0x5555); + /* State should be SECURE now */ + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + } +#endif + + /* + * State should be SECURE or NON_SECURE for operation of the part. If + * FAIL, mark failed (i.e. limited access to registers). Any other + * state, mark unimplemented, as the SCC is unuseable. + */ + if (smn_state == SMN_STATE_SECURE + || smn_state == SMN_STATE_NON_SECURE) { + /* Healthy */ + scc_availability = SCC_STATUS_OK; + } else if (smn_state == SMN_STATE_FAIL) { + scc_availability = SCC_STATUS_FAILED; /* uh oh - unhealthy */ + scc_perform_callbacks(); + os_printk(KERN_ERR "SCC: SCC went into FAILED mode\n"); + } else { + /* START, ZEROIZE RAM, HEALTH CHECK, or unknown */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* unuseable */ + os_printk(KERN_ERR "SCC: SCC declared UNIMPLEMENTED\n"); + } + } + /* if availability is initial or ok */ + return smn_status_register; +} + +/*****************************************************************************/ +/* fn scc_init_ccitt_crc() */ +/*****************************************************************************/ +/*! + * Populate the partial CRC lookup table. + * + * @return none + * + */ +static void scc_init_ccitt_crc(void) +{ + int dividend; /* index for lookup table */ + uint16_t remainder; /* partial value for a given dividend */ + int bit; /* index into bits of a byte */ + + /* + * Compute the remainder of each possible dividend. + */ + for (dividend = 0; dividend < 256; ++dividend) { + /* + * Start with the dividend followed by zeros. + */ + remainder = dividend << (8); + + /* + * Perform modulo-2 division, a bit at a time. + */ + for (bit = 8; bit > 0; --bit) { + /* + * Try to divide the current data bit. + */ + if (remainder & 0x8000) { + remainder = (remainder << 1) ^ CRC_POLYNOMIAL; + } else { + remainder = (remainder << 1); + } + } + + /* + * Store the result into the table. + */ + scc_crc_lookup_table[dividend] = remainder; + } + +} /* scc_init_ccitt_crc() */ + +/*****************************************************************************/ +/* fn grab_config_values() */ +/*****************************************************************************/ +/*! + * grab_config_values() will read the SCM Configuration and SMN Status + * registers and store away version and size information for later use. + * + * @return The current value of the SMN Status register. + */ +static uint32_t scc_grab_config_values(void) +{ + uint32_t config_register; + uint32_t smn_status_register = SMN_STATE_FAIL; + + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + /* access the SCC - these are 'safe' registers */ + config_register = SCC_READ_REGISTER(SCM_CONFIGURATION); + pr_debug("SCC Driver: SCM config is 0x%08x\n", config_register); + + /* Get SMN status and update scc_availability */ + smn_status_register = scc_update_state(); + pr_debug("SCC Driver: SMN status is 0x%08x\n", + smn_status_register); + + /* save sizes and versions information for later use */ + scc_configuration.block_size_bytes = (config_register & + SCM_CFG_BLOCK_SIZE_MASK) + >> SCM_CFG_BLOCK_SIZE_SHIFT; + + scc_configuration.red_ram_size_blocks = (config_register & + SCM_CFG_RED_SIZE_MASK) + >> SCM_CFG_RED_SIZE_SHIFT; + + scc_configuration.black_ram_size_blocks = (config_register & + SCM_CFG_BLACK_SIZE_MASK) + >> SCM_CFG_BLACK_SIZE_SHIFT; + + scc_configuration.scm_version = (config_register + & SCM_CFG_VERSION_ID_MASK) + >> SCM_CFG_VERSION_ID_SHIFT; + + scc_configuration.smn_version = (smn_status_register & + SMN_STATUS_VERSION_ID_MASK) + >> SMN_STATUS_VERSION_ID_SHIFT; + + if (scc_configuration.scm_version != SCM_VERSION_1) { + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* Unknown version */ + } + + scc_memory_size_bytes = (SCC_BLOCK_SIZE_BYTES() * + scc_configuration. + black_ram_size_blocks) + - SCM_NON_RESERVED_OFFSET; + + /* This last is for driver consumption only */ + scm_highest_memory_address = SCM_BLACK_MEMORY + + (SCC_BLOCK_SIZE_BYTES() * + scc_configuration.black_ram_size_blocks); + } + + return smn_status_register; +} /* grab_config_values */ + +/*****************************************************************************/ +/* fn setup_interrupt_handling() */ +/*****************************************************************************/ +/*! + * Register the SCM and SMN interrupt handlers. + * + * Called from #scc_init() + * + * @return 0 on success + */ +static int setup_interrupt_handling(void) +{ + int smn_error_code = -1; + int scm_error_code = -1; + + /* Disnable SCM interrupts */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + +#ifdef USE_SMN_INTERRUPT + /* Install interrupt service routine for SMN. */ + smn_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SMN, scc_irq); + if (smn_error_code != 0) { + os_printk + ("SCC Driver: Error installing SMN Interrupt Handler: %d\n", + smn_error_code); + } else { + smn_irq_set = 1; /* remember this for cleanup */ + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); + } +#else + smn_error_code = 0; /* no problems... will handle later */ +#endif + + /* + * Install interrupt service routine for SCM (or both together). + */ + scm_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SCM, scc_irq); + if (scm_error_code != 0) { +#ifndef MXC + os_printk + ("SCC Driver: Error installing SCM Interrupt Handler: %d\n", + scm_error_code); +#else + os_printk + ("SCC Driver: Error installing SCC Interrupt Handler: %d\n", + scm_error_code); +#endif + } else { + scm_irq_set = 1; /* remember this for cleanup */ +#if defined(USE_SMN_INTERRUPT) && !defined(NO_SMN_INTERRUPT) + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); +#endif + } + + /* Return an error if one was encountered */ + return scm_error_code ? scm_error_code : smn_error_code; +} /* setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn scc_do_crypto() */ +/*****************************************************************************/ +/*! Have the SCM perform the crypto function. + * + * Set up length register, and the store @c scm_control into control register + * to kick off the operation. Wait for completion, gather status, clear + * interrupt / status. + * + * @param byte_count number of bytes to perform in this operation + * @param scm_control Bit values to be set in @c SCM_CONTROL register + * + * @return 0 on success, value of #SCM_ERROR_STATUS on failure + */ +static uint32_t scc_do_crypto(int byte_count, uint32_t scm_control) +{ + int block_count = byte_count / SCC_BLOCK_SIZE_BYTES(); + uint32_t crypto_status; + + /* clear any outstanding flags */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* In length register, 0 means 1, etc. */ + SCC_WRITE_REGISTER(SCM_LENGTH, block_count - 1); + + /* set modes and kick off the operation */ + SCC_WRITE_REGISTER(SCM_CONTROL, scm_control); + + scc_wait_completion(); + + /* Mask for done and error bits */ + crypto_status = SCC_READ_REGISTER(SCM_STATUS) + & (SCM_STATUS_CIPHERING_DONE + | SCM_STATUS_LENGTH_ERROR | SCM_STATUS_INTERNAL_ERROR); + + /* Only done bit should be on */ + if (crypto_status != SCM_STATUS_CIPHERING_DONE) { + /* Replace with error status instead */ + crypto_status = SCC_READ_REGISTER(SCM_ERROR_STATUS); + pr_debug("SCM Failure: 0x%x\n", crypto_status); + if (crypto_status == 0) { + /* That came up 0. Turn on arbitrary bit to signal error. */ + crypto_status = SCM_ERR_INTERNAL_ERROR; + } + } else { + crypto_status = 0; + } + + pr_debug("SCC: Done waiting.\n"); + + /* Clear out any status. */ + SCC_WRITE_REGISTER(SCM_INTERRUPT_CTRL, + SCM_INTERRUPT_CTRL_CLEAR_INTERRUPT + | SCM_INTERRUPT_CTRL_MASK_INTERRUPTS); + + /* And clear any error status */ + SCC_WRITE_REGISTER(SCM_ERROR_STATUS, 0); + + return crypto_status; +} + +/*****************************************************************************/ +/* fn scc_encrypt() */ +/*****************************************************************************/ +/*! + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +static scc_return_t +scc_encrypt(uint32_t count_in_bytes, const uint8_t * data_in, + uint32_t scm_control, + uint8_t * data_out, int add_crc, unsigned long *count_out_bytes) + +{ + scc_return_t return_code = SCC_RET_FAIL; /* initialised for failure */ + uint32_t input_bytes_left = count_in_bytes; /* local copy */ + uint32_t output_bytes_copied = 0; /* running total */ + uint32_t bytes_to_process; /* multi-purpose byte counter */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + crc_t *crc_ptr = NULL; /* Reset if CRC required */ + /* byte address into SCM RAM */ + uint32_t scm_location = SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET; + /* free RED RAM */ + uint32_t scm_bytes_remaining = scc_memory_size_bytes; + /* CRC+padding holder */ + uint8_t padding_buffer[PADDING_BUFFER_MAX_BYTES]; + unsigned padding_byte_count = 0; /* Reset if padding required */ + uint32_t scm_error_status = 0; /* No known SCM error initially */ + uint32_t i; /* Counter for clear data loop */ + uint32_t dirty_bytes; /* Number of bytes of memory used + temporarily during encryption, + which need to be wiped after + completion of the operation. */ + + /* Set location of CRC and prepare padding bytes if required */ + if (add_crc != 0) { + crc_ptr = &crc; + padding_byte_count = SCC_BLOCK_SIZE_BYTES() + - (count_in_bytes + + CRC_SIZE_BYTES) % SCC_BLOCK_SIZE_BYTES(); + memcpy(padding_buffer + CRC_SIZE_BYTES, scc_block_padding, + padding_byte_count); + } + + /* Process remaining input or padding data */ + while (input_bytes_left > 0) { + + /* Determine how much work to do this pass */ + bytes_to_process = (input_bytes_left > scm_bytes_remaining) ? + scm_bytes_remaining : input_bytes_left; + + /* Copy plaintext into SCM RAM, calculating CRC if required */ + copy_to_scc(data_in, scm_location, bytes_to_process, crc_ptr); + + /* Adjust pointers & counters */ + input_bytes_left -= bytes_to_process; + data_in += bytes_to_process; + scm_location += bytes_to_process; + scm_bytes_remaining -= bytes_to_process; + + /* Add CRC and padding after the last byte is copied if required */ + if ((input_bytes_left == 0) && (crc_ptr != NULL)) { + + /* Copy CRC into padding buffer MSB first */ + padding_buffer[0] = (crc >> 8) & 0xFF; + padding_buffer[1] = crc & 0xFF; + + /* Reset pointers and counter */ + data_in = padding_buffer; + input_bytes_left = CRC_SIZE_BYTES + padding_byte_count; + crc_ptr = NULL; /* CRC no longer required */ + + /* Go round loop again to copy CRC and padding to SCM */ + continue; + } + + /* if no input and crc_ptr */ + /* Now have block-sized plaintext in SCM to encrypt */ + /* Encrypt plaintext; exit loop on error */ + bytes_to_process = scm_location - + (SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET); + + if (output_bytes_copied + bytes_to_process > *count_out_bytes) { + return_code = SCC_RET_INSUFFICIENT_SPACE; + scm_error_status = -1; /* error signal */ + pr_debug + ("SCC: too many ciphertext bytes for space available\n"); + break; + } + pr_debug("SCC: Starting encryption. %x for %d bytes (%p/%p)\n", + scm_control, bytes_to_process, + (void *)SCC_READ_REGISTER(SCM_RED_START), + (void *)SCC_READ_REGISTER(SCM_BLACK_START)); + scm_error_status = scc_do_crypto(bytes_to_process, scm_control); + if (scm_error_status != 0) { + break; + } + + /* Copy out ciphertext */ + copy_from_scc(SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET, + data_out, bytes_to_process, NULL); + + /* Adjust pointers and counters for next loop */ + output_bytes_copied += bytes_to_process; + data_out += bytes_to_process; + scm_location = SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET; + scm_bytes_remaining = scc_memory_size_bytes; + + } /* input_bytes_left > 0 */ + /* Clear all red and black memory used during ephemeral encryption */ + dirty_bytes = (count_in_bytes > scc_memory_size_bytes) ? + scc_memory_size_bytes : count_in_bytes; + + for (i = 0; i < dirty_bytes; i += 4) { + SCC_WRITE_REGISTER(SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET + i, + 0); + SCC_WRITE_REGISTER(SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET + + i, 0); + } + + /* If no SCM error, set OK status and save ouput byte count */ + if (scm_error_status == 0) { + return_code = SCC_RET_OK; + *count_out_bytes = output_bytes_copied; + } + + return return_code; +} /* scc_encrypt */ + +/*****************************************************************************/ +/* fn scc_decrypt() */ +/*****************************************************************************/ +/*! + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +static scc_return_t +scc_decrypt(uint32_t count_in_bytes, const uint8_t * data_in, + uint32_t scm_control, + uint8_t * data_out, int verify_crc, unsigned long *count_out_bytes) +{ + scc_return_t return_code = SCC_RET_FAIL; + uint32_t bytes_left = count_in_bytes; /* local copy */ + uint32_t bytes_copied = 0; /* running total of bytes going to user */ + uint32_t bytes_to_copy = 0; /* Number in this encryption 'chunk' */ + uint16_t crc = CRC_CCITT_START; /* running CRC value */ + /* next target for ctext */ + uint32_t scm_location = SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET; + unsigned padding_byte_count; /* number of bytes of padding stripped */ + uint8_t last_two_blocks[2 * SCC_BLOCK_SIZE_BYTES()]; /* temp */ + uint32_t scm_error_status = 0; /* register value */ + uint32_t i; /* Counter for clear data loop */ + uint32_t dirty_bytes; /* Number of bytes of memory used + temporarily during decryption, + which need to be wiped after + completion of the operation. */ + + scm_control |= SCM_DECRYPT_MODE; + + if (verify_crc) { + /* Save last two blocks (if there are at least two) of ciphertext for + special treatment. */ + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + if (bytes_left >= SCC_BLOCK_SIZE_BYTES()) { + bytes_left -= SCC_BLOCK_SIZE_BYTES(); + } + } + + /* Copy ciphertext into SCM BLACK memory */ + while (bytes_left && scm_error_status == 0) { + + /* Determine how much work to do this pass */ + if (bytes_left > (scc_memory_size_bytes)) { + bytes_to_copy = scc_memory_size_bytes; + } else { + bytes_to_copy = bytes_left; + } + + if (bytes_copied + bytes_to_copy > *count_out_bytes) { + scm_error_status = -1; + break; + } + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC: Starting decryption of %d bytes.\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = scc_do_crypto(bytes_to_copy, scm_control); + + copy_from_scc(SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET, + data_out, bytes_to_copy, &crc); + bytes_copied += bytes_to_copy; + data_out += bytes_to_copy; + scm_location = SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET; + + /* Do housekeeping */ + bytes_left -= bytes_to_copy; + + } /* while bytes_left */ + + /* At this point, either the process is finished, or this is verify mode */ + + if (scm_error_status == 0) { + if (!verify_crc) { + *count_out_bytes = bytes_copied; + return_code = SCC_RET_OK; + } else { + /* Verify mode. There are one or two blocks of unprocessed + * ciphertext sitting at data_in. They need to be moved to the + * SCM, decrypted, searched to remove padding, then the plaintext + * copied back to the user (while calculating CRC, of course). + */ + + /* Calculate ciphertext still left */ + bytes_to_copy = count_in_bytes - bytes_copied; + + copy_to_scc(data_in, scm_location, bytes_to_copy, NULL); + data_in += bytes_to_copy; /* move pointer */ + + pr_debug("SCC: Finishing decryption (%d bytes).\n", + bytes_to_copy); + + /* Do the work, wait for completion */ + scm_error_status = + scc_do_crypto(bytes_to_copy, scm_control); + + if (scm_error_status == 0) { + /* Copy decrypted data back from SCM RED memory */ + copy_from_scc(SCM_RED_MEMORY + + SCM_NON_RESERVED_OFFSET, + last_two_blocks, bytes_to_copy, + NULL); + + /* (Plaintext) + crc + padding should be in temp buffer */ + if (scc_strip_padding + (last_two_blocks + bytes_to_copy, + &padding_byte_count) == SCC_RET_OK) { + bytes_to_copy -= + padding_byte_count + CRC_SIZE_BYTES; + + /* verify enough space in user buffer */ + if (bytes_copied + bytes_to_copy <= + *count_out_bytes) { + int i = 0; + + /* Move out last plaintext and calc CRC */ + while (i < bytes_to_copy) { + CALC_CRC(last_two_blocks + [i], crc); + *data_out++ = + last_two_blocks + [i++]; + bytes_copied++; + } + + /* Verify the CRC by running over itself */ + CALC_CRC(last_two_blocks + [bytes_to_copy], crc); + CALC_CRC(last_two_blocks + [bytes_to_copy + 1], + crc); + if (crc == 0) { + /* Just fine ! */ + *count_out_bytes = + bytes_copied; + return_code = + SCC_RET_OK; + } else { + return_code = + SCC_RET_VERIFICATION_FAILED; + pr_debug + ("SCC: CRC values are %04x, %02x%02x\n", + crc, + last_two_blocks + [bytes_to_copy], + last_two_blocks + [bytes_to_copy + + 1]); + } + } /* if space available */ + } /* if scc_strip_padding... */ + else { + /* bad padding means bad verification */ + return_code = + SCC_RET_VERIFICATION_FAILED; + } + } + /* scm_error_status == 0 */ + } /* verify_crc */ + } + + /* scm_error_status == 0 */ + /* Clear all red and black memory used during ephemeral decryption */ + dirty_bytes = (count_in_bytes > scc_memory_size_bytes) ? + scc_memory_size_bytes : count_in_bytes; + + for (i = 0; i < dirty_bytes; i += 4) { + SCC_WRITE_REGISTER(SCM_RED_MEMORY + SCM_NON_RESERVED_OFFSET + i, + 0); + SCC_WRITE_REGISTER(SCM_BLACK_MEMORY + SCM_NON_RESERVED_OFFSET + + i, 0); + } + return return_code; +} /* scc_decrypt */ + +/*****************************************************************************/ +/* fn scc_alloc_slot() */ +/*****************************************************************************/ +/*! + * Allocate a key slot to fit the requested size. + * + * @param value_size_bytes Size of the key or other secure data + * @param owner_id Value to tie owner to slot + * @param[out] slot Handle to access or deallocate slot + * + * @return SCC_RET_OK on success, SCC_RET_INSUFFICIENT_SPACE if not slots of + * requested size are available. + */ +scc_return_t +scc_alloc_slot(uint32_t value_size_bytes, uint64_t owner_id, uint32_t * slot) +{ + scc_return_t status = SCC_RET_FAIL; + unsigned long irq_flags; + + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + pr_debug("SCC: Allocating %d-byte slot for 0x%Lx\n", + value_size_bytes, owner_id); + + if ((value_size_bytes != 0) && (value_size_bytes <= SCC_MAX_KEY_SIZE)) { + int i; + + for (i = 0; i < SCC_KEY_SLOTS; i++) { + if (scc_key_info[i].status == 0) { + scc_key_info[i].owner_id = owner_id; + scc_key_info[i].length = value_size_bytes; + scc_key_info[i].status = 1; /* assigned! */ + *slot = i; + status = SCC_RET_OK; + break; /* exit 'for' loop */ + } + } + + if (status != SCC_RET_OK) { + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + pr_debug("SCC: Allocated slot %d (0x%Lx)\n", i, + owner_id); + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + out: + return status; +} + +/*****************************************************************************/ +/* fn verify_slot_access() */ +/*****************************************************************************/ +inline static scc_return_t +verify_slot_access(uint64_t owner_id, uint32_t slot, uint32_t access_len) +{ + scc_return_t status = SCC_RET_FAIL; + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + + if ((slot < SCC_KEY_SLOTS) && scc_key_info[slot].status + && (scc_key_info[slot].owner_id == owner_id) + && (access_len <= SCC_KEY_SLOT_SIZE)) { + status = SCC_RET_OK; + pr_debug("SCC: Verify on slot %d succeeded\n", slot); + } else { + if (slot >= SCC_KEY_SLOTS) { + pr_debug("SCC: Verify on bad slot (%d) failed\n", slot); + } else if (scc_key_info[slot].status) { + pr_debug("SCC: Verify on slot %d failed (%Lx) \n", slot, + owner_id); + } else { + pr_debug + ("SCC: Verify on slot %d failed: not allocated\n", + slot); + } + } + + out: + return status; +} + +scc_return_t +scc_verify_slot_access(uint64_t owner_id, uint32_t slot, uint32_t access_len) +{ + return verify_slot_access(owner_id, slot, access_len); +} + +/*****************************************************************************/ +/* fn scc_dealloc_slot() */ +/*****************************************************************************/ +scc_return_t scc_dealloc_slot(uint64_t owner_id, uint32_t slot) +{ + scc_return_t status; + unsigned long irq_flags; + int i; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, 0); + + if (status == SCC_RET_OK) { + scc_key_info[slot].owner_id = 0; + scc_key_info[slot].status = 0; /* unassign */ + + /* clear old info */ + for (i = 0; i < SCC_KEY_SLOT_SIZE; i += 4) { + SCC_WRITE_REGISTER(SCM_RED_MEMORY + + scc_key_info[slot].offset + i, 0); + SCC_WRITE_REGISTER(SCM_BLACK_MEMORY + + scc_key_info[slot].offset + i, 0); + } + pr_debug("SCC: Deallocated slot %d\n", slot); + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_load_slot() */ +/*****************************************************************************/ +/*! + * Load a value into a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param key_data Data to load into the slot + * @param key_length Length, in bytes, of @c key_data to copy to SCC. + * + * @return SCC_RET_OK on success. SCC_RET_FAIL will be returned if slot + * specified cannot be accessed for any reason, or SCC_RET_INSUFFICIENT_SPACE + * if @c key_length exceeds the size of the slot. + */ +scc_return_t +scc_load_slot(uint64_t owner_id, uint32_t slot, const uint8_t * key_data, + uint32_t key_length) +{ + scc_return_t status; + unsigned long irq_flags; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, key_length); + if ((status == SCC_RET_OK) && (key_data != NULL)) { + status = SCC_RET_FAIL; /* reset expectations */ + + if (key_length > SCC_KEY_SLOT_SIZE) { + pr_debug + ("SCC: scc_load_slot() rejecting key of %d bytes.\n", + key_length); + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + if (copy_to_scc(key_data, + SCM_RED_MEMORY + + scc_key_info[slot].offset, key_length, + NULL)) { + pr_debug("SCC: RED copy_to_scc() failed for" + " scc_load_slot()\n"); + } else { + if ((key_length % 4) != 0) { + uint32_t zeros = 0; + + /* zero-pad to get remainder bytes in correct place */ + copy_to_scc((uint8_t *) & zeros, + SCM_RED_MEMORY + + + scc_key_info[slot].offset + + key_length, + 4 - (key_length % 4), NULL); + } + status = SCC_RET_OK; + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} /* scc_load_slot */ + +scc_return_t +scc_read_slot(uint64_t owner_id, uint32_t slot, uint32_t key_length, + uint8_t * key_data) +{ + scc_return_t status; + unsigned long irq_flags; + + /* ACQUIRE LOCK to prevent others from using SCC crypto */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, key_length); + if ((status == SCC_RET_OK) && (key_data != NULL)) { + status = SCC_RET_FAIL; /* reset expectations */ + + if (key_length > SCC_KEY_SLOT_SIZE) { + pr_debug + ("SCC: scc_read_slot() rejecting key of %d bytes.\n", + key_length); + status = SCC_RET_INSUFFICIENT_SPACE; + } else { + if (copy_from_scc + (SCM_RED_MEMORY + scc_key_info[slot].offset, + key_data, key_length, NULL)) { + pr_debug("SCC: RED copy_from_scc() failed for" + " scc_read_slot()\n"); + } else { + status = SCC_RET_OK; + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} /* scc_read_slot */ + +/*****************************************************************************/ +/* fn scc_encrypt_slot() */ +/*****************************************************************************/ +/*! + * Encrypt the key data stored in a slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location to store result of encrypting RED data in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_encrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t slot_offset = + scc_key_info[slot].offset / SCC_BLOCK_SIZE_BYTES(); + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + SCC_WRITE_REGISTER(SCM_BLACK_START, slot_offset); + SCC_WRITE_REGISTER(SCM_RED_START, slot_offset); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_0, *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_1, + *(((uint32_t *) & owner_id) + 1)); + + /* Set modes and kick off the encryption */ + crypto_status = scc_do_crypto(length, + SCM_CONTROL_START_CIPHER | + SCM_CBC_MODE); + + if (crypto_status != 0) { + pr_debug("SCM encrypt red crypto failure: 0x%x\n", + crypto_status); + } else { + + /* Give blob back to caller */ + if (!copy_from_scc + (SCM_BLACK_MEMORY + scc_key_info[slot].offset, + black_data, length, NULL)) { + status = SCC_RET_OK; + pr_debug("SCC: Encrypted slot %d\n", slot); + } + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_decrypt_slot() */ +/*****************************************************************************/ +/*! + * Decrypt some black data and leave result in the slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param length Length, in bytes, of @c black_data + * @param black_data Location of data to dencrypt and store in slot + * + * @return SCC_RET_OK on success, SCC_RET_FAIL if slot specified cannot be + * accessed for any reason. + */ +scc_return_t scc_decrypt_slot(uint64_t owner_id, uint32_t slot, + uint32_t length, const uint8_t * black_data) +{ + unsigned long irq_flags; + scc_return_t status; + uint32_t crypto_status; + uint32_t slot_offset = + scc_key_info[slot].offset / SCC_BLOCK_SIZE_BYTES(); + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + spin_lock_irqsave(&scc_crypto_lock, irq_flags); + + status = verify_slot_access(owner_id, slot, length); + if (status == SCC_RET_OK) { + status = SCC_RET_FAIL; /* reset expectations */ + + /* Place black key in to BLACK RAM and set up the SCC */ + copy_to_scc(black_data, + SCM_BLACK_MEMORY + scc_key_info[slot].offset, + length, NULL); + + SCC_WRITE_REGISTER(SCM_BLACK_START, slot_offset); + SCC_WRITE_REGISTER(SCM_RED_START, slot_offset); + + /* Use OwnerID as CBC IV to tie Owner to data */ + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_0, *(uint32_t *) & owner_id); + SCC_WRITE_REGISTER(SCM_INIT_VECTOR_1, + *(((uint32_t *) & owner_id) + 1)); + + /* Set modes and kick off the decryption */ + crypto_status = scc_do_crypto(length, + SCM_CONTROL_START_CIPHER + | SCM_CBC_MODE | + SCM_DECRYPT_MODE); + + if (crypto_status != 0) { + pr_debug("SCM decrypt black crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + } + } + + spin_unlock_irqrestore(&scc_crypto_lock, irq_flags); + + return status; +} + +/*****************************************************************************/ +/* fn scc_get_slot_info() */ +/*****************************************************************************/ +/*! + * Determine address and value length for a give slot. + * + * @param owner_id Value of owner of slot + * @param slot Handle of slot + * @param address Location to store kernel address of slot data + * @param value_size_bytes Location to store allocated length of data in slot. + * May be NULL if value is not needed by caller. + * @param slot_size_bytes Location to store max length data in slot + * May be NULL if value is not needed by caller. + * + * @return SCC_RET_OK or error indication + */ +scc_return_t +scc_get_slot_info(uint64_t owner_id, uint32_t slot, uint32_t * address, + uint32_t * value_size_bytes, uint32_t * slot_size_bytes) +{ + scc_return_t status = verify_slot_access(owner_id, slot, 0); + + if (status == SCC_RET_OK) { + *address = + SCC_BASE + SCM_RED_MEMORY + scc_key_info[slot].offset; + if (value_size_bytes != NULL) { + *value_size_bytes = scc_key_info[slot].length; + } + if (slot_size_bytes != NULL) { + *slot_size_bytes = SCC_KEY_SLOT_SIZE; + } + } + + return status; +} + +/*****************************************************************************/ +/* fn scc_wait_completion() */ +/*****************************************************************************/ +/*! + * Poll looking for end-of-cipher indication. Only used + * if @c SCC_SCM_SLEEP is not defined. + * + * @internal + * + * On a Tahiti, crypto under 230 or so bytes is done after the first loop, all + * the way up to five sets of spins for 1024 bytes. (8- and 16-byte functions + * are done when we first look. Zeroizing takes one pass around. + */ +static void scc_wait_completion(void) +{ + int i = 0; + + /* check for completion by polling */ + while (!is_cipher_done() && (i++ < SCC_CIPHER_MAX_POLL_COUNT)) { + udelay(10); + } + pr_debug("SCC: Polled DONE %d times\n", i); +} /* scc_wait_completion() */ + +/*****************************************************************************/ +/* fn is_cipher_done() */ +/*****************************************************************************/ +/*! + * This function returns non-zero if SCM Status register indicates + * that a cipher has terminated or some other interrupt-generating + * condition has occurred. + */ +static int is_cipher_done(void) +{ + register uint32_t scm_status; + register int cipher_done; + + scm_status = SCC_READ_REGISTER(SCM_STATUS); + + /* + * Done when 'SCM is currently performing a function' bits are zero + */ + cipher_done = !(scm_status & (SCM_STATUS_ZEROIZING | + SCM_STATUS_CIPHERING)); + + return cipher_done; +} /* is_cipher_done() */ + +/*****************************************************************************/ +/* fn offset_within_smn() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SMN register set. + * + * @param[in] register_offset register offset of SMN. + * + * @return 1 if true, 0 if false (not within SMN) + */ +static inline int offset_within_smn(uint32_t register_offset) +{ + return register_offset >= SMN_STATUS && register_offset <= SMN_TIMER; +} + +/*****************************************************************************/ +/* fn offset_within_scm() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCM register set. + * + * @param[in] register_offset Register offset of SCM + * + * @return 1 if true, 0 if false (not within SCM) + */ +static inline int offset_within_scm(uint32_t register_offset) +{ + return (register_offset >= SCM_RED_START) + && (register_offset < scm_highest_memory_address); + /* Although this would cause trouble for zeroize testing, this change would + * close a security whole which currently allows any kernel program to access + * any location in RED RAM. Perhaps enforce in non-SCC_DEBUG compiles? + && (register_offset <= SCM_INIT_VECTOR_1); */ +} + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/*! + * Given the current SCM and SMN status, verify that access to the requested + * register should be OK. + * + * @param[in] register_offset register offset within SCC + * @param[in] smn_status recent value from #SMN_STATUS + * @param[in] scm_status recent value from #SCM_STATUS + * + * @return #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t +check_register_accessible(uint32_t register_offset, uint32_t smn_status, + uint32_t scm_status) +{ + int error_code = SCC_RET_FAIL; + + /* Verify that the register offset passed in is not among the verboten set + * if the SMN is in Fail mode. + */ + if (offset_within_smn(register_offset)) { + if ((smn_status & SMN_STATUS_STATE_MASK) == SMN_STATE_FAIL) { + if (!((register_offset == SMN_STATUS) || + (register_offset == SMN_COMMAND) || + (register_offset == SMN_DEBUG_DETECT_STAT))) { + pr_debug + ("SCC Driver: Note: Security State is in FAIL state.\n"); + } /* register not a safe one */ + else { + /* SMN is in FAIL, but register is a safe one */ + error_code = SCC_RET_OK; + } + } /* State is FAIL */ + else { + /* State is not fail. All registers accessible. */ + error_code = SCC_RET_OK; + } + } + /* offset within SMN */ + /* Not SCM register. Check for SCM busy. */ + else if (offset_within_scm(register_offset)) { + /* This is the 'cannot access' condition in the SCM */ + if ((scm_status & SCM_STATUS_BUSY) + /* these are always available - rest fail on busy */ + && !((register_offset == SCM_STATUS) || + (register_offset == SCM_ERROR_STATUS) || + (register_offset == SCM_INTERRUPT_CTRL) || + (register_offset == SCM_CONFIGURATION))) { + pr_debug + ("SCC Driver: Note: Secure Memory is in BUSY state.\n"); + } /* status is busy & register inaccessible */ + else { + error_code = SCC_RET_OK; + } + } + /* offset within SCM */ + return error_code; + +} /* check_register_accessible() */ + +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCC register set. + * + * @param[in] register_offset register offset of SMN. + * + * #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t check_register_offset(uint32_t register_offset) +{ + int return_value = SCC_RET_FAIL; + + /* Is it valid word offset ? */ + if (SCC_BYTE_OFFSET(register_offset) == 0) { + /* Yes. Is register within SCM? */ + if (offset_within_scm(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + /* Not in SCM. Now look within the SMN */ + else if (offset_within_smn(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + } + + return return_value; +} + +#ifdef SCC_REGISTER_DEBUG + +/*****************************************************************************/ +/* fn dbg_scc_read_register() */ +/*****************************************************************************/ +/*! + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +static uint32_t dbg_scc_read_register(uint32_t offset) +{ + uint32_t value; + + value = readl(scc_base + offset); + +#ifndef SCC_RAM_DEBUG /* print no RAM references */ + if ((offset < SCM_RED_MEMORY) || (offset >= scm_highest_memory_address)) { +#endif + pr_debug("SCC RD: 0x%4x : 0x%08x\n", offset, value); +#ifndef SCC_RAM_DEBUG + } +#endif + + return value; +} + +/*****************************************************************************/ +/* fn dbg_scc_write_register() */ +/*****************************************************************************/ +/* + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +static void dbg_scc_write_register(uint32_t offset, uint32_t value) +{ + +#ifndef SCC_RAM_DEBUG /* print no RAM references */ + if ((offset < SCM_RED_MEMORY) || (offset >= scm_highest_memory_address)) { +#endif + pr_debug("SCC WR: 0x%4x : 0x%08x\n", offset, value); +#ifndef SCC_RAM_DEBUG + } +#endif + + (void)writel(value, scc_base + offset); +} + +#endif /* SCC_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/mxc_scc_internals.h b/drivers/mxc/security/mxc_scc_internals.h new file mode 100644 index 000000000000..ffdbcf62514a --- /dev/null +++ b/drivers/mxc/security/mxc_scc_internals.h @@ -0,0 +1,498 @@ +/* + * Copyright 2004-2009 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 + */ +#ifndef __MXC_SCC_INTERNALS_H__ +#define __MXC_SCC_INTERNALS_H__ + +/*! + * @file mxc_scc_internals.h + * + * @brief This is intended to be the file which contains most or all of the code or + * changes need to port the driver. It also includes other definitions needed + * by the driver. + * + * This header file should only ever be included by scc_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. + * @li Some start-of-SCC consideration, such as SCC_BASE_ADDR + * + * Some changes which could be made when porting this driver: + * #SCC_SPIN_COUNT + * + * @ingroup MXCSCC + */ +#if 0 +#include <linux/version.h> /* Current version Linux kernel */ +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/spinlock.h> +#include <linux/interrupt.h> /* IRQ / interrupt definitions */ +#include <linux/io.h> /* ioremap() */ +#endif +#include <linux/mxc_scc_driver.h> + +/* Get handle on certain per-platform symbols */ +#ifdef TAHITI +#include <asm/arch/mx2.h> + +/* + * Mark the SCC as always there... as Tahiti is not officially supported by + * driver. Porting opportunity. + */ +#define SCC_ENABLED() (1) + +#elif defined(MXC) + +#include <mach/iim.h> +#include <mach/mxc_scc.h> + +#ifdef SCC_FUSE + +/* + * This macro is used to determine whether the SCC is enabled/available + * on the platform. This macro may need to be ported. + */ +#define SCC_ENABLED() ((SCC_FUSE & MXC_IIMHWV1_SCC_DISABLE) == 0) + +#else + +#define SCC_ENABLED() (1) + +#endif + +#else /* neither TAHITI nor MXC */ + +#error Do not understand target architecture + +#endif /* TAHITI */ + +/* Temporarily define compile-time flags to make Doxygen happy. */ +#ifdef DOXYGEN_HACK +/*! @addtogroup scccompileflags */ +/*! @{ */ + +/*! @def NO_SMN_INTERRUPT + * The SMN interrupt is not wired to the CPU at all. + */ +#define NO_SMN_INTERRUPT + +/*! + * Register an interrupt handler for the SMN as well as + * the SCM. In some implementations, the SMN is not connected at all (see + * #NO_SMN_INTERRUPT), and in others, it is on the same interrupt line as the + * SCM. When defining this flag, the SMN interrupt should be on a separate + * line from the SCM interrupt. + */ + +#define USE_SMN_INTERRUPT + +/*! + * Turn on generation of run-time operational, debug, and error messages + */ +#define SCC_DEBUG + +/*! + * Turn on generation of run-time logging of access to the SCM and SMN + * registers. + */ +#define SCC_REGISTER_DEBUG + +/*! + * Turn on generation of run-time logging of access to the SCM Red and + * Black memories. Will only work if #SCC_REGISTER_DEBUG is also defined. + */ +#define SCC_RAM_DEBUG + +/*! + * If the driver finds the SCC in HEALTH_CHECK state, go ahead and + * run a quick ASC to bring it to SECURE state. + */ +#define SCC_BRINGUP + +/*! + * Expected to come from platform header files or compile command line. + * This symbol must be the address of the SCC + */ +#define SCC_BASE + +/*! + * This must be the interrupt line number of the SCM interrupt. + */ +#define INT_SCM + +/*! + * if #USE_SMN_INTERRUPT is defined, this must be the interrupt line number of + * the SMN interrupt. + */ +#define INT_SMN + +/*! + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS + +/*! + * Make sure that this flag is defined if compiling for a Little-Endian + * platform. Linux Kernel builds provide this flag. + */ +#define __LITTLE_ENDIAN + +/*! + * Make sure that this flag is defined if compiling for a Big-Endian platform. + * Linux Kernel builds provide this flag. + */ +#define __BIG_ENDIAN + +/*! + * Read a 32-bit register value from a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param offset Bus address of register to be read + * + * @return The value of the register + */ +#define readl(offset) + +/*! + * Write a 32-bit value to a register in a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param value The 32-bit value to store + * @param offset Bus address of register to be written + * + * return (none) + */ +#define writel(value,offset) + + /*! @} *//* end group scccompileflags */ + +#endif /* DOXYGEN_HACK */ + +/*! + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS 20 + +#ifndef SCC_KEY_SLOTS +#define SCC_KEY_SLOTS 0 + +#else + +#if (SCC_KEY_SLOTS < 0) || (SCC_KEY_SLOTS > 20) +#error Bad value for SCC_KEY_SLOTS +#endif + +/*! + * Maximum length of key/secret value which can be stored in SCC. + */ +#define SCC_MAX_KEY_SIZE 32 + +/*! + * This is the size, in bytes, of each key slot, and therefore the maximum size + * of the wrapped key. + */ +#define SCC_KEY_SLOT_SIZE 32 + +/*! + * This is the offset into each RAM of the base of the area which is + * not used for Stored Keys. + */ +#define SCM_NON_RESERVED_OFFSET (SCC_KEY_SLOTS * SCC_KEY_SLOT_SIZE) + +#endif + +/* These come for free with Linux, but may need to be set in a port. */ +#ifndef __BIG_ENDIAN +#ifndef __LITTLE_ENDIAN +#error One of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#else +#ifdef __LITTLE_ENDIAN +#error Exactly one of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#endif + +#ifndef SCC_CALLBACK_SIZE +/*! The number of function pointers which can be stored in #scc_callbacks. + * Defaults to 4, can be overridden with compile-line argument. + */ +#define SCC_CALLBACK_SIZE 4 +#endif + +/*! Initial CRC value for CCITT-CRC calculation. */ +#define CRC_CCITT_START 0xFFFF + +#ifdef TAHITI + +/*! + * The SCC_BASE has to be SMN_BASE_ADDR on TAHITI, as the banks of + * registers are swapped in place. + */ +#define SCC_BASE SMN_BASE_ADDR + +/*! The interrupt number for the SCC (SCM only!) on Tahiti */ +#define INT_SCC_SCM 62 + +/*! Tahiti does not have the SMN interrupt wired to the CPU. */ +#define NO_SMN_INTERRUPT + +#endif /* TAHITI */ + +/*! Number of times to spin between polling of SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_CIPHER_MAX_POLL_COUNT. */ +#define SCC_SPIN_COUNT 1000 + +/*! Number of times to polling SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_SPIN_COUNT. */ +#define SCC_CIPHER_MAX_POLL_COUNT 100 + +/*! + * @def SCC_READ_REGISTER + * Read a 32-bit value from an SCC register. Macro which depends upon + * #scc_base. Linux readl()/writel() macros operate on 32-bit quantities, as + * do SCC register reads/writes. + * + * @param offset Register offset within SCC. + * + * @return The value from the SCC's register. + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_READ_REGISTER(offset) __raw_readl(scc_base+(offset)) +#else +#define SCC_READ_REGISTER(offset) dbg_scc_read_register(offset) +#endif + +/*! + * Write a 32-bit value to an SCC register. Macro depends upon #scc_base. + * Linux readl()/writel() macros operate on 32-bit quantities, as do SCC + * register reads/writes. + * + * @param offset Register offset within SCC. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_WRITE_REGISTER(offset,value) (void)__raw_writel(value, scc_base+(offset)) +#else +#define SCC_WRITE_REGISTER(offset,value) dbg_scc_write_register(offset, value) +#endif + +/*! + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + +/*! + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + +/*! + * Determine number of bytes in an SCC block + * + * @return Bytes / block + */ +#define SCC_BLOCK_SIZE_BYTES() scc_configuration.block_size_bytes + +/*! + * Maximum number of additional bytes which may be added in CRC+padding mode. + */ +#define PADDING_BUFFER_MAX_BYTES (CRC_SIZE_BYTES + sizeof(scc_block_padding)) + +/*! + * Shorthand (clearer, anyway) for number of bytes in a CRC. + */ +#define CRC_SIZE_BYTES (sizeof(crc_t)) + +/*! + * The polynomial used in CCITT-CRC calculation + */ +#define CRC_POLYNOMIAL 0x1021 + +/*! + * Calculate CRC on one byte of data + * + * @param[in,out] running_crc A value of type crc_t where CRC is kept. This + * must be an rvalue and an lvalue. + * @param[in] byte_value The byte (uint8_t, char) to be put in the CRC + * + * @return none + */ +#define CALC_CRC(byte_value,running_crc) { \ + uint8_t data; \ + data = (0xff&(byte_value)) ^ (running_crc >> 8); \ + running_crc = scc_crc_lookup_table[data] ^ (running_crc << 8); \ +} + +/*! Value of 'beginning of padding' marker in driver-provided padding */ +#define SCC_DRIVER_PAD_CHAR 0x80 + +/*! Name of the driver. Used (on Linux, anyway) when registering interrupts */ +#define SCC_DRIVER_NAME "scc" + +/* Port -- these symbols are defined in Linux 2.6 and later. They are defined + * here for backwards compatibility because this started life as a 2.4 + * driver, and as a guide to portation to other platforms. + */ + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + +#define irqreturn_t void /* Return type of an interrupt handler */ + +#define IRQ_HANDLED /* Would be '1' for handled -- as in return IRQ_HANDLED; */ + +#define IRQ_NONE /* would be '0' for not handled -- as in return IRQ_NONE; */ + +#define IRQ_RETVAL(x) /* Return x==0 (not handled) or non-zero (handled) */ + +#endif /* LINUX earlier than 2.5 */ + +/* These are nice to have around */ +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/*! Provide a typedef for the CRC which can be used in encrypt/decrypt */ +typedef uint16_t crc_t; + +/*! Gives high-level view of state of the SCC */ +enum scc_status { + SCC_STATUS_INITIAL, /*!< State of driver before ever checking */ + SCC_STATUS_CHECKING, /*!< Transient state while driver loading */ + SCC_STATUS_UNIMPLEMENTED, /*!< SCC is non-existent or unuseable */ + SCC_STATUS_OK, /*!< SCC is in Secure or Default state */ + SCC_STATUS_FAILED /*!< In Failed state */ +}; + +/*! + * Information about a key slot. + */ +struct scc_key_slot { + uint64_t owner_id; /*!< Access control value. */ + uint32_t length; /*!< Length of value in slot. */ + uint32_t offset; /*!< Offset of value from start of each RAM. */ + uint32_t status; /*!< 0 = unassigned, 1 = assigned. */ +}; + +/* Forward-declare a number routines which are not part of user api */ +static int scc_init(void); +static void scc_cleanup(void); + +/* Forward defines of internal functions */ +OS_DEV_ISR(scc_irq); +/*! Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void); +static uint32_t copy_to_scc(const uint8_t * from, uint32_t to, + unsigned long count_bytes, uint16_t * crc); +static uint32_t copy_from_scc(const uint32_t from, uint8_t * to, + unsigned long count_bytes, uint16_t * crc); +static scc_return_t scc_strip_padding(uint8_t * from, + unsigned *count_bytes_stripped); +static uint32_t scc_update_state(void); +static void scc_init_ccitt_crc(void); +static uint32_t scc_grab_config_values(void); +static int setup_interrupt_handling(void); +/*! + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +static scc_return_t scc_encrypt(uint32_t count_in_bytes, + const uint8_t * data_in, + uint32_t scm_control, uint8_t * data_out, + int add_crc, unsigned long *count_out_bytes); + +/*! + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +static scc_return_t scc_decrypt(uint32_t count_in_bytes, + const uint8_t * data_in, + uint32_t scm_control, uint8_t * data_out, + int verify_crc, unsigned long *count_out_bytes); + +static void scc_wait_completion(void); +static int is_cipher_done(void); +static scc_return_t check_register_accessible(uint32_t offset, + uint32_t smn_status, + uint32_t scm_status); +static scc_return_t check_register_offset(uint32_t offset); + +#ifdef SCC_REGISTER_DEBUG +static uint32_t dbg_scc_read_register(uint32_t offset); +static void dbg_scc_write_register(uint32_t offset, uint32_t value); +#endif + +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(scc_get_configuration); +EXPORT_SYMBOL(scc_zeroize_memories); +EXPORT_SYMBOL(scc_crypt); +EXPORT_SYMBOL(scc_set_sw_alarm); +EXPORT_SYMBOL(scc_monitor_security_failure); +EXPORT_SYMBOL(scc_stop_monitoring_security_failure); +EXPORT_SYMBOL(scc_read_register); +EXPORT_SYMBOL(scc_write_register); +EXPORT_SYMBOL(scc_alloc_slot); +EXPORT_SYMBOL(scc_dealloc_slot); +EXPORT_SYMBOL(scc_load_slot); +EXPORT_SYMBOL(scc_encrypt_slot); +EXPORT_SYMBOL(scc_decrypt_slot); +EXPORT_SYMBOL(scc_get_slot_info); + +/* Tell Linux where to invoke driver at boot/module load time */ +module_init(scc_init); +/* Tell Linux where to invoke driver on module unload */ +module_exit(scc_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Device Driver for SCC (SMN/SCM)"); + +#endif /* __MXC_SCC_INTERNALS_H__ */ diff --git a/drivers/mxc/security/rng/Makefile b/drivers/mxc/security/rng/Makefile new file mode 100644 index 000000000000..7d3332e2e675 --- /dev/null +++ b/drivers/mxc/security/rng/Makefile @@ -0,0 +1,35 @@ +# Makefile for the Linux RNG Driver +# +# This makefile works within a kernel driver tree + + # Makefile for rng_driver + + +# Possible configurable paramters +CFG_RNG += -DRNGA_MAX_REQUEST_SIZE=32 + +#DBG_RNGA = -DRNGA_DEBUG +#DBG_RNGA += -DRNGA_REGISTER_DEBUG +#DBG_RNGA += -DRNGA_ENTROPY_DEBUG + +EXTRA_CFLAGS = -DLINUX_KERNEL $(CFG_RNG) $(DBG_RNG) + + +ifeq ($(CONFIG_MXC_RNG_TEST_DRIVER),y) +EXTRA_CFLAGS += -DRNG_REGISTER_PEEK_POKE +endif +ifeq ($(CONFIG_RNG_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + + +EXTRA_CFLAGS += -Idrivers/mxc/security/rng/include -Idrivers/mxc/security/sahara2/include + +obj-$(CONFIG_MXC_SECURITY_RNG) += shw.o +#shw-objs := shw_driver.o shw_memory_mapper.o ../sahara2/fsl_shw_keystore.o +shw-objs := shw_driver.o shw_memory_mapper.o ../sahara2/fsl_shw_keystore.o \ + fsl_shw_sym.o fsl_shw_wrap.o shw_dryice.o des_key.o \ + shw_hash.o shw_hmac.o + +obj-$(CONFIG_MXC_SECURITY_RNG) += rng.o +rng-objs := rng_driver.o diff --git a/drivers/mxc/security/rng/des_key.c b/drivers/mxc/security/rng/des_key.c new file mode 100644 index 000000000000..62ff9b89eb3e --- /dev/null +++ b/drivers/mxc/security/rng/des_key.c @@ -0,0 +1,385 @@ +/* + * Copyright 2009 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 des_key.c + * + * This file implements the function #fsl_shw_permute1_bytes(). + * + * The code was lifted from crypto++ v5.5.2, which is public domain code. The + * code to handle words instead of bytes was extensively modified from the byte + * version and then converted to handle one to three keys at once. + * + */ + +#include "shw_driver.h" +#ifdef DIAG_SECURITY_FUNC +#include "apihelp.h" +#endif + +#ifndef __KERNEL__ +#include <asm/types.h> +#include <linux/byteorder/little_endian.h> /* or whichever is proper for target arch */ +#endif + +#ifdef DEBUG +#undef DEBUG /* TEMPORARY */ +#endif + +#if defined(DEBUG) || defined(SELF_TEST) +static void DUMP_BYTES(const char *label, const uint8_t * data, int len) +{ + int i; + + printf("%s: ", label); + for (i = 0; i < len; i++) { + printf("%02X", data[i]); + if ((i % 8 == 0) && (i != 0)) { + printf("_"); /* key separator */ + } + } + printf("\n"); +} + +static void DUMP_WORDS(const char *label, const uint32_t * data, int len) +{ + int i, j; + + printf("%s: ", label); + /* Dump the words in reverse order, so that they are intelligible */ + for (i = len - 1; i >= 0; i--) { + for (j = 3; j >= 0; j--) { + uint32_t word = data[i]; + printf("%02X", (word >> ((j * 8)) & 0xff)); + if ((i != 0) && ((((i) * 4 + 5 + j) % 7) == 5)) + printf("_"); /* key separator */ + } + printf("|"); /* word separator */ + } + printf("\n"); +} +#else +#define DUMP_BYTES(label, data,len) +#define DUMP_WORDS(label, data,len) +#endif + +/*! + * permuted choice table (key) + * + * Note that this table has had one subtracted from each element so that the + * code doesn't have to do it. + */ +static const uint8_t pc1[] = { + 56, 48, 40, 32, 24, 16, 8, + 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, + 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, + 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, + 20, 12, 4, 27, 19, 11, 3, +}; + +/*! bit 0 is left-most in byte */ +static const int bytebit[] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +/*! + * Convert a 3-key 3DES key into the first-permutation 168-bit version. + * + * This is the format of the input key: + * + * @verbatim + BIT: |191 128|127 64|63 0| + BYTE: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | + KEY: | 0 | 1 | 2 | + @endverbatim + * + * This is the format of the output key: + * + * @verbatim + BIT: |167 112|111 56|55 0| + BYTE: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | + KEY: | 1 | 2 | 3 | + @endverbatim + * + * @param[in] key bytes of 3DES key + * @param[out] permuted_key 21 bytes of permuted key + * @param[in] key_count How many DES keys (2 or 3) + */ +void fsl_shw_permute1_bytes(const uint8_t * key, uint8_t * permuted_key, + int key_count) +{ + int i; + int j; + int l; + int m; + + DUMP_BYTES("Input key", key, 8 * key_count); + + /* For each individual sub-key */ + for (i = 0; i < 3; i++) { + DUMP_BYTES("(key)", key, 8); + memset(permuted_key, 0, 7); + /* For each bit of key */ + for (j = 0; j < 56; j++) { /* convert pc1 to bits of key */ + l = pc1[j]; /* integer bit location */ + m = l & 07; /* find bit */ + permuted_key[j >> 3] |= (((key[l >> 3] & /* find which key byte l is in */ + bytebit[m]) /* and which bit of that byte */ + ? 0x80 : 0) >> (j % 8)); /* and store 1-bit result */ + } + switch (i) { + case 0: + if (key_count != 1) + key += 8; /* move on to second key */ + break; + case 1: + if (key_count == 2) + key -= 8; /* go back to first key */ + else if (key_count == 3) + key += 8; /* move on to third key */ + break; + default: + break; + } + permuted_key += 7; + } + DUMP_BYTES("Output key (bytes)", permuted_key - 21, 21); +} + +#ifdef SELF_TEST +const uint8_t key1_in[] = { + /* FE01FE01FE01FE01_01FE01FE01FE01FE_FEFE0101FEFE0101 */ + 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, + 0xFE, 0xFE, 0x01, 0x01, 0xFE, 0xFE, 0x01, 0x01 +}; + +const uint32_t key1_word_in[] = { + 0xFE01FE01, 0xFE01FE01, + 0x01FE01FE, 0x01FE01FE, + 0xFEFE0101, 0xFEFE0101 +}; + +uint8_t exp_key1_out[] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33 +}; + +uint32_t exp_word_key1_out[] = { + 0x33333333, 0xAA333333, 0xAAAAAAAA, 0x5555AAAA, + 0x55555555, 0x00000055, +}; + +const uint8_t key2_in[] = { + 0xEF, 0x10, 0xBB, 0xA4, 0x23, 0x49, 0x42, 0x58, + 0x01, 0x28, 0x01, 0x4A, 0x10, 0xE4, 0x03, 0x59, + 0xFE, 0x84, 0x30, 0x29, 0x8E, 0xF1, 0x10, 0x5A +}; + +const uint32_t key2_word_in[] = { + 0xEF10BBA4, 0x23494258, + 0x0128014A, 0x10E40359, + 0xFE843029, 0x8EF1105A +}; + +uint8_t exp_key2_out[] = { + 0x0D, 0xE1, 0x1D, 0x85, 0x50, 0x9A, 0x56, 0x20, + 0xA8, 0x22, 0x94, 0x82, 0x08, 0xA0, 0x33, 0xA1, + 0x2D, 0xE9, 0x11, 0x39, 0x95 +}; + +uint32_t exp_word_key2_out[] = { + 0xE9113995, 0xA033A12D, 0x22948208, 0x9A5620A8, + 0xE11D8550, 0x0000000D +}; + +const uint8_t key3_in[] = { + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, + 0x89, 0x77, 0x73, 0x0C, 0xA0, 0x05, 0x41, 0x69, + 0xB3, 0x7C, 0x98, 0xD8, 0xC9, 0x35, 0x57, 0x19 +}; + +const uint32_t key3_word_in[] = { + 0xEF10BBA4, 0x23494258, + 0x0128014A, 0x10E40359, + 0xFE843029, 0x8EF1105A +}; + +uint8_t exp_key3_out[] = { + 0x02, 0x3E, 0x93, 0xA7, 0x9F, 0x18, 0xF1, 0x11, + 0xC6, 0x96, 0x00, 0x62, 0xA8, 0x96, 0x02, 0x3E, + 0x93, 0xA7, 0x9F, 0x18, 0xF1 +}; + +uint32_t exp_word_key3_out[] = { + 0xE9113995, 0xA033A12D, 0x22948208, 0x9A5620A8, + 0xE11D8550, 0x0000000D +}; + +const uint8_t key4_in[] = { + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, + 0x89, 0x77, 0x73, 0x0C, 0xA0, 0x05, 0x41, 0x69, +}; + +const uint32_t key4_word_in[] = { + 0xEF10BBA4, 0x23494258, + 0x0128014A, 0x10E40359, + 0xFE843029, 0x8EF1105A +}; + +const uint8_t key5_in[] = { + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, + 0x89, 0x77, 0x73, 0x0C, 0xA0, 0x05, 0x41, 0x69, + 0x3F, 0xE9, 0x49, 0x4B, 0x67, 0x57, 0x07, 0x3C, +}; + +uint8_t exp_key4_out[] = { + 0x0D, 0xE1, 0x1D, 0x85, 0x50, 0x9A, 0x56, 0x20, + 0xA8, 0x22, 0x94, 0x82, 0x08, 0xA0, 0x33, 0xA1, + 0x2D, 0xE9, 0x11, 0x39, 0x95 +}; + +uint32_t exp_word_key4_out[] = { + 0xE9113995, 0xA033A12D, 0x22948208, 0x9A5620A8, + 0xE11D8550, 0x0000000D +}; + +const uint8_t key6_in[] = { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +uint8_t exp_key6_out[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +uint32_t exp_word_key6_out[] = { + 0x00000000, 0x0000000, 0x0000000, 0x00000000, + 0x00000000, 0x0000000 +}; + +const uint8_t key7_in[] = { + /* 01FE01FE01FE01FE_FE01FE01FE01FE01_0101FEFE0101FEFE */ + /* 0101FEFE0101FEFE_FE01FE01FE01FE01_01FE01FE01FE01FE */ + 0x01, 0x01, 0xFE, 0xFE, 0x01, 0x01, 0xFE, 0xFE, + 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, + 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, 0x01, 0xFE, +}; + +uint8_t exp_key7_out[] = { + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa +}; + +uint32_t exp_word_key7_out[] = { + 0xcccccccc, 0x55cccccc, 0x55555555, 0xaaaa5555, + 0xaaaaaaaa, 0x000000aa +}; + +int run_test(const uint8_t * key_in, + const int key_count, + const uint32_t * key_word_in, + const uint8_t * exp_bytes_key_out, + const uint32_t * exp_word_key_out) +{ + uint8_t key_out[22]; + uint32_t word_key_out[6]; + int failed = 0; + + memset(key_out, 0x42, 22); + fsl_shw_permute1_bytes(key_in, key_out, key_count); + if (memcmp(key_out, exp_bytes_key_out, 21) != 0) { + printf("bytes_to_bytes: ERROR: \n"); + DUMP_BYTES("key_in", key_in, 8 * key_count); + DUMP_BYTES("key_out", key_out, 21); + DUMP_BYTES("exp_out", exp_bytes_key_out, 21); + failed |= 1; + } else if (key_out[21] != 0x42) { + printf("bytes_to_bytes: ERROR: Buffer overflow 0x%02x\n", + (int)key_out[21]); + } else { + printf("bytes_to_bytes: OK\n"); + } +#if 0 + memset(word_key_out, 0x42, 21); + fsl_shw_permute1_bytes_to_words(key_in, word_key_out, key_count); + if (memcmp(word_key_out, exp_word_key_out, 21) != 0) { + printf("bytes_to_words: ERROR: \n"); + DUMP_BYTES("key_in", key_in, 8 * key_count); + DUMP_WORDS("key_out", word_key_out, 6); + DUMP_WORDS("exp_out", exp_word_key_out, 6); + failed |= 1; + } else { + printf("bytes_to_words: OK\n"); + } + + if (key_word_in != NULL) { + memset(word_key_out, 0x42, 21); + fsl_shw_permute1_words_to_words(key_word_in, word_key_out); + if (memcmp(word_key_out, exp_word_key_out, 21) != 0) { + printf("words_to_words: ERROR: \n"); + DUMP_BYTES("key_in", key_in, 24); + DUMP_WORDS("key_out", word_key_out, 6); + DUMP_WORDS("exp_out", exp_word_key_out, 6); + failed |= 1; + } else { + printf("words_to_words: OK\n"); + } + } +#endif + + return failed; +} /* end fn run_test */ + +int main() +{ + int failed = 0; + + printf("key1\n"); + failed |= + run_test(key1_in, 3, key1_word_in, exp_key1_out, exp_word_key1_out); + printf("\nkey2\n"); + failed |= + run_test(key2_in, 3, key2_word_in, exp_key2_out, exp_word_key2_out); + printf("\nkey3\n"); + failed |= run_test(key3_in, 3, NULL, exp_key3_out, exp_word_key3_out); + printf("\nkey4\n"); + failed |= run_test(key4_in, 2, NULL, exp_key4_out, exp_word_key4_out); + printf("\nkey5\n"); + failed |= run_test(key5_in, 3, NULL, exp_key4_out, exp_word_key4_out); + printf("\nkey6 - 3\n"); + failed |= run_test(key6_in, 3, NULL, exp_key6_out, exp_word_key6_out); + printf("\nkey6 - 2\n"); + failed |= run_test(key6_in, 2, NULL, exp_key6_out, exp_word_key6_out); + printf("\nkey6 - 1\n"); + failed |= run_test(key6_in, 1, NULL, exp_key6_out, exp_word_key6_out); + printf("\nkey7\n"); + failed |= run_test(key7_in, 3, NULL, exp_key7_out, exp_word_key7_out); + printf("\n"); + + if (failed != 0) { + printf("TEST FAILED\n"); + } + return failed; +} + +#endif /* SELF_TEST */ diff --git a/drivers/mxc/security/rng/fsl_shw_hash.c b/drivers/mxc/security/rng/fsl_shw_hash.c new file mode 100644 index 000000000000..60572862a0fa --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_hash.c @@ -0,0 +1,84 @@ +/* + * Copyright 2005-2009 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 fsl_shw_hash.c + * + * This file implements Cryptographic Hashing functions of the FSL SHW API + * for Sahara. This does not include HMAC. + */ + +#include "shw_driver.h" + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)hash_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return ret; +} diff --git a/drivers/mxc/security/rng/fsl_shw_hmac.c b/drivers/mxc/security/rng/fsl_shw_hmac.c new file mode 100644 index 000000000000..4d2a104afcd8 --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_hmac.c @@ -0,0 +1,83 @@ +/* + * Copyright 2005-2009 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 fsl_shw_hmac.c + * + * This file implements Hashed Message Authentication Code functions of the FSL + * SHW API. + */ + +#include "shw_driver.h" + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Get the precompute information + * + * + * @param user_ctx + * @param key_info + * @param hmac_ctx + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + + return status; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Get the hmac + * + * + * @param user_ctx Info for acquiring memory + * @param key_info + * @param hmac_ctx + * @param msg + * @param length + * @param result + * @param result_len + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return status; +} diff --git a/drivers/mxc/security/rng/fsl_shw_rand.c b/drivers/mxc/security/rng/fsl_shw_rand.c new file mode 100644 index 000000000000..aa4d426c70fe --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_rand.c @@ -0,0 +1,122 @@ +/* + * Copyright 2005-2009 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 fsl_shw_rand.c + * + * This file implements Random Number Generation functions of the FSL SHW API + * in USER MODE for talking to a standalone RNGA/RNGC device driver. + * + * It contains the fsl_shw_get_random() and fsl_shw_add_entropy() functions. + * + * These routines will build a request block and pass it to the SHW driver. + */ + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <signal.h> + +#ifdef FSL_DEBUG +#include <stdio.h> +#include <errno.h> +#include <string.h> +#endif /* FSL_DEBUG */ + +#include "shw_driver.h" + +extern fsl_shw_return_t validate_uco(fsl_shw_uco_t * uco); + +#if defined(FSL_HAVE_RNGA) || defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC) + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* perform a sanity check / update uco */ + ret = validate_uco(user_ctx); + if (ret == FSL_RETURN_OK_S) { + struct get_random_req *req = malloc(sizeof(*req)); + + if (req == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + } else { + + init_req(&req->hdr, user_ctx); + req->size = length; + req->random = data; + + ret = + send_req(SHW_USER_REQ_GET_RANDOM, &req->hdr, + user_ctx); + } + } + + return ret; +} + +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* perform a sanity check on the uco */ + ret = validate_uco(user_ctx); + if (ret == FSL_RETURN_OK_S) { + struct add_entropy_req *req = malloc(sizeof(*req)); + + if (req == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + } else { + init_req(&req->hdr, user_ctx); + req->size = length; + req->entropy = data; + + ret = + send_req(SHW_USER_REQ_ADD_ENTROPY, &req->hdr, + user_ctx); + } + } + + return ret; +} + +#else /* no H/W RNG block */ + +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} + +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} +#endif diff --git a/drivers/mxc/security/rng/fsl_shw_sym.c b/drivers/mxc/security/rng/fsl_shw_sym.c new file mode 100644 index 000000000000..bbeb1e24bc48 --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_sym.c @@ -0,0 +1,317 @@ +/* + * Copyright 2009 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 fsl_shw_sym.c + * + * This file implements the Symmetric Cipher functions of the FSL SHW API. Its + * features are limited to what can be done with the combination of SCC and + * DryIce. + */ +#include "fsl_platform.h" +#include "shw_driver.h" + +#if defined(__KERNEL__) && defined(FSL_HAVE_DRYICE) + +#include "../dryice.h" +#include <linux/mxc_scc_driver.h> +#ifdef DIAG_SECURITY_FUNC +#include "apihelp.h" +#endif + +#include <diagnostic.h> + +#define SYM_DECRYPT 0 +#define SYM_ENCRYPT 1 + +extern fsl_shw_return_t shw_convert_pf_key(fsl_shw_pf_key_t shw_pf_key, + di_key_t * di_keyp); + +/*! 'Initial' IV for presence of FSL_SYM_CTX_LOAD flag */ +static uint8_t zeros[8] = { + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/*! + * Common function for encryption and decryption + * + * This is for a device with DryIce. + * + * A key must either refer to a 'pure' HW key, or, if PRG or PRG_IIM, + * established, then that key will be programmed. Then, the HW_key in the + * object will be selected. After this setup, the ciphering will be performed + * by calling the SCC driver.. + * + * The function 'releases' the reservations before it completes. + */ +fsl_shw_return_t do_symmetric(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + int encrypt, + uint32_t length, + const uint8_t * in, uint8_t * out) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + int key_selected = 0; + uint8_t *iv = NULL; + unsigned long count_out = length; + di_key_t di_key = DI_KEY_PK; /* default for user key */ + di_key_t di_key_orig; /* currently selected key */ + di_key_t selected_key = -1; + di_return_t di_code; + scc_return_t scc_code; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* No software keys allowed */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = FSL_RETURN_BAD_FLAG_S; + } + + /* The only algorithm the SCC supports */ + if (key_info->algorithm != FSL_KEY_ALG_TDES) { + ret = FSL_RETURN_BAD_ALGORITHM_S; + goto out; + } + + /* Validate key length */ + if ((key_info->key_length != 16) + && (key_info->key_length != 21) + && (key_info->key_length != 24)) { + ret = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + /* Validate data is multiple of DES/TDES block */ + if ((length & 7) != 0) { + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + goto out; + } + + /* Do some setup according to where the key lives */ + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + if ((key_info->pf_key != FSL_SHW_PF_KEY_PRG) + && (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG)) { + ret = FSL_RETURN_ERROR_S; + } + } else if (key_info->flags & FSL_SKO_KEY_PRESENT) { + ret = FSL_RETURN_BAD_FLAG_S; + } else if (key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY) { + /* + * No key present or established, just refer to HW + * as programmed. + */ + } else { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Now make proper selection */ + ret = shw_convert_pf_key(key_info->pf_key, &di_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Determine the current DI key selection */ + di_code = dryice_check_key(&di_key_orig); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not save current DI key state: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + /* If the requested DI key is already selected, don't re-select it. */ + if (di_key != di_key_orig) { + di_code = dryice_select_key(di_key, 0); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Error from select_key: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_INTERNAL_ERROR_S; + goto out; + } + } + key_selected = 1; + + /* Verify that we are using the key we want */ + di_code = dryice_check_key(&selected_key); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Error from check_key: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_INTERNAL_ERROR_S; + goto out; + } + + if (di_key != selected_key) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Wrong key in use: %d instead of %d\n\n", + selected_key, di_key); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + if (sym_ctx->mode == FSL_SYM_MODE_CBC) { + if ((sym_ctx->flags & FSL_SYM_CTX_LOAD) + && !(sym_ctx->flags & FSL_SYM_CTX_INIT)) { + iv = sym_ctx->context; + } else if ((sym_ctx->flags & FSL_SYM_CTX_INIT) + && !(sym_ctx->flags & FSL_SYM_CTX_LOAD)) { + iv = zeros; + } else { + /* Exactly one must be set! */ + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + } + + /* Now run the data through the SCC */ + scc_code = scc_crypt(length, in, iv, + encrypt ? SCC_ENCRYPT : SCC_DECRYPT, + (sym_ctx->mode == FSL_SYM_MODE_ECB) + ? SCC_ECB_MODE : SCC_CBC_MODE, + SCC_VERIFY_MODE_NONE, out, &count_out); + if (scc_code != SCC_RET_OK) { + ret = FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("scc_code from scc_crypt() is %d\n", scc_code); +#endif + goto out; + } + + if ((sym_ctx->mode == FSL_SYM_MODE_CBC) + && (sym_ctx->flags & FSL_SYM_CTX_SAVE)) { + /* Save the context for the caller */ + if (encrypt) { + /* Last ciphertext block ... */ + memcpy(sym_ctx->context, out + length - 8, 8); + } else { + /* Last ciphertext block ... */ + memcpy(sym_ctx->context, in + length - 8, 8); + } + } + + ret = FSL_RETURN_OK_S; + + out: + if (key_selected) { + (void)dryice_release_key_selection(); + } + + return ret; +} + +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +/*! + * Compute symmetric encryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_ENCRYPT, + length, pt, ct); + + return ret; +} + +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +/*! + * Compute symmetric decryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_DECRYPT, + length, ct, pt); + + return ret; +} + +#else /* __KERNEL__ && DRYICE */ + +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)pt; + (void)ct; + + return FSL_RETURN_ERROR_S; +} + +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)ct; + (void)pt; + + return FSL_RETURN_ERROR_S; +} + +#endif /* __KERNEL__ and DRYICE */ diff --git a/drivers/mxc/security/rng/fsl_shw_wrap.c b/drivers/mxc/security/rng/fsl_shw_wrap.c new file mode 100644 index 000000000000..05f812c534e0 --- /dev/null +++ b/drivers/mxc/security/rng/fsl_shw_wrap.c @@ -0,0 +1,1301 @@ +/* + * Copyright 2009 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 fsl_shw_wrap.c + * + * This file implements Key-Wrap (Black Key) and Key Establishment functions of + * the FSL SHW API for the SHW (non-SAHARA) driver. + * + * This is the Black Key information: + * + * <ul> + * <li> Ownerid is an 8-byte, user-supplied, value to keep KEY + * confidential.</li> + * <li> KEY is a 1-32 byte value which starts in SCC RED RAM before + * wrapping, and ends up there on unwrap. Length is limited because of + * size of SCC1 RAM.</li> + * <li> KEY' is the encrypted KEY</li> + * <li> LEN is a 1-byte (for now) byte-length of KEY</li> + * <li> ALG is a 1-byte value for the algorithm which which the key is + * associated. Values are defined by the FSL SHW API</li> + * <li> FLAGS is a 1-byte value contain information like "this key is for + * software" (TBD)</li> + * <li> Ownerid, LEN, and ALG come from the user's "key_info" object, as does + * the slot number where KEY already is/will be.</li> + * <li> T is a Nonce</li> + * <li> T' is the encrypted T</li> + * <li> KEK is a Key-Encryption Key for the user's Key</li> + * <li> ICV is the "Integrity Check Value" for the wrapped key</li> + * <li> Black Key is the string of bytes returned as the wrapped key</li> + * <li> Wrap Key is the user's choice for encrypting the nonce. One of + * the Fused Key, the Random Key, or the XOR of the two. + * </ul> +<table border="0"> +<tr><TD align="right">BLACK_KEY <TD width="3">=<TD>ICV | T' | LEN | ALG | + FLAGS | KEY'</td></tr> +<tr><td> </td></tr> + +<tr><th>To Wrap</th></tr> +<tr><TD align="right">T</td> <TD width="3">=</td> <TD>RND()<sub>16</sub> + </td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><TD>HASH<sub>sha256</sub>(T | + Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY'<TD width="3">=</td><TD> + TDES<sub>cbc-enc</sub>(Key=KEK, Data=KEY, IV=Ownerid)</td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | FLAGS | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">T'</td><TD width="3">=</td><TD>TDES<sub>ecb-enc</sub> + (Key=Wrap_Key, IV=Ownerid, Data=T)</td></tr> + +<tr><td> </td></tr> + +<tr><th>To Unwrap</th></tr> +<tr><TD align="right">T</td><TD width="3">=</td><TD>TDES<sub>ecb-dec</sub> + (Key=Wrap_Key, IV=Ownerid, Data=T')</td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | FLAGS | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><td>HASH<sub>sha256</sub> + (T | Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY<TD width="3">=</td><TD>TDES<sub>cbc-dec</sub> + (Key=KEK, Data=KEY', IV=Ownerid)</td></tr> +</table> + + * This code supports two types of keys: Software Keys and keys destined for + * (or residing in) the DryIce Programmed Key Register. + * + * Software Keys go to / from the keystore. + * + * PK keys go to / from the DryIce Programmed Key Register. + * + * This code only works on a platform with DryIce. "software" keys go into + * the keystore. "Program" keys go to the DryIce Programmed Key Register. + * As far as this code is concerned, the size of that register is 21 bytes, + * the size of a 3DES key with parity stripped. + * + * The maximum key size supported for wrapped/unwrapped keys depends upon + * LENGTH_LENGTH. Currently, it is one byte, so the maximum key size is + * 255 bytes. However, key objects cannot currently hold a key of this + * length, so a smaller key size is the max. + */ + +#include "fsl_platform.h" + +/* This code only works in kernel mode */ + +#include "shw_driver.h" +#ifdef DIAG_SECURITY_FUNC +#include "apihelp.h" +#endif + +#if defined(__KERNEL__) && defined(FSL_HAVE_DRYICE) + +#include "../dryice.h" +#include <linux/mxc_scc_driver.h> + +#include "portable_os.h" +#include "fsl_shw_keystore.h" + +#include <diagnostic.h> + +#include "shw_hmac.h" +#include "shw_hash.h" + +#define ICV_LENGTH 16 +#define T_LENGTH 16 +#define KEK_LENGTH 21 +#define LENGTH_LENGTH 1 +#define ALGORITHM_LENGTH 1 +#define FLAGS_LENGTH 1 + +/* ICV | T' | LEN | ALG | FLAGS | KEY' */ +#define ICV_OFFSET 0 +#define T_PRIME_OFFSET (ICV_OFFSET + ICV_LENGTH) +#define LENGTH_OFFSET (T_PRIME_OFFSET + T_LENGTH) +#define ALGORITHM_OFFSET (LENGTH_OFFSET + LENGTH_LENGTH) +#define FLAGS_OFFSET (ALGORITHM_OFFSET + ALGORITHM_LENGTH) +#define KEY_PRIME_OFFSET (FLAGS_OFFSET + FLAGS_LENGTH) + +#define FLAGS_SW_KEY 0x01 + +#define LENGTH_PATCH 8 +#define LENGTH_PATCH_MASK (LENGTH_PATCH - 1) + +/*! rounded up from 168 bits to the next word size */ +#define HW_KEY_LEN_WORDS_BITS 192 + +/*! + * Round a key length up to the TDES block size + * + * @param len Length of key, in bytes + * + * @return Length rounded up, if necessary + */ +#define ROUND_LENGTH(len) \ +({ \ + uint32_t orig_len = len; \ + uint32_t new_len; \ + \ + if ((orig_len & LENGTH_PATCH_MASK) != 0) { \ + new_len = (orig_len + LENGTH_PATCH \ + - (orig_len & LENGTH_PATCH_MASK)); \ + } \ + else { \ + new_len = orig_len; \ + } \ + \ + new_len; \ +}) + +/* This is the system keystore object */ +extern fsl_shw_kso_t system_keystore; + +#ifdef DIAG_SECURITY_FUNC +static void dump(const char *name, const uint8_t * data, unsigned int len) +{ + os_printk("%s: ", name); + while (len > 0) { + os_printk("%02x ", (unsigned)*data++); + len--; + } + os_printk("\n"); +} +#endif + +/* + * For testing of the algorithm implementation,, the DO_REPEATABLE_WRAP flag + * causes the T_block to go into the T field during a wrap operation. This + * will make the black key value repeatable (for a given SCC secret key, or + * always if the default key is in use). + * + * Normally, a random sequence is used. + */ +#ifdef DO_REPEATABLE_WRAP +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t T_block[16] = { + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42, + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42 +}; +#endif + +EXPORT_SYMBOL(fsl_shw_establish_key); +EXPORT_SYMBOL(fsl_shw_read_key); +EXPORT_SYMBOL(fsl_shw_extract_key); +EXPORT_SYMBOL(fsl_shw_release_key); + +extern fsl_shw_return_t alloc_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +extern fsl_shw_return_t load_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + const uint8_t * key); + +extern fsl_shw_return_t dealloc_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +/*! + * Initalialize SKO and SCCO used for T <==> T' cipher operation + * + * @param wrap_key Which wrapping key user wants + * @param key_info Key object for selecting wrap key + * @param wrap_ctx Sym Context object for doing the cipher op + */ +static inline void init_wrap_key(fsl_shw_pf_key_t wrap_key, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * wrap_ctx) +{ + fsl_shw_sko_init_pf_key(key_info, FSL_KEY_ALG_TDES, wrap_key); + fsl_shw_scco_init(wrap_ctx, FSL_KEY_ALG_TDES, FSL_SYM_MODE_ECB); +} + +/*! + * Insert descriptors to calculate ICV = HMAC(key=T, data=LEN|ALG|KEY') + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param T Location of nonce (length is T_LENGTH bytes) + * @param userid Location of userid/ownerid + * @param userid_len Length, in bytes of @c userid + * @param black_key Beginning of Black Key region + * @param key_length Number of bytes of key' there are in @c black_key + * @param[out] hmac Location to store ICV. Will be tagged "USES" so + * sf routines will not try to free it. + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t calc_icv(const uint8_t * T, + const uint8_t * userid, + unsigned int userid_len, + const uint8_t * black_key, + uint32_t key_length, uint8_t * hmac) +{ + fsl_shw_return_t code; + shw_hmac_state_t hmac_state; + + /* Load up T as key for the HMAC */ + code = shw_hmac_init(&hmac_state, T, T_LENGTH); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + /* Previous step loaded key; Now set up to hash the data */ + + /* Input - start with ownerid */ + code = shw_hmac_update(&hmac_state, userid, userid_len); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - Append black-key fields len, alg, key' */ + code = shw_hmac_update(&hmac_state, + (void *)black_key + LENGTH_OFFSET, + (LENGTH_LENGTH + + ALGORITHM_LENGTH + + FLAGS_LENGTH + key_length)); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + /* Output - computed ICV/HMAC */ + code = shw_hmac_final(&hmac_state, hmac, ICV_LENGTH); + if (code != FSL_RETURN_OK_S) { + goto out; + } + + out: + + return code; +} /* calc_icv */ + +/*! + * Compute and return the KEK (Key Encryption Key) from the inputs + * + * @param userid The user's 'secret' for the key + * @param userid_len Length, in bytes of @c userid + * @param T The nonce + * @param[out] kek Location to store the computed KEK. It will + * be 21 bytes long. + * + * @return the usual error code + */ +static fsl_shw_return_t calc_kek(const uint8_t * userid, + unsigned int userid_len, + const uint8_t * T, uint8_t * kek) +{ + fsl_shw_return_t code = FSL_RETURN_INTERNAL_ERROR_S; + shw_hash_state_t hash_state; + + code = shw_hash_init(&hash_state, FSL_HASH_ALG_SHA256); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Hash init failed: %s\n", fsl_error_string(code)); +#endif + goto out; + } + + code = shw_hash_update(&hash_state, T, T_LENGTH); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Hash for T failed: %s\n", + fsl_error_string(code)); +#endif + goto out; + } + + code = shw_hash_update(&hash_state, userid, userid_len); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Hash for userid failed: %s\n", + fsl_error_string(code)); +#endif + goto out; + } + + code = shw_hash_final(&hash_state, kek, KEK_LENGTH); + if (code != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not extract kek: %s\n", + fsl_error_string(code)); +#endif + goto out; + } + +#if KEK_LENGTH != 21 + { + uint8_t permuted_kek[21]; + + fsl_shw_permute1_bytes(kek, permuted_kek, KEK_LENGTH / 8); + memcpy(kek, permuted_kek, 21); + memset(permuted_kek, 0, sizeof(permuted_kek)); + } +#endif + +#ifdef DIAG_SECURITY_FUNC + dump("kek", kek, 21); +#endif + + out: + + return code; +} /* end fn calc_kek */ + +/*! + * Validate user's wrap key selection + * + * @param wrap_key The user's desired wrapping key + */ +static fsl_shw_return_t check_wrap_key(fsl_shw_pf_key_t wrap_key) +{ + /* unable to use desired key */ + fsl_shw_return_t ret = FSL_RETURN_NO_RESOURCE_S; + + if ((wrap_key != FSL_SHW_PF_KEY_IIM) && + (wrap_key != FSL_SHW_PF_KEY_RND) && + (wrap_key != FSL_SHW_PF_KEY_IIM_RND)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Invalid wrap_key in key wrap/unwrap attempt"); +#endif + goto out; + } + ret = FSL_RETURN_OK_S; + + out: + return ret; +} /* end fn check_wrap_key */ + +/*! + * Perform unwrapping of a black key into a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be unwrapped... key length, slot info, etc. + * @param black_key Encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t unwrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + const uint8_t * black_key) +{ + fsl_shw_return_t ret; + uint8_t hmac[ICV_LENGTH]; + uint8_t T[T_LENGTH]; + uint8_t kek[KEK_LENGTH + 20]; + int key_length = black_key[LENGTH_OFFSET]; + int rounded_key_length = ROUND_LENGTH(key_length); + uint8_t key[rounded_key_length]; + fsl_shw_sko_t t_key_info; + fsl_shw_scco_t t_key_ctx; + fsl_shw_sko_t kek_key_info; + fsl_shw_scco_t kek_ctx; + int unwrapping_sw_key = key_info->flags & FSL_SKO_KEY_SW_KEY; + int pk_needs_restoration = 0; /* bool */ + unsigned original_key_length = key_info->key_length; + int pk_was_held = 0; + uint8_t current_pk[21]; + di_return_t di_code; + + ret = check_wrap_key(user_ctx->wrap_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (black_key == NULL) { + ret = FSL_RETURN_ERROR_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("black", black_key, KEY_PRIME_OFFSET + key_length); +#endif + /* Validate SW flags to prevent misuse */ + if ((key_info->flags & FSL_SKO_KEY_SW_KEY) + && !(black_key[FLAGS_OFFSET] & FLAGS_SW_KEY)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Compute T = 3des-dec-ecb(wrap_key, T') */ + init_wrap_key(user_ctx->wrap_key, &t_key_info, &t_key_ctx); + ret = fsl_shw_symmetric_decrypt(user_ctx, &t_key_info, &t_key_ctx, + T_LENGTH, + black_key + T_PRIME_OFFSET, T); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Recovery of nonce (T) failed"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Compute ICV = HMAC(T, ownerid | len | alg | flags | key' */ + ret = calc_icv(T, (uint8_t *) & key_info->userid, + sizeof(key_info->userid), + black_key, original_key_length, hmac); + + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Calculation of ICV failed"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Validating MAC of wrapped key"); +#endif + + /* Check computed ICV against value in Black Key */ + if (memcmp(black_key + ICV_OFFSET, hmac, ICV_LENGTH) != 0) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Computed ICV fails validation\n"); +#endif + ret = FSL_RETURN_AUTH_FAILED_S; + goto out; + } + + /* Compute KEK = SHA256(T | ownerid). */ + ret = calc_kek((uint8_t *) & key_info->userid, sizeof(key_info->userid), + T, kek); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (unwrapping_sw_key) { + di_code = dryice_get_programmed_key(current_pk, 8 * 21); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not save current PK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + } + + /* + * "Establish" the KEK in the PK. If the PK was held and unwrapping a + * software key, then release it and try again, but remember that we need + * to leave it 'held' if we are unwrapping a software key. + * + * If the PK is held while we are unwrapping a key for the PK, then + * the user didn't call release, so gets an error. + */ + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + if ((di_code == DI_ERR_INUSE) && unwrapping_sw_key) { + /* Temporarily reprogram the PK out from under the user */ + pk_was_held = 1; + dryice_release_programmed_key(); + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + } + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not program KEK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + if (unwrapping_sw_key) { + pk_needs_restoration = 1; + } + dryice_release_programmed_key(); /* Because of previous 'set' */ + + /* Compute KEY = TDES-decrypt(KEK, KEY') */ + fsl_shw_sko_init_pf_key(&kek_key_info, FSL_KEY_ALG_TDES, + FSL_SHW_PF_KEY_PRG); + fsl_shw_sko_set_key_length(&kek_key_info, KEK_LENGTH); + + fsl_shw_scco_init(&kek_ctx, FSL_KEY_ALG_TDES, FSL_SYM_MODE_CBC); + fsl_shw_scco_set_flags(&kek_ctx, FSL_SYM_CTX_LOAD); + fsl_shw_scco_set_context(&kek_ctx, (uint8_t *) & key_info->userid); +#ifdef DIAG_SECURITY_FUNC + dump("KEY'", black_key + KEY_PRIME_OFFSET, rounded_key_length); +#endif + ret = fsl_shw_symmetric_decrypt(user_ctx, &kek_key_info, &kek_ctx, + rounded_key_length, + black_key + KEY_PRIME_OFFSET, key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("KEY", key, original_key_length); +#endif + /* Now either put key into PK or into a slot */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = load_slot(user_ctx, key_info, key); + } else { + /* + * Since we have just unwrapped a program key, it had + * to have been wrapped as a program key, so it must + * be 168 bytes long and permuted ... + */ + ret = dryice_set_programmed_key(key, 8 * key_length, 0); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + } + + out: + key_info->key_length = original_key_length; + + if (pk_needs_restoration) { + di_code = dryice_set_programmed_key(current_pk, 8 * 21, 0); + } + + if (!pk_was_held) { + dryice_release_programmed_key(); + } + + /* Erase tracks of confidential data */ + memset(T, 0, T_LENGTH); + memset(key, 0, rounded_key_length); + memset(current_pk, 0, sizeof(current_pk)); + memset(&t_key_info, 0, sizeof(t_key_info)); + memset(&t_key_ctx, 0, sizeof(t_key_ctx)); + memset(&kek_key_info, 0, sizeof(kek_key_info)); + memset(&kek_ctx, 0, sizeof(kek_ctx)); + memset(kek, 0, KEK_LENGTH); + + return ret; +} /* unwrap */ + +/*! + * Perform wrapping of a black key from a RED slot (or the PK register) + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be wrapped... key length, slot info, etc. + * @param black_key Place to store encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t wrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * black_key) +{ + fsl_shw_return_t ret = FSL_RETURN_OK_S; + fsl_shw_sko_t t_key_info; /* for holding T */ + fsl_shw_scco_t t_key_ctx; + fsl_shw_sko_t kek_key_info; + fsl_shw_scco_t kek_ctx; + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + uint8_t T[T_LENGTH]; + uint8_t kek[KEK_LENGTH + 20]; + uint8_t *red_key = 0; + int red_key_malloced = 0; /* bool */ + int pk_was_held = 0; /* bool */ + uint8_t saved_pk[21]; + uint8_t pk_needs_restoration; /* bool */ + di_return_t di_code; + + ret = check_wrap_key(user_ctx->wrap_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (black_key == NULL) { + ret = FSL_RETURN_ERROR_S; + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY) { + if ((key_info->pf_key != FSL_SHW_PF_KEY_PRG) + && (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG)) { + ret = FSL_RETURN_ERROR_S; + goto out; + } + } else { + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED)) { + ret = FSL_RETURN_BAD_FLAG_S; /* not established! */ + goto out; + } + } + + black_key[ALGORITHM_OFFSET] = key_info->algorithm; + +#ifndef DO_REPEATABLE_WRAP + /* Compute T = RND() */ + ret = fsl_shw_get_random(user_ctx, T_LENGTH, T); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#else + memcpy(T, T_block, T_LENGTH); +#endif + + /* Compute KEK = SHA256(T | ownerid). */ + ret = calc_kek((uint8_t *) & key_info->userid, sizeof(key_info->userid), + T, kek); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Calculation of KEK failed\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + rounded_key_length = ROUND_LENGTH(original_key_length); + + di_code = dryice_get_programmed_key(saved_pk, 8 * 21); + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not save current PK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + + /* + * Load KEK into DI PKR. Note that we are NOT permuting it before loading, + * so we are using it as though it is a 168-bit key ready for the SCC. + */ + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + if (di_code == DI_ERR_INUSE) { + /* Temporarily reprogram the PK out from under the user */ + pk_was_held = 1; + dryice_release_programmed_key(); + di_code = dryice_set_programmed_key(kek, 8 * 21, 0); + } + if (di_code != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("Could not program KEK: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + pk_needs_restoration = 1; + dryice_release_programmed_key(); + + /* Find red key */ + if (key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY) { + black_key[LENGTH_OFFSET] = 21; + rounded_key_length = 24; + + red_key = saved_pk; + } else { + black_key[LENGTH_OFFSET] = key_info->key_length; + + red_key = os_alloc_memory(key_info->key_length, 0); + if (red_key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + red_key_malloced = 1; + + ret = fsl_shw_read_key(user_ctx, key_info, red_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + } + +#ifdef DIAG_SECURITY_FUNC + dump("KEY", red_key, black_key[LENGTH_OFFSET]); +#endif + /* Compute KEY' = TDES-encrypt(KEK, KEY) */ + fsl_shw_sko_init_pf_key(&kek_key_info, FSL_KEY_ALG_TDES, + FSL_SHW_PF_KEY_PRG); + fsl_shw_sko_set_key_length(&kek_key_info, KEK_LENGTH); + + fsl_shw_scco_init(&kek_ctx, FSL_KEY_ALG_TDES, FSL_SYM_MODE_CBC); + fsl_shw_scco_set_flags(&kek_ctx, FSL_SYM_CTX_LOAD); + fsl_shw_scco_set_context(&kek_ctx, (uint8_t *) & key_info->userid); + ret = fsl_shw_symmetric_encrypt(user_ctx, &kek_key_info, &kek_ctx, + rounded_key_length, + red_key, black_key + KEY_PRIME_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Encryption of KEY failed\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Set up flags info */ + black_key[FLAGS_OFFSET] = 0; + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + black_key[FLAGS_OFFSET] |= FLAGS_SW_KEY; + } +#ifdef DIAG_SECURITY_FUNC + dump("KEY'", black_key + KEY_PRIME_OFFSET, rounded_key_length); +#endif + /* Compute and store ICV into Black Key */ + ret = calc_icv(T, + (uint8_t *) & key_info->userid, + sizeof(key_info->userid), + black_key, original_key_length, black_key + ICV_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Calculation of ICV failed\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Compute T' = 3des-enc-ecb(wrap_key, T); Result goes to Black Key */ + init_wrap_key(user_ctx->wrap_key, &t_key_info, &t_key_ctx); + ret = fsl_shw_symmetric_encrypt(user_ctx, &t_key_info, &t_key_ctx, + T_LENGTH, + T, black_key + T_PRIME_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Encryption of nonce failed"); +#endif + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("black", black_key, KEY_PRIME_OFFSET + black_key[LENGTH_OFFSET]); +#endif + + out: + if (pk_needs_restoration) { + dryice_set_programmed_key(saved_pk, 8 * 21, 0); + } + + if (!pk_was_held) { + dryice_release_programmed_key(); + } + + if (red_key_malloced) { + memset(red_key, 0, key_info->key_length); + os_free_memory(red_key); + } + + key_info->key_length = original_key_length; + + /* Erase tracks of confidential data */ + memset(T, 0, T_LENGTH); + memset(&t_key_info, 0, sizeof(t_key_info)); + memset(&t_key_ctx, 0, sizeof(t_key_ctx)); + memset(&kek_key_info, 0, sizeof(kek_key_info)); + memset(&kek_ctx, 0, sizeof(kek_ctx)); + memset(kek, 0, sizeof(kek)); + memset(saved_pk, 0, sizeof(saved_pk)); + + return ret; +} /* wrap */ + +static fsl_shw_return_t create(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + unsigned key_length = key_info->key_length; + di_return_t di_code; + + if (!(key_info->flags & FSL_SKO_KEY_SW_KEY)) { + /* Must be creating key for PK */ + if ((key_info->algorithm != FSL_KEY_ALG_TDES) || + ((key_info->key_length != 16) + && (key_info->key_length != 21) /* permuted 168-bit key */ + &&(key_info->key_length != 24))) { + ret = FSL_RETURN_ERROR_S; + goto out; + } + + key_length = 21; /* 168-bit PK */ + } + + /* operational block */ + { + uint8_t key_value[key_length]; + +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creating random key\n"); +#endif + ret = fsl_shw_get_random(user_ctx, key_length, key_value); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("get_random for CREATE KEY failed\n"); +#endif + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = load_slot(user_ctx, key_info, key_value); + } else { + di_code = + dryice_set_programmed_key(key_value, 8 * key_length, + 0); + if (di_code != 0) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("di set_pk failed: %s\n", + di_error_string(di_code)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + ret = FSL_RETURN_OK_S; + } + memset(key_value, 0, key_length); + } /* end operational block */ + +#ifdef DIAG_SECURITY_FUNC + if (ret != FSL_RETURN_OK_S) { + LOG_DIAG("Loading random key failed"); + } +#endif + + out: + + return ret; +} /* end fn create */ + +static fsl_shw_return_t accept(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, const uint8_t * key) +{ + uint8_t permuted_key[21]; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + if (key == NULL) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("ACCEPT: Red Key is NULL"); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + dump("red", key, key_info->key_length); +#endif + /* Only SW keys go into the keystore */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + + /* Copy in safe number of bytes of Red key */ + ret = load_slot(user_ctx, key_info, key); + } else { /* not SW key */ + di_return_t di_ret; + + /* Only 3DES PGM key types can be established */ + if (((key_info->pf_key != FSL_SHW_PF_KEY_PRG) + && (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG)) + || (key_info->algorithm != FSL_KEY_ALG_TDES)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("ACCEPT: Failed trying to establish non-PRG" + " or invalid 3DES Key: iim%d, iim_prg%d, alg%d\n", + (key_info->pf_key != FSL_SHW_PF_KEY_PRG), + (key_info->pf_key != FSL_SHW_PF_KEY_IIM_PRG), + (key_info->algorithm != FSL_KEY_ALG_TDES)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + if ((key_info->key_length != 16) + && (key_info->key_length != 21) + && (key_info->key_length != 24)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("ACCEPT: Failed trying to establish" + " invalid 3DES Key: len=%d (%d)\n", + key_info->key_length, + ((key_info->key_length != 16) + && (key_info->key_length != 21) + && (key_info->key_length != 24))); +#endif + ret = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + /* Convert key into 168-bit value and put it into PK */ + if (key_info->key_length != 21) { + fsl_shw_permute1_bytes(key, permuted_key, + key_info->key_length / 8); + di_ret = + dryice_set_programmed_key(permuted_key, 168, 0); + } else { + /* Already permuted ! */ + di_ret = dryice_set_programmed_key(key, 168, 0); + } + if (di_ret != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("ACCEPT: DryIce error setting Program Key: %s", + di_error_string(di_ret)); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + } + + ret = FSL_RETURN_OK_S; + + out: + memset(permuted_key, 0, 21); + + return ret; +} /* end fn accept */ + +/*! + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped by calling #fsl_shw_extract_key() at some later time). + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * @bug This whole discussion needs review. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + unsigned slot_allocated = 0; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("%s: Non-blocking call not supported\n", + __FUNCTION__); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* + HW keys are always 'established', but otherwise do not allow user + * to establish over the top of an established key. + */ + if ((key_info->flags & FSL_SKO_KEY_ESTABLISHED) + && !(key_info->flags & FSL_SKO_KEY_SELECT_PF_KEY)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("%s: Key already established\n", __FUNCTION__); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* @bug VALIDATE KEY flags here -- SW or PRG/IIM_PRG */ + + /* Write operations into SCC memory require word-multiple number of + * bytes. For ACCEPT and CREATE functions, the key length may need + * to be rounded up. Calculate. */ + if (LENGTH_PATCH && (original_key_length & LENGTH_PATCH_MASK) != 0) { + rounded_key_length = original_key_length + LENGTH_PATCH + - (original_key_length & LENGTH_PATCH_MASK); + } else { + rounded_key_length = original_key_length; + } + + /* SW keys need a place to live */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + ret = alloc_slot(user_ctx, key_info); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Slot allocation failed\n"); +#endif + goto out; + } + slot_allocated = 1; + } + + switch (establish_type) { + case FSL_KEY_WRAP_CREATE: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creating random key\n"); +#endif + ret = create(user_ctx, key_info); + break; + + case FSL_KEY_WRAP_ACCEPT: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Accepting plaintext key\n"); +#endif + ret = accept(user_ctx, key_info, key); + break; + + case FSL_KEY_WRAP_UNWRAP: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Unwrapping wrapped key\n"); +#endif + ret = unwrap(user_ctx, key_info, key); + break; + + default: + ret = FSL_RETURN_BAD_FLAG_S; + break; + } /* switch */ + + out: + if (ret != FSL_RETURN_OK_S) { + if (slot_allocated) { + (void)dealloc_slot(user_ctx, key_info); + } + key_info->flags &= ~FSL_SKO_KEY_ESTABLISHED; + } else { + key_info->flags |= FSL_SKO_KEY_ESTABLISHED; + } + + return ret; +} /* end fn fsl_shw_establish_key */ + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release a software key (see #fsl_shw_release_key()) + * so it must be re-established before reuse. This is not true of PGM keys. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the 48-octet wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Wrapping a key\n"); +#endif + + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("%s: Key not established\n", __FUNCTION__); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + /* Verify that a SW key info really belongs to a SW key */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + /* ret = FSL_RETURN_BAD_FLAG_S; + goto out;*/ + } + + ret = wrap(user_ctx, key_info, covered_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + /* Need to deallocate on successful extraction */ + (void)dealloc_slot(user_ctx, key_info); + /* Mark key not available in the flags */ + key_info->flags &= + ~(FSL_SKO_KEY_ESTABLISHED | FSL_SKO_KEY_PRESENT); + memset(key_info->key, 0, sizeof(key_info->key)); + } + + out: + return ret; +} /* end fn fsl_shw_extract_key */ + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Not in blocking mode\n"); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Releasing a key\n"); +#endif + + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Key not established\n"); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + (void)dealloc_slot(user_ctx, key_info); + /* Turn off 'established' flag */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("dealloc_slot() called\n"); +#endif + key_info->flags &= ~FSL_SKO_KEY_ESTABLISHED; + ret = FSL_RETURN_OK_S; + goto out; + } + + if ((key_info->pf_key == FSL_SHW_PF_KEY_PRG) + || (key_info->pf_key == FSL_SHW_PF_KEY_IIM_PRG)) { + di_return_t di_ret; + + di_ret = dryice_release_programmed_key(); + if (di_ret != DI_SUCCESS) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("dryice_release_programmed_key() failed: %d\n", + di_ret); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Neither SW nor HW key\n"); +#endif + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} /* end fn fsl_shw_release_key */ + +fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * key) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + /* Only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + printk("Reading a key\n"); +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Reading a key"); +#endif + if (key_info->flags & FSL_SKO_KEY_PRESENT) { + memcpy(key_info->key, key, key_info->key_length); + ret = FSL_RETURN_OK_S; + } else if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + printk("key established\n"); + if (key_info->keystore == NULL) { + printk("keystore is null\n"); + /* First verify that the key access is valid */ + ret = + system_keystore.slot_verify_access(system_keystore. + user_data, + key_info->userid, + key_info-> + handle); + + printk("key in system keystore\n"); + + /* Key is in system keystore */ + ret = keystore_slot_read(&system_keystore, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } else { + printk("key goes in user keystore.\n"); + /* Key goes in user keystore */ + ret = keystore_slot_read(key_info->keystore, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } + } + + out: + return ret; +} /* end fn fsl_shw_read_key */ + +#else /* __KERNEL__ && DRYICE */ + +/* User mode -- these functions are unsupported */ + +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + (void)user_ctx; + (void)key_info; + (void)establish_type; + (void)key; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + (void)user_ctx; + (void)key_info; + (void)covered_key; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + (void)user_ctx; + (void)key_info; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * key) +{ + (void)user_ctx; + (void)key_info; + (void)key; + + return FSL_RETURN_NO_RESOURCE_S; +} + +#endif /* __KERNEL__ && DRYICE */ diff --git a/drivers/mxc/security/rng/include/rng_driver.h b/drivers/mxc/security/rng/include/rng_driver.h new file mode 100644 index 000000000000..7d6d24dde915 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_driver.h @@ -0,0 +1,134 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef RNG_DRIVER_H +#define RNG_DRIVER_H + +#include "shw_driver.h" + +/* This is a Linux flag meaning 'compiling kernel code'... */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <memory.h> +#else +#include "../../sahara2/include/portable_os.h" +#endif + +#include "../../sahara2/include/fsl_platform.h" + +/*! @file rng_driver.h + * + * @brief Header file to use the RNG driver. + * + * @ingroup RNG + */ + +#if defined(FSL_HAVE_RNGA) + +#include "rng_rnga.h" + +#elif defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC) + +#include "rng_rngc.h" + +#else /* neither RNGA, RNGB, nor RNGC */ + +#error NO_RNG_TYPE_IDENTIFIED + +#endif + +/***************************************************************************** + * Enumerations + *****************************************************************************/ + +/*! Values from Version ID register */ +enum rng_type { + /*! Type RNGA. */ + RNG_TYPE_RNGA = 0, + /*! Type RNGB. */ + RNG_TYPE_RNGB = 1, + /*! Type RNGC */ + RNG_TYPE_RNGC = 2 +}; + +/*! + * Return values (error codes) for kernel register interface functions + */ +typedef enum rng_return { + RNG_RET_OK = 0, /*!< Function succeeded */ + RNG_RET_FAIL /*!< Non-specific failure */ +} rng_return_t; + +/***************************************************************************** + * Data Structures + *****************************************************************************/ +/*! + * An entry in the RNG Work Queue. Based upon standard SHW queue entry. + * + * This entry also gets saved (for non-blocking requests) in the user's result + * pool. When the user picks up the request, the final processing (copy from + * data_local to data_user) will get made if status was good. + */ +typedef struct rng_work_entry { + struct shw_queue_entry_t hdr; /*!< Standards SHW queue info. */ + uint32_t length; /*!< Number of bytes still needed to satisfy request. */ + uint32_t *data_local; /*!< Where data from RNG FIFO gets placed. */ + uint8_t *data_user; /*!< Ultimate target of data. */ + unsigned completed; /*!< Non-zero if job is done. */ +} rng_work_entry_t; + +/***************************************************************************** + * Function Prototypes + *****************************************************************************/ + +#ifdef RNG_REGISTER_PEEK_POKE +/*! + * Read value from an RNG register. + * The offset will be checked for validity as well as whether it is + * accessible at the time of the call. + * + * This routine cannot be used to read the RNG's Output FIFO if the RNG is in + * High Assurance mode. + * + * @param[in] register_offset The (byte) offset within the RNG block + * of the register to be queried. See + * RNG(A, C) registers for meanings. + * @param[out] value Pointer to where value from the register + * should be placed. + * + * @return See #rng_return_t. + */ +/* REQ-FSLSHW-PINTFC-API-LLF-001 */ +extern rng_return_t rng_read_register(uint32_t register_offset, + uint32_t * value); + +/*! + * Write a new value into an RNG register. + * + * The offset will be checked for validity as well as whether it is + * accessible at the time of the call. + * + * @param[in] register_offset The (byte) offset within the RNG block + * of the register to be modified. See + * RNG(A, C) registers for meanings. + * @param[in] value The value to store into the register. + * + * @return See #rng_return_t. + */ +/* REQ-FSLSHW-PINTFC-API-LLF-002 */ +extern rng_return_t rng_write_register(uint32_t register_offset, + uint32_t value); +#endif /* RNG_REGISTER_PEEK_POKE */ + +#endif /* RNG_DRIVER_H */ diff --git a/drivers/mxc/security/rng/include/rng_internals.h b/drivers/mxc/security/rng/include/rng_internals.h new file mode 100644 index 000000000000..62d195bf4df7 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_internals.h @@ -0,0 +1,680 @@ +/* + * Copyright 2005-2009 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 + */ + +#ifndef RNG_INTERNALS_H +#define RNG_INTERNALS_H + +/*! @file rng_internals.h + * + * This file contains definitions which are internal to the RNG driver. + * + * This header file should only ever be needed by rng_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. (FSL_HAVE_RNGA or FSL_HAVE_RNGC) + * + * @ingroup RNG + */ + +#include "portable_os.h" +#include "shw_driver.h" +#include "rng_driver.h" + +/*! @defgroup rngcompileflags RNG Compile Flags + * + * These are flags which are used to configure the RNG driver at compilation + * time. + * + * Most of them default to good values for normal operation, but some + * (#INT_RNG and #RNG_BASE_ADDR) need to be provided. + * + * The terms 'defined' and 'undefined' refer to whether a @c \#define (or -D on + * a compile command) has defined a given preprocessor symbol. If a given + * symbol is defined, then @c \#ifdef \<symbol\> will succeed. Some symbols + * described below default to not having a definition, i.e. they are undefined. + * + */ + +/*! @addtogroup rngcompileflags */ +/*! @{ */ + +/*! + * This is the maximum number of times the driver will loop waiting for the + * RNG hardware to say that it has generated random data. It prevents the + * driver from stalling forever should there be a hardware problem. + * + * Default value is 100. It should be revisited as CPU clocks speed up. + */ +#ifndef RNG_MAX_TRIES +#define RNG_MAX_TRIES 100 +#endif + +/* Temporarily define compile-time flags to make Doxygen happy and allow them + to get into the documentation. */ +#ifdef DOXYGEN_HACK + +/*! + * This symbol is the base address of the RNG in the CPU memory map. It may + * come from some included header file, or it may come from the compile command + * line. This symbol has no default, and the driver will not compile without + * it. + */ +#define RNG_BASE_ADDR +#undef RNG_BASE_ADDR + +/*! + * This symbol is the Interrupt Number of the RNG in the CPU. It may come + * from some included header file, or it may come from the compile command + * line. This symbol has no default, and the driver will not compile without + * it. + */ +#define INT_RNG +#undef INT_RNG + +/*! + * Defining this symbol will allow other kernel programs to call the + * #rng_read_register() and #rng_write_register() functions. If this symbol is + * not defined, those functions will not be present in the driver. + */ +#define RNG_REGISTER_PEEK_POKE +#undef RNG_REGISTER_PEEK_POKE + +/*! + * Turn on compilation of run-time operational, debug, and error messages. + * + * This flag is undefined by default. + */ +/* REQ-FSLSHW-DEBUG-001 */ + +/*! + * Turn on compilation of run-time logging of access to the RNG registers, + * except for the RNG's Output FIFO register. See #RNG_ENTROPY_DEBUG. + * + * This flag is undefined by default + */ +#define RNG_REGISTER_DEBUG +#undef RNG_REGISTER_DEBUG + +/*! + * Turn on compilation of run-time logging of reading of the RNG's Output FIFO + * register. This flag does nothing if #RNG_REGISTER_DEBUG is not defined. + * + * This flag is undefined by default + */ +#define RNG_ENTROPY_DEBUG +#undef RNG_ENTROPY_DEBUG + +/*! + * If this flag is defined, the driver will not attempt to put the RNG into + * High Assurance mode. + + * If it is undefined, the driver will attempt to put the RNG into High + * Assurance mode. If RNG fails to go into High Assurance mode, the driver + * will fail to initialize. + + * In either case, if the RNG is already in this mode, the driver will operate + * normally. + * + * This flag is undefined by default. + */ +#define RNG_NO_FORCE_HIGH_ASSURANCE +#undef RNG_NO_FORCE_HIGH_ASSURANCE + +/*! + * If this flag is defined, the driver will put the RNG into low power mode + * every opportunity. + * + * This flag is undefined by default. + */ +#define RNG_USE_LOW_POWER_MODE +#undef RNG_USE_LOW_POWER_MODE + +/*! @} */ +#endif /* end DOXYGEN_HACK */ + +/*! + * If this flag is defined, the driver will not attempt to put the RNG into + * High Assurance mode. + + * If it is undefined, the driver will attempt to put the RNG into High + * Assurance mode. If RNG fails to go into High Assurance mode, the driver + * will fail to initialize. + + * In either case, if the RNG is already in this mode, the driver will operate + * normally. + * + */ +#define RNG_NO_FORCE_HIGH_ASSURANCE + +/*! + * Read a 32-bit value from an RNG register. This macro depends upon + * #rng_base. The os_read32() macro operates on 32-bit quantities, as do + * all RNG register reads. + * + * @param offset Register byte offset within RNG. + * + * @return The value from the RNG's register. + */ +#ifndef RNG_REGISTER_DEBUG +#define RNG_READ_REGISTER(offset) os_read32(rng_base+(offset)) +#else +#define RNG_READ_REGISTER(offset) dbg_rng_read_register(offset) +#endif + +/*! + * Write a 32-bit value to an RNG register. This macro depends upon + * #rng_base. The os_write32() macro operates on 32-bit quantities, as do + * all RNG register writes. + * + * @param offset Register byte offset within RNG. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef RNG_REGISTER_DEBUG +#define RNG_WRITE_REGISTER(offset,value) \ + (void)os_write32(rng_base+(offset), value) +#else +#define RNG_WRITE_REGISTER(offset,value) dbg_rng_write_register(offset,value) +#endif + +#ifndef RNG_DRIVER_NAME +/*! @addtogroup rngcompileflags */ +/*! @{ */ +/*! Name the driver will use to register itself to the kernel as the driver. */ +#define RNG_DRIVER_NAME "rng" +/*! @} */ +#endif + +/*! + * Calculate number of words needed to hold the given number of bytes. + * + * @param byte_count Number of bytes + * + * @return Number of words + */ +#define BYTES_TO_WORDS(byte_count) \ + (((byte_count)+sizeof(uint32_t)-1)/sizeof(uint32_t)) + +/*! Gives high-level view of state of the RNG */ +typedef enum rng_status { + RNG_STATUS_INITIAL, /*!< Driver status before ever starting. */ + RNG_STATUS_CHECKING, /*!< During driver initialization. */ + RNG_STATUS_UNIMPLEMENTED, /*!< Hardware is non-existent / unreachable. */ + RNG_STATUS_OK, /*!< Hardware is In Secure or Default state. */ + RNG_STATUS_FAILED /*!< Hardware is In Failed state / other fatal + problem. Driver is still able to read/write + some registers, but cannot get Random + data. */ +} rng_status_t; + +static shw_queue_t rng_work_queue; + +/***************************************************************************** + * + * Function Declarations + * + *****************************************************************************/ + +/* kernel interface functions */ +OS_DEV_INIT_DCL(rng_init); +OS_DEV_TASK_DCL(rng_entropy_task); +OS_DEV_SHUTDOWN_DCL(rng_shutdown); +OS_DEV_ISR_DCL(rng_irq); + +#define RNG_ADD_QUEUE_ENTRY(pool, entry) \ + SHW_ADD_QUEUE_ENTRY(pool, (shw_queue_entry_t*)entry) + +#define RNG_REMOVE_QUEUE_ENTRY(pool, entry) \ + SHW_REMOVE_QUEUE_ENTRY(pool, (shw_queue_entry_t*)entry) +#define RNG_GET_WORK_ENTRY() \ + (rng_work_entry_t*)SHW_POP_FIRST_ENTRY(&rng_work_queue) + +/*! + * Add an work item to a work list. Item will be marked incomplete. + * + * @param work Work entry to place at tail of list. + * + * @return none + */ +inline static void RNG_ADD_WORK_ENTRY(rng_work_entry_t * work) +{ + work->completed = FALSE; + + SHW_ADD_QUEUE_ENTRY(&rng_work_queue, (shw_queue_entry_t *) work); + + os_dev_schedule_task(rng_entropy_task); +} + +/*! + * For #rng_check_register_accessible(), check read permission on given + * register. + */ +#define RNG_CHECK_READ 0 + +/*! + * For #rng_check_register_accessible(), check write permission on given + * register. + */ +#define RNG_CHECK_WRITE 1 + +/* Define different helper symbols based on RNG type */ +#ifdef FSL_HAVE_RNGA + +/****************************************************************************** + * + * RNGA support + * + *****************************************************************************/ + +/*! Interrupt number for driver. */ +#if defined(MXC_INT_RNG) +/* Most modern definition */ +#define INT_RNG MXC_INT_RNG +#elif defined(MXC_INT_RNGA) +#define INT_RNG MXC_INT_RNGA +#else +#define INT_RNG INT_RNGA +#endif + +/*! Base (bus?) address of RNG component. */ +#define RNG_BASE_ADDR RNGA_BASE_ADDR + +/*! Read and return the status register. */ +#define RNG_GET_STATUS() \ + RNG_READ_REGISTER(RNGA_STATUS) +/*! Configure RNG for Auto seeding */ +#define RNG_AUTO_SEED() +/* Put RNG for Seed Generation */ +#define RNG_SEED_GEN() +/*! + * Return RNG Type value. Should be RNG_TYPE_RNGA, RNG_TYPE_RNGB, + * or RNG_TYPE_RNGC. + */ +#define RNG_GET_RNG_TYPE() \ + ((RNG_READ_REGISTER(RNGA_CONTROL) & RNGA_CONTROL_RNG_TYPE_MASK) \ + >> RNGA_CONTROL_RNG_TYPE_SHIFT) + +/*! + * Verify Type value of RNG. + * + * Returns true of OK, false if not. + */ +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGA) + +/*! Returns non-zero if RNG device is reporting an error. */ +#define RNG_HAS_ERROR() \ + (RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_ERROR_INTERRUPT) +/*! Returns non-zero if Bad Key is selected */ +#define RNG_HAS_BAD_KEY() 0 +/*! Return non-zero if Self Test Done */ +#define RNG_SELF_TEST_DONE() 0 +/*! Returns non-zero if RNG ring oscillators have failed. */ +#define RNG_OSCILLATOR_FAILED() \ + (RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OSCILLATOR_DEAD) + +/*! Returns maximum number of 32-bit words in the RNG's output fifo. */ +#define RNG_GET_FIFO_SIZE() \ + ((RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OUTPUT_FIFO_SIZE_MASK) \ + >> RNGA_STATUS_OUTPUT_FIFO_SIZE_SHIFT) + +/*! Returns number of 32-bit words currently in the RNG's output fifo. */ +#define RNG_GET_WORDS_IN_FIFO() \ + ((RNG_READ_REGISTER(RNGA_STATUS) & RNGA_STATUS_OUTPUT_FIFO_LEVEL_MASK) \ + >> RNGA_STATUS_OUTPUT_FIFO_LEVEL_SHIFT) +/* Configuring RNG for Self Test */ +#define RNG_SELF_TEST() +/*! Get a random value from the RNG's output FIFO. */ +#define RNG_READ_FIFO() \ + RNG_READ_REGISTER(RNGA_OUTPUT_FIFO) + +/*! Put entropy into the RNG's algorithm. + * @param value 32-bit value to add to RNG's entropy. + **/ +#define RNG_ADD_ENTROPY(value) \ + RNG_WRITE_REGISTER(RNGA_ENTROPY, (value)) +/*! Return non-zero in case of Error during Self Test */ +#define RNG_CHECK_SELF_ERR() 0 +/*! Return non-zero in case of Error during Seed Generation */ +#define RNG_CHECK_SEED_ERR() 0 +/*! Get the RNG started at generating output. */ +#define RNG_GO() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_GO); \ +} +/*! To clear all Error Bits in Error Status Register */ +#define RNG_CLEAR_ERR() +/*! Put RNG into High Assurance mode */ +#define RNG_SET_HIGH_ASSURANCE() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_HIGH_ASSURANCE); \ +} + +/*! Return non-zero if the RNG is in High Assurance mode. */ +#define RNG_GET_HIGH_ASSURANCE() \ + (RNG_READ_REGISTER(RNGA_CONTROL) & RNGA_CONTROL_HIGH_ASSURANCE) + +/*! Clear all status, error and otherwise. */ +#define RNG_CLEAR_ALL_STATUS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_CLEAR_INTERRUPT); \ +} +/* Return non-zero if RESEED Required */ +#define RNG_RESEED() 1 + +/*! Return non-zero if Seeding is done */ +#define RNG_SEED_DONE() 1 + +/*! Return non-zero if everything seems OK with the RNG. */ +#define RNG_WORKING() \ + ((RNG_READ_REGISTER(RNGA_STATUS) \ + & (RNGA_STATUS_SLEEP | RNGA_STATUS_SECURITY_VIOLATION \ + | RNGA_STATUS_ERROR_INTERRUPT | RNGA_STATUS_FIFO_UNDERFLOW \ + | RNGA_STATUS_LAST_READ_STATUS )) == 0) + +/*! Put the RNG into sleep (low-power) mode. */ +#define RNG_SLEEP() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_SLEEP); \ +} + +/*! Wake the RNG from sleep (low-power) mode. */ +#define RNG_WAKE() \ +{ \ + uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control & ~RNGA_CONTROL_SLEEP); \ +} + +/*! Mask interrupts so that the driver/OS will not see them. */ +#define RNG_MASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control | RNGA_CONTROL_MASK_INTERRUPTS); \ +} + +/*! Unmask interrupts so that the driver/OS will see them. */ +#define RNG_UNMASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGA_CONTROL); \ + RNG_WRITE_REGISTER(RNGA_CONTROL, control & ~RNGA_CONTROL_MASK_INTERRUPTS);\ +} + +/*! + * @def RNG_PUT_RNG_TO_SLEEP() + * + * If compiled with #RNG_USE_LOW_POWER_MODE, this routine will put the RNG + * to sleep (low power mode). + * + * @return none + */ +/*! + * @def RNG_WAKE_RNG_FROM_SLEEP() + * + * If compiled with #RNG_USE_LOW_POWER_MODE, this routine will wake the RNG + * from sleep (low power mode). + * + * @return none + */ +#ifdef RNG_USE_LOW_POWER_MODE + +#define RNG_PUT_RNG_TO_SLEEP() \ + RNG_SLEEP() + +#define RNG_WAKE_FROM_SLEEP() \ + RNG_WAKE() 1 + +#else /* not low power mode */ + +#define RNG_PUT_RNG_TO_SLEEP() + +#define RNG_WAKE_FROM_SLEEP() + +#endif /* Use low-power mode */ + +#else /* FSL_HAVE_RNGB or FSL_HAVE_RNGC */ + +/****************************************************************************** + * + * RNGB and RNGC support + * + *****************************************************************************/ +/* + * The operational interfaces for RNGB and RNGC are almost identical, so + * the defines for RNGC work fine for both. There are minor differences + * which will be treated within this conditional block. + */ + +/*! Interrupt number for driver. */ +#if defined(MXC_INT_RNG) +/* Most modern definition */ +#define INT_RNG MXC_INT_RNG +#elif defined(MXC_INT_RNGC) +#define INT_RNG MXC_INT_RNGC +#elif defined(MXC_INT_RNGB) +#define INT_RNG MXC_INT_RNGB +#elif defined(INT_RNGC) +#define INT_RNG INT_RNGC +#else +#error NO_INTERRUPT_DEFINED +#endif + +/*! Base address of RNG component. */ +#ifdef FSL_HAVE_RNGB +#define RNG_BASE_ADDR RNGB_BASE_ADDR +#else +#define RNG_BASE_ADDR RNGC_BASE_ADDR +#endif + +/*! Read and return the status register. */ +#define RNG_GET_STATUS() \ + RNG_READ_REGISTER(RNGC_ERROR) + +/*! + * Return RNG Type value. Should be RNG_TYPE_RNGA or RNG_TYPE_RNGC. + */ +#define RNG_GET_RNG_TYPE() \ + ((RNG_READ_REGISTER(RNGC_VERSION_ID) & RNGC_VERID_RNG_TYPE_MASK) \ + >> RNGC_VERID_RNG_TYPE_SHIFT) + +/*! + * Verify Type value of RNG. + * + * Returns true of OK, false if not. + */ +#ifdef FSL_HAVE_RNGB +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGB) +#else /* RNGC */ +#define RNG_VERIFY_TYPE(type) \ + ((type) == RNG_TYPE_RNGC) +#endif + +/*! Returns non-zero if RNG device is reporting an error. */ +#define RNG_HAS_ERROR() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_ERROR) +/*! Returns non-zero if Bad Key is selected */ +#define RNG_HAS_BAD_KEY() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_BAD_KEY) +/*! Returns non-zero if RNG ring oscillators have failed. */ +#define RNG_OSCILLATOR_FAILED() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_OSC_ERR) + +/*! Returns maximum number of 32-bit words in the RNG's output fifo. */ +#define RNG_GET_FIFO_SIZE() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_FIFO_SIZE_MASK) \ + >> RNGC_STATUS_FIFO_SIZE_SHIFT) + +/*! Returns number of 32-bit words currently in the RNG's output fifo. */ +#define RNG_GET_WORDS_IN_FIFO() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_FIFO_LEVEL_MASK) \ + >> RNGC_STATUS_FIFO_LEVEL_SHIFT) + +/*! Get a random value from the RNG's output FIFO. */ +#define RNG_READ_FIFO() \ + RNG_READ_REGISTER(RNGC_FIFO) + +/*! Put entropy into the RNG's algorithm. + * @param value 32-bit value to add to RNG's entropy. + **/ +#ifdef FSL_HAVE_RNGB +#define RNG_ADD_ENTROPY(value) \ + RNG_WRITE_REGISTER(RNGB_ENTROPY, value) +#else /* RNGC does not have Entropy register */ +#define RNG_ADD_ENTROPY(value) +#endif +/*! Wake the RNG from sleep (low-power) mode. */ +#define RNG_WAKE() 1 +/*! Get the RNG started at generating output. */ +#define RNG_GO() +/*! Put RNG into High Assurance mode. */ +#define RNG_SET_HIGH_ASSURANCE() +/*! Returns non-zero in case of Error during Self Test */ +#define RNG_CHECK_SELF_ERR() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_ST_ERR) +/*! Return non-zero in case of Error during Seed Generation */ +#define RNG_CHECK_SEED_ERR() \ + (RNG_READ_REGISTER(RNGC_ERROR) & RNGC_ERROR_STATUS_STAT_ERR) + +/*! Configure RNG for Self Test */ +#define RNG_SELF_TEST() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_SELF_TEST); \ +} +/*! Clearing the Error bits in Error Status Register */ +#define RNG_CLEAR_ERR() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_CLEAR_ERROR); \ +} + +/*! Return non-zero if Self Test Done */ +#define RNG_SELF_TEST_DONE() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_ST_DONE) +/* Put RNG for SEED Generation */ +#define RNG_SEED_GEN() \ +{ \ + register uint32_t command = RNG_READ_REGISTER(RNGC_COMMAND); \ + RNG_WRITE_REGISTER(RNGC_COMMAND, command \ + | RNGC_COMMAND_SEED); \ +} +/* Return non-zero if RESEED Required */ +#define RNG_RESEED() \ + (RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_RESEED) + +/*! Return non-zero if the RNG is in High Assurance mode. */ +#define RNG_GET_HIGH_ASSURANCE() (RNG_READ_REGISTER(RNGC_STATUS) & \ + RNGC_STATUS_SEC_STATE) + +/*! Clear all status, error and otherwise. */ +#define RNG_CLEAR_ALL_STATUS() \ + RNG_WRITE_REGISTER(RNGC_COMMAND, \ + RNGC_COMMAND_CLEAR_INTERRUPT \ + | RNGC_COMMAND_CLEAR_ERROR) + +/*! Return non-zero if everything seems OK with the RNG. */ +#define RNG_WORKING() \ + ((RNG_READ_REGISTER(RNGC_ERROR) \ + & (RNGC_ERROR_STATUS_STAT_ERR | RNGC_ERROR_STATUS_RAND_ERR \ + | RNGC_ERROR_STATUS_FIFO_ERR | RNGC_ERROR_STATUS_ST_ERR | \ + RNGC_ERROR_STATUS_OSC_ERR | RNGC_ERROR_STATUS_LFSR_ERR )) == 0) +/*! Return Non zero if SEEDING is DONE */ +#define RNG_SEED_DONE() \ + ((RNG_READ_REGISTER(RNGC_STATUS) & RNGC_STATUS_SEED_DONE) != 0) + +/*! Put the RNG into sleep (low-power) mode. */ +#define RNG_SLEEP() + +/*! Wake the RNG from sleep (low-power) mode. */ + +/*! Mask interrupts so that the driver/OS will not see them. */ +#define RNG_MASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, control \ + | RNGC_CONTROL_MASK_DONE \ + | RNGC_CONTROL_MASK_ERROR); \ +} +/*! Configuring RNGC for self Test. */ + +#define RNG_AUTO_SEED() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, control \ + | RNGC_CONTROL_AUTO_SEED); \ +} + +/*! Unmask interrupts so that the driver/OS will see them. */ +#define RNG_UNMASK_ALL_INTERRUPTS() \ +{ \ + register uint32_t control = RNG_READ_REGISTER(RNGC_CONTROL); \ + RNG_WRITE_REGISTER(RNGC_CONTROL, \ + control & ~(RNGC_CONTROL_MASK_DONE|RNGC_CONTROL_MASK_ERROR)); \ +} + +/*! Put RNG to sleep if appropriate. */ +#define RNG_PUT_RNG_TO_SLEEP() + +/*! Wake RNG from sleep if necessary. */ +#define RNG_WAKE_FROM_SLEEP() + +#endif /* RNG TYPE */ + +/* internal functions */ +static os_error_code rng_map_RNG_memory(void); +static os_error_code rng_setup_interrupt_handling(void); +#ifdef RNG_REGISTER_PEEK_POKE +inline static int rng_check_register_offset(uint32_t offset); +inline static int rng_check_register_accessible(uint32_t offset, + int access_write); +#endif /* DEBUG_RNG_REGISTERS */ +static fsl_shw_return_t rng_drain_fifo(uint32_t * random_p, int count_words); +static os_error_code rng_grab_config_values(void); +static void rng_cleanup(void); + +#ifdef FSL_HAVE_RNGA +static void rng_sec_failure(void); +#endif + +#ifdef RNG_REGISTER_DEBUG +static uint32_t dbg_rng_read_register(uint32_t offset); +static void dbg_rng_write_register(uint32_t offset, uint32_t value); +#endif + +#if defined(LINUX_VERSION_CODE) + +EXPORT_SYMBOL(fsl_shw_add_entropy); +EXPORT_SYMBOL(fsl_shw_get_random); + +#ifdef RNG_REGISTER_PEEK_POKE +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(rng_read_register); +EXPORT_SYMBOL(rng_write_register); +#endif /* DEBUG_RNG_REGISTERS */ + + + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Device Driver for RNG"); + +#endif /* LINUX_VERSION_CODE */ + +#endif /* RNG_INTERNALS_H */ diff --git a/drivers/mxc/security/rng/include/rng_rnga.h b/drivers/mxc/security/rng/include/rng_rnga.h new file mode 100644 index 000000000000..971064d51522 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_rnga.h @@ -0,0 +1,181 @@ +/* + * Copyright 2005-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 + */ + +#ifndef RNG_RNGA_H +#define RNG_RNGA_H + +/*! @defgroup rngaregs RNGA Registers + * @ingroup RNG + * These are the definitions for the RNG registers and their offsets + * within the RNG. They are used in the @c register_offset parameter of + * #rng_read_register() and #rng_write_register(). + */ +/*! @addtogroup rngaregs */ +/*! @{ */ + +/*! Control Register. See @ref rngacontrolreg. */ +#define RNGA_CONTROL 0x00 +/*! Status Register. See @ref rngastatusreg. */ +#define RNGA_STATUS 0x04 +/*! Register for adding to the Entropy of the RNG */ +#define RNGA_ENTROPY 0x08 +/*! Register containing latest 32 bits of random value */ +#define RNGA_OUTPUT_FIFO 0x0c +/*! Mode Register. Non-secure mode access only. See @ref rngmodereg. */ +#define RNGA_MODE 0x10 +/*! Verification Control Register. Non-secure mode access only. See + * @ref rngvfctlreg. */ +#define RNGA_VERIFICATION_CONTROL 0x14 +/*! Oscillator Control Counter Register. Non-secure mode access only. + * See @ref rngosccntctlreg. */ +#define RNGA_OSCILLATOR_CONTROL_COUNTER 0x18 +/*! Oscillator 1 Counter Register. Non-secure mode access only. See + * @ref rngosccntreg. */ +#define RNGA_OSCILLATOR1_COUNTER 0x1c +/*! Oscillator 2 Counter Register. Non-secure mode access only. See + * @ref rngosccntreg. */ +#define RNGA_OSCILLATOR2_COUNTER 0x20 +/*! Oscillator Counter Status Register. Non-secure mode access only. See + * @ref rngosccntstatreg. */ +#define RNGA_OSCILLATOR_COUNTER_STATUS 0x24 +/*! @} */ + +/*! Total address space of the RNGA, in bytes */ +#define RNG_ADDRESS_RANGE 0x28 + +/*! @defgroup rngacontrolreg RNGA Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngacontrolreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGA_CONTROL_ZEROS_MASK 0x0fffffe0 +/*! 'RNG type' - should be 0 for RNGA */ +#define RNGA_CONTROL_RNG_TYPE_MASK 0xf0000000 +/*! Number of bits to shift the type to get it to LSB */ +#define RNGA_CONTROL_RNG_TYPE_SHIFT 28 +/*! Put RNG to sleep */ +#define RNGA_CONTROL_SLEEP 0x00000010 +/*! Clear interrupt & status */ +#define RNGA_CONTROL_CLEAR_INTERRUPT 0x00000008 +/*! Mask interrupt generation */ +#define RNGA_CONTROL_MASK_INTERRUPTS 0x00000004 +/*! Enter into Secure Mode. Notify SCC of security violation should FIFO + * underflow occur. */ +#define RNGA_CONTROL_HIGH_ASSURANCE 0x00000002 +/*! Load data into FIFO */ +#define RNGA_CONTROL_GO 0x00000001 +/*! @} */ + +/*! @defgroup rngastatusreg RNGA Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngastatusreg */ +/*! @{ */ +/*! RNG Oscillator not working */ +#define RNGA_STATUS_OSCILLATOR_DEAD 0x80000000 +/*! These bits are undefined or reserved */ +#define RNGA_STATUS_ZEROS1_MASK 0x7f000000 +/*! How big FIFO is, in bytes */ +#define RNGA_STATUS_OUTPUT_FIFO_SIZE_MASK 0x00ff0000 +/*! How many bits right to shift fifo size to make it LSB */ +#define RNGA_STATUS_OUTPUT_FIFO_SIZE_SHIFT 16 +/*! How many bytes are currently in the FIFO */ +#define RNGA_STATUS_OUTPUT_FIFO_LEVEL_MASK 0x0000ff00 +/*! How many bits right to shift fifo level to make it LSB */ +#define RNGA_STATUS_OUTPUT_FIFO_LEVEL_SHIFT 8 +/*! These bits are undefined or reserved. */ +#define RNGA_STATUS_ZEROS2_MASK 0x000000e0 +/*! RNG is sleeping. */ +#define RNGA_STATUS_SLEEP 0x00000010 +/*! Error detected. */ +#define RNGA_STATUS_ERROR_INTERRUPT 0x00000008 +/*! FIFO was empty on some read since last status read. */ +#define RNGA_STATUS_FIFO_UNDERFLOW 0x00000004 +/*! FIFO was empty on most recent read. */ +#define RNGA_STATUS_LAST_READ_STATUS 0x00000002 +/*! Security violation occurred. Will only happen in High Assurance mode. */ +#define RNGA_STATUS_SECURITY_VIOLATION 0x00000001 +/*! @} */ + +/*! @defgroup rngmodereg RNG Mode Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngmodereg */ +/*! @{ */ +/*! These bits are undefined or reserved */ +#define RNGA_MODE_ZEROS_MASK 0xfffffffc +/*! RNG is in / put RNG in Oscillator Frequency Test Mode. */ +#define RNGA_MODE_OSCILLATOR_FREQ_TEST 0x00000002 +/*! Put RNG in verification mode / RNG is in verification mode. */ +#define RNGA_MODE_VERIFICATION 0x00000001 +/*! @} */ + +/*! @defgroup rngvfctlreg RNG Verification Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngvfctlreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_VFCTL_ZEROS_MASK 0xfffffff8 +/*! Reset the shift registers. */ +#define RNGA_VFCTL_RESET_SHIFT_REGISTERS 0x00000004 +/*! Drive shift registers from system clock. */ +#define RNGA_VFCTL_FORCE_SYSTEM_CLOCK 0x00000002 +/*! Turn off shift register clocks. */ +#define RNGA_VFCTL_SHIFT_CLOCK_OFF 0x00000001 +/*! @} */ + +/*! + * @defgroup rngosccntctlreg RNG Oscillator Counter Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntctlreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_OSCCR_ZEROS_MASK 0xfffc0000 +/*! Bits containing clock cycle counter */ +#define RNGA_OSCCR_CLOCK_CYCLES_MASK 0x0003ffff +/*! Bits to shift right RNG_OSCCR_CLOCK_CYCLES_MASK */ +#define RNGA_OSCCR_CLOCK_CYCLES_SHIFT 0 +/*! @} */ + +/*! + * @defgroup rngosccntreg RNG Oscillator (1 and 2) Counter Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_COUNTER_ZEROS_MASK 0xfff00000 +/*! Bits containing number of clock pulses received from the oscillator. */ +#define RNGA_COUNTER_PULSES_MASK 0x000fffff +/*! Bits right to shift RNG_COUNTER_PULSES_MASK to make it LSB. */ +#define RNGA_COUNTER_PULSES_SHIFT 0 +/*! @} */ + +/*! + * @defgroup rngosccntstatreg RNG Oscillator Counter Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngosccntstatreg */ +/*! @{ */ +/*! These bits are undefined or reserved. */ +#define RNGA_COUNTER_STATUS_ZEROS_MASK 0xfffffffc +/*! Oscillator 2 has toggled 0x400 times */ +#define RNGA_COUNTER_STATUS_OSCILLATOR2 0x00000002 +/*! Oscillator 1 has toggled 0x400 times */ +#define RNGA_COUNTER_STATUS_OSCILLATOR1 0x00000001 +/*! @} */ + +#endif /* RNG_RNGA_H */ diff --git a/drivers/mxc/security/rng/include/rng_rngc.h b/drivers/mxc/security/rng/include/rng_rngc.h new file mode 100644 index 000000000000..68effa622b87 --- /dev/null +++ b/drivers/mxc/security/rng/include/rng_rngc.h @@ -0,0 +1,235 @@ +/* + * Copyright 2005-2009 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 rng_rngc.h + * + * Definition of the registers for the RNGB and RNGC. The names start with + * RNGC where they are in common or relate only to the RNGC; the RNGB-only + * definitions begin with RNGB. + * + */ + +#ifndef RNG_RNGC_H +#define RNG_RNGC_H + +#define RNGC_VERSION_MAJOR3 3 + +/*! @defgroup rngcregs RNGB/RNGC Registers + * These are the definitions for the RNG registers and their offsets + * within the RNG. They are used in the @c register_offset parameter of + * #rng_read_register() and #rng_write_register(). + * + * @ingroup RNG + */ +/*! @addtogroup rngcregs */ +/*! @{ */ + +/*! RNGC Version ID Register R/W */ +#define RNGC_VERSION_ID 0x0000 +/*! RNGC Command Register R/W */ +#define RNGC_COMMAND 0x0004 +/*! RNGC Control Register R/W */ +#define RNGC_CONTROL 0x0008 +/*! RNGC Status Register R */ +#define RNGC_STATUS 0x000C +/*! RNGC Error Status Register R */ +#define RNGC_ERROR 0x0010 +/*! RNGC FIFO Register W */ +#define RNGC_FIFO 0x0014 +/*! Undefined */ +#define RNGC_UNDEF_18 0x0018 +/*! RNGB Entropy Register W */ +#define RNGB_ENTROPY 0x0018 +/*! Undefined */ +#define RNGC_UNDEF_1C 0x001C +/*! RNGC Verification Control Register1 R/W */ +#define RNGC_VERIFICATION_CONTROL 0x0020 +/*! Undefined */ +#define RNGC_UNDEF_24 0x0024 +/*! RNGB XKEY Data Register R */ +#define RNGB_XKEY 0x0024 +/*! RNGC Oscillator Counter Control Register1 R/W */ +#define RNGC_OSC_COUNTER_CONTROL 0x0028 +/*! RNGC Oscillator Counter Register1 R */ +#define RNGC_OSC_COUNTER 0x002C +/*! RNGC Oscillator Counter Status Register1 R */ +#define RNGC_OSC_COUNTER_STATUS 0x0030 +/*! @} */ + +/*! @defgroup rngcveridreg RNGB/RNGC Version ID Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcveridreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGC_VERID_ZEROS_MASK 0x0f000000 +/*! Mask for RNG TYPE */ +#define RNGC_VERID_RNG_TYPE_MASK 0xf0000000 +/*! Shift to make RNG TYPE be LSB */ +#define RNGC_VERID_RNG_TYPE_SHIFT 28 +/*! Mask for RNG Chip Version */ +#define RNGC_VERID_CHIP_VERSION_MASK 0x00ff0000 +/*! Shift to make RNG Chip version be LSB */ +#define RNGC_VERID_CHIP_VERSION_SHIFT 16 +/*! Mask for RNG Major Version */ +#define RNGC_VERID_VERSION_MAJOR_MASK 0x0000ff00 +/*! Shift to make RNG Major version be LSB */ +#define RNGC_VERID_VERSION_MAJOR_SHIFT 8 +/*! Mask for RNG Minor Version */ +#define RNGC_VERID_VERSION_MINOR_MASK 0x000000ff +/*! Shift to make RNG Minor version be LSB */ +#define RNGC_VERID_VERSION_MINOR_SHIFT 0 +/*! @} */ + +/*! @defgroup rngccommandreg RNGB/RNGC Command Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngccommandreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved. */ +#define RNGC_COMMAND_ZEROS_MASK 0xffffff8c +/*! Perform a software reset of the RNGC. */ +#define RNGC_COMMAND_SOFTWARE_RESET 0x00000040 +/*! Clear error from Error Status register (and interrupt). */ +#define RNGC_COMMAND_CLEAR_ERROR 0x00000020 +/*! Clear interrupt & status. */ +#define RNGC_COMMAND_CLEAR_INTERRUPT 0x00000010 +/*! Start RNGC seed generation. */ +#define RNGC_COMMAND_SEED 0x00000002 +/*! Perform a self test of (and reset) the RNGC. */ +#define RNGC_COMMAND_SELF_TEST 0x00000001 +/*! @} */ + +/*! @defgroup rngccontrolreg RNGB/RNGC Control Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngccontrolreg */ +/*! @{ */ +/*! These bits are unimplemented or reserved */ +#define RNGC_CONTROL_ZEROS_MASK 0xfffffc8c +/*! Allow access to verification registers. */ +#define RNGC_CONTROL_CTL_ACC 0x00000200 +/*! Put RNGC into deterministic verifcation mode. */ +#define RNGC_CONTROL_VERIF_MODE 0x00000100 +/*! Prevent RNGC from generating interrupts caused by errors. */ +#define RNGC_CONTROL_MASK_ERROR 0x00000040 + +/*! + * Prevent RNGB/RNGC from generating interrupts after Seed Done or Self Test + * Mode completion. + */ +#define RNGC_CONTROL_MASK_DONE 0x00000020 +/*! Allow RNGC to generate a new seed whenever it is needed. */ +#define RNGC_CONTROL_AUTO_SEED 0x00000010 +/*! Set FIFO Underflow Response.*/ +#define RNGC_CONTROL_FIFO_UFLOW_MASK 0x00000003 +/*! Shift value to make FIFO Underflow Response be LSB. */ +#define RNGC_CONTROL_FIFO_UFLOW_SHIFT 0 + +/*! @} */ + +/*! @{ */ +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_ERROR 0 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_ERROR2 1 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_BUS_XFR 2 +/*! FIFO Underflow should cause ... */ +#define RNGC_CONTROL_FIFO_UFLOW_ZEROS_INTR 3 +/*! @} */ + +/*! @defgroup rngcstatusreg RNGB/RNGC Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcstatusreg */ +/*! @{ */ +/*! Unused or MBZ. */ +#define RNGC_STATUS_ZEROS_MASK 0x003e0080 +/*! + * Statistical tests pass-fail. Individual bits on indicate failure of a + * particular test. + */ +#define RNGC_STATUS_STAT_TEST_PF_MASK 0xff000000 +/*! Mask to get Statistical PF to be LSB. */ +#define RNGC_STATUS_STAT_TEST_PF_SHIFT 24 +/*! + * Self tests pass-fail. Individual bits on indicate failure of a + * particular test. + */ +#define RNGC_STATUS_ST_PF_MASK 0x00c00000 +/*! Shift value to get Self Test PF field to be LSB. */ +#define RNGC_STATUS_ST_PF_SHIFT 22 +/* TRNG Self test pass-fail */ +#define RNGC_STATUS_ST_PF_TRNG 0x00800000 +/* PRNG Self test pass-fail */ +#define RNGC_STATUS_ST_PF_PRNG 0x00400000 +/*! Error detected in RNGC. See Error Status register. */ +#define RNGC_STATUS_ERROR 0x00010000 +/*! Size of the internal FIFO in 32-bit words. */ +#define RNGC_STATUS_FIFO_SIZE_MASK 0x0000f000 +/*! Shift value to get FIFO Size to be LSB. */ +#define RNGC_STATUS_FIFO_SIZE_SHIFT 12 +/*! The level (available data) of the internal FIFO in 32-bit words. */ +#define RNGC_STATUS_FIFO_LEVEL_MASK 0x00000f00 +/*! Shift value to get FIFO Level to be LSB. */ +#define RNGC_STATUS_FIFO_LEVEL_SHIFT 8 +/*! A new seed is ready for use. */ +#define RNGC_STATUS_NEXT_SEED_DONE 0x00000040 +/*! The first seed has been generated. */ +#define RNGC_STATUS_SEED_DONE 0x00000020 +/*! Self Test has been completed. */ +#define RNGC_STATUS_ST_DONE 0x00000010 +/*! Reseed is necessary. */ +#define RNGC_STATUS_RESEED 0x00000008 +/*! RNGC is sleeping. */ +#define RNGC_STATUS_SLEEP 0x00000004 +/*! RNGC is currently generating numbers, seeding, generating next seed, or + performing a self test. */ +#define RNGC_STATUS_BUSY 0x00000002 +/*! RNGC is in secure state. */ +#define RNGC_STATUS_SEC_STATE 0x00000001 + +/*! @} */ + +/*! @defgroup rngcerrstatusreg RNGB/RNGC Error Status Register Definitions + * @ingroup RNG + */ +/*! @addtogroup rngcerrstatusreg */ +/*! @{ */ +/*! Unused or MBZ. */ +#define RNGC_ERROR_STATUS_ZEROS_MASK 0xffffff80 +/*! Bad Key Error Status */ +#define RNGC_ERROR_STATUS_BAD_KEY 0x00000040 +/*! Random Compare Error. Previous number matched the current number. */ +#define RNGC_ERROR_STATUS_RAND_ERR 0x00000020 +/*! FIFO Underflow. FIFO was read while empty. */ +#define RNGC_ERROR_STATUS_FIFO_ERR 0x00000010 +/*! Statistic Error Statistic Test failed for the last seed. */ +#define RNGC_ERROR_STATUS_STAT_ERR 0x00000008 +/*! Self-test error. Some self test has failed. */ +#define RNGC_ERROR_STATUS_ST_ERR 0x00000004 +/*! + * Oscillator Error. The oscillator may be broken. Clear by hard or soft + * reset. + */ +#define RNGC_ERROR_STATUS_OSC_ERR 0x00000002 +/*! LFSR Error. Clear by hard or soft reset. */ +#define RNGC_ERROR_STATUS_LFSR_ERR 0x00000001 + +/*! @} */ + +/*! Total address space of the RNGB/RNGC registers, in bytes */ +#define RNG_ADDRESS_RANGE 0x34 + +#endif /* RNG_RNGC_H */ diff --git a/drivers/mxc/security/rng/include/shw_driver.h b/drivers/mxc/security/rng/include/shw_driver.h new file mode 100644 index 000000000000..cdf0cb3b1b0f --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_driver.h @@ -0,0 +1,2971 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +#ifndef SHW_DRIVER_H +#define SHW_DRIVER_H + +/* This is a Linux flag meaning 'compiling kernel code'... */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <memory.h> +#include <stdio.h> +#else +#include "../../sahara2/include/portable_os.h" +#endif /* __KERNEL__ */ + +#include "../../sahara2/include/fsl_platform.h" + +/*! @file shw_driver.h + * + * @brief Header file to use the SHW driver. + * + * The SHW driver is used in two modes: By a user, from the FSL SHW API in user + * space, which goes through /dev/fsl_shw to make open(), ioctl(), and close() + * calls; and by other kernel modules/drivers, which use the FSL SHW API, parts + * of which are supported directly by the SHW driver. + * + * Testing is performed by using the apitest and kernel api test routines + * developed for the Sahara2 driver. + */ +/*#define DIAG_SECURITY_FUNC*/ +/*! Perform a security function. */ +#define SHW_IOCTL_REQUEST 21 + +/* This definition may need a new name, and needs to go somewhere which + * can determine platform, kernel vs. user, os, etc. + */ +#define copy_bytes(out, in, len) memcpy(out, in, len) + + +/*! + * This is part of the IOCTL request type passed between kernel and user space. + * It is added to #SHW_IOCTL_REQUEST to generate the actual value. + */ +typedef enum shw_user_request_t { + SHW_USER_REQ_REGISTER_USER, /*!< Initialize user-kernel discussion. */ + SHW_USER_REQ_DEREGISTER_USER, /*!< Terminate user-kernel discussion. */ + SHW_USER_REQ_GET_RESULTS, /*!< Get information on outstanding + results. */ + SHW_USER_REQ_GET_CAPABILITIES, /*!< Get information on hardware support. */ + SHW_USER_REQ_GET_RANDOM, /*!< Get random data from RNG. */ + SHW_USER_REQ_ADD_ENTROPY, /*!< Add entropy to hardware RNG. */ + SHW_USER_REQ_DROP_PERMS, /*!< Diminish the permissions of a block of + secure memory */ + SHW_USER_REQ_SSTATUS, /*!< Check the status of a block of secure + memory */ + SHW_USER_REQ_SFREE, /*!< Free a block of secure memory */ + SHW_USER_REQ_SCC_ENCRYPT, /*!< Encrypt a region of user-owned secure + memory */ + SHW_USER_REQ_SCC_DECRYPT, /*!< Decrypt a region of user-owned secure + memory */ +} shw_user_request_t; + + +/*! + * @typedef scc_partition_status_t + */ +/** Partition status information. */ +typedef enum fsl_shw_partition_status_t { + FSL_PART_S_UNUSABLE, /*!< Partition not implemented */ + FSL_PART_S_UNAVAILABLE, /*!< Partition owned by other host */ + FSL_PART_S_AVAILABLE, /*!< Partition available */ + FSL_PART_S_ALLOCATED, /*!< Partition owned by host but not engaged + */ + FSL_PART_S_ENGAGED, /*!< Partition owned by host and engaged */ +} fsl_shw_partition_status_t; + + +/* + * Structure passed during user ioctl() calls to manage secure partitions. + */ +typedef struct scc_partition_info_t { + uint32_t user_base; /*!< Userspace pointer to base of partition */ + uint32_t permissions; /*!< Permissions to give the partition (only + used in call to _DROP_PERMS) */ + fsl_shw_partition_status_t status; /*!< Status of the partition */ +} scc_partition_info_t; + + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + */ +typedef enum fsl_shw_user_ctx_flags_t +{ + /*! + * API will block the caller until operation completes. The result will be + * available in the return code. If this is not set, user will have to get + * results using #fsl_shw_get_results(). + */ + FSL_UCO_BLOCKING_MODE = 0x01, + /*! + * User wants callback (at the function specified with + * #fsl_shw_uco_set_callback()) when the operation completes. This flag is + * valid only if #FSL_UCO_BLOCKING_MODE is not set. + */ + FSL_UCO_CALLBACK_MODE = 0x02, + /*! Do not free descriptor chain after driver (adaptor) finishes */ + FSL_UCO_SAVE_DESC_CHAIN = 0x04, + /*! + * User has made at least one request with callbacks requested, so API is + * ready to handle others. + */ + FSL_UCO_CALLBACK_SETUP_COMPLETE = 0x08, + /*! + * (virtual) pointer to descriptor chain is completely linked with physical + * (DMA) addresses, ready for the hardware. This flag should not be used + * by FSL SHW API programs. + */ + FSL_UCO_CHAIN_PREPHYSICALIZED = 0x10, + /*! + * The user has changed the context but the changes have not been copied to + * the kernel driver. + */ + FSL_UCO_CONTEXT_CHANGED = 0x20, + /*! Internal Use. This context belongs to a user-mode API user. */ + FSL_UCO_USERMODE_USER = 0x40, +} fsl_shw_user_ctx_flags_t; + + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +/* REQ-FSLSHW-ERR-001 */ +typedef enum fsl_shw_return_t +{ + /*! + * No error. As a function return code in Non-blocking mode, this may + * simply mean that the operation was accepted for eventual execution. + */ + FSL_RETURN_OK_S = 0, + /*! Failure for non-specific reason. */ + FSL_RETURN_ERROR_S, + /*! + * Operation failed because some resource was not able to be allocated. + */ + FSL_RETURN_NO_RESOURCE_S, + /*! Crypto algorithm unrecognized or improper. */ + FSL_RETURN_BAD_ALGORITHM_S, + /*! Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_MODE_S, + /*! Flag setting unrecognized or inconsistent. */ + FSL_RETURN_BAD_FLAG_S, + /*! Improper or unsupported key length for algorithm. */ + FSL_RETURN_BAD_KEY_LENGTH_S, + /*! Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_KEY_PARITY_S, + /*! + * Improper or unsupported data length for algorithm or internal buffer. + */ + FSL_RETURN_BAD_DATA_LENGTH_S, + /*! Authentication / Integrity Check code check failed. */ + FSL_RETURN_AUTH_FAILED_S, + /*! A memory error occurred. */ + FSL_RETURN_MEMORY_ERROR_S, + /*! An error internal to the hardware occurred. */ + FSL_RETURN_INTERNAL_ERROR_S, + /*! ECC detected Point at Infinity */ + FSL_RETURN_POINT_AT_INFINITY_S, + /*! ECC detected No Point at Infinity */ + FSL_RETURN_POINT_NOT_AT_INFINITY_S, + /*! GCD is One */ + FSL_RETURN_GCD_IS_ONE_S, + /*! GCD is not One */ + FSL_RETURN_GCD_IS_NOT_ONE_S, + /*! Candidate is Prime */ + FSL_RETURN_PRIME_S, + /*! Candidate is not Prime */ + FSL_RETURN_NOT_PRIME_S, + /*! N register loaded improperly with even value */ + FSL_RETURN_EVEN_MODULUS_ERROR_S, + /*! Divisor is zero. */ + FSL_RETURN_DIVIDE_BY_ZERO_ERROR_S, + /*! Bad Exponent or Scalar value for Point Multiply */ + FSL_RETURN_BAD_EXPONENT_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_OSCILLATOR_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_STATISTICS_ERROR_S, +} fsl_shw_return_t; + + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t +{ + /*! + * Key will be used to perform an HMAC. Key size is 1 to 64 octets. Block + * size is 64 octets. + */ + FSL_KEY_ALG_HMAC, + /*! + * Advanced Encryption Standard (Rijndael). Block size is 16 octets. Key + * size is 16 octets. (The single choice of key size is a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_AES, + /*! + * Data Encryption Standard. Block size is 8 octets. Key size is 8 + * octets. + */ + FSL_KEY_ALG_DES, + /*! + * 2- or 3-key Triple DES. Block size is 8 octets. Key size is 16 octets + * for 2-key Triple DES, and 24 octets for 3-key. + */ + FSL_KEY_ALG_TDES, + /*! + * ARC4. No block size. Context size is 259 octets. Allowed key size is + * 1-16 octets. (The choices for key size are a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_ARC4, +} fsl_shw_key_alg_t; + + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t +{ + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg_t +{ + FSL_HASH_ALG_MD5, /*!< MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_SHA1, /*!< SHA-1 (aka SHA or SHA-160) algorithm. + Digest is 20 octets. */ + FSL_HASH_ALG_SHA224, /*!< SHA-224 algorithm. Digest is 28 octets, + though context is 32 octets. */ + FSL_HASH_ALG_SHA256 /*!< SHA-256 algorithm. Digest is 32 + octets. */ +} fsl_shw_hash_alg_t; + + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t +{ + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL +} fsl_shw_acc_mode_t; + + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + */ +typedef enum fsl_shw_hash_ctx_flags_t +{ + FSL_HASH_FLAGS_INIT = 0x01, /*!< Context is empty. Hash is started + from scratch, with a message-processed + count of zero. */ + FSL_HASH_FLAGS_SAVE = 0x02, /*!< Retrieve context from hardware after + hashing. If used with the + #FSL_HASH_FLAGS_FINALIZE flag, the final + digest value will be saved in the + object. */ + FSL_HASH_FLAGS_LOAD = 0x04, /*!< Place context into hardware before + hashing. */ + FSL_HASH_FLAGS_FINALIZE = 0x08, /*!< PAD message and perform final digest + operation. If user message is + pre-padded, this flag should not be + used. */ +} fsl_shw_hash_ctx_flags_t; + + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t +{ + FSL_HMAC_FLAGS_INIT = 1, /**< Message context is empty. HMAC is + started from scratch (with key) or from + precompute of inner hash, depending on + whether + #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is + set. */ + FSL_HMAC_FLAGS_SAVE = 2, /**< Retrieve ongoing context from hardware + after hashing. If used with the + #FSL_HMAC_FLAGS_FINALIZE flag, the final + digest value (HMAC) will be saved in the + object. */ + FSL_HMAC_FLAGS_LOAD = 4, /**< Place ongoing context into hardware + before hashing. */ + FSL_HMAC_FLAGS_FINALIZE = 8, /**< PAD message and perform final HMAC + operations of inner and outer hashes. */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16 /**< This means that the context + contains precomputed inner and outer + hash values. */ +} fsl_shw_hmac_ctx_flags_t; + + +/** + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t +{ + /** + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /** + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /** + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /** + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + + +/** + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t +{ + FSL_SKO_KEY_IGNORE_PARITY = 1, /*!< If algorithm is DES or 3DES, do not + validate the key parity bits. */ + FSL_SKO_KEY_PRESENT = 2, /*!< Clear key is present in the object. */ + FSL_SKO_KEY_ESTABLISHED = 4, /*!< Key has been established for use. This + feature is not available for all + platforms, nor for all algorithms and + modes.*/ + FSL_SKO_USE_SECRET_KEY = 8, /*!< Use device-unique key. Not always + available. */ + FSL_SKO_KEY_SW_KEY = 16, /*!< Clear key can be provided to the user */ + FSL_SKO_KEY_SELECT_PF_KEY = 32, /*!< Internal flag to show that this key + references one of the hardware keys, and + its value is in pf_key. */ +} fsl_shw_key_flags_t; + + +/** + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + + +/** + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t +{ + FSL_ACCO_CTX_INIT = 1, /**< Initialize Context(s) */ + FSL_ACCO_CTX_LOAD = 2, /**< Load intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, /**< Save intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_FINALIZE = 8, /**< Create MAC during this operation. */ + FSL_ACCO_NIST_CCM = 0x10, /**< Formatting of CCM input data is + performed by calls to + #fsl_shw_ccm_nist_format_ctr_and_iv() and + #fsl_shw_ccm_nist_update_ctr_and_iv(). */ +} fsl_shw_auth_ctx_flags_t; + + +/** + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t +{ + FSL_KEY_WRAP_CREATE, /**< Generate a key from random values. */ + FSL_KEY_WRAP_ACCEPT, /**< Use the provided clear key. */ + FSL_KEY_WRAP_UNWRAP /**< Unwrap a previously wrapped key. */ +} fsl_shw_key_wrap_t; + + +/** + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod_t +{ + FSL_CTR_MOD_8, /**< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /**< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /**< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /**< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /**< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /**< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /**< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /**< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /**< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /**< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /**< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /**< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /**< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /**< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /**< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /**< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + + +/** + * A work type associated with a work/result queue request. + */ +typedef enum shw_work_type_t +{ + SHW_WORK_GET_RANDOM = 1, /**< fsl_shw_get_random() request. */ + SHW_WORK_ADD_RANDOM, /**< fsl_shw_add_entropy() request. */ +} shw_work_type_t; + + +/** + * Permissions flags for Secure Partitions + */ +typedef enum fsl_shw_permission_t +{ +/** SCM Access Permission: Do not zeroize/deallocate partition on SMN Fail state */ + FSL_PERM_NO_ZEROIZE = 0x80000000, +/** SCM Access Permission: Enforce trusted key read in */ + FSL_PERM_TRUSTED_KEY_READ = 0x40000000, +/** SCM Access Permission: Ignore Supervisor/User mode in permission determination */ + FSL_PERM_HD_S = 0x00000800, +/** SCM Access Permission: Allow Read Access to Host Domain */ + FSL_PERM_HD_R = 0x00000400, +/** SCM Access Permission: Allow Write Access to Host Domain */ + FSL_PERM_HD_W = 0x00000200, +/** SCM Access Permission: Allow Execute Access to Host Domain */ + FSL_PERM_HD_X = 0x00000100, +/** SCM Access Permission: Allow Read Access to Trusted Host Domain */ + FSL_PERM_TH_R = 0x00000040, +/** SCM Access Permission: Allow Write Access to Trusted Host Domain */ + FSL_PERM_TH_W = 0x00000020, +/** SCM Access Permission: Allow Read Access to Other/World Domain */ + FSL_PERM_OT_R = 0x00000004, +/** SCM Access Permission: Allow Write Access to Other/World Domain */ + FSL_PERM_OT_W = 0x00000002, +/** SCM Access Permission: Allow Execute Access to Other/World Domain */ + FSL_PERM_OT_X = 0x00000001, +} fsl_shw_permission_t; + +/*! + * Select the cypher mode to use for partition cover/uncover operations. + * + * They currently map directly to the values used in the SCC2 driver, but this + * is not guarinteed behavior. + */ +typedef enum fsl_shw_cypher_mode_t +{ + FSL_SHW_CYPHER_MODE_ECB = 1, /*!< ECB mode */ + FSL_SHW_CYPHER_MODE_CBC = 2, /*!< CBC mode */ +} fsl_shw_cypher_mode_t; + +/*! + * Which platform key should be presented for cryptographic use. + */ +typedef enum fsl_shw_pf_key_t { + FSL_SHW_PF_KEY_IIM, /*!< Present fused IIM key */ + FSL_SHW_PF_KEY_PRG, /*!< Present Program key */ + FSL_SHW_PF_KEY_IIM_PRG, /*!< Present IIM ^ Program key */ + FSL_SHW_PF_KEY_IIM_RND, /*!< Present Random key */ + FSL_SHW_PF_KEY_RND, /*!< Present IIM ^ Random key */ +} fsl_shw_pf_key_t; + +/*! + * The various security tamper events + */ +typedef enum fsl_shw_tamper_t { + FSL_SHW_TAMPER_NONE, /*!< No error detected */ + FSL_SHW_TAMPER_WTD, /*!< wire-mesh tampering det */ + FSL_SHW_TAMPER_ETBD, /*!< ext tampering det: input B */ + FSL_SHW_TAMPER_ETAD, /*!< ext tampering det: input A */ + FSL_SHW_TAMPER_EBD, /*!< external boot detected */ + FSL_SHW_TAMPER_SAD, /*!< security alarm detected */ + FSL_SHW_TAMPER_TTD, /*!< temperature tampering det */ + FSL_SHW_TAMPER_CTD, /*!< clock tampering det */ + FSL_SHW_TAMPER_VTD, /*!< voltage tampering det */ + FSL_SHW_TAMPER_MCO, /*!< monotonic counter overflow */ + FSL_SHW_TAMPER_TCO, /*!< time counter overflow */ +} fsl_shw_tamper_t; + +/* + * Structure passed during user ioctl() calls to manage data stored in secure + * partitions. + */ + +typedef struct scc_region_t { + uint32_t partition_base; /*!< Base address of partition */ + uint32_t offset; /*!< Byte offset into partition */ + uint32_t length; /*!< Number of bytes in request */ + uint8_t *black_data; /*!< Address of cipher text */ + uint64_t owner_id; /*!< user's secret */ + fsl_shw_cypher_mode_t cypher_mode; /*!< ECB or CBC */ + uint32_t IV[4]; /*!< IV for CBC mode */ +} scc_region_t; + +/****************************************************************************** + * Data Structures + *****************************************************************************/ + +/** + * Initialization Object + */ +typedef struct fsl_sho_ibo +{ +} fsl_sho_ibo_t; + + +/** + * Common Entry structure for work queues, results queues. + */ +typedef struct shw_queue_entry_t { + struct shw_queue_entry_t* next; /**< Next entry in queue. */ + struct fsl_shw_uco_t* user_ctx; /**< Associated user context. */ + uint32_t flags; /**< User context flags at time of request. */ + void (*callback)(struct fsl_shw_uco_t* uco); /**< Any callback request. */ + uint32_t user_ref; /**< User's reference for this request. */ + fsl_shw_return_t code; /**< FSL SHW result of this operation. */ + uint32_t detail1; /**< Any extra error info. */ + uint32_t detail2; /**< More any extra error info. */ + void* user_mode_req; /**< Pointer into user space. */ + uint32_t (*postprocess)(struct shw_queue_entry_t* q); /**< (internal) + function to call + when this operation + completes. + */ +} shw_queue_entry_t; + + +/** + * A queue. Fields must be initialized to NULL before use. + */ +typedef struct shw_queue_t +{ + struct shw_queue_entry_t* head; /**< First entry in queue. */ + struct shw_queue_entry_t* tail; /**< Last entry. */ +} shw_queue_t; + + +/** + * Secure Partition information + */ +typedef struct fsl_shw_spo_t +{ + uint32_t user_base; + void* kernel_base; + struct fsl_shw_spo_t* next; +} fsl_shw_spo_t; + + +/* REQ-FSLSHW-PINTFC-COA-UCO-001 */ +/** + * User Context Object + */ +typedef struct fsl_shw_uco_t +{ + int openfd; /**< user-mode file descriptor */ + uint32_t user_ref; /**< User's reference */ + void (*callback)(struct fsl_shw_uco_t* uco); /**< User's callback fn */ + uint32_t flags; /**< from fsl_shw_user_ctx_flags_t */ + unsigned pool_size; /**< maximum size of user result pool */ +#ifdef __KERNEL__ + shw_queue_t result_pool; /**< where non-blocking results go */ + os_process_handle_t process; /**< remember for signalling User mode */ + fsl_shw_spo_t* partition; /**< chain of secure partitions owned by + the user */ +#endif + struct fsl_shw_uco_t* next; /**< To allow user-mode chaining of contexts, + for signalling and in kernel, to link user + contexts. */ + fsl_shw_pf_key_t wrap_key; /*!< What key for ciphering T */ +} fsl_shw_uco_t; + + +/* REQ-FSLSHW-PINTFC-API-GEN-006 ?? */ +/** + * Result object + */ +typedef struct fsl_shw_result_t +{ + uint32_t user_ref; /**< User's reference at time of request. */ + fsl_shw_return_t code; /**< Return code from request. */ + uint32_t detail1; /**< Extra error info. Unused in SHW driver. */ + uint32_t detail2; /**< Extra error info. Unused in SHW driver. */ + void* user_req; /**< Pointer to original user request. */ +} fsl_shw_result_t; + + +/** + * Keystore Object + */ +typedef struct fsl_shw_kso_t +{ +#ifdef __KERNEL__ + os_lock_t lock; /**< Pointer to lock that controls access to + the keystore. */ +#endif + void* user_data; /**< Pointer to user structure that handles + the internals of the keystore. */ + fsl_shw_return_t (*data_init) (fsl_shw_uco_t* user_ctx, + void** user_data); + void (*data_cleanup) (fsl_shw_uco_t* user_ctx, + void** user_data); + fsl_shw_return_t (*slot_verify_access)(void* user_data, uint64_t owner_id, + uint32_t slot); + fsl_shw_return_t (*slot_alloc) (void* user_data, uint32_t size_bytes, + uint64_t owner_id, uint32_t* slot); + fsl_shw_return_t (*slot_dealloc) (void* user_data, + uint64_t owner_id, uint32_t slot); + void* (*slot_get_address) (void* user_data, uint32_t slot); + uint32_t (*slot_get_base) (void* user_data, uint32_t slot); + uint32_t (*slot_get_offset) (void* user_data, uint32_t slot); + uint32_t (*slot_get_slot_size) (void* user_data, uint32_t slot); +} fsl_shw_kso_t; + + +/* REQ-FSLSHW-PINTFC-COA-SKO-001 */ +/** + * Secret Key Context Object + */ +typedef struct fsl_shw_sko_t +{ + uint32_t flags; /**< Flags from #fsl_shw_sym_ctx_flags_t. */ + fsl_shw_key_alg_t algorithm; /**< Algorithm for this key. */ + key_userid_t userid; /**< User's identifying value for Black key. */ + uint32_t handle; /**< Reference in SCC driver for Red key. */ + uint16_t key_length; /**< Length of stored key, in bytes. */ + uint8_t key[64]; /**< Bytes of stored key. */ + struct fsl_shw_kso_t* keystore; /**< If present, key is in keystore */ + fsl_shw_pf_key_t pf_key; /*!< What key to select for use when this key + is doing ciphering. If FSL_SHW_PF_KEY_PRG + or FSL_SHW_PF_KEY_PRG_IIM is the value, then + a 'present' or 'established' key will be + programed into the PK. */ +} fsl_shw_sko_t; + + +/* REQ-FSLSHW-PINTFC-COA-CO-001 */ +/** + * Platform Capability Object + * + * Pointer to this structure is returned by fsl_shw_get_capabilities() and + * queried with the various fsl_shw_pco_() functions. + */ +typedef struct fsl_shw_pco_t +{ + int api_major; /**< Major version number for API. */ + int api_minor; /**< Minor version number for API. */ + int driver_major; /**< Major version of some driver. */ + int driver_minor; /**< Minor version of some driver. */ + unsigned sym_algorithm_count; /**< Number of sym_algorithms. */ + fsl_shw_key_alg_t* sym_algorithms; /**< Pointer to array. */ + unsigned sym_mode_count; /**< Number of sym_modes. */ + fsl_shw_sym_mode_t* sym_modes; /**< Pointer to array. */ + unsigned hash_algorithm_count; /**< Number of hash_algorithms. */ + fsl_shw_hash_alg_t* hash_algorithms; /**< Pointer to array */ + uint8_t sym_support[5][4]; /**< indexed by key alg then mode */ + + int scc_driver_major; + int scc_driver_minor; + int scm_version; /**< Version from SCM Configuration register */ + int smn_version; /**< Version from SMN Status register */ + int block_size_bytes; /**< Number of bytes per block of RAM; also + block size of the crypto algorithm. */ + union { + struct scc_info { + int black_ram_size_blocks; /**< Number of blocks of Black RAM */ + int red_ram_size_blocks; /**< Number of blocks of Red RAM */ + } scc_info; + struct scc2_info { + int partition_size_bytes; /**< Number of bytes in each partition */ + int partition_count; /**< Number of partitions on this platform */ + } scc2_info; + } u; +} fsl_shw_pco_t; + + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/** + * Hash Context Object + */ +typedef struct fsl_shw_hco_t /* fsl_shw_hash_context_object */ +{ + fsl_shw_hash_alg_t algorithm; /**< Algorithm for this context. */ + uint32_t flags; /**< Flags from + #fsl_shw_hash_ctx_flags_t. */ + uint8_t digest_length; /**< hash result length in bytes */ + uint8_t context_length; /**< Context length in bytes */ + uint8_t context_register_length; /**< in bytes */ + uint32_t context[9]; /**< largest digest + msg size */ +} fsl_shw_hco_t; + + +/* REQ-FSLSHW-PINTFC-COA-HCO-001 */ +/** + * HMAC Context Object + */ +typedef struct fsl_shw_hmco_t /* fsl_shw_hmac_context_object */ +{ + fsl_shw_hash_alg_t algorithm; /**< Hash algorithm for the HMAC. */ + uint32_t flags; /**< Flags from + #fsl_shw_hmac_ctx_flags_t. */ + uint8_t digest_length; /**< in bytes */ + uint8_t context_length; /**< in bytes */ + uint8_t context_register_length; /**< in bytes */ + uint32_t ongoing_context[9]; /**< largest digest + msg + size */ + uint32_t inner_precompute[9]; /**< largest digest + msg + size */ + uint32_t outer_precompute[9]; /**< largest digest + msg + size */ +} fsl_shw_hmco_t; + + +/* REQ-FSLSHW-PINTFC-COA-SCCO-001 */ +/** + * Symmetric Crypto Context Object Context Object + */ +typedef struct fsl_shw_scco_t +{ + uint32_t flags; /**< Flags from #fsl_shw_sym_ctx_flags_t. */ + unsigned block_size_bytes; /**< Both block and ctx size */ + fsl_shw_sym_mode_t mode; /**< Symmetric mode for this context. */ + /* Could put modulus plus 16-octet context in union with arc4 + sbox+ptrs... */ + fsl_shw_ctr_mod_t modulus_exp; /**< Exponent value for CTR modulus */ + uint8_t context[8]; /**< Stored context. Large enough + for 3DES. */ +} fsl_shw_scco_t; + + +/** + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + */ +typedef struct fsl_shw_acco_t +{ + uint32_t flags; /**< See #fsl_shw_auth_ctx_flags_t for + meanings */ + fsl_shw_acc_mode_t mode; /**< CCM only */ + uint8_t mac_length; /**< User's value for length */ + unsigned q_length; /**< NIST parameter - */ + fsl_shw_scco_t cipher_ctx_info; /**< For running + encrypt/decrypt. */ + union { + fsl_shw_scco_t CCM_ctx_info; /**< For running the CBC in + AES-CCM. */ + fsl_shw_hco_t hash_ctx_info; /**< For running the hash */ + } auth_info; /**< "auth" info struct */ + uint8_t unencrypted_mac[16]; /**< max block size... */ +} fsl_shw_acco_t; + + +/** + * Common header in request structures between User-mode API and SHW driver. + */ +struct shw_req_header { + uint32_t flags; /**< Flags - from user-mode context. */ + uint32_t user_ref; /**< Reference - from user-mode context. */ + fsl_shw_return_t code; /**< Result code for operation. */ +}; + +/** + * Used by user-mode API to retrieve completed non-blocking results in + * SHW_USER_REQ_GET_RESULTS ioctl(). + */ +struct results_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned requested; /**< number of results requested, */ + unsigned actual; /**< number of results obtained. */ + fsl_shw_result_t *results; /**< pointer to memory to hold results. */ +}; + + +/** + * Used by user-mode API to retrieve hardware capabilities in + * SHW_USER_REQ_GET_CAPABILITIES ioctl(). + */ +struct capabilities_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned size; /**< Size, in bytes, capabilities. */ + fsl_shw_pco_t* capabilities; /**< Place to copy out the info. */ +}; + + +/** + * Used by user-mode API to get a random number + */ +struct get_random_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned size; /**< Size, in bytes, of random. */ + uint8_t* random; /**< Place to copy out the random number. */ +}; + + +/** + * Used by API to add entropy to a random number generator + */ +struct add_entropy_req { + struct shw_req_header hdr; /**< Boilerplate. */ + unsigned size; /**< Size, in bytes, of entropy. */ + uint8_t* entropy; /**< Location of the entropy to be added. */ +}; + + +/****************************************************************************** + * External variables + *****************************************************************************/ +#ifdef __KERNEL__ +extern os_lock_t shw_queue_lock; + +extern fsl_shw_uco_t* user_list; +#endif + + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/** + * Get FSL SHW API version + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the API is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the API is to be stored. + */ +#define fsl_shw_pco_get_version(pcobject, pcmajor, pcminor) \ +do { \ + *(pcmajor) = (pcobject)->api_major; \ + *(pcminor) = (pcobject)->api_minor; \ +} while (0) + + +/** + * Get underlying driver version. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the driver is to be stored. + */ +#define fsl_shw_pco_get_driver_version(pcobject, pcmajor, pcminor) \ +do { \ + *(pcmajor) = (pcobject)->driver_major; \ + *(pcminor) = (pcobject)->driver_minor; \ +} while (0) + + +/** + * Get list of symmetric algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcalgorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] pcacount A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +#define fsl_shw_pco_get_sym_algorithms(pcobject, pcalgorithms, pcacount) \ +do { \ + *(pcalgorithms) = (pcobject)->sym_algorithms; \ + *(pcacount) = (pcobject)->sym_algorithm_count; \ +} while (0) + + +/** + * Get list of symmetric modes supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsmodes A pointer to where to store the location of + * the list of modes. + * @param[out] gsacount A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +#define fsl_shw_pco_get_sym_modes(pcobject, gsmodes, gsacount) \ +do { \ + *(gsmodes) = (pcobject)->sym_modes; \ + *(gsacount) = (pcobject)->sym_mode_count; \ +} while (0) + + +/** + * Get list of hash algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsalgorithms A pointer which will be set to the list of + * algorithms. + * @param[out] gsacount The number of algorithms in the list at @a + * algorithms. + */ +#define fsl_shw_pco_get_hash_algorithms(pcobject, gsalgorithms, gsacount) \ +do { \ + *(gsalgorithms) = (pcobject)->hash_algorithms; \ + *(gsacount) = (pcobject)->hash_algorithm_count; \ +} while (0) + + +/** + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcalg A Symmetric Cipher algorithm. + * @param pcmode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + ((pcobject)->sym_support[pcalg][pcmode]) +#else +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + 0 +#endif + +/** + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_auth_supported(pcobject, pcmode) \ + 0 + + +/** + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 1 +#else +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 0 + +#endif + +/*! + * Determine whether Programmed Key features are available + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return 1 if Programmed Key features are available, otherwise zero. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_pk_supported(pcobject) \ + 1 +#else +#define fsl_shw_pco_check_pk_supported(pcobject) \ + 0 +#endif + +/*! + * Determine whether Software Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Software key features are available, otherwise zero. + */ +#if defined(FSL_HAVE_DRYICE) && defined(__KERNEL__) +#define fsl_shw_pco_check_sw_keys_supported(pcobject) \ + 1 +#else +#define fsl_shw_pco_check_sw_keys_supported(pcobject) \ + 0 +#endif + +/*! + * Get FSL SHW SCC driver version + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the SCC driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the SCC driver is to be stored. + */ +#define fsl_shw_pco_get_scc_driver_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->scc_driver_major; \ + *(pcminor) = (pcobject)->scc_driver_minor; \ +} + + +/** + * Get SCM hardware version + * + * @param pcobject The Platform Capababilities Object to query. + * @return The SCM hardware version + */ +#define fsl_shw_pco_get_scm_version(pcobject) \ + ((pcobject)->scm_version) + + +/** + * Get SMN hardware version + * + * @param pcobject The Platform Capababilities Object to query. + * @return The SMN hardware version + */ +#define fsl_shw_pco_get_smn_version(pcobject) \ + ((pcobject)->smn_version) + + +/** + * Get the size of an SCM block, in bytes + * + * @param pcobject The Platform Capababilities Object to query. + * @return The size of an SCM block, in bytes. + */ +#define fsl_shw_pco_get_scm_block_size(pcobject) \ + ((pcobject)->block_size_bytes) + + +/** + * Get size of Black and Red RAM memory + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] black_size A pointer to where the size of the Black RAM, in + * blocks, is to be placed. + * @param[out] red_size A pointer to where the size of the Red RAM, in + * blocks, is to be placed. + */ +#define fsl_shw_pco_get_smn_size(pcobject, black_size, red_size) \ +{ \ + if ((pcobject)->scm_version == 1) { \ + *(black_size) = (pcobject)->u.scc_info.black_ram_size_blocks; \ + *(red_size) = (pcobject)->u.scc_info.red_ram_size_blocks; \ + } else { \ + *(black_size) = 0; \ + *(red_size) = 0; \ + } \ +} + + +/** + * Determine whether Secure Partitions are supported + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if secure partitions are not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_spo_supported(pcobject) \ + ((pcobject)->scm_version == 2) + + +/** + * Get the size of a Secure Partitions + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return Partition size, in bytes. 0 if Secure Partitions not supported. + */ +#define fsl_shw_pco_get_spo_size_bytes(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->u.scc2_info.partition_size_bytes) : 0 ) \ + + +/** + * Get the number of Secure Partitions on this platform + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return Number of partitions. 0 if Secure Paritions not supported. Note + * that this returns the total number of partitions, not all may be + * available to the user. + */ +#define fsl_shw_pco_get_spo_count(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->u.scc2_info.partition_count) : 0 ) \ + + +/*! + * Initialize a Secret Key Object. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * + */ +#define fsl_shw_sko_init(skobject,skalgorithm) \ +{ \ + fsl_shw_sko_t* skop = skobject; \ + \ + skop->algorithm = skalgorithm; \ + skop->flags = 0; \ + skop->keystore = NULL; \ + skop->pf_key = FSL_SHW_PF_KEY_PRG; \ +} + +/*! + * Initialize a Secret Key Object to use a Platform Key register. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * @param skhwkey one of the fsl_shw_pf_key_t values. + * + */ +#define fsl_shw_sko_init_pf_key(skobject,skalgorithm,skhwkey) \ +{ \ + fsl_shw_sko_t* skop = skobject; \ + fsl_shw_key_alg_t alg = skalgorithm; \ + fsl_shw_pf_key_t key = skhwkey; \ + \ + skop->algorithm = alg; \ + if (alg == FSL_KEY_ALG_TDES) { \ + skop->key_length = 21; \ + } \ + skop->keystore = NULL; \ + skop->flags = FSL_SKO_KEY_SELECT_PF_KEY; \ + skop->pf_key = key; \ + if ((key == FSL_SHW_PF_KEY_IIM) || (key == FSL_SHW_PF_KEY_PRG) \ + || (key == FSL_SHW_PF_KEY_IIM_PRG) \ + || (key == FSL_SHW_PF_KEY_IIM_RND) \ + || (key == FSL_SHW_PF_KEY_RND)) { \ + skop->flags |= FSL_SKO_KEY_ESTABLISHED; \ + } \ +} + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag and + * resetting the #FSL_SKO_KEY_ESTABLISHED flag. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkey A pointer to the beginning of the key. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key(skobject, skkey, skkeylen) \ +{ \ + (skobject)->key_length = skkeylen; \ + copy_bytes((skobject)->key, skkey, skkeylen); \ + (skobject)->flags |= FSL_SKO_KEY_PRESENT; \ + (skobject)->flags &= ~FSL_SKO_KEY_ESTABLISHED; \ +} + +/** + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key_length(skobject, skkeylen) \ + (skobject)->key_length = skkeylen; + + +/** + * Set the User ID associated with the key. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to identify authorized users of the key. + */ +#define fsl_shw_sko_set_user_id(skobject, skuserid) \ + (skobject)->userid = (skuserid) + +/** + * Establish a user Keystore to hold the key. + */ +#define fsl_shw_sko_set_keystore(skobject, user_keystore) \ + (skobject)->keystore = (user_keystore) + + + +/** + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT flag + * will be cleared). + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to verify this user is an authorized user of + * the key. + * @param skhandle A @a handle from #fsl_shw_sko_get_established_info. + */ +#define fsl_shw_sko_set_established_info(skobject, skuserid, skhandle) \ +{ \ + (skobject)->userid = (skuserid); \ + (skobject)->handle = (skhandle); \ + (skobject)->flags |= FSL_SKO_KEY_ESTABLISHED; \ + (skobject)->flags &= \ + ~(FSL_SKO_KEY_PRESENT); \ +} + + +/** + * Retrieve the established-key handle from a key object. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skhandle The location to store the @a handle of the unwrapped + * key. + */ +#define fsl_shw_sko_get_established_info(skobject, skhandle) \ + *(skhandle) = (skobject)->handle + + +/** + * Extract the algorithm from a key object. + * + * @param skobject The Key Object to be queried. + * @param[out] skalgorithm A pointer to the location to store the algorithm. + */ +#define fsl_shw_sko_get_algorithm(skobject, skalgorithm) \ + *(skalgorithm) = (skobject)->algorithm + + +/** + * Retrieve the cleartext key from a key object that is stored in a user + * keystore. + * + * @param skobject The Key Object to be queried. + * @param[out] skkey A pointer to the location to store the key. NULL + * if the key is not stored in a user keystore. + */ +#define fsl_shw_sko_get_key(skobject, skkey) \ +{ \ + fsl_shw_kso_t* keystore = (skobject)->keystore; \ + if (keystore != NULL) { \ + *(skkey) = keystore->slot_get_address(keystore->user_data, \ + (skobject)->handle); \ + } else { \ + *(skkey) = NULL; \ + } \ +} + + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param wkeyinfo Information about a key to be wrapped. + * @param wkeylen Location to store the length of a wrapped + * version of the key in @a key_info. + */ +#define fsl_shw_sko_calculate_wrapped_size(wkeyinfo, wkeylen) \ +{ \ + register fsl_shw_sko_t* kp = wkeyinfo; \ + register uint32_t kl = kp->key_length; \ + int key_blocks; \ + int base_size = 35; /* ICV + T' + ALG + LEN + FLAGS */ \ + \ + if (kp->flags & FSL_SKO_KEY_SELECT_PF_KEY) { \ + kl = 21; /* 168-bit 3DES key */ \ + } \ + key_blocks = (kl + 7) / 8; \ + /* Round length up to 3DES block size for CBC mode */ \ + *(wkeylen) = base_size + 8 * key_blocks; \ +} + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +#define fsl_shw_sko_set_flags(skobject, skflags) \ + (skobject)->flags |= (skflags) + + +/** + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t + * which are to be reset. + */ +#define fsl_shw_sko_clear_flags(skobject, skflags) \ + (skobject)->flags &= ~(skflags) + +/** + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param ucontext The User Context object to operate on. + * @param usize The maximum number of operations which can be + * outstanding. + */ +#ifdef __KERNEL__ + +#define fsl_shw_uco_init(ucontext, usize) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->pool_size = usize; \ + (uco)->flags = FSL_UCO_BLOCKING_MODE | FSL_UCO_CONTEXT_CHANGED; \ + (uco)->openfd = -1; \ + (uco)->callback = NULL; \ + (uco)->partition = NULL; \ + (uco)->wrap_key = FSL_SHW_PF_KEY_IIM; \ +} while (0) + +#else /* __KERNEL__ */ + +#define fsl_shw_uco_init(ucontext, usize) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->pool_size = usize; \ + (uco)->flags = FSL_UCO_BLOCKING_MODE | FSL_UCO_CONTEXT_CHANGED; \ + (uco)->openfd = -1; \ + (uco)->callback = NULL; \ + (uco)->wrap_key = FSL_SHW_PF_KEY_IIM; \ +} while (0) + +#endif /* __KERNEL__ */ + + +/** + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param uref A value which will be passed back with a result. + */ +#define fsl_shw_uco_set_reference(ucontext, uref) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->user_ref = uref; \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + + +/** + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param ucallback The function the API will invoke when an operation + * completes. + */ +#define fsl_shw_uco_set_callback(ucontext, ucallback) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->callback = ucallback; \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + +/** + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_set_flags(ucontext, uflags) \ + (ucontext)->flags |= (uflags) | FSL_UCO_CONTEXT_CHANGED + + +/** + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_clear_flags(ucontext, uflags) \ +do { \ + fsl_shw_uco_t* uco = ucontext; \ + \ + (uco)->flags &= ~(uflags); \ + (uco)->flags |= FSL_UCO_CONTEXT_CHANGED; \ +} while (0) + + +/** + * Retrieve the reference value from a Result Object. + * + * @param robject The result object to query. + * + * @return The reference associated with the request. + */ +#define fsl_shw_ro_get_reference(robject) \ + (robject)->user_ref + + +/** + * Retrieve the status code from a Result Object. + * + * @param robject The result object to query. + * + * @return The status of the request. + */ +#define fsl_shw_ro_get_status(robject) \ + (robject)->code + + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-004 */ +/** + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hcobject The hash context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hco_init(hcobject, hcalgorithm) \ +do { \ + (hcobject)->algorithm = hcalgorithm; \ + (hcobject)->flags = 0; \ + switch (hcalgorithm) { \ + case FSL_HASH_ALG_MD5: \ + (hcobject)->digest_length = 16; \ + (hcobject)->context_length = 16; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA1: \ + (hcobject)->digest_length = 20; \ + (hcobject)->context_length = 20; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA224: \ + (hcobject)->digest_length = 28; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + case FSL_HASH_ALG_SHA256: \ + (hcobject)->digest_length = 32; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + default: \ + /* error ! */ \ + (hcobject)->digest_length = 1; \ + (hcobject)->context_length = 1; \ + (hcobject)->context_register_length = 1; \ + break; \ + } \ +} while (0) + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-001 */ +/** + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to query. + * @param[out] hccontext Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param hcclength Number of octets of hash value to copy. + * @param[out] hcmsglen Pointer to the location to store the number of octets + * already hashed. + */ +#define fsl_shw_hco_get_digest(hcobject, hccontext, hcclength, hcmsglen) \ +do { \ + memcpy(hccontext, (hcobject)->context, hcclength); \ + if ((hcobject)->algorithm == FSL_HASH_ALG_SHA224 \ + || (hcobject)->algorithm == FSL_HASH_ALG_SHA256) { \ + *(hcmsglen) = (hcobject)->context[8]; \ + } else { \ + *(hcmsglen) = (hcobject)->context[5]; \ + } \ +} while (0) + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-002 */ +/** + * Get the hash algorithm from the hash context object. + * + * @param hcobject The hash context to query. + * @param[out] hcalgorithm Pointer to where the algorithm is to be stored. + */ +#define fsl_shw_hco_get_info(hcobject, hcalgorithm) \ +do { \ + *(hcalgorithm) = (hcobject)->algorithm; \ +} while (0) + + +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-003 */ +/* REQ-FSL-SHW-PINTFC-API-BASIC-HASH-004 */ +/** + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to operate upon. + * @param hccontext Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param hcmsglen The number of octets of the message which have + * already been hashed. + * + */ +#define fsl_shw_hco_set_digest(hcobject, hccontext, hcmsglen) \ +do { \ + memcpy((hcobject)->context, hccontext, (hcobject)->context_length); \ + if (((hcobject)->algorithm == FSL_HASH_ALG_SHA224) \ + || ((hcobject)->algorithm == FSL_HASH_ALG_SHA256)) { \ + (hcobject)->context[8] = hcmsglen; \ + } else { \ + (hcobject)->context[5] = hcmsglen; \ + } \ +} while (0) + + +/** + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + + +/** + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + + +/** + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hcobject The HMAC context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hmco_init(hcobject, hcalgorithm) \ + fsl_shw_hco_init(hcobject, hcalgorithm) + + +/** + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + + +/** + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + + +/** + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param scobject The context object to operate on. + * @param scalg The cipher algorithm this context will be used with. + * @param scmode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +#define fsl_shw_scco_init(scobject, scalg, scmode) \ +do { \ + register uint32_t bsb; /* block-size bytes */ \ + \ + switch (scalg) { \ + case FSL_KEY_ALG_AES: \ + bsb = 16; \ + break; \ + case FSL_KEY_ALG_DES: \ + /* fall through */ \ + case FSL_KEY_ALG_TDES: \ + bsb = 8; \ + break; \ + case FSL_KEY_ALG_ARC4: \ + bsb = 259; \ + break; \ + case FSL_KEY_ALG_HMAC: \ + bsb = 1; /* meaningless */ \ + break; \ + default: \ + bsb = 00; \ + } \ + (scobject)->block_size_bytes = bsb; \ + (scobject)->mode = scmode; \ + (scobject)->flags = 0; \ +} while (0) + + +/** + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_set_flags(scobject, scflags) \ + (scobject)->flags |= (scflags) + + +/** + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_clear_flags(scobject, scflags) \ + (scobject)->flags &= ~(scflags) + + +/** + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param scobject The context object to operate on. + * @param sccontext A pointer to the buffer which contains the context. + * + */ +#define fsl_shw_scco_set_context(scobject, sccontext) \ + memcpy((scobject)->context, sccontext, \ + (scobject)->block_size_bytes) + + +/** + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param scobject The context object to operate on. + * @param[out] sccontext Pointer to location where context will be stored. + */ +#define fsl_shw_scco_get_context(scobject, sccontext) \ + memcpy(sccontext, (scobject)->context, (scobject)->block_size_bytes) + + +/** + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param scobject The context object to operate on. + * @param sccounter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param scmodulus The modulus for controlling the incrementing of the + * counter. + * + */ +#define fsl_shw_scco_set_counter_info(scobject, sccounter, scmodulus) \ +do { \ + if ((sccounter) != NULL) { \ + memcpy((scobject)->context, sccounter, \ + (scobject)->block_size_bytes); \ + } \ + (scobject)->modulus_exp = scmodulus; \ +} while (0) + + +/** + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param scobject The context object to query. + * @param[out] sccounter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] scmodulus Pointer to location to store the modulus. + * + */ +#define fsl_shw_scco_get_counter_info(scobject, sccounter, scmodulus) \ +do { \ + if ((sccounter) != NULL) { \ + memcpy(sccounter, (scobject)->context, \ + (scobject)->block_size_bytes); \ + } \ + if ((scmodulus) != NULL) { \ + *(scmodulus) = (scobject)->modulus_exp; \ + } \ +} while (0) + + +/** + * Initialize a Authentication-Cipher Context. + * + * @param acobject Pointer to object to operate on. + * @param acmode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +#define fsl_shw_acco_init(acobject, acmode) \ +do { \ + (acobject)->flags = 0; \ + (acobject)->mode = (acmode); \ +} while (0) + + +/** + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_set_flags(acobject, acflags) \ + (acobject)->flags |= (acflags) + + +/** + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_clear_flags(acobject, acflags) \ + (acobject)->flags &= ~(acflags) + + +/** + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The paramater @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param acobject Pointer to object to operate on. + * @param acalg Cipher algorithm. Only AES is supported. + * @param accounter The initial counter value. + * @param acmaclen The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_acco_set_ccm(acobject, acalg, accounter, acmaclen) \ + do { \ + (acobject)->flags = 0; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mac_length = acmaclen; \ + fsl_shw_scco_set_counter_info(&(acobject)->cipher_ctx_info, accounter, \ + FSL_CTR_MOD_128); \ +} while (0) + + +/** + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param acobject Pointer to object to operate on. + * @param act The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param acad Number of octets of Associated Data (may be zero). + * @param acq A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +#define fsl_shw_ccm_nist_format_ctr_and_iv(acobject, act, acad, acq, acN, acQ)\ + do { \ + uint64_t Q = acQ; \ + uint8_t bflag = ((acad)?0x40:0) | ((((act)-2)/2)<<3) | ((acq)-1); \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->flags = FSL_ACCO_NIST_CCM; \ + \ + /* Store away the MAC length (after calculating actual value */ \ + (acobject)->mac_length = (act); \ + /* Set Flag field in Block 0 */ \ + *((acobject)->auth_info.CCM_ctx_info.context) = bflag; \ + /* Set Nonce field in Block 0 */ \ + memcpy((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15-(acq)); \ + /* Set Flag field in ctr */ \ + *((acobject)->cipher_ctx_info.context) = (acq)-1; \ + /* Update the Q (payload length) field of Block0 */ \ + (acobject)->q_length = acq; \ + for (i = 0; i < (acq); i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Set the Nonce field of the ctr */ \ + memcpy((acobject)->cipher_ctx_info.context+1, acN, 15-(acq)); \ + /* Clear the block counter field of the ctr */ \ + memset((acobject)->cipher_ctx_info.context+16-(acq), 0, (acq)+1); \ + } while (0) + + +/** + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param acobject Pointer to object to operate on. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_update_ctr_and_iv(acobject, acN, acQ) \ + do { \ + uint64_t Q = acQ; \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + \ + /* Update the Nonce field field of Block0 */ \ + memcpy((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + /* Update the Q (payload length) field of Block0 */ \ + for (i = 0; i < (acobject)->q_length; i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Update the Nonce field of the ctr */ \ + memcpy((acobject)->cipher_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + } while (0) + + +/****************************************************************************** + * Library functions + *****************************************************************************/ +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-003 */ +/** + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +extern fsl_shw_pco_t* fsl_shw_get_capabilities(fsl_shw_uco_t* user_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-004 */ +/** + * Create an association between the the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t* user_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-005 */ +/** + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t* user_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-GEN-006 */ +/** + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t* user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned* result_count); + +/** + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped with a later call to #fsl_shw_extract_key(). Normal + * cleartext keys can simply be placed into #fsl_shw_sko_t key objects with + * #fsl_shw_sko_set_key() and used directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + */ +extern fsl_shw_return_t fsl_shw_establish_key( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t* key); + + +/** + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release the key (see #fsl_shw_release_key()) so + * that it must be re-established before reuse. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + uint8_t* covered_key); + +/*! + * Read the key value from a key object. + * + * Only a key marked as a software key (#FSL_SKO_KEY_SW_KEY) can be read with + * this call. It has no effect on the status of the key store. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The referenced key. + * @param[out] key The location to store the key value. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * key); + +/** + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_release_key( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info); + + +/* + * In userspace, partition assignments will be tracked using the user context. + * In kernel mode, partition assignments are based on address only. + */ + +/** + * Allocate a block of secure memory + * + * @param user_ctx User context + * @param size Memory size (octets). Note: currently only + * supports only single-partition sized blocks. + * @param UMID User Mode ID to use when registering the + * partition. + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return Address of the allocated memory. NULL if the + * call was not successful. + */ +extern void *fsl_shw_smalloc(fsl_shw_uco_t* user_ctx, + uint32_t size, + const uint8_t* UMID, + uint32_t permissions); + + +/** + * Free a block of secure memory that was allocated with #fsl_shw_smalloc + * + * @param user_ctx User context + * @param address Address of the block of secure memory to be + * released. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_sfree( + fsl_shw_uco_t* user_ctx, + void* address); + + +/** + * Check the status of a block of a secure memory that was allocated with + * #fsl_shw_smalloc + * + * @param user_ctx User context + * @param address Address of the block of secure memory to be + * released. + * @param status Status of the partition, of type + * #fsl_partition_status_t + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t* user_ctx, + void* address, + fsl_shw_partition_status_t* status); + + +/** + * Diminish the permissions of a block of secure memory. Note that permissions + * can only be revoked. + * + * @param user_ctx User context + * @param address Base address of the secure memory to work with + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_diminish_perms( + fsl_shw_uco_t* user_ctx, + void* address, + uint32_t permissions); + +extern fsl_shw_return_t do_scc_engage_partition( + fsl_shw_uco_t* user_ctx, + void* address, + const uint8_t* UMID, + uint32_t permissions); + +extern fsl_shw_return_t do_system_keystore_slot_alloc( + fsl_shw_uco_t* user_ctx, + uint32_t key_lenth, + uint64_t ownerid, + uint32_t *slot); + +extern fsl_shw_return_t do_system_keystore_slot_dealloc( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot); + +extern fsl_shw_return_t do_system_keystore_slot_load( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t *key, + uint32_t key_length); + +extern fsl_shw_return_t do_system_keystore_slot_encrypt( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t* black_data); + +extern fsl_shw_return_t do_system_keystore_slot_decrypt( + fsl_shw_uco_t* user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t* black_data); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/* REQ-FSL-SHW-PINTFC-COA-SCCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-SYM-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/** + * Encrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the init & save flags flags on in + * the @a sym_ctx. Subsequent operations would then run as normal with the + * load and save flags. Note that they key object is still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info Key and algorithm being used for this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the pt (and ct). + * @param pt pointer to plaintext to be encrypted. + * @param[out] ct pointer to where to store the resulting ciphertext. + * + * @return A return code of type #fsl_shw_return_t. + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_scco_t* sym_ctx, + uint32_t length, + const uint8_t* pt, + uint8_t* ct); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/* REQ-FSL-SHW-PINTFC-COA-SCCO */ +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/** + * Decrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the #FSL_SYM_CTX_INIT & + * #FSL_SYM_CTX_SAVE flags on in the @a sym_ctx. Subsequent operations would + * then run as normal with the load & save flags. Note that they key object is + * still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key and algorithm being used in this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the ct (and pt). + * @param ct pointer to ciphertext to be decrypted. + * @param[out] pt pointer to where to store the resulting plaintext. + * + * @return A return code of type #fsl_shw_return_t + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_scco_t* sym_ctx, + uint32_t length, + const uint8_t* ct, + uint8_t* pt); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-HCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-HASH-005 */ +/** + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hash( + fsl_shw_uco_t* user_ctx, + fsl_shw_hco_t* hash_ctx, + const uint8_t* msg, + uint32_t length, + uint8_t* result, + uint32_t result_len); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-API-BASIC-HMAC-001 */ +/** + * Precompute the Key hashes for an HMAC operation. + * + * This function may be used to calculate the inner and outer precomputes, + * which are the hash contexts resulting from hashing the XORed key for the + * 'inner hash' and the 'outer hash', respectively, of the HMAC function. + * + * After execution of this function, the @a hmac_ctx will contain the + * precomputed inner and outer contexts, so that they may be used by + * #fsl_shw_hmac(). The flags of @a hmac_ctx will be updated with + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT to mark their presence. In addtion, the + * #FSL_HMAC_FLAGS_INIT flag will be set. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key being used in this operation. Key must be + * 1 to 64 octets long. + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac_precompute( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_hmco_t* hmac_ctx); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-HMAC-002 */ +/** + * Continue, finalize, or one-shot an HMAC operation. + * + * There are a number of ways to use this function. The flags in the + * @a hmac_ctx object will determine what operations occur. + * + * If #FSL_HMAC_FLAGS_INIT is set, then the hash will be started either from + * the @a key_info, or from the precomputed inner hash value in the + * @a hmac_ctx, depending on the value of #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT. + * + * If, instead, #FSL_HMAC_FLAGS_LOAD is set, then the hash will be continued + * from the ongoing inner hash computation in the @a hmac_ctx. + * + * If #FSL_HMAC_FLAGS_FINALIZE are set, then the @a msg will be padded, hashed, + * the outer hash will be performed, and the @a result will be generated. + * + * If the #FSL_HMAC_FLAGS_SAVE flag is set, then the (ongoing or final) digest + * value will be stored in the ongoing inner hash computation field of the @a + * hmac_ctx. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info If #FSL_HMAC_FLAGS_INIT is set in the @a hmac_ctx, + * this is the key being used in this operation, and the + * IPAD. If #FSL_HMAC_FLAGS_INIT is set in the @a + * hmac_ctx and @a key_info is NULL, then + * #fsl_shw_hmac_precompute() has been used to populate + * the @a inner_precompute and @a outer_precompute + * contexts. If #FSL_HMAC_FLAGS_INIT is not set, this + * parameter is ignored. + + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @param msg Pointer to the message to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result Pointer, of @a result_len octets, to where to + * store the HMAC. + * @param result_len Length of @a result buffer. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac( + fsl_shw_uco_t* user_ctx, + fsl_shw_sko_t* key_info, + fsl_shw_hmco_t* hmac_ctx, + const uint8_t* msg, + uint32_t length, + uint8_t* result, + uint32_t result_len); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-002 */ +/** + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param[out] data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_random( + fsl_shw_uco_t* user_ctx, + uint32_t length, + uint8_t* data); + + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-003 */ +/** + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_add_entropy( + fsl_shw_uco_t* user_ctx, + uint32_t length, + uint8_t* data); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/** + * Perform Generation-Encryption by doing a Cipher and a Hash. + * + * Generate the authentication value @a auth_value as well as encrypt the @a + * payload into @a ct (the ciphertext). This is a one-shot function, so all of + * the @a auth_data and the total message @a payload must passed in one call. + * This also means that the flags in the @a auth_ctx must be #FSL_ACCO_CTX_INIT + * and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not encrypted. + * @param payload_length Length, in octets, of @a payload. + * @param payload Pointer to the plaintext to be encrypted. + * @param[out] ct Pointer to the where the encrypted @a payload + * will be stored. Must be @a payload_length + * octets long. + * @param[out] auth_value Pointer to where the generated authentication + * field will be stored. Must be as many octets as + * indicated by MAC length in the @a function_ctx. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_encrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_acco_t* auth_ctx, + fsl_shw_sko_t* cipher_key_info, + fsl_shw_sko_t* auth_key_info, + uint32_t auth_data_length, + const uint8_t* auth_data, + uint32_t payload_length, + const uint8_t* payload, + uint8_t* ct, + uint8_t* auth_value); + +/* REQ-FSL-SHW-PINTFC-COA-UCO */ +/* REQ-FSL-SHW-PINTFC-COA-SKO */ +/** + * Perform Authentication-Decryption in Cipher + Hash. + * + * This function will perform a one-shot decryption of a data stream as well as + * authenticate the authentication value. This is a one-shot function, so all + * of the @a auth_data and the total message @a payload must passed in one + * call. This also means that the flags in the @a auth_ctx must be + * #FSL_ACCO_CTX_INIT and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not decrypted. + * @param payload_length Length, in octets, of @a ct and @a pt. + * @param ct Pointer to the encrypted input stream. + * @param auth_value The (encrypted) authentication value which will + * be authenticated. This is the same data as the + * (output) @a auth_value argument to + * #fsl_shw_gen_encrypt(). + * @param[out] payload Pointer to where the plaintext resulting from + * the decryption will be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_auth_decrypt( + fsl_shw_uco_t* user_ctx, + fsl_shw_acco_t* auth_ctx, + fsl_shw_sko_t* cipher_key_info, + fsl_shw_sko_t* auth_key_info, + uint32_t auth_data_length, + const uint8_t* auth_data, + uint32_t payload_length, + const uint8_t* ct, + const uint8_t* auth_value, + uint8_t* payload); + +/*! + * Cause the hardware to create a new random key for secure memory use. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the hardware random key register. It will not be made + * active without a call to #fsl_shw_select_pf_key(). + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +#ifdef __KERNEL__ + +extern fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx); + +#else + +#define fsl_shw_gen_random_pf_key(user_ctx) FSL_RETURN_NO_RESOURCE_S + +#endif /* __KERNEL__ */ + +/*! + * Retrieve the detected tamper event. + * + * Note that if more than one event was detected, this routine will only ever + * return one of them. + * + * @param[in] user_ctx A user context from #fsl_shw_register_user(). + * @param[out] tamperp Location to store the tamper information. + * @param[out] timestampp Locate to store timestamp from hardwhare when + * an event was detected. + * + * + * @return A return code of type #fsl_shw_return_t (for instance, if the platform + * is not in a fail state. + */ +#ifdef __KERNEL__ + +extern fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * user_ctx, + fsl_shw_tamper_t * tamperp, + uint64_t * timestampp); +#else + +#define fsl_shw_read_tamper_event(user_ctx,tamperp,timestampp) \ + FSL_RETURN_NO_RESOURCE_S + +#endif /* __KERNEL__ */ + +/***************************************************************************** + * + * Functions internal to SHW driver. + * +*****************************************************************************/ + +fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t* user_ctx, + void* partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t* black_data, + uint32_t* IV, fsl_shw_cypher_mode_t cypher_mode); + +fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t* user_ctx, + void* partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t* black_data, + uint32_t* IV, fsl_shw_cypher_mode_t cypher_mode); + + +/***************************************************************************** + * + * Functions available to other SHW-family drivers. + * +*****************************************************************************/ + +#ifdef __KERNEL__ +/** + * Add an entry to a work/result queue. + * + * @param pool Pointer to list structure + * @param entry Entry to place at tail of list + * + * @return void + */ +inline static void SHW_ADD_QUEUE_ENTRY(shw_queue_t* pool, + shw_queue_entry_t* entry) +{ + os_lock_context_t lock_context; + + entry->next = NULL; + os_lock_save_context(shw_queue_lock, lock_context); + + if (pool->tail != NULL) { + pool->tail->next = entry; + } else { + /* Queue was empty, so this is also the head. */ + pool->head = entry; + } + pool->tail = entry; + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return; + + +} + + +/** + * Get first entry on the queue and remove it from the queue. + * + * @return Pointer to first entry, or NULL if none. + */ +inline static shw_queue_entry_t* SHW_POP_FIRST_ENTRY(shw_queue_t* queue) +{ + shw_queue_entry_t* entry; + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + entry = queue->head; + + if (entry != NULL) { + queue->head = entry->next; + entry->next = NULL; + /* If this was only entry, clear the tail. */ + if (queue->tail == entry) { + queue->tail = NULL; + } + } + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return entry; +} + + + +/** + * Remove an entry from the list. + * + * If the entry not on the queue, no error will be returned. + * + * @param pool Pointer to work queue + * @param entry Entry to remove from queue + * + * @return void + * + */ +inline static void SHW_QUEUE_REMOVE_ENTRY(shw_queue_t* pool, + shw_queue_entry_t* entry) +{ + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + /* Check for quick case.*/ + if (pool->head == entry) { + pool->head = entry->next; + entry->next = NULL; + if (pool->tail == entry) { + pool->tail = NULL; + } + } else { + register shw_queue_entry_t* prev = pool->head; + + /* We know it is not the head, so start looking at entry after head. */ + while (prev->next) { + if (prev->next != entry) { + prev = prev->next; /* Try another */ + continue; + } else { + /* Unlink from chain. */ + prev->next = entry->next; + entry->next = NULL; + /* If last in chain, update tail. */ + if (pool->tail == entry) { + pool->tail = prev; + } + break; + } + } /* while */ + } + + os_unlock_restore_context(shw_queue_lock, lock_context); + + return; +} +#endif /* __KERNEL__ */ + + +/***************************************************************************** + * + * Functions available to User-Mode API functions + * + ****************************************************************************/ +#ifndef __KERNEL__ + + + /** + * Sanity checks the user context object fields to ensure that they make some + * sense before passing the uco as a parameter. + * + * @brief Verify the user context object + * + * @param uco user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t validate_uco(fsl_shw_uco_t *uco); + + +/** + * Initialize a request block to go to the driver. + * + * @param hdr Pointer to request block header + * @param user_ctx Pointer to user's context + * + * @return void + */ +inline static void init_req(struct shw_req_header* hdr, + fsl_shw_uco_t* user_ctx) +{ + hdr->flags = user_ctx->flags; + hdr->user_ref = user_ctx->user_ref; + hdr->code = FSL_RETURN_ERROR_S; + + return; +} + + +/** + * Send a request block off to the driver. + * + * If this is a non-blocking request, then req will be freed. + * + * @param type The type of request being sent + * @param req Pointer to the request block + * @param ctx Pointer to user's context + * + * @return code from driver if ioctl() succeeded, otherwise + * FSL_RETURN_INTERNAL_ERROR_S. + */ +inline static fsl_shw_return_t send_req(shw_user_request_t type, + struct shw_req_header* req, + fsl_shw_uco_t* ctx) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + unsigned blocking = ctx->flags & FSL_UCO_BLOCKING_MODE; + int code; + + code = ioctl(ctx->openfd, SHW_IOCTL_REQUEST + type, req); + + if (code == 0) { + if (blocking) { + ret = req->code; + } else { + ret = FSL_RETURN_OK_S; + } + } else { +#ifdef FSL_DEBUG + fprintf(stderr, "SHW: send_req failed with (%d), %s\n", errno, + strerror(errno)); +#endif + } + + if (blocking) { + free(req); + } + + return ret; +} + + +#endif /* no __KERNEL__ */ + +#if defined(FSL_HAVE_DRYICE) +/* Some kernel functions */ +void fsl_shw_permute1_bytes(const uint8_t * key, uint8_t * permuted_key, + int key_count); +void fsl_shw_permute1_bytes_to_words(const uint8_t * key, + uint32_t * permuted_key, int key_count); + +#define PFKEY_TO_STR(key_in) \ +({ \ + di_key_t key = key_in; \ + \ + ((key == DI_KEY_FK) ? "IIM" : \ + ((key == DI_KEY_PK) ? "PRG" : \ + ((key == DI_KEY_RK) ? "RND" : \ + ((key == DI_KEY_FPK) ? "IIM_PRG" : \ + ((key == DI_KEY_FRK) ? "IIM_RND" : "unk"))))); \ +}) + +#ifdef DIAG_SECURITY_FUNC +extern const char *di_error_string(int code); +#endif + +#endif /* HAVE DRYICE */ + +#endif /* SHW_DRIVER_H */ diff --git a/drivers/mxc/security/rng/include/shw_hash.h b/drivers/mxc/security/rng/include/shw_hash.h new file mode 100644 index 000000000000..d0e7eed0e1d2 --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_hash.h @@ -0,0 +1,96 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @file shw_hash.h + * + * This file contains definitions for use of the (internal) SHW hash + * software computation. It defines the usual three steps: + * + * - #shw_hash_init() + * - #shw_hash_update() + * - #shw_hash_final() + * + * The only other item of note to callers is #SHW_HASH_LEN, which is the number + * of bytes calculated for the hash. + */ + +#ifndef SHW_HASH_H +#define SHW_HASH_H + +/*! Define which gives the number of bytes available in an hash result */ +#define SHW_HASH_LEN 32 + +/* Define which matches block length in bytes of the underlying hash */ +#define SHW_HASH_BLOCK_LEN 64 + +/* "Internal" define which matches SHA-256 state size (32-bit words) */ +#define SHW_HASH_STATE_WORDS 8 + +/* "Internal" define which matches word length in blocks of the underlying + hash. */ +#define SHW_HASH_BLOCK_WORD_SIZE 16 + +#define SHW_HASH_STATE_SIZE 32 + +/*! + * State for a SHA-1/SHA-2 Hash + * + * (Note to maintainers: state needs to be updated to uint64_t to handle + * SHA-384/SHA-512)... And bit_count to uint128_t (heh). + */ +typedef struct shw_hash_state { + unsigned int partial_count_bytes; /*!< Number of bytes of message sitting + * in @c partial_block */ + uint8_t partial_block[SHW_HASH_BLOCK_LEN]; /*!< Data waiting to be processed as a block */ + uint32_t state[SHW_HASH_STATE_WORDS]; /*!< Current hash state variables */ + uint64_t bit_count; /*!< Number of bits sent through the update function */ +} shw_hash_state_t; + +/*! + * Initialize the hash state structure + * + * @param state Address of hash state structure. + * @param algorithm Which hash algorithm to use (must be FSL_HASH_ALG_SHA256) + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hash_init(shw_hash_state_t * state, + fsl_shw_hash_alg_t algorithm); + +/*! + * Put data into the hash calculation + * + * @param state Address of hash state structure. + * @param msg Address of the message data for the hash. + * @param msg_len Number of bytes of @c msg. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hash_update(shw_hash_state_t * state, + const uint8_t * msg, unsigned int msg_len); + +/*! + * Calculate the final hash value + * + * @param state Address of hash state structure. + * @param hash Address of location to store the hash. + * @param hash_len Number of bytes of @c hash to be stored. + * + * @return FSL_RETURN_OK_S if all went well, FSL_RETURN_BAD_DATA_LENGTH_S if + * hash_len is too long, otherwise an error code. + */ +fsl_shw_return_t shw_hash_final(shw_hash_state_t * state, + uint8_t * hash, unsigned int hash_len); + +#endif /* SHW_HASH_H */ diff --git a/drivers/mxc/security/rng/include/shw_hmac.h b/drivers/mxc/security/rng/include/shw_hmac.h new file mode 100644 index 000000000000..99d373149f51 --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_hmac.h @@ -0,0 +1,82 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU Lesser General + * Public License. You may obtain a copy of the GNU Lesser General + * Public License Version 2.1 or later at the following locations: + * + * http://www.opensource.org/licenses/lgpl-license.html + * http://www.gnu.org/copyleft/lgpl.html + */ + +/*! + * @file shw_hmac.h + * + * This file contains definitions for use of the (internal) SHW HMAC + * software computation. It defines the usual three steps: + * + * - #shw_hmac_init() + * - #shw_hmac_update() + * - #shw_hmac_final() + * + * The only other item of note to callers is #SHW_HASH_LEN, which is the number + * of bytes calculated for the HMAC. + */ + +#ifndef SHW_HMAC_H +#define SHW_HMAC_H + +#include "shw_hash.h" + +/*! + * State for an HMAC + * + * Note to callers: This structure contains key material and should be kept in + * a secure location, such as internal RAM. + */ +typedef struct shw_hmac_state { + shw_hash_state_t inner_hash; /*!< Current state of inner hash */ + shw_hash_state_t outer_hash; /*!< Current state of outer hash */ +} shw_hmac_state_t; + +/*! + * Initialize the HMAC state structure with the HMAC key + * + * @param state Address of HMAC state structure. + * @param key Address of the key to be used for the HMAC. + * @param key_len Number of bytes of @c key. This must not be greater than + * the block size of the underlying hash (#SHW_HASH_BLOCK_LEN). + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_init(shw_hmac_state_t * state, + const uint8_t * key, unsigned int key_len); + +/*! + * Put data into the HMAC calculation + * + * @param state Address of HMAC state structure. + * @param msg Address of the message data for the HMAC. + * @param msg_len Number of bytes of @c msg. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_update(shw_hmac_state_t * state, + const uint8_t * msg, unsigned int msg_len); + +/*! + * Calculate the final HMAC + * + * @param state Address of HMAC state structure. + * @param hmac Address of location to store the HMAC. + * @param hmac_len Number of bytes of @c mac to be stored. Probably best if + * this value is no greater than #SHW_HASH_LEN. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_final(shw_hmac_state_t * state, + uint8_t * hmac, unsigned int hmac_len); + +#endif /* SHW_HMAC_H */ diff --git a/drivers/mxc/security/rng/include/shw_internals.h b/drivers/mxc/security/rng/include/shw_internals.h new file mode 100644 index 000000000000..c917ec813162 --- /dev/null +++ b/drivers/mxc/security/rng/include/shw_internals.h @@ -0,0 +1,162 @@ +/* + * Copyright 2005-2009 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 + */ + +#ifndef SHW_INTERNALS_H +#define SHW_INTERNALS_H + +/*! @file shw_internals.h + * + * This file contains definitions which are internal to the SHW driver. + * + * This header file should only ever be included by shw_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. + * + */ + +#include "portable_os.h" +#include "shw_driver.h" + +/*! @defgroup shwcompileflags SHW Compile Flags + * + * These are flags which are used to configure the SHW driver at compilation + * time. + * + * The terms 'defined' and 'undefined' refer to whether a @c \#define (or -D on + * a compile command) has defined a given preprocessor symbol. If a given + * symbol is defined, then @c \#ifdef \<symbol\> will succeed. Some symbols + * described below default to not having a definition, i.e. they are undefined. + * + */ + +/*! @addtogroup shwcompileflags */ +/*! @{ */ +#ifndef SHW_MAJOR_NODE +/*! + * This should be configured in a Makefile/compile command line. It is the + * value the driver will use to register itself as a device driver for a + * /dev/node file. Zero means allow (Linux) to assign a value. Any positive + * number will be attempted as the registration value, to allow for + * coordination with the creation/existence of a /dev/fsl_shw (for instance) + * file in the filesystem. + */ +#define SHW_MAJOR_NODE 0 +#endif + +/* Temporarily define compile-time flags to make Doxygen happy and allow them + to get into the documentation. */ +#ifdef DOXYGEN_HACK + +/*! + * Turn on compilation of run-time operational, debug, and error messages. + * + * This flag is undefined by default. + */ +/* REQ-FSLSHW-DEBUG-001 */ +#define SHW_DEBUG +#undef SHW_DEBUG + +/*! @} */ +#endif /* end DOXYGEN_HACK */ + +#ifndef SHW_DRIVER_NAME +/*! @addtogroup shwcompileflags */ +/*! @{ */ +/*! Name the driver will use to register itself to the kernel as the driver for + * the #shw_major_node and interrupt handling. */ +#define SHW_DRIVER_NAME "fsl_shw" +/*! @} */ +#endif +/*#define SHW_DEBUG*/ + +/*! + * Add a user context onto the list of registered users. + * + * Place it at the head of the #user_list queue. + * + * @param ctx A pointer to a user context + * + * @return void + */ +inline static void SHW_ADD_USER(fsl_shw_uco_t * ctx) +{ + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + ctx->next = user_list; + user_list = ctx; + os_unlock_restore_context(shw_queue_lock, lock_context); + +} + +/*! + * Remove a user context from the list of registered users. + * + * @param ctx A pointer to a user context + * + * @return void + * + */ +inline static void SHW_REMOVE_USER(fsl_shw_uco_t * ctx) +{ + fsl_shw_uco_t *prev_ctx = user_list; + os_lock_context_t lock_context; + + os_lock_save_context(shw_queue_lock, lock_context); + + if (prev_ctx == ctx) { + /* Found at head, so just set new head */ + user_list = ctx->next; + } else { + for (; (prev_ctx != NULL); prev_ctx = prev_ctx->next) { + if (prev_ctx->next == ctx) { + prev_ctx->next = ctx->next; + break; + } + } + } + os_unlock_restore_context(shw_queue_lock, lock_context); +} + +static void shw_user_callback(fsl_shw_uco_t * uco); + +/* internal functions */ +static os_error_code shw_setup_user_driver_interaction(void); +static void shw_cleanup(void); + +static os_error_code init_uco(fsl_shw_uco_t * user_ctx, void *user_mode_uco); +static os_error_code get_capabilities(fsl_shw_uco_t * user_ctx, + void *user_mode_pco_request); +static os_error_code get_results(fsl_shw_uco_t * user_ctx, + void *user_mode_result_req); +static os_error_code get_random(fsl_shw_uco_t * user_ctx, + void *user_mode_get_random_req); +static os_error_code add_entropy(fsl_shw_uco_t * user_ctx, + void *user_mode_add_entropy_req); + +void* wire_user_memory(void* address, uint32_t length, void** page_ctx); +void unwire_user_memory(void** page_ctx); +os_error_code map_user_memory(struct vm_area_struct* vma, + uint32_t physical_addr, uint32_t size); +os_error_code unmap_user_memory(uint32_t user_addr, uint32_t size); + +#if defined(LINUX_VERSION_CODE) + +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Device Driver for FSL SHW API"); + +#endif /* LINUX_VERSION_CODE */ + +#endif /* SHW_INTERNALS_H */ diff --git a/drivers/mxc/security/rng/rng_driver.c b/drivers/mxc/security/rng/rng_driver.c new file mode 100644 index 000000000000..5664e11860fe --- /dev/null +++ b/drivers/mxc/security/rng/rng_driver.c @@ -0,0 +1,1150 @@ +/* + * Copyright 2005-2009 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 rng_driver.c + * + * This is the driver code for the hardware Random Number Generator (RNG). + * + * It provides the following functions to callers: + * fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t* user_ctx, + * uint32_t length, + * uint8_t* data); + * + * fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t* user_ctx, + * uint32_t length, + * uint8_t* data); + * + * The life of the driver starts at boot (or module load) time, with a call by + * the kernel to #rng_init(). As part of initialization, a background task + * running #rng_entropy_task() will be created. + * + * The life of the driver ends when the kernel is shutting down (or the driver + * is being unloaded). At this time, #rng_shutdown() is called. No function + * will ever be called after that point. In the case that the driver is + * reloaded, a new copy of the driver, with fresh global values, etc., is + * loaded, and there will be a new call to #rng_init(). + * + * A call to fsl_shw_get_random() gets converted into a work entry which is + * queued and handed off to a background task for fulfilment. This provides + * for a single thread of control for reading the RNG's FIFO register, which + * might otherwise underflow if not carefully managed. + * + * A call to fsl_shw_add_entropy() will cause the additional entropy to + * be passed directly into the hardware. + * + * In a debug configuration, it provides the following kernel functions: + * rng_return_t rng_read_register(uint32_t byte_offset, uint32_t* valuep); + * rng_return_t rng_write_register(uint32_t byte_offset, uint32_t value); + * @ingroup RNG + */ + +#include "portable_os.h" +#include "fsl_shw.h" +#include "rng_internals.h" + +#ifdef FSL_HAVE_SCC2 +#include <linux/mxc_scc2_driver.h> +#else +#include <linux/mxc_scc_driver.h> +#endif + +#if defined(RNG_DEBUG) || defined(RNG_ENTROPY_DEBUG) || \ + defined(RNG_REGISTER_DEBUG) + +#include <diagnostic.h> + +#else + +#define LOG_KDIAG_ARGS(fmt, ...) +#define LOG_KDIAG(diag) + +#endif + +/* These are often handy */ +#ifndef FALSE +/*! Non-true value for arguments, return values. */ +#define FALSE 0 +#endif +#ifndef TRUE +/*! True value for arguments, return values. */ +#define TRUE 1 +#endif + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * This is type void* so that a) it cannot directly be dereferenced, and b) + * pointer arithmetic on it will function for the byte offsets in rng_rnga.h + * and rng_rngc.h + * + * rng_base is the location in the iomap where the RNG's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the RNG, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #RNG_READ_REGISTER and + * #RNG_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *rng_base; + +/*! + * Flag to say whether interrupt handler has been registered for RNG + * interrupt */ +static int rng_irq_set = FALSE; + +/*! + * Size of the RNG's OUTPUT_FIFO, in words. Retrieved with + * #RNG_GET_FIFO_SIZE() during driver initialization. + */ +static int rng_output_fifo_size; + +/*! Major number for device driver. */ +static int rng_major; + +/*! Registration handle for registering driver with OS. */ +os_driver_reg_t rng_reg_handle; + +/*! + * Internal flag to know whether RNG is in Failed state (and thus many + * registers are unavailable). If the value ever goes to #RNG_STATUS_FAILED, + * it will never change. + */ +static volatile rng_status_t rng_availability = RNG_STATUS_INITIAL; + +/*! + * Global lock for the RNG driver. Mainly used for entries on the RNG work + * queue. + */ +static os_lock_t rng_queue_lock = NULL; + +/*! + * Queue for the RNG task to process. + */ +static shw_queue_t rng_work_queue; + +/*! + * Flag to say whether task initialization succeeded. + */ +static unsigned task_started = FALSE; +/*! + * Waiting queue for RNG SELF TESTING + */ +static DECLARE_COMPLETION(rng_self_testing); +static DECLARE_COMPLETION(rng_seed_done); +/*! + * Object for blocking-mode callers of RNG driver to sleep. + */ +OS_WAIT_OBJECT(rng_wait_queue); + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn rng_init() */ +/*****************************************************************************/ +/*! + * Initialize the driver. + * + * Set up the driver to have access to RNG device registers and verify that + * it appears to be a proper working device. + * + * Set up interrupt handling. Assure RNG is ready to go and (possibly) set it + * into High Assurance mode. Create a background task to run + * #rng_entropy_task(). Set up up a callback with the SCC driver should the + * security alarm go off. Tell the kernel that the driver is here. + * + * This routine is called during kernel init or module load (insmod). + * + * The function will fail in one of two ways: Returning OK to the caller so + * that kernel startup / driver initialization completes, or returning an + * error. In the success case, the function could set the rng_avaailability to + * RNG_STATUS_FAILED so that only minimal support (e.g. register peek / poke) + * is available in the driver. + * + * @return a call to os_dev_init_return() + */ +OS_DEV_INIT(rng_init) +{ + struct clk *clk; + os_error_code return_code = OS_ERROR_FAIL_S; + rng_availability = RNG_STATUS_CHECKING; + +#if !defined(FSL_HAVE_RNGA) + INIT_COMPLETION(rng_self_testing); + INIT_COMPLETION(rng_seed_done); +#endif + rng_work_queue.head = NULL; + rng_work_queue.tail = NULL; + + clk = clk_get(NULL, "rng_clk"); + + // Check that the clock was found + if (IS_ERR(clk)) { + LOG_KDIAG("RNG: Failed to find rng_clock."); + return_code = OS_ERROR_FAIL_S; + goto check_err; + } + + clk_enable(clk); + + os_printk(KERN_INFO "RNG Driver: Loading\n"); + + return_code = rng_map_RNG_memory(); + if (return_code != OS_ERROR_OK_S) { + rng_availability = RNG_STATUS_UNIMPLEMENTED; + LOG_KDIAG_ARGS("RNG: Driver failed to map RNG registers. %d", + return_code); + goto check_err; + } + LOG_KDIAG_ARGS("RNG Driver: rng_base is 0x%08x", (uint32_t) rng_base); + /*Check SCC keys are fused */ + if (RNG_HAS_ERROR()) { + if (RNG_HAS_BAD_KEY()) { +#ifdef RNG_DEBUG +#if !defined(FSL_HAVE_RNGA) + LOG_KDIAG("ERROR: BAD KEYS SELECTED"); + { + uint32_t rngc_status = + RNG_READ_REGISTER(RNGC_STATUS); + uint32_t rngc_error = + RNG_READ_REGISTER(RNGC_ERROR); + LOG_KDIAG_ARGS + ("status register: %08x, error status: %08x", + rngc_status, rngc_error); + } +#endif +#endif + rng_availability = RNG_STATUS_FAILED; + return_code = OS_ERROR_FAIL_S; + goto check_err; + } + } + + /* Check RNG configuration and status */ + return_code = rng_grab_config_values(); + if (return_code != OS_ERROR_OK_S) { + rng_availability = RNG_STATUS_UNIMPLEMENTED; + goto check_err; + } + + /* Masking All Interrupts */ + /* They are unmasked later in rng_setup_interrupt_handling() */ + RNG_MASK_ALL_INTERRUPTS(); + + RNG_WAKE(); + + /* Determine status of RNG */ + if (RNG_OSCILLATOR_FAILED()) { + LOG_KDIAG("RNG Driver: RNG Oscillator is dead"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + /* Oscillator not dead. Setup interrupt code and start the RNG. */ + if ((return_code = rng_setup_interrupt_handling()) == OS_ERROR_OK_S) { +#if defined(FSL_HAVE_RNGA) + scc_return_t scc_code; +#endif + + RNG_GO(); + + /* Self Testing For RNG */ + do { + RNG_CLEAR_ERR(); + + /* wait for Clearing Erring finished */ + msleep(1); + + RNG_UNMASK_ALL_INTERRUPTS(); + RNG_SELF_TEST(); +#if !defined(FSL_HAVE_RNGA) + wait_for_completion(&rng_self_testing); +#endif + } while (RNG_CHECK_SELF_ERR()); + + RNG_CLEAR_ALL_STATUS(); + /* checking for RNG SEED done */ + do { + RNG_CLEAR_ERR(); + RNG_SEED_GEN(); +#if !defined(FSL_HAVE_RNGA) + wait_for_completion(&rng_seed_done); +#endif + } while (RNG_CHECK_SEED_ERR()); +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + RNG_SET_HIGH_ASSURANCE(); +#endif + if (RNG_GET_HIGH_ASSURANCE()) { + LOG_KDIAG("RNG Driver: RNG is in High Assurance mode"); + } else { +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + LOG_KDIAG + ("RNG Driver: RNG could not be put in High Assurance mode"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; +#endif /* RNG_NO_FORCE_HIGH_ASSURANCE */ + } + + /* Check that RNG is OK */ + if (!RNG_WORKING()) { + LOG_KDIAG_ARGS + ("RNG determined to be inoperable. Status %08x", + RNG_GET_STATUS()); + /* Couldn't wake it up or other problem */ + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + rng_queue_lock = os_lock_alloc_init(); + if (rng_queue_lock == NULL) { + LOG_KDIAG("RNG: lock initialization failed"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } + + return_code = os_create_task(rng_entropy_task); + if (return_code != OS_ERROR_OK_S) { + LOG_KDIAG("RNG: task initialization failed"); + rng_availability = RNG_STATUS_FAILED; + goto check_err; + } else { + task_started = TRUE; + } +#ifdef FSL_HAVE_RNGA + scc_code = scc_monitor_security_failure(rng_sec_failure); + if (scc_code != SCC_RET_OK) { + LOG_KDIAG_ARGS("Failed to register SCC callback: %d", + scc_code); +#ifndef RNG_NO_FORCE_HIGH_ASSURANCE + return_code = OS_ERROR_FAIL_S; + goto check_err; +#endif + } +#endif /* FSL_HAVE_RNGA */ + return_code = os_driver_init_registration(rng_reg_handle); + if (return_code != OS_ERROR_OK_S) { + goto check_err; + } + /* add power suspend here */ + /* add power resume here */ + return_code = + os_driver_complete_registration(rng_reg_handle, + rng_major, RNG_DRIVER_NAME); + } + /* RNG is working */ + + check_err: + + /* If FIFO underflow or other error occurred during drain, this will fail, + * as system will have been put into fail mode by SCC. */ + if ((return_code == OS_ERROR_OK_S) + && (rng_availability == RNG_STATUS_CHECKING)) { + RNG_PUT_RNG_TO_SLEEP(); + rng_availability = RNG_STATUS_OK; /* RNG & driver are ready */ + } else if (return_code != OS_ERROR_OK_S) { + os_printk(KERN_ALERT "Driver initialization failed. %d", + return_code); + rng_cleanup(); + } + + os_dev_init_return(return_code); + +} /* rng_init */ + +/*****************************************************************************/ +/* fn rng_shutdown() */ +/*****************************************************************************/ +/*! + * Prepare driver for exit. + * + * This is called during @c rmmod when the driver is unloading. + * Try to undo whatever was done during #rng_init(), to make the machine be + * in the same state, if possible. + * + * Calls rng_cleanup() to do all work, and then unmap device's register space. + */ +OS_DEV_SHUTDOWN(rng_shutdown) +{ + LOG_KDIAG("shutdown called"); + + rng_cleanup(); + + os_driver_remove_registration(rng_reg_handle); + if (rng_base != NULL) { + /* release the virtual memory map to the RNG */ + os_unmap_device((void *)rng_base, RNG_ADDRESS_RANGE); + rng_base = NULL; + } + + os_dev_shutdown_return(OS_ERROR_OK_S); +} /* rng_shutdown */ + +/*****************************************************************************/ +/* fn rng_cleanup() */ +/*****************************************************************************/ +/*! + * Undo everything done by rng_init() and place driver in fail mode. + * + * Deregister from SCC, stop tasklet, shutdown the RNG. Leave the register + * map in place in case other drivers call rng_read/write_register() + * + * @return void + */ +static void rng_cleanup(void) +{ + struct clk *clk; + +#ifdef FSL_HAVE_RNGA + scc_stop_monitoring_security_failure(rng_sec_failure); +#endif + + clk = clk_get(NULL, "rng_clk"); + clk_disable(clk); + if (task_started) { + os_dev_stop_task(rng_entropy_task); + } + + if (rng_base != NULL) { + /* mask off RNG interrupts */ + RNG_MASK_ALL_INTERRUPTS(); + RNG_SLEEP(); + + if (rng_irq_set) { + /* unmap the interrupts from the IRQ lines */ + os_deregister_interrupt(INT_RNG); + rng_irq_set = FALSE; + } + LOG_KDIAG("Leaving rng driver status as failed"); + rng_availability = RNG_STATUS_FAILED; + } else { + LOG_KDIAG("Leaving rng driver status as unimplemented"); + rng_availability = RNG_STATUS_UNIMPLEMENTED; + } + LOG_KDIAG("Cleaned up"); +} /* rng_cleanup */ + +/*! + * Post-process routine for fsl_shw_get_random(). + * + * This function will copy the random data generated by the background task + * into the user's buffer and then free the local buffer. + * + * @param gen_entry The work request. + * + * @return 0 = meaning work completed, pass back result. + */ +static uint32_t finish_random(shw_queue_entry_t * gen_entry) +{ + rng_work_entry_t *work = (rng_work_entry_t *) gen_entry; + + if (work->hdr.flags & FSL_UCO_USERMODE_USER) { + os_copy_to_user(work->data_user, work->data_local, + work->length); + } else { + memcpy(work->data_user, work->data_local, work->length); + } + + os_free_memory(work->data_local); + work->data_local = NULL; + + return 0; /* means completed. */ +} + +/* REQ-FSLSHW-PINTFC-API-BASIC-RNG-002 */ +/*****************************************************************************/ +/* fn fsl_shw_get_random() */ +/*****************************************************************************/ +/*! + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return FSL_RETURN_NO_RESOURCE_S A return code of type #fsl_shw_return_t. + * FSL_RETURN_OK_S + */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, uint32_t length, + uint8_t * data) +{ + fsl_shw_return_t return_code = FSL_RETURN_NO_RESOURCE_S; + /* Boost up length to cover any 'missing' bytes at end of a word */ + uint32_t *buf = os_alloc_memory(length + 3, 0); + volatile rng_work_entry_t *work = os_alloc_memory(sizeof(*work), 0); + + if ((rng_availability != RNG_STATUS_OK) || (buf == NULL) + || (work == NULL)) { + if (rng_availability != RNG_STATUS_OK) { + LOG_KDIAG_ARGS("rng not available: %d\n", + rng_availability); + } else { + LOG_KDIAG_ARGS + ("Resource allocation failure: %d or %d bytes", + length, sizeof(*work)); + } + /* Cannot perform function. Clean up and clear out. */ + if (buf != NULL) { + os_free_memory(buf); + } + if (work != NULL) { + os_free_memory((void *)work); + } + } else { + unsigned blocking = user_ctx->flags & FSL_UCO_BLOCKING_MODE; + + work->hdr.user_ctx = user_ctx; + work->hdr.flags = user_ctx->flags; + work->hdr.callback = user_ctx->callback; + work->hdr.user_ref = user_ctx->user_ref; + work->hdr.postprocess = finish_random; + work->length = length; + work->data_local = buf; + work->data_user = data; + + RNG_ADD_WORK_ENTRY((rng_work_entry_t *) work); + + if (blocking) { + os_sleep(rng_wait_queue, work->completed != FALSE, + FALSE); + finish_random((shw_queue_entry_t *) work); + return_code = work->hdr.code; + os_free_memory((void *)work); + } else { + return_code = FSL_RETURN_OK_S; + } + } + + return return_code; +} /* fsl_shw_get_entropy */ + +/*****************************************************************************/ +/* fn fsl_shw_add_entropy() */ +/*****************************************************************************/ +/*! + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, uint32_t length, + uint8_t * data) +{ + fsl_shw_return_t return_code = FSL_RETURN_NO_RESOURCE_S; +#if defined(FSL_HAVE_RNGC) + /* No Entropy Register in RNGC */ + return_code = FSL_RETURN_OK_S; +#else + uint32_t *local_data = NULL; + if (rng_availability == RNG_STATUS_OK) { + /* make 32-bit aligned place to hold data */ + local_data = os_alloc_memory(length + 3, 0); + if (local_data == NULL) { + return_code = FSL_RETURN_NO_RESOURCE_S; + } else { + memcpy(local_data, data, length); + + /* Copy one word at a time to hardware */ + while (TRUE) { + register uint32_t *ptr = local_data; + + RNG_ADD_ENTROPY(*ptr++); + if (length <= 4) { + break; + } + length -= 4; + } + return_code = FSL_RETURN_OK_S; + os_free_memory(local_data); + } /* else local_data not NULL */ + + } +#endif + /* rng_availability is OK */ + return return_code; +} /* fsl_shw_add_entropy */ + +#ifdef RNG_REGISTER_PEEK_POKE +/*****************************************************************************/ +/* fn rng_read_register() */ +/*****************************************************************************/ +/* + * Debug routines to allow reading of RNG registers. + * + * This routine is only for accesses by other than this driver. + * + * @param register_offset The byte offset of the register to be read. + * @param value Pointer to store the value of the register. + * + * @return RNG_RET_OK or an error return. + */ +rng_return_t rng_read_register(uint32_t register_offset, uint32_t * value) +{ + rng_return_t return_code = RNG_RET_FAIL; + + if ((rng_availability == RNG_STATUS_OK) + || (rng_availability == RNG_STATUS_FAILED)) { + if ((rng_check_register_offset(register_offset) + && rng_check_register_accessible(register_offset, + RNG_CHECK_READ))) { + /* The guards let the check through */ + *value = RNG_READ_REGISTER(register_offset); + return_code = RNG_RET_OK; + } + } + + return return_code; +} /* rng_read_register */ + +/*****************************************************************************/ +/* fn rng_write_register() */ +/*****************************************************************************/ +/* + * Debug routines to allow writing of RNG registers. + * + * This routine is only for accesses by other than this driver. + * + * @param register_offset The byte offset of the register to be written. + * @param value Value to store in the register. + * + * @return RNG_RET_OK or an error return. + */ +rng_return_t rng_write_register(uint32_t register_offset, uint32_t value) +{ + rng_return_t return_code = RNG_RET_FAIL; + + if ((rng_availability == RNG_STATUS_OK) + || (rng_availability == RNG_STATUS_FAILED)) { + if ((rng_check_register_offset(register_offset) + && rng_check_register_accessible(register_offset, + RNG_CHECK_WRITE))) { + RNG_WRITE_REGISTER(register_offset, value); + return_code = RNG_RET_OK; + } + } + + return return_code; +} /* rng_write_register */ +#endif /* RNG_REGISTER_PEEK_POKE */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +#ifdef RNG_REGISTER_PEEK_POKE +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/*! + * Verify that the @c offset is appropriate for the RNG's register set. + * + * @param[in] offset The (byte) offset within the RNG block + * of the register to be accessed. See + * RNG(A, C) register definitions for meanings. + * + * This routine is only for checking accesses by other than this driver. + * + * @return 0 if register offset out of bounds, 1 if ok to use + */ +inline int rng_check_register_offset(uint32_t offset) +{ + int return_code = FALSE; /* invalid */ + + /* Make sure offset isn't too high and also that it is aligned to + * aa 32-bit offset (multiple of four). + */ + if ((offset < RNG_ADDRESS_RANGE) && (offset % sizeof(uint32_t) == 0)) { + return_code = TRUE; /* OK */ + } else { + pr_debug("RNG: Denied access to offset %8x\n", offset); + } + + return return_code; + +} /* rng_check_register */ + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/*! + * Make sure that register access is legal. + * + * Verify that, if in secure mode, only safe registers are used. + * For any register access, make sure that read-only registers are not written + * and that write-only registers are not read. This check also disallows any + * access to the RNG's Output FIFO, to prevent other drivers from draining the + * FIFO and causing an underflow condition. + * + * This routine is only for checking accesses by other than this driver. + * + * @param offset The (byte) offset within the RNG block + * of the register to be accessed. See + * @ref rngregs for meanings. + * @param access_write 0 for read, anything else for write + * + * @return 0 if invalid, 1 if OK. + */ +static int rng_check_register_accessible(uint32_t offset, int access_write) +{ + int return_code = FALSE; /* invalid */ + uint32_t secure = RNG_GET_HIGH_ASSURANCE(); + + /* First check for RNG in Secure Mode -- most registers inaccessible. + * Also disallowing access to RNG_OUTPUT_FIFO except by the driver. + */ + if (! +#ifdef FSL_HAVE_RNGA + (secure && + ((offset == RNGA_OUTPUT_FIFO) || + (offset == RNGA_MODE) || + (offset == RNGA_VERIFICATION_CONTROL) || + (offset == RNGA_OSCILLATOR_CONTROL_COUNTER) || + (offset == RNGA_OSCILLATOR1_COUNTER) || + (offset == RNGA_OSCILLATOR2_COUNTER) || + (offset == RNGA_OSCILLATOR_COUNTER_STATUS))) +#else /* RNGB or RNGC */ + (secure && + ((offset == RNGC_FIFO) || + (offset == RNGC_VERIFICATION_CONTROL) || + (offset == RNGC_OSC_COUNTER_CONTROL) || + (offset == RNGC_OSC_COUNTER) || + (offset == RNGC_OSC_COUNTER_STATUS))) +#endif + ) { + + /* Passed that test. Either not in high assurance, and/or are + checking register that is always available. Now check + R/W permissions. */ + if (access_write == RNG_CHECK_READ) { /* read request */ + /* Only the entropy register is write-only */ +#ifdef FSL_HAVE_RNGC + /* No registers are write-only */ + return_code = TRUE; +#else /* else RNGA or RNGB */ +#ifdef FSL_HAVE_RNGA + if (1) { +#else + if (!(offset == RNGB_ENTROPY)) { +#endif + return_code = TRUE; /* Let all others be read */ + } else { + pr_debug + ("RNG: Offset %04x denied read access\n", + offset); + } +#endif /* RNGA or RNGB */ + } /* read */ + else { /* access_write means write */ + /* Check against list of non-writable registers */ + if (! +#ifdef FSL_HAVE_RNGA + ((offset == RNGA_STATUS) || + (offset == RNGA_OUTPUT_FIFO) || + (offset == RNGA_OSCILLATOR1_COUNTER) || + (offset == RNGA_OSCILLATOR2_COUNTER) || + (offset == RNGA_OSCILLATOR_COUNTER_STATUS)) +#else /* FSL_HAVE_RNGB or FSL_HAVE_RNGC */ + ((offset == RNGC_STATUS) || + (offset == RNGC_FIFO) || + (offset == RNGC_OSC_COUNTER) || + (offset == RNGC_OSC_COUNTER_STATUS)) +#endif + ) { + return_code = TRUE; /* can be written */ + } else { + LOG_KDIAG_ARGS + ("Offset %04x denied write access", offset); + } + } /* write */ + } /* not high assurance and inaccessible register... */ + else { + LOG_KDIAG_ARGS("Offset %04x denied high-assurance access", + offset); + } + + return return_code; +} /* rng_check_register_accessible */ +#endif /* RNG_REGISTER_PEEK_POKE */ + +/*****************************************************************************/ +/* fn rng_irq() */ +/*****************************************************************************/ +/*! + * This is the interrupt handler for the RNG. It is only ever invoked if the + * RNG detects a FIFO Underflow error. + * + * If the error is a Security Violation, this routine will + * set the #rng_availability to #RNG_STATUS_FAILED, as the entropy pool may + * have been corrupted. The RNG will also be placed into low power mode. The + * SCC will have noticed the problem as well. + * + * The other possibility, if the RNG is not in High Assurance mode, would be + * simply a FIFO Underflow. No special action, other than to + * clear the interrupt, is taken. + */ +OS_DEV_ISR(rng_irq) +{ + int handled = FALSE; /* assume interrupt isn't from RNG */ + + LOG_KDIAG("rng irq!"); + + if (RNG_SEED_DONE()) { + complete(&rng_seed_done); + RNG_CLEAR_ALL_STATUS(); + handled = TRUE; + } + + if (RNG_SELF_TEST_DONE()) { + complete(&rng_self_testing); + RNG_CLEAR_ALL_STATUS(); + handled = TRUE; + } + /* Look to see whether RNG needs attention */ + if (RNG_HAS_ERROR()) { + if (RNG_GET_HIGH_ASSURANCE()) { + RNG_SLEEP(); + rng_availability = RNG_STATUS_FAILED; + RNG_MASK_ALL_INTERRUPTS(); + } + handled = TRUE; + /* Clear the interrupt */ + RNG_CLEAR_ALL_STATUS(); + + } + os_dev_isr_return(handled); +} /* rng_irq */ + +/*****************************************************************************/ +/* fn map_RNG_memory() */ +/*****************************************************************************/ +/*! + * Place the RNG's memory into kernel virtual space. + * + * @return OS_ERROR_OK_S on success, os_error_code on failure + */ +static os_error_code rng_map_RNG_memory(void) +{ + os_error_code error_code = OS_ERROR_FAIL_S; + + rng_base = os_map_device(RNG_BASE_ADDR, RNG_ADDRESS_RANGE); + if (rng_base == NULL) { + /* failure ! */ + LOG_KDIAG("RNG Driver: ioremap failed."); + } else { + error_code = OS_ERROR_OK_S; + } + + return error_code; +} /* rng_map_RNG_memory */ + +/*****************************************************************************/ +/* fn rng_setup_interrupt_handling() */ +/*****************************************************************************/ +/*! + * Register #rng_irq() as the interrupt handler for #INT_RNG. + * + * @return OS_ERROR_OK_S on success, os_error_code on failure + */ +static os_error_code rng_setup_interrupt_handling(void) +{ + os_error_code error_code; + + /* + * Install interrupt service routine for the RNG. Ignore the + * assigned IRQ number. + */ + error_code = os_register_interrupt(RNG_DRIVER_NAME, INT_RNG, + OS_DEV_ISR_REF(rng_irq)); + if (error_code != OS_ERROR_OK_S) { + LOG_KDIAG("RNG Driver: Error installing Interrupt Handler"); + } else { + rng_irq_set = TRUE; + RNG_UNMASK_ALL_INTERRUPTS(); + } + + return error_code; +} /* rng_setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn rng_grab_config_values() */ +/*****************************************************************************/ +/*! + * Read configuration information from the RNG. + * + * Sets #rng_output_fifo_size. + * + * @return A error code indicating whether the part is the expected one. + */ +static os_error_code rng_grab_config_values(void) +{ + enum rng_type type; + os_error_code ret = OS_ERROR_FAIL_S; + + /* Go for type, versions... */ + type = RNG_GET_RNG_TYPE(); + + /* Make sure type is the one this code has been compiled for. */ + if (RNG_VERIFY_TYPE(type)) { + rng_output_fifo_size = RNG_GET_FIFO_SIZE(); + if (rng_output_fifo_size != 0) { + ret = OS_ERROR_OK_S; + } + } + if (ret != OS_ERROR_OK_S) { + LOG_KDIAG_ARGS + ("Unknown or unexpected RNG type %d (FIFO size %d)." + " Failing driver initialization", type, + rng_output_fifo_size); + } + + return ret; +} + + /* rng_grab_config_values */ + +/*****************************************************************************/ +/* fn rng_drain_fifo() */ +/*****************************************************************************/ +/*! + * This function copies words from the RNG FIFO into the caller's buffer. + * + * + * @param random_p Location to copy random data + * @param count_words Number of words to copy + * + * @return An error code. + */ +static fsl_shw_return_t rng_drain_fifo(uint32_t * random_p, int count_words) +{ + + int words_in_rng; /* Number of words available now in RNG */ + fsl_shw_return_t code = FSL_RETURN_ERROR_S; + int sequential_count = 0; /* times through big while w/empty FIFO */ + int fifo_empty_count = 0; /* number of times FIFO was empty */ + int max_sequential = 0; /* max times 0 seen in a row */ +#if !defined(FSL_HAVE_RNGA) + int count_for_reseed = 0; + INIT_COMPLETION(rng_seed_done); +#endif +#if !defined(FSL_HAVE_RNGA) + if (RNG_RESEED()) { + do { + LOG_KDIAG("Reseeding RNG"); + + RNG_CLEAR_ERR(); + RNG_SEED_GEN(); + wait_for_completion(&rng_seed_done); + if (count_for_reseed == 3) { + os_printk(KERN_ALERT + "Device was not able to enter RESEED Mode\n"); + code = FSL_RETURN_INTERNAL_ERROR_S; + } + count_for_reseed++; + } while (RNG_CHECK_SEED_ERR()); + } +#endif + /* Copy all of them in. Stop if pool fills. */ + while ((rng_availability == RNG_STATUS_OK) && (count_words > 0)) { + /* Ask RNG how many words currently in FIFO */ + words_in_rng = RNG_GET_WORDS_IN_FIFO(); + if (words_in_rng == 0) { + ++sequential_count; + fifo_empty_count++; + if (sequential_count > max_sequential) { + max_sequential = sequential_count; + } + if (sequential_count >= RNG_MAX_TRIES) { + LOG_KDIAG_ARGS("FIFO staying empty (%d)", + words_in_rng); + code = FSL_RETURN_NO_RESOURCE_S; + break; + } + } else { + /* Found at least one word */ + sequential_count = 0; + /* Now adjust: words_in_rng = MAX(count_words, words_in_rng) */ + words_in_rng = (count_words < words_in_rng) + ? count_words : words_in_rng; + } /* else found words */ + +#ifdef RNG_FORCE_FIFO_UNDERFLOW + /* + * For unit test, force occasional extraction of more words than + * available. This should cause FIFO Underflow, and IRQ invocation. + */ + words_in_rng = count_words; +#endif + + /* Copy out all available & neeeded data */ + while (words_in_rng-- > 0) { + *random_p++ = RNG_READ_FIFO(); + count_words--; + } + } /* while words still needed */ + + if (count_words == 0) { + code = FSL_RETURN_OK_S; + } + if (fifo_empty_count != 0) { + LOG_KDIAG_ARGS("FIFO empty %d times, max loop count %d", + fifo_empty_count, max_sequential); + } + + return code; +} /* rng_drain_fifo */ + +/*****************************************************************************/ +/* fn rng_entropy_task() */ +/*****************************************************************************/ +/*! + * This is the background task of the driver. It is scheduled by + * RNG_ADD_WORK_ENTRY(). + * + * This will process each entry on the #rng_work_queue. Blocking requests will + * cause sleepers to be awoken. Non-blocking requests will be placed on the + * results queue, and if appropriate, the callback function will be invoked. + */ +OS_DEV_TASK(rng_entropy_task) +{ + rng_work_entry_t *work; + + os_dev_task_begin(); + +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG("entropy task starting"); +#endif + + while ((work = RNG_GET_WORK_ENTRY()) != NULL) { +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG_ARGS("found %d bytes of work at %p (%p)", + work->length, work, work->data_local); +#endif + work->hdr.code = rng_drain_fifo(work->data_local, + BYTES_TO_WORDS(work->length)); + work->completed = TRUE; + + if (work->hdr.flags & FSL_UCO_BLOCKING_MODE) { +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG("Waking queued processes"); +#endif + os_wake_sleepers(rng_wait_queue); + } else { + os_lock_context_t lock_context; + + os_lock_save_context(rng_queue_lock, lock_context); + RNG_ADD_QUEUE_ENTRY(&work->hdr.user_ctx->result_pool, + work); + os_unlock_restore_context(rng_queue_lock, lock_context); + + if (work->hdr.flags & FSL_UCO_CALLBACK_MODE) { + if (work->hdr.callback != NULL) { + work->hdr.callback(work->hdr.user_ctx); + } else { +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG_ARGS + ("Callback ptr for %p is NULL", + work); +#endif + } + } + } + } /* while */ + +#ifdef RNG_ENTROPY_DEBUG + LOG_KDIAG("entropy task ending"); +#endif + + os_dev_task_return(OS_ERROR_OK_S); +} /* rng_entropy_task */ + +#ifdef FSL_HAVE_RNGA +/*****************************************************************************/ +/* fn rng_sec_failure() */ +/*****************************************************************************/ +/*! + * Function to handle "Security Alarm" indication from SCC. + * + * This function is registered with the Security Monitor ans the callback + * function for the RNG driver. Upon alarm, it will shut down the driver so + * that no more random data can be retrieved. + * + * @return void + */ +static void rng_sec_failure(void) +{ + os_printk(KERN_ALERT "RNG Driver: Security Failure Alarm received.\n"); + + rng_cleanup(); + + return; +} +#endif + +#ifdef RNG_REGISTER_DEBUG +/*****************************************************************************/ +/* fn dbg_rng_read_register() */ +/*****************************************************************************/ +/*! + * Noisily read a 32-bit value to an RNG register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +static uint32_t dbg_rng_read_register(uint32_t offset) +{ + uint32_t value; + + value = os_read32(rng_base + offset); +#ifndef RNG_ENTROPY_DEBUG + if (offset != RNG_OUTPUT_FIFO) { +#endif + pr_debug("RNG RD: 0x%4x : 0x%08x\n", offset, value); +#ifndef RNG_ENTROPY_DEBUG + } +#endif + return value; +} + +/*****************************************************************************/ +/* fn dbg_rng_write_register() */ +/*****************************************************************************/ +/*! + * Noisily write a 32-bit value to an RNG register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +static void dbg_rng_write_register(uint32_t offset, uint32_t value) +{ + LOG_KDIAG_ARGS("WR: 0x%4x : 0x%08x", offset, value); + os_write32(value, rng_base + offset); + return; +} + +#endif /* RNG_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/rng/shw_driver.c b/drivers/mxc/security/rng/shw_driver.c new file mode 100644 index 000000000000..24c912245099 --- /dev/null +++ b/drivers/mxc/security/rng/shw_driver.c @@ -0,0 +1,2335 @@ +/* + * Copyright 2005-2009 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 shw_driver.c + * + * This is the user-mode driver code for the FSL Security Hardware (SHW) API. + * as well as the 'common' FSL SHW API code for kernel API users. + * + * Its interaction with the Linux kernel is from calls to shw_init() when the + * driver is loaded, and shw_shutdown() should the driver be unloaded. + * + * The User API (driver interface) is handled by the following functions: + * @li shw_open() - handles open() system call on FSL SHW device + * @li shw_release() - handles close() system call on FSL SHW device + * @li shw_ioctl() - handles ioctl() system call on FSL SHW device + * + * The driver also provides the following functions for kernel users of the FSL + * SHW API: + * @li fsl_shw_register_user() + * @li fsl_shw_deregister_user() + * @li fsl_shw_get_capabilities() + * @li fsl_shw_get_results() + * + * All other functions are internal to the driver. + * + * The life of the driver starts at boot (or module load) time, with a call by + * the kernel to shw_init(). + * + * The life of the driver ends when the kernel is shutting down (or the driver + * is being unloaded). At this time, shw_shutdown() is called. No function + * will ever be called after that point. + * + * In the case that the driver is reloaded, a new copy of the driver, with + * fresh global values, etc., is loaded, and there will be a new call to + * shw_init(). + * + * In user mode, the user's fsl_shw_register_user() call causes an open() event + * on the driver followed by a ioctl() with the registration information. Any + * subsequent API calls by the user are handled through the ioctl() function + * and shuffled off to the appropriate routine (or driver) for service. The + * fsl_shw_deregister_user() call by the user results in a close() function + * call on the driver. + * + * In kernel mode, the driver provides the functions fsl_shw_register_user(), + * fsl_shw_deregister_user(), fsl_shw_get_capabilities(), and + * fsl_shw_get_results(). Other parts of the API are provided by other + * drivers, if available, to support the cryptographic functions. + */ + +#include "portable_os.h" +#include "fsl_shw.h" +#include "fsl_shw_keystore.h" + +#include "shw_internals.h" + +#ifdef FSL_HAVE_SCC2 +#include <linux/mxc_scc2_driver.h> +#else +#include <linux/mxc_scc_driver.h> +#endif + +#ifdef SHW_DEBUG +#include <diagnostic.h> +#endif + +/****************************************************************************** + * + * Function Declarations + * + *****************************************************************************/ + +/* kernel interface functions */ +OS_DEV_INIT_DCL(shw_init); +OS_DEV_SHUTDOWN_DCL(shw_shutdown); +OS_DEV_IOCTL_DCL(shw_ioctl); +OS_DEV_MMAP_DCL(shw_mmap); + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_smalloc); +EXPORT_SYMBOL(fsl_shw_sfree); +EXPORT_SYMBOL(fsl_shw_sstatus); +EXPORT_SYMBOL(fsl_shw_diminish_perms); +EXPORT_SYMBOL(do_scc_encrypt_region); +EXPORT_SYMBOL(do_scc_decrypt_region); + +EXPORT_SYMBOL(do_system_keystore_slot_alloc); +EXPORT_SYMBOL(do_system_keystore_slot_dealloc); +EXPORT_SYMBOL(do_system_keystore_slot_load); +EXPORT_SYMBOL(do_system_keystore_slot_encrypt); +EXPORT_SYMBOL(do_system_keystore_slot_decrypt); +#endif + +static os_error_code +shw_handle_scc_sfree(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_sstatus(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_drop_perms(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_encrypt(fsl_shw_uco_t * user_ctx, uint32_t info); + +static os_error_code +shw_handle_scc_decrypt(fsl_shw_uco_t * user_ctx, uint32_t info); + +#ifdef FSL_HAVE_SCC2 +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base); +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base); +void *lookup_user_partition(fsl_shw_uco_t * user_ctx, uint32_t user_base); + +#endif /* FSL_HAVE_SCC2 */ + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +/*! + * Major node (user/device interaction value) of this driver. + */ +static int shw_major_node = SHW_MAJOR_NODE; + +/*! + * Flag to know whether the driver has been associated with its user device + * node (e.g. /dev/shw). + */ +static int shw_device_registered = 0; + +/*! + * OS-dependent handle used for registering user interface of a driver. + */ +static os_driver_reg_t reg_handle; + +/*! + * Linked List of registered users of the API + */ +fsl_shw_uco_t *user_list; + +/*! + * This is the lock for all user request pools. H/W component drivers may also + * use it for their own work queues. + */ +os_lock_t shw_queue_lock = NULL; + +/* This is the system keystore object */ +fsl_shw_kso_t system_keystore; + +#ifndef FSL_HAVE_SAHARA +/*! Empty list of supported symmetric algorithms. */ +static fsl_shw_key_alg_t pf_syms[] = { +}; + +/*! Empty list of supported symmetric modes. */ +static fsl_shw_sym_mode_t pf_modes[] = { +}; + +/*! Empty list of supported hash algorithms. */ +static fsl_shw_hash_alg_t pf_hashes[] = { +}; +#endif /* no Sahara */ + +/*! This matches SHW capabilities... */ +static fsl_shw_pco_t cap = { + 1, 3, /* api version number - major & minor */ + 2, 3, /* driver version number - major & minor */ + sizeof(pf_syms) / sizeof(fsl_shw_key_alg_t), /* key alg count */ + pf_syms, /* key alg list ptr */ + sizeof(pf_modes) / sizeof(fsl_shw_sym_mode_t), /* sym mode count */ + pf_modes, /* modes list ptr */ + sizeof(pf_hashes) / sizeof(fsl_shw_hash_alg_t), /* hash alg count */ + pf_hashes, /* hash list ptr */ + /* + * The following table must be set to handle all values of key algorithm + * and sym mode, and be in the correct order.. + */ + { /* Stream, ECB, CBC, CTR */ + {0, 0, 0, 0} + , /* HMAC */ + {0, 0, 0, 0} + , /* AES */ + {0, 0, 0, 0} + , /* DES */ +#ifdef FSL_HAVE_DRYICE + {0, 1, 1, 0} + , /* 3DES - ECB and CBC */ +#else + {0, 0, 0, 0} + , /* 3DES */ +#endif + {0, 0, 0, 0} /* ARC4 */ + } + , + 0, 0, /* SCC driver version */ + 0, 0, 0, /* SCC version/capabilities */ + {{0, 0} + } + , /* (filled in during OS_INIT) */ +}; + +/* These are often handy */ +#ifndef FALSE +/*! Not true. Guaranteed to be zero. */ +#define FALSE 0 +#endif +#ifndef TRUE +/*! True. Guaranteed to be non-zero. */ +#define TRUE 1 +#endif + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn shw_init() */ +/*****************************************************************************/ +/*! + * Initialize the driver. + * + * This routine is called during kernel init or module load (insmod). + * + * @return OS_ERROR_OK_S on success, errno on failure + */ +OS_DEV_INIT(shw_init) +{ + os_error_code error_code = OS_ERROR_NO_MEMORY_S; /* assume failure */ + scc_config_t *shw_capabilities; + +#ifdef SHW_DEBUG + LOG_KDIAG("SHW Driver: Loading"); +#endif + + user_list = NULL; + shw_queue_lock = os_lock_alloc_init(); + + if (shw_queue_lock != NULL) { + error_code = shw_setup_user_driver_interaction(); + if (error_code != OS_ERROR_OK_S) { +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("SHW Driver: Failed to setup user i/f: %d", + error_code); +#endif + } + } + + /* queue_lock not NULL */ + /* Fill in the SCC portion of the capabilities object */ + shw_capabilities = scc_get_configuration(); + cap.scc_driver_major = shw_capabilities->driver_major_version; + cap.scc_driver_minor = shw_capabilities->driver_minor_version; + cap.scm_version = shw_capabilities->scm_version; + cap.smn_version = shw_capabilities->smn_version; + cap.block_size_bytes = shw_capabilities->block_size_bytes; + +#ifdef FSL_HAVE_SCC + cap.u.scc_info.black_ram_size_blocks = + shw_capabilities->black_ram_size_blocks; + cap.u.scc_info.red_ram_size_blocks = + shw_capabilities->red_ram_size_blocks; +#elif defined(FSL_HAVE_SCC2) + cap.u.scc2_info.partition_size_bytes = + shw_capabilities->partition_size_bytes; + cap.u.scc2_info.partition_count = shw_capabilities->partition_count; +#endif + +#if defined(FSL_HAVE_SCC2) || defined(FSL_HAVE_DRYICE) + if (error_code == OS_ERROR_OK_S) { + /* set up the system keystore, using the default keystore handler */ + fsl_shw_init_keystore_default(&system_keystore); + + if (fsl_shw_establish_keystore(NULL, &system_keystore) + == FSL_RETURN_OK_S) { + error_code = OS_ERROR_OK_S; + } else { + error_code = OS_ERROR_FAIL_S; + } + + if (error_code != OS_ERROR_OK_S) { +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("Registering the system keystore failed with error" + " code: %d\n", error_code); +#endif + } + } +#endif /* FSL_HAVE_SCC2 */ + + if (error_code != OS_ERROR_OK_S) { +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: Driver initialization failed. %d", + error_code); +#endif + shw_cleanup(); + } else { +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: Driver initialization complete."); +#endif + } + + os_dev_init_return(error_code); +} /* shw_init */ + +/*****************************************************************************/ +/* fn shw_shutdown() */ +/*****************************************************************************/ +/*! + * Prepare driver for exit. + * + * This is called during @c rmmod when the driver is unloading or when the + * kernel is shutting down. + * + * Calls shw_cleanup() to do all work to undo anything that happened during + * initialization or while driver was running. + */ +OS_DEV_SHUTDOWN(shw_shutdown) +{ + +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: shutdown called"); +#endif + shw_cleanup(); + + os_dev_shutdown_return(OS_ERROR_OK_S); +} /* shw_shutdown */ + +/*****************************************************************************/ +/* fn shw_cleanup() */ +/*****************************************************************************/ +/*! + * Prepare driver for shutdown. + * + * Remove the driver registration. + * + */ +static void shw_cleanup(void) +{ + if (shw_device_registered) { + + /* Turn off the all association with OS */ + os_driver_remove_registration(reg_handle); + shw_device_registered = 0; + } + + if (shw_queue_lock != NULL) { + os_lock_deallocate(shw_queue_lock); + } +#ifdef SHW_DEBUG + LOG_KDIAG("SHW Driver: Cleaned up"); +#endif +} /* shw_cleanup */ + +/*****************************************************************************/ +/* fn shw_open() */ +/*****************************************************************************/ +/*! + * Handle @c open() call from user. + * + * @return OS_ERROR_OK_S on success (always!) + */ +OS_DEV_OPEN(shw_open) +{ + os_error_code status = OS_ERROR_OK_S; + + os_dev_set_user_private(NULL); /* Make sure */ + + os_dev_open_return(status); +} /* shw_open */ + +/*****************************************************************************/ +/* fn shw_ioctl() */ +/*****************************************************************************/ +/*! + * Process an ioctl() request from user-mode API. + * + * This code determines which of the API requests the user has made and then + * sends the request off to the appropriate function. + * + * @return ioctl_return() + */ +OS_DEV_IOCTL(shw_ioctl) +{ + os_error_code code = OS_ERROR_FAIL_S; + + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: IOCTL %d received", os_dev_get_ioctl_op()); +#endif + switch (os_dev_get_ioctl_op()) { + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_REGISTER_USER: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: register_user ioctl received"); +#endif + { + fsl_shw_uco_t *user_ctx = + os_alloc_memory(sizeof(*user_ctx), 0); + + if (user_ctx == NULL) { + code = OS_ERROR_NO_MEMORY_S; + } else { + code = + init_uco(user_ctx, + (fsl_shw_uco_t *) + os_dev_get_ioctl_arg()); + if (code == OS_ERROR_OK_S) { + os_dev_set_user_private(user_ctx); + } else { + os_free_memory(user_ctx); + } + } + } + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_DEREGISTER_USER: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: deregister_user ioctl received"); +#endif + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + SHW_REMOVE_USER(user_ctx); + } + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_RESULTS: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: get_results ioctl received"); +#endif + code = get_results(user_ctx, + (struct results_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_CAPABILITIES: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: get_capabilities ioctl received"); +#endif + code = get_capabilities(user_ctx, + (fsl_shw_pco_t *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_GET_RANDOM: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: get_random ioctl received"); +#endif + code = get_random(user_ctx, + (struct get_random_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_ADD_ENTROPY: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: add_entropy ioctl received"); +#endif + code = add_entropy(user_ctx, + (struct add_entropy_req *) + os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_DROP_PERMS: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: drop permissions ioctl received"); +#endif + code = + shw_handle_scc_drop_perms(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SSTATUS: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: sstatus ioctl received"); +#endif + code = shw_handle_scc_sstatus(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SFREE: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: sfree ioctl received"); +#endif + code = shw_handle_scc_sfree(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SCC_ENCRYPT: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: scc encrypt ioctl received"); +#endif + code = shw_handle_scc_encrypt(user_ctx, os_dev_get_ioctl_arg()); + break; + + case SHW_IOCTL_REQUEST + SHW_USER_REQ_SCC_DECRYPT: +#ifdef SHW_DEBUG + LOG_KDIAG("SHW: scc decrypt ioctl received"); +#endif + code = shw_handle_scc_decrypt(user_ctx, os_dev_get_ioctl_arg()); + break; + + default: +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: Unexpected ioctl %d", + os_dev_get_ioctl_op()); +#endif + break; + } + + os_dev_ioctl_return(code); +} + +#ifdef FSL_HAVE_SCC2 + +/*****************************************************************************/ +/* fn get_user_smid() */ +/*****************************************************************************/ +uint32_t get_user_smid(void *proc) +{ + /* + * A real implementation would have some way to handle signed applications + * which wouild be assigned distinct SMIDs. For the reference + * implementation, we show where this would be determined (here), but + * always provide a fixed answer, thus not separating users at all. + */ + + return 0x42eaae42; +} + +/* user_base: userspace base address of the partition + * kernel_base: kernel mode base address of the partition + */ +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base) +{ + fsl_shw_spo_t *partition_info; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + if (user_ctx == NULL) { + goto out; + } + + partition_info = os_alloc_memory(sizeof(fsl_shw_spo_t), GFP_KERNEL); + + if (partition_info == NULL) { + goto out; + } + + /* stuff the partition info, then put it at the front of the chain */ + partition_info->user_base = user_base; + partition_info->kernel_base = kernel_base; + partition_info->next = user_ctx->partition; + + user_ctx->partition = (struct fsl_shw_spo_t *)partition_info; + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("partition with user_base=%p, kernel_base=%p registered.", + (void *)user_base, kernel_base); +#endif + + ret = FSL_RETURN_OK_S; + + out: + + return ret; +} + +/* if the partition is in the users list, remove it */ +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base) +{ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + fsl_shw_spo_t *last = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("deregister_user_partition: partition with " + "user_base=%p, kernel_base=%p deregistered.\n", + (void *)curr->user_base, curr->kernel_base); +#endif + + if (last == curr) { + user_ctx->partition = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } else { + last->next = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } + } + last = curr; + curr = (fsl_shw_spo_t *) curr->next; + } + + return FSL_RETURN_ERROR_S; +} + +/* Find the kernel-mode address of the partition. + * This can then be passed to the SCC functions. + */ +void *lookup_user_partition(fsl_shw_uco_t * user_ctx, uint32_t user_base) +{ + /* search through the partition chain to find one that matches the user base + * address. + */ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + return curr->kernel_base; + } + curr = (fsl_shw_spo_t *) curr->next; + } + return NULL; +} + +#endif /* FSL_HAVE_SCC2 */ + +/*! +******************************************************************************* +* This function implements the smalloc() function for userspace programs, by +* making a call to the SCC2 mmap() function that acquires a region of secure +* memory on behalf of the user, and then maps it into the users memory space. +* Currently, the only memory size supported is that of a single SCC2 partition. +* Requests for other sized memory regions will fail. +*/ +OS_DEV_MMAP(shw_mmap) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; + +#ifdef FSL_HAVE_SCC2 + { + scc_return_t scc_ret; + fsl_shw_return_t fsl_ret; + uint32_t partition_registered = FALSE; + + uint32_t user_base; + void *partition_base; + uint32_t smid; + scc_config_t *scc_configuration; + + int part_no = -1; + uint32_t part_phys; + + fsl_shw_uco_t *user_ctx = + (fsl_shw_uco_t *) os_dev_get_user_private(); + + /* Make sure that the user context is valid */ + if (user_ctx == NULL) { + user_ctx = + os_alloc_memory(sizeof(*user_ctx), GFP_KERNEL); + + if (user_ctx == NULL) { + status = OS_ERROR_NO_MEMORY_S; + goto out; + } + fsl_shw_register_user(user_ctx); + os_dev_set_user_private(user_ctx); + } + + /* Determine the size of a secure partition */ + scc_configuration = scc_get_configuration(); + + /* Check that the memory size requested is equal to the partition + * size, and that the requested destination is on a page boundary. + */ + if (((os_mmap_user_base() % PAGE_SIZE) != 0) || + (os_mmap_memory_size() != + scc_configuration->partition_size_bytes)) { + status = OS_ERROR_BAD_ARG_S; + goto out; + } + + /* Retrieve the SMID associated with the user */ + smid = get_user_smid(user_ctx->process); + + /* Attempt to allocate a secure partition */ + scc_ret = + scc_allocate_partition(smid, &part_no, &partition_base, + &part_phys); + if (scc_ret != SCC_RET_OK) { + pr_debug + ("SCC mmap() request failed to allocate partition;" + " error %d\n", status); + status = OS_ERROR_FAIL_S; + goto out; + } + + pr_debug("scc_mmap() acquired partition %d at %08x\n", + part_no, part_phys); + + /* Record partition info in the user context */ + user_base = os_mmap_user_base(); + fsl_ret = + register_user_partition(user_ctx, user_base, + partition_base); + + if (fsl_ret != FSL_RETURN_OK_S) { + pr_debug + ("SCC mmap() request failed to register partition with user" + " context, error: %d\n", fsl_ret); + status = OS_ERROR_FAIL_S; + } + + partition_registered = TRUE; + + status = map_user_memory(os_mmap_memory_ctx(), part_phys, + os_mmap_memory_size()); + +#ifdef SHW_DEBUG + if (status == OS_ERROR_OK_S) { + LOG_KDIAG_ARGS + ("Partition allocated: user_base=%p, partition_base=%p.", + (void *)user_base, partition_base); + } +#endif + + out: + /* If there is an error it has to be handled here */ + if (status != OS_ERROR_OK_S) { + /* if the partition was registered with the user, unregister it. */ + if (partition_registered == TRUE) { + deregister_user_partition(user_ctx, user_base); + } + + /* if the partition was allocated, deallocate it */ + if (partition_base != NULL) { + scc_release_partition(partition_base); + } + } + } +#endif /* FSL_HAVE_SCC2 */ + + return status; +} + +/*****************************************************************************/ +/* fn shw_release() */ +/*****************************************************************************/ +/*! + * Handle @c close() call from user. + * This is a Linux device driver interface routine. + * + * @return OS_ERROR_OK_S on success (always!) + */ +OS_DEV_CLOSE(shw_release) +{ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + os_error_code code = OS_ERROR_OK_S; + + if (user_ctx != NULL) { + + fsl_shw_deregister_user(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + + } + + os_dev_close_return(code); +} /* shw_release */ + +/*****************************************************************************/ +/* fn shw_user_callback() */ +/*****************************************************************************/ +/*! + * FSL SHW User callback function. + * + * This function is set in the kernel version of the user context as the + * callback function when the user mode user wants a callback. Its job is to + * inform the user process that results (may) be available. It does this by + * sending a SIGUSR2 signal which is then caught by the user-mode FSL SHW + * library. + * + * @param user_ctx Kernel version of uco associated with the request. + * + * @return void + */ +static void shw_user_callback(fsl_shw_uco_t * user_ctx) +{ +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: Signalling callback user process for context %p\n", + user_ctx); +#endif + os_send_signal(user_ctx->process, SIGUSR2); +} + +/*****************************************************************************/ +/* fn setup_user_driver_interaction() */ +/*****************************************************************************/ +/*! + * Register the driver with the kernel as the driver for shw_major_node. Note + * that this value may be zero, in which case the major number will be assigned + * by the OS. shw_major_node is never modified. + * + * The open(), ioctl(), and close() handles for the driver ned to be registered + * with the kernel. Upon success, shw_device_registered will be true; + * + * @return OS_ERROR_OK_S on success, or an os err code + */ +static os_error_code shw_setup_user_driver_interaction(void) +{ + os_error_code error_code; + + os_driver_init_registration(reg_handle); + os_driver_add_registration(reg_handle, OS_FN_OPEN, + OS_DEV_OPEN_REF(shw_open)); + os_driver_add_registration(reg_handle, OS_FN_IOCTL, + OS_DEV_IOCTL_REF(shw_ioctl)); + os_driver_add_registration(reg_handle, OS_FN_CLOSE, + OS_DEV_CLOSE_REF(shw_release)); + os_driver_add_registration(reg_handle, OS_FN_MMAP, + OS_DEV_MMAP_REF(shw_mmap)); + error_code = os_driver_complete_registration(reg_handle, shw_major_node, + SHW_DRIVER_NAME); + + if (error_code != OS_ERROR_OK_S) { + /* failure ! */ +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW Driver: register device driver failed: %d", + error_code); +#endif + } else { /* success */ + shw_device_registered = TRUE; +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW Driver: Major node is %d\n", + os_driver_get_major(reg_handle)); +#endif + } + + return error_code; +} /* shw_setup_user_driver_interaction */ + +/******************************************************************/ +/* User Mode Support */ +/******************************************************************/ + +/*! + * Initialze kernel User Context Object from User-space version. + * + * Copy user UCO into kernel UCO, set flags and fields for operation + * within kernel space. Add user to driver's list of users. + * + * @param user_ctx Pointer to kernel space UCO + * @param user_mode_uco User pointer to user space version + * + * @return os_error_code + */ +static os_error_code init_uco(fsl_shw_uco_t * user_ctx, void *user_mode_uco) +{ + os_error_code code; + + code = os_copy_from_user(user_ctx, user_mode_uco, sizeof(*user_ctx)); + if (code == OS_ERROR_OK_S) { + user_ctx->flags |= FSL_UCO_USERMODE_USER; + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + user_ctx->process = os_get_process_handle(); + user_ctx->callback = shw_user_callback; + SHW_ADD_USER(user_ctx); + } +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: init uco returning %d (flags %x)", + code, user_ctx->flags); +#endif + + return code; +} + +/*! + * Copy array from kernel to user space. + * + * This routine will check bounds before trying to copy, and return failure + * on bounds violation or error during the copy. + * + * @param userloc Location in userloc to place data. If NULL, the function + * will do nothing (except return NULL). + * @param userend Address beyond allowed copy region at @c userloc. + * @param data_start Location of data to be copied + * @param element_size sizeof() an element + * @param element_count Number of elements of size element_size to copy. + * @return New value of userloc, or NULL if there was an error. + */ +inline static void *copy_array(void *userloc, void *userend, void *data_start, + unsigned element_size, unsigned element_count) +{ + unsigned byte_count = element_size * element_count; + + if ((userloc == NULL) || (userend == NULL) + || ((userloc + byte_count) >= userend) || + (copy_to_user(userloc, data_start, byte_count) != OS_ERROR_OK_S)) { + userloc = NULL; + } else { + userloc += byte_count; + } + + return userloc; +} + +/*! + * Send an FSL SHW API return code up into the user-space request structure. + * + * @param user_header User address of request block / request header + * @param result_code The FSL SHW API code to be placed at header.code + * + * @return an os_error_code + * + * NOTE: This function assumes that the shw_req_header is at the beginning of + * each request structure. + */ +inline static os_error_code copy_fsl_code(void *user_header, + fsl_shw_return_t result_code) +{ + return os_copy_to_user(user_header + + offsetof(struct shw_req_header, code), + &result_code, sizeof(result_code)); +} + +static os_error_code shw_handle_scc_drop_perms(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + scc_return_t scc_ret; + scc_partition_info_t partition_info; + void *kernel_base; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("_scc_drop_perms(): failed to find partition\n"); +#endif + goto out; + } + + /* call scc driver to perform the drop */ + scc_ret = scc_diminish_permissions(kernel_base, + partition_info.permissions); + if (scc_ret == SCC_RET_OK) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_sstatus(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + scc_partition_info_t partition_info; + void *kernel_base; + + status = os_copy_from_user(&partition_info, + (void *)info, sizeof(partition_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("Failed to find partition\n"); +#endif + goto out; + } + + /* Call the SCC driver to ask about the partition status */ + partition_info.status = scc_partition_status(kernel_base); + + /* and copy the structure out */ + status = os_copy_to_user((void *)info, + &partition_info, sizeof(partition_info)); + + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_sfree(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + { + scc_partition_info_t partition_info; + void *kernel_base; + int ret; + + status = os_copy_from_user(&partition_info, + (void *)info, + sizeof(partition_info)); + + /* check that the copy was successful */ + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = + lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("failed to find partition\n"); +#endif /*SHW_DEBUG */ + goto out; + } + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + ret = unmap_user_memory(partition_info.user_base, 8192); + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + + /* release the partition */ + scc_release_partition(kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition_info.user_base); + + status = OS_ERROR_OK_S; + + } else { +#ifdef SHW_DEBUG + LOG_KDIAG("do_munmap not successful!"); +#endif + } + + } + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_encrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr = NULL; + void *partition_base = NULL; + scc_config_t *scc_configuration; + + status = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("failed to find secure partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + status = OS_ERROR_FAIL_S; + goto out; + } + + /* wire down black_data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + status = OS_ERROR_FAIL_S; + goto out; + } + + retval = + do_scc_encrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + if (retval == FSL_RETURN_OK_S) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + /* release black data */ + unwire_user_memory(&page_ctx); + } + out: + +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code shw_handle_scc_decrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr; + void *partition_base; + scc_config_t *scc_configuration; + + status = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("partition_base: %p, offset: %i, length: %i, black data: %p", + (void *)region_info.partition_base, region_info.offset, + region_info.length, (void *)region_info.black_data); +#endif + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef SHW_DEBUG + LOG_KDIAG("failed to find partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + status = OS_ERROR_FAIL_S; + goto out; + } + + /* wire down black_data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + status = OS_ERROR_FAIL_S; + goto out; + } + + retval = + do_scc_decrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + if (retval == FSL_RETURN_OK_S) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + /* release black data */ + unwire_user_memory(&page_ctx); + } + out: + +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +fsl_shw_return_t do_system_keystore_slot_alloc(fsl_shw_uco_t * user_ctx, + uint32_t key_length, + uint64_t ownerid, + uint32_t * slot) +{ + (void)user_ctx; + return keystore_slot_alloc(&system_keystore, key_length, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_dealloc(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot) +{ + (void)user_ctx; + return keystore_slot_dealloc(&system_keystore, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_load(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t * key, + uint32_t key_length) +{ + (void)user_ctx; + return keystore_slot_load(&system_keystore, ownerid, slot, + (void *)key, key_length); +} + +fsl_shw_return_t do_system_keystore_slot_encrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_encrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +fsl_shw_return_t do_system_keystore_slot_decrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_decrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +fsl_shw_return_t do_system_keystore_slot_read(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * key_data) +{ + (void)user_ctx; + + return keystore_slot_read(&system_keystore, ownerid, + slot, key_length, key_data); +} + +/*! + * Handle user-mode Get Capabilities request + * + * Right now, this function can only have a failure if the user has failed to + * provide a pointer to a location in user space with enough room to hold the + * fsl_shw_pco_t structure and any associated data. It will treat this failure + * as an ioctl failure and return an ioctl error code, instead of treating it + * as an API failure. + * + * @param user_ctx The kernel version of user's context + * @param user_mode_pco_request Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_capabilities(fsl_shw_uco_t * user_ctx, + void *user_mode_pco_request) +{ + os_error_code code; + struct capabilities_req req; + fsl_shw_pco_t local_cap; + + memcpy(&local_cap, &cap, sizeof(cap)); + /* Initialize pointers to out-of-struct arrays */ + local_cap.sym_algorithms = NULL; + local_cap.sym_modes = NULL; + local_cap.sym_modes = NULL; + + code = os_copy_from_user(&req, user_mode_pco_request, sizeof(req)); + if (code == OS_ERROR_OK_S) { + void *endcap; + void *user_bounds; +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHE: Received get_cap request: 0x%p/%u/0x%x", + req.capabilities, req.size, + sizeof(fsl_shw_pco_t)); +#endif + endcap = req.capabilities + 1; /* point to end of structure */ + user_bounds = (void *)req.capabilities + req.size; /* end of area */ + + /* First verify that request is big enough for the main structure */ + if (endcap >= user_bounds) { + endcap = NULL; /* No! */ + } + + /* Copy any Symmetric Algorithm suppport */ + if (cap.sym_algorithm_count != 0) { + local_cap.sym_algorithms = endcap; + endcap = + copy_array(endcap, user_bounds, cap.sym_algorithms, + sizeof(fsl_shw_key_alg_t), + cap.sym_algorithm_count); + } + + /* Copy any Symmetric Modes suppport */ + if (cap.sym_mode_count != 0) { + local_cap.sym_modes = endcap; + endcap = copy_array(endcap, user_bounds, cap.sym_modes, + sizeof(fsl_shw_sym_mode_t), + cap.sym_mode_count); + } + + /* Copy any Hash Algorithm suppport */ + if (cap.hash_algorithm_count != 0) { + local_cap.hash_algorithms = endcap; + endcap = + copy_array(endcap, user_bounds, cap.hash_algorithms, + sizeof(fsl_shw_hash_alg_t), + cap.hash_algorithm_count); + } + + /* Now copy up the (possibly modified) main structure */ + if (endcap != NULL) { + code = + os_copy_to_user(req.capabilities, &local_cap, + sizeof(cap)); + } + + if (endcap == NULL) { + code = OS_ERROR_BAD_ADDRESS_S; + } + + /* And return the FSL SHW code in the request structure. */ + if (code == OS_ERROR_OK_S) { + code = + copy_fsl_code(user_mode_pco_request, + FSL_RETURN_OK_S); + } + } + + /* code may already be set to an error. This is another error case. */ + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: get capabilities returning %d", code); +#endif + + return code; +} + +/*! + * Handle user-mode Get Results request + * + * Get arguments from user space into kernel space, then call + * fsl_shw_get_results, and then copy its return code and any results from + * kernel space back to user space. + * + * @param user_ctx The kernel version of user's context + * @param user_mode_results_req Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_results(fsl_shw_uco_t * user_ctx, + void *user_mode_results_req) +{ + os_error_code code; + struct results_req req; + fsl_shw_result_t *results = NULL; + int loop; + + code = os_copy_from_user(&req, user_mode_results_req, sizeof(req)); + loop = 0; + + if (code == OS_ERROR_OK_S) { + results = os_alloc_memory(req.requested * sizeof(*results), 0); + if (results == NULL) { + code = OS_ERROR_NO_MEMORY_S; + } + } + + if (code == OS_ERROR_OK_S) { + fsl_shw_return_t err = + fsl_shw_get_results(user_ctx, req.requested, + results, &req.actual); + + /* Send API return code up to user. */ + code = copy_fsl_code(user_mode_results_req, err); + + if ((code == OS_ERROR_OK_S) && (err == FSL_RETURN_OK_S)) { + /* Now copy up the result count */ + code = os_copy_to_user(user_mode_results_req + + offsetof(struct results_req, + actual), &req.actual, + sizeof(req.actual)); + if ((code == OS_ERROR_OK_S) && (req.actual != 0)) { + /* now copy up the results... */ + code = os_copy_to_user(req.results, results, + req.actual * + sizeof(*results)); + } + } + } + + if (results != NULL) { + os_free_memory(results); + } + + return code; +} + +/*! + * Process header of user-mode request. + * + * Mark header as User Mode request. Update UCO's flags and reference fields + * with current versions from the header. + * + * @param user_ctx Pointer to kernel version of UCO. + * @param hdr Pointer to common part of user request. + * + * @return void + */ +inline static void process_hdr(fsl_shw_uco_t * user_ctx, + struct shw_req_header *hdr) +{ + hdr->flags |= FSL_UCO_USERMODE_USER; + user_ctx->flags = hdr->flags; + user_ctx->user_ref = hdr->user_ref; + + return; +} + +/*! + * Handle user-mode Get Random request + * + * @param user_ctx The kernel version of user's context + * @param user_mode_get_random_req Pointer to user-space request + * + * @return an os_error_code + */ +static os_error_code get_random(fsl_shw_uco_t * user_ctx, + void *user_mode_get_random_req) +{ + os_error_code code; + struct get_random_req req; + + code = os_copy_from_user(&req, user_mode_get_random_req, sizeof(req)); + if (code == OS_ERROR_OK_S) { + process_hdr(user_ctx, &req.hdr); +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("SHW: get_random() for %d bytes in %sblocking mode", + req.size, + (req.hdr.flags & FSL_UCO_BLOCKING_MODE) ? "" : "non-"); +#endif + req.hdr.code = + fsl_shw_get_random(user_ctx, req.size, req.random); + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("SHW: get_random() returning %d", req.hdr.code); +#endif + + /* Copy FSL function status back to user */ + code = copy_fsl_code(user_mode_get_random_req, req.hdr.code); + } + + return code; +} + +/*! + * Handle user-mode Add Entropy request + * + * @param user_ctx Pointer to the kernel version of user's context + * @param user_mode_add_entropy_req Address of user-space request + * + * @return an os_error_code + */ +static os_error_code add_entropy(fsl_shw_uco_t * user_ctx, + void *user_mode_add_entropy_req) +{ + os_error_code code; + struct add_entropy_req req; + uint8_t *local_buffer = NULL; + + code = os_copy_from_user(&req, user_mode_add_entropy_req, sizeof(req)); + if (code == OS_ERROR_OK_S) { + local_buffer = os_alloc_memory(req.size, 0); /* for random */ + if (local_buffer != NULL) { + code = + os_copy_from_user(local_buffer, req.entropy, + req.size); + } + if (code == OS_ERROR_OK_S) { + req.hdr.code = fsl_shw_add_entropy(user_ctx, req.size, + local_buffer); + + code = + copy_fsl_code(user_mode_add_entropy_req, + req.hdr.code); + } + } + + if (local_buffer != NULL) { + os_free_memory(local_buffer); + } + + return code; +} + +/******************************************************************/ +/* End User Mode Support */ +/******************************************************************/ + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_register_user); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +/* + * Handle user registration. + * + * @param user_ctx The user context for the registration. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t code = FSL_RETURN_INTERNAL_ERROR_S; + + if ((user_ctx->flags & FSL_UCO_BLOCKING_MODE) && + (user_ctx->flags & FSL_UCO_CALLBACK_MODE)) { + code = FSL_RETURN_BAD_FLAG_S; + goto error_exit; + } else if (user_ctx->pool_size == 0) { + code = FSL_RETURN_NO_RESOURCE_S; + goto error_exit; + } else { + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + SHW_ADD_USER(user_ctx); + code = FSL_RETURN_OK_S; + } + + error_exit: + return code; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_deregister_user); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx) +{ + shw_queue_entry_t *finished_request; + fsl_shw_return_t ret = FSL_RETURN_OK_S; + + /* Clean up what we find in result pool. */ + do { + os_lock_context_t lock_context; + os_lock_save_context(shw_queue_lock, lock_context); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + SHW_QUEUE_REMOVE_ENTRY(&user_ctx->result_pool, + finished_request); + os_unlock_restore_context(shw_queue_lock, lock_context); + os_free_memory(finished_request); + } else { + os_unlock_restore_context(shw_queue_lock, lock_context); + } + } while (finished_request != NULL); + +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_spo_t *partition; + struct mm_struct *mm = current->mm; + + while ((user_ctx->partition != NULL) + && (ret == FSL_RETURN_OK_S)) { + + partition = user_ctx->partition; + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS + ("Found an abandoned secure partition at %p, releasing", + partition); +#endif + + /* It appears that current->mm is not valid if this is called from a + * close routine (perhaps only if the program raised an exception that + * caused it to close?) If that is the case, then still free the + * partition, but do not remove it from the memory space (dangerous?) + */ + + if (mm == NULL) { +#ifdef SHW_DEBUG + LOG_KDIAG + ("Warning: no mm structure found, not unmapping " + "partition from user memory\n"); +#endif + } else { + /* Unmap the memory region (see sys_munmap in mmap.c) */ + /* Note that this assumes a single memory partition */ + unmap_user_memory(partition->user_base, 8192); + } + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + /* release the partition */ + scc_release_partition(partition->kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition->user_base); + + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + + goto out; + } + } + } + out: +#endif /* FSL_HAVE_SCC2 */ + + SHW_REMOVE_USER(user_ctx); + + return ret; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_get_results); +#endif +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count) +{ + shw_queue_entry_t *finished_request; + unsigned loop = 0; + + do { + os_lock_context_t lock_context; + + /* Protect state of user's result pool until we have retrieved and + * remove the first entry, or determined that the pool is empty. */ + os_lock_save_context(shw_queue_lock, lock_context); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + uint32_t code = 0; + + SHW_QUEUE_REMOVE_ENTRY(&user_ctx->result_pool, + finished_request); + os_unlock_restore_context(shw_queue_lock, lock_context); + + results[loop].user_ref = finished_request->user_ref; + results[loop].code = finished_request->code; + results[loop].detail1 = 0; + results[loop].detail2 = 0; + results[loop].user_req = + finished_request->user_mode_req; + if (finished_request->postprocess != NULL) { + code = + finished_request-> + postprocess(finished_request); + } + + results[loop].code = finished_request->code; + os_free_memory(finished_request); + if (code == 0) { + loop++; + } + } else { /* finished_request is NULL */ + /* pool is empty */ + os_unlock_restore_context(shw_queue_lock, lock_context); + } + + } while ((loop < result_size) && (finished_request != NULL)); + + *result_count = loop; + + return FSL_RETURN_OK_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_get_capabilities); +#endif +fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx) +{ + + /* Unused */ + (void)user_ctx; + + return ∩ +} + +#if !(defined(FSL_HAVE_SAHARA) || defined(FSL_HAVE_RNGA) \ + || defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC)) + +#if defined(LINUX_VERSION_CODE) +EXPORT_SYMBOL(fsl_shw_get_random); +#endif +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + /* Unused */ + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} + +#if defined(LINUX_VERSION_CODE) +EXPORT_SYMBOL(fsl_shw_add_entropy); +#endif +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + + /* Unused */ + (void)user_ctx; + (void)length; + (void)data; + + return FSL_RETURN_ERROR_S; +} +#endif + +#if !defined(FSL_HAVE_DRYICE) && !defined(FSL_HAVE_SAHARA2) +#if 0 +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +#endif +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)ct; + (void)pt; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +#endif +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)sym_ctx; + (void)length; + (void)pt; + (void)ct; + + return FSL_RETURN_ERROR_S; +} + +/* DryIce support provided in separate file */ + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_establish_key); +#endif +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)establish_type; + (void)key; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_extract_key); +#endif +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)covered_key; + + return FSL_RETURN_ERROR_S; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_release_key); +#endif +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + + /* Unused */ + (void)user_ctx; + (void)key_info; + + return FSL_RETURN_ERROR_S; +} +#endif +#endif /* SAHARA or DRYICE */ + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hash); +#endif +#if !defined(FSL_HAVE_SAHARA) +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)hash_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return ret; +} +#endif + +#ifndef FSL_HAVE_SAHARA +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hmac_precompute); +#endif + +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + + return status; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_hmac); +#endif + +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)key_info; + (void)hmac_ctx; + (void)msg; + (void)length; + (void)result; + (void)result_len; + + return status; +} +#endif + +/*! + * Call the proper function to encrypt a region of encrypted secure memory + * + * @brief + * + * @param user_ctx User context of the partition owner (NULL in kernel) + * @param partition_base Base address (physical) of the partition + * @param offset_bytes Offset from base address of the data to be encrypted + * @param byte_count Length of the message (bytes) + * @param black_data Pointer to where the encrypted data is stored + * @param IV IV to use for encryption + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return status + */ +fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; +#ifdef FSL_HAVE_SCC2 + + scc_return_t scc_ret; + +#ifdef SHW_DEBUG + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); + + LOG_KDIAG_ARGS("Owner ID: %08x%08x\n", owner_32[1], owner_32[0]); +#endif /* SHW_DEBUG */ + (void)user_ctx; + + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_encrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + + /* The SCC2 DMA engine should have written to the black ram, so we need to + * invalidate that region of memory. Note that the red ram is not an + * because it is mapped with the cache disabled. + */ + os_cache_inv_range(black_data, byte_count); + +#endif /* FSL_HAVE_SCC2 */ + return retval; +} + +/*! + * Call the proper function to decrypt a region of encrypted secure memory + * + * @brief + * + * @param user_ctx User context of the partition owner (NULL in kernel) + * @param partition_base Base address (physical) of the partition + * @param offset_bytes Offset from base address that the decrypted data + * shall be placed + * @param byte_count Length of the message (bytes) + * @param black_data Pointer to where the encrypted data is stored + * @param IV IV to use for decryption + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return status + */ +fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef FSL_HAVE_SCC2 + + scc_return_t scc_ret; + +#ifdef SHW_DEBUG + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); + + LOG_KDIAG_ARGS("Owner ID: %08x%08x\n", owner_32[1], owner_32[0]); +#endif /* SHW_DEBUG */ + + (void)user_ctx; + + /* The SCC2 DMA engine will be reading from the black ram, so we need to + * make sure that the data is pushed out of the cache. Note that the red + * ram is not an issue because it is mapped with the cache disabled. + */ + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_decrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, + (uint8_t *) __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + +#endif /* FSL_HAVE_SCC2 */ + + return retval; +} + +void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, const uint8_t * UMID, uint32_t permissions) +{ +#ifdef FSL_HAVE_SCC2 + int part_no; + void *part_base; + uint32_t part_phys; + scc_config_t *scc_configuration; + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (size != scc_configuration->partition_size_bytes) { + return NULL; + } + + /* attempt to grab a partition. */ + if (scc_allocate_partition(0, &part_no, &part_base, &part_phys) + != SCC_RET_OK) { + return NULL; + } +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("Partition_base: %p, partition_base_phys: %p\n", + part_base, (void *)part_phys); +#endif + + if (scc_engage_partition(part_base, UMID, permissions) + != SCC_RET_OK) { + /* Engagement failed, so the partition needs to be de-allocated */ + +#ifdef SHW_DEBUG + LOG_KDIAG_ARGS("Failed to engage partition %p, de-allocating", + part_base); +#endif + scc_release_partition(part_base); + + return NULL; + } + + return part_base; + +#else /* FSL_HAVE_SCC2 */ + + (void)user_ctx; + (void)size; + (void)UMID; + (void)permissions; + return NULL; + +#endif /* FSL_HAVE_SCC2 */ +} + +/* Release a block of secure memory */ +fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + if (scc_release_partition(address) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + + return FSL_RETURN_ERROR_S; +} + +/* Check the status of a block of secure memory */ +fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t * user_ctx, + void *address, + fsl_shw_partition_status_t * part_status) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + *part_status = scc_partition_status(address); + + return FSL_RETURN_OK_S; +#endif + + return FSL_RETURN_ERROR_S; +} + +/* Diminish permissions on some secure memory */ +fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, uint32_t permissions) +{ + + (void)user_ctx; /* unused parameter warning */ + +#ifdef FSL_HAVE_SCC2 + if (scc_diminish_permissions(address, permissions) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + return FSL_RETURN_ERROR_S; +} + +#ifndef FSL_HAVE_SAHARA +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_gen_encrypt); +#endif + +fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)auth_ctx; + (void)cipher_key_info; + (void)auth_key_info; /* save compilation warning */ + (void)auth_data_length; + (void)auth_data; + (void)payload_length; + (void)payload; + (void)ct; + (void)auth_value; + + return status; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_auth_decrypt); +#endif +/*! + * @brief Authenticate and decrypt a (CCM) stream. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param ct The encrypted data + * @param auth_value The authentication code to validate + * @param[out] payload The location to store decrypted data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + /* Unused */ + (void)user_ctx; + (void)auth_ctx; + (void)cipher_key_info; + (void)auth_key_info; /* save compilation warning */ + (void)auth_data_length; + (void)auth_data; + (void)payload_length; + (void)ct; + (void)auth_value; + (void)payload; + + return status; +} + +#endif /* no SAHARA */ + +#ifndef FSL_HAVE_DRYICE + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_gen_random_pf_key); +#endif +/*! + * Cause the hardware to create a new random key for secure memory use. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the hardware random key register. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx) +{ + volatile fsl_shw_return_t status = FSL_RETURN_ERROR_S; + + return status; +} + +#endif /* not have DRYICE */ + +fsl_shw_return_t alloc_slot(fsl_shw_uco_t * user_ctx, fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + key_info->key_length, + key_info->userid, + &(key_info->handle)); +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("key length: %i, handle: %i", + key_info->key_length, key_info->handle); +#endif + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + key_info->key_length, + key_info->userid, + &(key_info->handle)); + } + + return ret; +} /* end fn alloc_slot */ + +fsl_shw_return_t load_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, const uint8_t * key) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_load(user_ctx, + key_info->userid, + key_info->handle, key, + key_info->key_length); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_load(key_info->keystore, + key_info->userid, + key_info->handle, key, + key_info->key_length); + } + + return ret; +} /* end fn load_slot */ + +fsl_shw_return_t dealloc_slot(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + key_info->handle); + } else { + /* Key goes in user keystore */ + keystore_slot_dealloc(key_info->keystore, + key_info->userid, key_info->handle); + } + + key_info->flags &= ~(FSL_SKO_KEY_ESTABLISHED | FSL_SKO_KEY_PRESENT); + + return ret; +} /* end fn slot_dealloc */ diff --git a/drivers/mxc/security/rng/shw_dryice.c b/drivers/mxc/security/rng/shw_dryice.c new file mode 100644 index 000000000000..1fbd4bfef986 --- /dev/null +++ b/drivers/mxc/security/rng/shw_dryice.c @@ -0,0 +1,204 @@ +/* + * Copyright 2009 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 + */ + +#include "shw_driver.h" +#include "../dryice.h" + +#include <diagnostic.h> + +#ifdef FSL_HAVE_DRYICE + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_gen_random_pf_key); +#endif +/*! + * Cause the hardware to create a new random key for secure memory use. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the hardware random key register. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + di_return_t di_ret; + + /* For now, only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + di_ret = dryice_set_random_key(0); + if (di_ret != DI_SUCCESS) { + printk("dryice_set_random_key returned %d\n", di_ret); + goto out; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} + +#ifdef LINUX_VERSION_CODE +EXPORT_SYMBOL(fsl_shw_read_tamper_event); +#endif +fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * user_ctx, + fsl_shw_tamper_t * tamperp, + uint64_t * timestampp) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + di_return_t di_ret; + uint32_t di_events = 0; + uint32_t di_time_stamp; + + /* Only blocking mode calls are supported */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + di_ret = dryice_get_tamper_event(&di_events, &di_time_stamp, 0); + if ((di_ret != DI_SUCCESS) && (di_ret != DI_ERR_STATE)) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("dryice_get_tamper_event returned %s\n", + di_error_string(di_ret)); +#endif + goto out; + } + + /* Pass time back to caller */ + *timestampp = (uint64_t) di_time_stamp; + + if (di_events & DI_TAMPER_EVENT_WTD) { + *tamperp = FSL_SHW_TAMPER_WTD; + } else if (di_events & DI_TAMPER_EVENT_ETBD) { + *tamperp = FSL_SHW_TAMPER_ETBD; + } else if (di_events & DI_TAMPER_EVENT_ETAD) { + *tamperp = FSL_SHW_TAMPER_ETAD; + } else if (di_events & DI_TAMPER_EVENT_EBD) { + *tamperp = FSL_SHW_TAMPER_EBD; + } else if (di_events & DI_TAMPER_EVENT_SAD) { + *tamperp = FSL_SHW_TAMPER_SAD; + } else if (di_events & DI_TAMPER_EVENT_TTD) { + *tamperp = FSL_SHW_TAMPER_TTD; + } else if (di_events & DI_TAMPER_EVENT_CTD) { + *tamperp = FSL_SHW_TAMPER_CTD; + } else if (di_events & DI_TAMPER_EVENT_VTD) { + *tamperp = FSL_SHW_TAMPER_VTD; + } else if (di_events & DI_TAMPER_EVENT_MCO) { + *tamperp = FSL_SHW_TAMPER_MCO; + } else if (di_events & DI_TAMPER_EVENT_TCO) { + *tamperp = FSL_SHW_TAMPER_TCO; + } else if (di_events != 0) { + /* Apparentliy a tamper type not known to this driver was detected */ + goto out; + } else { + *tamperp = FSL_SHW_TAMPER_NONE; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} /* end fn fsl_shw_read_tamper_event */ +#endif +/*! + * Convert an SHW HW key reference into a DI driver key reference + * + * @param shw_pf_key An SHW HW key value + * @param di_keyp Location to store the equivalent DI driver key + * + * @return FSL_RETURN_OK_S, or error if key is unknown or cannot translate. + */ +fsl_shw_return_t shw_convert_pf_key(fsl_shw_pf_key_t shw_pf_key, + di_key_t * di_keyp) +{ + fsl_shw_return_t ret = FSL_RETURN_BAD_FLAG_S; + + switch (shw_pf_key) { + case FSL_SHW_PF_KEY_IIM: + *di_keyp = DI_KEY_FK; + break; + case FSL_SHW_PF_KEY_RND: + *di_keyp = DI_KEY_RK; + break; + case FSL_SHW_PF_KEY_IIM_RND: + *di_keyp = DI_KEY_FRK; + break; + case FSL_SHW_PF_KEY_PRG: + *di_keyp = DI_KEY_PK; + break; + case FSL_SHW_PF_KEY_IIM_PRG: + *di_keyp = DI_KEY_FPK; + break; + default: + goto out; + } + + ret = FSL_RETURN_OK_S; + + out: + return ret; +} + +#ifdef DIAG_SECURITY_FUNC +const char *di_error_string(int code) +{ + char *str = "unknown"; + + switch (code) { + case DI_SUCCESS: + str = "operation was successful"; + break; + case DI_ERR_BUSY: + str = "device or resource busy"; + break; + case DI_ERR_STATE: + str = "dryice is in incompatible state"; + break; + case DI_ERR_INUSE: + str = "resource is already in use"; + break; + case DI_ERR_UNSET: + str = "resource has not been initialized"; + break; + case DI_ERR_WRITE: + str = "error occurred during register write"; + break; + case DI_ERR_INVAL: + str = "invalid argument"; + break; + case DI_ERR_FAIL: + str = "operation failed"; + break; + case DI_ERR_HLOCK: + str = "resource is hard locked"; + break; + case DI_ERR_SLOCK: + str = "resource is soft locked"; + break; + case DI_ERR_NOMEM: + str = "out of memory"; + break; + default: + break; + } + + return str; +} +#endif /* HAVE DRYICE */ diff --git a/drivers/mxc/security/rng/shw_hash.c b/drivers/mxc/security/rng/shw_hash.c new file mode 100644 index 000000000000..d87e1b75a7bc --- /dev/null +++ b/drivers/mxc/security/rng/shw_hash.c @@ -0,0 +1,328 @@ +/* + * Copyright 2009 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 shw_hash.c + * + * This file contains implementations for use of the (internal) SHW hash + * software computation. It defines the usual three steps: + * + * - #shw_hash_init() + * - #shw_hash_update() + * - #shw_hash_final() + * + * In support of the above functions, it also contains these functions: + * - #sha256_init() + * - #sha256_process_block() + * + * + * These functions depend upon the Linux Endian functions __be32_to_cpu(), + * __cpu_to_be32() to convert a 4-byte big-endian array to an integer and + * vice-versa. For those without Linux, it should be pretty obvious what they + * do. + * + * The #shw_hash_update() and #shw_hash_final() functions are generic enough to + * support SHA-1/SHA-224/SHA-256, as needed. Some extra tweaking would be + * necessary to get them to support SHA-384/SHA-512. + * + */ + +#include "shw_driver.h" +#include "shw_hash.h" + +#ifndef __KERNEL__ +#include <asm/types.h> +#include <linux/byteorder/little_endian.h> /* or whichever is proper for target arch */ +#define printk printf +#endif + +/*! + * Rotate a value right by a number of bits. + * + * @param x Word of data which needs rotating + * @param y Number of bits to rotate + * + * @return The new value + */ +inline uint32_t rotr32fixed(uint32_t x, unsigned int y) +{ + return (uint32_t) ((x >> y) | (x << (32 - y))); +} + +#define blk0(i) (W[i] = data[i]) +// Referencing parameters so many times is really poor practice. Do not imitate these macros +#define blk2(i) (W[i & 15] += s1(W[(i - 2) & 15]) + W[(i - 7) & 15] + s0(W[(i - 15) & 15])) + +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) ((x & y) | (z & (x | y))) + +#define a(i) T[(0 - i) & 7] +#define b(i) T[(1 - i) & 7] +#define c(i) T[(2 - i) & 7] +#define d(i) T[(3 - i) & 7] +#define e(i) T[(4 - i) & 7] +#define f(i) T[(5 - i) & 7] +#define g(i) T[(6 - i) & 7] +#define h(i) T[(7 - i) & 7] + +// This is a bad way to write a multi-statement macro... and referencing 'i' so many +// times is really poor practice. Do not imitate. +#define R(i) h(i) += S1( e(i)) + Ch(e(i), f(i), g(i)) + K[i + j] +(j ? blk2(i) : blk0(i));\ + d(i) += h(i);h(i) += S0(a(i)) + Maj(a(i), b(i), c(i)) + +// for SHA256 +#define S0(x) (rotr32fixed(x, 2) ^ rotr32fixed(x, 13) ^ rotr32fixed(x, 22)) +#define S1(x) (rotr32fixed(x, 6) ^ rotr32fixed(x, 11) ^ rotr32fixed(x, 25)) +#define s0(x) (rotr32fixed(x, 7) ^ rotr32fixed(x, 18) ^ (x >> 3)) +#define s1(x) (rotr32fixed(x, 17) ^ rotr32fixed(x, 19) ^ (x >> 10)) + +/*! + * Initialize the Hash State + * + * Constructs the SHA256 hash engine. + * Specification: + * State Size = 32 bytes + * Block Size = 64 bytes + * Digest Size = 32 bytes + * + * @param state Address of hash state structure + * + */ +void sha256_init(shw_hash_state_t * state) +{ + state->bit_count = 0; + state->partial_count_bytes = 0; + + state->state[0] = 0x6a09e667; + state->state[1] = 0xbb67ae85; + state->state[2] = 0x3c6ef372; + state->state[3] = 0xa54ff53a; + state->state[4] = 0x510e527f; + state->state[5] = 0x9b05688c; + state->state[6] = 0x1f83d9ab; + state->state[7] = 0x5be0cd19; +} + +const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/*! + * Hash a block of data into the SHA-256 hash state. + * + * This function hash the block of data in the @c partial_block + * element of the state structure into the state variables of the + * state structure. + * + * @param state Address of hash state structure + * + */ +static void sha256_process_block(shw_hash_state_t * state) +{ + uint32_t W[16]; + uint32_t T[8]; + uint32_t stack_buffer[SHW_HASH_BLOCK_WORD_SIZE]; + uint32_t *data = &stack_buffer[0]; + uint8_t *input = state->partial_block; + unsigned int i; + unsigned int j; + + /* Copy byte-oriented input block into word-oriented registers */ + for (i = 0; i < SHW_HASH_BLOCK_LEN / sizeof(uint32_t); + i++, input += sizeof(uint32_t)) { + stack_buffer[i] = __be32_to_cpu(*(uint32_t *) input); + } + + /* Copy context->state[] to working vars */ + memcpy(T, state->state, sizeof(T)); + + /* 64 operations, partially loop unrolled */ + for (j = 0; j < SHW_HASH_BLOCK_LEN; j += 16) { + R(0); + R(1); + R(2); + R(3); + R(4); + R(5); + R(6); + R(7); + R(8); + R(9); + R(10); + R(11); + R(12); + R(13); + R(14); + R(15); + } + /* Add the working vars back into context.state[] */ + state->state[0] += a(0); + state->state[1] += b(0); + state->state[2] += c(0); + state->state[3] += d(0); + state->state[4] += e(0); + state->state[5] += f(0); + state->state[6] += g(0); + state->state[7] += h(0); + + /* Wipe variables */ + memset(W, 0, sizeof(W)); + memset(T, 0, sizeof(T)); +} + +/*! + * Initialize the hash state structure + * + * @param state Address of hash state structure. + * @param alg Which hash algorithm to use (must be FSL_HASH_ALG_SHA1) + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hash_init(shw_hash_state_t * state, fsl_shw_hash_alg_t alg) +{ + if (alg != FSL_HASH_ALG_SHA256) { + return FSL_RETURN_BAD_ALGORITHM_S; + } + + sha256_init(state); + + return FSL_RETURN_OK_S; +} + +/*! + * Add input bytes to the hash + * + * The bytes are added to the partial_block element of the hash state, and as + * the partial block is filled, it is processed by sha1_process_block(). This + * function also updates the bit_count element of the hash state. + * + * @param state Address of hash state structure + * @param input Address of bytes to add to the hash + * @param input_len Numbef of bytes at @c input + * + */ +fsl_shw_return_t shw_hash_update(shw_hash_state_t * state, + const uint8_t * input, unsigned int input_len) +{ + unsigned int bytes_needed; /* Needed to fill a block */ + unsigned int bytes_to_copy; /* to copy into the block */ + + /* Account for new data */ + state->bit_count += 8 * input_len; + + /* + * Process input bytes into the ongoing block; process the block when it + * gets full. + */ + while (input_len > 0) { + bytes_needed = SHW_HASH_BLOCK_LEN - state->partial_count_bytes; + bytes_to_copy = ((input_len < bytes_needed) ? + input_len : bytes_needed); + + /* Add in the bytes and do the accounting */ + memcpy(state->partial_block + state->partial_count_bytes, + input, bytes_to_copy); + input += bytes_to_copy; + input_len -= bytes_to_copy; + state->partial_count_bytes += bytes_to_copy; + + /* Run a full block through the transform */ + if (state->partial_count_bytes == SHW_HASH_BLOCK_LEN) { + sha256_process_block(state); + state->partial_count_bytes = 0; + } + } + + return FSL_RETURN_OK_S; +} /* end fn shw_hash_update */ + +/*! + * Finalize the hash + * + * Performs the finalize operation on the previous input data & returns the + * resulting digest. The finalize operation performs the appropriate padding + * up to the block size. + * + * @param state Address of hash state structure + * @param result Location to store the hash result + * @param result_len Number of bytes of @c result to be stored. + * + * @return FSL_RETURN_OK_S if all went well, FSL_RETURN_BAD_DATA_LENGTH_S if + * hash_len is too long, otherwise an error code. + */ +fsl_shw_return_t shw_hash_final(shw_hash_state_t * state, uint8_t * result, + unsigned int result_len) +{ + static const uint8_t pad[SHW_HASH_BLOCK_LEN * 2] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + uint8_t data[sizeof(state->bit_count)]; + uint32_t pad_length; + uint64_t bit_count = state->bit_count; + uint8_t hash[SHW_HASH_LEN]; + int i; + + if (result_len > SHW_HASH_LEN) { + return FSL_RETURN_BAD_DATA_LENGTH_S; + } + + /* Save the length before padding. */ + for (i = sizeof(state->bit_count) - 1; i >= 0; i--) { + data[i] = bit_count & 0xFF; + bit_count >>= 8; + } + pad_length = ((state->partial_count_bytes < 56) ? + (56 - state->partial_count_bytes) : + (120 - state->partial_count_bytes)); + + /* Pad to 56 bytes mod 64 (BLOCK_SIZE). */ + shw_hash_update(state, pad, pad_length); + + /* + * Append the length. This should trigger transform of the final block. + */ + shw_hash_update(state, data, sizeof(state->bit_count)); + + /* Copy the result into a byte array */ + for (i = 0; i < SHW_HASH_STATE_WORDS; i++) { + *(uint32_t *) (hash + 4 * i) = __cpu_to_be32(state->state[i]); + } + + /* And copy the result out to caller */ + memcpy(result, hash, result_len); + + return FSL_RETURN_OK_S; +} /* end fn shw_hash_final */ diff --git a/drivers/mxc/security/rng/shw_hmac.c b/drivers/mxc/security/rng/shw_hmac.c new file mode 100644 index 000000000000..215f3d25055c --- /dev/null +++ b/drivers/mxc/security/rng/shw_hmac.c @@ -0,0 +1,145 @@ +/* + * Copyright 2009 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 shw_hmac.c + * + * This file contains implementations for use of the (internal) SHW HMAC + * software computation. It defines the usual three steps: + * + * - #shw_hmac_init() + * - #shw_hmac_update() + * - #shw_hmac_final() + * + * + */ + +#include "shw_driver.h" +#include "shw_hmac.h" + +#ifndef __KERNEL__ +#include <asm/types.h> +#include <linux/byteorder/little_endian.h> /* or whichever is proper for target arch */ +#define printk printf +#endif + +/*! XOR value for HMAC inner key */ +#define INNER_HASH_CONSTANT 0x36 + +/*! XOR value for HMAC outer key */ +#define OUTER_HASH_CONSTANT 0x5C + +/*! + * Initialize the HMAC state structure with the HMAC key + * + * @param state Address of HMAC state structure + * @param key Address of the key to be used for the HMAC. + * @param key_len Number of bytes of @c key. + * + * Convert the key into its equivalent inner and outer hash state objects. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_init(shw_hmac_state_t * state, + const uint8_t * key, unsigned int key_len) +{ + fsl_shw_return_t code = FSL_RETURN_ERROR_S; + uint8_t first_block[SHW_HASH_BLOCK_LEN]; + unsigned int i; + + /* Don't bother handling the pre-hash. */ + if (key_len > SHW_HASH_BLOCK_LEN) { + code = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + /* Prepare inner hash */ + for (i = 0; i < SHW_HASH_BLOCK_LEN; i++) { + if (i < key_len) { + first_block[i] = key[i] ^ INNER_HASH_CONSTANT; + } else { + first_block[i] = INNER_HASH_CONSTANT; + } + } + code = shw_hash_init(&state->inner_hash, FSL_HASH_ALG_SHA256); + if (code != FSL_RETURN_OK_S) { + goto out; + } + shw_hash_update(&state->inner_hash, first_block, SHW_HASH_BLOCK_LEN); + + /* Prepare outer hash */ + for (i = 0; i < SHW_HASH_BLOCK_LEN; i++) { + if (i < key_len) { + first_block[i] = key[i] ^ OUTER_HASH_CONSTANT; + } else { + first_block[i] = OUTER_HASH_CONSTANT; + } + } + code = shw_hash_init(&state->outer_hash, FSL_HASH_ALG_SHA256); + if (code != FSL_RETURN_OK_S) { + goto out; + } + shw_hash_update(&state->outer_hash, first_block, SHW_HASH_BLOCK_LEN); + + /* Wipe evidence of key */ + memset(first_block, 0, SHW_HASH_BLOCK_LEN); + + out: + return code; +} + +/*! + * Put data into the HMAC calculation + * + * Send the msg data inner inner hash's update function. + * + * @param state Address of HMAC state structure. + * @param msg Address of the message data for the HMAC. + * @param msg_len Number of bytes of @c msg. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_update(shw_hmac_state_t * state, + const uint8_t * msg, unsigned int msg_len) +{ + shw_hash_update(&state->inner_hash, msg, msg_len); + + return FSL_RETURN_OK_S; +} + +/*! + * Calculate the final HMAC + * + * @param state Address of HMAC state structure. + * @param hmac Address of location to store the HMAC. + * @param hmac_len Number of bytes of @c mac to be stored. Probably best if + * this value is no greater than #SHW_HASH_LEN. + * + * This function finalizes the internal hash, and uses that result as + * data for the outer hash. As many bytes of that result are passed + * to the user as desired. + * + * @return FSL_RETURN_OK_S if all went well, otherwise an error code. + */ +fsl_shw_return_t shw_hmac_final(shw_hmac_state_t * state, + uint8_t * hmac, unsigned int hmac_len) +{ + uint8_t hash_result[SHW_HASH_LEN]; + + shw_hash_final(&state->inner_hash, hash_result, sizeof(hash_result)); + shw_hash_update(&state->outer_hash, hash_result, SHW_HASH_LEN); + + shw_hash_final(&state->outer_hash, hmac, hmac_len); + + return FSL_RETURN_OK_S; +} diff --git a/drivers/mxc/security/rng/shw_memory_mapper.c b/drivers/mxc/security/rng/shw_memory_mapper.c new file mode 100644 index 000000000000..71f1d301f02a --- /dev/null +++ b/drivers/mxc/security/rng/shw_memory_mapper.c @@ -0,0 +1,213 @@ +/* + * Copyright 2005-2008 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 + */ + + + +/** + * Memory management functions, from Sahara Crypto API + * + * This is a subset of the memory management functions from the Sahara Crypto + * API, and is intended to support user secure partitions. + */ + +#include "portable_os.h" +#include "fsl_shw.h" + +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/pagemap.h> + +#ifdef SHW_DEBUG +#include <diagnostic.h> +#endif + +/* Page context structure. Used by wire_user_memory and unwire_user_memory */ +typedef struct page_ctx_t { + uint32_t count; + struct page **local_pages; +} page_ctx_t; + +/** +******************************************************************************* +* Map and wire down a region of user memory. +* +* +* @param address Userspace address of the memory to wire +* @param length Length of the memory region to wire +* @param page_ctx Page context, to be passed to unwire_user_memory +* +* @return (if successful) Kernel virtual address of the wired pages +*/ +void* wire_user_memory(void* address, uint32_t length, void **page_ctx) +{ + void* kernel_black_addr = NULL; + int result = -1; + int page_index = 0; + page_ctx_t *page_context; + int nr_pages = 0; + unsigned long start_page; + fsl_shw_return_t status; + + /* Determine the number of pages being used for this link */ + nr_pages = (((unsigned long)(address) & ~PAGE_MASK) + + length + ~PAGE_MASK) >> PAGE_SHIFT; + + start_page = (unsigned long)(address) & PAGE_MASK; + + /* Allocate some memory to keep track of the wired user pages, so that + * they can be deallocated later. The block of memory will contain both + * the structure and the array of pages. + */ + page_context = kmalloc(sizeof(page_ctx_t) + + nr_pages * sizeof(struct page *), GFP_KERNEL); + + if (page_context == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; /* no memory! */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("kmalloc() failed."); +#endif + return NULL; + } + + /* Set the page pointer to point to the allocated region of memory */ + page_context->local_pages = (void*)page_context + sizeof(page_ctx_t); + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, local_pages at: %p", + (void *)page_context, + (void *)(page_context->local_pages)); +#endif + + /* Wire down the pages from user space */ + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, + start_page, nr_pages, + WRITE, 0 /* noforce */, + (page_context->local_pages), NULL); + up_read(¤t->mm->mmap_sem); + + if (result < nr_pages) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("get_user_pages() failed."); +#endif + if (result > 0) { + for (page_index = 0; page_index < result; page_index++) + page_cache_release((page_context->local_pages[page_index])); + + kfree(page_context); + } + return NULL; + } + + kernel_black_addr = page_address(page_context->local_pages[0]) + + ((unsigned long)address & ~PAGE_MASK); + + page_context->count = nr_pages; + *page_ctx = page_context; + + return kernel_black_addr; +} + + +/** +******************************************************************************* +* Release and unmap a region of user memory. +* +* @param page_ctx Page context from wire_user_memory +*/ +void unwire_user_memory(void** page_ctx) +{ + int page_index = 0; + struct page_ctx_t *page_context = *page_ctx; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, first page at:%p, count: %i", + (void *)page_context, + (void *)(page_context->local_pages), + page_context->count); +#endif + + if ((page_context != NULL) && (page_context->local_pages != NULL)) { + for (page_index = 0; page_index < page_context->count; page_index++) + page_cache_release(page_context->local_pages[page_index]); + + kfree(page_context); + *page_ctx = NULL; + } +} + + +/** +******************************************************************************* +* Map some physical memory into a users memory space +* +* @param vma Memory structure to map to +* @param physical_addr Physical address of the memory to be mapped in +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code +map_user_memory(struct vm_area_struct *vma, uint32_t physical_addr, uint32_t size) +{ + os_error_code retval; + + /* Map the acquired partition into the user's memory space */ + vma->vm_end = vma->vm_start + size; + + /* set cache policy to uncached so that each write of the UMID and + * permissions get directly to the SCC2 in order to engage it + * properly. Once the permissions have been written, it may be + * useful to provide a service for the user to request a different + * cache policy + */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Make sure that the user cannot fork() a child which will inherit + * this mapping, as it creates a security hole. Likewise, do not + * allow the user to 'expand' his mapping beyond this partition. + */ + vma->vm_flags |= VM_IO | VM_RESERVED | VM_DONTCOPY | VM_DONTEXPAND; + + retval = remap_pfn_range(vma, + vma->vm_start, + __phys_to_pfn(physical_addr), + size, + vma->vm_page_prot); + + return retval; +} + + +/** +******************************************************************************* +* Remove some memory from a user's memory space +* +* @param user_addr Userspace address of the memory to be unmapped +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code +unmap_user_memory(uint32_t user_addr, uint32_t size) +{ + os_error_code retval; + struct mm_struct *mm = current->mm; + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + down_write(&mm->mmap_sem); + retval = do_munmap(mm, (unsigned long)user_addr, size); + up_write(&mm->mmap_sem); + + return retval; +} diff --git a/drivers/mxc/security/sahara2/Kconfig b/drivers/mxc/security/sahara2/Kconfig new file mode 100644 index 000000000000..ab4e6fdc2cfc --- /dev/null +++ b/drivers/mxc/security/sahara2/Kconfig @@ -0,0 +1,35 @@ +menu "SAHARA2 Security Hardware Support" + +config MXC_SAHARA + tristate "Security Hardware Support (FSL SHW)" + ---help--- + Provides driver and kernel mode API for using cryptographic + accelerators. + +config MXC_SAHARA_USER_MODE + tristate "User Mode API for FSL SHW" + depends on MXC_SAHARA + ---help--- + Provides kernel driver for User Mode API. + +config MXC_SAHARA_POLL_MODE + bool "Force driver to POLL for hardware completion." + depends on MXC_SAHARA + default n + ---help--- + When this flag is yes, the driver will not use interrupts to + determine when the hardware has completed a task, but instead + will hold onto the CPU and continually poll the hardware until + it completes. + +config MXC_SAHARA_POLL_MODE_TIMEOUT + hex "Poll loop timeout" + depends on MXC_SAHARA_POLL_MODE + default "0xFFFFFFFF" + help + To avoid infinite polling, a timeout is provided. Should the + timeout be reached, a fault is reported, indicating there must + be something wrong with SAHARA, and SAHARA is reset. The loop + will exit after the given number of iterations. + +endmenu diff --git a/drivers/mxc/security/sahara2/Makefile b/drivers/mxc/security/sahara2/Makefile new file mode 100644 index 000000000000..b515709be29b --- /dev/null +++ b/drivers/mxc/security/sahara2/Makefile @@ -0,0 +1,47 @@ +# Makefile for the Linux Sahara2 driver +# +# This makefile works within a kernel driver tree + +# Need to augment this to support optionally building user-mode support +API_SOURCES = fsl_shw_sym.c fsl_shw_user.c fsl_shw_hash.c fsl_shw_auth.c \ + fsl_shw_hmac.c fsl_shw_rand.c sf_util.c km_adaptor.c fsl_shw_keystore.c \ + fsl_shw_wrap.c \ + + +SOURCES = sah_driver_interface.c sah_hardware_interface.c \ + sah_interrupt_handler.c sah_queue.c sah_queue_manager.c \ + sah_status_manager.c sah_memory_mapper.c + + +# Turn on for mostly full debugging +# DIAGS = -DDIAG_DRV_STATUS -DDIAG_DRV_QUEUE -DDIAG_DRV_INTERRUPT -DDIAG_DRV_IF +# DIAGS += -DDIAG_DURING_INTERRUPT + +# Turn on for lint-type checking +#EXTRA_CFLAGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes +EXTRA_CFLAGS += -DLINUX_KERNEL $(DIAGS) + + +ifeq ($(CONFIG_MXC_SAHARA_POLL_MODE),y) +EXTRA_CFLAGS += -DSAHARA_POLL_MODE +EXTRA_CFLAGS += -DSAHARA_POLL_MODE_TIMEOUT=$(CONFIG_SAHARA_POLL_MODE_TIMEOUT) +endif + +ifeq ($(CONFIG_MXC_SAHARA_USER_MODE),y) +EXTRA_CFLAGS += -DSAHARA_USER_MODE +SOURCES += +endif + +ifeq ($(CONFIG_PM),y) +EXTRA_CFLAGS += -DSAHARA_POWER_MANAGMENT +endif + +EXTRA_CFLAGS += -Idrivers/mxc/security/sahara2/include + +# handle buggy BSP -- uncomment if these are undefined during build +#EXTRA_CFLAGS += -DSAHARA_BASE_ADDR=HAC_BASE_ADDR -DINT_SAHARA=INT_HAC_RTIC + + +obj-$(CONFIG_MXC_SAHARA) += sahara.o + +sahara-objs := $(SOURCES:.c=.o) $(API_SOURCES:.c=.o) diff --git a/drivers/mxc/security/sahara2/fsl_shw_auth.c b/drivers/mxc/security/sahara2/fsl_shw_auth.c new file mode 100644 index 000000000000..d3100f01380a --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_auth.c @@ -0,0 +1,706 @@ +/* + * Copyright 2004-2009 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 fsl_shw_auth.c + * + * This file contains the routines which do the combined encrypt+authentication + * functions. For now, only AES-CCM is supported. + */ + +#include "sahara.h" +#include "adaptor.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_gen_encrypt); +EXPORT_SYMBOL(fsl_shw_auth_decrypt); +#endif + + +/*! Size of buffer to repetively sink useless CBC output */ +#define CBC_BUF_LEN 4096 + +/*! + * Compute the size, in bytes, of the encoded auth length + * + * @param l The actual associated data length + * + * @return The encoded length + */ +#define COMPUTE_NIST_AUTH_LEN_SIZE(l) \ +({ \ + unsigned val; \ + uint32_t len = l; \ + if (len == 0) { \ + val = 0; \ + } else if (len < 65280) { \ + val = 2; \ + } else { /* cannot handle >= 2^32 */ \ + val = 6; \ + } \ + val; \ +}) + +/*! + * Store the encoded Auth Length into the Auth Data + * + * @param l The actual Auth Length + * @param p Location to store encoding (must be uint8_t*) + * + * @return void + */ +#define STORE_NIST_AUTH_LEN(l, p) \ +{ \ + register uint32_t L = l; \ + if ((uint32_t)(l) < 65280) { \ + (p)[1] = L & 0xff; \ + L >>= 8; \ + (p)[0] = L & 0xff; \ + } else { /* cannot handle >= 2^32 */ \ + int i; \ + for (i = 5; i > 1; i--) { \ + (p)[i] = L & 0xff; \ + L >>= 8; \ + } \ + (p)[1] = 0xfe; /* Markers */ \ + (p)[0] = 0xff; \ + } \ +} + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) \ + || defined (USE_S2_CCM_ENCRYPT_CHAIN) +/*! Buffer to repetively sink useless CBC output */ +static uint8_t cbc_buffer[CBC_BUF_LEN]; +#endif + +/*! + * Place to store useless output (while bumping CTR0 to CTR1, for instance. + * Must be maximum Symmetric block size + */ +static uint8_t garbage_output[16]; + +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t block_zeros[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/*! + * Append a descriptor chain which will compute CBC over the + * formatted associated data blocks. + * + * @param[in,out] link1 Where to append the new link + * @param[in,out] data_len Location of current/updated auth-only data length + * @param user_ctx Info for acquiring memory + * @param auth_ctx Location of block0 value + * @param auth_data Unformatted associated data + * @param auth_data_length Length in octets of @a auth_data + * @param[in,out] temp_buf Location of in-process data. + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t process_assoc_from_nist_params(sah_Link ** link1, + uint32_t * + data_len, + fsl_shw_uco_t * + user_ctx, + fsl_shw_acco_t * + auth_ctx, + const uint8_t * + auth_data, + uint32_t + auth_data_length, + uint8_t ** + temp_buf) +{ + fsl_shw_return_t status; + uint32_t auth_size_length = + COMPUTE_NIST_AUTH_LEN_SIZE(auth_data_length); + uint32_t auth_pad_length = + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes - + (auth_data_length + + auth_size_length) % + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + + if (auth_pad_length == + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes) { + auth_pad_length = 0; + } + + /* Put in Block0 */ + status = sah_Create_Link(user_ctx->mem_util, link1, + auth_ctx->auth_info.CCM_ctx_info.context, + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes, SAH_USES_LINK_DATA); + + if (auth_data_length != 0) { + if (status == FSL_RETURN_OK_S) { + /* Add on length preamble to auth data */ + STORE_NIST_AUTH_LEN(auth_data_length, *temp_buf); + status = sah_Append_Link(user_ctx->mem_util, *link1, + *temp_buf, auth_size_length, + SAH_OWNS_LINK_DATA); + *temp_buf += auth_size_length; /* 2, 6, or 10 bytes */ + } + + if (status == FSL_RETURN_OK_S) { + /* Add in auth data */ + status = sah_Append_Link(user_ctx->mem_util, *link1, + (uint8_t *) auth_data, + auth_data_length, + SAH_USES_LINK_DATA); + } + + if ((status == FSL_RETURN_OK_S) && (auth_pad_length > 0)) { + status = sah_Append_Link(user_ctx->mem_util, *link1, + block_zeros, auth_pad_length, + SAH_USES_LINK_DATA); + } + } + /* ... if auth_data_length != 0 */ + *data_len = auth_ctx->auth_info.CCM_ctx_info.block_size_bytes + + auth_data_length + auth_size_length + auth_pad_length; + + return status; +} /* end fn process_assoc_from_nist_params */ + +/*! + * Add a Descriptor which will process with CBC the NIST preamble data + * + * @param desc_chain Current chain + * @param user_ctx User's context + * @param auth_ctx Inf + * @pararm encrypt 0 => decrypt, non-zero => encrypt + * @param auth_data Additional auth data for this call + * @param auth_data_length Length in bytes of @a auth_data + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t add_assoc_preamble(sah_Head_Desc ** desc_chain, + fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + int encrypt, + const uint8_t * auth_data, + uint32_t auth_data_length) +{ + uint8_t *temp_buf; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + fsl_shw_return_t status = FSL_RETURN_OK_S; + uint32_t cbc_data_length = 0; + /* Assume AES */ + uint32_t header = SAH_HDR_SKHA_ENC_DEC; + uint32_t temp_buf_flag; + unsigned chain_s2 = 1; + +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_DECRYPT_CHAIN) + if (!encrypt) { + chain_s2 = 0; + } +#endif +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_ENCRYPT_CHAIN) + if (encrypt) { + chain_s2 = 0; + } +#endif + /* Grab a block big enough for multiple uses so that only one allocate + * request needs to be made. + */ + temp_buf = + user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, + 3 * + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes); + + if (temp_buf == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + + if (auth_ctx->flags & FSL_ACCO_NIST_CCM) { + status = process_assoc_from_nist_params(&link1, + &cbc_data_length, + user_ctx, + auth_ctx, + auth_data, + auth_data_length, + &temp_buf); + if (status != FSL_RETURN_OK_S) { + goto out; + } + /* temp_buf has been referenced (and incremented). Only 'own' it + * once, at its first value. Since the nist routine called above + * bumps it... + */ + temp_buf_flag = SAH_USES_LINK_DATA; + } else { /* if NIST */ + status = sah_Create_Link(user_ctx->mem_util, &link1, + (uint8_t *) auth_data, + auth_data_length, SAH_USES_LINK_DATA); + if (status != FSL_RETURN_OK_S) { + goto out; + } + /* for next/first use of temp_buf */ + temp_buf_flag = SAH_OWNS_LINK_DATA; + cbc_data_length = auth_data_length; + } /* else not NIST */ + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_ENCRYPT_CHAIN) \ + || defined (USE_S2_CCM_DECRYPT_CHAIN) + + if (!chain_s2) { + header = SAH_HDR_SKHA_CBC_ICV + ^ sah_insert_skha_mode_cbc ^ sah_insert_skha_aux0 + ^ sah_insert_skha_encrypt; + } else { + /* + * Auth data links have been created. Now create link for the + * useless output of the CBC calculation. + */ + status = sah_Create_Link(user_ctx->mem_util, &link2, + temp_buf, + auth_ctx->auth_info.CCM_ctx_info. + block_size_bytes, + temp_buf_flag | SAH_OUTPUT_LINK); + if (status != FSL_RETURN_OK_S) { + goto out; + } + + temp_buf += auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + + cbc_data_length -= + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes; + if (cbc_data_length != 0) { + while ((status == FSL_RETURN_OK_S) + && (cbc_data_length != 0)) { + uint32_t linklen = + (cbc_data_length > + CBC_BUF_LEN) ? CBC_BUF_LEN : + cbc_data_length; + + status = + sah_Append_Link(user_ctx->mem_util, link2, + cbc_buffer, linklen, + SAH_USES_LINK_DATA | + SAH_OUTPUT_LINK); + if (status != FSL_RETURN_OK_S) { + goto out; + } + cbc_data_length -= linklen; + } + } + } +#else + header = SAH_HDR_SKHA_CBC_ICV + ^ sah_insert_skha_mode_cbc ^ sah_insert_skha_aux0 + ^ sah_insert_skha_encrypt; +#endif + /* Crank through auth data */ + status = sah_Append_Desc(user_ctx->mem_util, desc_chain, + header, link1, link2); + + out: + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(user_ctx->mem_util, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(user_ctx->mem_util, link2); + } + } + + (void)encrypt; + return status; +} /* add_assoc_preamble() */ + +#if SUPPORT_SSL +/*! + * Generate an SSL value + * + * @param user_ctx Info for acquiring memory + * @param auth_ctx Info for CTR0, size of MAC + * @param cipher_key_info + * @param auth_key_info + * @param auth_data_length + * @param auth_data + * @param payload_length + * @param payload + * @param ct + * @param auth_value + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t do_ssl_gen(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + SAH_SF_DCLS; + uint8_t *ptr1 = NULL; + + /* Assume one-shot init-finalize... no precomputes */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY ^ + sah_insert_mdha_algorithm[auth_ctx->auth_info.hash_ctx_info. + algorithm] ^ sah_insert_mdha_init ^ + sah_insert_mdha_ssl ^ sah_insert_mdha_pdata ^ + sah_insert_mdha_mac_full; + + /* set up hmac */ + DESC_IN_KEY(header, 0, NULL, auth_key_info); + + /* This is wrong -- need to find 16 extra bytes of data from + * somewhere */ + DESC_IN_OUT(SAH_HDR_MDHA_HASH, payload_length, payload, 1, auth_value); + + /* set up encrypt */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode[auth_ctx->cipher_ctx_info.mode] + ^ sah_insert_skha_encrypt + ^ sah_insert_skha_algorithm[cipher_key_info->algorithm]; + + /* Honor 'no key parity checking' for DES and TDES */ + if ((cipher_key_info->flags & FSL_SKO_KEY_IGNORE_PARITY) && + ((cipher_key_info->algorithm == FSL_KEY_ALG_DES) || + (cipher_key_info->algorithm == FSL_KEY_ALG_TDES))) { + header ^= sah_insert_skha_no_key_parity; + } + + if (auth_ctx->cipher_ctx_info.mode == FSL_SYM_MODE_CTR) { + header ^= + sah_insert_skha_modulus[auth_ctx->cipher_ctx_info. + modulus_exp]; + } + + if ((auth_ctx->cipher_ctx_info.mode == FSL_SYM_MODE_ECB) + || (auth_ctx->cipher_ctx_info.flags & FSL_SYM_CTX_INIT)) { + ptr1 = block_zeros; + } else { + ptr1 = auth_ctx->cipher_ctx_info.context; + } + + DESC_IN_KEY(header, auth_ctx->cipher_ctx_info.block_size_bytes, ptr1, + cipher_key_info); + + /* This is wrong -- need to find 16 extra bytes of data from + * somewhere... + */ + if (payload_length != 0) { + DESC_IN_OUT(SAH_HDR_SKHA_ENC_DEC, + payload_length, payload, payload_length, ct); + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + /* Eliminate compiler warnings until full implementation... */ + (void)auth_data; + (void)auth_data_length; + + return ret; +} /* do_ssl_gen() */ +#endif + +/*! + * @brief Generate a (CCM) auth code and encrypt the payload. + * + * This is a very complicated function. Seven (or eight) descriptors are + * required to perform a CCM calculation. + * + * First: Load CTR0 and key. + * + * Second: Run an octet of data through to bump to CTR1. (This could be + * done in software, but software will have to bump and later decrement - + * or copy and bump. + * + * Third: (in Virtio) Load a descriptor with data of zeros for CBC IV. + * + * Fourth: Run any (optional) "additional data" through the CBC-mode + * portion of the algorithm. + * + * Fifth: Run the payload through in CCM mode. + * + * Sixth: Extract the unencrypted MAC. + * + * Seventh: Load CTR0. + * + * Eighth: Encrypt the MAC. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param payload The data to encrypt + * @param[out] ct The location to store encrypted data + * @param[out] auth_value The location to store authentication code + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (auth_ctx->mode == FSL_ACC_MODE_SSL) { +#if SUPPORT_SSL + ret = do_ssl_gen(user_ctx, auth_ctx, cipher_key_info, + auth_key_info, auth_data_length, auth_data, + payload_length, payload, ct, auth_value); +#else + ret = FSL_RETURN_BAD_MODE_S; +#endif + goto out; + } + + if (auth_ctx->mode != FSL_ACC_MODE_CCM) { + ret = FSL_RETURN_BAD_MODE_S; + goto out; + } + + /* Only support INIT and FINALIZE flags right now. */ + if ((auth_ctx->flags & (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_LOAD | + FSL_ACCO_CTX_SAVE | FSL_ACCO_CTX_FINALIZE)) + != (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_FINALIZE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Load CTR0 and Key */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode_ctr + ^ sah_insert_skha_modulus_128 ^ sah_insert_skha_encrypt); + DESC_IN_KEY(header, + auth_ctx->cipher_ctx_info.block_size_bytes, + auth_ctx->cipher_ctx_info.context, cipher_key_info); + + /* Encrypt dummy data to bump to CTR1 */ + header = SAH_HDR_SKHA_ENC_DEC; + DESC_IN_OUT(header, auth_ctx->mac_length, garbage_output, + auth_ctx->mac_length, garbage_output); + +#if defined(FSL_HAVE_SAHARA2) || defined(USE_S2_CCM_ENCRYPT_CHAIN) +#ifndef NO_ZERO_IV_LOAD + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt ^ sah_insert_skha_mode_cbc); + DESC_IN_IN(header, + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes, + block_zeros, 0, NULL); +#endif +#endif + + ret = add_assoc_preamble(&desc_chain, user_ctx, + auth_ctx, 1, auth_data, auth_data_length); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Process the payload */ + header = (SAH_HDR_SKHA_SET_MODE_ENC_DEC + ^ sah_insert_skha_mode_ccm + ^ sah_insert_skha_modulus_128 ^ sah_insert_skha_encrypt); +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_ENCRYPT_CHAIN) + header ^= sah_insert_skha_aux0; +#endif + if (payload_length != 0) { + DESC_IN_OUT(header, payload_length, payload, payload_length, + ct); + } else { + DESC_IN_OUT(header, 0, NULL, 0, NULL); + } /* if payload_length */ + +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_ENCRYPT_CHAIN) + /* Pull out the CBC-MAC value. */ + DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, 0, NULL, + auth_ctx->mac_length, auth_value); +#else + /* Pull out the unencrypted CBC-MAC value. */ + DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, + 0, NULL, auth_ctx->mac_length, auth_ctx->unencrypted_mac); + + /* Now load CTR0 in, and encrypt the MAC */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt + ^ sah_insert_skha_mode_ctr ^ sah_insert_skha_modulus_128; + DESC_IN_IN(header, + auth_ctx->cipher_ctx_info.block_size_bytes, + auth_ctx->cipher_ctx_info.context, 0, NULL); + + header = SAH_HDR_SKHA_ENC_DEC; /* Desc. #4 SKHA Enc/Dec */ + DESC_IN_OUT(header, + auth_ctx->mac_length, auth_ctx->unencrypted_mac, + auth_ctx->mac_length, auth_value); +#endif + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + (void)auth_key_info; + return ret; +} /* fsl_shw_gen_encrypt() */ + +/*! + * @brief Authenticate and decrypt a (CCM) stream. + * + * @param user_ctx The user's context + * @param auth_ctx Info on this Auth operation + * @param cipher_key_info Key to encrypt payload + * @param auth_key_info (unused - same key in CCM) + * @param auth_data_length Length in bytes of @a auth_data + * @param auth_data Any auth-only data + * @param payload_length Length in bytes of @a payload + * @param ct The encrypted data + * @param auth_value The authentication code to validate + * @param[out] payload The location to store decrypted data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload) +{ + SAH_SF_DCLS; +#if defined(FSL_HAVE_SAHARA2) || defined(USE_S2_CCM_DECRYPT_CHAIN) + uint8_t *calced_auth = NULL; + unsigned blocking = user_ctx->flags & FSL_UCO_BLOCKING_MODE; +#endif + + SAH_SF_USER_CHECK(); + + /* Only support CCM */ + if (auth_ctx->mode != FSL_ACC_MODE_CCM) { + ret = FSL_RETURN_BAD_MODE_S; + goto out; + } + /* Only support INIT and FINALIZE flags right now. */ + if ((auth_ctx->flags & (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_LOAD | + FSL_ACCO_CTX_SAVE | FSL_ACCO_CTX_FINALIZE)) + != (FSL_ACCO_CTX_INIT | FSL_ACCO_CTX_FINALIZE)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Load CTR0 and Key */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode_ctr ^ sah_insert_skha_modulus_128; +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_DECRYPT_CHAIN) + header ^= sah_insert_skha_aux0; +#endif + DESC_IN_KEY(header, + auth_ctx->cipher_ctx_info.block_size_bytes, + auth_ctx->cipher_ctx_info.context, cipher_key_info); + + /* Decrypt the MAC which the user passed in */ + header = SAH_HDR_SKHA_ENC_DEC; + DESC_IN_OUT(header, + auth_ctx->mac_length, auth_value, + auth_ctx->mac_length, auth_ctx->unencrypted_mac); + +#if defined(FSL_HAVE_SAHARA2) || defined(USE_S2_CCM_DECRYPT_CHAIN) +#ifndef NO_ZERO_IV_LOAD + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_encrypt ^ sah_insert_skha_mode_cbc); + DESC_IN_IN(header, + auth_ctx->auth_info.CCM_ctx_info.block_size_bytes, + block_zeros, 0, NULL); +#endif +#endif + + ret = add_assoc_preamble(&desc_chain, user_ctx, + auth_ctx, 0, auth_data, auth_data_length); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Process the payload */ + header = (SAH_HDR_SKHA_SET_MODE_ENC_DEC + ^ sah_insert_skha_mode_ccm ^ sah_insert_skha_modulus_128); +#if defined (FSL_HAVE_SAHARA4) && !defined (USE_S2_CCM_DECRYPT_CHAIN) + header ^= sah_insert_skha_aux0; +#endif + if (payload_length != 0) { + DESC_IN_OUT(header, payload_length, ct, payload_length, + payload); + } else { + DESC_IN_OUT(header, 0, NULL, 0, NULL); + } + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) + /* Now pull CBC context (unencrypted MAC) out for comparison. */ + /* Need to allocate a place for it, to handle non-blocking mode + * when this stack frame will disappear! + */ + calced_auth = DESC_TEMP_ALLOC(auth_ctx->mac_length); + header = SAH_HDR_SKHA_READ_CONTEXT_IV; + DESC_OUT_OUT(header, 0, NULL, auth_ctx->mac_length, calced_auth); + if (!blocking) { + /* get_results will need this for comparison */ + desc_chain->out1_ptr = calced_auth; + desc_chain->out2_ptr = auth_ctx->unencrypted_mac; + desc_chain->out_len = auth_ctx->mac_length; + } +#endif + + SAH_SF_EXECUTE(); + +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) + if (blocking && (ret == FSL_RETURN_OK_S)) { + unsigned i; + /* Validate the auth code */ + for (i = 0; i < auth_ctx->mac_length; i++) { + if (calced_auth[i] != auth_ctx->unencrypted_mac[i]) { + ret = FSL_RETURN_AUTH_FAILED_S; + break; + } + } + } +#endif + + out: + SAH_SF_DESC_CLEAN(); +#if defined (FSL_HAVE_SAHARA2) || defined (USE_S2_CCM_DECRYPT_CHAIN) + DESC_TEMP_FREE(calced_auth); +#endif + + (void)auth_key_info; + return ret; +} /* fsl_shw_gen_decrypt() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_hash.c b/drivers/mxc/security/sahara2/fsl_shw_hash.c new file mode 100644 index 000000000000..1551173c1c62 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_hash.c @@ -0,0 +1,186 @@ +/* + * 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 fsl_shw_hash.c + * + * This file implements Cryptographic Hashing functions of the FSL SHW API + * for Sahara. This does not include HMAC. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef LINUX_KERNEL +EXPORT_SYMBOL(fsl_shw_hash); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + SAH_SF_DCLS; + unsigned ctx_flags = (hash_ctx->flags & (FSL_HASH_FLAGS_INIT + | FSL_HASH_FLAGS_LOAD + | FSL_HASH_FLAGS_SAVE + | FSL_HASH_FLAGS_FINALIZE)); + + SAH_SF_USER_CHECK(); + + /* Reset expectations if user gets overly zealous. */ + if (result_len > hash_ctx->digest_length) { + result_len = hash_ctx->digest_length; + } + + /* Validate hash ctx flags. + * Need INIT or LOAD but not both. + * Need SAVE or digest ptr (both is ok). + */ + if (((ctx_flags & (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) + == (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) + || ((ctx_flags & (FSL_HASH_FLAGS_INIT | FSL_HASH_FLAGS_LOAD)) == 0) + || (!(ctx_flags & FSL_HASH_FLAGS_SAVE) && (result == NULL))) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + if (ctx_flags & FSL_HASH_FLAGS_INIT) { + sah_Oct_Str out_ptr; + unsigned out_len; + + /* Create desc to perform the initial hashing operation */ + /* Desc. #8 w/INIT and algorithm */ + header = SAH_HDR_MDHA_SET_MODE_HASH + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[hash_ctx->algorithm]; + + /* If user wants one-shot, set padding operation. */ + if (ctx_flags & FSL_HASH_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Determine where Digest will go - hash_ctx or result */ + if (ctx_flags & FSL_HASH_FLAGS_SAVE) { + out_ptr = (sah_Oct_Str) hash_ctx->context; + out_len = hash_ctx->context_register_length; + } else { + out_ptr = result; + out_len = (result_len > hash_ctx->digest_length) + ? hash_ctx->digest_length : result_len; + } + + DESC_IN_OUT(header, length, (sah_Oct_Str) msg, out_len, + out_ptr); + } else { /* not doing hash INIT */ + void *out_ptr; + unsigned out_len; + + /* + * Build two descriptors -- one to load in context/set mode, the + * other to compute & retrieve hash/context value. + * + * First up - Desc. #6 to load context. + */ + /* Desc. #8 w/algorithm */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY + ^ sah_insert_mdha_algorithm[hash_ctx->algorithm]; + + if (ctx_flags & FSL_HASH_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Message Digest (in) */ + DESC_IN_IN(header, + hash_ctx->context_register_length, + (sah_Oct_Str) hash_ctx->context, 0, NULL); + + if (ctx_flags & FSL_HASH_FLAGS_SAVE) { + out_ptr = hash_ctx->context; + out_len = hash_ctx->context_register_length; + } else { + out_ptr = result; + out_len = result_len; + } + + /* Second -- run data through and retrieve ctx regs */ + /* Desc. #10 - no mode register with this. */ + header = SAH_HDR_MDHA_HASH; + DESC_IN_OUT(header, length, (sah_Oct_Str) msg, out_len, + out_ptr); + } /* else not INIT */ + + /* Now that execution is rejoined, we can append another descriptor + to extract the digest/context a second time, into the result. */ + if ((ctx_flags & FSL_HASH_FLAGS_SAVE) + && (result != NULL) && (result_len != 0)) { + + header = SAH_HDR_MDHA_STORE_DIGEST; + + /* Message Digest (out) */ + DESC_IN_OUT(header, 0, NULL, + (result_len > hash_ctx->digest_length) + ? hash_ctx->digest_length : result_len, result); + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_hash() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_hmac.c b/drivers/mxc/security/sahara2/fsl_shw_hmac.c new file mode 100644 index 000000000000..4184d9b280dd --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_hmac.c @@ -0,0 +1,266 @@ +/* + * 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 fsl_shw_hmac.c + * + * This file implements Hashed Message Authentication Code functions of the FSL + * SHW API for Sahara. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_hmac_precompute); +EXPORT_SYMBOL(fsl_shw_hmac); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Get the precompute information + * + * + * @param user_ctx + * @param key_info + * @param hmac_ctx + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if ((key_info->algorithm != FSL_KEY_ALG_HMAC) || + (key_info->key_length > 64)) { + return FSL_RETURN_BAD_ALGORITHM_S; + } else if (key_info->key_length == 0) { + return FSL_RETURN_BAD_KEY_LENGTH_S; + } + + /* Set up to start the Inner Calculation */ + /* Desc. #8 w/IPAD, & INIT */ + header = SAH_HDR_MDHA_SET_MODE_HASH + ^ sah_insert_mdha_ipad + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[hmac_ctx->algorithm]; + + DESC_KEY_OUT(header, key_info, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->inner_precompute); + + /* Set up for starting Outer calculation */ + /* exchange IPAD bit for OPAD bit */ + header ^= (sah_insert_mdha_ipad ^ sah_insert_mdha_opad); + + /* Theoretically, we can leave this link out and use the key which is + * already in the register... however, if we do, the resulting opad + * hash does not have the correct value when using the model. */ + DESC_KEY_OUT(header, key_info, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->outer_precompute); + + SAH_SF_EXECUTE(); + if (ret == FSL_RETURN_OK_S) { + /* flag that precomputes have been entered in this hco + * assume it'll first be used for initilizing */ + hmac_ctx->flags |= (FSL_HMAC_FLAGS_INIT | + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT); + } + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Get the hmac + * + * + * @param user_ctx Info for acquiring memory + * @param key_info + * @param hmac_ctx + * @param msg + * @param length + * @param result + * @param result_len + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + /* check flag consistency */ + /* Note that Final, Init, and Save are an illegal combination when a key + * is being used. Because of the logic flow of this routine, that is + * taken care of without being explict */ + if ( + /* nothing to work on */ + (((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) == 0) && + ((hmac_ctx->flags & FSL_HMAC_FLAGS_LOAD) == 0)) || + /* can't do both */ + ((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) && + (hmac_ctx->flags & FSL_HMAC_FLAGS_LOAD)) || + /* must be some output */ + (((hmac_ctx->flags & FSL_HMAC_FLAGS_SAVE) == 0) && + ((hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) == 0))) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* build descriptor #6 */ + + /* start building descriptor header */ + header = SAH_HDR_MDHA_SET_MODE_MD_KEY ^ + sah_insert_mdha_algorithm[hmac_ctx->algorithm] ^ + sah_insert_mdha_init; + + /* if this is to finalize the digest, mark to pad last block */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_pdata; + } + + /* Check if this is a one shot */ + if ((hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) && + (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE)) { + + header ^= sah_insert_mdha_hmac; + + /* See if this uses Precomputes */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT) { + DESC_IN_IN(header, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->inner_precompute, + hmac_ctx->context_length, + (uint8_t *) hmac_ctx->outer_precompute); + } else { /* Precomputes not requested, try using Key */ + if (key_info->key != NULL) { + /* first, validate the key fields and related flag */ + if ((key_info->key_length == 0) + || (key_info->key_length > 64)) { + ret = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } else { + if (key_info->algorithm != + FSL_KEY_ALG_HMAC) { + ret = + FSL_RETURN_BAD_ALGORITHM_S; + goto out; + } + } + + /* finish building descriptor header (Key specific) */ + header ^= sah_insert_mdha_mac_full; + DESC_IN_KEY(header, 0, NULL, key_info); + } else { /* not using Key or Precomputes, so die */ + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + } + } else { /* it's not a one shot, must be multi-step */ + /* this the last chunk? */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + header ^= sah_insert_mdha_hmac; + DESC_IN_IN(header, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->ongoing_context, + hmac_ctx->context_length, + (uint8_t *) hmac_ctx->outer_precompute); + } else { /* not last chunk */ + uint8_t *ptr1; + + if (hmac_ctx->flags & FSL_HMAC_FLAGS_INIT) { + /* must be using precomputes, cannot 'chunk' message + * starting with a key */ + if (hmac_ctx-> + flags & FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT) + { + ptr1 = + (uint8_t *) hmac_ctx-> + inner_precompute; + } else { + ret = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + } else { + ptr1 = (uint8_t *) hmac_ctx->ongoing_context; + } + + if (ret != FSL_RETURN_OK_S) { + goto out; + } + DESC_IN_IN(header, + hmac_ctx->context_register_length, ptr1, + 0, NULL); + } + } /* multi-step */ + + /* build descriptor #10 & maybe 11 */ + header = SAH_HDR_MDHA_HASH; + + if (hmac_ctx->flags & FSL_HMAC_FLAGS_FINALIZE) { + /* check that the results parameters seem reasonable */ + if ((result_len != 0) && (result != NULL)) { + if (result_len > hmac_ctx->context_register_length) { + result_len = hmac_ctx->context_register_length; + } + + /* message in / digest out (descriptor #10) */ + DESC_IN_OUT(header, length, msg, result_len, result); + + /* see if descriptor #11 needs to be built */ + if (hmac_ctx->flags & FSL_HMAC_FLAGS_SAVE) { + header = SAH_HDR_MDHA_STORE_DIGEST; + /* nothing in / context out */ + DESC_IN_IN(header, 0, NULL, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx-> + ongoing_context); + } + } else { + /* something wrong with result or its length */ + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + } + } else { /* finalize not set, so store in ongoing context field */ + if ((length % 64) == 0) { /* this will change for 384/512 support */ + /* message in / context out */ + DESC_IN_OUT(header, length, msg, + hmac_ctx->context_register_length, + (uint8_t *) hmac_ctx->ongoing_context); + } else { + /* not final data, and not multiple of block length */ + ret = FSL_RETURN_BAD_DATA_LENGTH_S; + } + } + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_hmac() */ diff --git a/drivers/mxc/security/sahara2/fsl_shw_keystore.c b/drivers/mxc/security/sahara2/fsl_shw_keystore.c new file mode 100644 index 000000000000..5585b5b252a0 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_keystore.c @@ -0,0 +1,837 @@ +/* + * Copyright 2008-2009 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 fsl_shw_keystore.c + * + * File which implements a default keystore policy, for use as the system + * keystore. + */ +#include "fsl_platform.h" +#include "fsl_shw.h" +#include "fsl_shw_keystore.h" + +#if defined(DIAG_DRV_IF) +#include <diagnostic.h> +#endif + +#if !defined(FSL_HAVE_SCC2) && defined(__KERNEL__) +#include <linux/mxc_scc_driver.h> +#endif + +/* Define a semaphore to protect the keystore data */ +#ifdef __KERNEL__ +#define LOCK_INCLUDES os_lock_context_t context +#define ACQUIRE_LOCK os_lock_save_context(keystore->lock, context) +#define RELEASE_LOCK os_unlock_restore_context(keystore->lock, context); +#else +#define LOCK_INCLUDES +#define ACQUIRE_LOCK +#define RELEASE_LOCK +#endif /* __KERNEL__ */ + +/*! + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + +/*! + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + +/* Depending on the architecture, these functions should be defined + * differently. On Platforms with SCC2, the functions use the secure + * partition interface and should be available in both user and kernel space. + * On platforms with SCC, they use the SCC keystore interface. This is only + * available in kernel mode, so they should be stubbed out in user mode. + */ +#if defined(FSL_HAVE_SCC2) || (defined(FSL_HAVE_SCC) && defined(__KERNEL__)) +EXPORT_SYMBOL(fsl_shw_init_keystore); +void fsl_shw_init_keystore( + fsl_shw_kso_t *keystore, + fsl_shw_return_t(*data_init) (fsl_shw_uco_t *user_ctx, + void **user_data), + void (*data_cleanup) (fsl_shw_uco_t *user_ctx, + void **user_data), + fsl_shw_return_t(*slot_alloc) (void *user_data, + uint32_t size, + uint64_t owner_id, + uint32_t *slot), + fsl_shw_return_t(*slot_dealloc) (void *user_data, + uint64_t + owner_id, + uint32_t slot), + fsl_shw_return_t(*slot_verify_access) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + void *(*slot_get_address) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_base) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_offset) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_slot_size) (void *user_data, + uint32_t handle)) +{ + keystore->data_init = data_init; + keystore->data_cleanup = data_cleanup; + keystore->slot_alloc = slot_alloc; + keystore->slot_dealloc = slot_dealloc; + keystore->slot_verify_access = slot_verify_access; + keystore->slot_get_address = slot_get_address; + keystore->slot_get_base = slot_get_base; + keystore->slot_get_offset = slot_get_offset; + keystore->slot_get_slot_size = slot_get_slot_size; +} + +EXPORT_SYMBOL(fsl_shw_init_keystore_default); +void fsl_shw_init_keystore_default(fsl_shw_kso_t *keystore) +{ + keystore->data_init = shw_kso_init_data; + keystore->data_cleanup = shw_kso_cleanup_data; + keystore->slot_alloc = shw_slot_alloc; + keystore->slot_dealloc = shw_slot_dealloc; + keystore->slot_verify_access = shw_slot_verify_access; + keystore->slot_get_address = shw_slot_get_address; + keystore->slot_get_base = shw_slot_get_base; + keystore->slot_get_offset = shw_slot_get_offset; + keystore->slot_get_slot_size = shw_slot_get_slot_size; +} + +/*! + * Do any keystore specific initializations + */ +EXPORT_SYMBOL(fsl_shw_establish_keystore); +fsl_shw_return_t fsl_shw_establish_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + if (keystore->data_init == NULL) { + return FSL_RETURN_ERROR_S; + } + + /* Call the data_init function for any user setup */ + return keystore->data_init(user_ctx, &(keystore->user_data)); +} + +EXPORT_SYMBOL(fsl_shw_release_keystore); +void fsl_shw_release_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + + /* Call the data_cleanup function for any keystore cleanup. + * NOTE: The keystore doesn't have any way of telling which keys are using + * it, so it is up to the user program to manage their key objects + * correctly. + */ + if ((keystore != NULL) && (keystore->data_cleanup != NULL)) { + keystore->data_cleanup(user_ctx, &(keystore->user_data)); + } + return; +} + +fsl_shw_return_t keystore_slot_alloc(fsl_shw_kso_t *keystore, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef DIAG_DRV_IF + LOG_DIAG("In keystore_slot_alloc."); + +#endif + ACQUIRE_LOCK; + if ((keystore->slot_alloc == NULL) || (keystore->user_data == NULL)) { + goto out; + } + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("key length: %i, handle: %i\n", size, *slot); + +#endif +retval = keystore->slot_alloc(keystore->user_data, size, owner_id, slot); +out:RELEASE_LOCK; + return retval; +} + +fsl_shw_return_t keystore_slot_dealloc(fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot) +{ + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + ACQUIRE_LOCK; + if ((keystore->slot_alloc == NULL) || (keystore->user_data == NULL)) { + goto out; + } + retval = + keystore->slot_dealloc(keystore->user_data, owner_id, slot); +out:RELEASE_LOCK; + return retval; +} + +fsl_shw_return_t +keystore_slot_load(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + const uint8_t * key_data, uint32_t key_length) +{ + +#ifdef FSL_HAVE_SCC2 + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint32_t slot_size; + uint32_t i; + uint8_t * slot_location; + ACQUIRE_LOCK; + if ((keystore->slot_verify_access == NULL) || + (keystore->user_data == NULL)) + goto out; + if (keystore-> + slot_verify_access(keystore->user_data, owner_id, + slot) !=FSL_RETURN_OK_S) { + retval = FSL_RETURN_AUTH_FAILED_S; + goto out; + } + slot_size = keystore->slot_get_slot_size(keystore->user_data, slot); + if (key_length > slot_size) { + retval = FSL_RETURN_BAD_DATA_LENGTH_S; + goto out; + } + slot_location = keystore->slot_get_address(keystore->user_data, slot); + for (i = 0; i < key_length; i++) { + slot_location[i] = key_data[i]; + } + retval = FSL_RETURN_OK_S; +out:RELEASE_LOCK; + return retval; + +#else /* FSL_HAVE_SCC2 */ + fsl_shw_return_t retval; + scc_return_t scc_ret; + scc_ret = + scc_load_slot(owner_id, slot, (uint8_t *) key_data, key_length); + switch (scc_ret) { + case SCC_RET_OK: + retval = FSL_RETURN_OK_S; + break; + case SCC_RET_VERIFICATION_FAILED: + retval = FSL_RETURN_AUTH_FAILED_S; + break; + case SCC_RET_INSUFFICIENT_SPACE: + retval = FSL_RETURN_BAD_DATA_LENGTH_S; + break; + default: + retval = FSL_RETURN_ERROR_S; + } + return retval; + +#endif /* FSL_HAVE_SCC2 */ +} + +fsl_shw_return_t +keystore_slot_read(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + uint32_t key_length, uint8_t * key_data) +{ +#ifdef FSL_HAVE_SCC2 + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint8_t *slot_addr; + uint32_t slot_size; + + slot_addr = keystore->slot_get_address(keystore->user_data, slot); + slot_size = keystore->slot_get_slot_size(keystore->user_data, slot); + + if (key_length > slot_size) { + retval = FSL_RETURN_BAD_KEY_LENGTH_S; + goto out; + } + + memcpy(key_data, slot_addr, key_length); + retval = FSL_RETURN_OK_S; + + out: + return retval; + +#else /* Have SCC2 */ + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + scc_return_t scc_ret; + printk("keystore SCC \n"); + + scc_ret = + scc_read_slot(owner_id, slot, key_length, (uint8_t *) key_data); + printk("keystore SCC Ret value: %d \n", scc_ret); + switch (scc_ret) { + case SCC_RET_OK: + retval = FSL_RETURN_OK_S; + break; + case SCC_RET_VERIFICATION_FAILED: + retval = FSL_RETURN_AUTH_FAILED_S; + break; + case SCC_RET_INSUFFICIENT_SPACE: + retval = FSL_RETURN_BAD_DATA_LENGTH_S; + break; + default: + retval = FSL_RETURN_ERROR_S; + } + + return retval; + +#endif /* FSL_HAVE_SCC2 */ +}/* end fn keystore_slot_read */ + +fsl_shw_return_t +keystore_slot_encrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + uint8_t *destination) +{ + +#ifdef FSL_HAVE_SCC2 + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint32_t slot_length; + uint32_t IV[4]; + uint32_t * iv_ptr = (uint32_t *) & (owner_id); + + /* Build the IV */ + IV[0] = iv_ptr[0]; + IV[1] = iv_ptr[1]; + IV[2] = 0; + IV[3] = 0; + ACQUIRE_LOCK; + + /* Ensure that the data will fit in the key slot */ + slot_length = + keystore->slot_get_slot_size(keystore->user_data, slot); + if (length > slot_length) { + goto out; + } + + /* Call scc encrypt function to encrypt the data. */ + retval = do_scc_encrypt_region(user_ctx, + (void *)keystore-> + slot_get_base(keystore->user_data, + slot), + keystore->slot_get_offset(keystore-> + user_data, + slot), + length, destination, IV, + FSL_SHW_CYPHER_MODE_CBC); + goto out; +out:RELEASE_LOCK; + return retval; + +#else + scc_return_t retval; + retval = scc_encrypt_slot(owner_id, slot, length, destination); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + return FSL_RETURN_ERROR_S; + +#endif /* FSL_HAVE_SCC2 */ +} + +fsl_shw_return_t +keystore_slot_decrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + const uint8_t *source) +{ + +#ifdef FSL_HAVE_SCC2 + LOCK_INCLUDES; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + uint32_t slot_length; + uint32_t IV[4]; + uint32_t *iv_ptr = (uint32_t *) & (owner_id); + + /* Build the IV */ + IV[0] = iv_ptr[0]; + IV[1] = iv_ptr[1]; + IV[2] = 0; + IV[3] = 0; + ACQUIRE_LOCK; + + /* Call scc decrypt function to decrypt the data. */ + + /* Ensure that the data will fit in the key slot */ + slot_length = + keystore->slot_get_slot_size(keystore->user_data, slot); + if (length > slot_length) + goto out; + + /* Call scc decrypt function to encrypt the data. */ + retval = do_scc_decrypt_region(user_ctx, + (void *)keystore-> + slot_get_base(keystore->user_data, + slot), + keystore->slot_get_offset(keystore-> + user_data, + slot), + length, source, IV, + FSL_SHW_CYPHER_MODE_CBC); + goto out; +out:RELEASE_LOCK; + return retval; + +#else + scc_return_t retval; + retval = scc_decrypt_slot(owner_id, slot, length, source); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + return FSL_RETURN_ERROR_S; + +#endif /* FSL_HAVE_SCC2 */ +} + +#else /* SCC in userspace */ +void fsl_shw_init_keystore( + fsl_shw_kso_t *keystore, + fsl_shw_return_t(*data_init) (fsl_shw_uco_t *user_ctx, + void **user_data), + void (*data_cleanup) (fsl_shw_uco_t *user_ctx, + void **user_data), + fsl_shw_return_t(*slot_alloc) (void *user_data, + uint32_t size, + uint64_t owner_id, + uint32_t *slot), + fsl_shw_return_t(*slot_dealloc) (void *user_data, + uint64_t + owner_id, + uint32_t slot), + fsl_shw_return_t(*slot_verify_access) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + void *(*slot_get_address) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_base) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_offset) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_slot_size) (void *user_data, + uint32_t handle)) +{ + (void)keystore; + (void)data_init; + (void)data_cleanup; + (void)slot_alloc; + (void)slot_dealloc; + (void)slot_verify_access; + (void)slot_get_address; + (void)slot_get_base; + (void)slot_get_offset; + (void)slot_get_slot_size; +} + +void fsl_shw_init_keystore_default(fsl_shw_kso_t * keystore) +{ + (void)keystore; +} +fsl_shw_return_t fsl_shw_establish_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + (void)user_ctx; + (void)keystore; + return FSL_RETURN_NO_RESOURCE_S; +} +void fsl_shw_release_keystore(fsl_shw_uco_t *user_ctx, + fsl_shw_kso_t *keystore) +{ + (void)user_ctx; + (void)keystore; + return; +} + +fsl_shw_return_t keystore_slot_alloc(fsl_shw_kso_t *keystore, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + (void)keystore; + (void)size; + (void)owner_id; + (void)slot; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t keystore_slot_dealloc(fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot) +{ + (void)keystore; + (void)owner_id; + (void)slot; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_load(fsl_shw_kso_t *keystore, uint64_t owner_id, uint32_t slot, + const uint8_t *key_data, uint32_t key_length) +{ + (void)keystore; + (void)owner_id; + (void)slot; + (void)key_data; + (void)key_length; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_read(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + uint32_t key_length, uint8_t * key_data) +{ + (void)keystore; + (void)owner_id; + (void)slot; + (void)key_length; + (void)key_data; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_decrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + const uint8_t *source) +{ + (void)user_ctx; + (void)keystore; + (void)owner_id; + (void)slot; + (void)length; + (void)source; + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t +keystore_slot_encrypt(fsl_shw_uco_t *user_ctx, fsl_shw_kso_t *keystore, + uint64_t owner_id, uint32_t slot, uint32_t length, + uint8_t *destination) +{ + (void)user_ctx; + (void)keystore; + (void)owner_id; + (void)slot; + (void)length; + (void)destination; + return FSL_RETURN_NO_RESOURCE_S; +} + + +#endif /* FSL_HAVE_SCC2 */ + +/***** Default keystore implementation **************************************/ + +#ifdef FSL_HAVE_SCC2 + fsl_shw_return_t shw_kso_init_data(fsl_shw_uco_t *user_ctx, + void **user_data) +{ + int retval = FSL_RETURN_ERROR_S; + keystore_data_t *keystore_data = NULL; + fsl_shw_pco_t *capabilities = fsl_shw_get_capabilities(user_ctx); + uint32_t partition_size; + uint32_t slot_count; + uint32_t keystore_data_size; + uint8_t UMID[16] = { + 0x42, 0, 0, 0, 0x43, 0, 0, 0, 0x19, 0, 0, 0, 0x59, 0, 0, 0}; + uint32_t permissions = + FSL_PERM_TH_R | FSL_PERM_TH_W | FSL_PERM_HD_R | FSL_PERM_HD_W | + FSL_PERM_HD_X; + + /* Look up the size of a partition to see how big to make the keystore */ + partition_size = fsl_shw_pco_get_spo_size_bytes(capabilities); + + /* Calculate the required size of the keystore data structure, based on the + * number of keys that can fit in the partition. + */ + slot_count = partition_size / KEYSTORE_SLOT_SIZE; + keystore_data_size = + sizeof(keystore_data_t) + + slot_count * sizeof(keystore_data_slot_info_t); + +#ifdef __KERNEL__ + keystore_data = os_alloc_memory(keystore_data_size, GFP_KERNEL); + +#else + keystore_data = malloc(keystore_data_size); + +#endif + if (keystore_data == NULL) { + retval = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + + /* Clear the memory (effectively clear all key assignments) */ + memset(keystore_data, 0, keystore_data_size); + + /* Place the slot information structure directly after the keystore data + * structure. + */ + keystore_data->slot = + (keystore_data_slot_info_t *) (keystore_data + 1); + keystore_data->slot_count = slot_count; + + /* Retrieve a secure partition to put the keystore in. */ + keystore_data->base_address = + fsl_shw_smalloc(user_ctx, partition_size, UMID, permissions); + if (keystore_data->base_address == NULL) { + retval = FSL_RETURN_NO_RESOURCE_S; + goto out; + } + *user_data = keystore_data; + retval = FSL_RETURN_OK_S; +out:if (retval != FSL_RETURN_OK_S) { + if (keystore_data != NULL) { + if (keystore_data->base_address != NULL) + fsl_shw_sfree(NULL, + keystore_data->base_address); + +#ifdef __KERNEL__ + os_free_memory(keystore_data); + +#else + free(keystore_data); + +#endif + } + } + return retval; +} +void shw_kso_cleanup_data(fsl_shw_uco_t *user_ctx, void **user_data) +{ + if (user_data != NULL) { + keystore_data_t * keystore_data = + (keystore_data_t *) (*user_data); + fsl_shw_sfree(user_ctx, keystore_data->base_address); + +#ifdef __KERNEL__ + os_free_memory(*user_data); + +#else + free(*user_data); + +#endif + } + return; +} + +fsl_shw_return_t shw_slot_verify_access(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + keystore_data_t * data = user_data; + if (data->slot[slot].owner == owner_id) { + return FSL_RETURN_OK_S; + } else { + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("Access to slot %i fails.\n", slot); + +#endif + return FSL_RETURN_AUTH_FAILED_S; + } +} + +fsl_shw_return_t shw_slot_alloc(void *user_data, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + keystore_data_t *data = user_data; + uint32_t i; + if (size > KEYSTORE_SLOT_SIZE) + return FSL_RETURN_BAD_KEY_LENGTH_S; + for (i = 0; i < data->slot_count; i++) { + if (data->slot[i].allocated == 0) { + data->slot[i].allocated = 1; + data->slot[i].owner = owner_id; + (*slot) = i; + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("Keystore: allocated slot %i. Slot " + "address: %p\n", + (*slot), + data->base_address + + (*slot) * KEYSTORE_SLOT_SIZE); + +#endif + return FSL_RETURN_OK_S; + } + } + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t shw_slot_dealloc(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + keystore_data_t * data = user_data; + (void)owner_id; + (void)slot; + if (slot >= data->slot_count) + return FSL_RETURN_ERROR_S; + if (data->slot[slot].allocated == 1) { + /* Forcibly remove the data from the keystore */ + memset(shw_slot_get_address(user_data, slot), 0, + KEYSTORE_SLOT_SIZE); + data->slot[slot].allocated = 0; + return FSL_RETURN_OK_S; + } + return FSL_RETURN_ERROR_S; +} + +void *shw_slot_get_address(void *user_data, uint32_t slot) +{ + keystore_data_t * data = user_data; + if (slot >= data->slot_count) + return NULL; + return data->base_address + slot * KEYSTORE_SLOT_SIZE; +} + +uint32_t shw_slot_get_base(void *user_data, uint32_t slot) +{ + keystore_data_t * data = user_data; + + /* There could potentially be more than one secure partition object + * associated with this keystore. For now, there is just one. + */ + (void)slot; + return (uint32_t) (data->base_address); +} + +uint32_t shw_slot_get_offset(void *user_data, uint32_t slot) +{ + keystore_data_t *data = user_data; + if (slot >= data->slot_count) + return FSL_RETURN_ERROR_S; + return (slot * KEYSTORE_SLOT_SIZE); +} + +uint32_t shw_slot_get_slot_size(void *user_data, uint32_t slot) +{ + (void)user_data; + (void)slot; + + /* All slots are the same size in the default implementation */ + return KEYSTORE_SLOT_SIZE; +} + +#else /* FSL_HAVE_SCC2 */ + +#ifdef __KERNEL__ + fsl_shw_return_t shw_kso_init_data(fsl_shw_uco_t *user_ctx, + void **user_data) +{ + + /* The SCC does its own initialization. All that needs to be done here is + * make sure an SCC exists. + */ + *user_data = (void *)0xFEEDFEED; + return FSL_RETURN_OK_S; +} +void shw_kso_cleanup_data(fsl_shw_uco_t *user_ctx, void **user_data) +{ + + /* The SCC does its own cleanup. */ + *user_data = NULL; + return; +} + +fsl_shw_return_t shw_slot_verify_access(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + + /* Zero is used for the size because the newer interface does bounds + * checking later. + */ + scc_return_t retval; + retval = scc_verify_slot_access(owner_id, slot, 0); + if (retval == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } + return FSL_RETURN_AUTH_FAILED_S; +} + +fsl_shw_return_t shw_slot_alloc(void *user_data, uint32_t size, + uint64_t owner_id, uint32_t *slot) +{ + scc_return_t retval; + +#ifdef DIAG_DRV_IF + LOG_DIAG_ARGS("key length: %i, handle: %i\n", size, *slot); + +#endif + retval = scc_alloc_slot(size, owner_id, slot); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t shw_slot_dealloc(void *user_data, uint64_t owner_id, + uint32_t slot) +{ + scc_return_t retval; + retval = scc_dealloc_slot(owner_id, slot); + if (retval == SCC_RET_OK) + return FSL_RETURN_OK_S; + + return FSL_RETURN_ERROR_S; +} +void *shw_slot_get_address(void *user_data, uint32_t slot) +{ + uint64_t owner_id = *((uint64_t *) user_data); + uint32_t address; + uint32_t value_size_bytes; + uint32_t slot_size_bytes; + scc_return_t scc_ret; + scc_ret = + scc_get_slot_info(owner_id, slot, &address, &value_size_bytes, + &slot_size_bytes); + if (scc_ret == SCC_RET_OK) { + return (void *)address; + } + return NULL; +} + +uint32_t shw_slot_get_base(void *user_data, uint32_t slot) +{ + return 0; +} + +uint32_t shw_slot_get_offset(void *user_data, uint32_t slot) +{ + return 0; +} + + +/* Return the size of the key slot, in octets */ +uint32_t shw_slot_get_slot_size(void *user_data, uint32_t slot) +{ + uint64_t owner_id = *((uint64_t *) user_data); + uint32_t address; + uint32_t value_size_bytes; + uint32_t slot_size_bytes; + scc_return_t scc_ret; + scc_ret = + scc_get_slot_info(owner_id, slot, &address, &value_size_bytes, + &slot_size_bytes); + if (scc_ret == SCC_RET_OK) + return slot_size_bytes; + return 0; +} + + +#endif /* __KERNEL__ */ + +#endif /* FSL_HAVE_SCC2 */ + +/*****************************************************************************/ diff --git a/drivers/mxc/security/sahara2/fsl_shw_rand.c b/drivers/mxc/security/sahara2/fsl_shw_rand.c new file mode 100644 index 000000000000..566c9e5c1e0f --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_rand.c @@ -0,0 +1,96 @@ +/* + * 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 fsl_shw_rand.c + * + * This file implements Random Number Generation functions of the FSL SHW API + * for Sahara. + */ + +#include "sahara.h" +#include "sf_util.h" + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_get_random); +#endif + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Get a random number + * + * + * @param user_ctx + * @param length + * @param data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + SAH_SF_DCLS; + + /* perform a sanity check on the uco */ + ret = sah_validate_uco(user_ctx); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + DESC_OUT_OUT(header, length, data, 0, NULL); + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_add_entropy); +#endif + +/*! + * Add entropy to a random number generator + + * @param user_ctx + * @param length + * @param data + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data) +{ + SAH_SF_DCLS; + + /* perform a sanity check on the uco */ + ret = sah_validate_uco(user_ctx); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + + /* create descriptor #18. Generate random data */ + DESC_IN_IN(header, 0, NULL, length, data) + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_sym.c b/drivers/mxc/security/sahara2/fsl_shw_sym.c new file mode 100644 index 000000000000..454905bbcf68 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_sym.c @@ -0,0 +1,281 @@ +/* + * 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 fsl_shw_sym.c + * + * This file implements Symmetric Cipher functions of the FSL SHW API for + * Sahara. This does not include CCM. + */ + +#include "sahara.h" +#include "fsl_platform.h" + +#include "sf_util.h" +#include "adaptor.h" + +#ifdef LINUX_KERNEL +EXPORT_SYMBOL(fsl_shw_symmetric_encrypt); +EXPORT_SYMBOL(fsl_shw_symmetric_decrypt); +#endif + +#if defined(NEED_CTR_WORKAROUND) +/* CTR mode needs block-multiple data in/out */ +#define LENGTH_PATCH (sym_ctx->block_size_bytes) +#define LENGTH_PATCH_MASK (sym_ctx->block_size_bytes-1) +#else +#define LENGTH_PATCH 0 +#define LENGTH_PATCH_MASK 0 /* du not use! */ +#endif + +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint32_t block_zeros[4] = { + 0, 0, 0, 0 +}; + +typedef enum cipher_direction { + SYM_DECRYPT, + SYM_ENCRYPT +} cipher_direction_t; + +/*! + * Create and run the chain for a symmetric-key operation. + * + * @param user_ctx Who the user is + * @param key_info What key is to be used + * @param sym_ctx Info details about algorithm + * @param encrypt 0 = decrypt, non-zero = encrypt + * @param length Number of octets at @a in and @a out + * @param in Pointer to input data + * @param out Location to store output data + * + * @return The status of handing chain to driver, + * or an earlier argument/flag or allocation + * error. + */ +static fsl_shw_return_t do_symmetric(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + cipher_direction_t encrypt, + uint32_t length, + const uint8_t * in, uint8_t * out) +{ + SAH_SF_DCLS; + uint8_t *sink = NULL; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + sah_Oct_Str ptr1; + uint32_t size1 = sym_ctx->block_size_bytes; + + SAH_SF_USER_CHECK(); + + /* Two different sets of chains, depending on algorithm */ + if (key_info->algorithm == FSL_KEY_ALG_ARC4) { + if (sym_ctx->flags & FSL_SYM_CTX_INIT) { + /* Desc. #35 w/ARC4 - start from key */ + header = SAH_HDR_ARC4_SET_MODE_KEY + ^ sah_insert_skha_algorithm_arc4; + + DESC_IN_KEY(header, 0, NULL, key_info); + } else { /* load SBox */ + /* Desc. #33 w/ARC4 and NO PERMUTE */ + header = SAH_HDR_ARC4_SET_MODE_SBOX + ^ sah_insert_skha_no_permute + ^ sah_insert_skha_algorithm_arc4; + DESC_IN_IN(header, 256, sym_ctx->context, + 3, sym_ctx->context + 256); + } /* load SBox */ + + /* Add in-out data descriptor to process the data */ + if (length != 0) { + DESC_IN_OUT(SAH_HDR_SKHA_ENC_DEC, length, in, length, + out); + } + + /* Operation is done ... save what came out? */ + if (sym_ctx->flags & FSL_SYM_CTX_SAVE) { + /* Desc. #34 - Read SBox, pointers */ + header = SAH_HDR_ARC4_READ_SBOX; + DESC_OUT_OUT(header, 256, sym_ctx->context, + 3, sym_ctx->context + 256); + } + } else { /* not ARC4 */ + /* Doing 1- or 2- descriptor chain. */ + /* Desc. #1 and algorithm and mode */ + header = SAH_HDR_SKHA_SET_MODE_IV_KEY + ^ sah_insert_skha_mode[sym_ctx->mode] + ^ sah_insert_skha_algorithm[key_info->algorithm]; + + /* Honor 'no key parity checking' for DES and TDES */ + if ((key_info->flags & FSL_SKO_KEY_IGNORE_PARITY) && + ((key_info->algorithm == FSL_KEY_ALG_DES) || + (key_info->algorithm == FSL_KEY_ALG_TDES))) { + header ^= sah_insert_skha_no_key_parity; + } + + /* Header by default is decrypting, so... */ + if (encrypt == SYM_ENCRYPT) { + header ^= sah_insert_skha_encrypt; + } + + if (sym_ctx->mode == FSL_SYM_MODE_CTR) { + header ^= sah_insert_skha_modulus[sym_ctx->modulus_exp]; + } + + if (sym_ctx->mode == FSL_SYM_MODE_ECB) { + ptr1 = NULL; + size1 = 0; + } else if (sym_ctx->flags & FSL_SYM_CTX_INIT) { + ptr1 = (uint8_t *) block_zeros; + } else { + ptr1 = sym_ctx->context; + } + + DESC_IN_KEY(header, sym_ctx->block_size_bytes, ptr1, key_info); + + /* Add in-out data descriptor */ + if (length != 0) { + header = SAH_HDR_SKHA_ENC_DEC; + if (LENGTH_PATCH && (sym_ctx->mode == FSL_SYM_MODE_CTR) + && ((length & LENGTH_PATCH_MASK) != 0)) { + sink = DESC_TEMP_ALLOC(LENGTH_PATCH); + ret = + sah_Create_Link(user_ctx->mem_util, &link1, + (uint8_t *) in, length, + SAH_USES_LINK_DATA); + ret = + sah_Append_Link(user_ctx->mem_util, link1, + (uint8_t *) sink, + LENGTH_PATCH - + (length & + LENGTH_PATCH_MASK), + SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = + sah_Create_Link(user_ctx->mem_util, &link2, + out, length, + SAH_USES_LINK_DATA | + SAH_OUTPUT_LINK); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = sah_Append_Link(user_ctx->mem_util, link2, + sink, + LENGTH_PATCH - + (length & + LENGTH_PATCH_MASK), + SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + ret = + sah_Append_Desc(user_ctx->mem_util, + &desc_chain, header, link1, + link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + link1 = link2 = NULL; + } else { + DESC_IN_OUT(header, length, in, length, out); + } + } + + /* Unload any desired context */ + if (sym_ctx->flags & FSL_SYM_CTX_SAVE) { + DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, 0, NULL, + sym_ctx->block_size_bytes, + sym_ctx->context); + } + + } /* not ARC4 */ + + SAH_SF_EXECUTE(); + + out: + SAH_SF_DESC_CLEAN(); + DESC_TEMP_FREE(sink); + if (LENGTH_PATCH) { + sah_Destroy_Link(user_ctx->mem_util, link1); + sah_Destroy_Link(user_ctx->mem_util, link2); + } + + return ret; +} + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ + +/*! + * Compute symmetric encryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, uint8_t * ct) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_ENCRYPT, + length, pt, ct); + + return ret; +} + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ + +/*! + * Compute symmetric decryption + * + * + * @param user_ctx + * @param key_info + * @param sym_ctx + * @param length + * @param pt + * @param ct + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, uint8_t * pt) +{ + fsl_shw_return_t ret; + + ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_DECRYPT, + length, ct, pt); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_user.c b/drivers/mxc/security/sahara2/fsl_shw_user.c new file mode 100644 index 000000000000..49ec97e7662a --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_user.c @@ -0,0 +1,137 @@ +/* + * Copyright 2004-2008 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 fsl_shw_user.c + * + * This file implements user and platform capabilities functions of the FSL SHW + * API for Sahara + */ +#include "sahara.h" +#include <adaptor.h> +#include <sf_util.h> + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_get_capabilities); +EXPORT_SYMBOL(fsl_shw_register_user); +EXPORT_SYMBOL(fsl_shw_deregister_user); +EXPORT_SYMBOL(fsl_shw_get_results); +#endif /* __KERNEL__ */ + +struct cap_t { + unsigned populated; + union { + uint32_t buffer[sizeof(fsl_shw_pco_t)]; + fsl_shw_pco_t pco; + }; +}; + +static struct cap_t cap = { + 0, + {} +}; + +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +/*! + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_pco_t *retval = NULL; + + if (cap.populated) { + retval = &cap.pco; + } else { + if (get_capabilities(user_ctx, &cap.pco) == FSL_RETURN_OK_S) { + cap.populated = 1; + retval = &cap.pco; + } + } + return retval; +} + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ + +/*! + * Create an association between the the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx) +{ + return sah_register(user_ctx); +} + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ + +/*! + * Destroy the association between the the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx) +{ + return sah_deregister(user_ctx); +} + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ + +/*! + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count) +{ + fsl_shw_return_t status; + + /* perform a sanity check on the uco */ + status = sah_validate_uco(user_ctx); + + /* if uco appears ok, build structure and pass to get results */ + if (status == FSL_RETURN_OK_S) { + sah_results arg; + + /* if requested is zero, it's done before it started */ + if (result_size > 0) { + arg.requested = result_size; + arg.actual = result_count; + arg.results = results; + /* get the results */ + status = sah_get_results(&arg, user_ctx); + } + } + + return status; +} diff --git a/drivers/mxc/security/sahara2/fsl_shw_wrap.c b/drivers/mxc/security/sahara2/fsl_shw_wrap.c new file mode 100644 index 000000000000..ebcf9a6bd7c2 --- /dev/null +++ b/drivers/mxc/security/sahara2/fsl_shw_wrap.c @@ -0,0 +1,967 @@ +/* + * Copyright 2004-2009 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 fsl_shw_wrap.c + * + * This file implements Key-Wrap (Black Key) functions of the FSL SHW API for + * Sahara. + * + * - Ownerid is an 8-byte, user-supplied, value to keep KEY confidential. + * - KEY is a 1-32 byte value which starts in SCC RED RAM before + * wrapping, and ends up there on unwrap. Length is limited because of + * size of SCC1 RAM. + * - KEY' is the encrypted KEY + * - LEN is a 1-byte (for now) byte-length of KEY + * - ALG is a 1-byte value for the algorithm which which the key is + * associated. Values are defined by the FSL SHW API + * - Ownerid, LEN, and ALG come from the user's "key_info" object, as does the + * slot number where KEY already is/will be. + * - T is a Nonce + * - T' is the encrypted T + * - KEK is a Key-Encryption Key for the user's Key + * - ICV is the "Integrity Check Value" for the wrapped key + * - Black Key is the string of bytes returned as the wrapped key +<table> +<tr><TD align="right">BLACK_KEY <TD width="3">=<TD>ICV | T' | LEN | ALG | + KEY'</td></tr> +<tr><td> </td></tr> + +<tr><th>To Wrap</th></tr> +<tr><TD align="right">T</td> <TD width="3">=</td> <TD>RND()<sub>16</sub> + </td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><TD>HASH<sub>sha256</sub>(T | + Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY'<TD width="3">=</td><TD> + AES<sub>ctr-enc</sub>(Key=KEK, CTR=0, Data=KEY)</td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">T'</td><TD width="3">=</td><TD>TDES<sub>cbc-enc</sub> + (Key=SLID, IV=Ownerid, Data=T)</td></tr> + +<tr><td> </td></tr> + +<tr><th>To Unwrap</th></tr> +<tr><TD align="right">T</td><TD width="3">=</td><TD>TDES<sub>ecb-dec</sub> + (Key=SLID, IV=Ownerid, Data=T')</sub></td></tr> +<tr><TD align="right">ICV</td><TD width="3">=</td><td>HMAC<sub>sha256</sub> + (Key=T, Data=Ownerid | LEN | ALG | KEY')<sub>16</sub></td></tr> +<tr><TD align="right">KEK</td><TD width="3">=</td><td>HASH<sub>sha256</sub> + (T | Ownerid)<sub>16</sub></td></tr> +<tr><TD align="right">KEY<TD width="3">=</td><TD>AES<sub>ctr-dec</sub> + (Key=KEK, CTR=0, Data=KEY')</td></tr> +</table> + + */ + +#include "sahara.h" +#include "fsl_platform.h" +#include "fsl_shw_keystore.h" + +#include "sf_util.h" +#include "adaptor.h" + +#if defined(DIAG_SECURITY_FUNC) +#include <diagnostic.h> +#endif + +#if defined(NEED_CTR_WORKAROUND) +/* CTR mode needs block-multiple data in/out */ +#define LENGTH_PATCH 16 +#define LENGTH_PATCH_MASK 0xF +#else +#define LENGTH_PATCH 4 +#define LENGTH_PATCH_MASK 3 +#endif + +#if LENGTH_PATCH +#define ROUND_LENGTH(len) \ +({ \ + uint32_t orig_len = len; \ + uint32_t new_len; \ + \ + if ((orig_len & LENGTH_PATCH_MASK) != 0) { \ + new_len = (orig_len + LENGTH_PATCH \ + - (orig_len & LENGTH_PATCH_MASK)); \ + } \ + else { \ + new_len = orig_len; \ + } \ + new_len; \ +}) +#else +#define ROUND_LENGTH(len) (len) +#endif + +#ifdef __KERNEL__ +EXPORT_SYMBOL(fsl_shw_establish_key); +EXPORT_SYMBOL(fsl_shw_extract_key); +EXPORT_SYMBOL(fsl_shw_release_key); +EXPORT_SYMBOL(fsl_shw_read_key); +#endif + +#define ICV_LENGTH 16 +#define T_LENGTH 16 +#define KEK_LENGTH 16 +#define LENGTH_LENGTH 1 +#define ALGORITHM_LENGTH 1 +#define FLAGS_LENGTH 1 + +/* ICV | T' | LEN | ALG | KEY' */ +#define ICV_OFFSET 0 +#define T_PRIME_OFFSET (ICV_OFFSET + ICV_LENGTH) +#define LENGTH_OFFSET (T_PRIME_OFFSET + T_LENGTH) +#define ALGORITHM_OFFSET (LENGTH_OFFSET + LENGTH_LENGTH) +#define FLAGS_OFFSET (ALGORITHM_OFFSET + ALGORITHM_LENGTH) +#define KEY_PRIME_OFFSET (FLAGS_OFFSET + FLAGS_LENGTH) +#define FLAGS_SW_KEY 0x01 + +/* + * For testing of the algorithm implementation,, the DO_REPEATABLE_WRAP flag + * causes the T_block to go into the T field during a wrap operation. This + * will make the black key value repeatable (for a given SCC secret key, or + * always if the default key is in use). + * + * Normally, a random sequence is used. + */ +#ifdef DO_REPEATABLE_WRAP +/*! + * Block of zeroes which is maximum Symmetric block size, used for + * initializing context register, etc. + */ +static uint8_t T_block_[16] = { + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42, + 0x42, 0, 0, 0x42, 0x42, 0, 0, 0x42 +}; +#endif + +/*! + * Insert descriptors to calculate ICV = HMAC(key=T, data=LEN|ALG|KEY') + * + * @param user_ctx User's context for this operation + * @param desc_chain Descriptor chain to append to + * @param t_key_info T's key object + * @param black_key Beginning of Black Key region + * @param key_length Number of bytes of key' there are in @c black_key + * @param[out] hmac Location to store ICV. Will be tagged "USES" so + * sf routines will not try to free it. + * + * @return A return code of type #fsl_shw_return_t. + */ +static inline fsl_shw_return_t create_icv_calc(fsl_shw_uco_t * user_ctx, + sah_Head_Desc ** desc_chain, + fsl_shw_sko_t * t_key_info, + const uint8_t * black_key, + uint32_t key_length, + uint8_t * hmac) +{ + fsl_shw_return_t sah_code; + uint32_t header; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + /* Load up T as key for the HMAC */ + header = (SAH_HDR_MDHA_SET_MODE_MD_KEY /* #6 */ + ^ sah_insert_mdha_algorithm_sha256 + ^ sah_insert_mdha_init ^ sah_insert_mdha_hmac ^ + sah_insert_mdha_pdata ^ sah_insert_mdha_mac_full); + sah_code = sah_add_in_key_desc(header, NULL, 0, t_key_info, /* Reference T in RED */ + user_ctx->mem_util, desc_chain); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + /* Previous step loaded key; Now set up to hash the data */ + header = SAH_HDR_MDHA_HASH; /* #10 */ + + /* Input - start with ownerid */ + sah_code = sah_Create_Link(user_ctx->mem_util, &link1, + (void *)&t_key_info->userid, + sizeof(t_key_info->userid), + SAH_USES_LINK_DATA); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - Append black-key fields len, alg, key' */ + sah_code = sah_Append_Link(user_ctx->mem_util, link1, + (void *)black_key + LENGTH_OFFSET, + (LENGTH_LENGTH + + ALGORITHM_LENGTH + + key_length), SAH_USES_LINK_DATA); + + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + /* Output - computed ICV/HMAC */ + sah_code = sah_Create_Link(user_ctx->mem_util, &link2, + hmac, ICV_LENGTH, + SAH_USES_LINK_DATA | SAH_OUTPUT_LINK); + if (sah_code != FSL_RETURN_OK_S) { + goto out; + } + + sah_code = sah_Append_Desc(user_ctx->mem_util, desc_chain, + header, link1, link2); + + out: + if (sah_code != FSL_RETURN_OK_S) { + (void)sah_Destroy_Link(user_ctx->mem_util, link1); + (void)sah_Destroy_Link(user_ctx->mem_util, link2); + } + + return sah_code; +} /* create_icv_calc */ + +/*! + * Perform unwrapping of a black key into a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be unwrapped... key length, slot info, etc. + * @param black_key Encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t unwrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + const uint8_t * black_key) +{ + SAH_SF_DCLS; + uint8_t *hmac = NULL; + fsl_shw_sko_t t_key_info; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + unsigned i; + unsigned rounded_key_length; + unsigned original_key_length = key_info->key_length; + + hmac = DESC_TEMP_ALLOC(ICV_LENGTH); + + /* Set up key_info for "T" - use same slot as eventual key */ + fsl_shw_sko_init(&t_key_info, FSL_KEY_ALG_AES); + t_key_info.userid = key_info->userid; + t_key_info.handle = key_info->handle; + t_key_info.flags = key_info->flags; + t_key_info.key_length = T_LENGTH; + t_key_info.keystore = key_info->keystore; + + /* Validate SW flags to prevent misuse */ + if ((key_info->flags & FSL_SKO_KEY_SW_KEY) + && !(black_key[FLAGS_OFFSET] & FLAGS_SW_KEY)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + /* Compute T = SLID_decrypt(T'); leave in RED slot */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_decrypt(user_ctx, + key_info->userid, + t_key_info.handle, + T_LENGTH, + black_key + T_PRIME_OFFSET); + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_decrypt(user_ctx, + key_info->keystore, + key_info->userid, + t_key_info.handle, + T_LENGTH, + black_key + T_PRIME_OFFSET); + } + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Compute ICV = HMAC(T, ownerid | len | alg | key' */ + ret = create_icv_calc(user_ctx, &desc_chain, &t_key_info, + black_key, original_key_length, hmac); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key" + " flag!\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Validating MAC of wrapped key"); +#endif + SAH_SF_EXECUTE(); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + SAH_SF_DESC_CLEAN(); + + /* Check computed ICV against value in Black Key */ + for (i = 0; i < ICV_LENGTH; i++) { + if (black_key[ICV_OFFSET + i] != hmac[i]) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS("computed ICV fails at offset %i\n", i); + + { + char buff[300]; + int a; + for (a = 0; a < ICV_LENGTH; a++) + sprintf(&(buff[a * 2]), "%02x", + black_key[ICV_OFFSET + a]); + buff[a * 2 + 1] = 0; + LOG_DIAG_ARGS("black key: %s", buff); + + for (a = 0; a < ICV_LENGTH; a++) + sprintf(&(buff[a * 2]), "%02x", + hmac[a]); + buff[a * 2 + 1] = 0; + LOG_DIAG_ARGS("hmac: %s", buff); + } +#endif + ret = FSL_RETURN_AUTH_FAILED_S; + goto out; + } + } + + /* This is no longer needed. */ + DESC_TEMP_FREE(hmac); + + /* Compute KEK = SHA256(T | ownerid). Rewrite slot with value */ + header = (SAH_HDR_MDHA_SET_MODE_HASH /* #8 */ + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm_sha256 ^ sah_insert_mdha_pdata); + + /* Input - Start with T */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link1, &t_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Still input - append ownerid */ + ret = sah_Append_Link(user_ctx->mem_util, link1, + (void *)&key_info->userid, + sizeof(key_info->userid), SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Output - KEK goes into RED slot */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link2, &t_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Put the Hash calculation into the chain. */ + ret = sah_Append_Desc(user_ctx->mem_util, &desc_chain, + header, link1, link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Compute KEY = AES-decrypt(KEK, KEY') */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY /* #1 */ + ^ sah_insert_skha_mode_ctr + ^ sah_insert_skha_algorithm_aes + ^ sah_insert_skha_modulus_128); + /* Load KEK in as the key to use */ + DESC_IN_KEY(header, 0, NULL, &t_key_info); + + rounded_key_length = ROUND_LENGTH(original_key_length); + key_info->key_length = rounded_key_length; + + /* Now set up for computation. Result in RED */ + header = SAH_HDR_SKHA_ENC_DEC; /* #4 */ + DESC_IN_KEY(header, rounded_key_length, black_key + KEY_PRIME_OFFSET, + key_info); + + /* Perform the operation */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Decrypting key with KEK"); +#endif + SAH_SF_EXECUTE(); + + out: + key_info->key_length = original_key_length; + SAH_SF_DESC_CLEAN(); + + DESC_TEMP_FREE(hmac); + + /* Erase tracks */ + t_key_info.userid = 0xdeadbeef; + t_key_info.handle = 0xdeadbeef; + + return ret; +} /* unwrap */ + +/*! + * Perform wrapping of a black key from a RED slot + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be wrapped... key length, slot info, etc. + * @param black_key Place to store encrypted key + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t wrap(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * black_key) +{ + SAH_SF_DCLS; + unsigned slots_allocated = 0; /* boolean */ + fsl_shw_sko_t T_key_info; /* for holding T */ + fsl_shw_sko_t KEK_key_info; /* for holding KEK */ + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + sah_Link *link1; + sah_Link *link2; + + black_key[LENGTH_OFFSET] = key_info->key_length; + black_key[ALGORITHM_OFFSET] = key_info->algorithm; + + memcpy(&T_key_info, key_info, sizeof(T_key_info)); + fsl_shw_sko_set_key_length(&T_key_info, T_LENGTH); + T_key_info.algorithm = FSL_KEY_ALG_HMAC; + + memcpy(&KEK_key_info, &T_key_info, sizeof(KEK_key_info)); + KEK_key_info.algorithm = FSL_KEY_ALG_AES; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + T_LENGTH, key_info->userid, + &T_key_info.handle); + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + T_LENGTH, + key_info->userid, &T_key_info.handle); + } + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + KEK_LENGTH, key_info->userid, + &KEK_key_info.handle); + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + KEK_LENGTH, key_info->userid, + &KEK_key_info.handle); + } + + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("do_scc_slot_alloc() failed"); +#endif + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + (void)do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, T_key_info.handle); + + } else { + /* Key goes in user keystore */ + (void)keystore_slot_dealloc(key_info->keystore, + key_info->userid, T_key_info.handle); + } + } else { + slots_allocated = 1; + } + + /* Set up to compute everything except T' ... */ +#ifndef DO_REPEATABLE_WRAP + /* Compute T = RND() */ + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 */ + DESC_KEY_OUT(header, &T_key_info, 0, NULL); +#else + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_load(user_ctx, + T_key_info.userid, + T_key_info.handle, T_block, + T_key_info.key_length); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_load(key_info->keystore, + T_key_info.userid, + T_key_info.handle, + T_block, T_key_info.key_length); + } + + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#endif + + /* Compute KEK = SHA256(T | Ownerid) */ + header = (SAH_HDR_MDHA_SET_MODE_HASH /* #8 */ + ^ sah_insert_mdha_init + ^ sah_insert_mdha_algorithm[FSL_HASH_ALG_SHA256] + ^ sah_insert_mdha_pdata); + /* Input - Start with T */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link1, &T_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Still input - append ownerid */ + ret = sah_Append_Link(user_ctx->mem_util, link1, + (void *)&key_info->userid, + sizeof(key_info->userid), SAH_USES_LINK_DATA); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Output - KEK goes into RED slot */ + ret = sah_Create_Key_Link(user_ctx->mem_util, &link2, &KEK_key_info); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + /* Put the Hash calculation into the chain. */ + ret = sah_Append_Desc(user_ctx->mem_util, &desc_chain, + header, link1, link2); + if (ret != FSL_RETURN_OK_S) { + goto out; + } +#if defined(NEED_CTR_WORKAROUND) + rounded_key_length = ROUND_LENGTH(original_key_length); + key_info->key_length = rounded_key_length; +#else + rounded_key_length = original_key_length; +#endif + /* Compute KEY' = AES-encrypt(KEK, KEY) */ + header = (SAH_HDR_SKHA_SET_MODE_IV_KEY /* #1 */ + ^ sah_insert_skha_mode[FSL_SYM_MODE_CTR] + ^ sah_insert_skha_algorithm[FSL_KEY_ALG_AES] + ^ sah_insert_skha_modulus[FSL_CTR_MOD_128]); + /* Set up KEK as key to use */ + DESC_IN_KEY(header, 0, NULL, &KEK_key_info); + header = SAH_HDR_SKHA_ENC_DEC; + DESC_KEY_OUT(header, key_info, + key_info->key_length, black_key + KEY_PRIME_OFFSET); + + /* Set up flags info */ + black_key[FLAGS_OFFSET] = 0; + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + black_key[FLAGS_OFFSET] |= FLAGS_SW_KEY; + } + + /* Compute and store ICV into Black Key */ + ret = create_icv_calc(user_ctx, &desc_chain, &T_key_info, + black_key, original_key_length, + black_key + ICV_OFFSET); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key" + " flag!\n"); +#endif /*DIAG_SECURITY_FUNC */ + goto out; + } + + /* Now get Sahara to do the work. */ +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Encrypting key with KEK"); +#endif + SAH_SF_EXECUTE(); + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("sah_Descriptor_Chain_Execute() failed"); +#endif + goto out; + } + + /* Compute T' = SLID_encrypt(T); Result goes to Black Key */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_encrypt(user_ctx, + T_key_info.userid, T_key_info.handle, + T_LENGTH, black_key + T_PRIME_OFFSET); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_encrypt(user_ctx, + key_info->keystore, + T_key_info.userid, + T_key_info.handle, + T_LENGTH, + black_key + T_PRIME_OFFSET); + } + + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("do_scc_slot_encrypt() failed"); +#endif + goto out; + } + + out: + key_info->key_length = original_key_length; + + SAH_SF_DESC_CLEAN(); + if (slots_allocated) { + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + (void)do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + T_key_info. + handle); + (void)do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + KEK_key_info. + handle); + } else { + /* Key goes in user keystore */ + (void)keystore_slot_dealloc(key_info->keystore, + key_info->userid, + T_key_info.handle); + (void)keystore_slot_dealloc(key_info->keystore, + key_info->userid, + KEK_key_info.handle); + } + } + + return ret; +} /* wrap */ + +/*! + * Place a key into a protected location for use only by cryptographic + * algorithms. + * + * This only needs to be used to a) unwrap a key, or b) set up a key which + * could be wrapped with a later call to #fsl_shw_extract_key(). Normal + * cleartext keys can simply be placed into #fsl_shw_sko_t key objects with + * #fsl_shw_sko_set_key() and used directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key) +{ + SAH_SF_DCLS; + unsigned original_key_length = key_info->key_length; + unsigned rounded_key_length; + unsigned slot_allocated = 0; + uint32_t old_flags; + + header = SAH_HDR_RNG_GENERATE; /* Desc. #18 for rand */ + + /* TODO: THIS STILL NEEDS TO BE REFACTORED */ + + /* Write operations into SCC memory require word-multiple number of + * bytes. For ACCEPT and CREATE functions, the key length may need + * to be rounded up. Calculate. */ + if (LENGTH_PATCH && (original_key_length & LENGTH_PATCH_MASK) != 0) { + rounded_key_length = original_key_length + LENGTH_PATCH + - (original_key_length & LENGTH_PATCH_MASK); + } else { + rounded_key_length = original_key_length; + } + + SAH_SF_USER_CHECK(); + + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { +#ifdef DIAG_SECURITY_FUNC + ret = FSL_RETURN_BAD_FLAG_S; + LOG_DIAG("Key already established\n"); +#endif + } + + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_alloc(user_ctx, + key_info->key_length, + key_info->userid, + &(key_info->handle)); +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG_ARGS + ("key length: %i, handle: %i, rounded key length: %i", + key_info->key_length, key_info->handle, + rounded_key_length); +#endif + + } else { + /* Key goes in user keystore */ + ret = keystore_slot_alloc(key_info->keystore, + key_info->key_length, + key_info->userid, + &(key_info->handle)); + } + if (ret != FSL_RETURN_OK_S) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Slot allocation failed\n"); +#endif + goto out; + } + slot_allocated = 1; + + key_info->flags |= FSL_SKO_KEY_ESTABLISHED; + switch (establish_type) { + case FSL_KEY_WRAP_CREATE: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creating random key\n"); +#endif + /* Use safe version of key length */ + key_info->key_length = rounded_key_length; + /* Generate descriptor to put random value into */ + DESC_KEY_OUT(header, key_info, 0, NULL); + /* Restore actual, desired key length */ + key_info->key_length = original_key_length; + + old_flags = user_ctx->flags; + /* Now put random value into key */ + SAH_SF_EXECUTE(); + /* Restore user's old flag value */ + user_ctx->flags = old_flags; +#ifdef DIAG_SECURITY_FUNC + if (ret == FSL_RETURN_OK_S) { + LOG_DIAG("ret is ok"); + } else { + LOG_DIAG("ret is not ok"); + } +#endif + break; + + case FSL_KEY_WRAP_ACCEPT: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Accepting plaintext key\n"); +#endif + if (key == NULL) { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("ACCEPT: Red Key is NULL"); +#endif + ret = FSL_RETURN_ERROR_S; + goto out; + } + /* Copy in safe number of bytes of Red key */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_load(user_ctx, + key_info->userid, + key_info->handle, key, + rounded_key_length); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_load(key_info->keystore, + key_info->userid, + key_info->handle, key, + key_info->key_length); + } + break; + + case FSL_KEY_WRAP_UNWRAP: +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Unwrapping wrapped key\n"); +#endif + /* For now, disallow non-blocking calls. */ + if (!(user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { + ret = FSL_RETURN_BAD_FLAG_S; + } else if (key == NULL) { + ret = FSL_RETURN_ERROR_S; + } else { + ret = unwrap(user_ctx, key_info, key); + } + break; + + default: + ret = FSL_RETURN_BAD_FLAG_S; + break; + } /* switch */ + + out: + if (slot_allocated && (ret != FSL_RETURN_OK_S)) { + fsl_shw_return_t scc_err; + + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + scc_err = do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + key_info->handle); + } else { + /* Key goes in user keystore */ + scc_err = keystore_slot_dealloc(key_info->keystore, + key_info->userid, key_info->handle); + } + + key_info->flags &= ~FSL_SKO_KEY_ESTABLISHED; + } + + SAH_SF_DESC_CLEAN(); + + return ret; +} /* fsl_shw_establish_key() */ + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with #fsl_shw_establish_key(). + * + * This function will also release the key (see #fsl_shw_release_key()) so + * that it must be re-established before reuse. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the 48-octet wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + /* For now, only blocking mode calls are supported */ + if (user_ctx->flags & FSL_UCO_BLOCKING_MODE) { + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + ret = wrap(user_ctx, key_info, covered_key); + if (ret != FSL_RETURN_OK_S) { + goto out; + } + + /* Verify that a SW key info really belongs to a SW key */ + if (key_info->flags & FSL_SKO_KEY_SW_KEY) { + /* ret = FSL_RETURN_BAD_FLAG_S; + goto out;*/ + } + + /* Need to deallocate on successful extraction */ + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + ret = do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, key_info->handle); + } else { + /* Key goes in user keystore */ + ret = keystore_slot_dealloc(key_info->keystore, + key_info->userid, key_info->handle); + } + /* Mark key not available in the flags */ + key_info->flags &= + ~(FSL_SKO_KEY_ESTABLISHED | FSL_SKO_KEY_PRESENT); + } + } + +out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + if (key_info->keystore == NULL) { + /* Key goes in system keystore */ + do_system_keystore_slot_dealloc(user_ctx, + key_info->userid, + key_info->handle); + } else { + /* Key goes in user keystore */ + keystore_slot_dealloc(key_info->keystore, + key_info->userid, + key_info->handle); + } + key_info->flags &= ~(FSL_SKO_KEY_ESTABLISHED | + FSL_SKO_KEY_PRESENT); + } + +out: + SAH_SF_DESC_CLEAN(); + + return ret; +} + +fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, uint8_t * key) +{ + SAH_SF_DCLS; + + SAH_SF_USER_CHECK(); + + if (!(key_info->flags & FSL_SKO_KEY_ESTABLISHED) + || !(key_info->flags & FSL_SKO_KEY_SW_KEY)) { + ret = FSL_RETURN_BAD_FLAG_S; + goto out; + } + + if (key_info->keystore == NULL) { + /* Key lives in system keystore */ + ret = do_system_keystore_slot_read(user_ctx, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } else { + /* Key lives in user keystore */ + ret = keystore_slot_read(key_info->keystore, + key_info->userid, + key_info->handle, + key_info->key_length, key); + } + + out: + SAH_SF_DESC_CLEAN(); + + return ret; +} diff --git a/drivers/mxc/security/sahara2/include/adaptor.h b/drivers/mxc/security/sahara2/include/adaptor.h new file mode 100644 index 000000000000..d2cb35b21e4c --- /dev/null +++ b/drivers/mxc/security/sahara2/include/adaptor.h @@ -0,0 +1,113 @@ +/* + * Copyright 2004-2008 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 adaptor.h +* +* @brief The Adaptor component provides an interface to the device +* driver. +* +* Intended to be used by the FSL SHW API, this can also be called directly +*/ + +#ifndef ADAPTOR_H +#define ADAPTOR_H + +#include <sahara.h> + +/*! + * Structure passed during user ioctl() call to submit request. + */ +typedef struct sah_dar { + sah_Desc *desc_addr; /*!< head of descriptor chain */ + uint32_t uco_flags; /*!< copy of fsl_shw_uco flags field */ + uint32_t uco_user_ref; /*!< copy of fsl_shw_uco user_ref */ + uint32_t result; /*!< result of descriptor chain request */ + struct sah_dar *next; /*!< for driver use */ +} sah_dar_t; + +/*! + * Structure passed during user ioctl() call to Register a user + */ +typedef struct sah_register { + uint32_t pool_size; /*!< max number of outstanding requests possible */ + uint32_t result; /*!< result of registration request */ +} sah_register_t; + +/*! + * Structure passed during ioctl() call to request SCC operation + */ +typedef struct scc_data { + uint32_t length; /*!< length of data */ + uint8_t *in; /*!< input data */ + uint8_t *out; /*!< output data */ + unsigned direction; /*!< encrypt or decrypt */ + fsl_shw_sym_mode_t crypto_mode; /*!< CBC or EBC */ + uint8_t *init_vector; /*!< initialization vector or NULL */ +} scc_data_t; + +/*! + * Structure passed during user ioctl() calls to manage stored keys and + * stored-key slots. + */ +typedef struct scc_slot_t { + uint64_t ownerid; /*!< Owner's id to check/set permissions */ + uint32_t key_length; /*!< Length of key */ + uint32_t slot; /*!< Slot to operation on, or returned slot + number. */ + uint8_t *key; /*!< User-memory pointer to key value */ + fsl_shw_return_t code; /*!< API return code from operation */ +} scc_slot_t; + +/* + * Structure passed during user ioctl() calls to manage data stored in secure + * partitions. + */ +typedef struct scc_region_t { + uint32_t partition_base; /*!< User virtual address of the + partition base. */ + uint32_t offset; /*!< Offset from the start of the + partition where the cleartext data + is located. */ + uint32_t length; /*!< Length of the region to be + operated on */ + uint8_t *black_data; /*!< User virtual address of any black + (encrypted) data. */ + fsl_shw_cypher_mode_t cypher_mode; /*!< Cypher mode to use in an encryt/ + decrypt operation. */ + uint32_t IV[4]; /*!< Intialization vector to use in an + encrypt/decrypt operation. */ + fsl_shw_return_t code; /*!< API return code from operation */ +} scc_region_t; + +/* + * Structure passed during user ioctl() calls to manage secure partitions. + */ +typedef struct scc_partition_info_t { + uint32_t user_base; /**< Userspace pointer to base of partition */ + uint32_t permissions; /**< Permissions to give the partition (only + used in call to _DROP_PERMS) */ + fsl_shw_partition_status_t status; /*!< Status of the partition */ +} scc_partition_info_t; + +fsl_shw_return_t adaptor_Exec_Descriptor_Chain(sah_Head_Desc * dar, + fsl_shw_uco_t * uco); +fsl_shw_return_t sah_get_results(sah_results * arg, fsl_shw_uco_t * uco); +fsl_shw_return_t sah_register(fsl_shw_uco_t * user_ctx); +fsl_shw_return_t sah_deregister(fsl_shw_uco_t * user_ctx); +fsl_shw_return_t get_capabilities(fsl_shw_uco_t * user_ctx, + fsl_shw_pco_t *capabilities); + +#endif /* ADAPTOR_H */ + +/* End of adaptor.h */ diff --git a/drivers/mxc/security/sahara2/include/diagnostic.h b/drivers/mxc/security/sahara2/include/diagnostic.h new file mode 100644 index 000000000000..57f84d4cbb05 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/diagnostic.h @@ -0,0 +1,116 @@ +/* + * Copyright 2004-2009 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 diagnostic.h +* +* @brief Macros for outputting kernel and user space diagnostics. +*/ + +#ifndef DIAGNOSTIC_H +#define DIAGNOSTIC_H + +#ifndef __KERNEL__ /* linux flag */ +#include <stdio.h> +#endif +#include "fsl_platform.h" + +#if defined(FSL_HAVE_SAHARA2) || defined(FSL_HAVE_SAHARA4) +#define DEV_NAME "sahara" +#elif defined(FSL_HAVE_RNGA) || defined(FSL_HAVE_RNGB) || \ + defined(FSL_HAVE_RNGC) +#define DEV_NAME "shw" +#endif + +/*! +******************************************************************** +* @brief This macro logs diagnostic messages to stderr. +* +* @param diag String that must be logged, char *. +* +* @return void +* +*/ +//#if defined DIAG_SECURITY_FUNC || defined DIAG_ADAPTOR +#define LOG_DIAG(diag) \ +({ \ + const char* fname = strrchr(__FILE__, '/'); \ + \ + sah_Log_Diag(fname ? fname+1 : __FILE__, __LINE__, diag); \ +}) + +#ifdef __KERNEL__ + +#define LOG_DIAG_ARGS(fmt, ...) \ +({ \ + const char* fname = strrchr(__FILE__, '/'); \ + os_printk(KERN_ALERT "%s:%i: " fmt "\n", \ + fname ? fname+1 : __FILE__, \ + __LINE__, \ + __VA_ARGS__); \ +}) + +#else + +#define LOG_DIAG_ARGS(fmt, ...) \ +({ \ + const char* fname = strrchr(__FILE__, '/'); \ + printf("%s:%i: " fmt "\n", \ + fname ? fname+1 : __FILE__, \ + __LINE__, \ + __VA_ARGS__); \ +}) + +#ifndef __KERNEL__ +void sah_Log_Diag(char *source_name, int source_line, char *diag); +#endif +#endif /* if define DIAG_SECURITY_FUNC ... */ + +#ifdef __KERNEL__ +/*! +******************************************************************** +* @brief This macro logs kernel diagnostic messages to the kernel +* log. +* +* @param diag String that must be logged, char *. +* +* @return As for printf() +*/ +#if 0 +#if defined(DIAG_DRV_IF) || defined(DIAG_DRV_QUEUE) || \ + defined(DIAG_DRV_STATUS) || defined(DIAG_DRV_INTERRUPT) || \ + defined(DIAG_MEM) || defined(DIAG_SECURITY_FUNC) || defined(DIAG_ADAPTOR) +#endif +#endif + +#define LOG_KDIAG_ARGS(fmt, ...) \ +({ \ + os_printk (KERN_ALERT "%s (%s:%i): " fmt "\n", \ + DEV_NAME, strrchr(__FILE__, '/')+1, __LINE__, __VA_ARGS__); \ +}) + +#define LOG_KDIAG(diag) \ + os_printk (KERN_ALERT "%s (%s:%i): %s\n", \ + DEV_NAME, strrchr(__FILE__, '/')+1, __LINE__, diag); + +#define sah_Log_Diag(n, l, d) \ + os_printk(KERN_ALERT "%s:%i: %s\n", n, l, d) + +#else /* not KERNEL */ + +#define sah_Log_Diag(n, l, d) \ + printf("%s:%i: %s\n", n, l, d) + +#endif /* __KERNEL__ */ + +#endif /* DIAGNOSTIC_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_platform.h b/drivers/mxc/security/sahara2/include/fsl_platform.h new file mode 100644 index 000000000000..e7a6fd1718b2 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_platform.h @@ -0,0 +1,161 @@ +/* + * Copyright 2004-2009 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 fsl_platform.h + * + * Header file to isolate code which might be platform-dependent + */ + +#ifndef FSL_PLATFORM_H +#define FSL_PLATFORM_H + +#ifdef __KERNEL__ +#include "portable_os.h" +#endif + +#if defined(FSL_PLATFORM_OTHER) + +/* Have Makefile or other method of setting FSL_HAVE_* flags */ + +#elif defined(CONFIG_ARCH_MX3) /* i.MX31 */ + +#define FSL_HAVE_SCC +#define FSL_HAVE_RTIC +#define FSL_HAVE_RNGA + +#elif defined(CONFIG_ARCH_MX21) + +#define FSL_HAVE_HAC +#define FSL_HAVE_RNGA +#define FSL_HAVE_SCC + +#elif defined(CONFIG_ARCH_MX25) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGB +#define FSL_HAVE_RTIC3 +#define FSL_HAVE_DRYICE + +#elif defined(CONFIG_ARCH_MX27) + +#define FSL_HAVE_SAHARA2 +#define SUBMIT_MULTIPLE_DARS +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define ALLOW_LLO_DESCRIPTORS + +#elif defined(CONFIG_ARCH_MX35) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC +#define FSL_HAVE_RTIC + +#elif defined(CONFIG_ARCH_MX37) + +#define FSL_HAVE_SCC2 +#define FSL_HAVE_RNGC +#define FSL_HAVE_RTIC2 +#define FSL_HAVE_SRTC + +#elif defined(CONFIG_ARCH_MX51) + +#define FSL_HAVE_SCC2 +#define FSL_HAVE_SAHARA4 +#define FSL_HAVE_RTIC3 +#define FSL_HAVE_SRTC +#define NO_RESEED_WORKAROUND +#define NEED_CTR_WORKAROUND +#define USE_S2_CCM_ENCRYPT_CHAIN +#define USE_S2_CCM_DECRYPT_CHAIN +#define ALLOW_LLO_DESCRIPTORS + +#elif defined(CONFIG_ARCH_MXC91131) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC +#define FSL_HAVE_HAC + +#elif defined(CONFIG_ARCH_MXC91221) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC +#define FSL_HAVE_RTIC2 + +#elif defined(CONFIG_ARCH_MXC91231) + +#define FSL_HAVE_SAHARA2 +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define NO_OUTPUT_1K_CROSSING + +#elif defined(CONFIG_ARCH_MXC91311) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGC + +#elif defined(CONFIG_ARCH_MXC91314) + +#define FSL_HAVE_SCC +#define FSL_HAVE_SAHAR4 +#define FSL_HAVE_RTIC3 +#define NO_RESEED_WORKAROUND +#define NEED_CTR_WORKAROUND +#define USE_S2_CCM_ENCRYPT_CHAIN +#define USE_S2_CCM_DECRYPT_CHAIN +#define ALLOW_LLO_DESCRIPTORS + +#elif defined(CONFIG_ARCH_MXC91321) + +#define FSL_HAVE_SAHARA2 +#define FSL_HAVE_RTIC +#define FSL_HAVE_SCC +#define SCC_CLOCK_NOT_GATED +#define NO_OUTPUT_1K_CROSSING + +#elif defined(CONFIG_ARCH_MXC92323) + +#define FSL_HAVE_SCC2 +#define FSL_HAVE_SAHARA4 +#define FSL_HAVE_PKHA +#define FSL_HAVE_RTIC2 +#define NO_1K_CROSSING +#define NO_RESEED_WORKAROUND +#define NEED_CTR_WORKAROUND +#define USE_S2_CCM_ENCRYPT_CHAIN +#define USE_S2_CCM_DECRYPT_CHAIN +#define ALLOW_LLO_DESCRIPTORS + + +#elif defined(CONFIG_ARCH_MXC91331) + +#define FSL_HAVE_SCC +#define FSL_HAVE_RNGA +#define FSL_HAVE_HAC +#define FSL_HAVE_RTIC + +#elif defined(CONFIG_8548) + +#define FSL_HAVE_SEC2x + +#elif defined(CONFIG_MPC8374) + +#define FSL_HAVE_SEC3x + +#else + +#error UNKNOWN_PLATFORM + +#endif /* platform checks */ + +#endif /* FSL_PLATFORM_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_shw.h b/drivers/mxc/security/sahara2/include/fsl_shw.h new file mode 100644 index 000000000000..8f0159bef71a --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_shw.h @@ -0,0 +1,2515 @@ +/* + * Copyright 2004-2009 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 + */ + +/* + * NOTE TO MAINTAINERS: Although this header file is *the* header file to be + * #include'd by FSL SHW programs, it does not itself make any definitions for + * the API. Instead, it uses the fsl_platform.h file and / or compiler + * environment variables to determine which actual driver header file to + * include. This allows different implementations to contain different + * implementations of the various objects, macros, etc., or even to change + * which functions are macros and which are not. + */ + +/*! + * @file fsl_shw.h + * + * @brief Definition of the Freescale Security Hardware API. + * + * See @ref index for an overview of the API. + */ + +/*! + * @if USE_MAINPAGE + * @mainpage Common API for Freescale Security Hardware (FSL SHW API) + * @endif + * + * @section intro_sec Introduction + * + * This is the interface definition for the Freescale Security Hardware API + * (FSL SHW API) for User Mode and Kernel Mode to access Freescale Security + * Hardware components for cryptographic acceleration. The API is intended to + * provide cross-platform access to security hardware components of Freescale. + * + * This documentation has not been approved, and should not be taken to + * mean anything definite about future direction. + * + * Some example code is provided to give some idea of usage of this API. + * + * Note: This first version has been defined around the capabilities of the + * Sahara2 cryptographic accelerator, and may be expanded in the future to + * provide support for other platforms. The Platform Capabilities Object is + * intended as a way to allow programs to adapt to different platforms. + * + * The i.MX25 is an example of a platform without a SAHARA but yet has + * capabilities supported by this API. These include #fsl_shw_get_random() and + * #fsl_shw_add_entropy(), and the use of Triple-DES (TDEA) cipher algorithm + * (with no checking of key parity supported) in ECB and CBC modes with @ref + * sym_sec. See also the @ref di_sec for information on key handling, and @ref + * td_sec for detection of Tamper Events. Only the random functions are + * available from user space on this platform. + * + * @section usr_ctx The User Context + * + * The User Context Object (#fsl_shw_uco_t) controls the interaction between + * the user program and the API. It is initialized as part of user + * registration (#fsl_shw_register_user()), and is part of every interaction + * thereafter. + * + * @section pf_sec Platform Capabilities + * + * Since this API is not tied to one specific type of hardware or even one + * given version of a given type of hardware, the platform capabilities object + * could be used by a portable program to make choices about using software + * instead of hardware for certain operations. + * + * See the #fsl_shw_pco_t, returned by #fsl_shw_get_capabilities(). + * + * @ref pcoops are provided to query its contents. + * + * + * @section sym_sec Symmetric-Key Encryption and Decryption + * + * Symmetric-Key encryption support is provided for the block cipher algorithms + * AES, DES, and Triple DES. Modes supported are #FSL_SYM_MODE_ECB, + * #FSL_SYM_MODE_CBC, and #FSL_SYM_MODE_CTR, though not necessarily all modes + * for all algorithms. There is also support for the stream cipher algorithm + * commonly known as ARC4. + * + * Encryption and decryption are performed by using the functions + * #fsl_shw_symmetric_encrypt() and #fsl_shw_symmetric_decrypt(), respectively. + * There are two objects which provide information about the operation of these + * functions. They are the #fsl_shw_sko_t, to provide key and algorithm + * information; and the #fsl_shw_scco_t, to provide (and store) initial context + * or counter value information. + * + * CCM is not supported by these functions. For information CCM support, see + * @ref cmb_sec. + * + * + * @section hash_sec Cryptographic Hashing + * + * Hashing is performed by fsl_shw_hash(). Control of the function is through + * flags in the #fsl_shw_hco_t. The algorithms which are + * supported are listed in #fsl_shw_hash_alg_t. + * + * The hashing function works on octet streams. If a user application needs to + * hash a bitstream, it will need to do its own padding of the last block. + * + * + * @section hmac_sec Hashed Message Authentication Codes + * + * An HMAC is a method of combining a hash and a key so that a message cannot + * be faked by a third party. + * + * The #fsl_shw_hmac() can be used by itself for one-shot or multi-step + * operations, or in combination with #fsl_shw_hmac_precompute() to provide the + * ability to compute and save the beginning hashes from a key one time, and + * then use #fsl_shw_hmac() to calculate an HMAC on each message as it is + * processed. + * + * The maximum key length which is directly supported by this API is 64 octets. + * If a longer key size is needed for HMAC, the user will have to hash the key + * and present the digest value as the key to be used by the HMAC functions. + * + * + * @section rnd_sec Random Numbers + * + * Support is available for acquiring random values from a + * cryptographically-strong random number generator. See + * #fsl_shw_get_random(). The function #fsl_shw_add_entropy() may be used to + * add entropy to the random number generator. + * + * + * @section cmb_sec Combined Cipher and Authentication + * + * Some schemes require that messages be encrypted and that they also have an + * authentication code associated with the message. The function + * #fsl_shw_gen_encrypt() will generate the authentication code and encrypt the + * message. + * + * Upon receipt of such a message, the message must be decrypted and the + * authentication code validated. The function + * #fsl_shw_auth_decrypt() will perform these steps. + * + * Only AES-CCM is supported. + * + * + * @section wrap_sec Wrapped Keys + * + * On platforms with a Secure Memory, the function #fsl_shw_establish_key() can + * be used to place a key into the System Keystore. This key then can be used + * directly by the cryptographic hardware. It later then be wrapped + * (cryptographically obscured) by #fsl_shw_extract_key() and stored for later + * use. If a software key (#FSL_SKO_KEY_SW_KEY) was established, then its + * value can be retrieved with a call to #fsl_shw_read_key(). + * + * The wrapping and unwrapping functions provide security against unauthorized + * use and detection of tampering. + * + * The functions can also be used with a User Keystore. + * + * @section smalloc_sec Secure Memory Allocation + * + * On platforms with multiple partitions of Secure Memory, the function + * #fsl_shw_smalloc() can be used to acquire a partition for private use. The + * function #fsl_shw_diminish_perms() can then be used to revoke specific + * permissions on the partition, and #fsl_shw_sfree() can be used to release the + * partition. + * + * @section keystore_sec User Keystore + * + * User Keystore functionality is defined in fsl_shw_keystore.h. See @ref + * user_keystore for details. This is not supported on platforms without SCC2. + * + * @section di_sec Hardware key-select extensions - DryIce + * + * Some platforms have a component called DryIce which allows the software to + * control which key will be used by the secure memory encryption hardware. + * The choices are the secret per-chip Fused (IIM) Key, an unknown, hardware- + * generated Random Key, a software-written Programmed Key, or the IIM Key in + * combination with one of the others. #fsl_shw_pco_check_pk_supported() can + * be used to determine whether this feature is available on the platform. + * The rest of this section will explain the symmetric ciphering and key + * operations which are available on such a platform. + * + * The function #fsl_shw_sko_init_pf_key() will set up a Secret Key Object to + * refer to one of the system's platform keys. All keys which reference a + * platform key must use this initialization function, including a user- + * provided key value. Keys which are intended for software encryption must + * use #fsl_shw_sko_init(). + * + * To change the setting of the Programmed Key of the DryIce module, + * #fsl_shw_establish_key() must be called with a platform key object of type + * #FSL_SHW_PF_KEY_PRG or #FSL_SHW_PF_KEY_IIM_PRG. The key will be go + * into the PK register of DryIce and not to the keystore. Any symmetric + * operation which references either #FSL_SHW_PF_KEY_PRG or + * #FSL_SHW_PF_KEY_IIM_PRG will use the current PK value (possibly modified by + * the secret fused IIM key). Before the Flatform Key can be changed, a call to + * #fsl_shw_release_key() or #fsl_shw_extract_key() must be made. Neither + * function will change the value in the PK registers, and further ciphering + * can take place. + * + * When #fsl_shw_establish_key() is called to change the PK value, a plaintext + * key can be passed in with the #FSL_KEY_WRAP_ACCEPT argument or a previously + * wrapped key can be passed in with the #FSL_KEY_WRAP_UNWRAP argument. If + * #FSL_KEY_WRAP_CREATE is passed in, then a random value will be loaded into + * the PK register. The PK value can be wrapped by a call to + * #fsl_shw_extract_key() for later use with the #FSL_KEY_WRAP_UNWRAP argument. + * + * As an alternative to using only the fused key for @ref wrap_sec, + * #fsl_shw_uco_set_wrap_key() can be used to select either the random key or + * the random key with the fused key as the key which will be used to protect + * the one-time value used to wrap the key. This allows for these + * wrapped keys to be dependent upon and therefore unrecoverable after a tamper + * event causes the erasure of the DryIce Random Key register. + * + * The software can request that the hardware generate a (new) Random Key for + * DryIce by calling #fsl_shw_gen_random_pf_key(). + * + * + * @section td_sec Device Tamper-Detection + * + * Some platforms have a component which can detect certain types of tampering + * with the hardware. #fsl_shw_read_tamper_event() API will allow the + * retrieval of the type of event which caused a tamper-detection failure. + * + */ + +/*! @defgroup glossary Glossary + * + * @li @b AES - Advanced Encryption Standard - An NIST-created block cipher + * originally knowns as Rijndael. + * @li @b ARC4 - ARCFOUR - An S-Box-based OFB mode stream cipher. + * @li @b CBC - Cipher-Block Chaining - Each encrypted block is XORed with the + * result of the previous block's encryption. + * @li @b CCM - A way of combining CBC and CTR to perform cipher and + * authentication. + * @li @b ciphertext - @a plaintext which has been encrypted in some fashion. + * @li @b context - Information on the state of a cryptographic operation, + * excluding any key. This could include IV, Counter Value, or SBox. + * @li @b CTR - A mode where a counter value is encrypted and then XORed with + * the data. After each block, the counter value is incremented. + * @li @b DES - Data Encryption Standard - An 8-octet-block cipher. + * @li @b ECB - Electronic Codebook - A straight encryption/decryption of the + * data. + * @li @b hash - A cryptographically strong one-way function performed on data. + * @li @b HMAC - Hashed Message Authentication Code - A key-dependent one-way + * hash result, used to verify authenticity of a message. The equation + * for an HMAC is hash((K + A) || hash((K + B) || msg)), where K is the + * key, A is the constant for the outer hash, B is the constant for the + * inner hash, and hash is the hashing function (MD5, SHA256, etc). + * @li @b IPAD - In an HMAC operation, the context generated by XORing the key + * with a constant and then hashing that value as the first block of the + * inner hash. + * @li @b IV - An "Initial Vector" or @a context for modes like CBC. + * @li @b MAC - A Message Authentication Code. HMAC, hashing, and CCM all + * produce a MAC. + * @li @b mode - A way of using a cryptographic algorithm. See ECB, CBC, etc. + * @li @b MD5 - Message Digest 5 - A one-way hash function. + * @li @b plaintext - Data which has not been encrypted, or has been decrypted + * from @a ciphertext. + * @li @b OPAD - In an HMAC operation, the context generated by XORing the key + * with a constant and then hashing that value as the first block of the + * outer hash. + * @li @b SHA - Secure Hash Algorithm - A one-way hash function. + * @li @b TDES - AKA @b 3DES - Triple Data Encryption Standard - A method of + * using two or three keys and DES to perform three operations (encrypt + * decrypt encrypt) to create a new algorithm. + * @li @b XOR - Exclusive-OR. A Boolean arithmetic function. + * @li @b Wrapped value - A (key) which has been encrypted into an opaque datum + * which cannot be unwrapped (decrypted) for use except by an authorized + * user. Once created, the key is never visible, but may be used for + * other cryptographic operations. + */ + +#ifndef FSL_SHW_H +#define FSL_SHW_H + +/* Set FSL_HAVE_* flags */ + +#include "fsl_platform.h" + +#ifndef API_DOC + +#if defined(FSL_HAVE_SAHARA2) || defined(FSL_HAVE_SAHARA4) + +#include "sahara.h" + +#else + +#if defined(FSL_HAVE_RNGA) || defined(FSL_HAVE_RNGB) || defined(FSL_HAVE_RNGC) + +#include "rng_driver.h" + +#else + +#error FSL_SHW_API_platform_not_recognized + +#endif + +#endif /* HAVE SAHARA */ + +#else /* API_DOC */ + +#include <inttypes.h> /* for uint32_t, etc. */ +#include <stdio.h> /* Mainly for definition of NULL !! */ + +/* These groups will appear in the order in which they are defined. */ + +/*! + * @defgroup strgrp Objects + * + * These objects are used to pass information into and out of the API. Through + * flags and other settings, they control the behavior of the @ref opfuns. + * + * They are manipulated and queried by use of the various access functions. + * There are different sets defined for each object. See @ref objman. + */ + +/*! + * @defgroup consgrp Enumerations and other Constants + * + * This collection of symbols comprise the values which can be passed into + * various functions to control how the API will work. + */ + +/*! @defgroup opfuns Operational Functions + * + * These functions request that the underlying hardware perform cryptographic + * operations. They are the heart of the API. + */ + +/****** Organization the Object Operations under one group ! **********/ +/*! @defgroup objman Object-Manipulation Operations + * + */ +/*! @addtogroup objman + @{ */ +/*! + * @defgroup pcoops Platform Context Object Operations + * + * The Platform Context object is "read-only", so only query operations are + * provided for it. It is returned by the #fsl_shw_get_capabilities() + * function. + */ + +/*! @defgroup ucoops User Context Operations + * + * These operations should be the only access to the #fsl_shw_uco_t + * type/struct, as the internal members of the object are subject to change. + * The #fsl_shw_uco_init() function must be called before any other use of the + * object. + */ + +/*! + * @defgroup rops Result Object Operations + * + * As the Result Object contains the result of one of the @ref opfuns. The + * manipulations provided are query-only. No initialization is needed for this + * object. + */ + +/*! + * @defgroup skoops Secret Key Object Operations + * + * These operations should be the only access to the #fsl_shw_sko_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup ksoops Keystore Object Operations + * + * These operations should be the only access to the #fsl_shw_kso_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup hcops Hash Context Object Operations + * + * These operations should be the only access to the #fsl_shw_hco_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup hmcops HMAC Context Object Operations + * + * These operations should be the only access to the #fsl_shw_hmco_t + * type/struct, as the internal members of that object are subject to change. + */ + +/*! + * @defgroup sccops Symmetric Cipher Context Operations + * + * These operations should be the only access to the #fsl_shw_scco_t + * type/struct, as the internal members of that object are subject to change + */ + +/*! @defgroup accoops Authentication-Cipher Context Object Operations + * + * These functions operate on a #fsl_shw_acco_t. Their purpose is to set + * flags, fields, etc., in order to control the operation of + * #fsl_shw_gen_encrypt() and #fsl_shw_auth_decrypt(). + */ + + /* @} *//************ END GROUPING of Object Manipulations *****************/ + +/*! @defgroup miscfuns Miscellaneous Functions + * + * These functions are neither @ref opfuns nor @ref objman. Their behavior + * does not depend upon the flags in the #fsl_shw_uco_t, yet they may involve + * more interaction with the library and the kernel than simply querying an + * object. + */ + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! @addtogroup consgrp + @{ */ + +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + * + * These flags describe how the @ref opfuns will operate. + */ +typedef enum fsl_shw_user_ctx_flags_t { + /*! + * API will block the caller until operation completes. The result will be + * available in the return code. If this is not set, user will have to get + * results using #fsl_shw_get_results(). + */ + FSL_UCO_BLOCKING_MODE, + /*! + * User wants callback (at the function specified with + * #fsl_shw_uco_set_callback()) when the operation completes. This flag is + * valid only if #FSL_UCO_BLOCKING_MODE is not set. + */ + FSL_UCO_CALLBACK_MODE, + /*! Do not free descriptor chain after driver (adaptor) finishes */ + FSL_UCO_SAVE_DESC_CHAIN, + /*! + * User has made at least one request with callbacks requested, so API is + * ready to handle others. + */ + FSL_UCO_CALLBACK_SETUP_COMPLETE, + /*! + * (virtual) pointer to descriptor chain is completely linked with physical + * (DMA) addresses, ready for the hardware. This flag should not be used + * by FSL SHW API programs. + */ + FSL_UCO_CHAIN_PREPHYSICALIZED, + /*! + * The user has changed the context but the changes have not been copied to + * the kernel driver. + */ + FSL_UCO_CONTEXT_CHANGED, + /*! Internal Use. This context belongs to a user-mode API user. */ + FSL_UCO_USERMODE_USER, +} fsl_shw_user_ctx_flags_t; + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +typedef enum fsl_shw_return_t { + /*! + * No error. As a function return code in Non-blocking mode, this may + * simply mean that the operation was accepted for eventual execution. + */ + FSL_RETURN_OK_S = 0, + /*! Failure for non-specific reason. */ + FSL_RETURN_ERROR_S, + /*! + * Operation failed because some resource was not able to be allocated. + */ + FSL_RETURN_NO_RESOURCE_S, + /*! Crypto algorithm unrecognized or improper. */ + FSL_RETURN_BAD_ALGORITHM_S, + /*! Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_MODE_S, + /*! Flag setting unrecognized or inconsistent. */ + FSL_RETURN_BAD_FLAG_S, + /*! Improper or unsupported key length for algorithm. */ + FSL_RETURN_BAD_KEY_LENGTH_S, + /*! Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_KEY_PARITY_S, + /*! + * Improper or unsupported data length for algorithm or internal buffer. + */ + FSL_RETURN_BAD_DATA_LENGTH_S, + /*! Authentication / Integrity Check code check failed. */ + FSL_RETURN_AUTH_FAILED_S, + /*! A memory error occurred. */ + FSL_RETURN_MEMORY_ERROR_S, + /*! An error internal to the hardware occurred. */ + FSL_RETURN_INTERNAL_ERROR_S, + /*! ECC detected Point at Infinity */ + FSL_RETURN_POINT_AT_INFINITY_S, + /*! ECC detected No Point at Infinity */ + FSL_RETURN_POINT_NOT_AT_INFINITY_S, + /*! GCD is One */ + FSL_RETURN_GCD_IS_ONE_S, + /*! GCD is not One */ + FSL_RETURN_GCD_IS_NOT_ONE_S, + /*! Candidate is Prime */ + FSL_RETURN_PRIME_S, + /*! Candidate is not Prime */ + FSL_RETURN_NOT_PRIME_S, + /*! N register loaded improperly with even value */ + FSL_RETURN_EVEN_MODULUS_ERROR_S, + /*! Divisor is zero. */ + FSL_RETURN_DIVIDE_BY_ZERO_ERROR_S, + /*! Bad Exponent or Scalar value for Point Multiply */ + FSL_RETURN_BAD_EXPONENT_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_OSCILLATOR_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_STATISTICS_ERROR_S, +} fsl_shw_return_t; + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t { + FSL_KEY_ALG_HMAC, /*!< Key will be used to perform an HMAC. Key + size is 1 to 64 octets. Block size is 64 + octets. */ + FSL_KEY_ALG_AES, /*!< Advanced Encryption Standard (Rijndael). + Block size is 16 octets. Key size is 16 + octets. (The single choice of key size is a + Sahara platform limitation.) */ + FSL_KEY_ALG_DES, /*!< Data Encryption Standard. Block size is + 8 octets. Key size is 8 octets. */ + FSL_KEY_ALG_TDES, /*!< 2- or 3-key Triple DES. Block size is 8 + octets. Key size is 16 octets for 2-key + Triple DES, and 24 octets for 3-key. */ + FSL_KEY_ALG_ARC4 /*!< ARC4. No block size. Context size is 259 + octets. Allowed key size is 1-16 octets. + (The choices for key size are a Sahara + platform limitation.) */ +} fsl_shw_key_alg_t; + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t { + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg_t { + FSL_HASH_ALG_MD5, /*!< MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_SHA1, /*!< SHA-1 (aka SHA or SHA-160) algorithm. + Digest is 20 octets. */ + FSL_HASH_ALG_SHA224, /*!< SHA-224 algorithm. Digest is 28 octets, + though context is 32 octets. */ + FSL_HASH_ALG_SHA256 /*!< SHA-256 algorithm. Digest is 32 + octets. */ +} fsl_shw_hash_alg_t; + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t { + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL, +} fsl_shw_acc_mode_t; + +/*! + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t { + FSL_KEY_WRAP_CREATE, /*!< Generate a key from random values. */ + FSL_KEY_WRAP_ACCEPT, /*!< Use the provided clear key. */ + FSL_KEY_WRAP_UNWRAP /*!< Unwrap a previously wrapped key. */ +} fsl_shw_key_wrap_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + * + * These may be combined by ORing them together. See #fsl_shw_hco_set_flags() + * and #fsl_shw_hco_clear_flags(). + */ +typedef enum fsl_shw_hash_ctx_flags_t { + FSL_HASH_FLAGS_INIT = 1, /*!< Context is empty. Hash is started + from scratch, with a message-processed + count of zero. */ + FSL_HASH_FLAGS_SAVE = 2, /*!< Retrieve context from hardware after + hashing. If used with the + #FSL_HASH_FLAGS_FINALIZE flag, the final + digest value will be saved in the + object. */ + FSL_HASH_FLAGS_LOAD = 4, /*!< Place context into hardware before + hashing. */ + FSL_HASH_FLAGS_FINALIZE = 8, /*!< PAD message and perform final digest + operation. If user message is + pre-padded, this flag should not be + used. */ +} fsl_shw_hash_ctx_flags_t; + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t { + FSL_HMAC_FLAGS_INIT = 1, /*!< Message context is empty. HMAC is + started from scratch (with key) or from + precompute of inner hash, depending on + whether + #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is + set. */ + FSL_HMAC_FLAGS_SAVE = 2, /*!< Retrieve ongoing context from hardware + after hashing. If used with the + #FSL_HMAC_FLAGS_FINALIZE flag, the final + digest value (HMAC) will be saved in the + object. */ + FSL_HMAC_FLAGS_LOAD = 4, /*!< Place ongoing context into hardware + before hashing. */ + FSL_HMAC_FLAGS_FINALIZE = 8, /*!< PAD message and perform final HMAC + operations of inner and outer hashes. */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16 /*!< This means that the context + contains precomputed inner and outer + hash values. */ +} fsl_shw_hmac_ctx_flags_t; + +/*! + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t { + /*! + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /*! + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /*! + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /*! + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + +/*! + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t { + FSL_SKO_KEY_IGNORE_PARITY = 1, /*!< If algorithm is DES or 3DES, do not + validate the key parity bits. */ + FSL_SKO_KEY_PRESENT = 2, /*!< Clear key is present in the object. */ + FSL_SKO_KEY_ESTABLISHED = 4, /*!< Key has been established for use. This + feature is not available for all + platforms, nor for all algorithms and + modes. */ + FSL_SKO_KEY_SW_KEY = 8, /*!< This key is for software use, and can + be copied out of a keystore by its owner. + The default is that they key is available + only for hardware (or security driver) + use. */ +} fsl_shw_key_flags_t; + +/*! + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + +/*! + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t { + FSL_ACCO_CTX_INIT = 1, /*!< Initialize Context(s) */ + FSL_ACCO_CTX_LOAD = 2, /*!< Load intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, /*!< Save intermediate context(s). + This flag is unsupported. */ + FSL_ACCO_CTX_FINALIZE = 8, /*!< Create MAC during this operation. */ + FSL_ACCO_NIST_CCM = 16, /*!< Formatting of CCM input data is + performed by calls to + #fsl_shw_ccm_nist_format_ctr_and_iv() and + #fsl_shw_ccm_nist_update_ctr_and_iv(). */ +} fsl_shw_auth_ctx_flags_t; + +/*! + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod_t { + FSL_CTR_MOD_8, /*!< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /*!< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /*!< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /*!< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /*!< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /*!< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /*!< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /*!< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /*!< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /*!< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /*!< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /*!< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /*!< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /*!< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /*!< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /*!< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + +/*! + * Permissions flags for Secure Partitions + * + * They currently map directly to the SCC2 hardware values, but this is not + * guarinteed behavior. + */ +typedef enum fsl_shw_permission_t { +/*! SCM Access Permission: Do not zeroize/deallocate partition on SMN Fail state */ + FSL_PERM_NO_ZEROIZE, +/*! SCM Access Permission: Enforce trusted key read in */ + FSL_PERM_TRUSTED_KEY_READ, +/*! SCM Access Permission: Ignore Supervisor/User mode in permission determination */ + FSL_PERM_HD_S, +/*! SCM Access Permission: Allow Read Access to Host Domain */ + FSL_PERM_HD_R, +/*! SCM Access Permission: Allow Write Access to Host Domain */ + FSL_PERM_HD_W, +/*! SCM Access Permission: Allow Execute Access to Host Domain */ + FSL_PERM_HD_X, +/*! SCM Access Permission: Allow Read Access to Trusted Host Domain */ + FSL_PERM_TH_R, +/*! SCM Access Permission: Allow Write Access to Trusted Host Domain */ + FSL_PERM_TH_W, +/*! SCM Access Permission: Allow Read Access to Other/World Domain */ + FSL_PERM_OT_R, +/*! SCM Access Permission: Allow Write Access to Other/World Domain */ + FSL_PERM_OT_W, +/*! SCM Access Permission: Allow Execute Access to Other/World Domain */ + FSL_PERM_OT_X, +} fsl_shw_permission_t; + +/*! + * Select the cypher mode to use for partition cover/uncover operations. + * + * They currently map directly to the values used in the SCC2 driver, but this + * is not guarinteed behavior. + */ +typedef enum fsl_shw_cypher_mode_t { + FSL_SHW_CYPHER_MODE_ECB, /*!< ECB mode */ + FSL_SHW_CYPHER_MODE_CBC, /*!< CBC mode */ +} fsl_shw_cypher_mode_t; + +/*! + * Which platform key should be presented for cryptographic use. + */ +typedef enum fsl_shw_pf_key_t { + FSL_SHW_PF_KEY_IIM, /*!< Present fused IIM key */ + FSL_SHW_PF_KEY_PRG, /*!< Present Program key */ + FSL_SHW_PF_KEY_IIM_PRG, /*!< Present IIM ^ Program key */ + FSL_SHW_PF_KEY_IIM_RND, /*!< Present Random key */ + FSL_SHW_PF_KEY_RND, /*!< Present IIM ^ Random key */ +} fsl_shw_pf_key_t; + +/*! + * The various security tamper events + */ +typedef enum fsl_shw_tamper_t { + FSL_SHW_TAMPER_NONE, /*!< No error detected */ + FSL_SHW_TAMPER_WTD, /*!< wire-mesh tampering det */ + FSL_SHW_TAMPER_ETBD, /*!< ext tampering det: input B */ + FSL_SHW_TAMPER_ETAD, /*!< ext tampering det: input A */ + FSL_SHW_TAMPER_EBD, /*!< external boot detected */ + FSL_SHW_TAMPER_SAD, /*!< security alarm detected */ + FSL_SHW_TAMPER_TTD, /*!< temperature tampering det */ + FSL_SHW_TAMPER_CTD, /*!< clock tampering det */ + FSL_SHW_TAMPER_VTD, /*!< voltage tampering det */ + FSL_SHW_TAMPER_MCO, /*!< monotonic counter overflow */ + FSL_SHW_TAMPER_TCO, /*!< time counter overflow */ +} fsl_shw_tamper_t; + +/*! @} *//* consgrp */ + +/****************************************************************************** + * Data Structures + *****************************************************************************/ +/*! @addtogroup strgrp + @{ */ + +/* REQ-S2LRD-PINTFC-COA-IBO-001 */ +/*! + * Application Initialization Object + * + * This object, the operations on it, and its interaction with the driver are + * TBD. + */ +typedef struct fsl_sho_ibo_t { +} fsl_sho_ibo_t; + +/* REQ-S2LRD-PINTFC-COA-UCO-001 */ +/*! + * User Context Object + * + * This object must be initialized by a call to #fsl_shw_uco_init(). It must + * then be passed to #fsl_shw_register_user() before it can be used in any + * calls besides those in @ref ucoops. + * + * It contains the user's configuration for the API, for instance whether an + * operation should block, or instead should call back the user upon completion + * of the operation. + * + * See @ref ucoops for further information. + */ +typedef struct fsl_shw_uco_t { /* fsl_shw_user_context_object */ +} fsl_shw_uco_t; + +/* REQ-S2LRD-PINTFC-API-GEN-006 ?? */ +/*! + * Result Object + * + * This object will contain success and failure information about a specific + * cryptographic request which has been made. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref rops. + */ +typedef struct fsl_shw_result_t { /* fsl_shw_result */ +} fsl_shw_result_t; + +/*! + * Keystore Object + * + * This object holds the context of a user keystore, including the functions + * that define the interface and pointers to where the key data is stored. The + * user must supply a set of functions to handle keystore management, including + * slot allocation, deallocation, etc. A default keystore manager is provided + * as part of the API. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref ksoops. + */ +typedef struct fsl_shw_kso_t { /* fsl_shw_keystore_object */ +} fsl_shw_kso_t; + +/* REQ-S2LRD-PINTFC-COA-SKO-001 */ +/*! + * Secret Key Object + * + * This object contains a key for a cryptographic operation, and information + * about its current state, its intended usage, etc. It may instead contain + * information about a protected key, or an indication to use a platform- + * specific secret key. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref skoops. + */ +typedef struct fsl_shw_sko_t { /* fsl_shw_secret_key_object */ +} fsl_shw_sko_t; + +/* REQ-S2LRD-PINTFC-COA-CO-001 */ +/*! + * Platform Capabilities Object + * + * This object will contain information about the cryptographic features of the + * platform which the program is running on. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. + * + * See @ref pcoops. + */ +typedef struct fsl_shw_pco_t { /* fsl_shw_platform_capabilities_object */ +} fsl_shw_pco_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Hash Context Object + * + * This object contains information to control hashing functions. + + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref hcops. + */ +typedef struct fsl_shw_hco_t { /* fsl_shw_hash_context_object */ +} fsl_shw_hco_t; + +/*! + * HMAC Context Object + * + * This object contains information to control HMAC functions. + + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref hmcops. + */ +typedef struct fsl_shw_hmco_t { /* fsl_shw_hmac_context_object */ +} fsl_shw_hmco_t; + +/* REQ-S2LRD-PINTFC-COA-SCCO-001 */ +/*! + * Symmetric Cipher Context Object + * + * This object contains information to control Symmetric Ciphering encrypt and + * decrypt functions in #FSL_SYM_MODE_STREAM (ARC4), #FSL_SYM_MODE_ECB, + * #FSL_SYM_MODE_CBC, and #FSL_SYM_MODE_CTR modes and the + * #fsl_shw_symmetric_encrypt() and #fsl_shw_symmetric_decrypt() functions. + * CCM mode is controlled with the #fsl_shw_acco_t object. + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref sccops. + */ +typedef struct fsl_shw_scco_t { /* fsl_shw_symmetric_cipher_context_object */ +} fsl_shw_scco_t; + +/*! + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + * + * No direct access to its members should be made by programs. Instead, the + * object should be manipulated using the provided functions. See @ref + * accoops. + */ +typedef struct fsl_shw_acco_t { /* fsl_shw_authenticate_cipher_context_object */ +} fsl_shw_acco_t; + /*! @} *//* strgrp */ + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/*! @addtogroup pcoops + @{ */ + +/*! + * Get FSL SHW API version + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] major A pointer to where the major version + * of the API is to be stored. + * @param[out] minor A pointer to where the minor version + * of the API is to be stored. + */ +void fsl_shw_pco_get_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get underlying driver version. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] major A pointer to where the major version + * of the driver is to be stored. + * @param[out] minor A pointer to where the minor version + * of the driver is to be stored. + */ +void fsl_shw_pco_get_driver_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get list of symmetric algorithms supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] algorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] algorithm_count A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +void fsl_shw_pco_get_sym_algorithms(const fsl_shw_pco_t * pc_info, + fsl_shw_key_alg_t * algorithms[], + uint8_t * algorithm_count); + +/*! + * Get list of symmetric modes supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] modes A pointer to where to store the location of + * the list of modes. + * @param[out] mode_count A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +void fsl_shw_pco_get_sym_modes(const fsl_shw_pco_t * pc_info, + fsl_shw_sym_mode_t * modes[], + uint8_t * mode_count); + +/*! + * Get list of hash algorithms supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] algorithms A pointer which will be set to the list of + * algorithms. + * @param[out] algorithm_count The number of algorithms in the list at @a + * algorithms. + */ +void fsl_shw_pco_get_hash_algorithms(const fsl_shw_pco_t * pc_info, + fsl_shw_hash_alg_t * algorithms[], + uint8_t * algorithm_count); + +/*! + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param algorithm A Symmetric Cipher algorithm. + * @param mode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_sym_supported(const fsl_shw_pco_t * pc_info, + fsl_shw_key_alg_t algorithm, + fsl_shw_sym_mode_t mode); + +/*! + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pc_info The Platform Capabilities Object to query. + * @param mode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_auth_supported(const fsl_shw_pco_t * pc_info, + fsl_shw_acc_mode_t mode); + +/*! + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +int fsl_shw_pco_check_black_key_supported(const fsl_shw_pco_t * pc_info); + +/*! + * Get FSL SHW SCC driver version + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] major A pointer to where the major version + * of the SCC driver is to be stored. + * @param[out] minor A pointer to where the minor version + * of the SCC driver is to be stored. + */ +void fsl_shw_pco_get_scc_driver_version(const fsl_shw_pco_t * pc_info, + uint32_t * major, uint32_t * minor); + +/*! + * Get SCM hardware version + * + * @param pc_info The Platform Capabilities Object to query. + * @return The SCM hardware version + */ +uint32_t fsl_shw_pco_get_scm_version(const fsl_shw_pco_t * pc_info); + +/*! + * Get SMN hardware version + * + * @param pc_info The Platform Capabilities Object to query. + * @return The SMN hardware version + */ +uint32_t fsl_shw_pco_get_smn_version(const fsl_shw_pco_t * pc_info); + +/*! + * Get the size of an SCM block, in bytes + * + * @param pc_info The Platform Capabilities Object to query. + * @return The size of an SCM block, in bytes. + */ +uint32_t fsl_shw_pco_get_scm_block_size(const fsl_shw_pco_t * pc_info); + +/*! + * Get size of Black and Red RAM memory + * + * @param pc_info The Platform Capabilities Object to query. + * @param[out] black_size A pointer to where the size of the Black RAM, in + * blocks, is to be placed. + * @param[out] red_size A pointer to where the size of the Red RAM, in + * blocks, is to be placed. + */ +void fsl_shw_pco_get_smn_size(const fsl_shw_pco_t * pc_info, + uint32_t * black_size, uint32_t * red_size); + +/*! + * Determine whether Secure Partitions are supported + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 0 if secure partitions are not supported, non-zero if supported. + */ +int fsl_shw_pco_check_spo_supported(const fsl_shw_pco_t * pc_info); + +/*! + * Get the size of a Secure Partitions + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return Partition size, in bytes. 0 if Secure Partitions not supported. + */ +uint32_t fsl_shw_pco_get_spo_size_bytes(const fsl_shw_pco_t * pc_info); + +/*! + * Get the number of Secure Partitions on this platform + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return Number of partitions. 0 if Secure Partitions not supported. Note + * that this returns the total number of partitions, though + * not all may be available to the user. + */ +uint32_t fsl_shw_pco_get_spo_count(const fsl_shw_pco_t * pc_info); + +/*! + * Determine whether Platform Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Programmed Key features are available, otherwise zero. + */ +int fsl_shw_pco_check_pk_supported(const fsl_shw_pco_t * pc_info); + +/*! + * Determine whether Software Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Software key features are available, otherwise zero. + */ +int fsl_shw_pco_check_sw_keys_supported(const fsl_shw_pco_t * pc_info); + +/*! @} *//* pcoops */ + +/*! @addtogroup ucoops + @{ */ + +/*! + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param user_ctx The User Context object to operate on. + * @param pool_size The maximum number of operations which can be + * outstanding. + */ +void fsl_shw_uco_init(fsl_shw_uco_t * user_ctx, uint16_t pool_size); + +/*! + * Set the User Reference for the User Context. + * + * @param user_ctx The User Context object to operate on. + * @param reference A value which will be passed back with a result. + */ +void fsl_shw_uco_set_reference(fsl_shw_uco_t * user_ctx, uint32_t reference); + +/*! + * Set the callback routine for the User Context. + * + * Note that the callback routine may be called when no results are available, + * and possibly even when no requests are outstanding. + * + * + * @param user_ctx The User Context object to operate on. + * @param callback_fn The function the API will invoke when an operation + * completes. + */ +void fsl_shw_uco_set_callback(fsl_shw_uco_t * user_ctx, + void (*callback_fn) (fsl_shw_uco_t * uco)); + +/*! + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param user_ctx The User Context object to operate on. + * @param flags ORed values from #fsl_shw_user_ctx_flags_t. + */ +void fsl_shw_uco_set_flags(fsl_shw_uco_t * user_ctx, uint32_t flags); + +/*! + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param user_ctx The User Context object to operate on. + * @param flags ORed values from #fsl_shw_user_ctx_flags_t. + */ +void fsl_shw_uco_clear_flags(fsl_shw_uco_t * user_ctx, uint32_t flags); + +/*! + * Select a key for the key-wrap key for key wrapping/unwrapping + * + * Without a call to this function, default is FSL_SHW_PF_KEY_IIM. The wrap + * key is used to encrypt and decrypt the per-key random secret which is used + * to calculate the key which will encrypt/decrypt the user's key. + * + * @param user_ctx The User Context object to operate on. + * @param pf_key Which key to use. Valid choices are + * #FSL_SHW_PF_KEY_IIM, #FSL_SHW_PF_KEY_RND, and + * #FSL_SHW_PF_KEY_IIM_RND. + */ +void fsl_shw_uco_set_wrap_key(fsl_shw_uco_t * user_ctx, + fsl_shw_pf_key_t pf_key); + + /*! @} *//* ucoops */ + +/*! @addtogroup rops + @{ */ + +/*! + * Retrieve the status code from a Result Object. + * + * @param result The result object to query. + * + * @return The status of the request. + */ +fsl_shw_return_t fsl_shw_ro_get_status(fsl_shw_result_t * result); + +/*! + * Retrieve the reference value from a Result Object. + * + * @param result The result object to query. + * + * @return The reference associated with the request. + */ +uint32_t fsl_shw_ro_get_reference(fsl_shw_result_t * result); + + /* @} *//* rops */ + +/*! @addtogroup skoops + @{ */ + +/*! + * Initialize a Secret Key Object. + * + * This function or #fsl_shw_sko_init_pf_key() must be called before performing + * any other operation with the Object. + * + * @param key_info The Secret Key Object to be initialized. + * @param algorithm DES, AES, etc. + * + */ +void fsl_shw_sko_init(fsl_shw_sko_t * key_info, fsl_shw_key_alg_t algorithm); + +/*! + * Initialize a Secret Key Object to use a Platform Key register. + * + * This function or #fsl_shw_sko_init() must be called before performing any + * other operation with the Object. #fsl_shw_sko_set_key() does not work on + * a key object initialized in this way. + * + * If this function is used to initialize the key object, but no key is + * established with the key object, then the object will refer strictly to the + * key value specified by the @c pf_key selection. + * + * If the pf key is #FSL_SHW_PF_KEY_PRG or #FSL_SHW_PF_KEY_IIM_PRG, then the + * key object may be used with #fsl_shw_establish_key() to change the Program + * Key value. When the pf key is neither #FSL_SHW_PF_KEY_PRG nor + * #FSL_SHW_PF_KEY_IIM_PRG, it is an error to call #fsl_shw_establish_key(). + * + * @param key_info The Secret Key Object to be initialized. + * @param algorithm DES, AES, etc. + * @param pf_key Which platform key is referenced. + */ +void fsl_shw_sko_init_pf_key(fsl_shw_sko_t * key_info, + fsl_shw_key_alg_t algorithm, + fsl_shw_pf_key_t pf_key); + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag. It should + * not be used if there is a key established with the key object. If there is, + * a call to #fsl_shw_release_key() should be made first. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param key A pointer to the beginning of the key. + * @param key_length The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +void fsl_shw_sko_set_key(fsl_shw_sko_t * key_object, + const uint8_t * key, uint16_t key_length); + +/*! + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param key_length The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +void fsl_shw_sko_set_key_length(fsl_shw_sko_t * key_object, + uint16_t key_length); + +/*! + * Set the User ID associated with the key. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param userid The User ID to identify authorized users of the key. + */ +void fsl_shw_sko_set_user_id(fsl_shw_sko_t * key_object, key_userid_t userid); + +/*! + * Set the keystore that the key will be stored in. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param keystore The keystore to place the key in. This is a variable of + * type #fsl_shw_kso_t. + */ +void fsl_shw_sko_set_keystore(fsl_shw_sko_t * key_object, + fsl_shw_kso_t * keystore); + +/*! + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT + * flag will be cleared). + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param userid The User ID to verify this user is an authorized user of + * the key. + * @param handle A @a handle from #fsl_shw_sko_get_established_info. + */ +void fsl_shw_sko_set_established_info(fsl_shw_sko_t * key_object, + key_userid_t userid, uint32_t handle); + +/*! + * Extract the algorithm from a key object. + * + * @param key_info The Key Object to be queried. + * @param[out] algorithm A pointer to the location to store the algorithm. + */ +void fsl_shw_sko_get_algorithm(const fsl_shw_sko_t * key_info, + fsl_shw_key_alg_t * algorithm); + +/*! + * Retrieve the cleartext key from a key object that is stored in a user + * keystore. + * + * @param skobject The Key Object to be queried. + * @param[out] skkey A pointer to the location to store the key. NULL + * if the key is not stored in a user keystore. + */ +void fsl_shw_sko_get_key(const fsl_shw_sko_t * skobject, void *skkey); + +/*! + * Retrieve the established-key handle from a key object. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param handle The location to store the @a handle of the unwrapped + * key. + */ +void fsl_shw_sko_get_established_info(fsl_shw_sko_t * key_object, + uint32_t * handle); + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param key_info Information about a key to be wrapped. + * @param length Location to store the length of a wrapped + * version of the key in @a key_info. + */ +void fsl_shw_sko_calculate_wrapped_size(const fsl_shw_sko_t * key_info, + uint32_t * length); + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param flags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +void fsl_shw_sko_set_flags(fsl_shw_sko_t * key_object, uint32_t flags); + +/*! + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param key_object A variable of type #fsl_shw_sko_t. + * @param flags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be reset. + */ +void fsl_shw_sko_clear_flags(fsl_shw_sko_t * key_object, uint32_t flags); + + /*! @} *//* end skoops */ + +/*****************************************************************************/ + +/*! @addtogroup hcops + @{ */ + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-004 - partially */ +/*! + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hash_ctx The hash context to operate upon. + * @param algorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +void fsl_shw_hco_init(fsl_shw_hco_t * hash_ctx, fsl_shw_hash_alg_t algorithm); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-001 */ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-002 */ +/*! + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hash_ctx The hash context to query. + * @param[out] digest Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param length Number of octets of hash value to copy. + * @param[out] msg_length Pointer to the location to store the number of octets + * already hashed. + */ +void fsl_shw_hco_get_digest(const fsl_shw_hco_t * hash_ctx, uint8_t * digest, + uint8_t length, uint32_t * msg_length); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-002 - partially */ +/*! + * Get the hash algorithm from the hash context object. + * + * @param hash_ctx The hash context to query. + * @param[out] algorithm Pointer to where the algorithm is to be stored. + */ +void fsl_shw_hco_get_info(const fsl_shw_hco_t * hash_ctx, + fsl_shw_hash_alg_t * algorithm); + +/*****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-003 */ +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-004 */ +/*! + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hash_ctx The hash context to operate upon. + * @param context Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param msg_length The number of octets of the message which have + * already been hashed. + * + */ +void fsl_shw_hco_set_digest(fsl_shw_hco_t * hash_ctx, const uint8_t * context, + uint32_t msg_length); + +/*! + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hash_ctx The hash context to be operated on. + * @param flags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +void fsl_shw_hco_set_flags(fsl_shw_hco_t * hash_ctx, uint32_t flags); + +/*! + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hash_ctx The hash context to be operated on. + * @param flags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +void fsl_shw_hco_clear_flags(fsl_shw_hco_t * hash_ctx, uint32_t flags); + + /*! @} *//* end hcops */ + +/*****************************************************************************/ + +/*! @addtogroup hmcops + @{ */ + +/*! + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hmac_ctx The HMAC context to operate upon. + * @param algorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +void fsl_shw_hmco_init(fsl_shw_hmco_t * hmac_ctx, fsl_shw_hash_alg_t algorithm); + +/*! + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hmac_ctx The HMAC context to be operated on. + * @param flags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +void fsl_shw_hmco_set_flags(fsl_shw_hmco_t * hmac_ctx, uint32_t flags); + +/*! + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hmac_ctx The HMAC context to be operated on. + * @param flags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +void fsl_shw_hmco_clear_flags(fsl_shw_hmco_t * hmac_ctx, uint32_t flags); + +/*! @} */ + +/*****************************************************************************/ + +/*! @addtogroup sccops + @{ */ + +/*! + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param sym_ctx The context object to operate on. + * @param algorithm The cipher algorithm this context will be used with. + * @param mode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +void fsl_shw_scco_init(fsl_shw_scco_t * sym_ctx, + fsl_shw_key_alg_t algorithm, fsl_shw_sym_mode_t mode); + +/*! + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param sym_ctx The context object to operate on. + * @param flags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +void fsl_shw_scco_set_flags(fsl_shw_scco_t * sym_ctx, uint32_t flags); + +/*! + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param sym_ctx The context object to operate on. + * @param flags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +void fsl_shw_scco_clear_flags(fsl_shw_scco_t * sym_ctx, uint32_t flags); + +/*! + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param sym_ctx The context object to operate on. + * @param context A pointer to the buffer which contains the context. + * + */ +void fsl_shw_scco_set_context(fsl_shw_scco_t * sym_ctx, uint8_t * context); + +/*! + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param sym_ctx The context object to operate on. + * @param[out] context Pointer to location where context will be stored. + */ +void fsl_shw_scco_get_context(const fsl_shw_scco_t * sym_ctx, + uint8_t * context); + +/*! + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param sym_ctx The context object to operate on. + * @param counter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param modulus The modulus for controlling the incrementing of the counter. + * + */ +void fsl_shw_scco_set_counter_info(fsl_shw_scco_t * sym_ctx, + const uint8_t * counter, + fsl_shw_ctr_mod_t modulus); + +/*! + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param sym_ctx The context object to query. + * @param[out] counter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] modulus Pointer to location to store the modulus. + * + */ +void fsl_shw_scco_get_counter_info(const fsl_shw_scco_t * sym_ctx, + uint8_t * counter, + fsl_shw_ctr_mod_t * modulus); + + /*! @} *//* end sccops */ + +/*****************************************************************************/ + +/*! @addtogroup accoops + @{ */ + +/*! + * Initialize a Authentication-Cipher Context. + * + * @param auth_object Pointer to object to operate on. + * @param mode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +void fsl_shw_acco_init(fsl_shw_acco_t * auth_object, fsl_shw_acc_mode_t mode); + +/*! + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param auth_object Pointer to object to operate on. + * @param flags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +void fsl_shw_acco_set_flags(fsl_shw_acco_t * auth_object, uint32_t flags); + +/*! + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param auth_object Pointer to object to operate on. + * @param flags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +void fsl_shw_acco_clear_flags(fsl_shw_acco_t * auth_object, uint32_t flags); + +/*! + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The parameter @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param auth_object Pointer to object to operate on. + * @param algorithm Cipher algorithm. Only AES is supported. + * @param ctr The initial counter value. + * @param mac_length The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +void fsl_shw_acco_set_ccm(fsl_shw_acco_t * auth_object, + fsl_shw_key_alg_t algorithm, + const uint8_t * ctr, uint8_t mac_length); + +/*! + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param auth_object Pointer to object to operate on. + * @param t_length The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param ad_length Number of octets of Associated Data (may be zero). + * @param q_length A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param n The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param q The value of Q (size of the payload in octets). + * + */ +void fsl_shw_ccm_nist_format_ctr_and_iv(fsl_shw_acco_t * auth_object, + uint8_t t_length, + uint32_t ad_length, + uint8_t q_length, + const uint8_t * n, uint32_t q); + +/*! + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param auth_object Pointer to object to operate on. + * @param n The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param q The value of Q (size of the payload in octets). + * + */ +void fsl_shw_ccm_nist_update_ctr_and_iv(fsl_shw_acco_t * auth_object, + const uint8_t * n, uint32_t q); + + /* @} *//* accoops */ + +/****************************************************************************** + * Library functions + *****************************************************************************/ + +/*! @addtogroup miscfuns + @{ */ + +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +/*! + * Determine the hardware security capabilities of this platform. + * + * Though a user context object is passed into this function, it will always + * act in a non-blocking manner. + * + * @param user_ctx The user context which will be used for the query. + * + * @return A pointer to the capabilities object. + */ +extern fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +/*! + * Create an association between the user and the provider of the API. + * + * @param user_ctx The user context which will be used for this association. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +/*! + * Destroy the association between the user and the provider of the API. + * + * @param user_ctx The user context which is no longer needed. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +/*! + * Retrieve results from earlier operations. + * + * @param user_ctx The user's context. + * @param result_size The number of array elements of @a results. + * @param[in,out] results Pointer to first of the (array of) locations to + * store results. + * @param[out] result_count Pointer to store the number of results which + * were returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + uint16_t result_size, + fsl_shw_result_t results[], + uint16_t * result_count); + +/*! + * Allocate a block of secure memory + * + * @param user_ctx User context + * @param size Memory size (octets). Note: currently only + * supports only single-partition sized blocks. + * @param UMID User Mode ID to use when registering the + * partition. + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return Address of the allocated memory. NULL if the + * call was not successful. + */ +extern void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, + const uint8_t * UMID, uint32_t permissions); + +/*! + * Free a block of secure memory that was allocated with #fsl_shw_smalloc + * + * @param user_ctx User context + * @param address Address of the block of secure memory to be + * released. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address); + +/*! + * Diminish the permissions of a block of secure memory. Note that permissions + * can only be revoked. + * + * @param user_ctx User context + * @param address Base address of the secure memory to work with + * @param permissions Permissions to initialize the partition with. + * Can be made by ORing flags from the + * #fsl_shw_permission_t. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, + uint32_t permissions); + +/*! + * @brief Encrypt a region of secure memory using the hardware secret key + * + * @param user_ctx User context + * @param partition_base Base address of the partition + * @param offset_bytes Offset of data from the partition base + * @param byte_count Length of the data to encrypt + * @param black_data Location to store the encrypted data + * @param IV IV to use for the encryption routine + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + +/*! + * @brief Decrypt a region of secure memory using the hardware secret key + * + * @param user_ctx User context + * @param partition_base Base address of the partition + * @param offset_bytes Offset of data from the partition base + * @param byte_count Length of the data to encrypt + * @param black_data Location to store the encrypted data + * @param IV IV to use for the encryption routine + * @param cypher_mode Cyphering mode to use, specified by type + * #fsl_shw_cypher_mode_t + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + + /*! @} *//* miscfuns */ + +/*! @addtogroup opfuns + @{ */ + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Encrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the init & save flags flags on in + * the @a sym_ctx. Subsequent operations would then run as normal with the + * load and save flags. Note that they key object is still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info Key and algorithm being used for this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the pt (and ct). + * @param pt pointer to plaintext to be encrypted. + * @param[out] ct pointer to where to store the resulting ciphertext. + * + * @return A return code of type #fsl_shw_return_t. + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, + uint8_t * ct); + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +/*! + * Decrypt a stream of data with a symmetric-key algorithm. + * + * In ARC4, and also in #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_CTR modes, the + * flags of the @a sym_ctx object will control part of the operation of this + * function. The #FSL_SYM_CTX_INIT flag means that there is no context info in + * the object. The #FSL_SYM_CTX_LOAD means to use information in the + * @a sym_ctx at the start of the operation, and the #FSL_SYM_CTX_SAVE flag + * means to update the object's context information after the operation has + * been performed. + * + * All of the data for an operation can be run through at once using the + * #FSL_SYM_CTX_INIT or #FSL_SYM_CTX_LOAD flags, as appropriate, and then using + * a @a length for the whole of the data. + * + * If a #FSL_SYM_CTX_SAVE flag were added, an additional call to the function + * would "pick up" where the previous call left off, allowing the user to + * perform the larger function in smaller steps. + * + * In #FSL_SYM_MODE_CBC and #FSL_SYM_MODE_ECB modes, the @a length must always + * be a multiple of the block size for the algorithm being used. For proper + * operation in #FSL_SYM_MODE_CTR mode, the @a length must be a multiple of the + * block size until the last operation on the total octet stream. + * + * Some users of ARC4 may want to compute the context (S-Box and pointers) from + * the key before any data is available. This may be done by running this + * function with a @a length of zero, with the #FSL_SYM_CTX_INIT & + * #FSL_SYM_CTX_SAVE flags on in the @a sym_ctx. Subsequent operations would + * then run as normal with the load & save flags. Note that they key object is + * still required. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key and algorithm being used in this operation. + * @param[in,out] sym_ctx Info on cipher mode, state of the cipher. + * @param length Length, in octets, of the ct (and pt). + * @param ct pointer to ciphertext to be decrypted. + * @param[out] pt pointer to where to store the resulting plaintext. + * + * @return A return code of type #fsl_shw_return_t + * + */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, + uint8_t * pt); + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +/*! + * Hash a stream of data with a cryptographic hash algorithm. + * + * The flags in the @a hash_ctx control the operation of this function. + * + * Hashing functions work on 64 octets of message at a time. Therefore, when + * any partial hashing of a long message is performed, the message @a length of + * each segment must be a multiple of 64. When ready to + * #FSL_HASH_FLAGS_FINALIZE the hash, the @a length may be any value. + * + * With the #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_FINALIZE flags on, a + * one-shot complete hash, including padding, will be performed. The @a length + * may be any value. + * + * The first octets of a data stream can be hashed by setting the + * #FSL_HASH_FLAGS_INIT and #FSL_HASH_FLAGS_SAVE flags. The @a length must be + * a multiple of 64. + * + * The flag #FSL_HASH_FLAGS_LOAD is used to load a context previously saved by + * #FSL_HASH_FLAGS_SAVE. The two in combination will allow a (multiple-of-64 + * octets) 'middle sequence' of the data stream to be hashed with the + * beginning. The @a length must again be a multiple of 64. + * + * Since the flag #FSL_HASH_FLAGS_LOAD is used to load a context previously + * saved by #FSL_HASH_FLAGS_SAVE, the #FSL_HASH_FLAGS_LOAD and + * #FSL_HASH_FLAGS_FINALIZE flags, used together, can be used to finish the + * stream. The @a length may be any value. + * + * If the user program wants to do the padding for the hash, it can leave off + * the #FSL_HASH_FLAGS_FINALIZE flag. The @a length must then be a multiple of + * 64 octets. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] hash_ctx Hashing algorithm and state of the cipher. + * @param msg Pointer to the data to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result If not null, pointer to where to store the hash + * digest. + * @param result_len Number of octets to store in @a result. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +/*! + * Precompute the Key hashes for an HMAC operation. + * + * This function may be used to calculate the inner and outer precomputes, + * which are the hash contexts resulting from hashing the XORed key for the + * 'inner hash' and the 'outer hash', respectively, of the HMAC function. + * + * After execution of this function, the @a hmac_ctx will contain the + * precomputed inner and outer contexts, so that they may be used by + * #fsl_shw_hmac(). The flags of @a hmac_ctx will be updated with + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT to mark their presence. In addition, the + * #FSL_HMAC_FLAGS_INIT flag will be set. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The key being used in this operation. Key must be + * 1 to 64 octets long. + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +/*! + * Continue, finalize, or one-shot an HMAC operation. + * + * There are a number of ways to use this function. The flags in the + * @a hmac_ctx object will determine what operations occur. + * + * If #FSL_HMAC_FLAGS_INIT is set, then the hash will be started either from + * the @a key_info, or from the precomputed inner hash value in the + * @a hmac_ctx, depending on the value of #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT. + * + * If, instead, #FSL_HMAC_FLAGS_LOAD is set, then the hash will be continued + * from the ongoing inner hash computation in the @a hmac_ctx. + * + * If #FSL_HMAC_FLAGS_FINALIZE are set, then the @a msg will be padded, hashed, + * the outer hash will be performed, and the @a result will be generated. + * + * If the #FSL_HMAC_FLAGS_SAVE flag is set, then the (ongoing or final) digest + * value will be stored in the ongoing inner hash computation field of the @a + * hmac_ctx. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info If #FSL_HMAC_FLAGS_INIT is set in the @a hmac_ctx, + * this is the key being used in this operation, and the + * IPAD. If #FSL_HMAC_FLAGS_INIT is set in the @a + * hmac_ctx and @a key_info is NULL, then + * #fsl_shw_hmac_precompute() has been used to populate + * the @a inner_precompute and @a outer_precompute + * contexts. If #FSL_HMAC_FLAGS_INIT is not set, this + * parameter is ignored. + + * @param[in,out] hmac_ctx The context which controls, by its flags and + * algorithm, the operation of this function. + * @param msg Pointer to the message to be hashed. + * @param length Length, in octets, of the @a msg. + * @param[out] result Pointer, of @a result_len octets, to where to + * store the HMAC. + * @param result_len Length of @a result buffer. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Get random data. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length The number of octets of @a data being requested. + * @param[out] data A pointer to a location of @a length octets to where + * random data will be returned. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +/*! + * Add entropy to random number generator. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param length Number of bytes at @a data. + * @param data Entropy to add to random number generator. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +/*! + * Perform Generation-Encryption by doing a Cipher and a Hash. + * + * Generate the authentication value @a auth_value as well as encrypt the @a + * payload into @a ct (the ciphertext). This is a one-shot function, so all of + * the @a auth_data and the total message @a payload must passed in one call. + * This also means that the flags in the @a auth_ctx must be #FSL_ACCO_CTX_INIT + * and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not encrypted. + * @param payload_length Length, in octets, of @a payload. + * @param payload Pointer to the plaintext to be encrypted. + * @param[out] ct Pointer to the where the encrypted @a payload + * will be stored. Must be @a payload_length + * octets long. + * @param[out] auth_value Pointer to where the generated authentication + * field will be stored. Must be as many octets as + * indicated by MAC length in the @a function_ctx. + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value); + +/*! + * Perform Authentication-Decryption in Cipher + Hash. + * + * This function will perform a one-shot decryption of a data stream as well as + * authenticate the authentication value. This is a one-shot function, so all + * of the @a auth_data and the total message @a payload must passed in one + * call. This also means that the flags in the @a auth_ctx must be + * #FSL_ACCO_CTX_INIT and #FSL_ACCO_CTX_FINALIZE. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param auth_ctx Controlling object for Authenticate-decrypt. + * @param cipher_key_info The key being used for the cipher part of this + * operation. In CCM mode, this key is used for + * both parts. + * @param auth_key_info The key being used for the authentication part + * of this operation. In CCM mode, this key is + * ignored and may be NULL. + * @param auth_data_length Length, in octets, of @a auth_data. + * @param auth_data Data to be authenticated but not decrypted. + * @param payload_length Length, in octets, of @a ct and @a pt. + * @param ct Pointer to the encrypted input stream. + * @param auth_value The (encrypted) authentication value which will + * be authenticated. This is the same data as the + * (output) @a auth_value argument to + * #fsl_shw_gen_encrypt(). + * @param[out] payload Pointer to where the plaintext resulting from + * the decryption will be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload); + +/*! + * Establish the key in a protected location, which can be the system keystore, + * user keystore, or (on platforms that support it) as a Platform Key. + * + * By default, keys initialized with #fsl_shw_sko_init() will be placed into + * the system keystore. The user can cause the key to be established in a + * user keystore by first calling #fsl_shw_sko_set_keystore() on the key. + * Normally, keys in the system keystore can only be used for hardware + * encrypt or decrypt operations, however if the #FSL_SKO_KEY_SW_KEY flag is + * applied using #fsl_shw_sko_set_flags(), the key will be established as a + * software key, which can then be read out using #fsl_shw_read_key(). + * + * Keys initialized with #fsl_shw_sko_init_pf_key() are established as a + * Platform Key. Their use is covered in @ref di_sec. + * + * This function only needs to be used when unwrapping a key, setting up a key + * which could be wrapped with a later call to #fsl_shw_extract_key(), or + * setting up a key as a Platform Key. Normal cleartext keys can simply be + * placed into #fsl_shw_sko_t key objects with #fsl_shw_sko_set_key() and used + * directly. + * + * The maximum key size supported for wrapped/unwrapped keys is 32 octets. + * (This is the maximum reasonable key length on Sahara - 32 octets for an HMAC + * key based on SHA-256.) The key size is determined by the @a key_info. The + * expected length of @a key can be determined by + * #fsl_shw_sko_calculate_wrapped_size() + * + * The protected key will not be available for use until this operation + * successfully completes. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param[in,out] key_info The information about the key to be which will + * be established. In the create case, the key + * length must be set. + * @param establish_type How @a key will be interpreted to establish a + * key for use. + * @param key If @a establish_type is #FSL_KEY_WRAP_UNWRAP, + * this is the location of a wrapped key. If + * @a establish_type is #FSL_KEY_WRAP_CREATE, this + * parameter can be @a NULL. If @a establish_type + * is #FSL_KEY_WRAP_ACCEPT, this is the location + * of a plaintext key. + */ +extern fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key); + +/*! + * Read the key value from a key object. + * + * Only a key marked as a software key (#FSL_SKO_KEY_SW_KEY) can be read with + * this call. It has no effect on the status of the key store. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The referenced key. + * @param[out] key The location to store the key value. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * key); + +/*! + * Wrap a key and retrieve the wrapped value. + * + * A wrapped key is a key that has been cryptographically obscured. It is + * only able to be used with keys that have been established by + * #fsl_shw_establish_key(). + * + * For keys established in the system or user keystore, this function will + * also release the key (see #fsl_shw_release_key()) so that it must be re- + * established before reuse. This function will not release keys that are + * established as a Platform Key, so a call to #fsl_shw_release_key() is + * necessary to release those keys. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * @param[out] covered_key The location to store the wrapped key. + * (This size is based upon the maximum key size + * of 32 octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key); + +/*! + * De-establish a key so that it can no longer be accessed. + * + * The key will need to be re-established before it can again be used. + * + * This feature is not available for all platforms, nor for all algorithms and + * modes. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * @param key_info The information about the key to be deleted. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +/*! + * Cause the hardware to create a new random key for use by the secure memory + * encryption hardware. + * + * Have the hardware use the secure hardware random number generator to load a + * new secret key into the system's Random Key register. + * + * @param user_ctx A user context from #fsl_shw_register_user(). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * user_ctx); + +/*! + * Retrieve the detected tamper event. + * + * Note that if more than one event was detected, this routine will only ever + * return one of them. + * + * @param[in] user_ctx A user context from #fsl_shw_register_user(). + * @param[out] tamperp Location to store the tamper information. + * @param[out] timestampp Locate to store timestamp from hardwhare when + * an event was detected. + * + * + * @return A return code of type #fsl_shw_return_t (for instance, if the platform + * is not in a fail state. + */ +extern fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * user_ctx, + fsl_shw_tamper_t * tamperp, + uint64_t * timestampp); + +/*! @} *//* opfuns */ + +/* Insert example code into the API documentation. */ + +/*! + * @example apitest.c + */ + +/*! + * @example sym.c + */ + +/*! + * @example rand.c + */ + +/*! + * @example hash.c + */ + +/*! + * @example hmac1.c + */ + +/*! + * @example hmac2.c + */ + +/*! + * @example gen_encrypt.c + */ + +/*! + * @example auth_decrypt.c + */ + +/*! + * @example wrapped_key.c + */ + +/*! + * @example smalloc.c + */ + +/*! + * @example user_keystore.c + */ + +/*! + * @example dryice.c + */ + +#endif /* API_DOC */ + +#endif /* FSL_SHW_H */ diff --git a/drivers/mxc/security/sahara2/include/fsl_shw_keystore.h b/drivers/mxc/security/sahara2/include/fsl_shw_keystore.h new file mode 100644 index 000000000000..2a275da2dfa8 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/fsl_shw_keystore.h @@ -0,0 +1,475 @@ +/* + * Copyright 2008-2009 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 + */ + + +#ifndef FSL_SHW_KEYSTORE_H +#define FSL_SHW_KEYSTORE_H + +/*! + * @file fsl_shw_keystore.h + * + * @brief Definition of the User Keystore API. + * + */ + +/*! \page user_keystore User Keystore API + * + * Definition of the User Keystore API. + * + * On platforms with multiple partitions of Secure Memory, the Keystore Object + * (#fsl_shw_kso_t) is provided to allow users to manage a private keystore for + * use in software cryptographic routines. The user can define a custom set of + * methods for managing their keystore, or use a default keystore handler. The + * keystore is established by #fsl_shw_establish_keystore(), and released by + * #fsl_shw_release_keystore(). The intent of this design is to make the + * keystore implementation as flexible as possible. + * + * See @ref keystore_api for the generic keystore API, and @ref + * default_keystore for the default keystore implementation. + * + */ + +/*! + * @defgroup keystore_api User Keystore API + * + * Keystore API + * + * These functions define the generic keystore API, which can be used in + * conjunction with a keystore implementation backend to support a user + * keystore. + */ + +/*! + * @defgroup default_keystore Default Keystore Implementation + * + * Default Keystore Implementation + * + * These functions define the default keystore implementation, which is used + * for the system keystore and for user keystores initialized by + * #fsl_shw_init_keystore_default(). They can be used as-is or as a reference + * for creating a custom keystore handler. It uses an entire Secure Memory + * partition, divided in to equal slots of length #KEYSTORE_SLOT_SIZE. These + * functions are not intended to be used directly- all user interaction with + * the keystore should be through the @ref keystore_api and the Wrapped Key + * interface. + * + * The current implementation is designed to work with both SCC and SCC2. + * Differences between the two versions are noted below. + */ + +/*! @addtogroup keystore_api + @{ */ + +#ifndef KEYSTORE_SLOT_SIZE +/*! Size of each key slot, in octets. This sets an upper bound on the size + * of a key that can placed in the keystore. + */ +#define KEYSTORE_SLOT_SIZE 32 +#endif + +/*! + * Initialize a Keystore Object. + * + * This function must be called before performing any other operation with the + * Object. It allows the user to associate a custom keystore interface by + * specifying the correct set of functions that will be used to perform actions + * on the keystore object. To use the default keystore handler, the function + * #fsl_shw_init_keystore_default() can be used instead. + * + * @param keystore The Keystore object to operate on. + * @param data_init Keystore initialization function. This function is + * responsible for initializing the keystore. A + * user-defined object can be assigned to the user_data + * pointer, and will be passed to any function acting on + * that keystore. It is called during + * #fsl_shw_establish_keystore(). + * @param data_cleanup Keystore cleanup function. This function cleans up + * any data structures associated with the keyboard. It + * is called by #fsl_shw_release_keystore(). + * @param slot_alloc Slot allocation function. This function allocates a + * key slot, potentially based on size and owner id. It + * is called by #fsl_shw_establish_key(). + * @param slot_dealloc Slot deallocation function. + * @param slot_verify_access Function to verify that a given Owner ID + * credential matches the given slot. + * @param slot_get_address For SCC2: Get the virtual address (kernel or + * userspace) of the data stored in the slot. + * For SCC: Get the physical address of the data + * stored in the slot. + * @param slot_get_base For SCC2: Get the (virtual) base address of the + * partition that the slot is located on. + * For SCC: Not implemented. + * @param slot_get_offset For SCC2: Get the offset from the start of the + * partition that the slot data is located at (in + * octets) + * For SCC: Not implemented. + * @param slot_get_slot_size Get the size of the key slot, in octets. + */ +extern void fsl_shw_init_keystore(fsl_shw_kso_t * keystore, + fsl_shw_return_t(*data_init) (fsl_shw_uco_t * + user_ctx, + void + **user_data), + void (*data_cleanup) (fsl_shw_uco_t * + user_ctx, + void **user_data), + fsl_shw_return_t(*slot_alloc) (void + *user_data, + uint32_t size, + uint64_t + owner_id, + uint32_t * + slot), + fsl_shw_return_t(*slot_dealloc) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + fsl_shw_return_t(*slot_verify_access) (void + *user_data, + uint64_t + owner_id, + uint32_t + slot), + void *(*slot_get_address) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_base) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_offset) (void *user_data, + uint32_t handle), + uint32_t(*slot_get_slot_size) (void + *user_data, + uint32_t + handle)); + +/*! + * Initialize a Keystore Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the user keystore object up to use the default keystore + * handler. If a custom keystore handler is desired, the function + * #fsl_shw_init_keystore() can be used instead. + * + * @param keystore The Keystore object to operate on. + */ +extern void fsl_shw_init_keystore_default(fsl_shw_kso_t * keystore); + +/*! + * Establish a Keystore Object. + * + * This function establishes a keystore object that has been set up by a call + * to #fsl_shw_init_keystore(). It is a wrapper for the user-defined + * data_init() function, which is specified during keystore initialization. + * + * @param user_ctx The user context that this keystore should be attached + * to + * @param keystore The Keystore object to operate on. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t fsl_shw_establish_keystore(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore); + +/*! + * Release a Keystore Object. + * + * This function releases an established keystore object. It is a wrapper for + * the user-defined data_cleanup() function, which is specified during keystore + * initialization. + * + * @param user_ctx The user context that this keystore should be attached + * to. + * @param keystore The Keystore object to operate on. + */ +extern void fsl_shw_release_keystore(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore); + +/*! + * Allocate a slot in the Keystore. + * + * This function attempts to allocate a slot to hold a key in the keystore. It + * is called by #fsl_shw_establish_key() when establishing a Secure Key Object, + * if the key has been flagged to be stored in a user keystore by the + * #fsl_shw_sko_set_keystore() function. It is a wrapper for the + * implementation-specific function slot_alloc(). + * + * @param keystore The Keystore object to operate on. + * @param[in] size Size of the key to be stored (octets). + * @param[in] owner_id ID of the key owner. + * @param[out] slot If successful, assigned slot ID + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t keystore_slot_alloc(fsl_shw_kso_t * keystore, + uint32_t size, + uint64_t owner_id, uint32_t * slot); + +/*! + * Deallocate a slot in the Keystore. + * + * This function attempts to allocate a slot to hold a key in the keystore. + * It is called by #fsl_shw_extract_key() and #fsl_shw_release_key() when the + * key that it contains is to be released. It is a wrapper for the + * implmentation-specific function slot_dealloc(). + + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot If successful, assigned slot ID. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t keystore_slot_dealloc(fsl_shw_kso_t * keystore, + uint64_t owner_id, uint32_t slot); + +/*! + * Load cleartext key data into a key slot + * + * This function loads a key slot with cleartext data. + * + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot If successful, assigned slot ID. + * @param[in] key_data Pointer to the location of the cleartext key data. + * @param[in] key_length Length of the key data (octets). + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_load(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + const uint8_t * key_data, uint32_t key_length); + +/*! + * Read cleartext key data from a key slot + * + * This function returns the key in a key slot. + * + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot ID of slot where key resides. + * @param[in] key_length Length of the key data (octets). + * @param[out] key_data Pointer to the location of the cleartext key data. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_read(fsl_shw_kso_t * keystore, uint64_t owner_id, uint32_t slot, + uint32_t key_length, uint8_t * key_data); + +/*! + * Encrypt a keyslot + * + * This function encrypts a key using the hardware secret key. + * + * @param user_ctx User context + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot Slot ID of the key to encrypt. + * @param[in] length Length of the key + * @param[out] destination Pointer to the location where the encrypted data + * is to be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore, uint64_t owner_id, + uint32_t slot, uint32_t length, uint8_t * destination); + +/*! + * Decrypt a keyslot + * + * This function decrypts a key using the hardware secret key. + * + * @param user_ctx User context + * @param keystore The Keystore object to operate on. + * @param[in] owner_id ID of the key owner. + * @param[in] slot Slot ID of the key to encrypt. + * @param[in] length Length of the key + * @param[in] source Pointer to the location where the encrypted data + * is stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +extern fsl_shw_return_t +keystore_slot_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_kso_t * keystore, uint64_t owner_id, + uint32_t slot, uint32_t length, const uint8_t * source); + +/* @} */ + +/*! @addtogroup default_keystore + @{ */ + +/*! + * Data structure to hold per-slot information + */ +typedef struct keystore_data_slot_info_t { + uint8_t allocated; /*!< Track slot assignments */ + uint64_t owner; /*!< Owner IDs */ + uint32_t key_length; /*!< Size of the key */ +} keystore_data_slot_info_t; + +/*! + * Data structure to hold keystore information. + */ +typedef struct keystore_data_t { + void *base_address; /*!< Base of the Secure Partition */ + uint32_t slot_count; /*!< Number of slots in the keystore */ + struct keystore_data_slot_info_t *slot; /*!< Per-slot information */ +} keystore_data_t; + +/*! + * Default keystore initialization routine. + * + * This function acquires a Secure Partition Object to store the keystore, + * divides it into slots of length #KEYSTORE_SLOT_SIZE, and builds a data + * structure to hold key information. + * + * @param user_ctx User context + * @param[out] user_data Pointer to the location where the keystore data + * structure is to be stored. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_kso_init_data(fsl_shw_uco_t * user_ctx, void **user_data); + +/*! + * Default keystore cleanup routine. + * + * This function releases the Secure Partition Object and the memory holding + * the keystore data structure, that obtained by the shw_kso_init_data + * function. + * + * @param user_ctx User context + * @param[in,out] user_data Pointer to the location where the keystore data + * structure is stored. + */ +void shw_kso_cleanup_data(fsl_shw_uco_t * user_ctx, void **user_data); + +/*! + * Default keystore slot access verification + * + * This function compares the supplied Owner ID to the registered owner of + * the key slot, to see if the supplied ID is correct. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] owner_id Owner ID supplied as a credential. + * @param[in] slot Requested slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_slot_verify_access(void *user_data, uint64_t owner_id, + uint32_t slot); + +/*! + * Default keystore slot allocation + * + * This function first checks that the requested size is equal to or less than + * the maximum keystore slot size. If so, it searches the keystore for a free + * key slot, and if found, marks it as used and returns a slot reference to the + * user. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] size Size of the key data that will be stored in this slot + * (octets) + * @param[in] owner_id Owner ID supplied as a credential. + * @param[out] slot Requested slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_slot_alloc(void *user_data, uint32_t size, + uint64_t owner_id, uint32_t * slot); + +/*! + * Default keystore slot deallocation + * + * This function releases the given key slot in the keystore, making it + * available to store a new key. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] owner_id Owner ID supplied as a credential. + * @param[in] slot Requested slot + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t shw_slot_dealloc(void *user_data, + uint64_t owner_id, uint32_t slot); + +/*! + * Default keystore slot address lookup + * + * This function calculates the address where the key data is stored. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Virtual address (kernel or userspace) of the key data. + * SCC: Physical address of the key data. + */ +void *shw_slot_get_address(void *user_data, uint32_t slot); + +/*! + * Default keystore slot base address lookup + * + * This function calculates the base address of the Secure Partition on which + * the key data is located. For the reference design, only one Secure + * Partition is used per Keystore, however in general, any number may be used. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Secure Partition virtual (kernel or userspace) base address. + * SCC: Secure Partition physical base address. + */ +uint32_t shw_slot_get_base(void *user_data, uint32_t slot); + +/*! + * Default keystore slot offset lookup + * + * This function calculates the offset from the base of the Secure Partition + * where the key data is located. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Key data offset (octets) + * SCC: Not implemented + */ +uint32_t shw_slot_get_offset(void *user_data, uint32_t slot); + +/*! + * Default keystore slot offset lookup + * + * This function returns the size of the given key slot. In the reference + * implementation, all key slots are of the same size, however in general, + * the keystore slot sizes can be made variable. + * + * @param[in] user_data Pointer to the location where the keystore data + * structure stored. + * @param[in] slot Requested slot + * + * @return SCC2: Keystore slot size. + * SCC: Not implemented + */ +uint32_t shw_slot_get_slot_size(void *user_data, uint32_t slot); + +/* @} */ + +#endif /* FSL_SHW_KEYSTORE_H */ diff --git a/drivers/mxc/security/sahara2/include/linux_port.h b/drivers/mxc/security/sahara2/include/linux_port.h new file mode 100644 index 000000000000..492bbf4f893d --- /dev/null +++ b/drivers/mxc/security/sahara2/include/linux_port.h @@ -0,0 +1,1806 @@ +/* + * Copyright 2004-2009 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 linux_port.h + * + * OS_PORT ported to Linux (2.6.9+ for now) + * + */ + + /*! + * @if USE_MAINPAGE + * @mainpage ==Linux version of== Generic OS API for STC Drivers + * @endif + * + * @section intro_sec Introduction + * + * This API / kernel programming environment blah blah. + * + * See @ref dkops "Driver-to-Kernel Operations" as a good place to start. + */ + +#ifndef LINUX_PORT_H +#define LINUX_PORT_H + +#define PORTABLE_OS_VERSION 101 + +/* Linux Kernel Includes */ +#include <linux/version.h> /* Current version Linux kernel */ + +#if defined(CONFIG_MODVERSIONS) && ! defined(MODVERSIONS) +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +#include <linux/modversions.h> +#endif +#define MODVERSIONS +#endif +/*! + * __NO_VERSION__ defined due to Kernel module possibly spanning multiple + * files. + */ +#define __NO_VERSION__ + +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/fs.h> /* for inode */ +#include <linux/random.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> /* kmalloc */ + +#include <stdarg.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +#include <linux/device.h> /* used in dynamic power management */ +#else +#include <linux/platform_device.h> /* used in dynamic power management */ +#endif + +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> + +#include <linux/clk.h> /* clock en/disable for DPM */ + +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> + +#include <asm/uaccess.h> /* copy_to_user(), copy_from_user() */ +#include <asm/io.h> /* ioremap() */ +#include <asm/irq.h> +#include <asm/cacheflush.h> + +#include <mach/hardware.h> + +#ifndef TRUE +/*! Useful symbol for unsigned values used as flags. */ +#define TRUE 1 +#endif + +#ifndef FALSE +/*! Useful symbol for unsigned values used as flags. */ +#define FALSE 0 +#endif + +/* These symbols are defined in Linux 2.6 and later. Include here for minimal + * support of 2.4 kernel. + **/ +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +/*! + * Symbol defined somewhere in 2.5/2.6. It is the return signature of an ISR. + */ +#define irqreturn_t void +/*! Possible return value of 'modern' ISR routine. */ +#define IRQ_HANDLED +/*! Method of generating value of 'modern' ISR routine. */ +#define IRQ_RETVAL(x) +#endif + +/*! + * Type used for registering and deregistering interrupts. + */ +typedef int os_interrupt_id_t; + +/*! + * Type used as handle for a process + * + * See #os_get_process_handle() and #os_send_signal(). + */ +/* + * The following should be defined this way, but it gets compiler errors + * on the current tool chain. + * + * typedef task_t *os_process_handle_t; + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +typedef task_t *os_process_handle_t; +#else +typedef struct task_struct *os_process_handle_t; +#endif + +/*! + * Generic return code for functions which need such a thing. + * + * No knowledge should be assumed of the value of any of these symbols except + * that @c OS_ERROR_OK_S is guaranteed to be zero. + */ +typedef enum { + OS_ERROR_OK_S = 0, /*!< Success */ + OS_ERROR_FAIL_S = -EIO, /*!< Generic driver failure */ + OS_ERROR_NO_MEMORY_S = -ENOMEM, /*!< Failure to acquire/use memory */ + OS_ERROR_BAD_ADDRESS_S = -EFAULT, /*!< Bad address */ + OS_ERROR_BAD_ARG_S = -EINVAL, /*!< Bad input argument */ +} os_error_code; + +/*! + * Handle to a lock. + */ +#ifdef CONFIG_PREEMPT_RT +typedef raw_spinlock_t *os_lock_t; +#else +typedef spinlock_t *os_lock_t; +#endif + +/*! + * Context while locking. + */ +typedef unsigned long os_lock_context_t; + +/*! + * Declare a wait object for sleeping/waking processes. + */ +#define OS_WAIT_OBJECT(name) \ + DECLARE_WAIT_QUEUE_HEAD(name##_qh) + +/*! + * Driver registration handle + * + * Used with #os_driver_init_registration(), #os_driver_add_registration(), + * and #os_driver_complete_registration(). + */ +typedef struct { + unsigned reg_complete; /*!< TRUE if next inits succeeded. */ + dev_t dev; /*!< dev_t for register_chrdev() */ + struct file_operations fops; /*!< struct for register_chrdev() */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + struct class_simple *cs; /*!< results of class_simple_create() */ +#else + struct class *cs; /*!< results of class_create() */ +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) + struct class_device *cd; /*!< Result of class_device_create() */ +#else + struct device *cd; /*!< Result of device_create() */ +#endif + unsigned power_complete; /*!< TRUE if next inits succeeded */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + struct device_driver dd; /*!< struct for register_driver() */ +#else + struct platform_driver dd; /*!< struct for register_driver() */ +#endif + struct platform_device pd; /*!< struct for platform_register_device() */ +} os_driver_reg_t; + +/* + * Function types which can be associated with driver entry points. + * + * Note that init and shutdown are absent. + */ +/*! @{ */ +/*! Keyword for registering open() operation handler. */ +#define OS_FN_OPEN open +/*! Keyword for registering close() operation handler. */ +#define OS_FN_CLOSE release +/*! Keyword for registering read() operation handler. */ +#define OS_FN_READ read +/*! Keyword for registering write() operation handler. */ +#define OS_FN_WRITE write +/*! Keyword for registering ioctl() operation handler. */ +#define OS_FN_IOCTL ioctl +/*! Keyword for registering mmap() operation handler. */ +#define OS_FN_MMAP mmap +/*! @} */ + +/*! + * Function signature for the portable interrupt handler + * + * While it would be nice to know which interrupt is being serviced, the + * Least Common Denominator rule says that no arguments get passed in. + * + * @return Zero if not handled, non-zero if handled. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +typedef int (*os_interrupt_handler_t) (int, void *, struct pt_regs *); +#else +typedef int (*os_interrupt_handler_t) (int, void *); +#endif + +/*! + * @defgroup dkops Driver-to-Kernel Operations + * + * These are the operations which drivers should call to get the OS to perform + * services. + */ + +/*! @addtogroup dkops */ +/*! @{ */ + +/*! + * Register an interrupt handler. + * + * @param driver_name The name of the driver + * @param interrupt_id The interrupt line to monitor (type + * #os_interrupt_id_t) + * @param function The function to be called to handle an interrupt + * + * @return #os_error_code + */ +#define os_register_interrupt(driver_name, interrupt_id, function) \ + request_irq(interrupt_id, function, 0, driver_name, NULL) + +/*! + * Deregister an interrupt handler. + * + * @param interrupt_id The interrupt line to stop monitoring + * + * @return #os_error_code + */ +#define os_deregister_interrupt(interrupt_id) \ + free_irq(interrupt_id, NULL) + +/*! + * INTERNAL implementation of os_driver_init_registration() + * + * @return An os error code. + */ +inline static int os_drv_do_init_reg(os_driver_reg_t * handle) +{ + memset(handle, 0, sizeof(*handle)); + handle->fops.owner = THIS_MODULE; + handle->power_complete = FALSE; + handle->reg_complete = FALSE; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + handle->dd.name = NULL; +#else + handle->dd.driver.name = NULL; +#endif + + return OS_ERROR_OK_S; +} + +/*! + * Initialize driver registration. + * + * If the driver handles open(), close(), ioctl(), read(), write(), or mmap() + * calls, then it needs to register their location with the kernel so that they + * get associated with the device. + * + * @param handle The handle object to be used with this registration. The + * object must live (be in memory somewhere) at least until + * os_driver_remove_registration() is called. + * + * @return A handle for further driver registration, or NULL if failed. + */ +#define os_driver_init_registration(handle) \ + os_drv_do_init_reg(&handle) + +/*! + * Add a function registration to driver registration. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param name Which function is being supported. + * @param function The result of a call to a @c _REF version of one of the + * driver function signature macros + * @return void + */ +#define os_driver_add_registration(handle, name, function) \ + do {handle.fops.name = (void*)(function); } while (0) + +/*! + * Record 'power suspend' function for the device. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param function Name of function to call on power suspend request + * + * Status: Provisional + * + * @return void + */ +#define os_driver_register_power_suspend(handle, function) \ + handle.dd.suspend = function + +/*! + * Record 'power resume' function for the device. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * @param function Name of function to call on power resume request + * + * Status: Provisional + * + * @return void + */ +#define os_driver_register_resume(handle, function) \ + handle.dd.resume = function + +/*! + * INTERNAL function of the Linux port of the OS API. Implements the + * os_driver_complete_registration() function. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +inline static int os_drv_do_reg(os_driver_reg_t * handle, + unsigned major, char *driver_name) +{ + os_error_code code = OS_ERROR_NO_MEMORY_S; + char *name = kmalloc(strlen(driver_name) + 1, 0); + + if (name != NULL) { + memcpy(name, driver_name, strlen(driver_name) + 1); + code = OS_ERROR_OK_S; /* OK so far */ + /* If any chardev/POSIX routines were added, then do chrdev part */ + if (handle->fops.open || handle->fops.release + || handle->fops.read || handle->fops.write + || handle->fops.ioctl || handle->fops.mmap) { + + printk("ioctl pointer: %p. mmap pointer: %p\n", + handle->fops.ioctl, handle->fops.mmap); + + /* this method is depricated, see: + * http://lwn.net/Articles/126808/ + */ + code = + register_chrdev(major, driver_name, &handle->fops); + + /* instead something like this: */ +#if 0 + handle->dev = MKDEV(major, 0); + code = + register_chrdev_region(handle->dev, 1, driver_name); + if (code < 0) { + code = OS_ERROR_FAIL_S; + } else { + cdev_init(&handle->cdev, &handle->fops); + code = cdev_add(&handle->cdev, major, 1); + } +#endif + + if (code < 0) { + code = OS_ERROR_FAIL_S; + } else { + if (code != 0) { + /* Zero was passed in for major; code is actual value */ + handle->dev = MKDEV(code, 0); + } else { + handle->dev = MKDEV(major, 0); + } + code = OS_ERROR_OK_S; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + handle->cs = + class_simple_create(THIS_MODULE, + driver_name); + if (IS_ERR(handle->cs)) { + code = (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->cd = + class_simple_device_add(handle->cs, + handle->dev, + NULL, + driver_name); + if (IS_ERR(handle->cd)) { + class_simple_device_remove + (handle->dev); + unregister_chrdev(MAJOR + (handle->dev), + driver_name); + code = + (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->reg_complete = TRUE; + } + } +#else + handle->cs = + class_create(THIS_MODULE, driver_name); + if (IS_ERR(handle->cs)) { + code = (os_error_code) handle->cs; + handle->cs = NULL; + } else { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) + handle->cd = + class_device_create(handle->cs, + NULL, + handle->dev, + NULL, + driver_name); +#else + handle->cd = + device_create(handle->cs, NULL, + handle->dev, NULL, + driver_name); +#endif + if (IS_ERR(handle->cd)) { + class_destroy(handle->cs); + unregister_chrdev(MAJOR + (handle->dev), + driver_name); + code = + (os_error_code) handle->cs; + handle->cs = NULL; + } else { + handle->reg_complete = TRUE; + } + } +#endif + } + } + /* ... fops routine registered */ + /* Handle power management fns through separate interface */ + if ((code == OS_ERROR_OK_S) && + (handle->dd.suspend || handle->dd.resume)) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + handle->dd.name = name; + handle->dd.bus = &platform_bus_type; + code = driver_register(&handle->dd); +#else + handle->dd.driver.name = name; + handle->dd.driver.bus = &platform_bus_type; + code = driver_register(&handle->dd.driver); +#endif + if (code == OS_ERROR_OK_S) { + handle->pd.name = name; + handle->pd.id = 0; + code = platform_device_register(&handle->pd); + if (code != OS_ERROR_OK_S) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + driver_unregister(&handle->dd); +#else + driver_unregister(&handle->dd.driver); +#endif + } else { + handle->power_complete = TRUE; + } + } + } /* ... suspend or resume */ + } /* name != NULL */ + return code; +} + +/*! + * Finalize the driver registration with the kernel. + * + * Upon return from this call, the driver may begin receiving calls at the + * defined entry points. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +#define os_driver_complete_registration(handle, major, driver_name) \ + os_drv_do_reg(&handle, major, driver_name) + +/*! + * Get driver Major Number from handle after a successful registration. + * + * @param handle A handle which has completed registration. + * + * @return The major number (if any) associated with the handle. + */ +#define os_driver_get_major(handle) \ + (handle.reg_complete ? MAJOR(handle.dev) : -1) + +/*! + * INTERNAL implemention of os_driver_remove_registration. + * + * @param handle A handle initialized by #os_driver_init_registration(). + * + * @return An error code. + */ +inline static int os_drv_rmv_reg(os_driver_reg_t * handle) +{ + if (handle->reg_complete) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + if (handle->cd != NULL) { + class_simple_device_remove(handle->dev); + handle->cd = NULL; + } + if (handle->cs != NULL) { + class_simple_destroy(handle->cs); + handle->cs = NULL; + } + unregister_chrdev(MAJOR(handle->dev), handle->dd.name); +#else + if (handle->cd != NULL) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26) + class_device_destroy(handle->cs, handle->dev); +#else + device_destroy(handle->cs, handle->dev); +#endif + handle->cd = NULL; + } + if (handle->cs != NULL) { + class_destroy(handle->cs); + handle->cs = NULL; + } + unregister_chrdev(MAJOR(handle->dev), handle->dd.driver.name); +#endif + handle->reg_complete = FALSE; + } + if (handle->power_complete) { + platform_device_unregister(&handle->pd); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + driver_unregister(&handle->dd); +#else + driver_unregister(&handle->dd.driver); +#endif + handle->power_complete = FALSE; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + if (handle->dd.name != NULL) { + kfree(handle->dd.name); + handle->dd.name = NULL; + } +#else + if (handle->dd.driver.name != NULL) { + kfree(handle->dd.driver.name); + handle->dd.driver.name = NULL; + } +#endif + return OS_ERROR_OK_S; +} + +/*! + * Remove the driver's registration with the kernel. + * + * Upon return from this call, the driver not receive any more calls at the + * defined entry points (other than ISR and shutdown). + * + * @param handle A handle initialized by #os_driver_init_registration(). + * + * @return An error code. + */ +#define os_driver_remove_registration(handle) \ + os_drv_rmv_reg(&handle) + +/*! + * Register a driver with the Linux Device Model. + * + * @param driver_information The device_driver structure information + * + * @return An error code. + * + * Status: denigrated in favor of #os_driver_complete_registration() + */ +#define os_register_to_driver(driver_information) \ + driver_register(driver_information) + +/*! + * Unregister a driver from the Linux Device Model + * + * this routine unregisters from the Linux Device Model + * + * @param driver_information The device_driver structure information + * + * @return An error code. + * + * Status: Denigrated. See #os_register_to_driver(). + */ +#define os_unregister_from_driver(driver_information) \ + driver_unregister(driver_information) + +/*! + * register a device to a driver + * + * this routine registers a drivers devices to the Linux Device Model + * + * @param device_information The platform_device structure information + * + * @return An error code. + * + * Status: denigrated in favor of #os_driver_complete_registration() + */ +#define os_register_a_device(device_information) \ + platform_device_register(device_information) + +/*! + * unregister a device from a driver + * + * this routine unregisters a drivers devices from the Linux Device Model + * + * @param device_information The platform_device structure information + * + * @return An error code. + * + * Status: Denigrated. See #os_register_a_device(). + */ +#define os_unregister_a_device(device_information) \ + platform_device_unregister(device_information) + +/*! + * Print a message to console / into log file. After the @c msg argument a + * number of printf-style arguments may be added. Types should be limited to + * printf string, char, octal, decimal, and hexadecimal types. (This excludes + * pointers, and floating point). + * + * @param msg The main text of the message to be logged + * @param s The printf-style arguments which go with msg, if any + * + * @return (void) + */ +#define os_printk(...) \ + (void) printk(__VA_ARGS__) + +/*! + * Prepare a task to execute the given function. This should only be done once + * per function,, during the driver's initialization routine. + * + * @param task_fn Name of the OS_DEV_TASK() function to be created. + * + * @return an OS ERROR code. + */ +#define os_create_task(function_name) \ + OS_ERROR_OK_S + +/*! + * Schedule execution of a task. + * + * @param function_name The function associated with the task. + * + * @return (void) + */ +#define os_dev_schedule_task(function_name) \ + tasklet_schedule(&(function_name ## let)) + +/*! + * Make sure that task is no longer running and will no longer run. + * + * This function will not return until both are true. This is useful when + * shutting down a driver. + */ +#define os_dev_stop_task(function_name) \ +do { \ + tasklet_disable(&(function_name ## let)); \ + tasklet_kill(&(function_name ## let)); \ +} while (0) + +/*! + * Allocate some kernel memory + * + * @param amount Number of 8-bit bytes to allocate + * @param flags Some indication of purpose of memory (needs definition) + * + * @return Pointer to allocated memory, or NULL if failed. + */ +#define os_alloc_memory(amount, flags) \ + (void*)kmalloc(amount, flags) + +/*! + * Free some kernel memory + * + * @param location The beginning of the region to be freed. + * + * Do some OSes have separate free() functions which should be + * distinguished by passing in @c flags here, too? Don't some also require the + * size of the buffer being freed? + */ +#define os_free_memory(location) \ + kfree(location) + +/*! + * Allocate cache-coherent memory + * + * @param amount Number of bytes to allocate + * @param[out] dma_addrp Location to store physical address of allocated + * memory. + * @param flags Some indication of purpose of memory (needs + * definition). + * + * @return (virtual space) pointer to allocated memory, or NULL if failed. + * + */ +#define os_alloc_coherent(amount, dma_addrp, flags) \ + (void*)dma_alloc_coherent(NULL, amount, dma_addrp, flags) + +/*! + * Free cache-coherent memory + * + * @param size Number of bytes which were allocated. + * @param virt_addr Virtual(kernel) address of memory.to be freed, as + * returned by #os_alloc_coherent(). + * @param dma_addr Physical address of memory.to be freed, as returned + * by #os_alloc_coherent(). + * + * @return void + * + */ +#define os_free_coherent(size, virt_addr, dma_addr) \ + dma_free_coherent(NULL, size, virt_addr, dma_addr + +/*! + * Map an I/O space into kernel memory space + * + * @param start The starting address of the (physical / io space) region + * @param range_bytes The number of bytes to map + * + * @return A pointer to the mapped area, or NULL on failure + */ +#define os_map_device(start, range_bytes) \ + (void*)ioremap_nocache((start), range_bytes) + +/*! + * Unmap an I/O space from kernel memory space + * + * @param start The starting address of the (virtual) region + * @param range_bytes The number of bytes to unmap + * + * @return None + */ +#define os_unmap_device(start, range_bytes) \ + iounmap((void*)(start)) + +/*! + * Copy data from Kernel space to User space + * + * @param to The target location in user memory + * @param from The source location in kernel memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +#define os_copy_to_user(to, from, size) \ + ((copy_to_user(to, from, size) == 0) ? 0 : OS_ERROR_BAD_ADDRESS_S) + +/*! + * Copy data from User space to Kernel space + * + * @param to The target location in kernel memory + * @param from The source location in user memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +#define os_copy_from_user(to, from, size) \ + ((copy_from_user(to, from, size) == 0) ? 0 : OS_ERROR_BAD_ADDRESS_S) + +/*! + * Read a 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read8(register_address) \ + __raw_readb(register_address) + +/*! + * Write a 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write8(register_address, value) \ + __raw_writeb(value, register_address) + +/*! + * Read a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read16(register_address) \ + __raw_readw(register_address) + +/*! + * Write a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write16(register_address, value) \ + __raw_writew(value, (uint32_t*)(register_address)) + +/*! + * Read a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read32(register_address) \ + __raw_readl((uint32_t*)(register_address)) + +/*! + * Write a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write32(register_address, value) \ + __raw_writel(value, register_address) + +/*! + * Read a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +#define os_read64(register_address) \ + ERROR_UNIMPLEMENTED + +/*! + * Write a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +#define os_write64(register_address, value) \ + ERROR_UNIMPLEMENTED + +/*! + * Delay some number of microseconds + * + * Note that this is a busy-loop, not a suspension of the task/process. + * + * @param msecs The number of microseconds to delay + * + * @return void + */ +#define os_mdelay mdelay + +/*! + * Calculate virtual address from physical address + * + * @param pa Physical address + * + * @return virtual address + * + * @note this assumes that addresses are 32 bits wide + */ +#define os_va __va + +/*! + * Calculate physical address from virtual address + * + * + * @param va Virtual address + * + * @return physical address + * + * @note this assumes that addresses are 32 bits wide + */ +#define os_pa __pa + +#ifdef CONFIG_PREEMPT_RT +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +inline static os_lock_t os_lock_alloc_init(void) +{ + raw_spinlock_t *lockp; + lockp = (raw_spinlock_t *) kmalloc(sizeof(raw_spinlock_t), 0); + if (lockp) { + _raw_spin_lock_init(lockp); + } else { + printk("OS: lock init failed\n"); + } + + return lockp; +} +#else +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +inline static os_lock_t os_lock_alloc_init(void) +{ + spinlock_t *lockp; + lockp = (spinlock_t *) kmalloc(sizeof(spinlock_t), 0); + if (lockp) { + spin_lock_init(lockp); + } else { + printk("OS: lock init failed\n"); + } + + return lockp; +} +#endif /* CONFIG_PREEMPT_RT */ + +/*! + * Acquire a lock. + * + * This function should only be called from an interrupt service routine. + * + * @param lock_handle A handle to the lock to acquire. + * + * @return void + */ +#define os_lock(lock_handle) \ + spin_lock(lock_handle) + +/*! + * Unlock a lock. Lock must have been acquired by #os_lock(). + * + * @param lock_handle A handle to the lock to unlock. + * + * @return void + */ +#define os_unlock(lock_handle) \ + spin_unlock(lock_handle) + +/*! + * Acquire a lock in non-ISR context + * + * This function will spin until the lock is available. + * + * @param lock_handle A handle of the lock to acquire. + * @param context Place to save the before-lock context + * + * @return void + */ +#define os_lock_save_context(lock_handle, context) \ + spin_lock_irqsave(lock_handle, context) + +/*! + * Release a lock in non-ISR context + * + * @param lock_handle A handle of the lock to release. + * @param context Place where before-lock context was saved. + * + * @return void + */ +#define os_unlock_restore_context(lock_handle, context) \ + spin_unlock_irqrestore(lock_handle, context) + +/*! + * Deallocate a lock handle. + * + * @param lock_handle An #os_lock_t that has been allocated. + * + * @return void + */ +#define os_lock_deallocate(lock_handle) \ + kfree(lock_handle) + +/*! + * Determine process handle + * + * The process handle of the current user is returned. + * + * @return A handle on the current process. + */ +#define os_get_process_handle() \ + current + +/*! + * Send a signal to a process + * + * @param proc A handle to the target process. + * @param sig The POSIX signal to send to that process. + */ +#define os_send_signal(proc, sig) \ + send_sig(sig, proc, 0); + +/*! + * Get some random bytes + * + * @param buf The location to store the random data. + * @param count The number of bytes to store. + * + * @return void + */ +#define os_get_random_bytes(buf, count) \ + get_random_bytes(buf, count) + +/*! + * Go to sleep on an object. + * + * @param object The object on which to sleep + * @param condition An expression to check for sleep completion. Must be + * coded so that it can be referenced more than once inside + * macro, i.e., no ++ or other modifying expressions. + * @param atomic Non-zero if sleep must not return until condition. + * + * @return error code -- OK or sleep interrupted?? + */ +#define os_sleep(object, condition, atomic) \ +({ \ + DEFINE_WAIT(_waitentry_); \ + os_error_code code = OS_ERROR_OK_S; \ + \ + while (!(condition)) { \ + prepare_to_wait(&(object##_qh), &_waitentry_, \ + atomic ? 0 : TASK_INTERRUPTIBLE); \ + if (!(condition)) { \ + schedule(); \ + } \ + \ + finish_wait(&(object##_qh), &_waitentry_); \ + \ + if (!atomic && signal_pending(current)) { \ + code = OS_ERROR_FAIL_S; /* NEED SOMETHING BETTER */ \ + break; \ + } \ + }; \ + \ + code; \ +}) + +/*! + * Wake up whatever is sleeping on sleep object + * + * @param object The object on which things might be sleeping + * + * @return none + */ +#define os_wake_sleepers(object) \ + wake_up_interruptible(&(object##_qh)); + + /*! @} *//* dkops */ + +/****************************************************************************** + * Function signature-generating macros + *****************************************************************************/ + +/*! + * @defgroup drsigs Driver Signatures + * + * These macros will define the entry point signatures for interrupt handlers; + * driver initialization and shutdown; device open/close; etc. + * + * There are two versions of each macro for a given Driver Entry Point. The + * first version is used to define a function and its implementation in the + * driver.c file, e.g. #OS_DEV_INIT(). + * + * The second form is used whenever a forward declaration (prototype) is + * needed. It has the letters @c _DCL appended to the name of the defintion + * function, and takes only the first two arguments (driver_name and + * function_name). These are not otherwise mentioned in this documenation. + * + * There is a third form used when a reference to a function is required, for + * instance when passing the routine as a pointer to a function. It has the + * letters @c _REF appended to it, and takes only the first two arguments + * (driver_name and function_name). These functions are not otherwise + * mentioned in this documentation. + * + * (Note that these two extra forms are required because of the + * possibility/likelihood of having a 'wrapper function' which invokes the + * generic function with expected arguments. An alternative would be to have a + * generic function which isn't able to get at any arguments directly, but + * would be equipped with macros which could get at information passed in. + * + * Example: + * + * (in a header file) + * @code + * OS_DEV_INIT_DCL(widget, widget_init); + * @endcode + * + * (in an implementation file) + * @code + * OS_DEV_INIT(widget, widget_init) + * { + * os_dev_init_return(TRUE); + * } + * @endcode + * + */ + +/*! @addtogroup drsigs */ +/*! @{ */ + +/*! + * Define a function which will handle device initialization + * + * This is tne driver initialization routine. This is normally where the + * part would be initialized; queues, locks, interrupts handlers defined; + * long-term dynamic memory allocated for driver use; etc. + * + * @param function_name The name of the portable initialization function. + * + * @return A call to #os_dev_init_return() + * + */ +#define OS_DEV_INIT(function_name) \ +module_init(function_name); \ +static int __init function_name (void) + +/*! Make declaration for driver init function. + * @param function_name foo + */ +#define OS_DEV_INIT_DCL(function_name) \ +static int __init function_name (void); + +/*! + * Generate a function reference to the driver's init function. + * @param function_name Name of the OS_DEV_INIT() function. + * + * @return A function pointer. + */ +#define OS_DEV_INIT_REF(function_name) \ +function_name + +/*! + * Define a function which will handle device shutdown + * + * This is the inverse of the #OS_DEV_INIT() routine. + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + * + */ +#define OS_DEV_SHUTDOWN(function_name) \ +module_exit(function_name); \ +static void function_name(void) + +/*! + * Generate a function reference to the driver's shutdown function. + * @param function_name Name of the OS_DEV_HUSTDOWN() function. + * + * @return A function pointer. + */ +#define OS_DEV_SHUTDOWN_DCL(function_name) \ +static void function_name(void); + +/*! + * Generate a reference to driver's shutdown function + * @param function_name Name of the OS_DEV_HUSTDOWN() function. +*/ + +#define OS_DEV_SHUTDOWN_REF(function_name) \ +function_name + +/*! + * Define a function which will open the device for a user. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_) + +/*! + * Declare prototype for an open() function. + * + * @param function_name The name of the OS_DEV_OPEN() function. + */ +#define OS_DEV_OPEN_DCL(function_name) \ +OS_DEV_OPEN(function_name); + +/*! + * Generate a function reference to the driver's open() function. + * @param function_name Name of the OS_DEV_OPEN() function. + * + * @return A function pointer. + */ +#define OS_DEV_OPEN_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's ioctl() request + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_, \ + unsigned int cmd_, unsigned long data_) + +/*! Boo. */ +#define OS_DEV_IOCTL_DCL(function_name) \ +OS_DEV_IOCTL(function_name); + +/*! + * Generate a function reference to the driver's ioctl() function. + * @param function_name Name of the OS_DEV_IOCTL() function. + * + * @return A function pointer. + */ +#define OS_DEV_IOCTL_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's mmap() request + * + * @param function_name The name of the driver mmap() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_MMAP(function_name) \ +int function_name(struct file* file_p_, struct vm_area_struct* vma_) + +#define OS_DEV_MMAP_DCL(function_name) \ +OS_DEV_MMAP(function_name); + +#define OS_DEV_MMAP_REF(function_name) \ +function_name + +/* Retrieve the context to the memory structure that is to be MMAPed */ +#define os_mmap_memory_ctx() (vma_) + +/* Determine the size of the requested MMAP region*/ +#define os_mmap_memory_size() (vma_->vm_end - vma_->vm_start) + +/* Determine the base address of the requested MMAP region*/ +#define os_mmap_user_base() (vma_->vm_start) + +/*! + * Declare prototype for an read() function. + * + * @param function_name The name of the driver read function. + */ +#define OS_DEV_READ_DCL(function_name) \ +OS_DEV_READ(function_name); + +/*! + * Generate a function reference to the driver's read() routine + * @param function_name Name of the OS_DEV_READ() function. + * + * @return A function pointer. + */ +#define OS_DEV_READ_REF(function_name) \ +function_name + +/*! + * Define a function which will handle a user's write() request + * + * @param function_name The name of the driver write() function + * + * @return A call to #os_dev_write_return() + */ +#define OS_DEV_WRITE(function_name) \ +static ssize_t function_name(struct file* file_p_, char* user_buffer_, \ + size_t count_bytes_, loff_t* file_position_) + +/*! + * Declare prototype for an write() function. + * + * @param function_name The name of the driver write function. + */ +#define OS_DEV_WRITE_DCL(function_name) \ +OS_DEV_WRITE(function_name); + +/*! + * Generate a function reference to the driver's write() routine + * @param function_name Name of the OS_DEV_WRITE() function. + * + * @return A function pointer. + */ +#define OS_DEV_WRITE_REF(function_name) \ +function_name + +/*! + * Define a function which will close the device - opposite of OS_DEV_OPEN() + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE(function_name) \ +static int function_name(struct inode* inode_p_, struct file* file_p_) + +/*! + * Declare prototype for an close() function + * + * @param function_name The name of the driver close() function. + */ +#define OS_DEV_CLOSE_DCL(function_name) \ +OS_DEV_CLOSE(function_name); + +/*! + * Generate a function reference to the driver's close function. + * @param function_name Name of the OS_DEV_CLOSE() function. + * + * @return A function pointer. + */ +#define OS_DEV_CLOSE_REF(function_name) \ +function_name + +/*! + * Define a function which will handle an interrupt + * + * No arguments are available to the generic function. It must not invoke any + * OS functions which are illegal in a ISR. It gets no parameters, and must + * have a call to #os_dev_isr_return() instead of any/all return statements. + * + * Example: + * @code + * OS_DEV_ISR(widget) + * { + * os_dev_isr_return(1); + * } + * @endcode + * + * @param function_name The name of the driver ISR function + * + * @return A call to #os_dev_isr_return() + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +#define OS_DEV_ISR(function_name) \ +static irqreturn_t function_name(int N1_, void* N2_, struct pt_regs* N3_) +#else +#define OS_DEV_ISR(function_name) \ +static irqreturn_t function_name(int N1_, void* N2_) +#endif + +/*! + * Declare prototype for an ISR function. + * + * @param function_name The name of the driver ISR function. + */ +#define OS_DEV_ISR_DCL(function_name) \ +OS_DEV_ISR(function_name); + +/*! + * Generate a function reference to the driver's interrupt service routine + * @param function_name Name of the OS_DEV_ISR() function. + * + * @return A function pointer. + */ +#define OS_DEV_ISR_REF(function_name) \ +function_name + +/*! + * Define a function which will operate as a background task / bottom half. + * + * Tasklet stuff isn't strictly limited to 'Device drivers', but leave it + * this namespace anyway. + * + * @param function_name The name of this background task function + * + * @return A call to #os_dev_task_return() + */ +#define OS_DEV_TASK(function_name) \ +static void function_name(unsigned long data_) + +/*! + * Declare prototype for a background task / bottom half function + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK_DCL(function_name) \ +OS_DEV_TASK(function_name); \ +DECLARE_TASKLET(function_name ## let, function_name, 0); + +/*! + * Generate a reference to an #OS_DEV_TASK() function + * + * @param function_name The name of the task being referenced. + */ +#define OS_DEV_TASK_REF(function_name) \ + (function_name ## let) + + /*! @} *//* drsigs */ + +/***************************************************************************** + * Functions/Macros for returning values from Driver Signature routines + *****************************************************************************/ + +/*! + * Return from the #OS_DEV_INIT() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_init_return(code) \ + return code + +/*! + * Return from the #OS_DEV_SHUTDOWN() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_shutdown_return(code) \ + return + +/*! + * Return from the #OS_DEV_ISR() function + * + * The function should verify that it really was supposed to be called, + * and that its device needed attention, in order to properly set the + * return code. + * + * @param code non-zero if interrupt handled, zero otherwise. + * + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +#define os_dev_isr_return(code) \ +do { \ + /* Unused warnings */ \ + (void)N1_; \ + (void)N2_; \ + (void)N3_; \ + \ + return IRQ_RETVAL(code); \ +} while (0) +#else +#define os_dev_isr_return(code) \ +do { \ + /* Unused warnings */ \ + (void)N1_; \ + (void)N2_; \ + \ + return IRQ_RETVAL(code); \ +} while (0) +#endif + +/*! + * Return from the #OS_DEV_OPEN() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_open_return(code) \ +do { \ + int retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_IOCTL() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_ioctl_return(code) \ +do { \ + int retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + (void)cmd_; \ + (void)data_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_READ() function + * + * @param code Number of bytes read, or an error code to report failure. + * + */ +#define os_dev_read_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)file_p_; \ + (void)user_buffer_; \ + (void)count_bytes_; \ + (void)file_position_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_WRITE() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +#define os_dev_write_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)file_p_; \ + (void)user_buffer_; \ + (void)count_bytes_; \ + (void)file_position_; \ + \ + return retcode; \ +} while (0) + +/*! + * Return from the #OS_DEV_CLOSE() function + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_close_return(code) \ +do { \ + ssize_t retcode = code; \ + \ + /* get rid of 'unused parameter' warnings */ \ + (void)inode_p_; \ + (void)file_p_; \ + \ + return retcode; \ +} while (0) + +/*! + * Start the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a label for + * the os_dev_task_return() call. + * + * @return none + */ +#define os_dev_task_begin() + +/*! + * Return from the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a sleep followed + * by a jump back to the os_dev_task_begin() call. + * + * @param code An error code to report success or failure. + * + */ +#define os_dev_task_return(code) \ +do { \ + /* Unused warnings */ \ + (void)data_; \ + \ + return; \ +} while (0) + +/***************************************************************************** + * Functions/Macros for accessing arguments from Driver Signature routines + *****************************************************************************/ + +/*! @defgroup drsigargs Functions for Getting Arguments in Signature functions + * + */ +/* @addtogroup @drsigargs */ +/*! @{ */ +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting read + * (permission) + */ +#define os_dev_is_flag_read() \ + (file_p_->f_mode & FMODE_READ) + +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting write + * (permission) + */ +#define os_dev_is_flag_write() \ + (file_p_->f_mode & FMODE_WRITE) + +/*! + * Used in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines to check whether user is requesting non-blocking + * I/O. + */ +#define os_dev_is_flag_nonblock() \ + (file_p_->f_flags & (O_NONBLOCK | O_NDELAY)) + +/*! + * Used in #OS_DEV_OPEN() and #OS_DEV_CLOSE() to determine major device being + * accessed. + */ +#define os_dev_get_major() \ + (imajor(inode_p_)) + +/*! + * Used in #OS_DEV_OPEN() and #OS_DEV_CLOSE() to determine minor device being + * accessed. + */ +#define os_dev_get_minor() \ + (iminor(inode_p_)) + +/*! + * Used in #OS_DEV_IOCTL() to determine which operation the user wants + * performed. + * + * @return Value of the operation. + */ +#define os_dev_get_ioctl_op() \ + (cmd_) + +/*! + * Used in #OS_DEV_IOCTL() to return the associated argument for the desired + * operation. + * + * @return A value which can be cast to a struct pointer or used as + * int/long. + */ +#define os_dev_get_ioctl_arg() \ + (data_) + +/*! + * Used in OS_DEV_READ() and OS_DEV_WRITE() routines to access the requested + * byte count. + * + * @return (unsigned) a count of bytes + */ +#define os_dev_get_count() \ + ((unsigned)count_bytes_) + +/*! + * Used in OS_DEV_READ() and OS_DEV_WRITE() routines to return the pointer + * byte count. + * + * @return char* pointer to user buffer + */ +#define os_dev_get_user_buffer() \ + ((void*)user_buffer_) + +/*! + * Used in OS_DEV_READ(), OS_DEV_WRITE(), and OS_DEV_IOCTL() routines to + * get the POSIX flags field for the associated open file). + * + * @return The flags associated with the file. + */ +#define os_dev_get_file_flags() \ + (file_p_->f_flags) + +/*! + * Set the driver's private structure associated with this file/open. + * + * Generally used during #OS_DEV_OPEN(). See #os_dev_get_user_private(). + * + * @param struct_p The driver data structure to associate with this user. + */ +#define os_dev_set_user_private(struct_p) \ + file_p_->private_data = (void*)(struct_p) + +/*! + * Get the driver's private structure associated with this file. + * + * May be used during #OS_DEV_OPEN(), #OS_DEV_READ(), #OS_DEV_WRITE(), + * #OS_DEV_IOCTL(), and #OS_DEV_CLOSE(). See #os_dev_set_user_private(). + * + * @return The driver data structure to associate with this user. + */ +#define os_dev_get_user_private() \ + ((void*)file_p_->private_data) + +/*! + * Get the IRQ associated with this call to the #OS_DEV_ISR() function. + * + * @return The IRQ (integer) interrupt number. + */ +#define os_dev_get_irq() \ + N1_ + + /*! @} *//* drsigargs */ + +/*! + * @defgroup cacheops Cache Operations + * + * These functions are for synchronizing processor cache with RAM. + */ +/*! @addtogroup cacheops */ +/*! @{ */ + +/*! + * Flush and invalidate all cache lines. + */ +#if 0 +#define os_flush_cache_all() \ + flush_cache_all() +#else +/* Call ARM fn directly, in case L2cache=on3 not set */ +#define os_flush_cache_all() \ + v6_flush_kern_cache_all_L2() + +/*! + * ARM-routine to flush all cache. Defined here, because it exists in no + * easy-access header file. ARM-11 with L210 cache only! + */ +extern void v6_flush_kern_cache_all_L2(void); +#endif + +/* + * These macros are using part of the Linux DMA API. They rely on the + * map function to do nothing more than the equivalent clean/inv/flush + * operation at the time of the mapping, and do nothing at an unmapping + * call, which the Sahara driver code will never invoke. + */ + +/*! + * Clean a range of addresses from the cache. That is, write updates back + * to (RAM, next layer). + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21) +#define os_cache_clean_range(start,len) \ + dma_map_single(NULL, (void*)start, len, DMA_TO_DEVICE) +#else +#define os_cache_clean_range(start,len) \ +{ \ + void *s = (void*)start; \ + void *e = s + len; \ + dmac_clean_range(s, e); \ + outer_clean_range(__pa(s), __pa(e)); \ +} +#endif + +/*! + * Invalidate a range of addresses in the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21) +#define os_cache_inv_range(start,len) \ + dma_map_single(NULL, (void*)start, len, DMA_FROM_DEVICE) +#else +#define os_cache_inv_range(start,len) \ +{ \ + void *s = (void*)start; \ + void *e = s + len; \ + dmac_inv_range(s, e); \ + outer_inv_range(__pa(s), __pa(e)); \ +} +#endif + +/*! + * Flush a range of addresses from the cache. That is, perform clean + * and invalidate + * + * @param start Starting virtual address + * @param len Number of bytes to flush + * + * @return void + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21) +#define os_cache_flush_range(start,len) \ + dma_map_single(NULL, (void*)start, len, DMA_BIDIRECTIONAL) +#else +#define os_cache_flush_range(start,len) \ +{ \ + void *s = (void*)start; \ + void *e = s + len; \ + dmac_flush_range(s, e); \ + outer_flush_range(__pa(s), __pa(e)); \ +} +#endif + + /*! @} *//* cacheops */ + +#endif /* LINUX_PORT_H */ diff --git a/drivers/mxc/security/sahara2/include/platform_abstractions.h b/drivers/mxc/security/sahara2/include/platform_abstractions.h new file mode 100644 index 000000000000..c2f9c489eb0b --- /dev/null +++ b/drivers/mxc/security/sahara2/include/platform_abstractions.h @@ -0,0 +1,15 @@ +/* + * Copyright 2005-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 platform_abstractions.h + */ diff --git a/drivers/mxc/security/sahara2/include/portable_os.h b/drivers/mxc/security/sahara2/include/portable_os.h new file mode 100644 index 000000000000..e904b3222cbb --- /dev/null +++ b/drivers/mxc/security/sahara2/include/portable_os.h @@ -0,0 +1,1453 @@ +/* + * Copyright 2004-2009 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 + */ + +#ifndef PORTABLE_OS_H +#define PORTABLE_OS_H + +/***************************************************************************/ + +/* + * Add support for your target OS by checking appropriate flags and then + * including the appropriate file. Don't forget to document the conditions + * in the later documentation section at the beginning of the + * DOXYGEN_PORTABLE_OS_DOC. + */ + +#if defined(LINUX_KERNEL) + +#include "linux_port.h" + +#elif defined(PORTABLE_OS) + +#include "check_portability.h" + +#else + +#error Target OS unsupported or unspecified + +#endif + + +/***************************************************************************/ + +/*! + * @file portable_os.h + * + * This file should be included by portable driver code in order to gain access + * to the OS-specific header files. It is the only OS-related header file that + * the writer of a portable driver should need. + * + * This file also contains the documentation for the common API. + * + * Begin reading the documentation for this file at the @ref index "main page". + * + */ + +/*! + * @if USE_MAINPAGE + * @mainpage Generic OS API for STC Drivers + * @endif + * + * @section intro_sec Introduction + * + * This defines the API / kernel programming environment for portable drivers. + * + * This API is broken up into several functional areas. It greatly limits the + * choices of a device-driver author, but at the same time should allow for + * greater portability of the resulting code. + * + * Each kernel-to-driver function (initialization function, interrupt service + * routine, etc.) has a 'portable signature' which must be used, and a specific + * function which must be called to generate the return statement. There is + * one exception, a background task or "bottom half" routine, which instead has + * a specific structure which must be followed. These signatures and function + * definitions are found in @ref drsigs. + * + * None of these kernel-to-driver functions seem to get any arguments passed to + * them. Instead, there are @ref drsigargs which allow one of these functions + * to get at fairly generic parts of its calling arguments, if there are any. + * + * Almost every driver will have some need to call the operating system + * @ref dkops is the list of services which are available to the driver. + * + * + * @subsection warn_sec Warning + * + * The specifics of the types, values of the various enumerations + * (unless specifically stated, like = 0), etc., are only here for illustrative + * purposes. No attempts should be made to make use of any specific knowledge + * gleaned from this documentation. These types are only meant to be passed in + * and out of the API, and their contents are to be handled only by the + * provided OS-specific functions. + * + * Also, note that the function may be provided as macros in some + * implementations, or vice versa. + * + * + * @section dev_sec Writing a Portable Driver + * + * First off, writing a portable driver means calling no function in an OS + * except what is available through this header file. + * + * Secondly, all OS-called functions in your driver must be invoked and + * referenced through the signature routines. + * + * Thirdly, there might be some rules which you can get away with ignoring or + * violating on one OS, yet will cause your code not to be portable to a + * different OS. + * + * + * @section limit_sec Limitations + * + * This API is not expected to handle every type of driver which may be found + * in an operating system. For example, it will not be able to handle the + * usual design for something like a UART driver, where there are multiple + * logical devices to access, because the design usually calls for some sort of + * indication to the #OS_DEV_TASK() function or OS_DEV_ISR() to indicate which + * channel is to be serviced by that instance of the task/function. This sort + * of argument is missing in this API for functions like os_dev_schedule_task() and + * os_register_interrupt(). + * + * + * @section port_guide Porting Guidelines + * + * This section is intended for a developer who needs to port the header file + * to an operating system which is not yet supported. + * + * This interface allows for a lot of flexibility when it comes to porting to + * an operating systems device driver interface. There are three main areas to + * examine: The use of Driver Routine Signatures, the use of Driver Argument + * Access functions, the Calls to Kernel Functions, and Data Types. + * + * + * @subsection port_sig Porting Driver Routine Signatures + * + * The three macros for each function (e.g. #OS_DEV_INIT(), #OS_DEV_INIT_DCL(), + * and #OS_DEV_INIT_REF()) allow the flexibility of having a 'wrapper' function + * with the OS-required signature, which would then call the user's function + * with some different signature. + * + * The first form would lay down the wrapper function, followed by the + * signature for the user function. The second form would lay down just the + * signatures for both functions, and the last function would reference the + * wrapper function, since that is the interface function called by the OS. + * + * Note that the driver author has no visibility at all to the signature of the + * routines. The author can access arguments only through a limited set of + * functions, and must return via another function. + * + * The Return Functions allow a lot of flexibility in converting the return + * value, or not returning a value at all. These will likely be implemented as + * macros. + * + * + * @subsection port_arg Porting Driver Argument Access Functions + * + * The signatures defined by the guide will usually be replaced with macro + * definitions. + * + * + * @subsection port_dki Porting Calls to Kernel Functions + * + * The signatures defined by the guide may be replaced with macro definitions, + * if that makes more sense. + * + * Implementors are free to ignore arguments which are not applicable to their + * OS. + * + * @subsection port_datatypes Porting Data Types + * + * + */ + +/*************************************************************************** + * Compile flags + **************************************************************************/ + +/* + * This compile flag should only be turned on when running doxygen to generate + * the API documentation. + */ +#ifdef DOXYGEN_PORTABLE_OS_DOC + +/*! + * @todo module_init()/module_cleanup() for Linux need to be added to OS + * abstractions. Also need EXPORT_SYMBOL() equivalent?? + * + */ + +/* Drop OS differentation documentation here */ + +/*! + * \#define this flag to build your driver as a Linux driver + */ +#define LINUX + +/* end OS differentation documentation */ + +/*! + * Symbol to give version number of the implementation file. Earliest + * definition is in version 1.1, with value 101 (to mean version 1.1) + */ +#define PORTABLE_OS_VERSION 101 + +/* + * NOTICE: The following definitions (the rest of the file) are not meant ever + * to be compiled. Instead, they are the documentation for the portable OS + * API, to be used by driver writers. + * + * Each individual OS port will define each of these types, functions, or + * macros as appropriate to the target OS. This is why they are under the + * DOXYGEN_PORTABLE_OS_DOC flag. + */ + +/*************************************************************************** + * Type definitions + **************************************************************************/ + +/*! + * Type used for registering and deregistering interrupts. + * + * This is typically an interrupt channel number. + */ +typedef int os_interrupt_id_t; + +/*! + * Type used as handle for a process + * + * See #os_get_process_handle() and #os_send_signal(). + */ +typedef int os_process_handle_t; + +/*! + * Generic return code for functions which need such a thing. + * + * No knowledge should be assumed of the value of any of these symbols except + * that @c OS_ERROR_OK_S is guaranteed to be zero. + * + * @todo Any other named values? What about (-EAGAIN? -ERESTARTSYS? Are they + * too Linux/Unix-specific read()/write() return values) ? + */ +typedef enum { + OS_ERROR_OK_S = 0, /*!< Success */ + OS_ERROR_FAIL_S, /*!< Generic driver failure */ + OS_ERROR_NO_MEMORY_S, /*!< Failure to acquire/use memory */ + OS_ERROR_BAD_ADDRESS_S, /*!< Bad address */ + OS_ERROR_BAD_ARG_S /*!< Bad input argument */ +} os_error_code; + +/*! + * Handle to a lock. + */ +typedef int *os_lock_t; + +/*! + * Context while locking. + */ +typedef int os_lock_context_t; + +/*! + * An object which can be slept on and later used to wake any/all sleepers. + */ +typedef int os_sleep_object_t; + +/*! + * Driver registration handle + */ +typedef void *os_driver_reg_t; + +/*! + * Function signature for an #OS_DEV_INIT() function. + * + * @return A call to os_dev_init_return() function. + */ +typedef void (*os_init_function_t) (void); + +/*! + * Function signature for an #OS_DEV_SHUTDOWN() function. + * + * @return A call to os_dev_shutdown_return() function. + */ +typedef void (*os_shutdown_function_t) (void); + +/*! + * Function signature for a user-driver function. + * + * @return A call to the appropriate os_dev_xxx_return() function. + */ +typedef void (*os_user_function_t) (void); + +/*! + * Function signature for the portable interrupt handler + * + * While it would be nice to know which interrupt is being serviced, the + * Least Common Denominator rule says that no arguments get passed in. + * + * @return A call to #os_dev_isr_return() + */ +typedef void (*os_interrupt_handler_t) (void); + +/*! + * Function signature for a task function + * + * Many task function definitions get some sort of generic argument so that the + * same function can run across many (queues, channels, ...) as separate task + * instances. This has been left out of this API. + * + * This function must be structured as documented by #OS_DEV_TASK(). + * + */ +typedef void (*os_task_fn_t) (void); + +/*! + * Function types which can be associated with driver entry points. These are + * used in os_driver_add_registration(). + * + * Note that init and shutdown are absent. + */ +typedef enum { + OS_FN_OPEN, /*!< open() operation handler. */ + OS_FN_CLOSE, /*!< close() operation handler. */ + OS_FN_READ, /*!< read() operation handler. */ + OS_FN_WRITE, /*!< write() operation handler. */ + OS_FN_IOCTL, /*!< ioctl() operation handler. */ + OS_FN_MMAP /*!< mmap() operation handler. */ +} os_driver_fn_t; + +/*************************************************************************** + * Driver-to-Kernel Operations + **************************************************************************/ + +/*! + * @defgroup dkops Driver-to-Kernel Operations + * + * These are the operations which drivers should call to get the OS to perform + * services. + */ + +/*! @addtogroup dkops */ +/*! @{ */ + +/*! + * Register an interrupt handler. + * + * @param driver_name The name of the driver + * @param interrupt_id The interrupt line to monitor (type + * #os_interrupt_id_t) + * @param function The function to be called to handle an interrupt + * + * @return #os_error_code + */ +os_error_code os_register_interrupt(char *driver_name, + os_interrupt_id_t interrupt_id, + os_interrupt_handler_t function); + +/*! + * Deregister an interrupt handler. + * + * @param interrupt_id The interrupt line to stop monitoring + * + * @return #os_error_code + */ +os_error_code os_deregister_interrupt(os_interrupt_id_t interrupt_id); + +/*! + * Initialize driver registration. + * + * If the driver handles open(), close(), ioctl(), read(), write(), or mmap() + * calls, then it needs to register their location with the kernel so that they + * get associated with the device. + * + * @param handle The handle object to be used with this registration. The + * object must live (be in memory somewhere) at least until + * os_driver_remove_registration() is called. + * + * @return An os error code. + */ +os_error_code os_driver_init_registration(os_driver_reg_t handle); + +/*! + * Add a function registration to driver registration. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param name Which function is being supported. + * @param function The result of a call to a @c _REF version of one of the + * driver function signature macros + * driver function signature macros + * @return void + */ +void os_driver_add_registration(os_driver_reg_t handle, os_driver_fn_t name, + void *function); + +/*! + * Finalize the driver registration with the kernel. + * + * Upon return from this call, the driver may begin receiving calls at the + * defined entry points. + * + * @param handle The handle used with #os_driver_init_registration(). + * @param major The major device number to be associated with the driver. + * If this value is zero, a major number may be assigned. + * See #os_driver_get_major() to determine final value. + * #os_driver_remove_registration(). + * @param driver_name The driver name. Can be used as part of 'device node' + * name on platforms which support such a feature. + * + * @return An error code + */ +os_error_code os_driver_complete_registration(os_driver_reg_t handle, + int major, char *driver_name); + +/*! + * Get driver Major Number from handle after a successful registration. + * + * @param handle A handle which has completed registration. + * + * @return The major number (if any) associated with the handle. + */ +uint32_t os_driver_get_major(os_driver_reg_t handle); + +/*! + * Remove the driver's registration with the kernel. + * + * Upon return from this call, the driver not receive any more calls at the + * defined entry points (other than ISR and shutdown). + * + * @param major The major device number to be associated with the driver. + * @param driver_name The driver name + * + * @return An error code. + */ +os_error_code os_driver_remove_registration(int major, char *driver_name); + +/*! + * Print a message to console / into log file. After the @c msg argument a + * number of printf-style arguments may be added. Types should be limited to + * printf string, char, octal, decimal, and hexadecimal types. (This excludes + * pointers, and floating point). + * + * @param msg The message to print to console / system log + * + * @return (void) + */ +void os_printk(char *msg, ...); + +/*! + * Allocate some kernel memory + * + * @param amount Number of 8-bit bytes to allocate + * @param flags Some indication of purpose of memory (needs definition) + * + * @return Pointer to allocated memory, or NULL if failed. + */ +void *os_alloc_memory(unsigned amount, int flags); + +/*! + * Free some kernel memory + * + * @param location The beginning of the region to be freed. + * + * Do some OSes have separate free() functions which should be + * distinguished by passing in @c flags here, too? Don't some also require the + * size of the buffer being freed? Perhaps separate routines for each + * alloc/free pair (DMAable, etc.)? + */ +void os_free_memory(void *location); + +/*! + * Allocate cache-coherent memory + * + * @param amount Number of bytes to allocate + * @param[out] dma_addrp Location to store physical address of allocated + * memory. + * @param flags Some indication of purpose of memory (needs + * definition). + * + * @return (virtual space) pointer to allocated memory, or NULL if failed. + * + */ +void *os_alloc_coherent(unsigned amount, uint32_t * dma_addrp, int flags); + +/*! + * Free cache-coherent memory + * + * @param size Number of bytes which were allocated. + * @param[out] virt_addr Virtual(kernel) address of memory.to be freed, as + * returned by #os_alloc_coherent(). + * @param[out] dma_addr Physical address of memory.to be freed, as returned + * by #os_alloc_coherent(). + * + * @return void + * + */ +void os_free_coherent(unsigned size, void *virt_addr, uint32_t dma_addr); + +/*! + * Map an I/O space into kernel memory space + * + * @param start The starting address of the (physical / io space) region + * @param range_bytes The number of bytes to map + * + * @return A pointer to the mapped area, or NULL on failure + */ +void *os_map_device(uint32_t start, unsigned range_bytes); + +/*! + * Unmap an I/O space from kernel memory space + * + * @param start The starting address of the (virtual) region + * @param range_bytes The number of bytes to unmap + * + * @return None + */ +void os_unmap_device(void *start, unsigned range_bytes); + +/*! + * Copy data from Kernel space to User space + * + * @param to The target location in user memory + * @param from The source location in kernel memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +os_error_code os_copy_to_user(void *to, void *from, unsigned size); + +/*! + * Copy data from User space to Kernel space + * + * @param to The target location in kernel memory + * @param from The source location in user memory + * @param size The number of bytes to be copied + * + * @return #os_error_code + */ +os_error_code os_copy_from_user(void *to, void *from, unsigned size); + +/*! + * Read an 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint8_t os_read8(uint8_t * register_address); + +/*! + * Write an 8-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write8(uint8_t * register_address, uint8_t value); + +/*! + * Read a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint16_t os_read16(uint16_t * register_address); + +/*! + * Write a 16-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write16(uint16_t * register_address, uint16_t value); + +/*! + * Read a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint32_t os_read32(uint32_t * register_address); + +/*! + * Write a 32-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write32(uint32_t * register_address, uint32_t value); + +/*! + * Read a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @return The value in the register + */ +uint64_t os_read64(uint64_t * register_address); + +/*! + * Write a 64-bit device register + * + * @param register_address The (bus) address of the register to write to + * @param value The value to write into the register + */ +void os_write64(uint64_t * register_address, uint64_t value); + +/*! + * Prepare a task to execute the given function. This should only be done once + * per task, during the driver's initialization routine. + * + * @param task_fn Name of the OS_DEV_TASK() function to be created. + * + * @return an OS ERROR code. + */ +os_error os_create_task(os_task_fn_t * task_fn); + +/*! + * Run the task associated with an #OS_DEV_TASK() function + * + * The task will begin execution sometime after or during this call. + * + * @param task_fn Name of the OS_DEV_TASK() function to be scheduled. + * + * @return void + */ +void os_dev_schedule_task(os_task_fn_t * task_fn); + +/*! + * Make sure that task is no longer running and will no longer run. + * + * This function will not return until both are true. This is useful when + * shutting down a driver. + * + * @param task_fn Name of the OS_DEV_TASK() funciton to be stopped. + * + */ +void os_stop_task(os_task_fn_t * task_fn); + +/*! + * Delay some number of microseconds + * + * Note that this is a busy-loop, not a suspension of the task/process. + * + * @param msecs The number of microseconds to delay + * + * @return void + */ +void os_mdelay(unsigned long msecs); + +/*! + * Calculate virtual address from physical address + * + * @param pa Physical address + * + * @return virtual address + * + * @note this assumes that addresses are 32 bits wide + */ +void *os_va(uint32_t pa); + +/*! + * Calculate physical address from virtual address + * + * + * @param va Virtual address + * + * @return physical address + * + * @note this assumes that addresses are 32 bits wide + */ +uint32_t os_pa(void *va); + +/*! + * Allocate and initialize a lock, returning a lock handle. + * + * The lock state will be initialized to 'unlocked'. + * + * @return A lock handle, or NULL if an error occurred. + */ +os_lock_t os_lock_alloc_init(void); + +/*! + * Acquire a lock. + * + * This function should only be called from an interrupt service routine. + * + * @param lock_handle A handle to the lock to acquire. + * + * @return void + */ +void os_lock(os_lock_t lock_handle); + +/*! + * Unlock a lock. Lock must have been acquired by #os_lock(). + * + * @param lock_handle A handle to the lock to unlock. + * + * @return void + */ +void os_unlock(os_lock_t lock_handle); + +/*! + * Acquire a lock in non-ISR context + * + * This function will spin until the lock is available. + * + * @param lock_handle A handle of the lock to acquire. + * @param context Place to save the before-lock context + * + * @return void + */ +void os_lock_save_context(os_lock_t lock_handle, os_lock_context_t context); + +/*! + * Release a lock in non-ISR context + * + * @param lock_handle A handle of the lock to release. + * @param context Place where before-lock context was saved. + * + * @return void + */ +void os_unlock_restore_context(os_lock_t lock_handle, + os_lock_context_t context); + +/*! + * Deallocate a lock handle. + * + * @param lock_handle An #os_lock_t that has been allocated. + * + * @return void + */ +void os_lock_deallocate(os_lock_t lock_handle); + +/*! + * Determine process handle + * + * The process handle of the current user is returned. + * + * @return A handle on the current process. + */ +os_process_handle_t os_get_process_handle(); + +/*! + * Send a signal to a process + * + * @param proc A handle to the target process. + * @param sig The POSIX signal to send to that process. + */ +void os_send_signal(os_process_handle_t proc, int sig); + +/*! + * Get some random bytes + * + * @param buf The location to store the random data. + * @param count The number of bytes to store. + * + * @return void + */ +void os_get_random_bytes(void *buf, unsigned count); + +/*! + * Go to sleep on an object. + * + * Example: code = os_sleep(my_queue, available_count == 0, 0); + * + * @param object The object on which to sleep + * @param condition An expression to check for sleep completion. Must be + * coded so that it can be referenced more than once inside + * macro, i.e., no ++ or other modifying expressions. + * @param atomic Non-zero if sleep must not return until condition. + * + * @return error code -- OK or sleep interrupted?? + */ +os_error_code os_sleep(os_sleep_object_t object, unsigned condition, + unsigned atomic); + +/*! + * Wake up whatever is sleeping on sleep object + * + * @param object The object on which things might be sleeping + * + * @return none + */ +void os_wake_sleepers(os_sleep_object_t object); + + /*! @} *//* dkops */ + +/***************************************************************************** + * Function-signature-generating macros + *****************************************************************************/ + +/*! + * @defgroup drsigs Driver Function Signatures + * + * These macros will define the entry point signatures for interrupt handlers; + * driver initialization and shutdown; device open/close; etc. They are to be + * used whenever the Kernel will call into the Driver. They are not + * appropriate for driver calls to other routines in the driver. + * + * There are three versions of each macro for a given Driver Entry Point. The + * first version is used to define a function and its implementation in the + * driver.c file, e.g. #OS_DEV_INIT(). + * + * The second form is used whenever a forward declaration (prototype) is + * needed. It has the letters @c _DCL appended to the name of the definition + * function. These are not otherwise mentioned in this documenation. + * + * There is a third form used when a reference to a function is required, for + * instance when passing the routine as a pointer to a function. It has the + * letters @c _REF appended to the name of the definition function + * (e.g. DEV_IOCTL_REF). + * + * Note that these two extra forms are required because of the possibility of + * having an invisible 'wrapper function' created by the os-specific header + * file which would need to be invoked by the operating system, and which in + * turn would invoke the generic function. + * + * Example: + * + * (in a header file) + * @code + * OS_DEV_INIT_DCL(widget_init); + * OS_DEV_ISR_DCL(widget_isr); + * @endcode + * + * (in an implementation file) + * @code + * OS_DEV_INIT(widget, widget_init) + * { + * + * os_register_interrupt("widget", WIDGET_IRQ, OS_DEV_ISR_REF(widget_isr)); + * + * os_dev_init_return(OS_RETURN_NO_ERROR_S); + * } + * + * OS_DEV_ISR(widget_isr) + * { + * os_dev_isr_return(TRUE); + * } + * @endcode + */ + +/*! @addtogroup drsigs */ +/*! @{ */ + +/*! + * Define a function which will handle device initialization + * + * This is tne driver initialization routine. This is normally where the + * part would be initialized; queues, locks, interrupts handlers defined; + * long-term dynamic memory allocated for driver use; etc. + * + * @param function_name The name of the portable initialization function. + * + * @return A call to #os_dev_init_return() + * + */ +#define OS_DEV_INIT(function_name) + +/*! + * Define a function which will handle device shutdown + * + * This is the reverse of the #OS_DEV_INIT() routine. + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + */ +#define OS_DEV_SHUTDOWN(function_name) + +/*! + * Define a function which will open the device for a user. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN(function_name) + +/*! + * Define a function which will handle a user's ioctl() request + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL(function_name) + +/*! + * Define a function which will handle a user's read() request + * + * @param function_name The name of the driver read() function + * + * @return A call to #os_dev_read_return() + */ +#define OS_DEV_READ(function_name) + +/*! + * Define a function which will handle a user's write() request + * + * @param function_name The name of the driver write() function + * + * @return A call to #os_dev_write_return() + */ +#define OS_DEV_WRITE(function_name) + +/*! + * Define a function which will handle a user's mmap() request + * + * The mmap() function requests the driver to map some memory into user space. + * + * @todo Determine what support functions are needed for mmap() handling. + * + * @param function_name The name of the driver mmap() function + * + * @return A call to #os_dev_mmap_return() + */ +#define OS_DEV_MMAP(function_name) + +/*! + * Define a function which will close the device - opposite of OS_DEV_OPEN() + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE(function_name) + +/*! + * Define a function which will handle an interrupt + * + * No arguments are available to the generic function. It must not invoke any + * OS functions which are illegal in a ISR. It gets no parameters, and must + * have a call to #os_dev_isr_return() instead of any/all return statements. + * + * Example: + * @code + * OS_DEV_ISR(widget, widget_isr, WIDGET_IRQ_NUMBER) + * { + * os_dev_isr_return(1); + * } + * @endcode + * + * @param function_name The name of the driver ISR function + * + * @return A call to #os_dev_isr_return() + */ +#define OS_DEV_ISR(function_name) + +/*! + * Define a function which will operate as a background task / bottom half. + * + * The function implementation must be structured in the following manner: + * @code + * OS_DEV_TASK(widget_task) + * { + * OS_DEV_TASK_SETUP(widget_task); + * + * while OS_DEV_TASK_CONDITION(widget_task) } + * + * }; + * } + * @endcode + * + * @todo In some systems the OS_DEV_TASK_CONDITION() will be an action which + * will cause the task to sleep on some event triggered by os_run_task(). In + * others, the macro will reference a variable laid down by + * OS_DEV_TASK_SETUP() to make sure that the loop is only performed once. + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK(function_name) + + /*! @} *//* drsigs */ + +/*! @defgroup dclsigs Routines to declare Driver Signature routines + * + * These macros drop prototypes suitable for forward-declaration of + * @ref drsigs "function signatures". + */ + +/*! @addtogroup dclsigs */ +/*! @{ */ + +/*! + * Declare prototype for the device initialization function + * + * @param function_name The name of the portable initialization function. + */ +#define OS_DEV_INIT_DCL(function_name) + +/*! + * Declare prototype for the device shutdown function + * + * @param function_name The name of the portable driver shutdown routine. + * + * @return A call to #os_dev_shutdown_return() + */ +#define OS_DEV_SHUTDOWN_DCL(function_name) + +/*! + * Declare prototype for the open() function. + * + * @param function_name The name of the driver open() function + * + * @return A call to #os_dev_open_return() + */ +#define OS_DEV_OPEN_DCL(function_name) + +/*! + * Declare prototype for the user's ioctl() request function + * + * @param function_name The name of the driver ioctl() function + * + * @return A call to #os_dev_ioctl_return() + */ +#define OS_DEV_IOCTL_DCL(function_name) + +/*! + * Declare prototype for the function a user's read() request + * + * @param function_name The name of the driver read() function + */ +#define OS_DEV_READ_DCL(function_name) + +/*! + * Declare prototype for the user's write() request function + * + * @param function_name The name of the driver write() function + */ +#define OS_DEV_WRITE_DCL(function_name) + +/*! + * Declare prototype for the user's mmap() request function + * + * @param function_name The name of the driver mmap() function + */ +#define OS_DEV_MMAP_DCL(function_name) + +/*! + * Declare prototype for the close function + * + * @param function_name The name of the driver close() function + * + * @return A call to #os_dev_close_return() + */ +#define OS_DEV_CLOSE_DCL(function_name) + +/*! + * Declare prototype for the interrupt handling function + * + * @param function_name The name of the driver ISR function + */ +#define OS_DEV_ISR_DCL(function_name) + +/*! + * Declare prototype for a background task / bottom half function + * + * @param function_name The name of this background task function + */ +#define OS_DEV_TASK_DCL(function_name) + + /*! @} *//* dclsigs */ + +/***************************************************************************** + * Functions for Returning Values from Driver Signature routines + *****************************************************************************/ + +/*! + * @defgroup retfns Functions to Return Values from Driver Signature routines + */ + +/*! @addtogroup retfns */ +/*! @{ */ + +/*! + * Return from the #OS_DEV_INIT() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_init_return(os_error_code code); + +/*! + * Return from the #OS_DEV_SHUTDOWN() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_shutdown_return(os_error_code code); + +/*! + * Return from the #OS_DEV_ISR() function + * + * The function should verify that it really was supposed to be called, + * and that its device needed attention, in order to properly set the + * return code. + * + * @param code non-zero if interrupt handled, zero otherwise. + * + */ +void os_dev_isr_return(int code); + +/*! + * Return from the #OS_DEV_OPEN() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_open_return(os_error_code code); + +/*! + * Return from the #OS_DEV_IOCTL() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_ioctl_return(os_error_code code); + +/*! + * Return from the #OS_DEV_READ() function + * + * @param code Number of bytes read, or an error code to report failure. + * + */ +void os_dev_read_return(os_error_code code); + +/*! + * Return from the #OS_DEV_WRITE() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +void os_dev_write_return(os_error_code code); + +/*! + * Return from the #OS_DEV_MMAP() function + * + * @param code Number of bytes written, or an error code to report failure. + * + */ +void os_dev_mmap_return(os_error_code code); + +/*! + * Return from the #OS_DEV_CLOSE() function + * + * @param code An error code to report success or failure. + * + */ +void os_dev_close_return(os_error_code code); + +/*! + * Start the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a label for + * the os_dev_task_return() call. + * + * For a more portable interface, should this take the sleep object as an + * argument??? + * + * @return none + */ +void os_dev_task_begin(void); + +/*! + * Return from the #OS_DEV_TASK() function + * + * In some implementations, this could be turned into a sleep followed + * by a jump back to the os_dev_task_begin() call. + * + * @param code An error code to report success or failure. + * + */ +void os_dev_task_return(os_error_code code); + + /*! @} *//* retfns */ + +/***************************************************************************** + * Functions/Macros for accessing arguments from Driver Signature routines + *****************************************************************************/ + +/*! @defgroup drsigargs Functions for Getting Arguments in Signature functions + * + */ +/* @addtogroup @drsigargs */ +/*! @{ */ + +/*! + * Check whether user is requesting read (permission) on the file/device. + * Usable in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() + * and #OS_DEV_WRITE() routines. + */ +int os_dev_is_flag_read(void); + +/*! + * Check whether user is requesting write (permission) on the file/device. + * Usable in #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() + * and #OS_DEV_WRITE() routines. + */ +int os_dev_is_flag_write(void); + +/*! + * Check whether user is requesting non-blocking I/O. Usable in + * #OS_DEV_OPEN(), #OS_DEV_CLOSE(), #OS_DEV_IOCTL(), #OS_DEV_READ() and + * #OS_DEV_WRITE() routines. + * + * @todo Specify required behavior when nonblock is requested and (sufficient?) + * data are not available to fulfill the request. + * + */ +int os_dev_is_flag_nonblock(void); + +/*! + * Determine which major device is being accessed. Usable in #OS_DEV_OPEN() + * and #OS_DEV_CLOSE(). + */ +int os_dev_get_major(void); + +/*! + * Determine which minor device is being accessed. Usable in #OS_DEV_OPEN() + * and #OS_DEV_CLOSE(). + */ +int os_dev_get_minor(void); + +/*! + * Determine which operation the user wants performed. Usable in + * #OS_DEV_IOCTL(). + * + * @return Value of the operation. + * + * @todo Define some generic way to define the individual operations. + */ +unsigned os_dev_get_ioctl_op(void); + +/*! + * Retrieve the associated argument for the desired operation. Usable in + * #OS_DEV_IOCTL(). + * + * @return A value which can be cast to a struct pointer or used as + * int/long. + */ +os_dev_ioctl_arg_t os_dev_get_ioctl_arg(void); + +/*! + * Determine the requested byte count. This should be the size of buffer at + * #os_dev_get_user_buffer(). Usable in OS_DEV_READ() and OS_DEV_WRITE() + * routines. + * + * @return A count of bytes + */ +unsigned os_dev_get_count(void); + +/*! + * Get the pointer to the user's data buffer. Usable in OS_DEV_READ(), + * OS_DEV_WRITE(), and OS_DEV_MMAP() routines. + * + * @return Pointer to user buffer (in user space). See #os_copy_to_user() + * and #os_copy_from_user(). + */ +void *os_dev_get_user_buffer(void); + +/*! + * Get the POSIX flags field for the associated open file. Usable in + * OS_DEV_READ(), OS_DEV_WRITE(), and OS_DEV_IOCTL() routines. + * + * @return The flags associated with the file. + */ +unsigned os_dev_get_file_flags(void); + +/*! + * Set the driver's private structure associated with this file/open. + * + * Generally used during #OS_DEV_OPEN(). May also be used during + * #OS_DEV_READ(), #OS_DEV_WRITE(), #OS_DEV_IOCTL(), #OS_DEV_MMAP(), and + * #OS_DEV_CLOSE(). See also #os_dev_get_user_private(). + * + * @param struct_p The driver data structure to associate with this user. + */ +void os_dev_set_user_private(void *struct_p); + +/*! + * Get the driver's private structure associated with this file. + * + * May be used during #OS_DEV_OPEN(), #OS_DEV_READ(), #OS_DEV_WRITE(), + * #OS_DEV_IOCTL(), #OS_DEV_MMAP(), and #OS_DEV_CLOSE(). See + * also #os_dev_set_user_private(). + * + * @return The driver data structure to associate with this user. + */ +void *os_dev_get_user_private(void); + +/*! + * Get the IRQ associated with this call to the #OS_DEV_ISR() function. + * + * @return The IRQ (integer) interrupt number. + */ +int os_dev_get_irq(void); + + /*! @} *//* drsigargs */ + +/***************************************************************************** + * Functions for Generating References to Driver Routines + *****************************************************************************/ + +/*! + * @defgroup drref Functions for Generating References to Driver Routines + * + * These functions will most likely be implemented as macros. They are a + * necessary part of the portable API to guarantee portability. The @c symbol + * type in here is the same symbol passed to the associated + * signature-generating macro. + * + * These macros must be used whenever referring to a + * @ref drsigs "driver signature function", for instance when storing or + * passing a pointer to the function. + */ + +/*! @addtogroup drref */ +/*! @{ */ + +/*! + * Generate a reference to an #OS_DEV_INIT() function + * + * @param function_name The name of the init function being referenced. + * + * @return A reference to the function + */ +os_init_function_t OS_DEV_INIT_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_SHUTDOWN() function + * + * @param function_name The name of the shutdown function being referenced. + * + * @return A reference to the function + */ +os_shutdown_function_t OS_DEV_SHUTDOWN_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_OPEN() function + * + * @param function_name The name of the open function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_OPEN_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_CLOSE() function + * + * @param function_name The name of the close function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_CLOSE_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_READ() function + * + * @param function_name The name of the read function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_READ_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_WRITE() function + * + * @param function_name The name of the write function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_WRITE_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_IOCTL() function + * + * @param function_name The name of the ioctl function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_IOCTL_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_MMAP() function + * + * @param function_name The name of the mmap function being referenced. + * + * @return A reference to the function + */ +os_user_function_t OS_DEV_MMAP_REF(symbol function_name); + +/*! + * Generate a reference to an #OS_DEV_ISR() function + * + * @param function_name The name of the isr being referenced. + * + * @return a reference to the function + */ +os_interrupt_handler_t OS_DEV_ISR_REF(symbol function_name); + + /*! @} *//* drref */ + +/*! + * Flush and invalidate all cache lines. + */ +void os_flush_cache_all(void); + +/*! + * Flush a range of addresses from the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_flush_range(void *start, uint32_t len); + +/*! + * Invalidate a range of addresses in the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_inv_range(void *start, uint32_t len); + +/*! + * Clean a range of addresses from the cache + * + * @param start Starting virtual address + * @param len Number of bytes to flush + */ +void os_cache_clean_range(void *start, uint32_t len); + +/*! + * @example widget.h + */ + +/*! + * @example widget.c + */ + +/*! + * @example rng_driver.h + */ + +/*! + * @example rng_driver.c + */ + +/*! + * @example shw_driver.h + */ + +/*! + * @example shw_driver.c + */ + +#endif /* DOXYGEN_PORTABLE_OS_DOC */ + +#endif /* PORTABLE_OS_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_driver_common.h b/drivers/mxc/security/sahara2/include/sah_driver_common.h new file mode 100644 index 000000000000..7cfd32a30352 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_driver_common.h @@ -0,0 +1,102 @@ +/* + * Copyright 2004-2008 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 sah_driver_common.h +* +* @brief Provides types and defined values for use in the Driver Interface. +* +*/ + +#ifndef SAH_DRIVER_COMMON_H +#define SAH_DRIVER_COMMON_H + +#include "fsl_platform.h" +#include <sahara.h> +#include <adaptor.h> + +/** This specifies the permissions for the device file. It is equivalent to + * chmod 666. + */ +#define SAHARA_DEVICE_MODE S_IFCHR | S_IRUGO | S_IWUGO + +/** +* The status of entries in the Queue. +* +******************************************************************************/ +typedef enum +{ + /** This state indicates that the entry is in the queue and awaits + * execution on SAHARA. */ + SAH_STATE_PENDING, + /** This state indicates that the entry has been written to the SAHARA + * DAR. */ + SAH_STATE_ON_SAHARA, + /** This state indicates that the entry is off of SAHARA, and is awaiting + post-processing. */ + SAH_STATE_OFF_SAHARA, + /** This state indicates that the entry is successfully executed on SAHARA, + and it is finished with post-processing. */ + SAH_STATE_COMPLETE, + /** This state indicates that the entry caused an error or fault on SAHARA, + * and it is finished with post-processing. */ + SAH_STATE_FAILED, + /** This state indicates that the entry was reset via the Reset IO + * Control, and it is finished with post-processing. */ + SAH_STATE_RESET, + /** This state indicates that the entry was signalled from user-space and + * either in the DAR, IDAR or has finished executing pending Bottom Half + * processing. */ + SAH_STATE_IGNORE, + /** This state indicates that the entry was signalled from user-space and + * has been processed by the bottom half. */ + SAH_STATE_IGNORED +} sah_Queue_Status; + +/* any of these conditions being true indicates the descriptor's processing + * is complete */ +#define SAH_DESC_PROCESSED(status) \ + (((status) == SAH_STATE_COMPLETE) || \ + ((status) == SAH_STATE_FAILED ) || \ + ((status) == SAH_STATE_RESET )) + +extern os_lock_t desc_queue_lock; + +extern uint32_t dar_count; +extern uint32_t interrupt_count; +extern uint32_t done1done2_count; +extern uint32_t done1busy2_count; +extern uint32_t done1_count; + +#ifdef FSL_HAVE_SCC2 +extern void *lookup_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base); +#endif + +int sah_get_results_pointers(fsl_shw_uco_t* user_ctx, uint32_t arg); +fsl_shw_return_t sah_get_results_from_pool(volatile fsl_shw_uco_t* user_ctx, + sah_results *arg); +fsl_shw_return_t sah_handle_registration(fsl_shw_uco_t *user_cts); +fsl_shw_return_t sah_handle_deregistration(fsl_shw_uco_t *user_cts); + +int sah_Queue_Manager_Count_Entries(int ignore_state, sah_Queue_Status state); +unsigned long sah_Handle_Poll(sah_Head_Desc *entry); + +#ifdef DIAG_DRV_IF +/****************************************************************************** +* Descriptor and Link dumping functions. +******************************************************************************/ +void sah_Dump_Chain(const sah_Desc *chain, dma_addr_t addr); +#endif /* DIAG_DRV_IF */ + +#endif /* SAH_DRIVER_COMMON_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_hardware_interface.h b/drivers/mxc/security/sahara2/include/sah_hardware_interface.h new file mode 100644 index 000000000000..0933346fc223 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_hardware_interface.h @@ -0,0 +1,99 @@ +/* + * Copyright 2004-2008 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 sah_hardware_interface.h +* +* @brief Provides an interface to the SAHARA hardware registers. +* +*/ + +#ifndef SAH_HARDWARE_INTERFACE_H +#define SAH_HARDWARE_INTERFACE_H + +#include <sah_driver_common.h> +#include <sah_status_manager.h> + +/* These values can be used with sah_HW_Write_Control(). */ +#ifdef SAHARA1 +/** Define platform as Little-Endian */ +#define CTRL_LITTLE_END 0x00000002 +/** Bit to cause endian change in transfers */ +#define CTRL_INT_EN 0x00000004 +/** Set High Assurance mode */ +#define CTRL_HA 0x00000008 +#else +/** Bit to cause byte swapping in (data?) transfers */ +#define CTRL_BYTE_SWAP 0x00000001 +/** Bit to cause halfword swapping in (data?) transfers */ +#define CTRL_HALFWORD_SWAP 0x00000002 +/** Bit to cause endian change in (data?) transfers */ +#define CTRL_INT_EN 0x00000010 +/** Set High Assurance mode */ +#define CTRL_HA 0x00000020 +/** Disable High Assurance */ +#define CTRL_HA_DISABLE 0x00000040 +/** Reseed the RNG CHA */ +#define CTRL_RNG_RESEED 0x00000080 +#endif + + +/* These values can be used with sah_HW_Write_Command(). */ +/** Reset the Sahara */ +#define CMD_RESET 0x00000001 +/** Set Sahara into Batch mode. */ +#define CMD_BATCH 0x00010000 +/** Clear the Sahara interrupt */ +#define CMD_CLR_INT_BIT 0x00000100 +/** Clear the Sahara error */ +#define CMD_CLR_ERROR_BIT 0x00000200 + + +/** error status register contains error */ +#define STATUS_ERROR 0x00000010 + +/** Op status register contains op status */ +#define OP_STATUS 0x00000020 + + +/* High Level functions */ +int sah_HW_Reset(void); +fsl_shw_return_t sah_HW_Set_HA(void); +sah_Execute_Status sah_Wait_On_Sahara(void); + +/* Low Level functions */ +uint32_t sah_HW_Read_Version(void); +uint32_t sah_HW_Read_Control(void); +uint32_t sah_HW_Read_Status(void); +uint32_t sah_HW_Read_Error_Status(void); +uint32_t sah_HW_Read_Op_Status(void); +uint32_t sah_HW_Read_DAR(void); +uint32_t sah_HW_Read_CDAR(void); +uint32_t sah_HW_Read_IDAR(void); +uint32_t sah_HW_Read_Fault_Address(void); +uint32_t sah_HW_Read_MM_Status(void); +uint32_t sah_HW_Read_Config(void); +void sah_HW_Write_Command(uint32_t command); +void sah_HW_Write_Control(uint32_t control); +void sah_HW_Write_DAR(uint32_t pointer); +void sah_HW_Write_Config(uint32_t configuration); + +#if defined DIAG_DRV_IF || defined(DO_DBG) + +void sah_Dump_Words(const char *prefix, const unsigned *data, dma_addr_t addr, + unsigned length); +#endif + +#endif /* SAH_HARDWARE_INTERFACE_H */ + +/* End of sah_hardware_interface.c */ diff --git a/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h b/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h new file mode 100644 index 000000000000..8eb8690ff093 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_interrupt_handler.h @@ -0,0 +1,42 @@ +/* + * 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 sah_interrupt_handler.h +* +* @brief Provides a hardware interrupt handling mechanism for device driver. +* +*/ +/****************************************************************************** +* +* CAUTION: +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/03 MW Initial Creation +******************************************************************* +*/ + +#ifndef SAH_INTERRUPT_HANDLER_H +#define SAH_INTERRUPT_HANDLER_H + +#include <sah_driver_common.h> + +/****************************************************************************** +* External function declarations +******************************************************************************/ +int sah_Intr_Init (wait_queue_head_t *wait_queue); +void sah_Intr_Release (void); + +#endif /* SAH_INTERRUPT_HANDLER_H */ diff --git a/drivers/mxc/security/sahara2/include/sah_kernel.h b/drivers/mxc/security/sahara2/include/sah_kernel.h new file mode 100644 index 000000000000..42013272f610 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_kernel.h @@ -0,0 +1,113 @@ +/* + * Copyright 2004-2009 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 sah_kernel.h +* +* @brief Provides definitions for items that user-space and kernel-space share. +*/ +/****************************************************************************** +* +* This file needs to be PORTED to a non-Linux platform +*/ + +#ifndef SAH_KERNEL_H +#define SAH_KERNEL_H + +#if defined(__KERNEL__) + +#if defined(CONFIG_ARCH_MXC91321) || defined(CONFIG_ARCH_MXC91231) \ + || defined(CONFIG_ARCH_MX27) || defined(CONFIG_ARCH_MXC92323) +#include <mach/hardware.h> +#define SAHA_BASE_ADDR SAHARA_BASE_ADDR +#define SAHARA_IRQ MXC_INT_SAHARA +#elif defined(CONFIG_ARCH_MX51) +#include <mach/hardware.h> +#define SAHA_BASE_ADDR SAHARA_BASE_ADDR +#define SAHARA_IRQ MXC_INT_SAHARA_H0 +#else +#include <mach/mx2.h> +#endif + +#endif /* KERNEL */ + +/* IO Controls */ +/* The magic number 'k' is reserved for the SPARC architecture. (See <kernel + * source root>/Documentation/ioctl-number.txt. + * + * Note: Numbers 8-13 were used in a previous version of the API and should + * be avoided. + */ +#define SAH_IOC_MAGIC 'k' +#define SAHARA_HWRESET _IO(SAH_IOC_MAGIC, 0) +#define SAHARA_SET_HA _IO(SAH_IOC_MAGIC, 1) +#define SAHARA_CHK_TEST_MODE _IOR(SAH_IOC_MAGIC,2, int) +#define SAHARA_DAR _IO(SAH_IOC_MAGIC, 3) +#define SAHARA_GET_RESULTS _IO(SAH_IOC_MAGIC, 4) +#define SAHARA_REGISTER _IO(SAH_IOC_MAGIC, 5) +#define SAHARA_DEREGISTER _IO(SAH_IOC_MAGIC, 6) +/* 7 */ +/* 8 */ +/* 9 */ +/* 10 */ +/* 11 */ +/* 12 */ +/* 13 */ + +#define SAHARA_SCC_DROP_PERMS _IOWR(SAH_IOC_MAGIC, 14, scc_partition_info_t) +#define SAHARA_SCC_SFREE _IOWR(SAH_IOC_MAGIC, 15, scc_partition_info_t) + +#define SAHARA_SK_ALLOC _IOWR(SAH_IOC_MAGIC, 16, scc_slot_t) +#define SAHARA_SK_DEALLOC _IOWR(SAH_IOC_MAGIC, 17, scc_slot_t) +#define SAHARA_SK_LOAD _IOWR(SAH_IOC_MAGIC, 18, scc_slot_t) +#define SAHARA_SK_UNLOAD _IOWR(SAH_IOC_MAGIC, 19, scc_slot_t) +#define SAHARA_SK_SLOT_ENC _IOWR(SAH_IOC_MAGIC, 20, scc_slot_t) +#define SAHARA_SK_SLOT_DEC _IOWR(SAH_IOC_MAGIC, 21, scc_slot_t) + +#define SAHARA_SCC_ENCRYPT _IOWR(SAH_IOC_MAGIC, 22, scc_region_t) +#define SAHARA_SCC_DECRYPT _IOWR(SAH_IOC_MAGIC, 23, scc_region_t) +#define SAHARA_GET_CAPS _IOWR(SAH_IOC_MAGIC, 24, fsl_shw_pco_t) + +#define SAHARA_SCC_SSTATUS _IOWR(SAH_IOC_MAGIC, 25, scc_partition_info_t) + +#define SAHARA_SK_READ _IOWR(SAH_IOC_MAGIC, 29, scc_slot_t) + +/*! This is the name that will appear in /proc/interrupts */ +#define SAHARA_NAME "SAHARA" + +/*! + * SAHARA IRQ number. See page 9-239 of TLICS - Motorola Semiconductors H.K. + * TAHITI-Lite IC Specification, Rev 1.1, Feb 2003. + * + * TAHITI has two blocks of 32 interrupts. The SAHARA IRQ is number 27 + * in the second block. This means that the SAHARA IRQ is 27 + 32 = 59. + */ +#ifndef SAHARA_IRQ +#define SAHARA_IRQ (27+32) +#endif + +/*! + * Device file definition. The #ifndef is here to support the unit test code + * by allowing it to specify a different test device. + */ +#ifndef SAHARA_DEVICE_SHORT +#define SAHARA_DEVICE_SHORT "sahara" +#endif + +#ifndef SAHARA_DEVICE +#define SAHARA_DEVICE "/dev/"SAHARA_DEVICE_SHORT +#endif + +#endif /* SAH_KERNEL_H */ + +/* End of sah_kernel.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_memory_mapper.h b/drivers/mxc/security/sahara2/include/sah_memory_mapper.h new file mode 100644 index 000000000000..838ce47cbf85 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_memory_mapper.h @@ -0,0 +1,79 @@ +/* + * Copyright 2004-2008 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 sah_memory_mapper.h +* +* @brief Re-creates SAHARA data structures in Kernel memory such that they are +* suitable for DMA. +* +*/ + +#ifndef SAH_MEMORY_MAPPER_H +#define SAH_MEMORY_MAPPER_H + +#include <sah_driver_common.h> +#include <sah_queue_manager.h> + + +/****************************************************************************** +* External function declarations +******************************************************************************/ +sah_Head_Desc *sah_Copy_Descriptors(fsl_shw_uco_t * user_ctx, + sah_Head_Desc * desc); + +sah_Link *sah_Copy_Links(fsl_shw_uco_t * user_ctx, sah_Link * ptr); + +sah_Head_Desc *sah_Physicalise_Descriptors(sah_Head_Desc * desc); + +sah_Link *sah_Physicalise_Links (sah_Link *ptr); + +sah_Head_Desc *sah_DePhysicalise_Descriptors (sah_Head_Desc *desc); + +sah_Link *sah_DePhysicalise_Links (sah_Link *ptr); + +sah_Link *sah_Make_Links(fsl_shw_uco_t * user_ctx, + sah_Link * ptr, sah_Link ** tail); + + +void sah_Destroy_Descriptors (sah_Head_Desc *desc); + +void sah_Destroy_Links (sah_Link *link); + +void sah_Free_Chained_Descriptors (sah_Head_Desc *desc); + +void sah_Free_Chained_Links (sah_Link *link); + +int sah_Init_Mem_Map (void); + +void sah_Stop_Mem_Map (void); + +int sah_Block_Add_Page (int big); + +sah_Desc *sah_Alloc_Descriptor (void); +sah_Head_Desc *sah_Alloc_Head_Descriptor (void); +void sah_Free_Descriptor (sah_Desc *desc); +void sah_Free_Head_Descriptor (sah_Head_Desc *desc); +sah_Link *sah_Alloc_Link (void); +void sah_Free_Link (sah_Link *link); + +void *wire_user_memory(void *address, uint32_t length, void **page_ctx); +void unwire_user_memory(void **page_ctx); + +os_error_code map_user_memory(struct vm_area_struct *vma, + uint32_t physical_addr, uint32_t size); +os_error_code unmap_user_memory(uint32_t user_addr, uint32_t size); + +#endif /* SAH_MEMORY_MAPPER_H */ + +/* End of sah_memory_mapper.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_queue_manager.h b/drivers/mxc/security/sahara2/include/sah_queue_manager.h new file mode 100644 index 000000000000..2dde5883e193 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_queue_manager.h @@ -0,0 +1,63 @@ +/* + * 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 sah_queue_manager.h +* +* @brief This file definitions for the Queue Manager. + +* The Queue Manager manages additions and removal from the queue and updates +* the status of queue entries. It also calls sah_HW_* functions to interact +* with the hardware. +* +*/ + +#ifndef SAH_QUEUE_MANAGER_H +#define SAH_QUEUE_MANAGER_H + +#include <sah_driver_common.h> +#include <sah_status_manager.h> + + +/************************* +* Queue Manager Functions +*************************/ +fsl_shw_return_t sah_Queue_Manager_Init(void); +void sah_Queue_Manager_Close(void); +void sah_Queue_Manager_Reset_Entries(void); +void sah_Queue_Manager_Append_Entry(sah_Head_Desc *entry); +void sah_Queue_Manager_Remove_Entry(sah_Head_Desc *entry); + + +/************************* +* Queue Functions +*************************/ +sah_Queue *sah_Queue_Construct(void); +void sah_Queue_Destroy(sah_Queue *this); +void sah_Queue_Append_Entry(sah_Queue *this, sah_Head_Desc *entry); +void sah_Queue_Remove_Entry(sah_Queue *this); +void sah_Queue_Remove_Any_Entry(sah_Queue *this, sah_Head_Desc *entry); +void sah_postprocess_queue(unsigned long reset_flag); + + +/************************* +* Misc Releated Functions +*************************/ + +int sah_blocking_mode(struct sah_Head_Desc *entry); +fsl_shw_return_t sah_convert_error_status(uint32_t error_status); + + +#endif /* SAH_QUEUE_MANAGER_H */ + +/* End of sah_queue_manager.h */ diff --git a/drivers/mxc/security/sahara2/include/sah_status_manager.h b/drivers/mxc/security/sahara2/include/sah_status_manager.h new file mode 100644 index 000000000000..e38731bf4835 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sah_status_manager.h @@ -0,0 +1,228 @@ +/* + * 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 sah_status_manager.h +* +* @brief SAHARA Status Manager Types and Function Prototypes +* +* @author Stuart Holloway (SH) +* +*/ + +#ifndef STATUS_MANAGER_H +#define STATUS_MANAGER_H +#include "sah_driver_common.h" +#include "sahara.h" + + +/****************************************************************************** +* User defined data types +******************************************************************************/ +/** +****************************************************************************** +* sah_Execute_Status +* Types read from SAHARA Status Register, with additional state for Op Status +******************************************************************************/ +typedef enum sah_Execute_Status +{ + /** Sahara is Idle. */ + SAH_EXEC_IDLE = 0, + /** SAHARA is busy performing a resest or processing a decriptor chain. */ + SAH_EXEC_BUSY = 1, + /** An error occurred while SAHARA executed the first descriptor. */ + SAH_EXEC_ERROR1 = 2, + /** SAHARA has failed internally. */ + SAH_EXEC_FAULT = 3, + /** SAHARA has finished processing a descriptor chain and is idle. */ + SAH_EXEC_DONE1 = 4, + /** SAHARA has finished processing a descriptor chain, and is processing a + * second chain. */ + SAH_EXEC_DONE1_BUSY2 = 5, + /** SAHARA has finished processing a descriptor chain, but has generated an + * error while processing a second descriptor chain. */ + SAH_EXEC_DONE1_ERROR2 = 6, + /** SAHARA has finished two descriptors. */ + SAH_EXEC_DONE1_DONE2 = 7, + /** SAHARA has stopped, and first descriptor has Op Status, not Err */ + SAH_EXEC_OPSTAT1 = 0x20, +} sah_Execute_Status; + +/** + * When this bit is on in a #sah_Execute_Status, it means that DONE1 is true. + */ +#define SAH_EXEC_DONE1_BIT 4 + +/** + * Bits which make up the Sahara State + */ +#define SAH_EXEC_STATE_MASK 0x00000007 + +/** +******************************************************************************* +* sah_Execute_Error +* Types read from SAHARA Error Status Register +******************************************************************************/ +typedef enum sah_Execute_Error +{ + /** No Error */ + SAH_ERR_NONE = 0, + /** Header is not valid. */ + SAH_ERR_HEADER = 1, + /** Descriptor length is not correct. */ + SAH_ERR_DESC_LENGTH = 2, + /** Length or pointer field is zero while the other is non-zero. */ + SAH_ERR_DESC_POINTER = 3, + /** Length of the link is not a multiple of 4 and is not the last link */ + SAH_ERR_LINK_LENGTH = 4, + /** The data pointer in a link is zero */ + SAH_ERR_LINK_POINTER = 5, + /** Input Buffer reported an overflow */ + SAH_ERR_INPUT_BUFFER = 6, + /** Output Buffer reported an underflow */ + SAH_ERR_OUTPUT_BUFFER = 7, + /** Incorrect data in output buffer after CHA's has signalled 'done'. */ + SAH_ERR_OUTPUT_BUFFER_STARVATION = 8, + /** Internal Hardware Failure. */ + SAH_ERR_INTERNAL_STATE = 9, + /** Current Descriptor was not legal, but cause is unknown. */ + SAH_ERR_GENERAL_DESCRIPTOR = 10, + /** Reserved pointer fields have been set to 1. */ + SAH_ERR_RESERVED_FIELDS = 11, + /** Descriptor address error */ + SAH_ERR_DESCRIPTOR_ADDRESS = 12, + /** Link address error */ + SAH_ERR_LINK_ADDRESS = 13, + /** Processing error in CHA module */ + SAH_ERR_CHA = 14, + /** Processing error during DMA */ + SAH_ERR_DMA = 15 +} sah_Execute_Error; + + +/** +******************************************************************************* +* sah_CHA_Error_Source +* Types read from SAHARA Error Status Register for CHA Error Source +* +******************************************************************************/ +typedef enum sah_CHA_Error_Source +{ + /** No Error indicated in Source CHA Error. */ + SAH_CHA_NO_ERROR = 0, + /** Error in SKHA module. */ + SAH_CHA_SKHA_ERROR = 1, + /** Error in MDHA module. */ + SAH_CHA_MDHA_ERROR = 2, + /** Error in RNG module. */ + SAH_CHA_RNG_ERROR = 3, + /** Error in PKHA module. */ + SAH_CHA_PKHA_ERROR = 4, +} sah_CHA_Error_Source; + +/** +****************************************************************************** +* sah_CHA_Error_Status +* Types read from SAHARA Error Status Register for CHA Error Status +* +******************************************************************************/ +typedef enum sah_CHA_Error_Status +{ + /** No CHA error detected */ + SAH_CHA_NO_ERR = 0x000, + /** Non-empty input buffer when complete. */ + SAH_CHA_IP_BUF = 0x001, + /** Illegal Address Error. */ + SAH_CHA_ADD_ERR = 0x002, + /** Illegal Mode Error. */ + SAH_CHA_MODE_ERR = 0x004, + /** Illegal Data Size Error. */ + SAH_CHA_DATA_SIZE_ERR = 0x008, + /** Illegal Key Size Error. */ + SAH_CHA_KEY_SIZE_ERR = 0x010, + /** Mode/Context/Key written during processing. */ + SAH_CHA_PROC_ERR = 0x020, + /** Context Read During Processing. */ + SAH_CHA_CTX_READ_ERR = 0x040, + /** Internal Hardware Error. */ + SAH_CHA_INTERNAL_HW_ERR = 0x080, + /** Input Buffer not enabled or underflow. */ + SAH_CHA_IP_BUFF_ERR = 0x100, + /** Output Buffer not enabled or overflow. */ + SAH_CHA_OP_BUFF_ERR = 0x200, + /** DES key parity error (SKHA) */ + SAH_CHA_DES_KEY_ERR = 0x400, + /** Reserved error code. */ + SAH_CHA_RES = 0x800 +} sah_CHA_Error_Status; + +/** +***************************************************************************** +* sah_DMA_Error_Status +* Types read from SAHARA Error Status Register for DMA Error Status +******************************************************************************/ +typedef enum sah_DMA_Error_Status +{ + /** No DMA Error Code. */ + SAH_DMA_NO_ERR = 0, + /** AHB terminated a bus cycle with an error. */ + SAH_DMA_AHB_ERR = 2, + /** Internal IP bus cycle was terminated with an error termination. */ + SAH_DMA_IP_ERR = 4, + /** Parity error detected on DMA command. */ + SAH_DMA_PARITY_ERR = 6, + /** DMA was requested to cross a 256 byte internal address boundary. */ + SAH_DMA_BOUNDRY_ERR = 8, + /** DMA controller is busy */ + SAH_DMA_BUSY_ERR = 10, + /** Memory Bounds Error */ + SAH_DMA_RESERVED_ERR = 12, + /** Internal DMA hardware error detected */ + SAH_DMA_INT_ERR = 14 +} sah_DMA_Error_Status; + +/** +***************************************************************************** +* sah_DMA_Error_Size +* Types read from SAHARA Error Status Register for DMA Error Size +* +******************************************************************************/ +typedef enum sah_DMA_Error_Size +{ + /** Error during Byte transfer. */ + SAH_DMA_SIZE_BYTE = 0, + /** Error during Half-word transfer. */ + SAH_DMA_SIZE_HALF_WORD = 1, + /** Error during Word transfer. */ + SAH_DMA_SIZE_WORD = 2, + /** Reserved DMA word size. */ + SAH_DMA_SIZE_RES = 3 +} sah_DMA_Error_Size; + + + + +extern bool sah_dpm_flag; + +/************************* +* Status Manager Functions +*************************/ + +unsigned long sah_Handle_Interrupt(sah_Execute_Status hw_status); +sah_Head_Desc *sah_Find_With_State (sah_Queue_Status status); +int sah_dpm_init(void); +void sah_dpm_close(void); +void sah_Queue_Manager_Prime (sah_Head_Desc *entry); + + +#endif /* STATUS_MANAGER_H */ diff --git a/drivers/mxc/security/sahara2/include/sahara.h b/drivers/mxc/security/sahara2/include/sahara.h new file mode 100644 index 000000000000..28f4e1448c29 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sahara.h @@ -0,0 +1,2265 @@ +/* + * Copyright 2004-2009 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 sahara.h + * + * File which implements the FSL_SHW API when used on Sahara + */ +/*! + * @if USE_MAINPAGE + * @mainpage Sahara2 implemtation of FSL Security Hardware API + * @endif + * + */ + +#define _DIAG_DRV_IF +#define _DIAG_SECURITY_FUNC +#define _DIAG_ADAPTOR + +#ifndef SAHARA2_API_H +#define SAHARA2_API_H + +#ifdef DIAG_SECURITY_FUNC +#include <diagnostic.h> +#endif /* DIAG_SECURITY_FUNC */ + +/* This is a Linux flag... ? */ +#ifndef __KERNEL__ +#include <inttypes.h> +#include <stdlib.h> +#include <memory.h> +#else +#include "portable_os.h" +#endif + +/* This definition may need a new name, and needs to go somewhere which + * can determine platform, kernel vs. user, os, etc. + */ +#define copy_bytes(out, in, len) memcpy(out, in, len) + +/* Does this belong here? */ +#ifndef SAHARA_DEVICE +#define SAHARA_DEVICE "/dev/sahara" +#endif + +/*! +******************************************************************************* +* @defgroup lnkflags Link Flags +* +* @brief Flags to show information about link data and link segments +* +******************************************************************************/ +/*! @addtogroup lnkflags + * @{ + */ + +/*! +******************************************************************************* +* This flag indicates that the data in a link is owned by the security +* function component and this memory will be freed by the security function +* component. To be used as part of the flag field of the sah_Link structure. +******************************************************************************/ +#define SAH_OWNS_LINK_DATA 0x01 + +/*! +******************************************************************************* +* The data in a link is not owned by the security function component and +* therefore it will not attempt to free this memory. To be used as part of the +* flag field of the sah_Link structure. +******************************************************************************/ +#define SAH_USES_LINK_DATA 0x02 + +/*! +******************************************************************************* +* The data in this link will change when the descriptor gets executed. +******************************************************************************/ +#define SAH_OUTPUT_LINK 0x04 + +/*! +******************************************************************************* +* The ptr and length in this link are really 'established key' info. They +* are to be converted to ptr/length before putting on request queue. +******************************************************************************/ +#define SAH_KEY_IS_HIDDEN 0x08 + +/*! +******************************************************************************* +* The link structure has been appended to the previous one by the driver. It +* needs to be removed before leaving the driver (and returning to API). +******************************************************************************/ +#define SAH_REWORKED_LINK 0x10 + +/*! +******************************************************************************* +* The length and data fields of this link contain the slot and user id +* used to access the SCC stored key +******************************************************************************/ +#define SAH_STORED_KEY_INFO 0x20 + +/*! +******************************************************************************* +* The Data field points to a physical address, and does not need to be +* processed by the driver. Honored only in Kernel API. +******************************************************************************/ +#define SAH_PREPHYS_DATA 0x40 + +/*! +******************************************************************************* +* The link was inserted during the Physicalise procedure. It is tagged so +* it can be removed during DePhysicalise, thereby returning to the caller an +* intact chain. +******************************************************************************/ +#define SAH_LINK_INSERTED_LINK 0x80 + +/*! +******************************************************************************* +* The Data field points to the location of the key, which is in a secure +* partition held by the user. The memory address needs to be converted to +* kernel space manually, by looking through the partitions that the user holds. +******************************************************************************/ +#define SAH_IN_USER_KEYSTORE 0x100 + +/*! +******************************************************************************* +* sah_Link_Flags +* +* Type to be used for flags associated with a Link in security function. +* These flags are used internally by the security function component only. +* +* Values defined at @ref lnkflags +* +* @brief typedef for flags field of sah_Link +******************************************************************************/ +typedef uint32_t sah_Link_Flags; + +/* +******************************************************************************* +* Security Parameters Related Structures +* +* All of structures associated with API parameters +* +******************************************************************************/ + +/* +******************************************************************************* +* Common Types +* +* All of structures used across several classes of crytography +******************************************************************************/ + +/*! +******************************************************************************* +* @brief Indefinite precision integer used for security operations on SAHARA +* accelerator. The data will always be in little Endian format. +******************************************************************************/ +typedef uint8_t *sah_Int; + +/*! +******************************************************************************* +* @brief Byte array used for block cipher and hash digest/MAC operations on +* SAHARA accelerator. The Endian format will be as specified by the function +* using the sah_Oct_Str. +******************************************************************************/ +typedef uint8_t *sah_Oct_Str; + +/*! + * A queue of descriptor heads -- used to hold requests waiting for user to + * pick up the results. */ +typedef struct sah_Queue { + int count; /*!< # entries in queue */ + struct sah_Head_Desc *head; /*!< first entry in queue */ + struct sah_Head_Desc *tail; /*!< last entry in queue */ +} sah_Queue; + +/****************************************************************************** + * Enumerations + *****************************************************************************/ +/*! + * Flags for the state of the User Context Object (#fsl_shw_uco_t). + */ +typedef enum fsl_shw_user_ctx_flags_t { + /*! + * API will block the caller until operation completes. The result will be + * available in the return code. If this is not set, user will have to get + * results using #fsl_shw_get_results(). + */ + FSL_UCO_BLOCKING_MODE = 0x01, + /*! + * User wants callback (at the function specified with + * #fsl_shw_uco_set_callback()) when the operation completes. This flag is + * valid only if #FSL_UCO_BLOCKING_MODE is not set. + */ + FSL_UCO_CALLBACK_MODE = 0x02, + /*! Do not free descriptor chain after driver (adaptor) finishes */ + FSL_UCO_SAVE_DESC_CHAIN = 0x04, + /*! + * User has made at least one request with callbacks requested, so API is + * ready to handle others. + */ + FSL_UCO_CALLBACK_SETUP_COMPLETE = 0x08, + /*! + * (virtual) pointer to descriptor chain is completely linked with physical + * (DMA) addresses, ready for the hardware. This flag should not be used + * by FSL SHW API programs. + */ + FSL_UCO_CHAIN_PREPHYSICALIZED = 0x10, + /*! + * The user has changed the context but the changes have not been copied to + * the kernel driver. + */ + FSL_UCO_CONTEXT_CHANGED = 0x20, + /*! Internal Use. This context belongs to a user-mode API user. */ + FSL_UCO_USERMODE_USER = 0x40, +} fsl_shw_user_ctx_flags_t; + +/*! + * Return code for FSL_SHW library. + * + * These codes may be returned from a function call. In non-blocking mode, + * they will appear as the status in a Result Object. + */ +typedef enum fsl_shw_return_t { + /*! + * No error. As a function return code in Non-blocking mode, this may + * simply mean that the operation was accepted for eventual execution. + */ + FSL_RETURN_OK_S = 0, + /*! Failure for non-specific reason. */ + FSL_RETURN_ERROR_S, + /*! + * Operation failed because some resource was not able to be allocated. + */ + FSL_RETURN_NO_RESOURCE_S, + /*! Crypto algorithm unrecognized or improper. */ + FSL_RETURN_BAD_ALGORITHM_S, + /*! Crypto mode unrecognized or improper. */ + FSL_RETURN_BAD_MODE_S, + /*! Flag setting unrecognized or inconsistent. */ + FSL_RETURN_BAD_FLAG_S, + /*! Improper or unsupported key length for algorithm. */ + FSL_RETURN_BAD_KEY_LENGTH_S, + /*! Improper parity in a (DES, TDES) key. */ + FSL_RETURN_BAD_KEY_PARITY_S, + /*! + * Improper or unsupported data length for algorithm or internal buffer. + */ + FSL_RETURN_BAD_DATA_LENGTH_S, + /*! Authentication / Integrity Check code check failed. */ + FSL_RETURN_AUTH_FAILED_S, + /*! A memory error occurred. */ + FSL_RETURN_MEMORY_ERROR_S, + /*! An error internal to the hardware occurred. */ + FSL_RETURN_INTERNAL_ERROR_S, + /*! ECC detected Point at Infinity */ + FSL_RETURN_POINT_AT_INFINITY_S, + /*! ECC detected No Point at Infinity */ + FSL_RETURN_POINT_NOT_AT_INFINITY_S, + /*! GCD is One */ + FSL_RETURN_GCD_IS_ONE_S, + /*! GCD is not One */ + FSL_RETURN_GCD_IS_NOT_ONE_S, + /*! Candidate is Prime */ + FSL_RETURN_PRIME_S, + /*! Candidate is not Prime */ + FSL_RETURN_NOT_PRIME_S, + /*! N register loaded improperly with even value */ + FSL_RETURN_EVEN_MODULUS_ERROR_S, + /*! Divisor is zero. */ + FSL_RETURN_DIVIDE_BY_ZERO_ERROR_S, + /*! Bad Exponent or Scalar value for Point Multiply */ + FSL_RETURN_BAD_EXPONENT_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_OSCILLATOR_ERROR_S, + /*! RNG hardware problem. */ + FSL_RETURN_STATISTICS_ERROR_S, +} fsl_shw_return_t; + +/*! + * Algorithm Identifier. + * + * Selection of algorithm will determine how large the block size of the + * algorithm is. Context size is the same length unless otherwise specified. + * Selection of algorithm also affects the allowable key length. + */ +typedef enum fsl_shw_key_alg_t { + /*! + * Key will be used to perform an HMAC. Key size is 1 to 64 octets. Block + * size is 64 octets. + */ + FSL_KEY_ALG_HMAC, + /*! + * Advanced Encryption Standard (Rijndael). Block size is 16 octets. Key + * size is 16 octets. (The single choice of key size is a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_AES, + /*! + * Data Encryption Standard. Block size is 8 octets. Key size is 8 + * octets. + */ + FSL_KEY_ALG_DES, + /*! + * 2- or 3-key Triple DES. Block size is 8 octets. Key size is 16 octets + * for 2-key Triple DES, and 24 octets for 3-key. + */ + FSL_KEY_ALG_TDES, + /*! + * ARC4. No block size. Context size is 259 octets. Allowed key size is + * 1-16 octets. (The choices for key size are a Sahara platform + * limitation.) + */ + FSL_KEY_ALG_ARC4, + /*! + * Private key of a public-private key-pair. Max is 512 bits... + */ + FSL_KEY_PK_PRIVATE, +} fsl_shw_key_alg_t; + +/*! + * Mode selector for Symmetric Ciphers. + * + * The selection of mode determines how a cryptographic algorithm will be + * used to process the plaintext or ciphertext. + * + * For all modes which are run block-by-block (that is, all but + * #FSL_SYM_MODE_STREAM), any partial operations must be performed on a text + * length which is multiple of the block size. Except for #FSL_SYM_MODE_CTR, + * these block-by-block algorithms must also be passed a total number of octets + * which is a multiple of the block size. + * + * In modes which require that the total number of octets of data be a multiple + * of the block size (#FSL_SYM_MODE_ECB and #FSL_SYM_MODE_CBC), and the user + * has a total number of octets which are not a multiple of the block size, the + * user must perform any necessary padding to get to the correct data length. + */ +typedef enum fsl_shw_sym_mode_t { + /*! + * Stream. There is no associated block size. Any request to process data + * may be of any length. This mode is only for ARC4 operations, and is + * also the only mode used for ARC4. + */ + FSL_SYM_MODE_STREAM, + + /*! + * Electronic Codebook. Each block of data is encrypted/decrypted. The + * length of the data stream must be a multiple of the block size. This + * mode may be used for DES, 3DES, and AES. The block size is determined + * by the algorithm. + */ + FSL_SYM_MODE_ECB, + /*! + * Cipher-Block Chaining. Each block of data is encrypted/decrypted and + * then "chained" with the previous block by an XOR function. Requires + * context to start the XOR (previous block). This mode may be used for + * DES, 3DES, and AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CBC, + /*! + * Counter. The counter is encrypted, then XORed with a block of data. + * The counter is then incremented (using modulus arithmetic) for the next + * block. The final operation may be non-multiple of block size. This mode + * may be used for AES. The block size is determined by the algorithm. + */ + FSL_SYM_MODE_CTR, +} fsl_shw_sym_mode_t; + +/*! + * Algorithm selector for Cryptographic Hash functions. + * + * Selection of algorithm determines how large the context and digest will be. + * Context is the same size as the digest (resulting hash), unless otherwise + * specified. + */ +typedef enum fsl_shw_hash_alg_t { + /*! MD5 algorithm. Digest is 16 octets. */ + FSL_HASH_ALG_MD5, + /*! SHA-1 (aka SHA or SHA-160) algorithm. Digest is 20 octets. */ + FSL_HASH_ALG_SHA1, + /*! + * SHA-224 algorithm. Digest is 28 octets, though context is 32 octets. + */ + FSL_HASH_ALG_SHA224, + /*! SHA-256 algorithm. Digest is 32 octets. */ + FSL_HASH_ALG_SHA256 +} fsl_shw_hash_alg_t; + +/*! + * The type of Authentication-Cipher function which will be performed. + */ +typedef enum fsl_shw_acc_mode_t { + /*! + * CBC-MAC for Counter. Requires context and modulus. Final operation may + * be non-multiple of block size. This mode may be used for AES. + */ + FSL_ACC_MODE_CCM, + /*! + * SSL mode. Not supported. Combines HMAC and encrypt (or decrypt). + * Needs one key object for encryption, another for the HMAC. The usual + * hashing and symmetric encryption algorithms are supported. + */ + FSL_ACC_MODE_SSL, +} fsl_shw_acc_mode_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Flags which control a Hash operation. + */ +typedef enum fsl_shw_hash_ctx_flags_t { + /*! + * Context is empty. Hash is started from scratch, with a + * message-processed count of zero. + */ + FSL_HASH_FLAGS_INIT = 0x01, + /*! + * Retrieve context from hardware after hashing. If used with the + * #FSL_HASH_FLAGS_FINALIZE flag, the final digest value will be saved in + * the object. + */ + FSL_HASH_FLAGS_SAVE = 0x02, + /*! Place context into hardware before hashing. */ + FSL_HASH_FLAGS_LOAD = 0x04, + /*! + * PAD message and perform final digest operation. If user message is + * pre-padded, this flag should not be used. + */ + FSL_HASH_FLAGS_FINALIZE = 0x08, +} fsl_shw_hash_ctx_flags_t; + +/*! + * Flags which control an HMAC operation. + * + * These may be combined by ORing them together. See #fsl_shw_hmco_set_flags() + * and #fsl_shw_hmco_clear_flags(). + */ +typedef enum fsl_shw_hmac_ctx_flags_t { + /*! + * Message context is empty. HMAC is started from scratch (with key) or + * from precompute of inner hash, depending on whether + * #FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT is set. + */ + FSL_HMAC_FLAGS_INIT = 1, + /*! + * Retrieve ongoing context from hardware after hashing. If used with the + * #FSL_HMAC_FLAGS_FINALIZE flag, the final digest value (HMAC) will be + * saved in the object. + */ + FSL_HMAC_FLAGS_SAVE = 2, + /*! Place ongoing context into hardware before hashing. */ + FSL_HMAC_FLAGS_LOAD = 4, + /*! + * PAD message and perform final HMAC operations of inner and outer + * hashes. + */ + FSL_HMAC_FLAGS_FINALIZE = 8, + /*! + * This means that the context contains precomputed inner and outer hash + * values. + */ + FSL_HMAC_FLAGS_PRECOMPUTES_PRESENT = 16, +} fsl_shw_hmac_ctx_flags_t; + +/*! + * Flags to control use of the #fsl_shw_scco_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_scco_set_flags() and #fsl_shw_scco_clear_flags() + */ +typedef enum fsl_shw_sym_ctx_flags_t { + /*! + * Context is empty. In ARC4, this means that the S-Box needs to be + * generated from the key. In #FSL_SYM_MODE_CBC mode, this allows an IV of + * zero to be specified. In #FSL_SYM_MODE_CTR mode, it means that an + * initial CTR value of zero is desired. + */ + FSL_SYM_CTX_INIT = 1, + /*! + * Load context from object into hardware before running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_LOAD = 2, + /*! + * Save context from hardware into object after running cipher. In + * #FSL_SYM_MODE_CTR mode, this would refer to the Counter Value. + */ + FSL_SYM_CTX_SAVE = 4, + /*! + * Context (SBox) is to be unwrapped and wrapped on each use. + * This flag is unsupported. + * */ + FSL_SYM_CTX_PROTECT = 8, +} fsl_shw_sym_ctx_flags_t; + +/*! + * Flags which describe the state of the #fsl_shw_sko_t. + * + * These may be ORed together to get the desired effect. + * See #fsl_shw_sko_set_flags() and #fsl_shw_sko_clear_flags() + */ +typedef enum fsl_shw_key_flags_t { + /*! If algorithm is DES or 3DES, do not validate the key parity bits. */ + FSL_SKO_KEY_IGNORE_PARITY = 1, + /*! Clear key is present in the object. */ + FSL_SKO_KEY_PRESENT = 2, + /*! + * Key has been established for use. This feature is not available for all + * platforms, nor for all algorithms and modes. + */ + FSL_SKO_KEY_ESTABLISHED = 4, + /*! + * Key intended for user (software) use; can be read cleartext from the + * keystore. + */ + FSL_SKO_KEY_SW_KEY = 8, +} fsl_shw_key_flags_t; + +/*! + * Type of value which is associated with an established key. + */ +typedef uint64_t key_userid_t; + +/*! + * Flags which describe the state of the #fsl_shw_acco_t. + * + * The @a FSL_ACCO_CTX_INIT and @a FSL_ACCO_CTX_FINALIZE flags, when used + * together, provide for a one-shot operation. + */ +typedef enum fsl_shw_auth_ctx_flags_t { + /*! Initialize Context(s) */ + FSL_ACCO_CTX_INIT = 1, + /*! Load intermediate context(s). This flag is unsupported. */ + FSL_ACCO_CTX_LOAD = 2, + /*! Save intermediate context(s). This flag is unsupported. */ + FSL_ACCO_CTX_SAVE = 4, + /*! Create MAC during this operation. */ + FSL_ACCO_CTX_FINALIZE = 8, + /*! + * Formatting of CCM input data is performed by calls to + * #fsl_shw_ccm_nist_format_ctr_and_iv() and + * #fsl_shw_ccm_nist_update_ctr_and_iv(). + */ + FSL_ACCO_NIST_CCM = 0x10, +} fsl_shw_auth_ctx_flags_t; + +/*! + * The operation which controls the behavior of #fsl_shw_establish_key(). + * + * These values are passed to #fsl_shw_establish_key(). + */ +typedef enum fsl_shw_key_wrap_t { + /*! Generate a key from random values. */ + FSL_KEY_WRAP_CREATE, + /*! Use the provided clear key. */ + FSL_KEY_WRAP_ACCEPT, + /*! Unwrap a previously wrapped key. */ + FSL_KEY_WRAP_UNWRAP +} fsl_shw_key_wrap_t; + +/*! + * Modulus Selector for CTR modes. + * + * The incrementing of the Counter value may be modified by a modulus. If no + * modulus is needed or desired for AES, use #FSL_CTR_MOD_128. + */ +typedef enum fsl_shw_ctr_mod_t { + FSL_CTR_MOD_8, /*!< Run counter with modulus of 2^8. */ + FSL_CTR_MOD_16, /*!< Run counter with modulus of 2^16. */ + FSL_CTR_MOD_24, /*!< Run counter with modulus of 2^24. */ + FSL_CTR_MOD_32, /*!< Run counter with modulus of 2^32. */ + FSL_CTR_MOD_40, /*!< Run counter with modulus of 2^40. */ + FSL_CTR_MOD_48, /*!< Run counter with modulus of 2^48. */ + FSL_CTR_MOD_56, /*!< Run counter with modulus of 2^56. */ + FSL_CTR_MOD_64, /*!< Run counter with modulus of 2^64. */ + FSL_CTR_MOD_72, /*!< Run counter with modulus of 2^72. */ + FSL_CTR_MOD_80, /*!< Run counter with modulus of 2^80. */ + FSL_CTR_MOD_88, /*!< Run counter with modulus of 2^88. */ + FSL_CTR_MOD_96, /*!< Run counter with modulus of 2^96. */ + FSL_CTR_MOD_104, /*!< Run counter with modulus of 2^104. */ + FSL_CTR_MOD_112, /*!< Run counter with modulus of 2^112. */ + FSL_CTR_MOD_120, /*!< Run counter with modulus of 2^120. */ + FSL_CTR_MOD_128 /*!< Run counter with modulus of 2^128. */ +} fsl_shw_ctr_mod_t; + +/*! + * Permissions flags for Secure Partitions + */ +typedef enum fsl_shw_permission_t { +/*! SCM Access Permission: Do not zeroize/deallocate partition on SMN Fail state */ + FSL_PERM_NO_ZEROIZE = 0x80000000, +/*! SCM Access Permission: Enforce trusted key read in */ + FSL_PERM_TRUSTED_KEY_READ = 0x40000000, +/*! SCM Access Permission: Ignore Supervisor/User mode in permission determination */ + FSL_PERM_HD_S = 0x00000800, +/*! SCM Access Permission: Allow Read Access to Host Domain */ + FSL_PERM_HD_R = 0x00000400, +/*! SCM Access Permission: Allow Write Access to Host Domain */ + FSL_PERM_HD_W = 0x00000200, +/*! SCM Access Permission: Allow Execute Access to Host Domain */ + FSL_PERM_HD_X = 0x00000100, +/*! SCM Access Permission: Allow Read Access to Trusted Host Domain */ + FSL_PERM_TH_R = 0x00000040, +/*! SCM Access Permission: Allow Write Access to Trusted Host Domain */ + FSL_PERM_TH_W = 0x00000020, +/*! SCM Access Permission: Allow Read Access to Other/World Domain */ + FSL_PERM_OT_R = 0x00000004, +/*! SCM Access Permission: Allow Write Access to Other/World Domain */ + FSL_PERM_OT_W = 0x00000002, +/*! SCM Access Permission: Allow Execute Access to Other/World Domain */ + FSL_PERM_OT_X = 0x00000001, +} fsl_shw_permission_t; + +typedef enum fsl_shw_cypher_mode_t { + FSL_SHW_CYPHER_MODE_ECB = 1, /*!< ECB mode */ + FSL_SHW_CYPHER_MODE_CBC = 2, /*!< CBC mode */ +} fsl_shw_cypher_mode_t; + +typedef enum fsl_shw_pf_key_t { + FSL_SHW_PF_KEY_IIM, /*!< Present fused IIM key */ + FSL_SHW_PF_KEY_PRG, /*!< Present Program key */ + FSL_SHW_PF_KEY_IIM_PRG, /*!< Present IIM ^ Program key */ + FSL_SHW_PF_KEY_IIM_RND, /*!< Present Random key */ + FSL_SHW_PF_KEY_RND, /*!< Present IIM ^ Random key */ +} fsl_shw_pf_key_t; + +typedef enum fsl_shw_tamper_t { + FSL_SHW_TAMPER_NONE, /*!< No error detected */ + FSL_SHW_TAMPER_WTD, /*!< wire-mesh tampering det */ + FSL_SHW_TAMPER_ETBD, /*!< ext tampering det: input B */ + FSL_SHW_TAMPER_ETAD, /*!< ext tampering det: input A */ + FSL_SHW_TAMPER_EBD, /*!< external boot detected */ + FSL_SHW_TAMPER_SAD, /*!< security alarm detected */ + FSL_SHW_TAMPER_TTD, /*!< temperature tampering det */ + FSL_SHW_TAMPER_CTD, /*!< clock tampering det */ + FSL_SHW_TAMPER_VTD, /*!< voltage tampering det */ + FSL_SHW_TAMPER_MCO, /*!< monotonic counter overflow */ + FSL_SHW_TAMPER_TCO, /*!< time counter overflow */ +} fsl_shw_tamper_t; + +/****************************************************************************** + * Data Structures + *****************************************************************************/ + +/*! + * + * @brief Structure type for descriptors + * + * The first five fields are passed to the hardware. + * + *****************************************************************************/ +#ifndef USE_NEW_PTRS /* Experimental */ + +typedef struct sah_Desc { + uint32_t header; /*!< descriptor header value */ + uint32_t len1; /*!< number of data bytes in 'ptr1' buffer */ + void *ptr1; /*!< pointer to first sah_Link structure */ + uint32_t len2; /*!< number of data bytes in 'ptr2' buffer */ + void *ptr2; /*!< pointer to second sah_Link structure */ + struct sah_Desc *next; /*!< pointer to next descriptor */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two must be last. See sah_Copy_Descriptors */ + struct sah_Desc *virt_addr; /*!< Virtual (kernel) address of this + descriptor. */ + dma_addr_t dma_addr; /*!< Physical (bus) address of this + descriptor. */ + void *original_ptr1; /*!< user's pointer to ptr1 */ + void *original_ptr2; /*!< user's pointer to ptr2 */ + struct sah_Desc *original_next; /*!< user's pointer to next */ +#endif +} sah_Desc; + +#else + +typedef struct sah_Desc { + uint32_t header; /*!< descriptor header value */ + uint32_t len1; /*!< number of data bytes in 'ptr1' buffer */ + uint32_t hw_ptr1; /*!< pointer to first sah_Link structure */ + uint32_t len2; /*!< number of data bytes in 'ptr2' buffer */ + uint32_t hw_ptr2; /*!< pointer to second sah_Link structure */ + uint32_t hw_next; /*!< pointer to next descriptor */ + struct sah_Link *ptr1; /*!< (virtual) pointer to first sah_Link structure */ + struct sah_Link *ptr2; /*!< (virtual) pointer to first sah_Link structure */ + struct sah_Desc *next; /*!< (virtual) pointer to next descriptor */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two must be last. See sah_Copy_Descriptors */ + struct sah_Desc *virt_addr; /*!< Virtual (kernel) address of this + descriptor. */ + dma_addr_t dma_addr; /*!< Physical (bus) address of this + descriptor. */ +#endif +} sah_Desc; + +#endif + +/*! +******************************************************************************* +* @brief The first descriptor in a chain +******************************************************************************/ +typedef struct sah_Head_Desc { + sah_Desc desc; /*!< whole struct - must be first */ + struct fsl_shw_uco_t *user_info; /*!< where result pool lives */ + uint32_t user_ref; /*!< at time of request */ + uint32_t uco_flags; /*!< at time of request */ + uint32_t status; /*!< Status of queue entry */ + uint32_t error_status; /*!< If error, register from Sahara */ + uint32_t fault_address; /*!< If error, register from Sahara */ + uint32_t op_status; /*!< If error, register from Sahara */ + fsl_shw_return_t result; /*!< Result of running descriptor */ + struct sah_Head_Desc *next; /*!< Next in queue */ + struct sah_Head_Desc *prev; /*!< previous in queue */ + struct sah_Head_Desc *user_desc; /*!< For API async get_results */ + void *out1_ptr; /*!< For async post-processing */ + void *out2_ptr; /*!< For async post-processing */ + uint32_t out_len; /*!< For async post-processing */ +} sah_Head_Desc; + +/*! + * @brief Structure type for links + * + * The first three fields are used by hardware. + *****************************************************************************/ +#ifndef USE_NEW_PTRS + +typedef struct sah_Link { + size_t len; /*!< len of 'data' buffer in bytes */ + uint8_t *data; /*!< buffer to store data */ + struct sah_Link *next; /*!< pointer to the next sah_Link storing + * data */ + sah_Link_Flags flags; /*!< indicates the component that created the + * data buffer. Security Function internal + * information */ + key_userid_t ownerid; /*!< Auth code for established key */ + uint32_t slot; /*!< Location of the the established key */ +#ifdef __KERNEL__ /* This needs a better test */ + /* These two elements must be last. See sah_Copy_Links() */ + struct sah_Link *virt_addr; + dma_addr_t dma_addr; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + struct page *vm_info; +#endif + uint8_t *original_data; /*!< user's version of data pointer */ + struct sah_Link *original_next; /*!< user's version of next pointer */ +#ifdef SAH_COPY_DATA + uint8_t *copy_data; /*!< Virtual address of acquired buffer */ +#endif +#endif /* kernel-only */ +} sah_Link; + +#else + +typedef struct sah_Link { + /*! len of 'data' buffer in bytes */ + size_t len; + /*! buffer to store data */ + uint32_t hw_data; + /*! Physical address */ + uint32_t hw_next; + /*! + * indicates the component that created the data buffer. Security Function + * internal information + */ + sah_Link_Flags flags; + /*! (virtual) pointer to data */ + uint8_t *data; + /*! (virtual) pointer to the next sah_Link storing data */ + struct sah_Link *next; + /*! Auth code for established key */ + key_userid_t ownerid; + /*! Location of the the established key */ + uint32_t slot; +#ifdef __KERNEL__ /* This needs a better test */ + /* These two elements must be last. See sah_Copy_Links() */ + struct sah_Link *virt_addr; + dma_addr_t dma_addr; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + struct page *vm_info; +#endif +#endif /* kernel-only */ +} sah_Link; + +#endif + +/*! + * Initialization Object + */ +typedef struct fsl_sho_ibo_t { +} fsl_sho_ibo_t; + +/* Imported from Sahara1 driver -- is it needed forever? */ +/*! +******************************************************************************* +* FIELDS +* +* void * ref - parameter to be passed into the memory function calls +* +* void * (*malloc)(void *ref, size_t n) - pointer to user's malloc function +* +* void (*free)(void *ref, void *ptr) - pointer to user's free function +* +* void * (*memcpy)(void *ref, void *dest, const void *src, size_t n) - +* pointer to user's memcpy function +* +* void * (*memset)(void *ref, void *ptr, int ch, size_t n) - pointer to +* user's memset function +* +* @brief Structure for API memory utilities +******************************************************************************/ +typedef struct sah_Mem_Util { + /*! Who knows. Vestigial. */ + void *mu_ref; + /*! Acquire buffer of size n bytes */ + void *(*mu_malloc) (void *ref, size_t n); + /*! Acquire a sah_Head_Desc */ + sah_Head_Desc *(*mu_alloc_head_desc) (void *ref); + /* Acquire a sah_Desc */ + sah_Desc *(*mu_alloc_desc) (void *ref); + /* Acquire a sah_Link */ + sah_Link *(*mu_alloc_link) (void *ref); + /*! Free buffer at ptr */ + void (*mu_free) (void *ref, void *ptr); + /*! Free sah_Head_Desc at ptr */ + void (*mu_free_head_desc) (void *ref, sah_Head_Desc * ptr); + /*! Free sah_Desc at ptr */ + void (*mu_free_desc) (void *ref, sah_Desc * ptr); + /*! Free sah_Link at ptr */ + void (*mu_free_link) (void *ref, sah_Link * ptr); + /*! Funciton which will copy n bytes from src to dest */ + void *(*mu_memcpy) (void *ref, void *dest, const void *src, size_t n); + /*! Set all n bytes of ptr to ch */ + void *(*mu_memset) (void *ref, void *ptr, int ch, size_t n); +} sah_Mem_Util; + +/*! + * Secure Partition information + * + * This holds the context to a single secure partition owned by the user. It + * is only available in the kernel version of the User Context Object. + */ +typedef struct fsl_shw_spo_t { + uint32_t user_base; /*!< Base address (user virtual) */ + void *kernel_base; /*!< Base address (kernel virtual) */ + struct fsl_shw_spo_t *next; /*!< Pointer to the next partition + owned by the user. NULL if this + is the last partition. */ +} fsl_shw_spo_t; + +/* REQ-S2LRD-PINTFC-COA-UCO-001 */ +/*! + * User Context Object + */ +typedef struct fsl_shw_uco_t { + int sahara_openfd; /*!< this should be kernel-only?? */ + sah_Mem_Util *mem_util; /*!< Memory utility fns */ + uint32_t user_ref; /*!< User's reference */ + void (*callback) (struct fsl_shw_uco_t * uco); /*!< User's callback fn */ + uint32_t flags; /*!< from fsl_shw_user_ctx_flags_t */ + unsigned pool_size; /*!< maximum size of user pool */ +#ifdef __KERNEL__ + sah_Queue result_pool; /*!< where non-blocking results go */ + os_process_handle_t process; /*!< remember for signalling User mode */ + fsl_shw_spo_t *partition; /*!< chain of secure partitions owned by + the user */ +#else + struct fsl_shw_uco_t *next; /*!< To allow user-mode chaining of contexts, + for signalling. */ +#endif +} fsl_shw_uco_t; + +/* REQ-S2LRD-PINTFC-API-GEN-006 ?? */ +/*! + * Result object + */ +typedef struct fsl_shw_result_t { + uint32_t user_ref; + fsl_shw_return_t code; + uint32_t detail1; + uint32_t detail2; + sah_Head_Desc *user_desc; +} fsl_shw_result_t; + +/*! + * Keystore Object + */ +typedef struct fsl_shw_kso_t { +#ifdef __KERNEL__ + os_lock_t lock; /*!< Pointer to lock that controls access to + the keystore. */ +#endif + void *user_data; /*!< Pointer to user structure that handles + the internals of the keystore. */ + fsl_shw_return_t(*data_init) (fsl_shw_uco_t * user_ctx, + void **user_data); + void (*data_cleanup) (fsl_shw_uco_t * user_ctx, void **user_data); + fsl_shw_return_t(*slot_verify_access) (void *user_data, + uint64_t owner_id, + uint32_t slot); + fsl_shw_return_t(*slot_alloc) (void *user_data, uint32_t size_bytes, + uint64_t owner_id, uint32_t * slot); + fsl_shw_return_t(*slot_dealloc) (void *user_data, uint64_t owner_id, + uint32_t slot); + void *(*slot_get_address) (void *user_data, uint32_t slot); + uint32_t(*slot_get_base) (void *user_data, uint32_t slot); + uint32_t(*slot_get_offset) (void *user_data, uint32_t slot); + uint32_t(*slot_get_slot_size) (void *user_data, uint32_t slot); +} fsl_shw_kso_t; + +/* REQ-S2LRD-PINTFC-COA-SKO-001 */ +/*! + * Secret Key Context Object + */ +typedef struct fsl_shw_sko_t { + uint32_t flags; + fsl_shw_key_alg_t algorithm; + key_userid_t userid; + uint32_t handle; + uint16_t key_length; + uint8_t key[64]; + struct fsl_shw_kso_t *keystore; /*!< If present, key is in keystore */ +} fsl_shw_sko_t; + +/* REQ-S2LRD-PINTFC-COA-CO-001 */ +/*! + * @brief Platform Capability Object + */ +typedef struct fsl_shw_pco_t { /* Consider turning these constants into symbols */ + int api_major; + int api_minor; + int driver_major; + int driver_minor; + fsl_shw_key_alg_t sym_algorithms[4]; + fsl_shw_sym_mode_t sym_modes[4]; + fsl_shw_hash_alg_t hash_algorithms[4]; + uint8_t sym_support[5][4]; /* indexed by key alg then mode */ + + int scc_driver_major; + int scc_driver_minor; + int scm_version; /*!< Version from SCM Configuration register */ + int smn_version; /*!< Version from SMN Status register */ + int block_size_bytes; /*!< Number of bytes per block of RAM; also + block size of the crypto algorithm. */ + union { + struct { + int black_ram_size_blocks; /*!< Number of blocks of Black RAM */ + int red_ram_size_blocks; /*!< Number of blocks of Red RAM */ + } scc_info; + struct { + int partition_size_bytes; /*!< Number of bytes in each partition */ + int partition_count; /*!< Number of partitions on this platform */ + } scc2_info; + }; +} fsl_shw_pco_t; + +/* REQ-S2LRD-PINTFC-COA-HCO-001 */ +/*! + * Hash Context Object + */ +typedef struct fsl_shw_hco_t { /* fsl_shw_hash_context_object */ + fsl_shw_hash_alg_t algorithm; + uint32_t flags; + uint8_t digest_length; /* in bytes */ + uint8_t context_length; /* in bytes */ + uint8_t context_register_length; /* in bytes */ + uint32_t context[9]; /* largest digest + msg size */ +} fsl_shw_hco_t; + +/*! + * HMAC Context Object + */ +typedef struct fsl_shw_hmco_t { /* fsl_shw_hmac_context_object */ + fsl_shw_hash_alg_t algorithm; + uint32_t flags; + uint8_t digest_length; /*!< in bytes */ + uint8_t context_length; /*!< in bytes */ + uint8_t context_register_length; /*!< in bytes */ + uint32_t ongoing_context[9]; /*!< largest digest + msg + size */ + uint32_t inner_precompute[9]; /*!< largest digest + msg + size */ + uint32_t outer_precompute[9]; /*!< largest digest + msg + size */ +} fsl_shw_hmco_t; + +/* REQ-S2LRD-PINTFC-COA-SCCO-001 */ +/*! + * Symmetric Crypto Context Object Context Object + */ +typedef struct fsl_shw_scco_t { + uint32_t flags; + unsigned block_size_bytes; /* double duty block&ctx size */ + fsl_shw_sym_mode_t mode; + /* Could put modulus plus 16-octet context in union with arc4 + sbox+ptrs... */ + fsl_shw_ctr_mod_t modulus_exp; + uint8_t context[259]; +} fsl_shw_scco_t; + +/*! + * Authenticate-Cipher Context Object + + * An object for controlling the function of, and holding information about, + * data for the authenticate-cipher functions, #fsl_shw_gen_encrypt() and + * #fsl_shw_auth_decrypt(). + */ +typedef struct fsl_shw_acco_t { + uint32_t flags; /*!< See #fsl_shw_auth_ctx_flags_t for + meanings */ + fsl_shw_acc_mode_t mode; /*!< CCM only */ + uint8_t mac_length; /*!< User's value for length */ + unsigned q_length; /*!< NIST parameter - */ + fsl_shw_scco_t cipher_ctx_info; /*!< For running + encrypt/decrypt. */ + union { + fsl_shw_scco_t CCM_ctx_info; /*!< For running the CBC in + AES-CCM. */ + fsl_shw_hco_t hash_ctx_info; /*!< For running the hash */ + } auth_info; /*!< "auth" info struct */ + uint8_t unencrypted_mac[16]; /*!< max block size... */ +} fsl_shw_acco_t; + +/*! + * Used by Sahara API to retrieve completed non-blocking results. + */ +typedef struct sah_results { + unsigned requested; /*!< number of results requested */ + unsigned *actual; /*!< number of results obtained */ + fsl_shw_result_t *results; /*!< pointer to memory to hold results */ +} sah_results; + +/*! + * @typedef scc_partition_status_t + */ +/*! Partition status information. */ +typedef enum fsl_shw_partition_status_t { + FSL_PART_S_UNUSABLE, /*!< Partition not implemented */ + FSL_PART_S_UNAVAILABLE, /*!< Partition owned by other host */ + FSL_PART_S_AVAILABLE, /*!< Partition available */ + FSL_PART_S_ALLOCATED, /*!< Partition owned by host but not engaged + */ + FSL_PART_S_ENGAGED, /*!< Partition owned by host and engaged */ +} fsl_shw_partition_status_t; + +/****************************************************************************** + * Access Macros for Objects + *****************************************************************************/ +/*! + * Get FSL SHW API version + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the API is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the API is to be stored. + */ +#define fsl_shw_pco_get_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->api_major; \ + *(pcminor) = (pcobject)->api_minor; \ +} + +/*! + * Get underlying driver version. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the driver is to be stored. + */ +#define fsl_shw_pco_get_driver_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->driver_major; \ + *(pcminor) = (pcobject)->driver_minor; \ +} + +/*! + * Get list of symmetric algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] pcalgorithms A pointer to where to store the location of + * the list of algorithms. + * @param[out] pcacount A pointer to where to store the number of + * algorithms in the list at @a algorithms. + */ +#define fsl_shw_pco_get_sym_algorithms(pcobject, pcalgorithms, pcacount) \ +{ \ + *(pcalgorithms) = (pcobject)->sym_algorithms; \ + *(pcacount) = sizeof((pcobject)->sym_algorithms)/4; \ +} + +/*! + * Get list of symmetric modes supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsmodes A pointer to where to store the location of + * the list of modes. + * @param[out] gsacount A pointer to where to store the number of + * algorithms in the list at @a modes. + */ +#define fsl_shw_pco_get_sym_modes(pcobject, gsmodes, gsacount) \ +{ \ + *(gsmodes) = (pcobject)->sym_modes; \ + *(gsacount) = sizeof((pcobject)->sym_modes)/4; \ +} + +/*! + * Get list of hash algorithms supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param[out] gsalgorithms A pointer which will be set to the list of + * algorithms. + * @param[out] gsacount The number of algorithms in the list at @a + * algorithms. + */ +#define fsl_shw_pco_get_hash_algorithms(pcobject, gsalgorithms, gsacount) \ +{ \ + *(gsalgorithms) = (pcobject)->hash_algorithms; \ + *(gsacount) = sizeof((pcobject)->hash_algorithms)/4; \ +} + +/*! + * Determine whether the combination of a given symmetric algorithm and a given + * mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcalg A Symmetric Cipher algorithm. + * @param pcmode A Symmetric Cipher mode. + * + * @return 0 if combination is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_sym_supported(pcobject, pcalg, pcmode) \ + ((pcobject)->sym_support[pcalg][pcmode]) + +/*! + * Determine whether a given Encryption-Authentication mode is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * @param pcmode The Authentication mode. + * + * @return 0 if mode is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_auth_supported(pcobject, pcmode) \ + ((pcmode == FSL_ACC_MODE_CCM) ? 1 : 0) + +/*! + * Determine whether Black Keys (key establishment / wrapping) is supported. + * + * @param pcobject The Platform Capababilities Object to query. + * + * @return 0 if wrapping is not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_black_key_supported(pcobject) \ + 1 + +/*! + * Determine whether Programmed Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Programmed Key features are available, otherwise zero. + */ +#define fsl_shw_pco_check_pk_supported(pcobject) \ + 0 + +/*! + * Determine whether Software Key features are available + * + * @param pc_info The Platform Capabilities Object to query. + * + * @return 1 if Software key features are available, otherwise zero. + */ +#define fsl_shw_pco_check_sw_keys_supported(pcobject) \ + 0 + +/*! + * Get FSL SHW SCC driver version + * + * @param pcobject The Platform Capabilities Object to query. + * @param[out] pcmajor A pointer to where the major version + * of the SCC driver is to be stored. + * @param[out] pcminor A pointer to where the minor version + * of the SCC driver is to be stored. + */ +#define fsl_shw_pco_get_scc_driver_version(pcobject, pcmajor, pcminor) \ +{ \ + *(pcmajor) = (pcobject)->scc_driver_major; \ + *(pcminor) = (pcobject)->scc_driver_minor; \ +} + +/*! + * Get SCM hardware version + * + * @param pcobject The Platform Capabilities Object to query. + * @return The SCM hardware version + */ +#define fsl_shw_pco_get_scm_version(pcobject) \ + ((pcobject)->scm_version) + +/*! + * Get SMN hardware version + * + * @param pcobject The Platform Capabilities Object to query. + * @return The SMN hardware version + */ +#define fsl_shw_pco_get_smn_version(pcobject) \ + ((pcobject)->smn_version) + +/*! + * Get the size of an SCM block, in bytes + * + * @param pcobject The Platform Capabilities Object to query. + * @return The size of an SCM block, in bytes. + */ +#define fsl_shw_pco_get_scm_block_size(pcobject) \ + ((pcobject)->block_size_bytes) + +/*! + * Get size of Black and Red RAM memory + * + * @param pcobject The Platform Capabilities Object to query. + * @param[out] black_size A pointer to where the size of the Black RAM, in + * blocks, is to be placed. + * @param[out] red_size A pointer to where the size of the Red RAM, in + * blocks, is to be placed. + */ +#define fsl_shw_pco_get_smn_size(pcobject, black_size, red_size) \ +{ \ + if ((pcobject)->scm_version == 1) { \ + *(black_size) = (pcobject)->scc_info.black_ram_size_blocks; \ + *(red_size) = (pcobject)->scc_info.red_ram_size_blocks; \ + } else { \ + *(black_size) = 0; \ + *(red_size) = 0; \ + } \ +} + +/*! + * Determine whether Secure Partitions are supported + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return 0 if secure partitions are not supported, non-zero if supported. + */ +#define fsl_shw_pco_check_spo_supported(pcobject) \ + ((pcobject)->scm_version == 2) + +/*! + * Get the size of a Secure Partitions + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return Partition size, in bytes. 0 if Secure Partitions not supported. + */ +#define fsl_shw_pco_get_spo_size_bytes(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->scc2_info.partition_size_bytes) : 0 ) + +/*! + * Get the number of Secure Partitions on this platform + * + * @param pcobject The Platform Capabilities Object to query. + * + * @return Number of partitions. 0 if Secure Paritions not supported. Note + * that this returns the total number of partitions, not all may be + * available to the user. + */ +#define fsl_shw_pco_get_spo_count(pcobject) \ + (((pcobject)->scm_version == 2) ? \ + ((pcobject)->scc2_info.partition_count) : 0 ) + +/*! + * Initialize a User Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the User Context Object to initial values, and set the size + * of the results pool. The mode will be set to a default of + * #FSL_UCO_BLOCKING_MODE. + * + * When using non-blocking operations, this sets the maximum number of + * operations which can be outstanding. This number includes the counts of + * operations waiting to start, operation(s) being performed, and results which + * have not been retrieved. + * + * Changes to this value are ignored once user registration has completed. It + * should be set to 1 if only blocking operations will ever be performed. + * + * @param ucontext The User Context object to operate on. + * @param usize The maximum number of operations which can be + * outstanding. + */ +#ifdef __KERNEL__ +#define fsl_shw_uco_init(ucontext, usize) \ +{ \ + (ucontext)->pool_size = usize; \ + (ucontext)->flags = FSL_UCO_BLOCKING_MODE; \ + (ucontext)->sahara_openfd = -1; \ + (ucontext)->mem_util = NULL; \ + (ucontext)->partition = NULL; \ + (ucontext)->callback = NULL; \ +} +#else +#define fsl_shw_uco_init(ucontext, usize) \ +{ \ + (ucontext)->pool_size = usize; \ + (ucontext)->flags = FSL_UCO_BLOCKING_MODE; \ + (ucontext)->sahara_openfd = -1; \ + (ucontext)->mem_util = NULL; \ + (ucontext)->callback = NULL; \ +} +#endif + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param uref A value which will be passed back with a result. + */ +#define fsl_shw_uco_set_reference(ucontext, uref) \ + (ucontext)->user_ref = uref + +/*! + * Set the User Reference for the User Context. + * + * @param ucontext The User Context object to operate on. + * @param ucallback The function the API will invoke when an operation + * completes. + */ +#define fsl_shw_uco_set_callback(ucontext, ucallback) \ + (ucontext)->callback = ucallback + +/*! + * Set flags in the User Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_set_flags(ucontext, uflags) \ + (ucontext)->flags |= (uflags) + +/*! + * Clear flags in the User Context. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param ucontext The User Context object to operate on. + * @param uflags ORed values from #fsl_shw_user_ctx_flags_t. + */ +#define fsl_shw_uco_clear_flags(ucontext, uflags) \ + (ucontext)->flags &= ~(uflags) + +/*! + * Retrieve the reference value from a Result Object. + * + * @param robject The result object to query. + * + * @return The reference associated with the request. + */ +#define fsl_shw_ro_get_reference(robject) \ + (robject)->user_ref + +/*! + * Retrieve the status code from a Result Object. + * + * @param robject The result object to query. + * + * @return The status of the request. + */ +#define fsl_shw_ro_get_status(robject) \ + (robject)->code + +/*! + * Initialize a Secret Key Object. + * + * This function must be called before performing any other operation with + * the Object. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * + */ +#define fsl_shw_sko_init(skobject,skalgorithm) \ +{ \ + (skobject)->algorithm = skalgorithm; \ + (skobject)->flags = 0; \ + (skobject)->keystore = NULL; \ +} + +/*! + * Initialize a Secret Key Object to use a Platform Key register. + * + * This function must be called before performing any other operation with + * the Object. INVALID on this platform. + * + * @param skobject The Secret Key Object to be initialized. + * @param skalgorithm DES, AES, etc. + * @param skhwkey one of the fsl_shw_pf_key_t values. + * + */ +#define fsl_shw_sko_init_pf_key(skobject,skalgorithm,skhwkey) \ +{ \ + (skobject)->algorithm = -1; \ + (skobject)->flags = -1; \ + (skobject)->keystore = NULL; \ +} + +/*! + * Store a cleartext key in the key object. + * + * This has the side effect of setting the #FSL_SKO_KEY_PRESENT flag and + * resetting the #FSL_SKO_KEY_ESTABLISHED flag. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkey A pointer to the beginning of the key. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key(skobject, skkey, skkeylen) \ +{ \ + (skobject)->key_length = skkeylen; \ + copy_bytes((skobject)->key, skkey, skkeylen); \ + (skobject)->flags |= FSL_SKO_KEY_PRESENT; \ + (skobject)->flags &= ~FSL_SKO_KEY_ESTABLISHED; \ +} + +/*! + * Set a size for the key. + * + * This function would normally be used when the user wants the key to be + * generated from a random source. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skkeylen The length, in octets, of the key. The value should be + * appropriate to the key size supported by the algorithm. + * 64 octets is the absolute maximum value allowed for this + * call. + */ +#define fsl_shw_sko_set_key_length(skobject, skkeylen) \ + (skobject)->key_length = skkeylen; + +/*! + * Set the User ID associated with the key. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to identify authorized users of the key. + */ +#define fsl_shw_sko_set_user_id(skobject, skuserid) \ + (skobject)->userid = (skuserid) + +/*! + * Establish a user Keystore to hold the key. + */ +#define fsl_shw_sko_set_keystore(skobject, user_keystore) \ + (skobject)->keystore = (user_keystore) + +/*! + * Set the establish key handle into a key object. + * + * The @a userid field will be used to validate the access to the unwrapped + * key. This feature is not available for all platforms, nor for all + * algorithms and modes. + * + * The #FSL_SKO_KEY_ESTABLISHED will be set (and the #FSL_SKO_KEY_PRESENT flag + * will be cleared). + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skuserid The User ID to verify this user is an authorized user of + * the key. + * @param skhandle A @a handle from #fsl_shw_sko_get_established_info. + */ +#define fsl_shw_sko_set_established_info(skobject, skuserid, skhandle) \ +{ \ + (skobject)->userid = (skuserid); \ + (skobject)->handle = (skhandle); \ + (skobject)->flags |= FSL_SKO_KEY_ESTABLISHED; \ + (skobject)->flags &= \ + ~(FSL_SKO_KEY_PRESENT); \ +} + +/*! + * Retrieve the established-key handle from a key object. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skhandle The location to store the @a handle of the unwrapped + * key. + */ +#define fsl_shw_sko_get_established_info(skobject, skhandle) \ + *(skhandle) = (skobject)->handle + +/*! + * Extract the algorithm from a key object. + * + * @param skobject The Key Object to be queried. + * @param[out] skalgorithm A pointer to the location to store the algorithm. + */ +#define fsl_shw_sko_get_algorithm(skobject, skalgorithm) \ + *(skalgorithm) = (skobject)->algorithm + +/*! + * Retrieve the cleartext key from a key object that is stored in a user + * keystore. + * + * @param skobject The Key Object to be queried. + * @param[out] skkey A pointer to the location to store the key. NULL + * if the key is not stored in a user keystore. + */ +#define fsl_shw_sko_get_key(skobject, skkey) \ +{ \ + fsl_shw_kso_t* keystore = (skobject)->keystore; \ + if (keystore != NULL) { \ + *(skkey) = keystore->slot_get_address(keystore->user_data, \ + (skobject)->handle); \ + } else { \ + *(skkey) = NULL; \ + } \ +} + +/*! + * Determine the size of a wrapped key based upon the cleartext key's length. + * + * This function can be used to calculate the number of octets that + * #fsl_shw_extract_key() will write into the location at @a covered_key. + * + * If zero is returned at @a length, this means that the key length in + * @a key_info is not supported. + * + * @param wkeyinfo Information about a key to be wrapped. + * @param wkeylen Location to store the length of a wrapped + * version of the key in @a key_info. + */ +#define fsl_shw_sko_calculate_wrapped_size(wkeyinfo, wkeylen) \ +{ \ + register fsl_shw_sko_t* kp = wkeyinfo; \ + register uint32_t kl = kp->key_length; \ + int key_blocks = (kl + 15) / 16; \ + int base_size = 35; /* ICV + T' + ALG + LEN + FLAGS */ \ + \ + *(wkeylen) = base_size + 16 * key_blocks; \ +} + +/*! + * Set some flags in the key object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t which + * are to be set. + */ +#define fsl_shw_sko_set_flags(skobject, skflags) \ + (skobject)->flags |= (skflags) + +/*! + * Clear some flags in the key object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param skobject A variable of type #fsl_shw_sko_t. + * @param skflags (One or more) ORed members of #fsl_shw_key_flags_t + * which are to be reset. + */ +#define fsl_shw_sko_clear_flags(skobject, skflags) \ + (skobject)->flags &= ~(skflags) + +/*! + * Initialize a Hash Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the hash + * context object. + * + * @param hcobject The hash context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hco_init(hcobject, hcalgorithm) \ +{ \ + (hcobject)->algorithm = hcalgorithm; \ + (hcobject)->flags = 0; \ + switch (hcalgorithm) { \ + case FSL_HASH_ALG_MD5: \ + (hcobject)->digest_length = 16; \ + (hcobject)->context_length = 16; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA1: \ + (hcobject)->digest_length = 20; \ + (hcobject)->context_length = 20; \ + (hcobject)->context_register_length = 24; \ + break; \ + case FSL_HASH_ALG_SHA224: \ + (hcobject)->digest_length = 28; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + case FSL_HASH_ALG_SHA256: \ + (hcobject)->digest_length = 32; \ + (hcobject)->context_length = 32; \ + (hcobject)->context_register_length = 36; \ + break; \ + default: \ + /* error ! */ \ + (hcobject)->digest_length = 1; \ + (hcobject)->context_length = 1; \ + (hcobject)->context_register_length = 1; \ + break; \ + } \ +} + +/*! + * Get the current hash value and message length from the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to query. + * @param[out] hccontext Pointer to the location of @a length octets where to + * store a copy of the current value of the digest. + * @param hcclength Number of octets of hash value to copy. + * @param[out] hcmsglen Pointer to the location to store the number of octets + * already hashed. + */ +#define fsl_shw_hco_get_digest(hcobject, hccontext, hcclength, hcmsglen) \ +{ \ + copy_bytes(hccontext, (hcobject)->context, hcclength); \ + if ((hcobject)->algorithm == FSL_HASH_ALG_SHA224 \ + || (hcobject)->algorithm == FSL_HASH_ALG_SHA256) { \ + *(hcmsglen) = (hcobject)->context[8]; \ + } else { \ + *(hcmsglen) = (hcobject)->context[5]; \ + } \ +} + +/*! + * Get the hash algorithm from the hash context object. + * + * @param hcobject The hash context to query. + * @param[out] hcalgorithm Pointer to where the algorithm is to be stored. + */ +#define fsl_shw_hco_get_info(hcobject, hcalgorithm) \ +{ \ + *(hcalgorithm) = (hcobject)->algorithm; \ +} + +/*! + * Set the current hash value and message length in the hash context object. + * + * The algorithm must have already been specified. See #fsl_shw_hco_init(). + * + * @param hcobject The hash context to operate upon. + * @param hccontext Pointer to buffer of appropriate length to copy into + * the hash context object. + * @param hcmsglen The number of octets of the message which have + * already been hashed. + * + */ +#define fsl_shw_hco_set_digest(hcobject, hccontext, hcmsglen) \ +{ \ + copy_bytes((hcobject)->context, hccontext, (hcobject)->context_length); \ + if (((hcobject)->algorithm == FSL_HASH_ALG_SHA224) \ + || ((hcobject)->algorithm == FSL_HASH_ALG_SHA256)) { \ + (hcobject)->context[8] = hcmsglen; \ + } else { \ + (hcobject)->context[5] = hcmsglen; \ + } \ +} + +/*! + * Set flags in a Hash Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in a Hash Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The hash context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hash_ctx_flags_t. + */ +#define fsl_shw_hco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize an HMAC Context Object. + * + * This function must be called before performing any other operation with the + * Object. It sets the current message length and hash algorithm in the HMAC + * context object. + * + * @param hcobject The HMAC context to operate upon. + * @param hcalgorithm The hash algorithm to be used (#FSL_HASH_ALG_MD5, + * #FSL_HASH_ALG_SHA256, etc). + * + */ +#define fsl_shw_hmco_init(hcobject, hcalgorithm) \ + fsl_shw_hco_init(hcobject, hcalgorithm) + +/*! + * Set flags in an HMAC Context Object. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be set in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_set_flags(hcobject, hcflags) \ + (hcobject)->flags |= (hcflags) + +/*! + * Clear flags in an HMAC Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param hcobject The HMAC context to be operated on. + * @param hcflags The flags to be reset in the context. These can be ORed + * members of #fsl_shw_hmac_ctx_flags_t. + */ +#define fsl_shw_hmco_clear_flags(hcobject, hcflags) \ + (hcobject)->flags &= ~(hcflags) + +/*! + * Initialize a Symmetric Cipher Context Object. + * + * This function must be called before performing any other operation with the + * Object. This will set the @a mode and @a algorithm and initialize the + * Object. + * + * @param scobject The context object to operate on. + * @param scalg The cipher algorithm this context will be used with. + * @param scmode #FSL_SYM_MODE_CBC, #FSL_SYM_MODE_ECB, etc. + * + */ +#define fsl_shw_scco_init(scobject, scalg, scmode) \ +{ \ + register uint32_t bsb; /* block-size bytes */ \ + \ + switch (scalg) { \ + case FSL_KEY_ALG_AES: \ + bsb = 16; \ + break; \ + case FSL_KEY_ALG_DES: \ + /* fall through */ \ + case FSL_KEY_ALG_TDES: \ + bsb = 8; \ + break; \ + case FSL_KEY_ALG_ARC4: \ + bsb = 259; \ + break; \ + case FSL_KEY_ALG_HMAC: \ + bsb = 1; /* meaningless */ \ + break; \ + default: \ + bsb = 00; \ + } \ + (scobject)->block_size_bytes = bsb; \ + (scobject)->mode = scmode; \ + (scobject)->flags = 0; \ +} + +/*! + * Set the flags for a Symmetric Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_set_flags(scobject, scflags) \ + (scobject)->flags |= (scflags) + +/*! + * Clear some flags in a Symmetric Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param scobject The context object to operate on. + * @param scflags The flags to reset (one or more values from + * #fsl_shw_sym_ctx_flags_t ORed together). + * + */ +#define fsl_shw_scco_clear_flags(scobject, scflags) \ + (scobject)->flags &= ~(scflags) + +/*! + * Set the Context (IV) for a Symmetric Cipher Context. + * + * This is to set the context/IV for #FSL_SYM_MODE_CBC mode, or to set the + * context (the S-Box and pointers) for ARC4. The full context size will + * be copied. + * + * @param scobject The context object to operate on. + * @param sccontext A pointer to the buffer which contains the context. + * + */ +#define fsl_shw_scco_set_context(scobject, sccontext) \ + copy_bytes((scobject)->context, sccontext, \ + (scobject)->block_size_bytes) + +/*! + * Get the Context for a Symmetric Cipher Context. + * + * This is to retrieve the context/IV for #FSL_SYM_MODE_CBC mode, or to + * retrieve context (the S-Box and pointers) for ARC4. The full context + * will be copied. + * + * @param scobject The context object to operate on. + * @param[out] sccontext Pointer to location where context will be stored. + */ +#define fsl_shw_scco_get_context(scobject, sccontext) \ + copy_bytes(sccontext, (scobject)->context, (scobject)->block_size_bytes) + +/*! + * Set the Counter Value for a Symmetric Cipher Context. + * + * This will set the Counter Value for CTR mode. + * + * @param scobject The context object to operate on. + * @param sccounter The starting counter value. The number of octets. + * copied will be the block size for the algorithm. + * @param scmodulus The modulus for controlling the incrementing of the + * counter. + * + */ +#define fsl_shw_scco_set_counter_info(scobject, sccounter, scmodulus) \ + { \ + if ((sccounter) != NULL) { \ + copy_bytes((scobject)->context, sccounter, \ + (scobject)->block_size_bytes); \ + } \ + (scobject)->modulus_exp = scmodulus; \ + } + +/*! + * Get the Counter Value for a Symmetric Cipher Context. + * + * This will retrieve the Counter Value is for CTR mode. + * + * @param scobject The context object to query. + * @param[out] sccounter Pointer to location to store the current counter + * value. The number of octets copied will be the + * block size for the algorithm. + * @param[out] scmodulus Pointer to location to store the modulus. + * + */ +#define fsl_shw_scco_get_counter_info(scobject, sccounter, scmodulus) \ + { \ + if ((sccounter) != NULL) { \ + copy_bytes(sccounter, (scobject)->context, \ + (scobject)->block_size_bytes); \ + } \ + if ((scmodulus) != NULL) { \ + *(scmodulus) = (scobject)->modulus_exp; \ + } \ + } + +/*! + * Initialize a Authentication-Cipher Context. + * + * @param acobject Pointer to object to operate on. + * @param acmode The mode for this object (only #FSL_ACC_MODE_CCM + * supported). + */ +#define fsl_shw_acco_init(acobject, acmode) \ + { \ + (acobject)->flags = 0; \ + (acobject)->mode = (acmode); \ + } + +/*! + * Set the flags for a Authentication-Cipher Context. + * + * Turns on the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to set (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_set_flags(acobject, acflags) \ + (acobject)->flags |= (acflags) + +/*! + * Clear some flags in a Authentication-Cipher Context Object. + * + * Turns off the flags specified in @a flags. Other flags are untouched. + * + * @param acobject Pointer to object to operate on. + * @param acflags The flags to reset (one or more from + * #fsl_shw_auth_ctx_flags_t ORed together). + * + */ +#define fsl_shw_acco_clear_flags(acobject, acflags) \ + (acobject)->flags &= ~(acflags) + +/*! + * Set up the Authentication-Cipher Object for CCM mode. + * + * This will set the @a auth_object for CCM mode and save the @a ctr, + * and @a mac_length. This function can be called instead of + * #fsl_shw_acco_init(). + * + * The paramater @a ctr is Counter Block 0, (counter value 0), which is for the + * MAC. + * + * @param acobject Pointer to object to operate on. + * @param acalg Cipher algorithm. Only AES is supported. + * @param accounter The initial counter value. + * @param acmaclen The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_acco_set_ccm(acobject, acalg, accounter, acmaclen) \ +{ \ + (acobject)->flags = 0; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mac_length = acmaclen; \ + fsl_shw_scco_set_counter_info(&(acobject)->cipher_ctx_info, accounter, \ + FSL_CTR_MOD_128); \ +} + +/*! + * Format the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will also set the IV and CTR values per Appendix A of NIST + * Special Publication 800-38C (May 2004). It will also perform the + * #fsl_shw_acco_set_ccm() operation with information derived from this set of + * parameters. + * + * Note this function assumes the algorithm is AES. It initializes the + * @a auth_object by setting the mode to #FSL_ACC_MODE_CCM and setting the + * flags to be #FSL_ACCO_NIST_CCM. + * + * @param acobject Pointer to object to operate on. + * @param act The number of octets used for the MAC. Valid values are + * 4, 6, 8, 10, 12, 14, and 16. + * @param acad Number of octets of Associated Data (may be zero). + * @param acq A value for the size of the length of @a q field. Valid + * values are 1-8. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_format_ctr_and_iv(acobject, act, acad, acq, acN, acQ)\ + { \ + uint64_t Q = acQ; \ + uint8_t bflag = ((acad)?0x40:0) | ((((act)-2)/2)<<3) | ((acq)-1); \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + (acobject)->auth_info.CCM_ctx_info.block_size_bytes = 16; \ + (acobject)->cipher_ctx_info.block_size_bytes = 16; \ + (acobject)->mode = FSL_ACC_MODE_CCM; \ + (acobject)->flags = FSL_ACCO_NIST_CCM; \ + \ + /* Store away the MAC length (after calculating actual value */ \ + (acobject)->mac_length = (act); \ + /* Set Flag field in Block 0 */ \ + *((acobject)->auth_info.CCM_ctx_info.context) = bflag; \ + /* Set Nonce field in Block 0 */ \ + copy_bytes((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15-(acq)); \ + /* Set Flag field in ctr */ \ + *((acobject)->cipher_ctx_info.context) = (acq)-1; \ + /* Update the Q (payload length) field of Block0 */ \ + (acobject)->q_length = acq; \ + for (i = 0; i < (acq); i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Set the Nonce field of the ctr */ \ + copy_bytes((acobject)->cipher_ctx_info.context+1, acN, 15-(acq)); \ + /* Clear the block counter field of the ctr */ \ + memset((acobject)->cipher_ctx_info.context+16-(acq), 0, (acq)+1); \ + } + +/*! + * Update the First Block (IV) & Initial Counter Value per NIST CCM. + * + * This function will set the IV and CTR values per Appendix A of NIST Special + * Publication 800-38C (May 2004). + * + * Note this function assumes that #fsl_shw_ccm_nist_format_ctr_and_iv() has + * previously been called on the @a auth_object. + * + * @param acobject Pointer to object to operate on. + * @param acN The Nonce (packet number or other changing value). Must + * be (15 - @a q_length) octets long. + * @param acQ The value of Q (size of the payload in octets). + * + */ +/* Do we need to stash the +1 value of the CTR somewhere? */ +#define fsl_shw_ccm_nist_update_ctr_and_iv(acobject, acN, acQ) \ + { \ + uint64_t Q = acQ; \ + unsigned i; \ + uint8_t* qptr = (acobject)->auth_info.CCM_ctx_info.context + 15; \ + \ + /* Update the Nonce field field of Block0 */ \ + copy_bytes((acobject)->auth_info.CCM_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + /* Update the Q (payload length) field of Block0 */ \ + for (i = 0; i < (acobject)->q_length; i++) { \ + *qptr-- = Q & 0xFF; \ + Q >>= 8; \ + } \ + /* Update the Nonce field of the ctr */ \ + copy_bytes((acobject)->cipher_ctx_info.context+1, acN, \ + 15 - (acobject)->q_length); \ + } + +/****************************************************************************** + * Library functions + *****************************************************************************/ +/* REQ-S2LRD-PINTFC-API-GEN-003 */ +extern fsl_shw_pco_t *fsl_shw_get_capabilities(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-004 */ +extern fsl_shw_return_t fsl_shw_register_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-005 */ +extern fsl_shw_return_t fsl_shw_deregister_user(fsl_shw_uco_t * user_ctx); + +/* REQ-S2LRD-PINTFC-API-GEN-006 */ +extern fsl_shw_return_t fsl_shw_get_results(fsl_shw_uco_t * user_ctx, + unsigned result_size, + fsl_shw_result_t results[], + unsigned *result_count); + +extern fsl_shw_return_t fsl_shw_establish_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_key_wrap_t establish_type, + const uint8_t * key); + +extern fsl_shw_return_t fsl_shw_extract_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * covered_key); + +extern fsl_shw_return_t fsl_shw_release_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info); + +extern void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, + const uint8_t * UMID, uint32_t permissions); + +extern fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address); + +extern fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t * user_ctx, + void *address, + fsl_shw_partition_status_t * status); + +extern fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, + uint32_t permissions); + +extern fsl_shw_return_t do_scc_engage_partition(fsl_shw_uco_t * user_ctx, + void *address, + const uint8_t * UMID, + uint32_t permissions); + +extern fsl_shw_return_t do_system_keystore_slot_alloc(fsl_shw_uco_t * user_ctx, + uint32_t key_lenth, + uint64_t ownerid, + uint32_t * slot); + +extern fsl_shw_return_t do_system_keystore_slot_dealloc(fsl_shw_uco_t * + user_ctx, + uint64_t ownerid, + uint32_t slot); + +extern fsl_shw_return_t do_system_keystore_slot_load(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t * key, + uint32_t key_length); + +extern fsl_shw_return_t do_system_keystore_slot_read(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * key); + +extern fsl_shw_return_t do_system_keystore_slot_encrypt(fsl_shw_uco_t * + user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * black_data); + +extern fsl_shw_return_t do_system_keystore_slot_decrypt(fsl_shw_uco_t * + user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * + black_data); + +extern fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + +extern fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode); + +extern fsl_shw_return_t +system_keystore_get_slot_info(uint64_t owner_id, uint32_t slot, + uint32_t * address, uint32_t * slot_size_bytes); + +/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +extern fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * pt, + uint8_t * ct); + +/* PINTFC-API-BASIC-SYM-002 */ +/* PINTFC-API-BASIC-SYM-ARC4-001 */ +/* PINTFC-API-BASIC-SYM-ARC4-002 */ +extern fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_scco_t * sym_ctx, + uint32_t length, + const uint8_t * ct, + uint8_t * pt); + +/* REQ-S2LRD-PINTFC-API-BASIC-HASH-005 */ +extern fsl_shw_return_t fsl_shw_hash(fsl_shw_uco_t * user_ctx, + fsl_shw_hco_t * hash_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-001 */ +extern fsl_shw_return_t fsl_shw_hmac_precompute(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx); + +/* REQ-S2LRD-PINTFC-API-BASIC-HMAC-002 */ +extern fsl_shw_return_t fsl_shw_hmac(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + fsl_shw_hmco_t * hmac_ctx, + const uint8_t * msg, + uint32_t length, + uint8_t * result, uint32_t result_len); + +/* REQ-S2LRD-PINTFC-API-BASIC-RNG-002 */ +extern fsl_shw_return_t fsl_shw_get_random(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +extern fsl_shw_return_t fsl_shw_add_entropy(fsl_shw_uco_t * user_ctx, + uint32_t length, uint8_t * data); + +extern fsl_shw_return_t fsl_shw_gen_encrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * payload, + uint8_t * ct, uint8_t * auth_value); + +extern fsl_shw_return_t fsl_shw_auth_decrypt(fsl_shw_uco_t * user_ctx, + fsl_shw_acco_t * auth_ctx, + fsl_shw_sko_t * cipher_key_info, + fsl_shw_sko_t * auth_key_info, + uint32_t auth_data_length, + const uint8_t * auth_data, + uint32_t payload_length, + const uint8_t * ct, + const uint8_t * auth_value, + uint8_t * payload); + +extern fsl_shw_return_t fsl_shw_read_key(fsl_shw_uco_t * user_ctx, + fsl_shw_sko_t * key_info, + uint8_t * key); + +static inline fsl_shw_return_t fsl_shw_gen_random_pf_key(fsl_shw_uco_t * + user_ctx) +{ + (void)user_ctx; + + return FSL_RETURN_NO_RESOURCE_S; +} + +static inline fsl_shw_return_t fsl_shw_read_tamper_event(fsl_shw_uco_t * + user_ctx, + fsl_shw_tamper_t * + tamperp, + uint64_t * timestampp) +{ + (void)user_ctx; + (void)tamperp; + (void)timestampp; + + return FSL_RETURN_NO_RESOURCE_S; +} + +fsl_shw_return_t sah_Append_Desc(const sah_Mem_Util * mu, + sah_Head_Desc ** desc_head, + const uint32_t header, + sah_Link * link1, sah_Link * link2); + +/* Utility Function leftover from sahara1 API */ +void sah_Descriptor_Chain_Destroy(const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/* Utility Function leftover from sahara1 API */ +fsl_shw_return_t sah_Descriptor_Chain_Execute(sah_Head_Desc * desc_chain, + fsl_shw_uco_t * user_ctx); + +fsl_shw_return_t sah_Append_Link(const sah_Mem_Util * mu, + sah_Link * link, + uint8_t * p, + const size_t length, + const sah_Link_Flags flags); + +fsl_shw_return_t sah_Create_Link(const sah_Mem_Util * mu, + sah_Link ** link, + uint8_t * p, + const size_t length, + const sah_Link_Flags flags); + +fsl_shw_return_t sah_Create_Key_Link(const sah_Mem_Util * mu, + sah_Link ** link, + fsl_shw_sko_t * key_info); + +void sah_Destroy_Link(const sah_Mem_Util * mu, sah_Link * link); + +void sah_Postprocess_Results(fsl_shw_uco_t * user_ctx, + sah_results * result_info); + +#endif /* SAHARA2_API_H */ diff --git a/drivers/mxc/security/sahara2/include/sahara2_kernel.h b/drivers/mxc/security/sahara2/include/sahara2_kernel.h new file mode 100644 index 000000000000..b833f0ab8f56 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sahara2_kernel.h @@ -0,0 +1,49 @@ +/* + * 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 + */ + +#define DRIVER_NAME sahara2 + +#define SAHARA_MAJOR_NODE 78 + +#include "portable_os.h" + +#include "platform_abstractions.h" + +/* Forward-declare prototypes using signature macros */ + +OS_DEV_ISR_DCL(sahara2_isr); + +OS_DEV_INIT_DCL(sahara2_init); + +OS_DEV_SHUTDOWN_DCL(sahara2_shutdown); + +OS_DEV_OPEN_DCL(sahara2_open); + +OS_DEV_CLOSE_DCL(sahara2_release); + +OS_DEV_IOCTL_DCL(sahara2_ioctl); + +struct sahara2_kernel_user { + void *command_ring[32]; +}; + +struct sahara2_sym_arg { + char *key; + unsigned key_len; +}; + +/*! These need to be added to Linux / OS abstractions */ +/* +module_init(OS_DEV_INIT_REF(sahara2_init)); +module_cleanup(OS_DEV_SHUTDOWN_REF(sahara2_shutdown)); +*/ diff --git a/drivers/mxc/security/sahara2/include/sf_util.h b/drivers/mxc/security/sahara2/include/sf_util.h new file mode 100644 index 000000000000..c0af0c96ff02 --- /dev/null +++ b/drivers/mxc/security/sahara2/include/sf_util.h @@ -0,0 +1,466 @@ +/* + * Copyright 2004-2009 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 sf_util.h +* +* @brief Header for Sahara Descriptor-chain building Functions +*/ +#ifndef SF_UTIL_H +#define SF_UTIL_H + +#include <fsl_platform.h> +#include <sahara.h> + +/*! Header value for Sahara Descriptor 1 */ +#define SAH_HDR_SKHA_SET_MODE_IV_KEY 0x10880000 +/*! Header value for Sahara Descriptor 2 */ +#define SAH_HDR_SKHA_SET_MODE_ENC_DEC 0x108D0000 +/*! Header value for Sahara Descriptor 4 */ +#define SAH_HDR_SKHA_ENC_DEC 0x90850000 +/*! Header value for Sahara Descriptor 5 */ +#define SAH_HDR_SKHA_READ_CONTEXT_IV 0x10820000 +/*! Header value for Sahara Descriptor 6 */ +#define SAH_HDR_MDHA_SET_MODE_MD_KEY 0x20880000 +/*! Header value for Sahara Descriptor 8 */ +#define SAH_HDR_MDHA_SET_MODE_HASH 0x208D0000 +/*! Header value for Sahara Descriptor 10 */ +#define SAH_HDR_MDHA_HASH 0xA0850000 +/*! Header value for Sahara Descriptor 11 */ +#define SAH_HDR_MDHA_STORE_DIGEST 0x20820000 +/*! Header value for Sahara Descriptor 18 */ +#define SAH_HDR_RNG_GENERATE 0x308C0000 +/*! Header value for Sahara Descriptor 19 */ +#define SAH_HDR_PKHA_LD_N_E 0xC0800000 +/*! Header value for Sahara Descriptor 20 */ +#define SAH_HDR_PKHA_LD_A_EX_ST_B 0x408D0000 +/*! Header value for Sahara Descriptor 21 */ +#define SAH_HDR_PKHA_LD_N_EX_ST_B 0x408E0000 +/*! Header value for Sahara Descriptor 22 */ +#define SAH_HDR_PKHA_LD_A_B 0xC0830000 +/*! Header value for Sahara Descriptor 23 */ +#define SAH_HDR_PKHA_LD_A0_A1 0x40840000 +/*! Header value for Sahara Descriptor 24 */ +#define SAH_HDR_PKHA_LD_A2_A3 0xC0850000 +/*! Header value for Sahara Descriptor 25 */ +#define SAH_HDR_PKHA_LD_B0_B1 0xC0860000 +/*! Header value for Sahara Descriptor 26 */ +#define SAH_HDR_PKHA_LD_B2_B3 0x40870000 +/*! Header value for Sahara Descriptor 27 */ +#define SAH_HDR_PKHA_ST_A_B 0x40820000 +/*! Header value for Sahara Descriptor 28 */ +#define SAH_HDR_PKHA_ST_A0_A1 0x40880000 +/*! Header value for Sahara Descriptor 29 */ +#define SAH_HDR_PKHA_ST_A2_A3 0xC0890000 +/*! Header value for Sahara Descriptor 30 */ +#define SAH_HDR_PKHA_ST_B0_B1 0xC08A0000 +/*! Header value for Sahara Descriptor 31 */ +#define SAH_HDR_PKHA_ST_B2_B3 0x408B0000 +/*! Header value for Sahara Descriptor 32 */ +#define SAH_HDR_PKHA_EX_ST_B1 0xC08C0000 +/*! Header value for Sahara Descriptor 33 */ +#define SAH_HDR_ARC4_SET_MODE_SBOX 0x90890000 +/*! Header value for Sahara Descriptor 34 */ +#define SAH_HDR_ARC4_READ_SBOX 0x90860000 +/*! Header value for Sahara Descriptor 35 */ +#define SAH_HDR_ARC4_SET_MODE_KEY 0x90830000 +/*! Header value for Sahara Descriptor 36 */ +#define SAH_HDR_PKHA_LD_A3_B0 0x40810000 +/*! Header value for Sahara Descriptor 37 */ +#define SAH_HDR_PKHA_ST_B1_B2 0xC08F0000 +/*! Header value for Sahara Descriptor 38 */ +#define SAH_HDR_SKHA_CBC_ICV 0x10840000 +/*! Header value for Sahara Descriptor 39 */ +#define SAH_HDR_MDHA_ICV_CHECK 0xA08A0000 + +/*! Header bit indicating "Link-List optimization" */ +#define SAH_HDR_LLO 0x01000000 + +#define SAH_SF_DCLS \ + fsl_shw_return_t ret; \ + unsigned sf_executed = 0; \ + sah_Head_Desc* desc_chain = NULL; \ + uint32_t header + +#define SAH_SF_USER_CHECK() \ +do { \ + ret = sah_validate_uco(user_ctx); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} while (0) + +#define SAH_SF_EXECUTE() \ +do { \ + sf_executed = 1; \ + ret = sah_Descriptor_Chain_Execute(desc_chain, user_ctx); \ +} while (0) + +#define SAH_SF_DESC_CLEAN() \ +do { \ + if (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE)) { \ + sah_Descriptor_Chain_Destroy(user_ctx->mem_util, &desc_chain); \ + } \ + (void) header; \ +} while (0) + +/*! Add Descriptor with two inputs */ +#define DESC_IN_IN(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_in_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with two vectors */ +#define DESC_D_D(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_d_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and a key */ +#define DESC_IN_KEY(hdr, len1, ptr1, key2) \ +{ \ + ret = sah_add_in_key_desc(hdr, ptr1, len1, key2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and an output */ +#define DESC_IN_OUT(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_in_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with input and a key output */ +#define DESC_IN_KEYOUT(hdr, len1, ptr1, key2) \ +{ \ + ret = sah_add_in_keyout_desc(hdr, ptr1, len1, key2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with a key and an output */ +#define DESC_KEY_OUT(hdr, key1, len2, ptr2) \ +{ \ + ret = sah_add_key_out_desc(hdr, key1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with two outputs */ +#define DESC_OUT_OUT(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +/*! Add Descriptor with output then input pointers */ +#define DESC_OUT_IN(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_out_in_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} + +#ifdef SAH_SF_DEBUG +/*! Add Descriptor with two outputs */ +#define DBG_DESC(hdr, len1, ptr1, len2, ptr2) \ +{ \ + ret = sah_add_two_out_desc(hdr, ptr1, len1, ptr2, len2, \ + user_ctx->mem_util, &desc_chain); \ + if (ret != FSL_RETURN_OK_S) { \ + goto out; \ + } \ +} +#else +#define DBG_DESC(hdr, len1, ptr1, len2, ptr2) +#endif + +#ifdef __KERNEL__ +#define DESC_DBG_ON ({console_loglevel = 8;}) +#define DESC_DBG_OFF ({console_loglevel = 7;}) +#else +#define DESC_DBG_ON system("echo 8 > /proc/sys/kernel/printk") +#define DESC_DBG_OFF system("echo 7 > /proc/sys/kernel/printk") +#endif + +#define DESC_TEMP_ALLOC(size) \ +({ \ + uint8_t* ptr; \ + ptr = user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, \ + size); \ + if (ptr == NULL) { \ + ret = FSL_RETURN_NO_RESOURCE_S; \ + goto out; \ + } \ + \ + ptr; \ +}) + +#define DESC_TEMP_FREE(ptr) \ +({ \ + if ((ptr != NULL) && \ + (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE))) { \ + user_ctx->mem_util-> \ + mu_free(user_ctx->mem_util->mu_ref, ptr); \ + ptr = NULL; \ + } \ +}) + +/* Temporary implementation. This needs to be in internal/secure RAM */ +#define DESC_TEMP_SECURE_ALLOC(size) \ +({ \ + uint8_t* ptr; \ + ptr = user_ctx->mem_util->mu_malloc(user_ctx->mem_util->mu_ref, \ + size); \ + if (ptr == NULL) { \ + ret = FSL_RETURN_NO_RESOURCE_S; \ + goto out; \ + } \ + \ + ptr; \ +}) + +#define DESC_TEMP_SECURE_FREE(ptr, size) \ +({ \ + if ((ptr != NULL) && \ + (!sf_executed || (user_ctx->flags & FSL_UCO_BLOCKING_MODE))) { \ + user_ctx->mem_util->mu_memset(user_ctx->mem_util->mu_ref, \ + ptr, 0, size); \ + \ + user_ctx->mem_util-> \ + mu_free(user_ctx->mem_util->mu_ref, ptr); \ + ptr = NULL; \ + } \ +}) + +extern const uint32_t sah_insert_mdha_algorithm[]; + +/*! @defgroup mdhaflags MDHA Mode Register Values + * + * These are bit fields and combinations of bit fields for setting the Mode + * Register portion of a Sahara Descriptor Header field. + * + * The parity bit has been set to ensure that these values have even parity, + * therefore using an Exclusive-OR operation against an existing header will + * preserve its parity. + * + * @addtogroup mdhaflags + @{ + */ +#define sah_insert_mdha_icv_check 0x80001000 +#define sah_insert_mdha_ssl 0x80000400 +#define sah_insert_mdha_mac_full 0x80000200 +#define sah_insert_mdha_opad 0x80000080 +#define sah_insert_mdha_ipad 0x80000040 +#define sah_insert_mdha_init 0x80000020 +#define sah_insert_mdha_hmac 0x80000008 +#define sah_insert_mdha_pdata 0x80000004 +#define sah_insert_mdha_algorithm_sha224 0x00000003 +#define sah_insert_mdha_algorithm_sha256 0x80000002 +#define sah_insert_mdha_algorithm_md5 0x80000001 +#define sah_insert_mdha_algorithm_sha1 0x00000000 +/*! @} */ + +extern const uint32_t sah_insert_skha_algorithm[]; +extern const uint32_t sah_insert_skha_mode[]; +extern const uint32_t sah_insert_skha_modulus[]; + +/*! @defgroup skhaflags SKHA Mode Register Values + * + * These are bit fields and combinations of bit fields for setting the Mode + * Register portion of a Sahara Descriptor Header field. + * + * The parity bit has been set to ensure that these values have even parity, + * therefore using an Exclusive-OR operation against an existing header will + * preserve its parity. + * + * @addtogroup skhaflags + @{ + */ +/*! */ +#define sah_insert_skha_modulus_128 0x00001e00 +#define sah_insert_skha_no_key_parity 0x80000100 +#define sah_insert_skha_ctr_last_block 0x80000020 +#define sah_insert_skha_suppress_cbc 0x80000020 +#define sah_insert_skha_no_permute 0x80000020 +#define sah_insert_skha_algorithm_arc4 0x00000003 +#define sah_insert_skha_algorithm_tdes 0x80000002 +#define sah_insert_skha_algorithm_des 0x80000001 +#define sah_insert_skha_algorithm_aes 0x00000000 +#define sah_insert_skha_aux0 0x80000020 +#define sah_insert_skha_mode_ctr 0x00000018 +#define sah_insert_skha_mode_ccm 0x80000010 +#define sah_insert_skha_mode_cbc 0x80000008 +#define sah_insert_skha_mode_ecb 0x00000000 +#define sah_insert_skha_encrypt 0x80000004 +#define sah_insert_skha_decrypt 0x00000000 +/*! @} */ + +/*! @defgroup rngflags RNG Mode Register Values + * + */ +/*! */ +#define sah_insert_rng_gen_seed 0x80000001 + +/*! @} */ + +/*! @defgroup pkhaflags PKHA Mode Register Values + * + */ +/*! */ +#define sah_insert_pkha_soft_err_false 0x80000200 +#define sah_insert_pkha_soft_err_true 0x80000100 + +#define sah_insert_pkha_rtn_clr_mem 0x80000001 +#define sah_insert_pkha_rtn_clr_eram 0x80000002 +#define sah_insert_pkha_rtn_mod_exp 0x00000003 +#define sah_insert_pkha_rtn_mod_r2modn 0x80000004 +#define sah_insert_pkha_rtn_mod_rrmodp 0x00000005 +#define sah_insert_pkha_rtn_ec_fp_aff_ptmul 0x00000006 +#define sah_insert_pkha_rtn_ec_f2m_aff_ptmul 0x80000007 +#define sah_insert_pkha_rtn_ec_fp_proj_ptmul 0x80000008 +#define sah_insert_pkha_rtn_ec_f2m_proj_ptmul 0x00000009 +#define sah_insert_pkha_rtn_ec_fp_add 0x0000000A +#define sah_insert_pkha_rtn_ec_fp_double 0x8000000B +#define sah_insert_pkha_rtn_ec_f2m_add 0x0000000C +#define sah_insert_pkha_rtn_ec_f2m_double 0x8000000D +#define sah_insert_pkha_rtn_f2m_r2modn 0x8000000E +#define sah_insert_pkha_rtn_f2m_inv 0x0000000F +#define sah_insert_pkha_rtn_mod_inv 0x80000010 +#define sah_insert_pkha_rtn_rsa_sstep 0x00000011 +#define sah_insert_pkha_rtn_mod_emodn 0x00000012 +#define sah_insert_pkha_rtn_f2m_emodn 0x80000013 +#define sah_insert_pkha_rtn_ec_fp_ptmul 0x00000014 +#define sah_insert_pkha_rtn_ec_f2m_ptmul 0x80000015 +#define sah_insert_pkha_rtn_f2m_gcd 0x80000016 +#define sah_insert_pkha_rtn_mod_gcd 0x00000017 +#define sah_insert_pkha_rtn_f2m_dbl_aff 0x00000018 +#define sah_insert_pkha_rtn_fp_dbl_aff 0x80000019 +#define sah_insert_pkha_rtn_f2m_add_aff 0x8000001A +#define sah_insert_pkha_rtn_fp_add_aff 0x0000001B +#define sah_insert_pkha_rtn_f2m_exp 0x8000001C +#define sah_insert_pkha_rtn_mod_exp_teq 0x0000001D +#define sah_insert_pkha_rtn_rsa_sstep_teq 0x0000001E +#define sah_insert_pkha_rtn_f2m_multn 0x8000001F +#define sah_insert_pkha_rtn_mod_multn 0x80000020 +#define sah_insert_pkha_rtn_mod_add 0x00000021 +#define sah_insert_pkha_rtn_mod_sub 0x00000022 +#define sah_insert_pkha_rtn_mod_mult1_mont 0x80000023 +#define sah_insert_pkha_rtn_mod_mult2_deconv 0x00000024 +#define sah_insert_pkha_rtn_f2m_add 0x80000025 +#define sah_insert_pkha_rtn_f2m_mult1_mont 0x80000026 +#define sah_insert_pkha_rtn_f2m_mult2_deconv 0x00000027 +#define sah_insert_pkha_rtn_miller_rabin 0x00000028 +#define sah_insert_pkha_rtn_mod_amodn 0x00000029 +#define sah_insert_pkha_rtn_f2m_amodn 0x8000002A +/*! @} */ + +/*! Add a descriptor with two input pointers */ +fsl_shw_return_t sah_add_two_in_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + const uint8_t * in2, + uint32_t in2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two 'data' pointers */ +fsl_shw_return_t sah_add_two_d_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + const uint8_t * in2, + uint32_t in2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and key pointer */ +fsl_shw_return_t sah_add_in_key_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + fsl_shw_sko_t * key_info, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two key pointers */ +fsl_shw_return_t sah_add_key_key_desc(uint32_t header, + fsl_shw_sko_t * key_info1, + fsl_shw_sko_t * key_info2, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with two output pointers */ +fsl_shw_return_t sah_add_two_out_desc(uint32_t header, + uint8_t * out1, + uint32_t out1_length, + uint8_t * out2, + uint32_t out2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and output pointer */ +fsl_shw_return_t sah_add_in_out_desc(uint32_t header, + const uint8_t * in, uint32_t in_length, + uint8_t * out, uint32_t out_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an input and key output pointer */ +fsl_shw_return_t sah_add_in_keyout_desc(uint32_t header, + const uint8_t * in, uint32_t in_length, + fsl_shw_sko_t * key_info, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with a key and an output pointer */ +fsl_shw_return_t sah_add_key_out_desc(uint32_t header, + const fsl_shw_sko_t * key_info, + uint8_t * out, uint32_t out_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Add a descriptor with an output and input pointer */ +fsl_shw_return_t sah_add_out_in_desc(uint32_t header, + uint8_t * out, uint32_t out_length, + const uint8_t * in, uint32_t in_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain); + +/*! Verify that supplied User Context Object is valid */ +fsl_shw_return_t sah_validate_uco(fsl_shw_uco_t * uco); + +#endif /* SF_UTIL_H */ + +/* End of sf_util.h */ diff --git a/drivers/mxc/security/sahara2/km_adaptor.c b/drivers/mxc/security/sahara2/km_adaptor.c new file mode 100644 index 000000000000..50c4eac3c701 --- /dev/null +++ b/drivers/mxc/security/sahara2/km_adaptor.c @@ -0,0 +1,849 @@ +/* + * Copyright 2004-2009 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 km_adaptor.c +* +* @brief The Adaptor component provides an interface to the +* driver for a kernel user. +*/ + +#include <adaptor.h> +#include <sf_util.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#include <fsl_shw_keystore.h> +#ifdef FSL_HAVE_SCC +#include <linux/mxc_scc_driver.h> +#elif defined (FSL_HAVE_SCC2) +#include <linux/mxc_scc2_driver.h> +#endif + + +EXPORT_SYMBOL(adaptor_Exec_Descriptor_Chain); +EXPORT_SYMBOL(sah_register); +EXPORT_SYMBOL(sah_deregister); +EXPORT_SYMBOL(sah_get_results); +EXPORT_SYMBOL(fsl_shw_smalloc); +EXPORT_SYMBOL(fsl_shw_sfree); +EXPORT_SYMBOL(fsl_shw_sstatus); +EXPORT_SYMBOL(fsl_shw_diminish_perms); +EXPORT_SYMBOL(do_scc_encrypt_region); +EXPORT_SYMBOL(do_scc_decrypt_region); +EXPORT_SYMBOL(do_system_keystore_slot_alloc); +EXPORT_SYMBOL(do_system_keystore_slot_dealloc); +EXPORT_SYMBOL(do_system_keystore_slot_load); +EXPORT_SYMBOL(do_system_keystore_slot_read); +EXPORT_SYMBOL(do_system_keystore_slot_encrypt); +EXPORT_SYMBOL(do_system_keystore_slot_decrypt); + + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DIAG_ADAPTOR) +#include <diagnostic.h> +#endif + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DIAG_ADAPTOR) +#define MAX_DUMP 16 + +#define DIAG_MSG_SIZE 300 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +/* This is the wait queue to this mode of driver */ +DECLARE_WAIT_QUEUE_HEAD(Wait_queue_km); + +/*! This matches Sahara2 capabilities... */ +fsl_shw_pco_t sahara2_capabilities = { + 1, 3, /* api version number - major & minor */ + 1, 6, /* driver version number - major & minor */ + { + FSL_KEY_ALG_AES, + FSL_KEY_ALG_DES, + FSL_KEY_ALG_TDES, + FSL_KEY_ALG_ARC4}, + { + FSL_SYM_MODE_STREAM, + FSL_SYM_MODE_ECB, + FSL_SYM_MODE_CBC, + FSL_SYM_MODE_CTR}, + { + FSL_HASH_ALG_MD5, + FSL_HASH_ALG_SHA1, + FSL_HASH_ALG_SHA224, + FSL_HASH_ALG_SHA256}, + /* + * The following table must be set to handle all values of key algorithm + * and sym mode, and be in the correct order.. + */ + { /* Stream, ECB, CBC, CTR */ + {0, 0, 0, 0}, /* HMAC */ + {0, 1, 1, 1}, /* AES */ + {0, 1, 1, 0}, /* DES */ + {0, 1, 1, 0}, /* 3DES */ + {1, 0, 0, 0} /* ARC4 */ + }, + 0, 0, + 0, 0, 0, + {{0, 0}} +}; + +#ifdef DIAG_ADAPTOR +void km_Dump_Chain(const sah_Desc * chain); + +void km_Dump_Region(const char *prefix, const unsigned char *data, + unsigned length); + +static void km_Dump_Link(const char *prefix, const sah_Link * link); + +void km_Dump_Words(const char *prefix, const unsigned *data, unsigned length); +#endif + +/**** Memory routines ****/ + +static void *my_malloc(void *ref, size_t n) +{ + register void *mem; + +#ifndef DIAG_MEM_ERRORS + mem = os_alloc_memory(n, GFP_KERNEL); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + mem = 0; + } else { + mem = os_alloc_memory(n, GFP_ATOMIC); + } + } +#endif /* DIAG_MEM_ERRORS */ + +#ifdef DIAG_MEM + sprintf(Diag_msg, "API kmalloc: %p for %d\n", mem, n); + LOG_KDIAG(Diag_msg); +#endif + ref = 0; /* unused param warning */ + return mem; +} + +static sah_Head_Desc *my_alloc_head_desc(void *ref) +{ + register sah_Head_Desc *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Head_Descriptor(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Head_Descriptor(); + } + } +#endif + ref = 0; + return ptr; +} + +static sah_Desc *my_alloc_desc(void *ref) +{ + register sah_Desc *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Descriptor(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Descriptor(); + } + } +#endif + ref = 0; + return ptr; +} + +static sah_Link *my_alloc_link(void *ref) +{ + register sah_Link *ptr; + +#ifndef DIAG_MEM_ERRORS + ptr = sah_Alloc_Link(); + +#else + { + uint32_t rand; + /* are we feeling lucky ? */ + os_get_random_bytes(&rand, sizeof(rand)); + if ((rand % DIAG_MEM_CONST) == 0) { + ptr = 0; + } else { + ptr = sah_Alloc_Link(); + } + } +#endif + ref = 0; + return ptr; +} + +static void my_free(void *ref, void *ptr) +{ + ref = 0; /* unused param warning */ +#ifdef DIAG_MEM + sprintf(Diag_msg, "API kfree: %p\n", ptr); + LOG_KDIAG(Diag_msg); +#endif + os_free_memory(ptr); +} + +static void my_free_head_desc(void *ref, sah_Head_Desc * ptr) +{ + sah_Free_Head_Descriptor(ptr); +} + +static void my_free_desc(void *ref, sah_Desc * ptr) +{ + sah_Free_Descriptor(ptr); +} + +static void my_free_link(void *ref, sah_Link * ptr) +{ + sah_Free_Link(ptr); +} + +static void *my_memcpy(void *ref, void *dest, const void *src, size_t n) +{ + ref = 0; /* unused param warning */ + return memcpy(dest, src, n); +} + +static void *my_memset(void *ref, void *ptr, int ch, size_t n) +{ + ref = 0; /* unused param warning */ + return memset(ptr, ch, n); +} + +/*! Standard memory manipulation routines for kernel API. */ +static sah_Mem_Util std_kernelmode_mem_util = { + .mu_ref = 0, + .mu_malloc = my_malloc, + .mu_alloc_head_desc = my_alloc_head_desc, + .mu_alloc_desc = my_alloc_desc, + .mu_alloc_link = my_alloc_link, + .mu_free = my_free, + .mu_free_head_desc = my_free_head_desc, + .mu_free_desc = my_free_desc, + .mu_free_link = my_free_link, + .mu_memcpy = my_memcpy, + .mu_memset = my_memset +}; + +fsl_shw_return_t get_capabilities(fsl_shw_uco_t * user_ctx, + fsl_shw_pco_t * capabilities) +{ + scc_config_t *scc_capabilities; + + /* Fill in the Sahara2 capabilities. */ + memcpy(capabilities, &sahara2_capabilities, sizeof(fsl_shw_pco_t)); + + /* Fill in the SCC portion of the capabilities object */ + scc_capabilities = scc_get_configuration(); + capabilities->scc_driver_major = scc_capabilities->driver_major_version; + capabilities->scc_driver_minor = scc_capabilities->driver_minor_version; + capabilities->scm_version = scc_capabilities->scm_version; + capabilities->smn_version = scc_capabilities->smn_version; + capabilities->block_size_bytes = scc_capabilities->block_size_bytes; + +#ifdef FSL_HAVE_SCC + capabilities->scc_info.black_ram_size_blocks = + scc_capabilities->black_ram_size_blocks; + capabilities->scc_info.red_ram_size_blocks = + scc_capabilities->red_ram_size_blocks; +#elif defined(FSL_HAVE_SCC2) + capabilities->scc2_info.partition_size_bytes = + scc_capabilities->partition_size_bytes; + capabilities->scc2_info.partition_count = + scc_capabilities->partition_count; +#endif + + return FSL_RETURN_OK_S; +} + +/*! + * Sends a request to register this user + * + * @brief Sends a request to register this user + * + * @param[in,out] user_ctx part of the structure contains input parameters and + * part is filled in by the driver + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_register(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t status; + + /* this field is used in user mode to indicate a file open has occured. + * it is used here, in kernel mode, to indicate that the uco is registered + */ + user_ctx->sahara_openfd = 0; /* set to 'registered' */ + user_ctx->mem_util = &std_kernelmode_mem_util; + + /* check that uco is valid */ + status = sah_validate_uco(user_ctx); + + /* If life is good, register this user */ + if (status == FSL_RETURN_OK_S) { + status = sah_handle_registration(user_ctx); + } + + if (status != FSL_RETURN_OK_S) { + user_ctx->sahara_openfd = -1; /* set to 'not registered' */ + } + + return status; +} + +/*! + * Sends a request to deregister this user + * + * @brief Sends a request to deregister this user + * + * @param[in,out] user_ctx Info on user being deregistered. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_deregister(fsl_shw_uco_t * user_ctx) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + + if (user_ctx->sahara_openfd == 0) { + status = sah_handle_deregistration(user_ctx); + user_ctx->sahara_openfd = -1; /* set to 'no registered */ + } + + return status; +} + +/*! + * Sends a request to get results for this user + * + * @brief Sends a request to get results for this user + * + * @param[in,out] arg Pointer to structure to collect results + * @param uco User's context + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_get_results(sah_results * arg, fsl_shw_uco_t * uco) +{ + fsl_shw_return_t code = sah_get_results_from_pool(uco, arg); + + if ((code == FSL_RETURN_OK_S) && (arg->actual != 0)) { + sah_Postprocess_Results(uco, arg); + } + + return code; +} + +/*! + * This function writes the Descriptor Chain to the kernel driver. + * + * @brief Writes the Descriptor Chain to the kernel driver. + * + * @param dar A pointer to a Descriptor Chain of type sah_Head_Desc + * @param uco The user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t adaptor_Exec_Descriptor_Chain(sah_Head_Desc * dar, + fsl_shw_uco_t * uco) +{ + sah_Head_Desc *kernel_space_desc = NULL; + fsl_shw_return_t code = FSL_RETURN_OK_S; + int os_error_code = 0; + unsigned blocking_mode = dar->uco_flags & FSL_UCO_BLOCKING_MODE; + +#ifdef DIAG_ADAPTOR + km_Dump_Chain(&dar->desc); +#endif + + dar->user_info = uco; + dar->user_desc = dar; + + /* This code has been shamelessly copied from sah_driver_interface.c */ + /* It needs to be moved somewhere common ... */ + kernel_space_desc = sah_Physicalise_Descriptors(dar); + + if (kernel_space_desc == NULL) { + /* We may have failed due to a -EFAULT as well, but we will return + * -ENOMEM since either way it is a memory related failure. */ + code = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Physicalise_Descriptors() failed\n"); +#endif + } else { + if (blocking_mode) { +#ifdef SAHARA_POLL_MODE + os_error_code = sah_Handle_Poll(dar); +#else + os_error_code = sah_blocking_mode(dar); +#endif + if (os_error_code != 0) { + code = FSL_RETURN_ERROR_S; + } else { /* status of actual operation */ + code = dar->result; + } + } else { +#ifdef SAHARA_POLL_MODE + sah_Handle_Poll(dar); +#else + /* just put someting in the DAR */ + sah_Queue_Manager_Append_Entry(dar); +#endif /* SAHARA_POLL_MODE */ + } + } + + return code; +} + + +/* System keystore context, defined in sah_driver_interface.c */ +extern fsl_shw_kso_t system_keystore; + +fsl_shw_return_t do_system_keystore_slot_alloc(fsl_shw_uco_t * user_ctx, + uint32_t key_length, + uint64_t ownerid, + uint32_t * slot) +{ + (void)user_ctx; + return keystore_slot_alloc(&system_keystore, key_length, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_dealloc(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot) +{ + (void)user_ctx; + return keystore_slot_dealloc(&system_keystore, ownerid, slot); +} + +fsl_shw_return_t do_system_keystore_slot_load(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + const uint8_t * key, + uint32_t key_length) +{ + (void)user_ctx; + return keystore_slot_load(&system_keystore, ownerid, slot, + (void *)key, key_length); +} + +fsl_shw_return_t do_system_keystore_slot_read(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * key) +{ + (void)user_ctx; + return keystore_slot_read(&system_keystore, ownerid, slot, + key_length, (void *)key); +} + +fsl_shw_return_t do_system_keystore_slot_encrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_encrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +fsl_shw_return_t do_system_keystore_slot_decrypt(fsl_shw_uco_t * user_ctx, + uint64_t ownerid, + uint32_t slot, + uint32_t key_length, + const uint8_t * black_data) +{ + (void)user_ctx; + return keystore_slot_decrypt(NULL, &system_keystore, ownerid, + slot, key_length, black_data); +} + +void *fsl_shw_smalloc(fsl_shw_uco_t * user_ctx, + uint32_t size, const uint8_t * UMID, uint32_t permissions) +{ +#ifdef FSL_HAVE_SCC2 + int part_no; + void *part_base; + uint32_t part_phys; + scc_config_t *scc_configuration; + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (size != scc_configuration->partition_size_bytes) { + return NULL; + } + + /* Attempt to grab a partition. */ + if (scc_allocate_partition(0, &part_no, &part_base, &part_phys) + != SCC_RET_OK) { + return NULL; + } + printk(KERN_ALERT "In fsh_shw_smalloc (km): partition_base:%p " + "partition_base_phys: %p\n", part_base, (void *)part_phys); + + /* these bits should be in a separate function */ + printk(KERN_ALERT "writing UMID and MAP to secure the partition\n"); + + scc_engage_partition(part_base, UMID, permissions); + + (void)user_ctx; /* unused param warning */ + + return part_base; +#else /* FSL_HAVE_SCC2 */ + (void)user_ctx; + (void)size; + (void)UMID; + (void)permissions; + return NULL; +#endif /* FSL_HAVE_SCC2 */ + +} + +fsl_shw_return_t fsl_shw_sfree(fsl_shw_uco_t * user_ctx, void *address) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + if (scc_release_partition(address) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + + return FSL_RETURN_ERROR_S; +} + +fsl_shw_return_t fsl_shw_sstatus(fsl_shw_uco_t * user_ctx, + void *address, + fsl_shw_partition_status_t * status) +{ + (void)user_ctx; + +#ifdef FSL_HAVE_SCC2 + *status = scc_partition_status(address); + return FSL_RETURN_OK_S; +#endif + + return FSL_RETURN_ERROR_S; +} + +/* Diminish permissions on some secure memory */ +fsl_shw_return_t fsl_shw_diminish_perms(fsl_shw_uco_t * user_ctx, + void *address, uint32_t permissions) +{ + + (void)user_ctx; /* unused parameter warning */ + +#ifdef FSL_HAVE_SCC2 + if (scc_diminish_permissions(address, permissions) == SCC_RET_OK) { + return FSL_RETURN_OK_S; + } +#endif + return FSL_RETURN_ERROR_S; +} + +/* + * partition_base - physical address of the partition + * offset - offset, in blocks, of the data from the start of the partition + * length - length, in bytes, of the data to be encrypted (multiple of 4) + * black_data - virtual address that the encrypted data should be stored at + * Note that this virtual address must be translatable using the __virt_to_phys + * macro; ie, it can't be a specially mapped address. To do encryption with those + * addresses, use the scc_encrypt_region function directly. This is to make + * this function compatible with the user mode declaration, which does not know + * the physical addresses of the data it is using. + */ +fsl_shw_return_t +do_scc_encrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + scc_return_t scc_ret; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef FSL_HAVE_SCC2 + +#ifdef DIAG_ADAPTOR + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); +#endif + (void)user_ctx; + + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_encrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + + /* The SCC2 DMA engine should have written to the black ram, so we need to + * invalidate that region of memory. Note that the red ram is not an + * because it is mapped with the cache disabled. + */ + os_cache_inv_range(black_data, byte_count); + +#else + (void)scc_ret; +#endif /* FSL_HAVE_SCC2 */ + + return retval; +} + +/*! + * Call the proper function to decrypt a region of encrypted secure memory + * + * @brief + * + * @param user_ctx User context of the partition owner (NULL in kernel) + * @param partition_base Base address (physical) of the partition + * @param offset_bytes Offset from base address that the decrypted data + * shall be placed + * @param byte_count Length of the message (bytes) + * @param black_data Pointer to where the encrypted data is stored + * @param owner_id + * + * @return status + */ + +fsl_shw_return_t +do_scc_decrypt_region(fsl_shw_uco_t * user_ctx, + void *partition_base, uint32_t offset_bytes, + uint32_t byte_count, const uint8_t * black_data, + uint32_t * IV, fsl_shw_cypher_mode_t cypher_mode) +{ + scc_return_t scc_ret; + fsl_shw_return_t retval = FSL_RETURN_ERROR_S; + +#ifdef FSL_HAVE_SCC2 + +#ifdef DIAG_ADAPTOR + uint32_t *owner_32 = (uint32_t *) & (owner_id); + + LOG_KDIAG_ARGS + ("partition base: %p, offset: %i, count: %i, black data: %p\n", + partition_base, offset_bytes, byte_count, (void *)black_data); +#endif + + (void)user_ctx; + + /* The SCC2 DMA engine will be reading from the black ram, so we need to + * make sure that the data is pushed out of the cache. Note that the red + * ram is not an issue because it is mapped with the cache disabled. + */ + os_cache_flush_range(black_data, byte_count); + + scc_ret = + scc_decrypt_region((uint32_t) partition_base, offset_bytes, + byte_count, + (uint8_t *) __virt_to_phys(black_data), IV, + cypher_mode); + + if (scc_ret == SCC_RET_OK) { + retval = FSL_RETURN_OK_S; + } else { + retval = FSL_RETURN_ERROR_S; + } + +#else + (void)scc_ret; +#endif /* FSL_HAVE_SCC2 */ + + return retval; +} + +#ifdef DIAG_ADAPTOR +/*! + * Dump chain of descriptors to the log. + * + * @brief Dump descriptor chain + * + * @param chain Kernel virtual address of start of chain of descriptors + * + * @return void + */ +void km_Dump_Chain(const sah_Desc * chain) +{ + while (chain != NULL) { + km_Dump_Words("Desc", (unsigned *)chain, + 6 /*sizeof(*chain)/sizeof(unsigned) */ ); + /* place this definition elsewhere */ + if (chain->ptr1) { + if (chain->header & SAH_HDR_LLO) { + km_Dump_Region(" Data1", chain->ptr1, + chain->len1); + } else { + km_Dump_Link(" Link1", chain->ptr1); + } + } + if (chain->ptr2) { + if (chain->header & SAH_HDR_LLO) { + km_Dump_Region(" Data2", chain->ptr2, + chain->len2); + } else { + km_Dump_Link(" Link2", chain->ptr2); + } + } + + chain = chain->next; + } +} + +/*! + * Dump chain of links to the log. + * + * @brief Dump chain of links + * + * @param prefix Text to put in front of dumped data + * @param link Kernel virtual address of start of chain of links + * + * @return void + */ +static void km_Dump_Link(const char *prefix, const sah_Link * link) +{ + while (link != NULL) { + km_Dump_Words(prefix, (unsigned *)link, + 3 /* # words in h/w link */ ); + if (link->flags & SAH_STORED_KEY_INFO) { +#ifdef CAN_DUMP_SCC_DATA + uint32_t len; +#endif + +#ifdef CAN_DUMP_SCC_DATA + { + char buf[50]; + + scc_get_slot_info(link->ownerid, link->slot, (uint32_t *) & link->data, /* RED key address */ + &len); /* key length */ + sprintf(buf, " SCC slot %d: ", link->slot); + km_Dump_Words(buf, + (void *)IO_ADDRESS((uint32_t) + link->data), + link->len / 4); + } +#else + sprintf(Diag_msg, " SCC slot %d", link->slot); + LOG_KDIAG(Diag_msg); +#endif + } else if (link->data != NULL) { + km_Dump_Region(" Data", link->data, link->len); + } + + link = link->next; + } +} + +/*! + * Dump given region of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param length Amount of data to dump + * + * @return void +*/ +void km_Dump_Region(const char *prefix, const unsigned char *data, + unsigned length) +{ + unsigned count; + char *output; + unsigned data_len; + + sprintf(Diag_msg, "%s (%08X,%u):", prefix, (uint32_t) data, length); + + /* Restrict amount of data to dump */ + if (length > MAX_DUMP) { + data_len = MAX_DUMP; + } else { + data_len = length; + } + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + for (count = 0; count < data_len; count++) { + if (count % 4 == 0) { + *output++ = ' '; + } + sprintf(output, "%02X", *data++); + output += 2; + } + + LOG_KDIAG(Diag_msg); +} + +/*! + * Dump given wors of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param word_count Amount of data to dump + * + * @return void +*/ +void km_Dump_Words(const char *prefix, const unsigned *data, + unsigned word_count) +{ + char *output; + + sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, (uint32_t) data, + word_count); + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + while (word_count--) { + sprintf(output, "%08X ", *data++); + output += 9; + } + + LOG_KDIAG(Diag_msg); +} +#endif diff --git a/drivers/mxc/security/sahara2/sah_driver_interface.c b/drivers/mxc/security/sahara2/sah_driver_interface.c new file mode 100644 index 000000000000..7029bc5f9c00 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_driver_interface.c @@ -0,0 +1,2179 @@ +/* + * Copyright 2004-2009 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 sah_driver_interface.c +* +* @brief Provides a Linux Kernel Module interface to the SAHARA h/w device. +* +*/ + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_kernel.h> +#include <sah_memory_mapper.h> +#include <sah_queue_manager.h> +#include <sah_status_manager.h> +#include <sah_interrupt_handler.h> +#include <sah_hardware_interface.h> +#include <fsl_shw_keystore.h> +#include <adaptor.h> +#ifdef FSL_HAVE_SCC +#include <linux/mxc_scc_driver.h> +#else +#include <linux/mxc_scc2_driver.h> +#endif + +#ifdef DIAG_DRV_IF +#include <diagnostic.h> +#endif + +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) +#include <linux/devfs_fs_kernel.h> +#else +#include <linux/proc_fs.h> +#endif + +#include <mach/spba.h> + +#ifdef PERF_TEST +#define interruptible_sleep_on(x) sah_Handle_Interrupt() +#endif + +#define TEST_MODE_OFF 1 +#define TEST_MODE_ON 2 + +/*! Version register on first deployments */ +#define SAHARA_VERSION2 2 +/*! Version register on MX27 */ +#define SAHARA_VERSION3 3 +/*! Version register on MXC92323 */ +#define SAHARA_VERSION4 4 + +/****************************************************************************** +* Module function declarations +******************************************************************************/ + +OS_DEV_INIT_DCL(sah_init); +OS_DEV_SHUTDOWN_DCL(sah_cleanup); +OS_DEV_OPEN_DCL(sah_open); +OS_DEV_CLOSE_DCL(sah_release); +OS_DEV_IOCTL_DCL(sah_ioctl); +OS_DEV_MMAP_DCL(sah_mmap); + +static os_error_code sah_handle_get_capabilities(fsl_shw_uco_t* user_ctx, + uint32_t info); + +static void sah_user_callback(fsl_shw_uco_t * user_ctx); +static os_error_code sah_handle_scc_sfree(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_sstatus(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_drop_perms(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_encrypt(fsl_shw_uco_t* user_ctx, + uint32_t info); +static os_error_code sah_handle_scc_decrypt(fsl_shw_uco_t* user_ctx, + uint32_t info); + +#ifdef FSL_HAVE_SCC2 +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base); +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base); +#endif + +static os_error_code sah_handle_sk_slot_alloc(uint32_t info); +static os_error_code sah_handle_sk_slot_dealloc(uint32_t info); +static os_error_code sah_handle_sk_slot_load(uint32_t info); +static os_error_code sah_handle_sk_slot_read(uint32_t info); +static os_error_code sah_handle_sk_slot_decrypt(uint32_t info); +static os_error_code sah_handle_sk_slot_encrypt(uint32_t info); + +/*! Boolean flag for whether interrupt handler needs to be released on exit */ +static unsigned interrupt_registered; + +static int handle_sah_ioctl_dar(fsl_shw_uco_t * filp, uint32_t user_space_desc); + +#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int sah_read_procfs(char *buf, + char **start, + off_t offset, int count, int *eof, void *data); + +static int sah_write_procfs(struct file *file, const char __user * buffer, + unsigned long count, void *data); +#endif + +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + +/* This is a handle to the sahara DEVFS entry. */ +static devfs_handle_t Sahara_devfs_handle; + +#else + +/* Major number assigned to our device driver */ +static int Major; + +/* This is a handle to the sahara PROCFS entry */ +static struct proc_dir_entry *Sahara_procfs_handle; + +#endif + +uint32_t sah_hw_version; + +/* This is the wait queue to this driver. Linux declaration. */ +DECLARE_WAIT_QUEUE_HEAD(Wait_queue); + +/* This is a global variable that is used to track how many times the device + * has been opened simultaneously. */ +#ifdef DIAG_DRV_IF +static int Device_in_use = 0; +#endif + +/* This is the system keystore object */ +fsl_shw_kso_t system_keystore; + +/*! + * OS-dependent handle used for registering user interface of a driver. + */ +static os_driver_reg_t reg_handle; + +#ifdef DIAG_DRV_IF +/* This is for sprintf() to use when constructing output. */ +#define DIAG_MSG_SIZE 1024 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)) +/** Pointer to Sahara clock information. Initialized during os_dev_init(). */ +static struct clk *sah_clk; +#endif + +/*! +******************************************************************************* +* This function gets called when the module is inserted (insmod) into the +* running kernel. +* +* @brief SAHARA device initialisation function. +* +* @return 0 on success +* @return -EBUSY if the device or proc file entry cannot be created. +* @return OS_ERROR_NO_MEMORY_S if kernel memory could not be allocated. +* @return OS_ERROR_FAIL_S if initialisation of proc entry failed +*/ +OS_DEV_INIT(sah_init) +{ + /* Status variable */ + int os_error_code = 0; + + interrupt_registered = 0; + + /* Enable the SAHARA Clocks */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Enabling the IPG and AHB clocks\n") +#endif /*DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SAHARA2_CLK); +#else + { + sah_clk = clk_get(NULL, "sahara_clk"); + if (sah_clk != ERR_PTR(ENOENT)) + clk_enable(sah_clk); + } +#endif + + /* Check for SPBA need */ +#if defined(CONFIG_ARCH_MXC91231) || defined(CONFIG_ARCH_MXC91321) + /* This needs to be a PLATFORM abstraction */ + if (spba_take_ownership(SPBA_SAHARA, SPBA_MASTER_A)) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Sahara driver could not take ownership of Sahara\n"); +#endif + os_error_code = OS_ERROR_FAIL_S; + } +#endif /* SPBA */ + + if (os_error_code == OS_ERROR_OK_S) { + sah_hw_version = sah_HW_Read_Version(); + os_printk("Sahara HW Version is 0x%08x\n", sah_hw_version); + + /* verify code and hardware are version compatible */ + if ((sah_hw_version != SAHARA_VERSION2) + && (sah_hw_version != SAHARA_VERSION3)) { + if (((sah_hw_version >> 8) & 0xff) != SAHARA_VERSION4) { + os_printk + ("Sahara HW Version was not expected value.\n"); + os_error_code = OS_ERROR_FAIL_S; + } + } + } + + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_Init_Mem_Map to initialise " + "memory subsystem."); +#endif + /* Do any memory-routine initialization */ + os_error_code = sah_Init_Mem_Map(); + } + + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_HW_Reset() to Initialise the Hardware."); +#endif + /* Initialise the hardware */ + os_error_code = sah_HW_Reset(); + if (os_error_code != OS_ERROR_OK_S) { + os_printk + ("sah_HW_Reset() failed to Initialise the Hardware.\n"); + } + + } + + if (os_error_code == OS_ERROR_OK_S) { +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + /* Register the DEVFS entry */ + Sahara_devfs_handle = devfs_register(NULL, + SAHARA_DEVICE_SHORT, + DEVFS_FL_AUTO_DEVNUM, + 0, 0, + SAHARA_DEVICE_MODE, + &Fops, NULL); + if (Sahara_devfs_handle == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Registering the DEVFS character device failed."); +#endif /* DIAG_DRV_IF */ + os_error_code = -EBUSY; + } +#else /* CONFIG_DEVFS_FS */ + /* Create the PROCFS entry. This is used to report the assigned device + * major number back to user-space. */ +#if 1 + Sahara_procfs_handle = create_proc_entry(SAHARA_DEVICE_SHORT, 0700, /* default mode */ + NULL); /* parent dir */ + if (Sahara_procfs_handle == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Registering the PROCFS interface failed."); +#endif /* DIAG_DRV_IF */ + os_error_code = OS_ERROR_FAIL_S; + } else { + Sahara_procfs_handle->nlink = 1; + Sahara_procfs_handle->data = 0; + Sahara_procfs_handle->read_proc = sah_read_procfs; + Sahara_procfs_handle->write_proc = sah_write_procfs; + } +#endif /* #if 1 */ + } + + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Calling sah_Queue_Manager_Init() to Initialise the Queue " + "Manager."); +#endif + /* Initialise the Queue Manager */ + if (sah_Queue_Manager_Init() != FSL_RETURN_OK_S) { + os_error_code = -ENOMEM; + } + } +#ifndef SAHARA_POLL_MODE + if (os_error_code == OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Calling sah_Intr_Init() to Initialise the Interrupt " + "Handler."); +#endif + /* Initialise the Interrupt Handler */ + os_error_code = sah_Intr_Init(&Wait_queue); + if (os_error_code == OS_ERROR_OK_S) { + interrupt_registered = 1; + } + } +#endif /* ifndef SAHARA_POLL_MODE */ + +#ifdef SAHARA_POWER_MANAGEMENT + if (os_error_code == OS_ERROR_OK_S) { + /* set up dynamic power management (dmp) */ + os_error_code = sah_dpm_init(); + } +#endif + + if (os_error_code == OS_ERROR_OK_S) { + os_driver_init_registration(reg_handle); + os_driver_add_registration(reg_handle, OS_FN_OPEN, + OS_DEV_OPEN_REF(sah_open)); + os_driver_add_registration(reg_handle, OS_FN_IOCTL, + OS_DEV_IOCTL_REF(sah_ioctl)); + os_driver_add_registration(reg_handle, OS_FN_CLOSE, + OS_DEV_CLOSE_REF(sah_release)); + os_driver_add_registration(reg_handle, OS_FN_MMAP, + OS_DEV_MMAP_REF(sah_mmap)); + + os_error_code = + os_driver_complete_registration(reg_handle, Major, + "sahara"); + + if (os_error_code < OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Registering the regular " + "character device failed with error code: %d\n", + os_error_code); + LOG_KDIAG(Diag_msg); +#endif + } + } +#endif /* CONFIG_DEVFS_FS */ + + if (os_error_code == OS_ERROR_OK_S) { + /* set up the system keystore, using the default keystore handler */ + fsl_shw_init_keystore_default(&system_keystore); + + if (fsl_shw_establish_keystore(NULL, &system_keystore) + == FSL_RETURN_OK_S) { + os_error_code = OS_ERROR_OK_S; + } else { + os_error_code = OS_ERROR_FAIL_S; + } + + if (os_error_code != OS_ERROR_OK_S) { +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Registering the system keystore " + "failed with error code: %d\n", os_error_code); + LOG_KDIAG(Diag_msg); +#endif + } + } + + if (os_error_code != OS_ERROR_OK_S) { +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + cleanup_module(); +#else + sah_cleanup(); +#endif + } +#ifdef DIAG_DRV_IF + else { + LOG_KDIAG_ARGS("Sahara major node is %d\n", Major); + } +#endif + +/* Disabling the Clock after the driver has been registered fine. + This is done to save power when Sahara is not in use.*/ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Disabling the clocks\n") +#endif /* DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SAHARA2_CLK); +#else + { + if (sah_clk != ERR_PTR(ENOENT)) + clk_disable(sah_clk); + } +#endif + + os_dev_init_return(os_error_code); +} + +/*! +******************************************************************************* +* This function gets called when the module is removed (rmmod) from the running +* kernel. +* +* @brief SAHARA device clean-up function. +* +* @return void +*/ +OS_DEV_SHUTDOWN(sah_cleanup) +{ + int ret_val = 0; + + printk(KERN_ALERT "Sahara going into cleanup\n"); + + /* clear out the system keystore */ + fsl_shw_release_keystore(NULL, &system_keystore); + + /* Unregister the device */ +#if defined(CONFIG_DEVFS_FS) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)) + devfs_unregister(Sahara_devfs_handle); +#else + + if (Sahara_procfs_handle != NULL) { + remove_proc_entry(SAHARA_DEVICE_SHORT, NULL); + } + + if (Major >= 0) { + ret_val = os_driver_remove_registration(reg_handle); + } +#ifdef DIAG_DRV_IF + if (ret_val < 0) { + snprintf(Diag_msg, DIAG_MSG_SIZE, "Error while attempting to " + "unregister the device: %d\n", ret_val); + LOG_KDIAG(Diag_msg); + } +#endif + +#endif /* CONFIG_DEVFS_FS */ + sah_Queue_Manager_Close(); + +#ifndef SAHARA_POLL_MODE + if (interrupt_registered) { + sah_Intr_Release(); + interrupt_registered = 0; + } +#endif + sah_Stop_Mem_Map(); +#ifdef SAHARA_POWER_MANAGEMENT + sah_dpm_close(); +#endif + +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Disabling the clocks\n") +#endif /* DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_disable(SAHARA2_CLK); +#else + { + if (sah_clk != ERR_PTR(ENOENT)) + clk_disable(sah_clk); + clk_put(sah_clk); + } +#endif + + os_dev_shutdown_return(OS_ERROR_OK_S); +} + +/*! +******************************************************************************* +* This function simply increments the module usage count. +* +* @brief SAHARA device open function. +* +* @param inode Part of the kernel prototype. +* @param file Part of the kernel prototype. +* +* @return 0 - Always returns 0 since any number of calls to this function are +* allowed. +* +*/ +OS_DEV_OPEN(sah_open) +{ + +#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)) + MOD_INC_USE_COUNT; +#endif + +#ifdef DIAG_DRV_IF + Device_in_use++; + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Incrementing module use count to: %d ", Device_in_use); + LOG_KDIAG(Diag_msg); +#endif + + os_dev_set_user_private(NULL); + + /* Return 0 to indicate success */ + os_dev_open_return(0); +} + +/*! +******************************************************************************* +* This function simply decrements the module usage count. +* +* @brief SAHARA device release function. +* +* @param inode Part of the kernel prototype. +* @param file Part of the kernel prototype. +* +* @return 0 - Always returns 0 since this function does not fail. +*/ +OS_DEV_CLOSE(sah_release) +{ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + +#if defined(LINUX_VERSION) && (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)) + MOD_DEC_USE_COUNT; +#endif + +#ifdef DIAG_DRV_IF + Device_in_use--; + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Decrementing module use count to: %d ", Device_in_use); + LOG_KDIAG(Diag_msg); +#endif + + if (user_ctx != NULL) { + sah_handle_deregistration(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + } + + /* Return 0 to indicate success */ + os_dev_close_return(OS_ERROR_OK_S); +} + +/*! +******************************************************************************* +* This function provides the IO Controls for the SAHARA driver. Three IO +* Controls are supported: +* +* SAHARA_HWRESET and +* SAHARA_SET_HA +* SAHARA_CHK_TEST_MODE +* +* @brief SAHARA device IO Control function. +* +* @param inode Part of the kernel prototype. +* @param filp Part of the kernel prototype. +* @param cmd Part of the kernel prototype. +* @param arg Part of the kernel prototype. +* +* @return 0 on success +* @return -EBUSY if the HA bit could not be set due to busy hardware. +* @return -ENOTTY if an unsupported IOCTL was attempted on the device. +* @return -EFAULT if put_user() fails +*/ +OS_DEV_IOCTL(sah_ioctl) +{ + int status = 0; + int test_mode; + + switch (os_dev_get_ioctl_op()) { + case SAHARA_HWRESET: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_HWRESET IOCTL."); +#endif + /* We need to reset the hardware. */ + sah_HW_Reset(); + + /* Mark all the entries in the Queue Manager's queue with state + * SAH_STATE_RESET. + */ + sah_Queue_Manager_Reset_Entries(); + + /* Wake up all sleeping write() calls. */ + wake_up_interruptible(&Wait_queue); + break; +#ifdef SAHARA_HA_ENABLED + case SAHARA_SET_HA: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SET_HA IOCTL."); +#endif /* DIAG_DRV_IF */ + if (sah_HW_Set_HA() == ERR_INTERNAL) { + status = -EBUSY; + } + break; +#endif /* SAHARA_HA_ENABLED */ + + case SAHARA_CHK_TEST_MODE: + /* load test_mode */ + test_mode = TEST_MODE_OFF; +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_CHECK_TEST_MODE IOCTL."); + test_mode = TEST_MODE_ON; +#endif /* DIAG_DRV_IF */ +#if defined(KERNEL_TEST) || defined(PERF_TEST) + test_mode = TEST_MODE_ON; +#endif /* KERNEL_TEST || PERF_TEST */ + /* copy test_mode back to user space. put_user() is Linux fn */ + /* compiler warning `register': no problem found so ignored */ + status = put_user(test_mode, (int *)os_dev_get_ioctl_arg()); + break; + + case SAHARA_DAR: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_DAR IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = + handle_sah_ioctl_dar(user_ctx, + os_dev_get_ioctl_arg + ()); + } else { + status = OS_ERROR_FAIL_S; + } + + } + break; + + case SAHARA_GET_RESULTS: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_GET_RESULTS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = + sah_get_results_pointers(user_ctx, + os_dev_get_ioctl_arg + ()); + } else { + status = OS_ERROR_FAIL_S; + } + } + break; + + case SAHARA_REGISTER: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_REGISTER IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx != NULL) { + status = OS_ERROR_FAIL_S; /* already registered */ + } else { + user_ctx = + os_alloc_memory(sizeof(fsl_shw_uco_t), + GFP_KERNEL); + if (user_ctx == NULL) { + status = OS_ERROR_NO_MEMORY_S; + } else { + /* Copy UCO from user, but only as big as the common UCO */ + if (os_copy_from_user(user_ctx, + (void *) + os_dev_get_ioctl_arg + (), + offsetof + (fsl_shw_uco_t, + result_pool))) { + status = OS_ERROR_FAIL_S; + } else { + os_dev_set_user_private + (user_ctx); + status = + sah_handle_registration + (user_ctx); + } + } + } + } + break; + + /* This ioctl cmd should disappear in favor of a close() routine. */ + case SAHARA_DEREGISTER: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_DEREGISTER IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + if (user_ctx == NULL) { + status = OS_ERROR_FAIL_S; + } else { + status = sah_handle_deregistration(user_ctx); + os_free_memory(user_ctx); + os_dev_set_user_private(NULL); + } + } + break; + case SAHARA_SCC_DROP_PERMS: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_DROP_PERMS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + /* drop permissions on the specified partition */ + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_drop_perms(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_SFREE: + /* Unmap the specified partition from the users space, and then + * free it for use by someone else. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_SFREE IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_sfree(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_SSTATUS: + /* Unmap the specified partition from the users space, and then + * free it for use by someone else. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_SSTATUS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_sstatus(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_ENCRYPT: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_ENCRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_encrypt(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SCC_DECRYPT: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SCC_DECRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_scc_decrypt(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + case SAHARA_SK_ALLOC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_ALLOC IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_alloc(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_DEALLOC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_DEALLOC IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_dealloc(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_LOAD: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_LOAD IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_load(os_dev_get_ioctl_arg()); + break; + case SAHARA_SK_READ: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_READ IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_read(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_SLOT_DEC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_SLOT_DECRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_decrypt(os_dev_get_ioctl_arg()); + break; + + case SAHARA_SK_SLOT_ENC: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_SK_SLOT_ENCRYPT IOCTL."); +#endif /* DIAG_DRV_IF */ + status = sah_handle_sk_slot_encrypt(os_dev_get_ioctl_arg()); + break; + case SAHARA_GET_CAPS: +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA_GET_CAPS IOCTL."); +#endif /* DIAG_DRV_IF */ + { + fsl_shw_uco_t *user_ctx = os_dev_get_user_private(); + + status = + sah_handle_get_capabilities(user_ctx, + os_dev_get_ioctl_arg()); + } + break; + + default: +#ifdef DIAG_DRV_IF + LOG_KDIAG("Unknown SAHARA IOCTL."); +#endif /* DIAG_DRV_IF */ + status = OS_ERROR_FAIL_S; + } + + os_dev_ioctl_return(status); +} + +/* Fill in the user's capabilities structure */ +static os_error_code sah_handle_get_capabilities(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_FAIL_S; + fsl_shw_pco_t capabilities; + + status = os_copy_from_user(&capabilities, (void *)info, + sizeof(fsl_shw_pco_t)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + if (get_capabilities(user_ctx, &capabilities) == FSL_RETURN_OK_S) { + status = os_copy_to_user((void *)info, &capabilities, + sizeof(fsl_shw_pco_t)); + } + + out: + return status; +} + +#ifdef FSL_HAVE_SCC2 + +/* Find the kernel-mode address of the partition. + * This can then be passed to the SCC functions. + */ +void *lookup_user_partition(fsl_shw_uco_t * user_ctx, uint32_t user_base) +{ + /* search through the partition chain to find one that matches the user base + * address. + */ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + return curr->kernel_base; + } + curr = (fsl_shw_spo_t *) curr->next; + } + return NULL; +} + +/* user_base: userspace base address of the partition + * kernel_base: kernel mode base address of the partition + */ +static fsl_shw_return_t register_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base, + void *kernel_base) +{ + fsl_shw_spo_t *partition_info; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + if (user_ctx == NULL) { + goto out; + } + + partition_info = os_alloc_memory(sizeof(fsl_shw_spo_t), GFP_KERNEL); + + if (partition_info == NULL) { + goto out; + } + + /* stuff the partition info, then put it at the front of the chain */ + partition_info->user_base = user_base; + partition_info->kernel_base = kernel_base; + partition_info->next = user_ctx->partition; + + user_ctx->partition = (struct fsl_shw_spo_t *)partition_info; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("partition with user_base=%p, kernel_base=%p registered.", + (void *)user_base, kernel_base); +#endif + + ret = FSL_RETURN_OK_S; + + out: + + return ret; +} + +/* if the partition is in the users list, remove it */ +static fsl_shw_return_t deregister_user_partition(fsl_shw_uco_t * user_ctx, + uint32_t user_base) +{ + fsl_shw_spo_t *curr = (fsl_shw_spo_t *) user_ctx->partition; + fsl_shw_spo_t *last = (fsl_shw_spo_t *) user_ctx->partition; + + while (curr != NULL) { + if (curr->user_base == user_base) { + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("deregister_user_partition: partition with " + "user_base=%p, kernel_base=%p deregistered.\n", + (void *)curr->user_base, curr->kernel_base); +#endif + + if (last == curr) { + user_ctx->partition = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } else { + last->next = curr->next; + os_free_memory(curr); + return FSL_RETURN_OK_S; + } + } + last = curr; + curr = (fsl_shw_spo_t *) curr->next; + } + + return FSL_RETURN_ERROR_S; +} + +#endif /* FSL_HAVE_SCC2 */ + +static os_error_code sah_handle_scc_drop_perms(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + scc_return_t scc_ret; + scc_partition_info_t partition_info; + void *kernel_base; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("_scc_drop_perms(): failed to find partition\n"); +#endif + goto out; + } + + /* call scc driver to perform the drop */ + scc_ret = scc_diminish_permissions(kernel_base, + partition_info.permissions); + if (scc_ret == SCC_RET_OK) { + status = OS_ERROR_OK_S; + } else { + status = OS_ERROR_FAIL_S; + } + + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code sah_handle_scc_sfree(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + { + scc_partition_info_t partition_info; + void *kernel_base; + int ret; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + /* check that the copy was successful */ + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = + lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find partition\n"); +#endif /*DIAG_DRV_IF */ + goto out; + } + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + ret = unmap_user_memory(partition_info.user_base, 8192); + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + + /* release the partition */ + scc_release_partition(kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition_info.user_base); + + status = OS_ERROR_OK_S; + } + } + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code sah_handle_scc_sstatus(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; +#ifdef FSL_HAVE_SCC2 + { + scc_partition_info_t partition_info; + void *kernel_base; + + status = + os_copy_from_user(&partition_info, (void *)info, + sizeof(partition_info)); + + /* check that the copy was successful */ + if (status != OS_ERROR_OK_S) { + goto out; + } + + /* validate that the user owns this partition, and look up its handle */ + kernel_base = + lookup_user_partition(user_ctx, partition_info.user_base); + + if (kernel_base == NULL) { + status = OS_ERROR_FAIL_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find partition\n"); +#endif /*DIAG_DRV_IF */ + goto out; + } + + partition_info.status = scc_partition_status(kernel_base); + + status = + os_copy_to_user((void *)info, &partition_info, + sizeof(partition_info)); + } + out: +#endif /* FSL_HAVE_SCC2 */ + return status; +} + +static os_error_code sah_handle_scc_encrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code os_err = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr = NULL; + void *partition_base = NULL; + scc_config_t *scc_configuration; + + os_err = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + + if (os_err != OS_ERROR_OK_S) { + goto out; + } +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("partition_base: %p, offset: %i, length: %i, black data: %p", + (void *)region_info.partition_base, region_info.offset, + region_info.length, (void *)region_info.black_data); +#endif + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + retval = FSL_RETURN_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find secure partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + /* wire down black data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + retval = + do_scc_encrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + /* release black data */ + unwire_user_memory(&page_ctx); + + out: + if (os_err == OS_ERROR_OK_S) { + /* Return error code */ + region_info.code = retval; + os_err = + os_copy_to_user((void *)info, ®ion_info, + sizeof(region_info)); + } + } + +#endif + return os_err; +} + +static os_error_code sah_handle_scc_decrypt(fsl_shw_uco_t * user_ctx, + uint32_t info) +{ + os_error_code os_err = OS_ERROR_FAIL_S; +#ifdef FSL_HAVE_SCC2 + { + fsl_shw_return_t retval; + scc_region_t region_info; + void *page_ctx = NULL; + void *black_addr; + void *partition_base; + scc_config_t *scc_configuration; + + os_err = + os_copy_from_user(®ion_info, (void *)info, + sizeof(region_info)); + + if (os_err != OS_ERROR_OK_S) { + goto out; + } +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("partition_base: %p, offset: %i, length: %i, black data: %p", + (void *)region_info.partition_base, region_info.offset, + region_info.length, (void *)region_info.black_data); +#endif + + /* validate that the user owns this partition, and look up its handle */ + partition_base = lookup_user_partition(user_ctx, + region_info. + partition_base); + + if (partition_base == NULL) { + retval = FSL_RETURN_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("failed to find partition\n"); +#endif + goto out; + } + + /* Check that the memory size requested is correct */ + scc_configuration = scc_get_configuration(); + if (region_info.offset + region_info.length > + scc_configuration->partition_size_bytes) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + /* wire down black data */ + black_addr = wire_user_memory(region_info.black_data, + region_info.length, &page_ctx); + + if (black_addr == NULL) { + retval = FSL_RETURN_ERROR_S; + goto out; + } + + retval = + do_scc_decrypt_region(NULL, partition_base, + region_info.offset, + region_info.length, black_addr, + region_info.IV, + region_info.cypher_mode); + + /* release black data */ + unwire_user_memory(&page_ctx); + + out: + if (os_err == OS_ERROR_OK_S) { + /* Return error code */ + region_info.code = retval; + os_err = + os_copy_to_user((void *)info, ®ion_info, + sizeof(region_info)); + } + } + +#endif /* FSL_HAVE_SCC2 */ + return os_err; +} + +/*****************************************************************************/ +/* fn get_user_smid() */ +/*****************************************************************************/ +uint32_t get_user_smid(void *proc) +{ + /* + * A real implementation would have some way to handle signed applications + * which wouild be assigned distinct SMIDs. For the reference + * implementation, we show where this would be determined (here), but + * always provide a fixed answer, thus not separating users at all. + */ + + return 0x42eaae42; +} + +/*! +******************************************************************************* +* This function implements the smalloc() function for userspace programs, by +* making a call to the SCC2 mmap() function that acquires a region of secure +* memory on behalf of the user, and then maps it into the users memory space. +* Currently, the only memory size supported is that of a single SCC2 partition. +* Requests for other sized memory regions will fail. +*/ +OS_DEV_MMAP(sah_mmap) +{ + os_error_code status = OS_ERROR_NO_MEMORY_S; + +#ifdef FSL_HAVE_SCC2 + { + scc_return_t scc_ret; + fsl_shw_return_t fsl_ret; + uint32_t partition_registered = FALSE; + + uint32_t user_base; + void *partition_base; + uint32_t smid; + scc_config_t *scc_configuration; + + int part_no = -1; + uint32_t part_phys; + + fsl_shw_uco_t *user_ctx = + (fsl_shw_uco_t *) os_dev_get_user_private(); + + /* Make sure that the user context is valid */ + if (user_ctx == NULL) { + user_ctx = + os_alloc_memory(sizeof(*user_ctx), GFP_KERNEL); + + if (user_ctx == NULL) { + status = OS_ERROR_NO_MEMORY_S; + goto out; + } + + sah_handle_registration(user_ctx); + os_dev_set_user_private(user_ctx); + } + + /* Determine the size of a secure partition */ + scc_configuration = scc_get_configuration(); + + /* Check that the memory size requested is equal to the partition + * size, and that the requested destination is on a page boundary. + */ + if (((os_mmap_user_base() % PAGE_SIZE) != 0) || + (os_mmap_memory_size() != + scc_configuration->partition_size_bytes)) { + status = OS_ERROR_BAD_ARG_S; + goto out; + } + + /* Retrieve the SMID associated with the user */ + smid = get_user_smid(user_ctx->process); + + /* Attempt to allocate a secure partition */ + scc_ret = + scc_allocate_partition(smid, &part_no, &partition_base, + &part_phys); + if (scc_ret != SCC_RET_OK) { + pr_debug + ("SCC mmap() request failed to allocate partition;" + " error %d\n", status); + status = OS_ERROR_FAIL_S; + goto out; + } + + pr_debug("scc_mmap() acquired partition %d at %08x\n", + part_no, part_phys); + + /* Record partition info in the user context */ + user_base = os_mmap_user_base(); + fsl_ret = + register_user_partition(user_ctx, user_base, + partition_base); + + if (fsl_ret != FSL_RETURN_OK_S) { + pr_debug + ("SCC mmap() request failed to register partition with user" + " context, error: %d\n", fsl_ret); + status = OS_ERROR_FAIL_S; + } + + partition_registered = TRUE; + + status = map_user_memory(os_mmap_memory_ctx(), part_phys, + os_mmap_memory_size()); + +#ifdef SHW_DEBUG + if (status == OS_ERROR_OK_S) { + LOG_KDIAG_ARGS + ("Partition allocated: user_base=%p, partition_base=%p.", + (void *)user_base, partition_base); + } +#endif + + out: + /* If there is an error it has to be handled here */ + if (status != OS_ERROR_OK_S) { + /* if the partition was registered with the user, unregister it. */ + if (partition_registered == TRUE) { + deregister_user_partition(user_ctx, user_base); + } + + /* if the partition was allocated, deallocate it */ + if (partition_base != NULL) { + scc_release_partition(partition_base); + } + } + } +#endif /* FSL_HAVE_SCC2 */ + + return status; +} + +/* Find the physical address of a key stored in the system keystore */ +fsl_shw_return_t +system_keystore_get_slot_info(uint64_t owner_id, uint32_t slot, + uint32_t * address, uint32_t * slot_size_bytes) +{ + fsl_shw_return_t retval; + void *kernel_address; + + /* First verify that the key access is valid */ + retval = system_keystore.slot_verify_access(system_keystore.user_data, + owner_id, slot); + + if (retval != FSL_RETURN_OK_S) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("verification failed"); +#endif + return retval; + } + + if (address != NULL) { +#ifdef FSL_HAVE_SCC2 + kernel_address = + system_keystore.slot_get_address(system_keystore.user_data, + slot); + (*address) = scc_virt_to_phys(kernel_address); +#else + kernel_address = + system_keystore.slot_get_address((void *)&owner_id, slot); + (*address) = (uint32_t) kernel_address; +#endif + } + + if (slot_size_bytes != NULL) { +#ifdef FSL_HAVE_SCC2 + *slot_size_bytes = + system_keystore.slot_get_slot_size(system_keystore. + user_data, slot); +#else + *slot_size_bytes = + system_keystore.slot_get_slot_size((void *)&owner_id, slot); +#endif + } + + return retval; +} + +static os_error_code sah_handle_sk_slot_alloc(uint32_t info) +{ + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + if (os_err == OS_ERROR_OK_S) { + scc_ret = keystore_slot_alloc(&system_keystore, + slot_info.key_length, + slot_info.ownerid, + &slot_info.slot); + if (scc_ret == SCC_RET_OK) { + slot_info.code = FSL_RETURN_OK_S; + } else if (scc_ret == SCC_RET_INSUFFICIENT_SPACE) { + slot_info.code = FSL_RETURN_NO_RESOURCE_S; + } else { + slot_info.code = FSL_RETURN_ERROR_S; + } + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("key length: %i, handle: %i\n", + slot_info.key_length, slot_info.slot); +#endif + + /* Return error code and slot info */ + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + if (os_err != OS_ERROR_OK_S) { + (void)keystore_slot_dealloc(&system_keystore, + slot_info.ownerid, + slot_info.slot); + } + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_dealloc(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + scc_ret = keystore_slot_dealloc(&system_keystore, + slot_info.ownerid, + slot_info.slot); + + if (scc_ret == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + } + slot_info.code = ret; + + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_load(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + /* Allow slop in alloc in case we are rounding up to word multiple */ + key = os_alloc_memory(slot_info.key_length + 3, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_NO_MEMORY_S; + } else { + os_err = os_copy_from_user(key, slot_info.key, + slot_info.key_length); + } + } + + if (os_err == OS_ERROR_OK_S) { + unsigned key_length = slot_info.key_length; + + /* Round up if necessary, as SCC call wants a multiple of 32-bit + * values for the full object being loaded. */ + if ((key_length & 3) != 0) { + key_length += 4 - (key_length & 3); + } + ret = keystore_slot_load(&system_keystore, + slot_info.ownerid, slot_info.slot, key, + key_length); + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_read(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + + /* This operation is not allowed for user keys */ + slot_info.code = FSL_RETURN_NO_RESOURCE_S; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + return os_err; + } + + if (os_err == OS_ERROR_OK_S) { + /* Allow slop in alloc in case we are rounding up to word multiple */ + key = os_alloc_memory(slot_info.key_length + 3, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_NO_MEMORY_S; + } + } + + if (os_err == OS_ERROR_OK_S) { + unsigned key_length = slot_info.key_length; + + /* @bug Do some PERMISSIONS checking - make sure this is SW key */ + + /* Round up if necessary, as SCC call wants a multiple of 32-bit + * values for the full object being loaded. */ + if ((key_length & 3) != 0) { + key_length += 4 - (key_length & 3); + } + ret = keystore_slot_read(&system_keystore, + slot_info.ownerid, slot_info.slot, + key_length, key); + + /* @bug do some error checking */ + + /* Send key back to user */ + os_err = os_copy_to_user(slot_info.key, key, + slot_info.key_length); + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_encrypt(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; + os_error_code os_err; + scc_return_t scc_ret; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + key = os_alloc_memory(slot_info.key_length, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + } + } + + if (key != NULL) { + + scc_ret = keystore_slot_encrypt(NULL, &system_keystore, + slot_info.ownerid, + slot_info.slot, + slot_info.key_length, key); + + if (scc_ret != SCC_RET_OK) { + ret = FSL_RETURN_ERROR_S; + } else { + os_err = + os_copy_to_user(slot_info.key, key, + slot_info.key_length); + if (os_err != OS_ERROR_OK_S) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + ret = FSL_RETURN_OK_S; + } + } + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +static os_error_code sah_handle_sk_slot_decrypt(uint32_t info) +{ + fsl_shw_return_t ret = FSL_RETURN_INTERNAL_ERROR_S; + scc_slot_t slot_info; /*!< decrypt request fields */ + os_error_code os_err; + scc_return_t scc_ret; + uint8_t *key = NULL; + + os_err = os_copy_from_user(&slot_info, (void *)info, sizeof(slot_info)); + + if (os_err == OS_ERROR_OK_S) { + key = os_alloc_memory(slot_info.key_length, GFP_KERNEL); + if (key == NULL) { + ret = FSL_RETURN_NO_RESOURCE_S; + os_err = OS_ERROR_OK_S; + } else { + os_err = os_copy_from_user(key, slot_info.key, + slot_info.key_length); + } + } + + if (os_err == OS_ERROR_OK_S) { + scc_ret = keystore_slot_decrypt(NULL, &system_keystore, + slot_info.ownerid, + slot_info.slot, + slot_info.key_length, key); + if (scc_ret == SCC_RET_OK) { + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + } + + slot_info.code = ret; + os_err = + os_copy_to_user((void *)info, &slot_info, + sizeof(slot_info)); + } + + if (key != NULL) { + memset(key, 0, slot_info.key_length); + os_free_memory(key); + } + + return os_err; +} + +/*! + * Register a user + * + * @brief Register a user + * + * @param user_ctx information about this user + * + * @return status code + */ +fsl_shw_return_t sah_handle_registration(fsl_shw_uco_t * user_ctx) +{ + /* Initialize the user's result pool (like sah_Queue_Construct() */ + user_ctx->result_pool.head = NULL; + user_ctx->result_pool.tail = NULL; + user_ctx->result_pool.count = 0; + + /* initialize the user's partition chain */ + user_ctx->partition = NULL; + + return FSL_RETURN_OK_S; +} + +/*! + * Deregister a user + * + * @brief Deregister a user + * + * @param user_ctx information about this user + * + * @return status code + */ +fsl_shw_return_t sah_handle_deregistration(fsl_shw_uco_t * user_ctx) +{ + /* NOTE: + * This will release any secure partitions that are held by the user. + * Encryption keys that were placed in the system keystore by the user + * should not be removed here, because they might have been shared with + * another process. The user must be careful to release any that are no + * longer in use. + */ + fsl_shw_return_t ret = FSL_RETURN_OK_S; + +#ifdef FSL_HAVE_SCC2 + fsl_shw_spo_t *partition; + struct mm_struct *mm = current->mm; + + while ((user_ctx->partition != NULL) && (ret == FSL_RETURN_OK_S)) { + + partition = user_ctx->partition; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("Found an abandoned secure partition at %p, releasing", + partition); +#endif + + /* It appears that current->mm is not valid if this is called from a + * close routine (perhaps only if the program raised an exception that + * caused it to close?) If that is the case, then still free the + * partition, but do not remove it from the memory space (dangerous?) + */ + + if (mm == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("Warning: no mm structure found, not unmapping " + "partition from user memory\n"); +#endif + } else { + /* Unmap the memory region (see sys_munmap in mmap.c) */ + /* Note that this assumes a single memory partition */ + unmap_user_memory(partition->user_base, 8192); + } + + /* If the memory was successfully released */ + if (ret == OS_ERROR_OK_S) { + /* release the partition */ + scc_release_partition(partition->kernel_base); + + /* and remove it from the users context */ + deregister_user_partition(user_ctx, + partition->user_base); + + ret = FSL_RETURN_OK_S; + } else { + ret = FSL_RETURN_ERROR_S; + + goto out; + } + } + out: +#endif /* FSL_HAVE_SCC2 */ + + return ret; +} + +/*! + * Sets up memory to extract results from results pool + * + * @brief Sets up memory to extract results from results pool + * + * @param user_ctx information about this user + * @param[in,out] arg contains input parameters and fields that the driver + * fills in + * + * @return os error code or 0 on success + */ +int sah_get_results_pointers(fsl_shw_uco_t * user_ctx, uint32_t arg) +{ + sah_results results_arg; /* kernel mode usable version of 'arg' */ + fsl_shw_result_t *user_results; /* user mode address of results */ + unsigned *user_actual; /* user mode address of actual number of results */ + unsigned actual; /* local memory of actual number of results */ + int ret_val = OS_ERROR_FAIL_S; + sah_Head_Desc *finished_request; + unsigned int loop; + + /* copy structure from user to kernel space */ + if (!os_copy_from_user(&results_arg, (void *)arg, sizeof(sah_results))) { + /* save user space pointers */ + user_actual = results_arg.actual; /* where count goes */ + user_results = results_arg.results; /* where results goe */ + + /* Set pointer for actual value to temporary kernel memory location */ + results_arg.actual = &actual; + + /* Allocate kernel memory to hold temporary copy of the results */ + results_arg.results = + os_alloc_memory(sizeof(fsl_shw_result_t) * + results_arg.requested, GFP_KERNEL); + + /* if memory allocated, continue */ + if (results_arg.results == NULL) { + ret_val = OS_ERROR_NO_MEMORY_S; + } else { + fsl_shw_return_t get_status; + + /* get the results */ + get_status = + sah_get_results_from_pool(user_ctx, &results_arg); + + /* free the copy of the user space descriptor chain */ + for (loop = 0; loop < actual; ++loop) { + /* get sah_Head_Desc from results and put user address into + * the return structure */ + finished_request = + results_arg.results[loop].user_desc; + results_arg.results[loop].user_desc = + finished_request->user_desc; + /* return the descriptor chain memory to the block free pool */ + sah_Free_Chained_Descriptors(finished_request); + } + + /* if no errors, copy results and then the actual number of results + * back to user space + */ + if (get_status == FSL_RETURN_OK_S) { + if (os_copy_to_user + (user_results, results_arg.results, + actual * sizeof(fsl_shw_result_t)) + || os_copy_to_user(user_actual, &actual, + sizeof(user_actual))) { + ret_val = OS_ERROR_FAIL_S; + } else { + ret_val = 0; /* no error */ + } + } + /* free the allocated memory */ + os_free_memory(results_arg.results); + } + } + + return ret_val; +} + +/*! + * Extracts results from results pool + * + * @brief Extract results from results pool + * + * @param user_ctx information about this user + * @param[in,out] arg contains input parameters and fields that the + * driver fills in + * + * @return status code + */ +fsl_shw_return_t sah_get_results_from_pool(volatile fsl_shw_uco_t * user_ctx, + sah_results * arg) +{ + sah_Head_Desc *finished_request; + unsigned int loop = 0; + os_lock_context_t int_flags; + + /* Get the number of results requested, up to total number of results + * available + */ + do { + /* Protect state of user's result pool until we have retrieved and + * remove the first entry, or determined that the pool is empty. */ + os_lock_save_context(desc_queue_lock, int_flags); + finished_request = user_ctx->result_pool.head; + + if (finished_request != NULL) { + sah_Queue_Remove_Entry((sah_Queue *) & user_ctx-> + result_pool); + os_unlock_restore_context(desc_queue_lock, int_flags); + + /* Prepare to free. */ + (void)sah_DePhysicalise_Descriptors(finished_request); + + arg->results[loop].user_ref = + finished_request->user_ref; + arg->results[loop].code = finished_request->result; + arg->results[loop].detail1 = + finished_request->fault_address; + arg->results[loop].detail2 = 0; + arg->results[loop].user_desc = finished_request; + + loop++; + } else { /* finished_request is NULL */ + /* pool is empty */ + os_unlock_restore_context(desc_queue_lock, int_flags); + } + + } while ((loop < arg->requested) && (finished_request != NULL)); + + /* record number of results actually obtained */ + *arg->actual = loop; + + return FSL_RETURN_OK_S; +} + +/*! + * Converts descriptor chain to kernel space (from user space) and submits + * chain to Sahara for processing + * + * @brief Submits converted descriptor chain to sahara + * + * @param user_ctx Pointer to Kernel version of user's ctx + * @param user_space_desc user space address of descriptor chain that is + * in user space + * + * @return OS status code + */ +static int handle_sah_ioctl_dar(fsl_shw_uco_t * user_ctx, + uint32_t user_space_desc) +{ + int os_error_code = OS_ERROR_FAIL_S; + sah_Head_Desc *desc_chain_head; /* chain in kernel - virtual address */ + + /* This will re-create the linked list so that the SAHARA hardware can + * DMA on it. + */ + desc_chain_head = sah_Copy_Descriptors(user_ctx, + (sah_Head_Desc *) + user_space_desc); + + if (desc_chain_head == NULL) { + /* We may have failed due to a -EFAULT as well, but we will return + * OS_ERROR_NO_MEMORY_S since either way it is a memory related + * failure. + */ + os_error_code = OS_ERROR_NO_MEMORY_S; + } else { + fsl_shw_return_t stat; + + desc_chain_head->user_info = user_ctx; + desc_chain_head->user_desc = (sah_Head_Desc *) user_space_desc; + + if (desc_chain_head->uco_flags & FSL_UCO_BLOCKING_MODE) { +#ifdef SAHARA_POLL_MODE + sah_Handle_Poll(desc_chain_head); +#else + sah_blocking_mode(desc_chain_head); +#endif + stat = desc_chain_head->result; + /* return the descriptor chain memory to the block free pool */ + sah_Free_Chained_Descriptors(desc_chain_head); + /* Tell user how the call turned out */ + + /* Copy 'result' back up to the result member. + * + * The dereference of the different member will cause correct the + * arithmetic to occur on the user-space address because of the + * missing dma/bus locations in the user mode version of the + * sah_Desc structure. */ + os_error_code = + os_copy_to_user((void *)(user_space_desc + + offsetof(sah_Head_Desc, + uco_flags)), + &stat, sizeof(fsl_shw_return_t)); + + } else { /* not blocking mode - queue and forget */ + + if (desc_chain_head->uco_flags & FSL_UCO_CALLBACK_MODE) { + user_ctx->process = os_get_process_handle(); + user_ctx->callback = sah_user_callback; + } +#ifdef SAHARA_POLL_MODE + /* will put results in result pool */ + sah_Handle_Poll(desc_chain_head); +#else + /* just put someting in the DAR */ + sah_Queue_Manager_Append_Entry(desc_chain_head); +#endif + /* assume all went well */ + os_error_code = OS_ERROR_OK_S; + } + } + + return os_error_code; +} + +static void sah_user_callback(fsl_shw_uco_t * user_ctx) +{ + os_send_signal(user_ctx->process, SIGUSR2); +} + +/*! + * This function is called when a thread attempts to read from the /proc/sahara + * file. Upon read, statistics and information about the state of the driver + * are returned in nthe supplied buffer. + * + * @brief SAHARA PROCFS read function. + * + * @param buf Anything written to this buffer will be returned to the + * user-space process that is reading from this proc entry. + * @param start Part of the kernel prototype. + * @param offset Part of the kernel prototype. + * @param count The size of the buf argument. + * @param eof An integer which is set to one to tell the user-space + * process that there is no more data to read. + * @param data Part of the kernel prototype. + * + * @return The number of bytes written to the proc entry. + */ +#if !defined(CONFIG_DEVFS_FS) || (LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)) +static int sah_read_procfs(char *buf, + char **start, + off_t offset, int count, int *eof, void *data) +{ + int output_bytes = 0; + int in_queue_count = 0; + os_lock_context_t lock_context; + + os_lock_save_context(desc_queue_lock, lock_context); + in_queue_count = sah_Queue_Manager_Count_Entries(TRUE, 0); + os_unlock_restore_context(desc_queue_lock, lock_context); + output_bytes += snprintf(buf, count - output_bytes, "queued: %d\n", + in_queue_count); + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Descriptors: %d, " + "Interrupts %d (%d Done1Done2, %d Done1Busy2, " + " %d Done1)\n", + dar_count, interrupt_count, done1done2_count, + done1busy2_count, done1_count); + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Control: %08x\n", sah_HW_Read_Control()); +#if !defined(FSL_HAVE_SAHARA4) || defined(SAHARA4_NO_USE_SQUIB) + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "IDAR: %08x; CDAR: %08x\n", + sah_HW_Read_IDAR(), sah_HW_Read_CDAR()); +#endif +#ifdef DIAG_DRV_STATUS + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "Status: %08x; Error Status: %08x; Op Status: %08x\n", + sah_HW_Read_Status(), + sah_HW_Read_Error_Status(), + sah_HW_Read_Op_Status()); +#endif +#ifdef FSL_HAVE_SAHARA4 + output_bytes += snprintf(buf + output_bytes, count - output_bytes, + "MMStat: %08x; Config: %08x\n", + sah_HW_Read_MM_Status(), sah_HW_Read_Config()); +#endif + + /* Signal the end of the file */ + *eof = 1; + + /* To get rid of the unused parameter warnings */ + (void)start; + (void)data; + (void)offset; + + return output_bytes; +} + +static int sah_write_procfs(struct file *file, const char __user * buffer, + unsigned long count, void *data) +{ + + /* Any write to this file will reset all counts. */ + dar_count = interrupt_count = done1done2_count = + done1busy2_count = done1_count = 0; + + (void)file; + (void)buffer; + (void)data; + + return count; +} + +#endif + +#ifndef SAHARA_POLL_MODE +/*! + * Block user call until processing is complete. + * + * @param entry The user's request. + * + * @return An OS error code, or 0 if no error + */ +int sah_blocking_mode(sah_Head_Desc * entry) +{ + int os_error_code = 0; + sah_Queue_Status status; + + /* queue entry, put something in the DAR, if nothing is there currently */ + sah_Queue_Manager_Append_Entry(entry); + + /* get this descriptor chain's current status */ + status = ((volatile sah_Head_Desc *)entry)->status; + + while (!SAH_DESC_PROCESSED(status)) { + extern sah_Queue *main_queue; + + DEFINE_WAIT(sahara_wait); /* create a wait queue entry. Linux */ + + /* enter the wait queue entry into the queue */ + prepare_to_wait(&Wait_queue, &sahara_wait, TASK_INTERRUPTIBLE); + + /* check if this entry has been processed */ + status = ((volatile sah_Head_Desc *)entry)->status; + + if (!SAH_DESC_PROCESSED(status)) { + /* go to sleep - Linux */ + schedule(); + } + + /* un-queue the 'prepare to wait' queue? - Linux */ + finish_wait(&Wait_queue, &sahara_wait); + + /* signal belongs to this thread? */ + if (signal_pending(current)) { /* Linux */ + os_lock_context_t lock_flags; + + /* don't allow access during this check and operation */ + os_lock_save_context(desc_queue_lock, lock_flags); + status = ((volatile sah_Head_Desc *)entry)->status; + if (status == SAH_STATE_PENDING) { + sah_Queue_Remove_Any_Entry(main_queue, entry); + entry->result = FSL_RETURN_INTERNAL_ERROR_S; + ((volatile sah_Head_Desc *)entry)->status = + SAH_STATE_FAILED; + } + os_unlock_restore_context(desc_queue_lock, lock_flags); + } + + status = ((volatile sah_Head_Desc *)entry)->status; + } /* while ... */ + + /* Do this so that caller can free */ + (void)sah_DePhysicalise_Descriptors(entry); + + return os_error_code; +} + +/*! + * If interrupt does not return in a reasonable time, time out, trigger + * interrupt, and continue with process + * + * @param data ignored + */ +void sahara_timeout_handler(unsigned long data) +{ + /* Sahara has not issuing an interrupt, so timed out */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("Sahara HW did not respond. Resetting.\n"); +#endif + /* assume hardware needs resetting */ + sah_Handle_Interrupt(SAH_EXEC_FAULT); + /* wake up sleeping thread to try again */ + wake_up_interruptible(&Wait_queue); +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +/* End of sah_driver_interface.c */ diff --git a/drivers/mxc/security/sahara2/sah_hardware_interface.c b/drivers/mxc/security/sahara2/sah_hardware_interface.c new file mode 100644 index 000000000000..a83ecdd5b805 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_hardware_interface.c @@ -0,0 +1,854 @@ +/* + * Copyright 2004-2008 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 sah_hardware_interface.c + * + * @brief Provides an interface to the SAHARA hardware registers. + * + */ + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_hardware_interface.h> +#include <sah_memory_mapper.h> +#include <sah_kernel.h> + +#if defined DIAG_DRV_IF || defined(DO_DBG) +#include <diagnostic.h> +#ifndef LOG_KDIAG +#define LOG_KDIAG(x) os_printk("%s\n", x) +#endif + +static void sah_Dump_Link(const char *prefix, const sah_Link * link, + dma_addr_t addr); + +/* This is for sprintf() to use when constructing output. */ +#define DIAG_MSG_SIZE 1024 +/* was 200 */ +#define MAX_DUMP 200 +static char Diag_msg[DIAG_MSG_SIZE]; + +#endif /* DIAG_DRV_IF */ + +/*! + * Number of descriptors sent to Sahara. This value should only be updated + * with the main queue lock held. + */ +uint32_t dar_count; + +/*! The "link-list optimize" bit in the Header of a Descriptor */ +#define SAH_HDR_LLO 0x01000000 + +/* IO_ADDRESS() is Linux macro -- need portable equivalent */ +#define SAHARA_BASE_ADDRESS IO_ADDRESS(SAHA_BASE_ADDR) +#define SAHARA_VERSION_REGISTER_OFFSET 0x000 +#define SAHARA_DAR_REGISTER_OFFSET 0x004 +#define SAHARA_CONTROL_REGISTER_OFFSET 0x008 +#define SAHARA_COMMAND_REGISTER_OFFSET 0x00C +#define SAHARA_STATUS_REGISTER_OFFSET 0x010 +#define SAHARA_ESTATUS_REGISTER_OFFSET 0x014 +#define SAHARA_FLT_ADD_REGISTER_OFFSET 0x018 +#define SAHARA_CDAR_REGISTER_OFFSET 0x01C +#define SAHARA_IDAR_REGISTER_OFFSET 0x020 +#define SAHARA_OSTATUS_REGISTER_OFFSET 0x028 +#define SAHARA_CONFIG_REGISTER_OFFSET 0x02C +#define SAHARA_MM_STAT_REGISTER_OFFSET 0x030 + +/*! Register within Sahara which contains hardware version. (1 or 2). */ +#define SAHARA_VERSION_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_VERSION_REGISTER_OFFSET) + +/*! Register within Sahara which is used to provide new work to the block. */ +#define SAHARA_DAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_DAR_REGISTER_OFFSET) + +/*! Register with Sahara which is used for configuration. */ +#define SAHARA_CONTROL_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CONTROL_REGISTER_OFFSET) + +/*! Register with Sahara which is used for changing status. */ +#define SAHARA_COMMAND_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_COMMAND_REGISTER_OFFSET) + +/*! Register with Sahara which is contains status and state. */ +#define SAHARA_STATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_STATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains error status information. */ +#define SAHARA_ESTATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_ESTATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains faulting address information. */ +#define SAHARA_FLT_ADD_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_FLT_ADD_REGISTER_OFFSET) + +/*! Register with Sahara which is contains current descriptor address. */ +#define SAHARA_CDAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CDAR_REGISTER_OFFSET) + +/*! Register with Sahara which is contains initial descriptor address (of a + chain). */ +#define SAHARA_IDAR_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_IDAR_REGISTER_OFFSET) + +/*! Register with Sahara which is contains op status information. */ +#define SAHARA_OSTATUS_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_OSTATUS_REGISTER_OFFSET) + +/*! Register with Sahara which is contains configuration information. */ +#define SAHARA_CONFIG_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_CONFIG_REGISTER_OFFSET) + +/*! Register with Sahara which is contains configuration information. */ +#define SAHARA_MM_STAT_REGISTER (SAHARA_BASE_ADDRESS + \ + SAHARA_MM_STAT_REGISTER_OFFSET) + +/* Local Functions */ +#if defined DIAG_DRV_IF || defined DO_DBG +void sah_Dump_Region(const char *prefix, const unsigned char *data, + dma_addr_t addr, unsigned length); + +#endif /* DIAG_DRV_IF */ + +/* time out value when polling SAHARA status register for completion */ +static uint32_t sah_poll_timeout = 0xFFFFFFFF; + +/*! + * Polls Sahara to determine when its current operation is complete + * + * @return last value found in Sahara's status register + */ +sah_Execute_Status sah_Wait_On_Sahara() +{ + uint32_t count = 0; /* ensure we don't get stuck in the loop forever */ + sah_Execute_Status status; /* Sahara's status register */ + uint32_t stat_reg; + + pr_debug("Entered sah_Wait_On_Sahara\n"); + + do { + /* get current status register from Sahara */ + stat_reg = sah_HW_Read_Status(); + status = stat_reg & SAH_EXEC_STATE_MASK; + + /* timeout if SAHARA takes too long to complete */ + if (++count == sah_poll_timeout) { + status = SAH_EXEC_FAULT; + printk("sah_Wait_On_Sahara timed out\n"); + } + + /* stay in loop as long as Sahara is still busy */ + } while ((status == SAH_EXEC_BUSY) || (status == SAH_EXEC_DONE1_BUSY2)); + + if (status == SAH_EXEC_ERROR1) { + if (stat_reg & OP_STATUS) { + status = SAH_EXEC_OPSTAT1; + } + } + + return status; +} /* sah_Wait_on_Sahara() */ + +/*! + * This function resets the SAHARA hardware. The following operations are + * performed: + * 1. Resets SAHARA. + * 2. Requests BATCH mode. + * 3. Enables interrupts. + * 4. Requests Little Endian mode. + * + * @brief SAHARA hardware reset function. + * + * @return void + */ +int sah_HW_Reset(void) +{ + sah_Execute_Status sah_state; + int status; /* this is the value to return to the calling routine */ + uint32_t saha_control = 0; + +#ifndef USE_3WORD_BURST +#ifdef FSL_HAVE_SAHARA2 + saha_control |= (8 << 16); /* Allow 8-word burst */ +#endif +#else +/***************** HARDWARE BUG WORK AROUND ******************/ +/* A burst size of > 4 can cause Sahara DMA to issue invalid AHB transactions + * when crossing 1KB boundaries. By limiting the 'burst size' to 3, these + * invalid transactions will not be generated, but Sahara will still transfer + * data more efficiently than if the burst size were set to 1. + */ + saha_control |= (3 << 16); /* Limit DMA burst size. For versions 2/3 */ +#endif /* USE_3WORD_BURST */ + +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Address of SAHARA_BASE_ADDRESS = 0x%08x\n", + SAHARA_BASE_ADDRESS); + LOG_KDIAG(Diag_msg); + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register before reset: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + + /* Write the Reset & BATCH mode command to the SAHARA Command register. */ + sah_HW_Write_Command(CMD_BATCH | CMD_RESET); +#ifdef SAHARA4_NO_USE_SQUIB + { + uint32_t cfg = sah_HW_Read_Config(); + cfg &= ~0x10000; + sah_HW_Write_Config(cfg); + } +#endif + + sah_poll_timeout = 0x0FFFFFFF; + sah_state = sah_Wait_On_Sahara(); +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register after reset: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + /* on reset completion, check that Sahara is in the idle state */ + status = (sah_state == SAH_EXEC_IDLE) ? 0 : OS_ERROR_FAIL_S; + + /* Set initial value out of reset */ + sah_HW_Write_Control(saha_control); + +#ifndef NO_RESEED_WORKAROUND +/***************** HARDWARE BUG WORK AROUND ******************/ +/* In order to set the 'auto reseed' bit, must first acquire a random value. */ + /* + * to solve a hardware bug, a random number must be generated before + * the 'RNG Auto Reseed' bit can be set. So this generates a random + * number that is thrown away. + * + * Note that the interrupt bit has not been set at this point so + * the result can be polled. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("Create and submit Random Number Descriptor"); +#endif + + if (status == OS_ERROR_OK_S) { + /* place to put random number */ + volatile uint32_t *random_data_ptr; + sah_Head_Desc *random_desc; + dma_addr_t desc_dma; + dma_addr_t rand_dma; + const int rnd_cnt = 3; /* how many random 32-bit values to get */ + + /* Get space for data -- assume at least 32-bit aligned! */ + random_data_ptr = os_alloc_memory(rnd_cnt * sizeof(uint32_t), + GFP_ATOMIC); + + random_desc = sah_Alloc_Head_Descriptor(); + + if ((random_data_ptr == NULL) || (random_desc == NULL)) { + status = OS_ERROR_FAIL_S; + } else { + int i; + + /* Clear out values */ + for (i = 0; i < rnd_cnt; i++) { + random_data_ptr[i] = 0; + } + + rand_dma = os_pa(random_data_ptr); + + random_desc->desc.header = 0xB18C0000; /* LLO get random number */ + random_desc->desc.len1 = + rnd_cnt * sizeof(*random_data_ptr); + random_desc->desc.ptr1 = (void *)rand_dma; + random_desc->desc.original_ptr1 = + (void *)random_data_ptr; + + random_desc->desc.len2 = 0; /* not used */ + random_desc->desc.ptr2 = 0; /* not used */ + + random_desc->desc.next = 0; /* chain terminates here */ + random_desc->desc.original_next = 0; /* chain terminates here */ + + desc_dma = random_desc->desc.dma_addr; + + /* Force in-cache data out to RAM */ + os_cache_clean_range(random_data_ptr, + rnd_cnt * + sizeof(*random_data_ptr)); + + /* pass descriptor to Sahara */ + sah_HW_Write_DAR(desc_dma); + + /* + * Wait for RNG to complete (interrupts are disabled at this point + * due to sahara being reset previously) then check for error + */ + sah_state = sah_Wait_On_Sahara(); + /* Force CPU to ignore in-cache and reload from RAM */ + os_cache_inv_range(random_data_ptr, + rnd_cnt * sizeof(*random_data_ptr)); + + /* if it didn't move to done state, an error occured */ + if ( +#ifndef SUBMIT_MULTIPLE_DARS + (sah_state != SAH_EXEC_IDLE) && +#endif + (sah_state != SAH_EXEC_DONE1) + ) { + status = OS_ERROR_FAIL_S; + os_printk + ("(sahara) Failure: state is %08x; random_data is" + " %08x\n", sah_state, *random_data_ptr); + os_printk + ("(sahara) CDAR: %08x, IDAR: %08x, FADR: %08x," + " ESTAT: %08x\n", sah_HW_Read_CDAR(), + sah_HW_Read_IDAR(), + sah_HW_Read_Fault_Address(), + sah_HW_Read_Error_Status()); + } else { + int i; + int seen_rand = 0; + + for (i = 0; i < rnd_cnt; i++) { + if (*random_data_ptr != 0) { + seen_rand = 1; + break; + } + } + if (!seen_rand) { + status = OS_ERROR_FAIL_S; + os_printk + ("(sahara) Error: Random number is zero!\n"); + } + } + } + + if (random_data_ptr) { + os_free_memory((void *)random_data_ptr); + } + if (random_desc) { + sah_Free_Head_Descriptor(random_desc); + } + } +/***************** END HARDWARE BUG WORK AROUND ******************/ +#endif + + if (status == 0) { +#ifdef FSL_HAVE_SAHARA2 + saha_control |= CTRL_RNG_RESEED; +#endif + +#ifndef SAHARA_POLL_MODE + saha_control |= CTRL_INT_EN; /* enable interrupts */ +#else + sah_poll_timeout = SAHARA_POLL_MODE_TIMEOUT; +#endif + +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Setting up Sahara's Control Register: %08x\n", + saha_control); + LOG_KDIAG(Diag_msg); +#endif + + /* Rewrite the setup to the SAHARA Control register */ + sah_HW_Write_Control(saha_control); +#ifdef DIAG_DRV_IF + snprintf(Diag_msg, DIAG_MSG_SIZE, + "Sahara Status register after control write: %08x", + sah_HW_Read_Status()); + LOG_KDIAG(Diag_msg); +#endif + +#ifdef FSL_HAVE_SAHARA4 + { + uint32_t cfg = sah_HW_Read_Config(); + sah_HW_Write_Config(cfg | 0x100); /* Add RNG auto-reseed */ + } +#endif + } else { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Reset failed\n"); +#endif + } + + return status; +} /* sah_HW_Reset() */ + +/*! + * This function enables High Assurance mode. + * + * @brief SAHARA hardware enable High Assurance mode. + * + * @return FSL_RETURN_OK_S - if HA was set successfully + * @return FSL_RETURN_INTERNAL_ERROR_S - if HA was not set due to SAHARA + * being busy. + */ +fsl_shw_return_t sah_HW_Set_HA(void) +{ + /* This is the value to write to the register */ + uint32_t value; + + /* Read from the control register. */ + value = sah_HW_Read_Control(); + + /* Set the HA bit */ + value |= CTRL_HA; + + /* Write to the control register. */ + sah_HW_Write_Control(value); + + /* Read from the control register. */ + value = sah_HW_Read_Control(); + + return (value & CTRL_HA) ? FSL_RETURN_OK_S : + FSL_RETURN_INTERNAL_ERROR_S; +} + +/*! + * This function reads the SAHARA hardware Version Register. + * + * @brief Read SAHARA hardware Version Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Version(void) +{ + return os_read32(SAHARA_VERSION_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Control Register. + * + * @brief Read SAHARA hardware Control Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Control(void) +{ + return os_read32(SAHARA_CONTROL_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Status Register. + * + * @brief Read SAHARA hardware Status Register. + * + * @return uint32_t Register value. + */ +uint32_t sah_HW_Read_Status(void) +{ + return os_read32(SAHARA_STATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Error Status Register. + * + * @brief Read SAHARA hardware Error Status Register. + * + * @return uint32_t Error Status value. + */ +uint32_t sah_HW_Read_Error_Status(void) +{ + return os_read32(SAHARA_ESTATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Op Status Register. + * + * @brief Read SAHARA hardware Op Status Register. + * + * @return uint32_t Op Status value. + */ +uint32_t sah_HW_Read_Op_Status(void) +{ + return os_read32(SAHARA_OSTATUS_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Descriptor Address Register. + * + * @brief Read SAHARA hardware DAR Register. + * + * @return uint32_t DAR value. + */ +uint32_t sah_HW_Read_DAR(void) +{ + return os_read32(SAHARA_DAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Current Descriptor Address Register. + * + * @brief Read SAHARA hardware CDAR Register. + * + * @return uint32_t CDAR value. + */ +uint32_t sah_HW_Read_CDAR(void) +{ + return os_read32(SAHARA_CDAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Initial Descriptor Address Register. + * + * @brief Read SAHARA hardware IDAR Register. + * + * @return uint32_t IDAR value. + */ +uint32_t sah_HW_Read_IDAR(void) +{ + return os_read32(SAHARA_IDAR_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Fault Address Register. + * + * @brief Read SAHARA Fault Address Register. + * + * @return uint32_t Fault Address value. + */ +uint32_t sah_HW_Read_Fault_Address(void) +{ + return os_read32(SAHARA_FLT_ADD_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Multiple Master Status Register. + * + * @brief Read SAHARA hardware MM Stat Register. + * + * @return uint32_t MM Stat value. + */ +uint32_t sah_HW_Read_MM_Status(void) +{ + return os_read32(SAHARA_MM_STAT_REGISTER); +} + +/*! + * This function reads the SAHARA hardware Configuration Register. + * + * @brief Read SAHARA Configuration Register. + * + * @return uint32_t Configuration value. + */ +uint32_t sah_HW_Read_Config(void) +{ + return os_read32(SAHARA_CONFIG_REGISTER); +} + +/*! + * This function writes a command to the SAHARA hardware Command Register. + * + * @brief Write to SAHARA hardware Command Register. + * + * @param command An unsigned 32bit command value. + * + * @return void + */ +void sah_HW_Write_Command(uint32_t command) +{ + os_write32(SAHARA_COMMAND_REGISTER, command); +} + +/*! + * This function writes a control value to the SAHARA hardware Control + * Register. + * + * @brief Write to SAHARA hardware Control Register. + * + * @param control An unsigned 32bit control value. + * + * @return void + */ +void sah_HW_Write_Control(uint32_t control) +{ + os_write32(SAHARA_CONTROL_REGISTER, control); +} + +/*! + * This function writes a configuration value to the SAHARA hardware Configuration + * Register. + * + * @brief Write to SAHARA hardware Configuration Register. + * + * @param configuration An unsigned 32bit configuration value. + * + * @return void + */ +void sah_HW_Write_Config(uint32_t configuration) +{ + os_write32(SAHARA_CONFIG_REGISTER, configuration); +} + +/*! + * This function writes a descriptor address to the SAHARA Descriptor Address + * Register. + * + * @brief Write to SAHARA Descriptor Address Register. + * + * @param pointer An unsigned 32bit descriptor address value. + * + * @return void + */ +void sah_HW_Write_DAR(uint32_t pointer) +{ + os_write32(SAHARA_DAR_REGISTER, pointer); + dar_count++; +} + +#if defined DIAG_DRV_IF || defined DO_DBG + +static char *interpret_header(uint32_t header) +{ + unsigned desc_type = ((header >> 24) & 0x70) | ((header >> 16) & 0xF); + + switch (desc_type) { + case 0x12: + return "5/SKHA_ST_CTX"; + case 0x13: + return "35/SKHA_LD_MODE_KEY"; + case 0x14: + return "38/SKHA_LD_MODE_IN_CPHR_ST_CTX"; + case 0x15: + return "4/SKHA_IN_CPHR_OUT"; + case 0x16: + return "34/SKHA_ST_SBOX"; + case 0x18: + return "1/SKHA_LD_MODE_IV_KEY"; + case 0x19: + return "33/SKHA_ST_SBOX"; + case 0x1D: + return "2/SKHA_LD_MODE_IN_CPHR_OUT"; + case 0x22: + return "11/MDHA_ST_MD"; + case 0x25: + return "10/MDHA_HASH_ST_MD"; + case 0x28: + return "6/MDHA_LD_MODE_MD_KEY"; + case 0x2A: + return "39/MDHA_ICV"; + case 0x2D: + return "8/MDHA_LD_MODE_HASH_ST_MD"; + case 0x3C: + return "18/RNG_GEN"; + case 0x40: + return "19/PKHA_LD_N_E"; + case 0x41: + return "36/PKHA_LD_A3_B0"; + case 0x42: + return "27/PKHA_ST_A_B"; + case 0x43: + return "22/PKHA_LD_A_B"; + case 0x44: + return "23/PKHA_LD_A0_A1"; + case 0x45: + return "24/PKHA_LD_A2_A3"; + case 0x46: + return "25/PKHA_LD_B0_B1"; + case 0x47: + return "26/PKHA_LD_B2_B3"; + case 0x48: + return "28/PKHA_ST_A0_A1"; + case 0x49: + return "29/PKHA_ST_A2_A3"; + case 0x4A: + return "30/PKHA_ST_B0_B1"; + case 0x4B: + return "31/PKHA_ST_B2_B3"; + case 0x4C: + return "32/PKHA_EX_ST_B1"; + case 0x4D: + return "20/PKHA_LD_A_EX_ST_B"; + case 0x4E: + return "21/PKHA_LD_N_EX_ST_B"; + case 0x4F: + return "37/PKHA_ST_B1_B2"; + default: + return "??/UNKNOWN"; + } +} /* cvt_desc_name() */ + +/*! + * Dump chain of descriptors to the log. + * + * @brief Dump descriptor chain + * + * @param chain Kernel virtual address of start of chain of descriptors + * + * @return void + */ +void sah_Dump_Chain(const sah_Desc * chain, dma_addr_t addr) +{ + int desc_no = 1; + + pr_debug("Chain for Sahara\n"); + + while (chain != NULL) { + char desc_name[50]; + + sprintf(desc_name, "Desc %02d (%s)\n" KERN_DEBUG "Desc ", + desc_no++, interpret_header(chain->header)); + + sah_Dump_Words(desc_name, (unsigned *)chain, addr, + 6 /* #words in h/w link */ ); + if (chain->original_ptr1) { + if (chain->header & SAH_HDR_LLO) { + sah_Dump_Region(" Data1", + (unsigned char *)chain-> + original_ptr1, + (dma_addr_t) chain->ptr1, + chain->len1); + } else { + sah_Dump_Link(" Link1", chain->original_ptr1, + (dma_addr_t) chain->ptr1); + } + } + if (chain->ptr2) { + if (chain->header & SAH_HDR_LLO) { + sah_Dump_Region(" Data2", + (unsigned char *)chain-> + original_ptr2, + (dma_addr_t) chain->ptr2, + chain->len2); + } else { + sah_Dump_Link(" Link2", chain->original_ptr2, + (dma_addr_t) chain->ptr2); + } + } + + addr = (dma_addr_t) chain->next; + chain = (chain->next) ? (chain->original_next) : NULL; + } +} + +/*! + * Dump chain of links to the log. + * + * @brief Dump chain of links + * + * @param prefix Text to put in front of dumped data + * @param link Kernel virtual address of start of chain of links + * + * @return void + */ +static void sah_Dump_Link(const char *prefix, const sah_Link * link, + dma_addr_t addr) +{ +#ifdef DUMP_SCC_DATA + extern uint8_t *sahara_partition_base; + extern dma_addr_t sahara_partition_phys; +#endif + + while (link != NULL) { + sah_Dump_Words(prefix, (unsigned *)link, addr, + 3 /* # words in h/w link */ ); + if (link->flags & SAH_STORED_KEY_INFO) { +#ifdef SAH_DUMP_DATA +#ifdef DUMP_SCC_DATA + sah_Dump_Region(" Data", + (uint8_t *) link->data - + (uint8_t *) sahara_partition_phys + + sahara_partition_base, + (dma_addr_t) link->data, link->len); +#else + pr_debug(" Key Slot %d\n", link->slot); +#endif +#endif + } else { +#ifdef SAH_DUMP_DATA + sah_Dump_Region(" Data", link->original_data, + (dma_addr_t) link->data, link->len); +#endif + } + addr = (dma_addr_t) link->next; + link = link->original_next; + } +} + +/*! + * Dump given region of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param length Amount of data to dump + * + * @return void + */ +void sah_Dump_Region(const char *prefix, const unsigned char *data, + dma_addr_t addr, unsigned length) +{ + unsigned count; + char *output; + unsigned data_len; + + sprintf(Diag_msg, "%s (%08X,%u):", prefix, addr, length); + + /* Restrict amount of data to dump */ + if (length > MAX_DUMP) { + data_len = MAX_DUMP; + } else { + data_len = length; + } + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + for (count = 0; count < data_len; count++) { + if ((count % 4) == 0) { + *output++ = ' '; + } + sprintf(output, "%02X", *data++); + output += 2; + } + + pr_debug("%s\n", Diag_msg); +} + +/*! + * Dump given word of data to the log. + * + * @brief Dump data + * + * @param prefix Text to put in front of dumped data + * @param data Kernel virtual address of start of region to dump + * @param word_count Amount of data to dump + * + * @return void + */ +void sah_Dump_Words(const char *prefix, const unsigned *data, dma_addr_t addr, + unsigned word_count) +{ + char *output; + + sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, addr, word_count); + + /* We've already printed some text in output buffer, skip over it */ + output = Diag_msg + strlen(Diag_msg); + + while (word_count--) { + sprintf(output, "%08X ", *data++); + output += 9; + } + + pr_debug("%s\n", Diag_msg); + +} + +#endif /* DIAG_DRV_IF */ + +/* End of sah_hardware_interface.c */ diff --git a/drivers/mxc/security/sahara2/sah_interrupt_handler.c b/drivers/mxc/security/sahara2/sah_interrupt_handler.c new file mode 100644 index 000000000000..3d70fe8d2c64 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_interrupt_handler.c @@ -0,0 +1,216 @@ +/* + * Copyright 2004-2008 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 sah_interrupt_handler.c +* +* @brief Provides a hardware interrupt handling mechanism for device driver. +* +* This file needs to be ported for a non-Linux OS. +* +* It gets a call at #sah_Intr_Init() during initialization. +* +* #sah_Intr_Top_Half() is intended to be the Interrupt Service Routine. It +* calls a portable function in another file to process the Sahara status. +* +* #sah_Intr_Bottom_Half() is a 'background' task scheduled by the top half to +* take care of the expensive tasks of the interrupt processing. +* +* The driver shutdown code calls #sah_Intr_Release(). +* +*/ + +#include <portable_os.h> + +/* SAHARA Includes */ +#include <sah_kernel.h> +#include <sah_interrupt_handler.h> +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#include <sah_queue_manager.h> + +/*Enable this flag for debugging*/ +#if 0 +#define DIAG_DRV_INTERRUPT +#endif + +#ifdef DIAG_DRV_INTERRUPT +#include <diagnostic.h> +#endif + +/*! + * Number of interrupts received. This value should only be updated during + * interrupt processing. + */ +uint32_t interrupt_count; + +#ifndef SAHARA_POLL_MODE + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +#define irqreturn_t void +#define IRQ_HANDLED +#define IRQ_RETVAL(x) +#endif + +/* Internal Prototypes */ +static irqreturn_t sah_Intr_Top_Half(int irq, void *dev_id); + +#ifdef KERNEL_TEST +extern void (*SAHARA_INT_PTR) (int, void *); +#endif + +unsigned long reset_flag; +static void sah_Intr_Bottom_Half(unsigned long reset_flag); + +/* This is the Bottom Half Task, (reset flag set to false) */ +DECLARE_TASKLET(BH_task, sah_Intr_Bottom_Half, (unsigned long)&reset_flag); + +/*! This is set by the Initialisation function */ +wait_queue_head_t *int_queue = NULL; + +/*! +******************************************************************************* +* This function registers the Top Half of the interrupt handler with the Kernel +* and the SAHARA IRQ number. +* +* @brief SAHARA Interrupt Handler Initialisation +* +* @param wait_queue Pointer to the wait queue used by driver interface +* +* @return int A return of Zero indicates successful initialisation. +*/ +/****************************************************************************** +* +* CAUTION: NONE +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/2003 MW Initial Creation +******************************************************************************/ +int sah_Intr_Init(wait_queue_head_t * wait_queue) +{ + +#ifdef DIAG_DRV_INTERRUPT + char err_string[200]; +#endif + + int result; + +#ifdef KERNEL_TEST + SAHARA_INT_PTR = sah_Intr_Top_Half; +#endif + + /* Set queue used by the interrupt handler to match the driver interface */ + int_queue = wait_queue; + + /* Request use of the Interrupt line. */ + result = request_irq(SAHARA_IRQ, + sah_Intr_Top_Half, 0, SAHARA_NAME, NULL); + +#ifdef DIAG_DRV_INTERRUPT + if (result != 0) { + sprintf(err_string, "Cannot use SAHARA interrupt line %d. " + "request_irq() return code is %i.", SAHARA_IRQ, result); + LOG_KDIAG(err_string); + } else { + sprintf(err_string, + "SAHARA driver registered for interrupt %d. ", + SAHARA_IRQ); + LOG_KDIAG(err_string); + } +#endif + + return result; +} + +/*! +******************************************************************************* +* This function releases the Top Half of the interrupt handler. The driver will +* not receive any more interrupts after calling this functions. +* +* @brief SAHARA Interrupt Handler Release +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: NONE +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 30/07/2003 MW Initial Creation +******************************************************************************/ +void sah_Intr_Release(void) +{ + /* Release the Interrupt. */ + free_irq(SAHARA_IRQ, NULL); +} + +/*! +******************************************************************************* +* This function is the Top Half of the interrupt handler. It updates the +* status of any finished descriptor chains and then tries to add any pending +* requests into the hardware. It then queues the bottom half to complete +* operations on the finished chains. +* +* @brief SAHARA Interrupt Handler Top Half +* +* @param irq Part of the kernel prototype. +* @param dev_id Part of the kernel prototype. +* +* @return An IRQ_RETVAL() -- non-zero to that function means 'handled' +*/ +static irqreturn_t sah_Intr_Top_Half(int irq, void *dev_id) +{ +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG("Top half of Sahara's interrupt handler called."); +#endif + + interrupt_count++; + reset_flag = sah_Handle_Interrupt(sah_HW_Read_Status()); + + /* Schedule the Bottom Half of the Interrupt. */ + tasklet_schedule(&BH_task); + + /* To get rid of the unused parameter warnings. */ + irq = 0; + dev_id = NULL; + return IRQ_RETVAL(1); +} + +/*! +******************************************************************************* +* This function is the Bottom Half of the interrupt handler. It calls +* #sah_postprocess_queue() to complete the processing of the Descriptor Chains +* which were finished by the hardware. +* +* @brief SAHARA Interrupt Handler Bottom Half +* +* @param data Part of the kernel prototype. +* +* @return void +*/ +static void sah_Intr_Bottom_Half(unsigned long reset_flag) +{ +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG("Bottom half of Sahara's interrupt handler called."); +#endif + sah_postprocess_queue(*(unsigned long *)reset_flag); + + return; +} + +/* end of sah_interrupt_handler.c */ +#endif /* ifndef SAHARA_POLL_MODE */ diff --git a/drivers/mxc/security/sahara2/sah_memory_mapper.c b/drivers/mxc/security/sahara2/sah_memory_mapper.c new file mode 100644 index 000000000000..29080343ee11 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_memory_mapper.c @@ -0,0 +1,2349 @@ +/* + * Copyright 2004-2009 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 sah_memory_mapper.c +* +* @brief Re-creates SAHARA data structures in Kernel memory such that they are +* suitable for DMA. Provides support for kernel API. +* +* This file needs to be ported. +* +* The memory mapper gets a call at #sah_Init_Mem_Map() during driver +* initialization. +* +* The routine #sah_Copy_Descriptors() is used to bring descriptor chains from +* user memory down to kernel memory, relink using physical addresses, and make +* sure that all user data will be accessible by the Sahara DMA. +* #sah_Destroy_Descriptors() does the inverse. +* +* The #sah_Alloc_Block(), #sah_Free_Block(), and #sah_Block_Add_Page() routines +* implement a cache of free blocks used when allocating descriptors and links +* within the kernel. +* +* The memory mapper gets a call at #sah_Stop_Mem_Map() during driver shutdown. +* +*/ + +#include <sah_driver_common.h> +#include <sah_kernel.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#ifdef FSL_HAVE_SCC2 +#include <linux/mxc_scc2_driver.h> +#else +#include <linux/mxc_scc_driver.h> +#endif + +#if defined(DIAG_DRV_IF) || defined(DIAG_MEM) || defined(DO_DBG) +#include <diagnostic.h> +#include <sah_hardware_interface.h> +#endif + +#include <linux/mm.h> /* get_user_pages() */ +#include <linux/pagemap.h> +#include <linux/dmapool.h> + +#include <linux/slab.h> +#include <linux/highmem.h> + +#if defined(DIAG_MEM) || defined(DIAG_DRV_IF) +#define DIAG_MSG_SIZE 1024 +static char Diag_msg[DIAG_MSG_SIZE]; +#endif + +#ifdef LINUX_VERSION_CODE +#define FLUSH_SPECIFIC_DATA_ONLY +#else +#define SELF_MANAGED_POOL +#endif + +#if defined(LINUX_VERSION_CODE) +EXPORT_SYMBOL(sah_Alloc_Link); +EXPORT_SYMBOL(sah_Free_Link); +EXPORT_SYMBOL(sah_Alloc_Descriptor); +EXPORT_SYMBOL(sah_Free_Descriptor); +EXPORT_SYMBOL(sah_Alloc_Head_Descriptor); +EXPORT_SYMBOL(sah_Free_Head_Descriptor); +EXPORT_SYMBOL(sah_Physicalise_Descriptors); +EXPORT_SYMBOL(sah_DePhysicalise_Descriptors); +#endif + +/* Determine if L2 cache support should be built in. */ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)) +#ifdef CONFIG_OUTER_CACHE +#define HAS_L2_CACHE +#endif +#else +#ifdef CONFIG_CPU_CACHE_L210 +#define HAS_L2_CACHE +#endif +#endif + +/* Number of bytes the hardware uses out of sah_Link and sah_*Desc structs */ +#define SAH_HW_LINK_LEN 1 +#define SAH_HW_DESC_LEN 24 + +/* Macros for Descriptors */ +#define SAH_LLO_BIT 0x01000000 +#define sah_Desc_Get_LLO(desc) (desc->header & SAH_LLO_BIT) +#define sah_Desc_Set_Header(desc, h) (desc->header = (h)) + +#define sah_Desc_Get_Next(desc) (desc->next) +#define sah_Desc_Set_Next(desc, n) (desc->next = (n)) + +#define sah_Desc_Get_Ptr1(desc) (desc->ptr1) +#define sah_Desc_Get_Ptr2(desc) (desc->ptr2) +#define sah_Desc_Set_Ptr1(desc,p1) (desc->ptr1 = (p1)) +#define sah_Desc_Set_Ptr2(desc,p2) (desc->ptr2 = (p2)) + +#define sah_Desc_Get_Len1(desc) (desc->len1) +#define sah_Desc_Get_Len2(desc) (desc->len2) +#define sah_Desc_Set_Len1(desc,l1) (desc->len1 = (l1)) +#define sah_Desc_Set_Len2(desc,l2) (desc->len2 = (l2)) + +/* Macros for Links */ +#define sah_Link_Get_Next(link) (link->next) +#define sah_Link_Set_Next(link, n) (link->next = (n)) + +#define sah_Link_Get_Data(link) (link->data) +#define sah_Link_Set_Data(link,d) (link->data = (d)) + +#define sah_Link_Get_Len(link) (link->len) +#define sah_Link_Set_Len(link, l) (link->len = (l)) + +#define sah_Link_Get_Flags(link) (link->flags) + +/* Memory block defines */ +/* Warning! This assumes that kernel version of sah_Link + * is larger than kernel version of sah_Desc. + */ +#define MEM_BLOCK_SIZE sizeof(sah_Link) + +/*! Structure for link/descriptor memory blocks in internal pool */ +typedef struct mem_block { + uint8_t data[MEM_BLOCK_SIZE]; /*!< the actual buffer area */ + struct mem_block *next; /*!< next block when in free chain */ + dma_addr_t dma_addr; /*!< physical address of @a data */ +} Mem_Block; + +#define MEM_BLOCK_ENTRIES (PAGE_SIZE / sizeof(Mem_Block)) + +#define MEM_BIG_BLOCK_SIZE sizeof(sah_Head_Desc) + +/*! Structure for head descriptor memory blocks in internal pool */ +typedef struct mem_big_block { + uint8_t data[MEM_BIG_BLOCK_SIZE]; /*!< the actual buffer area */ + struct mem_big_block *next; /*!< next block when in free chain */ + uint32_t dma_addr; /*!< physical address of @a data */ +} Mem_Big_Block; + +#define MEM_BIG_BLOCK_ENTRIES (PAGE_SIZE / sizeof(Mem_Big_Block)) + +/* Shared variables */ + +/*! + * Lock to protect the memory chain composed of #block_free_head and + * #block_free_tail. + */ +static os_lock_t mem_lock; + +#ifndef SELF_MANAGED_POOL +static struct dma_pool *big_dma_pool = NULL; +static struct dma_pool *small_dma_pool = NULL; +#endif + +#ifdef SELF_MANAGED_POOL +/*! + * Memory block free pool - pointer to first block. Chain is protected by + * #mem_lock. + */ +static Mem_Block *block_free_head = NULL; +/*! + * Memory block free pool - pointer to last block. Chain is protected by + * #mem_lock. + */ +static Mem_Block *block_free_tail = NULL; +/*! + * Memory block free pool - pointer to first block. Chain is protected by + * #mem_lock. + */ +static Mem_Big_Block *big_block_free_head = NULL; +/*! + * Memory block free pool - pointer to last block. Chain is protected by + * #mem_lock. +a */ +static Mem_Big_Block *big_block_free_tail = NULL; +#endif /* SELF_MANAGED_POOL */ + +static Mem_Block *sah_Alloc_Block(void); +static void sah_Free_Block(Mem_Block * block); +static Mem_Big_Block *sah_Alloc_Big_Block(void); +static void sah_Free_Big_Block(Mem_Big_Block * block); +#ifdef SELF_MANAGED_POOL +static void sah_Append_Block(Mem_Block * block); +static void sah_Append_Big_Block(Mem_Big_Block * block); +#endif /* SELF_MANAGED_POOL */ + +/* Page context structure. Used by wire_user_memory and unwire_user_memory */ +typedef struct page_ctx_t { + uint32_t count; + struct page **local_pages; +} page_ctx_t; + +/*! +******************************************************************************* +* Map and wire down a region of user memory. +* +* +* @param address Userspace address of the memory to wire +* @param length Length of the memory region to wire +* @param page_ctx Page context, to be passed to unwire_user_memory +* +* @return (if successful) Kernel virtual address of the wired pages +*/ +void *wire_user_memory(void *address, uint32_t length, void **page_ctx) +{ + void *kernel_black_addr = NULL; + int result = -1; + int page_index = 0; + page_ctx_t *page_context; + int nr_pages = 0; + unsigned long start_page; + fsl_shw_return_t status; + + /* Determine the number of pages being used for this link */ + nr_pages = (((unsigned long)(address) & ~PAGE_MASK) + + length + ~PAGE_MASK) >> PAGE_SHIFT; + + start_page = (unsigned long)(address) & PAGE_MASK; + + /* Allocate some memory to keep track of the wired user pages, so that + * they can be deallocated later. The block of memory will contain both + * the structure and the array of pages. + */ + page_context = kmalloc(sizeof(page_ctx_t) + + nr_pages * sizeof(struct page *), GFP_KERNEL); + + if (page_context == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; /* no memory! */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("kmalloc() failed."); +#endif + return NULL; + } + + /* Set the page pointer to point to the allocated region of memory */ + page_context->local_pages = (void *)page_context + sizeof(page_ctx_t); + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, local_pages at: %p", + (void *)page_context, + (void *)(page_context->local_pages)); +#endif + + /* Wire down the pages from user space */ + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, + start_page, nr_pages, WRITE, 0 /* noforce */ , + (page_context->local_pages), NULL); + up_read(¤t->mm->mmap_sem); + + if (result < nr_pages) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("get_user_pages() failed."); +#endif + if (result > 0) { + for (page_index = 0; page_index < result; page_index++) { + page_cache_release((page_context-> + local_pages[page_index])); + } + + kfree(page_context); + } + return NULL; + } + + kernel_black_addr = page_address(page_context->local_pages[0]) + + ((unsigned long)address & ~PAGE_MASK); + + page_context->count = nr_pages; + *page_ctx = page_context; + + return kernel_black_addr; +} + +/*! +******************************************************************************* +* Release and unmap a region of user memory. +* +* @param page_ctx Page context from wire_user_memory +*/ +void unwire_user_memory(void **page_ctx) +{ + int page_index = 0; + struct page_ctx_t *page_context = *page_ctx; + +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS("page_context at: %p, first page at:%p, count: %i", + (void *)page_context, + (void *)(page_context->local_pages), + page_context->count); +#endif + + if ((page_context != NULL) && (page_context->local_pages != NULL)) { + for (page_index = 0; page_index < page_context->count; + page_index++) { + page_cache_release(page_context-> + local_pages[page_index]); + } + + kfree(page_context); + *page_ctx = NULL; + } +} + +/*! +******************************************************************************* +* Map some physical memory into a users memory space +* +* @param vma Memory structure to map to +* @param physical_addr Physical address of the memory to be mapped in +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code +map_user_memory(struct vm_area_struct *vma, uint32_t physical_addr, + uint32_t size) +{ + os_error_code retval; + + /* Map the acquired partition into the user's memory space */ + vma->vm_end = vma->vm_start + size; + + /* set cache policy to uncached so that each write of the UMID and + * permissions get directly to the SCC2 in order to engage it + * properly. Once the permissions have been written, it may be + * useful to provide a service for the user to request a different + * cache policy + */ + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Make sure that the user cannot fork() a child which will inherit + * this mapping, as it creates a security hole. Likewise, do not + * allow the user to 'expand' his mapping beyond this partition. + */ + vma->vm_flags |= VM_IO | VM_RESERVED | VM_DONTCOPY | VM_DONTEXPAND; + + retval = remap_pfn_range(vma, + vma->vm_start, + __phys_to_pfn(physical_addr), + size, vma->vm_page_prot); + + return retval; +} + +/*! +******************************************************************************* +* Remove some memory from a user's memory space +* +* @param user_addr Userspace address of the memory to be unmapped +* @param size Size of the memory to map (bytes) +* +* @return +*/ +os_error_code unmap_user_memory(uint32_t user_addr, uint32_t size) +{ + os_error_code retval; + struct mm_struct *mm = current->mm; + + /* Unmap the memory region (see sys_munmap in mmap.c) */ + down_write(&mm->mmap_sem); + retval = do_munmap(mm, (unsigned long)user_addr, size); + up_write(&mm->mmap_sem); + + return retval; +} + +/*! +******************************************************************************* +* Free descriptor back to free pool +* +* @brief Free descriptor +* +* @param desc A descriptor allocated with sah_Alloc_Descriptor(). +* +* @return none +* +*/ +void sah_Free_Descriptor(sah_Desc * desc) +{ + memset(desc, 0x45, sizeof(*desc)); + sah_Free_Block((Mem_Block *) desc); +} + +/*! +******************************************************************************* +* Free Head descriptor back to free pool +* +* @brief Free Head descriptor +* +* @param desc A Head descriptor allocated with sah_Alloc_Head_Descriptor(). +* +* @return none +* +*/ +void sah_Free_Head_Descriptor(sah_Head_Desc * desc) +{ + memset(desc, 0x43, sizeof(*desc)); + sah_Free_Big_Block((Mem_Big_Block *) desc); +} + +/*! +******************************************************************************* +* Free link back to free pool +* +* @brief Free link +* +* @param link A link allocated with sah_Alloc_Link(). +* +* @return none +* +*/ +void sah_Free_Link(sah_Link * link) +{ + memset(link, 0x41, sizeof(*link)); + sah_Free_Block((Mem_Block *) link); +} + +/*! +******************************************************************************* +* This function runs through a descriptor chain pointed to by a user-space +* address. It duplicates each descriptor in Kernel space memory and calls +* sah_Copy_Links() to handle any links attached to the descriptors. This +* function cleans-up everything that it created in the case of a failure. +* +* @brief Kernel Descriptor Chain Copier +* +* @param fsl_shw_uco_t The user context to act under +* @param user_head_desc A Head Descriptor pointer from user-space. +* +* @return sah_Head_Desc * - A virtual address of the first descriptor in the +* chain. +* @return NULL - If there was some error. +* +*/ +sah_Head_Desc *sah_Copy_Descriptors(fsl_shw_uco_t * user_ctx, + sah_Head_Desc * user_head_desc) +{ + sah_Desc *curr_desc = NULL; + sah_Desc *prev_desc = NULL; + sah_Desc *next_desc = NULL; + sah_Head_Desc *head_desc = NULL; + sah_Desc *user_desc = NULL; + unsigned long result; + + /* Internal status variable to be used in this function */ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Head_Desc *ret_val = NULL; + + /* This will be set to True when we have finished processing our + * descriptor chain. + */ + int drv_if_done = FALSE; + int is_this_the_head = TRUE; + + do { + /* Allocate memory for this descriptor */ + if (is_this_the_head) { + head_desc = + (sah_Head_Desc *) sah_Alloc_Head_Descriptor(); + +#ifdef DIAG_MEM + sprintf(Diag_msg, + "Alloc_Head_Descriptor returned %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + if (head_desc == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Alloc_Head_Descriptor() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_NO_RESOURCE_S; + } else { + void *virt_addr = head_desc->desc.virt_addr; + dma_addr_t dma_addr = head_desc->desc.dma_addr; + + /* Copy the head descriptor from user-space */ + /* Instead of copying the whole structure, + * unneeded bits at the end are left off. + * The user space version is missing virt/dma addrs, which + * means that the copy will be off for flags... */ + result = copy_from_user(head_desc, + user_head_desc, + (sizeof(*head_desc) - + sizeof(head_desc->desc. + dma_addr) - + sizeof(head_desc->desc. + virt_addr) - + sizeof(head_desc->desc. + original_ptr1) - +/* sizeof(head_desc->desc.original_ptr2) - + sizeof(head_desc->status) - + sizeof(head_desc->error_status) - + sizeof(head_desc->fault_address) - + sizeof(head_desc->current_dar) - + sizeof(head_desc->result) - + sizeof(head_desc->next) - + sizeof(head_desc->prev) - + sizeof(head_desc->user_desc) - +*/ sizeof(head_desc->out1_ptr) - + sizeof(head_desc-> + out2_ptr) - + sizeof(head_desc-> + out_len))); + /* there really isn't a 'next' descriptor at this point, so + * set that pointer to NULL, but remember it for if/when there + * is a next */ + next_desc = head_desc->desc.next; + head_desc->desc.next = NULL; + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + /* when destroying the descriptor, skip these links. + * They've not been copied down, so don't exist */ + head_desc->desc.ptr1 = NULL; + head_desc->desc.ptr2 = NULL; + + } else { + /* The kernel DESC has five more words than user DESC, so + * the missing values are in the middle of the HEAD DESC, + * causing values after the missing ones to be at different + * offsets in kernel and user space. + * + * Patch up the problem by moving field two spots. + * This assumes sizeof(pointer) == sizeof(uint32_t). + * Note that 'user_info' is not needed, so not copied. + */ + head_desc->user_ref = + (uint32_t) head_desc->desc.dma_addr; + head_desc->uco_flags = + (uint32_t) head_desc->desc. + original_ptr1; +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS( + "User flags: %x; User Reference: %x", + head_desc->uco_flags, + head_desc->user_ref); +#endif + /* These values were destroyed by the copy. */ + head_desc->desc.virt_addr = virt_addr; + head_desc->desc.dma_addr = dma_addr; + + /* ensure that the save descriptor chain bit is not set. + * the copy of the user space descriptor chain should + * always be deleted */ + head_desc->uco_flags &= + ~FSL_UCO_SAVE_DESC_CHAIN; + + curr_desc = (sah_Desc *) head_desc; + is_this_the_head = FALSE; + } + } + } else { /* not head */ + curr_desc = sah_Alloc_Descriptor(); +#ifdef DIAG_MEM + LOG_KDIAG_ARGS("Alloc_Descriptor returned %p\n", + curr_desc); +#endif + if (curr_desc == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Descriptor() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_NO_RESOURCE_S; + } else { + /* need to update the previous descriptors' next field to + * pointer to the current descriptor. */ + prev_desc->original_next = curr_desc; + prev_desc->next = + (sah_Desc *) curr_desc->dma_addr; + + /* Copy the current descriptor from user-space */ + /* The virtual address and DMA address part of the sah_Desc + * struct are not copied to user space */ + result = copy_from_user(curr_desc, user_desc, (sizeof(sah_Desc) - sizeof(dma_addr_t) - /* dma_addr */ + sizeof(uint32_t) - /* virt_addr */ + sizeof(void *) - /* original_ptr1 */ + sizeof(void *) - /* original_ptr2 */ + sizeof(sah_Desc **))); /* original_next */ + /* there really isn't a 'next' descriptor at this point, so + * set that pointer to NULL, but remember it for if/when there + * is a next */ + next_desc = curr_desc->next; + curr_desc->next = NULL; + curr_desc->original_next = NULL; + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + /* when destroying the descriptor chain, skip these links. + * They've not been copied down, so don't exist */ + curr_desc->ptr1 = NULL; + curr_desc->ptr2 = NULL; + } + } + } /* end if (is_this_the_head) */ + + if (status == FSL_RETURN_OK_S) { + if (!(curr_desc->header & SAH_LLO_BIT)) { + /* One or both pointer fields being NULL is a valid + * configuration. */ + if (curr_desc->ptr1 == NULL) { + curr_desc->original_ptr1 = NULL; + } else { + /* pointer fields point to sah_Link structures */ + curr_desc->original_ptr1 = + sah_Copy_Links(user_ctx, curr_desc->ptr1); + if (curr_desc->original_ptr1 == NULL) { + /* This descriptor and any links created successfully + * are cleaned-up at the bottom of this function. */ + drv_if_done = TRUE; + status = + FSL_RETURN_INTERNAL_ERROR_S; + /* mark that link 2 doesn't exist */ + curr_desc->ptr2 = NULL; +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Copy_Links() failed."); +#endif + } else { + curr_desc->ptr1 = (void *) + ((sah_Link *) curr_desc-> + original_ptr1)->dma_addr; + } + } + + if (status == FSL_RETURN_OK_S) { + if (curr_desc->ptr2 == NULL) { + curr_desc->original_ptr2 = NULL; + } else { + /* pointer fields point to sah_Link structures */ + curr_desc->original_ptr2 = + sah_Copy_Links(user_ctx, curr_desc->ptr2); + if (curr_desc->original_ptr2 == + NULL) { + /* This descriptor and any links created + * successfully are cleaned-up at the bottom of + * this function. */ + drv_if_done = TRUE; + status = + FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("sah_Copy_Links() failed."); +#endif + } else { + curr_desc->ptr2 = + (void + *)(((sah_Link *) + curr_desc-> + original_ptr2) + ->dma_addr); + } + } + } + } else { + /* Pointer fields point directly to user buffers. We don't + * support this mode. + */ +#ifdef DIAG_DRV_IF + LOG_KDIAG + ("The LLO bit in the Descriptor Header field was " + "set. This an invalid configuration."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + + if (status == FSL_RETURN_OK_S) { + user_desc = next_desc; + prev_desc = curr_desc; + if (user_desc == NULL) { + /* We have reached the end our our descriptor chain */ + drv_if_done = TRUE; + } + } + + } while (drv_if_done == FALSE); + + if (status != FSL_RETURN_OK_S) { + /* Clean-up if failed */ + if (head_desc != NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("Error! Calling destroy descriptors!\n"); +#endif + sah_Destroy_Descriptors(head_desc); + } + ret_val = NULL; + } else { + /* Flush the caches */ +#ifndef FLUSH_SPECIFIC_DATA_ONLY + os_flush_cache_all(); +#endif + + /* Success. Return the DMA'able head descriptor. */ + ret_val = head_desc; + + } + + return ret_val; +} /* sah_Copy_Descriptors() */ + +/*! +******************************************************************************* +* This function runs through a sah_Link chain pointed to by a kernel-space +* address. It computes the physical address for each pointer, and converts +* the chain to use these physical addresses. +* +****** +* This function needs to return some indication that the chain could not be +* converted. It also needs to back out any conversion already taken place on +* this chain of links. +* +* Then, of course, sah_Physicalise_Descriptors() will need to recognize that +* an error occured, and then be able to back out any physicalization of the +* chain which had taken place up to that point! +****** +* +* @brief Convert kernel Link chain +* +* @param first_link A sah_Link pointer from kernel space; must not be +* NULL, so error case can be distinguished. +* +* @return sah_Link * A dma'able address of the first descriptor in the +* chain. +* @return NULL If Link chain could not be physicalised, i.e. ERROR +* +*/ +sah_Link *sah_Physicalise_Links(sah_Link * first_link) +{ + sah_Link *link = first_link; + + while (link != NULL) { +#ifdef DO_DBG + sah_Dump_Words("Link", (unsigned *)link, link->dma_addr, 3); +#endif + link->vm_info = NULL; + + /* need to retrieve stored key? */ + if (link->flags & SAH_STORED_KEY_INFO) { + uint32_t max_len = 0; /* max slot length */ + fsl_shw_return_t ret_status; + + /* get length and physical address of stored key */ + ret_status = system_keystore_get_slot_info(link->ownerid, link->slot, (uint32_t *) & link->data, /* RED key address */ + &max_len); + if ((ret_status != FSL_RETURN_OK_S) || (link->len > max_len)) { + /* trying to illegally/incorrectly access a key. Cause the + * error status register to show a Link Length Error by + * putting a zero in the links length. */ + link->len = 0; /* Cause error. Somebody is up to no good. */ + } + } else if (link->flags & SAH_IN_USER_KEYSTORE) { + +#ifdef FSL_HAVE_SCC2 + /* The data field points to the virtual address of the key. Convert + * this to a physical address by modifying the address based + * on where the secure memory was mapped to the kernel. Note: In + * kernel mode, no attempt is made to track or control who owns what + * memory partition. + */ + link->data = (uint8_t *) scc_virt_to_phys(link->data); + + /* Do bounds checking to ensure that the user is not overstepping + * the bounds of their partition. This is a simple implementation + * that assumes the user only owns one partition. It only checks + * to see if the address of the last byte of data steps over a + * page boundary. + */ + +#ifdef DO_DBG + LOG_KDIAG_ARGS("start page: %08x, end page: %08x" + "first addr: %p, last addr: %p, len; %i", + ((uint32_t) (link->data) >> PAGE_SHIFT), + (((uint32_t) link->data + + link->len) >> PAGE_SHIFT), link->data, + link->data + link->len, link->len); +#endif + + if ((((uint32_t) link->data + + link->len) >> PAGE_SHIFT) != + ((uint32_t) link->data >> PAGE_SHIFT)) { + link->len = 0; /* Cause error. Somebody is up to no good. */ + } +#else /* FSL_HAVE_SCC2 */ + + /* User keystores are not valid on non-SCC2 platforms */ + link->len = 0; /* Cause error. Somebody is up to no good. */ + +#endif /* FSL_HAVE_SCC2 */ + + } else { + if (!(link->flags & SAH_PREPHYS_DATA)) { + link->original_data = link->data; + + /* All pointers are virtual right now */ + link->data = (void *)os_pa(link->data); +#ifdef DO_DBG + os_printk("%sput: %p (%d)\n", + (link-> + flags & SAH_OUTPUT_LINK) ? "out" : + "in", link->data, link->len); +#endif + + if (link->flags & SAH_OUTPUT_LINK) { + /* clean and invalidate */ + os_cache_flush_range(link-> + original_data, + link->len); + } else { + os_cache_clean_range(link->original_data, + link->len); + } + } /* not prephys */ + } /* else not key reference */ + +#if defined(NO_OUTPUT_1K_CROSSING) || defined(NO_1K_CROSSING) + if ( +#ifdef NO_OUTPUT_1K_CROSSING + /* Insert extra link if 1k boundary on output pointer + * crossed not at an 8-word boundary */ + (link->flags & SAH_OUTPUT_LINK) && + (((uint32_t) link->data % 32) != 0) && +#endif + ((((uint32_t) link->data & 1023) + link->len) > + 1024)) { + uint32_t full_length = link->len; + sah_Link *new_link = sah_Alloc_Link(); + link->len = 1024 - ((uint32_t) link->data % 1024); + new_link->len = full_length - link->len; + new_link->data = link->data + link->len; + new_link->original_data = + link->original_data + link->len; + new_link->flags = link->flags & ~(SAH_OWNS_LINK_DATA); + new_link->flags |= SAH_LINK_INSERTED_LINK; + new_link->next = link->next; + + link->next = (sah_Link *) new_link->dma_addr; + link->original_next = new_link; + link = new_link; + } +#endif /* ALLOW_OUTPUT_1K_CROSSING */ + + link->original_next = link->next; + if (link->next != NULL) { + link->next = (sah_Link *) link->next->dma_addr; + } +#ifdef DO_DBG + sah_Dump_Words("Link", link, link->dma_addr, 3); +#endif + + link = link->original_next; + } + + return (sah_Link *) first_link->dma_addr; +} /* sah_Physicalise_Links */ + +/*! + * Run through descriptors and links created by KM-API and set the + * dma addresses and 'do not free' flags. + * + * @param first_desc KERNEL VIRTUAL address of first descriptor in chain. + * + * Warning! This ONLY works without LLO flags in headers!!! + * + * @return Virtual address of @a first_desc. + * @return NULL if Descriptor Chain could not be physicalised + */ +sah_Head_Desc *sah_Physicalise_Descriptors(sah_Head_Desc * first_desc) +{ + sah_Desc *desc = &first_desc->desc; + + if (!(first_desc->uco_flags & FSL_UCO_CHAIN_PREPHYSICALIZED)) { + while (desc != NULL) { + sah_Desc *next_desc; + +#ifdef DO_DBG + + sah_Dump_Words("Desc", (unsigned *)desc, desc->dma_addr, 6); +#endif + + desc->original_ptr1 = desc->ptr1; + if (desc->ptr1 != NULL) { + if ((desc->ptr1 = + sah_Physicalise_Links(desc->ptr1)) == + NULL) { + /* Clean up ... */ + sah_DePhysicalise_Descriptors + (first_desc); + first_desc = NULL; + break; + } + } + desc->original_ptr2 = desc->ptr2; + if (desc->ptr2 != NULL) { + if ((desc->ptr2 = + sah_Physicalise_Links(desc->ptr2)) == + NULL) { + /* Clean up ... */ + sah_DePhysicalise_Descriptors + (first_desc); + first_desc = NULL; + break; + } + } + + desc->original_next = desc->next; + next_desc = desc->next; /* save for bottom of while loop */ + if (desc->next != NULL) { + desc->next = (sah_Desc *) desc->next->dma_addr; + } + + desc = next_desc; + } + } + /* not prephysicalized */ +#ifdef DO_DBG + os_printk("Physicalise finished\n"); +#endif + + return first_desc; +} /* sah_Physicalise_Descriptors() */ + +/*! +******************************************************************************* +* This function runs through a sah_Link chain pointed to by a physical address. +* It computes the virtual address for each pointer +* +* @brief Convert physical Link chain +* +* @param first_link A kernel address of a sah_Link +* +* @return sah_Link * A kernal address for the link chain of @c first_link +* @return NULL If there was some error. +* +* @post All links will be chained together by original virtual addresses, +* data pointers will point to virtual addresses. Appropriate cache +* lines will be flushed, memory unwired, etc. +*/ +sah_Link *sah_DePhysicalise_Links(sah_Link * first_link) +{ + sah_Link *link = first_link; + sah_Link *prev_link = NULL; + + /* Loop on virtual link pointer */ + while (link != NULL) { + +#ifdef DO_DBG + sah_Dump_Words("Link", (unsigned *)link, link->dma_addr, 3); +#endif + + /* if this references stored keys, don't want to dephysicalize them */ + if (!(link->flags & SAH_STORED_KEY_INFO) + && !(link->flags & SAH_PREPHYS_DATA) + && !(link->flags & SAH_IN_USER_KEYSTORE)) { + + /* */ + if (link->flags & SAH_OUTPUT_LINK) { + os_cache_inv_range(link->original_data, + link->len); + } + + /* determine if there is a page in user space associated with this + * link */ + if (link->vm_info != NULL) { + /* check that this isn't reserved and contains output */ + if (!PageReserved(link->vm_info) && + (link->flags & SAH_OUTPUT_LINK)) { + + /* Mark to force page eventually to backing store */ + SetPageDirty(link->vm_info); + } + + /* Untie this page from physical memory */ + page_cache_release(link->vm_info); + } else { + /* kernel-mode data */ +#ifdef DO_DBG + os_printk("%sput: %p (%d)\n", + (link-> + flags & SAH_OUTPUT_LINK) ? "out" : + "in", link->original_data, link->len); +#endif + } + link->data = link->original_data; + } +#ifndef ALLOW_OUTPUT_1K_CROSSING + if (link->flags & SAH_LINK_INSERTED_LINK) { + /* Reconsolidate data by merging this link with previous */ + prev_link->len += link->len; + prev_link->next = link->next; + prev_link->original_next = link->original_next; + sah_Free_Link(link); + link = prev_link; + + } +#endif + + if (link->next != NULL) { + link->next = link->original_next; + } + prev_link = link; + link = link->next; + } + + return first_link; +} /* sah_DePhysicalise_Links() */ + +/*! + * Run through descriptors and links that have been Physicalised + * (sah_Physicalise_Descriptors function) and set the dma addresses back + * to KM virtual addresses + * + * @param first_desc Kernel virtual address of first descriptor in chain. + * + * Warning! This ONLY works without LLO flags in headers!!! + */ +sah_Head_Desc *sah_DePhysicalise_Descriptors(sah_Head_Desc * first_desc) +{ + sah_Desc *desc = &first_desc->desc; + + if (!(first_desc->uco_flags & FSL_UCO_CHAIN_PREPHYSICALIZED)) { + while (desc != NULL) { +#ifdef DO_DBG + sah_Dump_Words("Desc", (unsigned *)desc, desc->dma_addr, 6); +#endif + + if (desc->ptr1 != NULL) { + desc->ptr1 = + sah_DePhysicalise_Links(desc-> + original_ptr1); + } + if (desc->ptr2 != NULL) { + desc->ptr2 = + sah_DePhysicalise_Links(desc-> + original_ptr2); + } + if (desc->next != NULL) { + desc->next = desc->original_next; + } + desc = desc->next; + } + } + /* not prephysicalized */ + return first_desc; +} /* sah_DePhysicalise_Descriptors() */ + +/*! +******************************************************************************* +* This walks through a SAHARA descriptor chain and free()'s everything +* that is not NULL. Finally it also unmaps all of the physical memory and +* frees the kiobuf_list Queue. +* +* @brief Kernel Descriptor Chain Destructor +* +* @param head_desc A Descriptor pointer from kernel-space. +* +* @return void +* +*/ +void sah_Free_Chained_Descriptors(sah_Head_Desc * head_desc) +{ + sah_Desc *desc = NULL; + sah_Desc *next_desc = NULL; + int this_is_head = 1; + + desc = &head_desc->desc; + + while (desc != NULL) { + + sah_Free_Chained_Links(desc->ptr1); + sah_Free_Chained_Links(desc->ptr2); + + /* Get a bus pointer to the next Descriptor */ + next_desc = desc->next; + + /* Zero the header and Length fields for security reasons. */ + desc->header = 0; + desc->len1 = 0; + desc->len2 = 0; + + if (this_is_head) { + sah_Free_Head_Descriptor(head_desc); + this_is_head = 0; +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Head_Descriptor: %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + } else { + /* free this descriptor */ + sah_Free_Descriptor(desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Descriptor: %p\n", desc); + LOG_KDIAG(Diag_msg); +#endif + } + + /* Look at the next Descriptor */ + desc = next_desc; + } +} /* sah_Free_Chained_Descriptors() */ + +/*! +******************************************************************************* +* This walks through a SAHARA link chain and frees everything that is +* not NULL, excluding user-space buffers. +* +* @brief Kernel Link Chain Destructor +* +* @param link A Link pointer from kernel-space. This is in bus address +* space. +* +* @return void +* +*/ +void sah_Free_Chained_Links(sah_Link * link) +{ + sah_Link *next_link = NULL; + + while (link != NULL) { + /* Get a bus pointer to the next Link */ + next_link = link->next; + + /* Zero some fields for security reasons. */ + link->data = NULL; + link->len = 0; + link->ownerid = 0; + + /* Free this Link */ +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Link: %p(->%p)\n", link, link->next); + LOG_KDIAG(Diag_msg); +#endif + sah_Free_Link(link); + + /* Move on to the next Link */ + link = next_link; + } +} + +/*! +******************************************************************************* +* This function runs through a link chain pointed to by a user-space +* address. It makes a temporary kernel-space copy of each link in the +* chain and calls sah_Make_Links() to create a set of kernel-side links +* to replace it. +* +* @brief Kernel Link Chain Copier +* +* @param ptr A link pointer from user-space. +* +* @return sah_Link * - The virtual address of the first link in the +* chain. +* @return NULL - If there was some error. +*/ +sah_Link *sah_Copy_Links(fsl_shw_uco_t * user_ctx, sah_Link * ptr) +{ + sah_Link *head_link = NULL; + sah_Link *new_head_link = NULL; + sah_Link *new_tail_link = NULL; + sah_Link *prev_tail_link = NULL; + sah_Link *user_link = ptr; + sah_Link link_copy; + int link_data_length = 0; + + /* Internal status variable to be used in this function */ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *ret_val = NULL; + + /* This will be set to True when we have finished processing our + * link chain. */ + int drv_if_done = FALSE; + int is_this_the_head = TRUE; + int result; + + /* transfer all links, on this link chain, from user space */ + while (drv_if_done == FALSE) { + /* Copy the current link from user-space. The virtual address, DMA + * address, and vm_info fields of the sah_Link struct are not part + * of the user-space structure. They must be the last elements and + * should not be copied. */ + result = copy_from_user(&link_copy, + user_link, (sizeof(sah_Link) - +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)) + sizeof(struct page *) - /* vm_info */ +#endif + sizeof(dma_addr_t) - /* dma_addr */ + sizeof(uint32_t) - /* virt_addr */ + sizeof(uint8_t *) - /* original_data */ + sizeof(sah_Link *))); /* original_next */ + + if (result != 0) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("copy_from_user() failed."); +#endif + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; + } + + if (status == FSL_RETURN_OK_S) { + /* This will create new links which can be used to replace tmp_link + * in the chain. This will return a new head and tail link. */ + link_data_length = link_data_length + link_copy.len; + new_head_link = + sah_Make_Links(user_ctx, &link_copy, &new_tail_link); + + if (new_head_link == NULL) { + /* If we ran out of memory or a user pointer was invalid */ + drv_if_done = TRUE; + status = FSL_RETURN_INTERNAL_ERROR_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Make_Links() failed."); +#endif + } else { + if (is_this_the_head == TRUE) { + /* Keep a reference to the head link */ + head_link = new_head_link; + is_this_the_head = FALSE; + } else { + /* Need to update the previous links' next field to point + * to the current link. */ + prev_tail_link->next = + (void *)new_head_link->dma_addr; + prev_tail_link->original_next = + new_head_link; + } + } + } + + if (status == FSL_RETURN_OK_S) { + /* Get to the next link in the chain. */ + user_link = link_copy.next; + prev_tail_link = new_tail_link; + + /* Check if the end of the link chain was reached (TRUE) or if + * there is another linked to this one (FALSE) */ + drv_if_done = (user_link == NULL) ? TRUE : FALSE; + } + } /* end while */ + + if (status != FSL_RETURN_OK_S) { + ret_val = NULL; + /* There could be clean-up to do here because we may have made some + * successful iterations through the while loop and as a result, the + * links created by sah_Make_Links() need to be destroyed. + */ + if (head_link != NULL) { + /* Failed somewhere in the while loop and need to clean-up. */ + sah_Destroy_Links(head_link); + } + } else { + /* Success. Return the head link. */ + ret_val = head_link; + } + + return ret_val; +} /* sah_Copy_Links() */ + +/*! +******************************************************************************* +* This function takes an input link pointed to by a user-space address +* and returns a chain of links that span the physical pages pointed +* to by the input link. +* +* @brief Kernel Link Chain Constructor +* +* @param ptr A link pointer from user-space. +* @param tail The address of a link pointer. This is used to return +* the tail link created by this function. +* +* @return sah_Link * - A virtual address of the first link in the +* chain. +* @return NULL - If there was some error. +* +*/ +sah_Link *sah_Make_Links(fsl_shw_uco_t * user_ctx, + sah_Link * ptr, sah_Link ** tail) +{ + int result = -1; + int page_index = 0; + fsl_shw_return_t status = FSL_RETURN_OK_S; + int is_this_the_head = TRUE; + void *buffer_start = NULL; + sah_Link *link = NULL; + sah_Link *prev_link = NULL; + sah_Link *head_link = NULL; + sah_Link *ret_val = NULL; + int buffer_length = 0; + struct page **local_pages = NULL; + int nr_pages = 0; + int write = (sah_Link_Get_Flags(ptr) & SAH_OUTPUT_LINK) ? WRITE : READ; + + /* need to retrieve stored key? */ + if (ptr->flags & SAH_STORED_KEY_INFO) { + fsl_shw_return_t ret_status; + + /* allocate space for this link */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif + + if (link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed!"); +#endif + return link; + } else { + uint32_t max_len = 0; /* max slot length */ + + /* get length and physical address of stored key */ + ret_status = system_keystore_get_slot_info(ptr->ownerid, ptr->slot, (uint32_t *) & link->data, /* RED key address */ + &max_len); +#ifdef DIAG_DRV_IF + LOG_KDIAG_ARGS + ("ret_status==SCC_RET_OK? %s. slot: %i. data: %p" + ". len: %i, key length: %i", + (ret_status == FSL_RETURN_OK_S ? "yes" : "no"), + ptr->slot, link->data, max_len, ptr->len); +#endif + + if ((ret_status == FSL_RETURN_OK_S) && (ptr->len <= max_len)) { + /* finish populating the link */ + link->len = ptr->len; + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + *tail = link; + } else { +#ifdef DIAG_DRV_IF + if (ret_status == FSL_RETURN_OK_S) { + LOG_KDIAG + ("SCC sah_Link key slot reference is too long"); + } else { + LOG_KDIAG + ("SCC sah_Link slot slot reference is invalid"); + } +#endif + sah_Free_Link(link); + status = FSL_RETURN_INTERNAL_ERROR_S; + return NULL; + } + return link; + } + } else if (ptr->flags & SAH_IN_USER_KEYSTORE) { + +#ifdef FSL_HAVE_SCC2 + + void *kernel_base; + + /* allocate space for this link */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif /* DIAG_MEM */ + + if (link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed!"); +#endif + return link; + } else { + /* link->data points to the virtual address of the key data, however + * this memory does not need to be locked down. + */ + kernel_base = lookup_user_partition(user_ctx, + (uint32_t) ptr-> + data & PAGE_MASK); + + link->data = (uint8_t *) scc_virt_to_phys(kernel_base + + ((unsigned + long)ptr-> + data & + ~PAGE_MASK)); + + /* Do bounds checking to ensure that the user is not overstepping + * the bounds of their partition. This is a simple implementation + * that assumes the user only owns one partition. It only checks + * to see if the address of the last byte of data steps over a + * page boundary. + */ + if ((kernel_base != NULL) && + ((((uint32_t) link->data + + link->len) >> PAGE_SHIFT) == + ((uint32_t) link->data >> PAGE_SHIFT))) { + /* finish populating the link */ + link->len = ptr->len; + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + *tail = link; + } else { +#ifdef DIAG_DRV_IF + if (kernel_base != NULL) { + LOG_KDIAG + ("SCC sah_Link key slot reference is too long"); + } else { + LOG_KDIAG + ("SCC sah_Link slot slot reference is invalid"); + } +#endif + sah_Free_Link(link); + status = FSL_RETURN_INTERNAL_ERROR_S; + return NULL; + } + return link; + } + +#else /* FSL_HAVE_SCC2 */ + + return NULL; + +#endif /* FSL_HAVE_SCC2 */ + } + + if (ptr->data == NULL) { + /* The user buffer must not be NULL because map_user_kiobuf() cannot + * handle NULL pointer input. + */ + status = FSL_RETURN_BAD_DATA_LENGTH_S; +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Link data pointer is NULL."); +#endif + } + + if (status == FSL_RETURN_OK_S) { + unsigned long start_page = (unsigned long)ptr->data & PAGE_MASK; + + /* determine number of pages being used for this link */ + nr_pages = (((unsigned long)(ptr->data) & ~PAGE_MASK) + + ptr->len + ~PAGE_MASK) >> PAGE_SHIFT; + + /* ptr contains all the 'user space' information, add the pages + * to it also just so everything is in one place */ + local_pages = + kmalloc(nr_pages * sizeof(struct page *), GFP_KERNEL); + + if (local_pages == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; /* no memory! */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("kmalloc() failed."); +#endif + } else { + /* get the actual pages being used in 'user space' */ + + down_read(¤t->mm->mmap_sem); + result = get_user_pages(current, current->mm, + start_page, nr_pages, + write, 0 /* noforce */ , + local_pages, NULL); + up_read(¤t->mm->mmap_sem); + + if (result < nr_pages) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("get_user_pages() failed."); +#endif + if (result > 0) { + for (page_index = 0; + page_index < result; + page_index++) { + page_cache_release(local_pages + [page_index]); + } + } + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + } + + /* Now we can walk through the list of pages in the buffer */ + if (status == FSL_RETURN_OK_S) { + +#if defined(FLUSH_SPECIFIC_DATA_ONLY) && !defined(HAS_L2_CACHE) + /* + * Now that pages are wired, clear user data from cache lines. When + * there is just an L1 cache, clean based on user virtual for ARM. + */ + if (write == WRITE) { + os_cache_flush_range(ptr->data, ptr->len); + } else { + os_cache_clean_range(ptr->data, ptr->len); + } +#endif + + for (page_index = 0; page_index < nr_pages; page_index++) { + /* Allocate a new link structure */ + link = sah_Alloc_Link(); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Alloc_Link returned %p/%p\n", link, + (void *)link->dma_addr); + LOG_KDIAG(Diag_msg); +#endif + if (link == NULL) { +#ifdef DIAG_DRV_IF + LOG_KDIAG("sah_Alloc_Link() failed."); +#endif + status = FSL_RETURN_NO_RESOURCE_S; + + /* need to free the rest of the pages. Destroy_Links will take + * care of the ones already assigned to a link */ + for (; page_index < nr_pages; page_index++) { + page_cache_release(local_pages + [page_index]); + } + break; /* exit 'for page_index' loop */ + } + + if (status == FSL_RETURN_OK_S) { + if (is_this_the_head == TRUE) { + /* keep a reference to the head link */ + head_link = link; + /* remember that we have seen the head link */ + is_this_the_head = FALSE; + } else { + /* If this is not the head link then set the previous + * link's next pointer to point to this link */ + prev_link->original_next = link; + prev_link->next = + (sah_Link *) link->dma_addr; + } + + buffer_start = + page_address(local_pages[page_index]); + + if (page_index == 0) { + /* If this is the first page, there might be an + * offset. We need to increment the address by this offset + * so we don't just get the start of the page. + */ + buffer_start += + (unsigned long) + sah_Link_Get_Data(ptr) + & ~PAGE_MASK; + buffer_length = PAGE_SIZE + - + ((unsigned long) + sah_Link_Get_Data(ptr) + & ~PAGE_MASK); + } else { + buffer_length = PAGE_SIZE; + } + + if (page_index == nr_pages - 1) { + /* if this is the last page, we need to adjust + * the buffer_length to account for the last page being + * partially used. + */ + buffer_length -= + nr_pages * PAGE_SIZE - + sah_Link_Get_Len(ptr) - + ((unsigned long) + sah_Link_Get_Data(ptr) & + ~PAGE_MASK); + } +#if defined(FLUSH_SPECIFIC_DATA_ONLY) && defined(HAS_L2_CACHE) + /* + * When there is an L2 cache, clean based on kernel + * virtual.. + */ + if (write == WRITE) { + os_cache_flush_range(buffer_start, + buffer_length); + } else { + os_cache_clean_range(buffer_start, + buffer_length); + } +#endif + + /* Fill in link information */ + link->len = buffer_length; +#if !defined(HAS_L2_CACHE) + /* use original virtual */ + link->original_data = ptr->data; +#else + /* use kernel virtual */ + link->original_data = buffer_start; +#endif + link->data = (void *)os_pa(buffer_start); + link->flags = ptr->flags & ~SAH_PREPHYS_DATA; + link->vm_info = local_pages[page_index]; + prev_link = link; + +#if defined(NO_OUTPUT_1K_CROSSING) || defined(NO_1K_CROSSING) + if ( +#ifdef NO_OUTPUT_1K_CROSSING + /* Insert extra link if 1k boundary on output pointer + * crossed not at an 8-word boundary */ + (link->flags & SAH_OUTPUT_LINK) && + (((uint32_t) buffer_start % 32) != 0) + && +#endif + ((((uint32_t) buffer_start & 1023) + + buffer_length) > 1024)) { + + /* Shorten current link to 1k boundary */ + link->len = + 1024 - + ((uint32_t) buffer_start % 1024); + + /* Get new link to follow it */ + link = sah_Alloc_Link(); + prev_link->len = + 1024 - + ((uint32_t) buffer_start % 1024); + prev_link->original_next = link; + prev_link->next = + (sah_Link *) link->dma_addr; + buffer_length -= prev_link->len; + buffer_start += prev_link->len; + +#if !defined(HAS_L2_CACHE) + /* use original virtual */ + link->original_data = ptr->data; +#else + /* use kernel virtual */ + link->original_data = buffer_start; +#endif + link->data = + (void *)os_pa(buffer_start); + link->vm_info = prev_link->vm_info; + prev_link->vm_info = NULL; /* delay release */ + link->flags = ptr->flags; + link->len = buffer_length; + prev_link = link; + } /* while link would cross 1K boundary */ +#endif /* 1K_CROSSING */ + } + } /* for each page */ + } + + if (local_pages != NULL) { + kfree(local_pages); + } + + if (status != FSL_RETURN_OK_S) { + /* De-allocated any links created, this routine first looks if + * head_link is NULL */ + sah_Destroy_Links(head_link); + + /* Clean-up of the KIOBUF will occur in the * sah_Copy_Descriptors() + * function. + * Clean-up of the Queue entry must occur in the function called + * sah_Copy_Descriptors(). + */ + } else { + + /* Success. Return the head link. */ + ret_val = head_link; + link->original_next = NULL; + /* return the tail link as well */ + *tail = link; + } + + return ret_val; +} /* sah_Make_Links() */ + +/*! +******************************************************************************* +* This walks through a SAHARA descriptor chain and frees everything +* that is not NULL. Finally it also unmaps all of the physical memory and +* frees the kiobuf_list Queue. +* +* @brief Kernel Descriptor Chain Destructor +* +* @param desc A Descriptor pointer from kernel-space. This should be +* in bus address space. +* +* @return void +* +*/ +void sah_Destroy_Descriptors(sah_Head_Desc * head_desc) +{ + sah_Desc *this_desc = (sah_Desc *) head_desc; + sah_Desc *next_desc = NULL; + int this_is_head = 1; + + /* + * Flush the D-cache. This flush is here because the hardware has finished + * processing this descriptor and probably has changed the contents of + * some linked user buffers as a result. This flush will enable + * user-space applications to see the correct data rather than the + * out-of-date cached version. + */ +#ifndef FLUSH_SPECIFIC_DATA_ONLY + os_flush_cache_all(); +#endif + + head_desc = (sah_Head_Desc *) this_desc->virt_addr; + + while (this_desc != NULL) { + if (this_desc->ptr1 != NULL) { + sah_Destroy_Links(this_desc->original_ptr1 + ? this_desc-> + original_ptr1 : this_desc->ptr1); + } + if (this_desc->ptr2 != NULL) { + sah_Destroy_Links(this_desc->original_ptr2 + ? this_desc-> + original_ptr2 : this_desc->ptr2); + } + + /* Get a bus pointer to the next Descriptor */ + next_desc = (this_desc->original_next + ? this_desc->original_next : this_desc->next); + + /* Zero the header and Length fields for security reasons. */ + this_desc->header = 0; + this_desc->len1 = 0; + this_desc->len2 = 0; + + if (this_is_head) { + sah_Free_Head_Descriptor(head_desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Head_Descriptor: %p\n", + head_desc); + LOG_KDIAG(Diag_msg); +#endif + this_is_head = 0; + } else { + /* free this descriptor */ + sah_Free_Descriptor(this_desc); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Descriptor: %p\n", this_desc); + LOG_KDIAG(Diag_msg); +#endif + } + + /* Set up for next round. */ + this_desc = (sah_Desc *) next_desc; + } +} + +/*! +******************************************************************************* +* This walks through a SAHARA link chain and frees everything that is +* not NULL excluding user-space buffers. +* +* @brief Kernel Link Chain Destructor +* +* @param link A Link pointer from kernel-space. +* +* @return void +* +*/ +void sah_Destroy_Links(sah_Link * link) +{ + sah_Link *this_link = link; + sah_Link *next_link = NULL; + + while (this_link != NULL) { + + /* if this link indicates an associated page, process it */ + if (this_link->vm_info != NULL) { + /* since this function is only called from the routine that + * creates a kernel copy of the user space descriptor chain, + * there are no pages to dirty. All that is needed is to release + * the page from cache */ + page_cache_release(this_link->vm_info); + } + + /* Get a bus pointer to the next Link */ + next_link = (this_link->original_next + ? this_link->original_next : this_link->next); + + /* Zero the Pointer and Length fields for security reasons. */ + this_link->data = NULL; + this_link->len = 0; + + /* Free this Link */ + sah_Free_Link(this_link); +#ifdef DIAG_MEM + sprintf(Diag_msg, "Free_Link: %p\n", this_link); + LOG_KDIAG(Diag_msg); +#endif + + /* Look at the next Link */ + this_link = next_link; + } +} + +/*! +******************************************************************************* +* @brief Initialize memory manager/mapper. +* +* In 2.4, this function also allocates a kiovec to be used when mapping user +* data to kernel space +* +* @return 0 for success, OS error code on failure +* +*/ +int sah_Init_Mem_Map(void) +{ + int ret = OS_ERROR_FAIL_S; + + mem_lock = os_lock_alloc_init(); + + /* + * If one of these fails, change the calculation in the #define earlier in + * the file to be the other one. + */ + if (sizeof(sah_Link) > MEM_BLOCK_SIZE) { + os_printk("Sahara Driver: sah_Link structure is too large\n"); + } else if (sizeof(sah_Desc) > MEM_BLOCK_SIZE) { + os_printk("Sahara Driver: sah_Desc structure is too large\n"); + } else { + ret = OS_ERROR_OK_S; + } + +#ifndef SELF_MANAGED_POOL + + big_dma_pool = dma_pool_create("sah_big_blocks", NULL, + sizeof(Mem_Big_Block), sizeof(uint32_t), + PAGE_SIZE); + small_dma_pool = dma_pool_create("sah_small_blocks", NULL, + sizeof(Mem_Block), sizeof(uint32_t), + PAGE_SIZE); +#else + +#endif + return ret; +} + +/*! +******************************************************************************* +* @brief Clean up memory manager/mapper. +* +* In 2.4, this function also frees the kiovec used when mapping user data to +* kernel space. +* +* @return none +* +*/ +void sah_Stop_Mem_Map(void) +{ + os_lock_deallocate(mem_lock); + +#ifndef SELF_MANAGED_POOL + if (big_dma_pool != NULL) { + dma_pool_destroy(big_dma_pool); + } + if (small_dma_pool != NULL) { + dma_pool_destroy(small_dma_pool); + } +#endif +} + +/*! +******************************************************************************* +* Allocate Head descriptor from free pool. +* +* @brief Allocate Head descriptor +* +* @return sah_Head_Desc Free descriptor, NULL if no free descriptors available. +* +*/ +sah_Head_Desc *sah_Alloc_Head_Descriptor(void) +{ + Mem_Big_Block *block; + sah_Head_Desc *desc; + + block = sah_Alloc_Big_Block(); + if (block != NULL) { + /* initialize everything */ + desc = (sah_Head_Desc *) block->data; + + desc->desc.virt_addr = (sah_Desc *) desc; + desc->desc.dma_addr = block->dma_addr; + desc->desc.original_ptr1 = NULL; + desc->desc.original_ptr2 = NULL; + desc->desc.original_next = NULL; + + desc->desc.ptr1 = NULL; + desc->desc.ptr2 = NULL; + desc->desc.next = NULL; + } else { + desc = NULL; + } + + return desc; +} + +/*! +******************************************************************************* +* Allocate descriptor from free pool. +* +* @brief Allocate descriptor +* +* @return sah_Desc Free descriptor, NULL if no free descriptors available. +* +*/ +sah_Desc *sah_Alloc_Descriptor(void) +{ + Mem_Block *block; + sah_Desc *desc; + + block = sah_Alloc_Block(); + if (block != NULL) { + /* initialize everything */ + desc = (sah_Desc *) block->data; + + desc->virt_addr = desc; + desc->dma_addr = block->dma_addr; + desc->original_ptr1 = NULL; + desc->original_ptr2 = NULL; + desc->original_next = NULL; + + desc->ptr1 = NULL; + desc->ptr2 = NULL; + desc->next = NULL; + } else { + desc = NULL; + } + + return (desc); +} + +/*! +******************************************************************************* +* Allocate link from free pool. +* +* @brief Allocate link +* +* @return sah_Link Free link, NULL if no free links available. +* +*/ +sah_Link *sah_Alloc_Link(void) +{ + Mem_Block *block; + sah_Link *link; + + block = sah_Alloc_Block(); + if (block != NULL) { + /* initialize everything */ + link = (sah_Link *) block->data; + + link->virt_addr = link; + link->original_next = NULL; + link->original_data = NULL; + /* information found in allocated block */ + link->dma_addr = block->dma_addr; + + /* Sahara link fields */ + link->len = 0; + link->data = NULL; + link->next = NULL; + + /* driver required fields */ + link->flags = 0; + link->vm_info = NULL; + } else { + link = NULL; + } + + return link; +} + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Add a new page to end of block free pool. This will allocate one page and +* fill the pool with entries, appending to the end. +* +* @brief Add page of blocks to block free pool. +* +* @pre This function must be called with the #mem_lock held. +* +* @param big 0 - make blocks big enough for sah_Desc +* non-zero - make blocks big enough for sah_Head_Desc +* +* @return int TRUE if blocks added succeesfully, FALSE otherwise +* +*/ +int sah_Block_Add_Page(int big) +{ + void *page; + int success; + dma_addr_t dma_addr; + unsigned block_index; + uint32_t dma_offset; + unsigned block_entries = + big ? MEM_BIG_BLOCK_ENTRIES : MEM_BLOCK_ENTRIES; + unsigned block_size = big ? sizeof(Mem_Big_Block) : sizeof(Mem_Block); + void *block; + + /* Allocate page of memory */ +#ifndef USE_COHERENT_MEMORY + page = os_alloc_memory(PAGE_SIZE, GFP_ATOMIC | __GFP_DMA); + dma_addr = os_pa(page); +#else + page = os_alloc_coherent(PAGE_SIZE, &dma_addr, GFP_ATOMIC); +#endif + if (page != NULL) { + /* + * Find the difference between the virtual address and the DMA + * address of the page. This is used later to determine the DMA + * address of each individual block. + */ + dma_offset = page - (void *)dma_addr; + + /* Split page into blocks and add to free pool */ + block = page; + for (block_index = 0; block_index < block_entries; + block_index++) { + if (big) { + register Mem_Big_Block *blockp = block; + blockp->dma_addr = + (uint32_t) (block - dma_offset); + sah_Append_Big_Block(blockp); + } else { + register Mem_Block *blockp = block; + blockp->dma_addr = + (uint32_t) (block - dma_offset); + /* sah_Append_Block must be protected with spin locks. This is + * done in sah_Alloc_Block(), which calls + * sah_Block_Add_Page() */ + sah_Append_Block(blockp); + } + block += block_size; + } + success = TRUE; +#ifdef DIAG_MEM + LOG_KDIAG("Succeeded in allocating new page"); +#endif + } else { + success = FALSE; +#ifdef DIAG_MEM + LOG_KDIAG("Failed in allocating new page"); +#endif + } + + return success; +} +#endif /* SELF_MANAGED_POOL */ + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +static Mem_Big_Block *sah_Alloc_Big_Block(void) +{ + Mem_Big_Block *block; + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + + /* If the pool is empty, try to allocate more entries */ + if (big_block_free_head == NULL) { + (void)sah_Block_Add_Page(1); + } + + /* Check that the pool now has some free entries */ + if (big_block_free_head != NULL) { + /* Return the head of the free pool */ + block = big_block_free_head; + + big_block_free_head = big_block_free_head->next; + if (big_block_free_head == NULL) { + /* Allocated last entry in pool */ + big_block_free_tail = NULL; + } + } else { + block = NULL; + } + os_unlock_restore_context(mem_lock, lock_flags); + + return block; +} +#else +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +static Mem_Big_Block *sah_Alloc_Big_Block(void) +{ + dma_addr_t dma_addr; + Mem_Big_Block *block = + dma_pool_alloc(big_dma_pool, GFP_ATOMIC, &dma_addr); + + if (block == NULL) { + } else { + block->dma_addr = dma_addr; + } + + return block; +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +/****************************************************************************** +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 31/10/2003 RWK PR52734 - Implement functions to allocate +* descriptors and links. Replace +* consistent_alloc() calls. Initial creation. +* +******************************************************************************/ +static Mem_Block *sah_Alloc_Block(void) +{ + Mem_Block *block; + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + + /* If the pool is empty, try to allocate more entries */ + if (block_free_head == NULL) { + (void)sah_Block_Add_Page(0); + } + + /* Check that the pool now has some free entries */ + if (block_free_head != NULL) { + /* Return the head of the free pool */ + block = block_free_head; + + block_free_head = block_free_head->next; + if (block_free_head == NULL) { + /* Allocated last entry in pool */ + block_free_tail = NULL; + } + } else { + block = NULL; + } + os_unlock_restore_context(mem_lock, lock_flags); + + return block; +} +#else +/*! +******************************************************************************* +* Allocate block from free pool. A block is large enough to fit either a link +* or descriptor. +* +* @brief Allocate memory block +* +* @return Mem_Block Free block, NULL if no free blocks available. +* +*/ +/****************************************************************************** +* +* MODIFICATION HISTORY: +* +* Date Person Change +* 31/10/2003 RWK PR52734 - Implement functions to allocate +* descriptors and links. Replace +* consistent_alloc() calls. Initial creation. +* +******************************************************************************/ +static Mem_Block *sah_Alloc_Block(void) +{ + + dma_addr_t dma_addr; + Mem_Block *block = + dma_pool_alloc(small_dma_pool, GFP_ATOMIC, &dma_addr); + + if (block == NULL) { + } else { + block->dma_addr = dma_addr; + } + + return block; +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Block(Mem_Block * block) +{ + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + sah_Append_Block(block); + os_unlock_restore_context(mem_lock, lock_flags); +} +#else +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Block(Mem_Block * block) +{ + dma_pool_free(small_dma_pool, block, block->dma_addr); +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Big_Block(Mem_Big_Block * block) +{ + os_lock_context_t lock_flags; + + os_lock_save_context(mem_lock, lock_flags); + sah_Append_Big_Block(block); + os_unlock_restore_context(mem_lock, lock_flags); +} +#else +/*! +******************************************************************************* +* Free memory block back to free pool +* +* @brief Free memory block +* +* @param block A block allocated with sah_Alloc_Block(). +* +* @return none +* +*/ +static void sah_Free_Big_Block(Mem_Big_Block * block) +{ + dma_pool_free(big_dma_pool, block, block->dma_addr); +} +#endif + +#ifdef SELF_MANAGED_POOL +/*! +******************************************************************************* +* Append memory block to end of free pool. +* +* @param block A block entry +* +* @return none +* +* @pre This function must be called with the #mem_lock held. +* +* @brief Append memory block to free pool +*/ +static void sah_Append_Big_Block(Mem_Big_Block * block) +{ + + /* Initialise block */ + block->next = NULL; + + /* Remember that block may be the first in the pool */ + if (big_block_free_tail != NULL) { + big_block_free_tail->next = block; + } else { + /* Pool is empty */ + big_block_free_head = block; + } + + big_block_free_tail = block; +} + +/*! +******************************************************************************* +* Append memory block to end of free pool. +* +* @brief Append memory block to free pool +* +* @param block A block entry +* +* @return none +* +* @pre #mem_lock must be held +* +*/ +static void sah_Append_Block(Mem_Block * block) +{ + + /* Initialise block */ + block->next = NULL; + + /* Remember that block may be the first in the pool */ + if (block_free_tail != NULL) { + block_free_tail->next = block; + } else { + /* Pool is empty */ + block_free_head = block; + } + + block_free_tail = block; +} +#endif /* SELF_MANAGED_POOL */ + +/* End of sah_memory_mapper.c */ diff --git a/drivers/mxc/security/sahara2/sah_queue.c b/drivers/mxc/security/sahara2/sah_queue.c new file mode 100644 index 000000000000..0f3e56e4c254 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_queue.c @@ -0,0 +1,249 @@ +/* + * 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 sah_queue.c +* +* @brief This file provides a FIFO Queue implementation. +* +*/ +/****************************************************************************** +* +* CAUTION: +******************************************************************* +*/ + +/* SAHARA Includes */ +#include <sah_queue_manager.h> +#ifdef DIAG_DRV_QUEUE +#include <diagnostic.h> +#endif + +/****************************************************************************** +* Queue Functions +******************************************************************************/ + +/*! +******************************************************************************* +* This function constructs a new sah_Queue. +* +* @brief sah_Queue Constructor +* +* @return A pointer to a newly allocated sah_Queue. +* @return NULL if allocation of memory failed. +*/ +/****************************************************************************** +* +* CAUTION: This function may sleep in low-memory situations, as it uses +* kmalloc ( ..., GFP_KERNEL). +******************************************************************************/ +sah_Queue *sah_Queue_Construct(void) +{ + sah_Queue *q = (sah_Queue *) os_alloc_memory(sizeof(sah_Queue), + GFP_KERNEL); + + if (q != NULL) { + /* Initialise the queue to an empty state. */ + q->head = NULL; + q->tail = NULL; + q->count = 0; + } +#ifdef DIAG_DRV_QUEUE + else { + LOG_KDIAG("kmalloc() failed."); + } +#endif + + return q; +} + +/*! +******************************************************************************* +* This function destroys a sah_Queue. +* +* @brief sah_Queue Destructor +* +* @param q A pointer to a sah_Queue. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This function does not free any queue entries. +* +******************************************************************************/ +void sah_Queue_Destroy(sah_Queue * q) +{ +#ifdef DIAG_DRV_QUEUE + if (q == NULL) { + LOG_KDIAG("Trying to kfree() a NULL pointer."); + } else { + if (q->count != 0) { + LOG_KDIAG + ("Trying to destroy a queue that is not empty."); + } + } +#endif + + if (q != NULL) { + os_free_memory(q); + q = NULL; + } +} + +/*! +******************************************************************************* +* This function appends a sah_Head_Desc to the tail of a sah_Queue. +* +* @brief Appends a sah_Head_Desc to a sah_Queue. +* +* @param q A pointer to a sah_Queue to append to. +* @param entry A pointer to a sah_Head_Desc to append. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: NONE +******************************************************************************/ +void sah_Queue_Append_Entry(sah_Queue * q, sah_Head_Desc * entry) +{ + sah_Head_Desc *tail_entry = NULL; + + if ((q == NULL) || (entry == NULL)) { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("Null pointer input."); +#endif + return; + } + + if (q->count == 0) { + /* The queue is empty */ + q->head = entry; + q->tail = entry; + entry->next = NULL; + entry->prev = NULL; + } else { + /* The queue is not empty */ + tail_entry = q->tail; + tail_entry->next = entry; + entry->next = NULL; + entry->prev = tail_entry; + q->tail = entry; + } + q->count++; +} + +/*! +******************************************************************************* +* This function a removes a sah_Head_Desc from the head of a sah_Queue. +* +* @brief Removes a sah_Head_Desc from a the head of a sah_Queue. +* +* @param q A pointer to a sah_Queue to remove from. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This does not kfree() the entry. +******************************************************************************/ +void sah_Queue_Remove_Entry(sah_Queue * q) +{ + sah_Queue_Remove_Any_Entry(q, q->head); +} + +/*! +******************************************************************************* +* This function a removes a sah_Head_Desc from anywhere in a sah_Queue. +* +* @brief Removes a sah_Head_Desc from anywhere in a sah_Queue. +* +* @param qq A pointer to a sah_Queue to remove from. +* @param entry A pointer to a sah_Head_Desc to remove. +* +* @pre The #desc_queue_lock must be held before calling this function. +* +* @return void +*/ +/****************************************************************************** +* +* CAUTION: This does not kfree() the entry. Does not check to see if the entry +* actually belongs to the queue. +******************************************************************************/ +void sah_Queue_Remove_Any_Entry(sah_Queue * q, sah_Head_Desc * entry) +{ + sah_Head_Desc *prev_entry = NULL; + sah_Head_Desc *next_entry = NULL; + + if ((q == NULL) || (entry == NULL)) { +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + LOG_KDIAG("Null pointer input."); +#endif + return; + } + + if (q->count == 1) { + /* If q is the only entry in the queue. */ + q->tail = NULL; + q->head = NULL; + q->count = 0; + } else if (q->count > 1) { + /* There are 2 or more entries in the queue. */ + +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + if ((entry->next == NULL) && (entry->prev == NULL)) { + LOG_KDIAG + ("Queue is not empty yet both next and prev pointers" + " are NULL"); + } +#endif + + if (entry->next == NULL) { + /* If this is the end of the queue */ + prev_entry = entry->prev; + prev_entry->next = NULL; + q->tail = prev_entry; + } else if (entry->prev == NULL) { + /* If this is the head of the queue */ + next_entry = entry->next; + next_entry->prev = NULL; + q->head = next_entry; + } else { + /* If this is somewhere in the middle of the queue */ + prev_entry = entry->prev; + next_entry = entry->next; + prev_entry->next = next_entry; + next_entry->prev = prev_entry; + } + q->count--; + } + /* + * Otherwise we are removing an entry from an empty queue. + * Don't do anything in the product code + */ +#if defined DIAG_DRV_QUEUE && defined DIAG_DURING_INTERRUPT + else { + LOG_KDIAG("Trying to remove an entry from an empty queue."); + } +#endif + + entry->next = NULL; + entry->prev = NULL; +} + +/* End of sah_queue.c */ diff --git a/drivers/mxc/security/sahara2/sah_queue_manager.c b/drivers/mxc/security/sahara2/sah_queue_manager.c new file mode 100644 index 000000000000..1602c7043a13 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_queue_manager.c @@ -0,0 +1,1050 @@ +/* + * Copyright 2004-2009 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 sah_queue_manager.c + * + * @brief This file provides a Queue Manager implementation. + * + * The Queue Manager manages additions and removal from the queue and updates + * the status of queue entries. It also calls sah_HW_* functions to interract + * with the hardware. +*/ + +#include "portable_os.h" + +/* SAHARA Includes */ +#include <sah_driver_common.h> +#include <sah_queue_manager.h> +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#if defined(DIAG_DRV_QUEUE) || defined(DIAG_DRV_STATUS) +#include <diagnostic.h> +#endif +#include <sah_memory_mapper.h> + +#ifdef DIAG_DRV_STATUS + +#define FSL_INVALID_RETURN 13 +#define MAX_RETURN_STRING_LEN 22 +#endif + +/* Defines for parsing value from Error Status register */ +#define SAH_STATUS_MASK 0x07 +#define SAH_ERROR_MASK 0x0F +#define SAH_CHA_ERR_SOURCE_MASK 0x07 +#define SAH_CHA_ERR_STATUS_MASK 0x0FFF +#define SAH_DMA_ERR_STATUS_MASK 0x0F +#define SAH_DMA_ERR_SIZE_MASK 0x03 +#define SAH_DMA_ERR_DIR_MASK 0x01 + +#define SHA_ERROR_STATUS_CONTINUE 0xFFFFFFFF +#define SHA_CHA_ERROR_STATUS_DONE 0xFFFFFFFF + +/* this maps the error status register's error source 4 bit field to the API + * return values. A 0xFFFFFFFF indicates additional fields must be checked to + * determine an appropriate return value */ +static sah_Execute_Error sah_Execute_Error_Array[] = { + FSL_RETURN_ERROR_S, /* SAH_ERR_NONE */ + FSL_RETURN_BAD_FLAG_S, /* SAH_ERR_HEADER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_DESC_LENGTH */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_DESC_POINTER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_LINK_LENGTH */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_LINK_POINTER */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_INPUT_BUFFER */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_OUTPUT_BUFFER */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_ERR_OUTPUT_BUFFER_STARVATION */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_INTERNAL_STATE */ + FSL_RETURN_ERROR_S, /* SAH_ERR_GENERAL_DESCRIPTOR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_ERR_RESERVED_FIELDS */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_ERR_DESCRIPTOR_ADDRESS */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_ERR_LINK_ADDRESS */ + SHA_ERROR_STATUS_CONTINUE, /* SAH_ERR_CHA */ + SHA_ERROR_STATUS_CONTINUE /* SAH_ERR_DMA */ +}; + +static sah_DMA_Error_Status sah_DMA_Error_Status_Array[] = { + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_NO_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_AHB_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_IP_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_PARITY_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_DMA_BOUNDRY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_BUSY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_DMA_RESERVED_ERR */ + FSL_RETURN_INTERNAL_ERROR_S /* SAH_DMA_INT_ERR */ +}; + +static sah_CHA_Error_Status sah_CHA_Error_Status_Array[] = { + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_NO_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_CHA_IP_BUF */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_ADD_ERR */ + FSL_RETURN_BAD_MODE_S, /* SAH_CHA_MODE_ERR */ + FSL_RETURN_BAD_DATA_LENGTH_S, /* SAH_CHA_DATA_SIZE_ERR */ + FSL_RETURN_BAD_KEY_LENGTH_S, /* SAH_CHA_KEY_SIZE_ERR */ + FSL_RETURN_BAD_MODE_S, /* SAH_CHA_PROC_ERR */ + FSL_RETURN_ERROR_S, /* SAH_CHA_CTX_READ_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_INTERNAL_HW_ERR */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_CHA_IP_BUFF_ERR */ + FSL_RETURN_MEMORY_ERROR_S, /* SAH_CHA_OP_BUFF_ERR */ + FSL_RETURN_BAD_KEY_PARITY_S, /* SAH_CHA_DES_KEY_ERR */ + FSL_RETURN_INTERNAL_ERROR_S, /* SAH_CHA_RES */ +}; + +#ifdef DIAG_DRV_STATUS + +char sah_return_text[FSL_INVALID_RETURN][MAX_RETURN_STRING_LEN] = { + "No error", /* FSL_RETURN_OK_S */ + "Error", /* FSL_RETURN_ERROR_S */ + "No resource", /* FSL_RETURN_NO_RESOURCE_S */ + "Bad algorithm", /* FSL_RETURN_BAD_ALGORITHM_S */ + "Bad mode", /* FSL_RETURN_BAD_MODE_S */ + "Bad flag", /* FSL_RETURN_BAD_FLAG_S */ + "Bad key length", /* FSL_RETURN_BAD_KEY_LENGTH_S */ + "Bad key parity", /* FSL_RETURN_BAD_KEY_PARITY_S */ + "Bad data length", /* FSL_RETURN_BAD_DATA_LENGTH_S */ + "Authentication failed", /* FSL_RETURN_AUTH_FAILED_S */ + "Memory error", /* FSL_RETURN_MEMORY_ERROR_S */ + "Internal error", /* FSL_RETURN_INTERNAL_ERROR_S */ + "unknown value", /* default */ +}; + +#endif /* DIAG_DRV_STATUS */ + +/*! + * This lock must be held while performing any queuing or unqueuing functions, + * including reading the first pointer on the queue. It also protects reading + * and writing the Sahara DAR register. It must be held during a read-write + * operation on the DAR so that the 'test-and-set' is atomic. + */ +os_lock_t desc_queue_lock; + +/*! This is the main queue for the driver. This is shared between all threads + * and is not protected by mutexes since the kernel is non-preemptable. */ +sah_Queue *main_queue = NULL; + +/* Internal Prototypes */ +sah_Head_Desc *sah_Find_With_State(sah_Queue_Status state); + +#ifdef DIAG_DRV_STATUS +void sah_Log_Error(uint32_t descriptor, uint32_t error, uint32_t fault_address); +#endif + +extern wait_queue_head_t *int_queue; + +/*! + * This function initialises the Queue Manager + * + * @brief Initialise the Queue Manager + * + * @return FSL_RETURN_OK_S on success; FSL_RETURN_MEMORY_ERROR_S if not + */ +fsl_shw_return_t sah_Queue_Manager_Init(void) +{ + fsl_shw_return_t ret_val = FSL_RETURN_OK_S; + + desc_queue_lock = os_lock_alloc_init(); + + if (main_queue == NULL) { + /* Construct the main queue. */ + main_queue = sah_Queue_Construct(); + + if (main_queue == NULL) { + ret_val = FSL_RETURN_MEMORY_ERROR_S; + } + } else { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG + ("Trying to initialise the queue manager more than once."); +#endif + } + + return ret_val; +} + +/*! + * This function closes the Queue Manager + * + * @brief Close the Queue Manager + * + * @return void + */ +void sah_Queue_Manager_Close(void) +{ +#ifdef DIAG_DRV_QUEUE + if (main_queue && main_queue->count != 0) { + LOG_KDIAG + ("Trying to close the main queue when it is not empty."); + } +#endif + + if (main_queue) { + /* There is no error checking here because there is no way to handle + it. */ + sah_Queue_Destroy(main_queue); + main_queue = NULL; + } +} + +/*! + * Count the number of entries on the Queue Manager's queue + * + * @param ignore_state If non-zero, the @a state parameter is ignored. + * If zero, only entries matching @a state are counted. + * @param state State of entry to match for counting. + * + * @return Number of entries which matched criteria + */ +int sah_Queue_Manager_Count_Entries(int ignore_state, sah_Queue_Status state) +{ + int count = 0; + sah_Head_Desc *current_entry; + + /* Start at the head */ + current_entry = main_queue->head; + while (current_entry != NULL) { + if (ignore_state || (current_entry->status == state)) { + count++; + } + /* Jump to the next entry. */ + current_entry = current_entry->next; + } + + return count; +} + +/*! + * This function removes an entry from the Queue Manager's queue. The entry to + * be removed can be anywhere in the queue. + * + * @brief Remove an entry from the Queue Manager's queue. + * + * @param entry A pointer to a sah_Head_Desc to remove from the Queue + * Manager's queue. + * + * @pre The #desc_queue_lock must be held before calling this function. + * + * @return void + */ +void sah_Queue_Manager_Remove_Entry(sah_Head_Desc * entry) +{ + if (entry == NULL) { +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("NULL pointer input."); +#endif + } else { + sah_Queue_Remove_Any_Entry(main_queue, entry); + } +} + +/*! + * This function appends an entry to the Queue Managers queue. It primes SAHARA + * if this entry is the first PENDING entry in the Queue Manager's Queue. + * + * @brief Appends an entry to the Queue Manager's queue. + * + * @param entry A pointer to a sah_Head_Desc to append to the Queue + * Manager's queue. + * + * @pre The #desc_queue_lock may not may be held when calling this function. + * + * @return void + */ +void sah_Queue_Manager_Append_Entry(sah_Head_Desc * entry) +{ + sah_Head_Desc *current_entry; + os_lock_context_t int_flags; + +#ifdef DIAG_DRV_QUEUE + if (entry == NULL) { + LOG_KDIAG("NULL pointer input."); + } +#endif + entry->status = SAH_STATE_PENDING; + os_lock_save_context(desc_queue_lock, int_flags); + sah_Queue_Append_Entry(main_queue, entry); + + /* Prime SAHARA if the operation that was just appended is the only PENDING + * operation in the queue. + */ + current_entry = sah_Find_With_State(SAH_STATE_PENDING); + if (current_entry != NULL) { + if (current_entry == entry) { + sah_Queue_Manager_Prime(entry); + } + } + + os_unlock_restore_context(desc_queue_lock, int_flags); +} + +/*! + * This function marks all entries in the Queue Manager's queue with state + * SAH_STATE_RESET. + * + * @brief Mark all entries with state SAH_STATE_RESET + * + * @return void + * + * @note This feature needs re-visiting + */ +void sah_Queue_Manager_Reset_Entries(void) +{ + sah_Head_Desc *current_entry = NULL; + + /* Start at the head */ + current_entry = main_queue->head; + + while (current_entry != NULL) { + /* Set the state. */ + current_entry->status = SAH_STATE_RESET; + /* Jump to the next entry. */ + current_entry = current_entry->next; + } +} + +/*! + * This function primes SAHARA for the first time or after the queue becomes + * empty. Queue lock must have been set by the caller of this routine. + * + * @brief Prime SAHARA. + * + * @param entry A pointer to a sah_Head_Desc to Prime SAHARA with. + * + * @return void + */ +void sah_Queue_Manager_Prime(sah_Head_Desc * entry) +{ +#ifdef DIAG_DRV_QUEUE + LOG_KDIAG("Priming SAHARA"); + if (entry == NULL) { + LOG_KDIAG("Trying to prime SAHARA with a NULL entry pointer."); + } +#endif + +#ifndef SUBMIT_MULTIPLE_DARS + /* BUG FIX: state machine can transition from Done1 Busy2 directly + * to Idle. To fix that problem, only one DAR is being allowed on + * SAHARA at a time */ + if (sah_Find_With_State(SAH_STATE_ON_SAHARA) != NULL) { + return; + } +#endif + +#ifdef SAHARA_POWER_MANAGEMENT + /* check that dynamic power management is not asserted */ + if (!sah_dpm_flag) { +#endif + + /* Enable the SAHARA Clocks */ +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Enabling the IPG and AHB clocks\n") +#endif /*DIAG_DRV_IF */ + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_enable(SAHARA2_CLK); +#else + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) + clk_enable(clk); + clk_put(clk); + } +#endif + + /* Make sure nothing is in the DAR */ + if (sah_HW_Read_DAR() == 0) { +#if defined(DIAG_DRV_IF) + sah_Dump_Chain(&entry->desc, entry->desc.dma_addr); +#endif /* DIAG_DRV_IF */ + + sah_HW_Write_DAR((entry->desc.dma_addr)); + entry->status = SAH_STATE_ON_SAHARA; + } +#ifdef DIAG_DRV_QUEUE + else { + LOG_KDIAG("DAR should be empty when Priming SAHARA"); + } +#endif +#ifdef SAHARA_POWER_MANAGEMENT + } +#endif +} + +#ifndef SAHARA_POLL_MODE + +/*! + * Reset SAHARA, then load the next descriptor on it, if one exists + */ +void sah_reset_sahara_request(void) +{ + sah_Head_Desc *desc; + os_lock_context_t lock_flags; + +#ifdef DIAG_DRV_STATUS + LOG_KDIAG("Sahara required reset from tasklet, replace chip"); +#endif + sah_HW_Reset(); + + /* Now stick in a waiting request */ + os_lock_save_context(desc_queue_lock, lock_flags); + if ((desc = sah_Find_With_State(SAH_STATE_PENDING))) { + sah_Queue_Manager_Prime(desc); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); +} + +/*! + * Post-process a descriptor chain after the hardware has finished with it. + * + * The status of the descriptor could also be checked. (for FATAL or IGNORED). + * + * @param desc_head The finished chain + * @param error A boolean to mark whether hardware reported error + * + * @pre The #desc_queue_lock may not be held when calling this function. + */ +void sah_process_finished_request(sah_Head_Desc * desc_head, unsigned error) +{ + os_lock_context_t lock_flags; + + if (!error) { + desc_head->result = FSL_RETURN_OK_S; + } else if (desc_head->error_status == -1) { + /* Disaster! Sahara has faulted */ + desc_head->result = FSL_RETURN_ERROR_S; + } else { + /* translate from SAHARA error status to fsl_shw return values */ + desc_head->result = + sah_convert_error_status(desc_head->error_status); +#ifdef DIAG_DRV_STATUS + sah_Log_Error(desc_head->current_dar, desc_head->error_status, + desc_head->fault_address); +#endif + } + + /* Show that the request has been processd */ + desc_head->status = error ? SAH_STATE_FAILED : SAH_STATE_COMPLETE; + + if (desc_head->uco_flags & FSL_UCO_BLOCKING_MODE) { + + /* Wake up all processes on Sahara queue */ + wake_up_interruptible(int_queue); + + } else { + os_lock_save_context(desc_queue_lock, lock_flags); + sah_Queue_Append_Entry(&desc_head->user_info->result_pool, + desc_head); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* perform callback */ + if (desc_head->uco_flags & FSL_UCO_CALLBACK_MODE) { + desc_head->user_info->callback(desc_head->user_info); + } + } +} /* sah_process_finished_request */ + +/*! Called from bottom half. + * + * @pre The #desc_queue_lock may not be held when calling this function. + */ +void sah_postprocess_queue(unsigned long reset_flag) +{ + + /* if SAHARA needs to be reset, do it here. This starts a descriptor chain + * if one is ready also */ + if (reset_flag) { + sah_reset_sahara_request(); + } + + /* now handle the descriptor chain(s) that has/have completed */ + do { + sah_Head_Desc *first_entry; + os_lock_context_t lock_flags; + + os_lock_save_context(desc_queue_lock, lock_flags); + + first_entry = main_queue->head; + if ((first_entry != NULL) && + (first_entry->status == SAH_STATE_OFF_SAHARA)) { + sah_Queue_Remove_Entry(main_queue); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + sah_process_finished_request(first_entry, + (first_entry-> + error_status != 0)); + } else { + os_unlock_restore_context(desc_queue_lock, lock_flags); + break; + } + } while (1); + + return; +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +/*! + * This is a helper function for Queue Manager. This function finds the first + * entry in the Queue Manager's queue whose state matches the given input + * state. This function starts at the head of the queue and works towards the + * tail. If a matching entry was found, the address of the entry is returned. + * + * @brief Handle the IDLE state. + * + * @param state A sah_Queue_Status value. + * + * @pre The #desc_queue_lock must be held before calling this function. + * + * @return A pointer to a sah_Head_Desc that matches the given state. + * @return NULL otherwise. + */ +sah_Head_Desc *sah_Find_With_State(sah_Queue_Status state) +{ + sah_Head_Desc *current_entry = NULL; + sah_Head_Desc *ret_val = NULL; + int done_looping = FALSE; + + /* Start at the head */ + current_entry = main_queue->head; + + while ((current_entry != NULL) && (done_looping == FALSE)) { + if (current_entry->status == state) { + done_looping = TRUE; + ret_val = current_entry; + } + /* Jump to the next entry. */ + current_entry = current_entry->next; + } + + return ret_val; +} /* sah_postprocess_queue */ + +/*! + * Process the value from the Sahara error status register and convert it into + * an FSL SHW API error code. + * + * Warning, this routine must only be called if an error exists. + * + * @param error_status The value from the error status register. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_convert_error_status(uint32_t error_status) +{ + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; /* catchall */ + uint8_t error_source; + uint8_t DMA_error_status; + uint8_t DMA_error_size; + + /* get the error source from the error status register */ + error_source = error_status & SAH_ERROR_MASK; + + /* array size is maximum allowed by mask, so no boundary checking is + * needed here */ + ret = sah_Execute_Error_Array[error_source]; + + /* is this one that needs additional fields checked to determine the + * error condition? */ + if (ret == SHA_ERROR_STATUS_CONTINUE) { + /* check the DMA fields */ + if (error_source == SAH_ERR_DMA) { + /* get the DMA transfer error size. If this indicates that no + * error was detected, something is seriously wrong */ + DMA_error_size = + (error_status >> 9) & SAH_DMA_ERR_SIZE_MASK; + if (DMA_error_size == SAH_DMA_NO_ERR) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + /* get DMA error status */ + DMA_error_status = (error_status >> 12) & + SAH_DMA_ERR_STATUS_MASK; + + /* the DMA error bits cover all the even numbers. By dividing + * by 2 it can be used as an index into the error array */ + ret = + sah_DMA_Error_Status_Array[DMA_error_status + >> 1]; + } + } else { /* not SAH_ERR_DMA, so must be SAH_ERR_CHA */ + uint16_t CHA_error_status; + uint8_t CHA_error_source; + + /* get CHA Error Source. If this indicates that no error was + * detected, something is seriously wrong */ + CHA_error_source = + (error_status >> 28) & SAH_CHA_ERR_SOURCE_MASK; + if (CHA_error_source == SAH_CHA_NO_ERROR) { + ret = FSL_RETURN_INTERNAL_ERROR_S; + } else { + uint32_t mask = 1; + uint32_t count = 0; + + /* get CHA Error Status */ + CHA_error_status = (error_status >> 16) & + SAH_CHA_ERR_STATUS_MASK; + + /* If more than one bit is set (which shouldn't happen), only + * the first will be captured */ + if (CHA_error_status != 0) { + count = 1; + while (CHA_error_status != mask) { + ++count; + mask <<= 1; + } + } + + ret = sah_CHA_Error_Status_Array[count]; + } + } + } + + return ret; +} + +fsl_shw_return_t sah_convert_op_status(uint32_t op_status) +{ + unsigned op_source = (op_status >> 28) & 0x7; + unsigned op_detail = op_status & 0x3f; + fsl_shw_return_t ret = FSL_RETURN_ERROR_S; + + switch (op_source) { + case 1: /* SKHA */ + /* Can't this have "ICV" error from CCM ?? */ + break; + case 2: /* MDHA */ + if (op_detail == 1) { + ret = FSL_RETURN_AUTH_FAILED_S; + } + break; + case 3: /* RNGA */ + /* Self-test and Compare errors... what to do? */ + break; + case 4: /* PKHA */ + switch (op_detail) { + case 0x01: + ret = FSL_RETURN_PRIME_S; + break; + case 0x02: + ret = FSL_RETURN_NOT_PRIME_S; + break; + case 0x04: + ret = FSL_RETURN_POINT_AT_INFINITY_S; + break; + case 0x08: + ret = FSL_RETURN_POINT_NOT_AT_INFINITY_S; + break; + case 0x10: + ret = FSL_RETURN_GCD_IS_ONE_S; + break; + case 0x20: + ret = FSL_RETURN_GCD_IS_NOT_ONE_S; + break; + default: + break; + } + break; + default: + break; + } + return ret; +} + +#ifdef DIAG_DRV_STATUS + +/*! + * This function logs the diagnostic information for the given error and + * descriptor address. Only used for diagnostic purposes. + * + * @brief (debug only) Log a description of hardware-detected error. + * + * @param descriptor The descriptor address that caused the error + * @param error The SAHARA error code + * @param fault_address Value from the Fault address register + * + * @return void + */ +void sah_Log_Error(uint32_t descriptor, uint32_t error, uint32_t fault_address) +{ + char *source_text; /* verbose error source from register */ + char *address; /* string buffer for descriptor address */ + char *error_log; /* the complete logging message */ + char *cha_log = NULL; /* string buffer for descriptor address */ + char *dma_log = NULL; /* string buffer for descriptor address */ + + uint16_t cha_error = 0; + uint16_t dma_error = 0; + + uint8_t error_source; + sah_Execute_Error return_code; + + /* log error code and descriptor address */ + error_source = error & SAH_ERROR_MASK; + return_code = sah_Execute_Error_Array[error_source]; + + source_text = os_alloc_memory(64, GFP_KERNEL); + + switch (error_source) { + case SAH_ERR_HEADER: + sprintf(source_text, "%s", "Header is not valid"); + break; + + case SAH_ERR_DESC_LENGTH: + sprintf(source_text, "%s", + "Descriptor length not equal to sum of link lengths"); + break; + + case SAH_ERR_DESC_POINTER: + sprintf(source_text, "%s", "Length or pointer " + "field is zero while the other is non-zero"); + break; + + case SAH_ERR_LINK_LENGTH: + /* note that the Sahara Block Guide 2.7 has an invalid explaination + * of this. It only happens when a link length is zero */ + sprintf(source_text, "%s", "A data length is a link is zero"); + break; + + case SAH_ERR_LINK_POINTER: + sprintf(source_text, "%s", + "The data pointer in a link is zero"); + break; + + case SAH_ERR_INPUT_BUFFER: + sprintf(source_text, "%s", "Input Buffer reported an overflow"); + break; + + case SAH_ERR_OUTPUT_BUFFER: + sprintf(source_text, "%s", + "Output Buffer reported an underflow"); + break; + + case SAH_ERR_OUTPUT_BUFFER_STARVATION: + sprintf(source_text, "%s", "Incorrect data in output " + "buffer after CHA has signalled 'done'"); + break; + + case SAH_ERR_INTERNAL_STATE: + sprintf(source_text, "%s", "Internal Hardware Failure"); + break; + + case SAH_ERR_GENERAL_DESCRIPTOR: + sprintf(source_text, "%s", + "Current Descriptor was not legal, but cause is unknown"); + break; + + case SAH_ERR_RESERVED_FIELDS: + sprintf(source_text, "%s", + "Reserved pointer field is non-zero"); + break; + + case SAH_ERR_DESCRIPTOR_ADDRESS: + sprintf(source_text, "%s", + "Descriptor address not word aligned"); + break; + + case SAH_ERR_LINK_ADDRESS: + sprintf(source_text, "%s", "Link address not word aligned"); + break; + + case SAH_ERR_CHA: + sprintf(source_text, "%s", "CHA Error"); + { + char *cha_module = os_alloc_memory(5, GFP_KERNEL); + char *cha_text = os_alloc_memory(45, GFP_KERNEL); + + cha_error = (error >> 28) & SAH_CHA_ERR_SOURCE_MASK; + + switch (cha_error) { + case SAH_CHA_SKHA_ERROR: + sprintf(cha_module, "%s", "SKHA"); + break; + + case SAH_CHA_MDHA_ERROR: + sprintf(cha_module, "%s", "MDHA"); + break; + + case SAH_CHA_RNG_ERROR: + sprintf(cha_module, "%s", "RNG "); + break; + + case SAH_CHA_PKHA_ERROR: + sprintf(cha_module, "%s", "PKHA"); + break; + + case SAH_CHA_NO_ERROR: + /* can't happen */ + /* no break */ + default: + sprintf(cha_module, "%s", "????"); + break; + } + + cha_error = (error >> 16) & SAH_CHA_ERR_STATUS_MASK; + + /* Log CHA Error Status */ + switch (cha_error) { + case SAH_CHA_IP_BUF: + sprintf(cha_text, "%s", + "Non-empty input buffer when done"); + break; + + case SAH_CHA_ADD_ERR: + sprintf(cha_text, "%s", "Illegal address"); + break; + + case SAH_CHA_MODE_ERR: + sprintf(cha_text, "%s", "Illegal mode"); + break; + + case SAH_CHA_DATA_SIZE_ERR: + sprintf(cha_text, "%s", "Illegal data size"); + break; + + case SAH_CHA_KEY_SIZE_ERR: + sprintf(cha_text, "%s", "Illegal key size"); + break; + + case SAH_CHA_PROC_ERR: + sprintf(cha_text, "%s", + "Mode/Context/Key written during processing"); + break; + + case SAH_CHA_CTX_READ_ERR: + sprintf(cha_text, "%s", + "Context read during processing"); + break; + + case SAH_CHA_INTERNAL_HW_ERR: + sprintf(cha_text, "%s", "Internal hardware"); + break; + + case SAH_CHA_IP_BUFF_ERR: + sprintf(cha_text, "%s", + "Input buffer not enabled or underflow"); + break; + + case SAH_CHA_OP_BUFF_ERR: + sprintf(cha_text, "%s", + "Output buffer not enabled or overflow"); + break; + + case SAH_CHA_DES_KEY_ERR: + sprintf(cha_text, "%s", "DES key parity error"); + break; + + case SAH_CHA_RES: + sprintf(cha_text, "%s", "Reserved"); + break; + + case SAH_CHA_NO_ERR: + /* can't happen */ + /* no break */ + default: + sprintf(cha_text, "%s", "Unknown error"); + break; + } + + cha_log = os_alloc_memory(90, GFP_KERNEL); + sprintf(cha_log, + " Module %s encountered the error: %s.", + cha_module, cha_text); + + os_free_memory(cha_module); + os_free_memory(cha_text); + + { + uint32_t mask = 1; + uint32_t count = 0; + + if (cha_error != 0) { + count = 1; + while (cha_error != mask) { + ++count; + mask <<= 1; + } + } + + return_code = sah_CHA_Error_Status_Array[count]; + } + cha_error = 1; + } + break; + + case SAH_ERR_DMA: + sprintf(source_text, "%s", "DMA Error"); + { + char *dma_direction = os_alloc_memory(6, GFP_KERNEL); + char *dma_size = os_alloc_memory(14, GFP_KERNEL); + char *dma_text = os_alloc_memory(250, GFP_KERNEL); + + if ((dma_direction == NULL) || (dma_size == NULL) || + (dma_text == NULL)) { + LOG_KDIAG + ("No memory allocated for DMA debug messages\n"); + } + + /* log DMA error direction */ + sprintf(dma_direction, "%s", + (((error >> 8) & SAH_DMA_ERR_DIR_MASK) == 1) ? + "read" : "write"); + + /* log the size of the DMA transfer error */ + dma_error = (error >> 9) & SAH_DMA_ERR_SIZE_MASK; + switch (dma_error) { + case SAH_DMA_SIZE_BYTE: + sprintf(dma_size, "%s", "byte"); + break; + + case SAH_DMA_SIZE_HALF_WORD: + sprintf(dma_size, "%s", "half-word"); + break; + + case SAH_DMA_SIZE_WORD: + sprintf(dma_size, "%s", "word"); + break; + + case SAH_DMA_SIZE_RES: + sprintf(dma_size, "%s", "reserved size"); + break; + + default: + sprintf(dma_size, "%s", "unknown size"); + break; + } + + /* log DMA error status */ + dma_error = (error >> 12) & SAH_DMA_ERR_STATUS_MASK; + switch (dma_error) { + case SAH_DMA_NO_ERR: + sprintf(dma_text, "%s", "No DMA Error Code"); + break; + + case SAH_DMA_AHB_ERR: + sprintf(dma_text, "%s", + "AHB terminated a bus cycle with an error"); + break; + + case SAH_DMA_IP_ERR: + sprintf(dma_text, "%s", + "Internal IP bus cycle was terminated with an " + "error termination. This would likely be " + "caused by a descriptor length being too " + "large, and thus accessing an illegal " + "internal address. Verify the length field " + "of the current descriptor"); + break; + + case SAH_DMA_PARITY_ERR: + sprintf(dma_text, "%s", + "Parity error detected on DMA command from " + "Descriptor Decoder. Cause is likely to be " + "internal hardware fault"); + break; + + case SAH_DMA_BOUNDRY_ERR: + sprintf(dma_text, "%s", + "DMA was requested to cross a 256 byte " + "internal address boundary. Cause is likely a " + "descriptor length being too large, thus " + "accessing two different internal hardware " + "blocks"); + break; + + case SAH_DMA_BUSY_ERR: + sprintf(dma_text, "%s", + "Descriptor Decoder has made a DMA request " + "while the DMA controller is busy. Cause is " + "likely due to hardware fault"); + break; + + case SAH_DMA_RESERVED_ERR: + sprintf(dma_text, "%s", "Reserved"); + break; + + case SAH_DMA_INT_ERR: + sprintf(dma_text, "%s", + "Internal DMA hardware error detected. The " + "DMA controller has detected an internal " + "condition which should never occur"); + break; + + default: + sprintf(dma_text, "%s", + "Unknown DMA Error Status Code"); + break; + } + + return_code = + sah_DMA_Error_Status_Array[dma_error >> 1]; + dma_error = 1; + + dma_log = os_alloc_memory(320, GFP_KERNEL); + sprintf(dma_log, + " Occurred during a %s operation of a %s transfer: %s.", + dma_direction, dma_size, dma_text); + + os_free_memory(dma_direction); + os_free_memory(dma_size); + os_free_memory(dma_text); + } + break; + + case SAH_ERR_NONE: + default: + sprintf(source_text, "%s", "Unknown Error Code"); + break; + } + + address = os_alloc_memory(35, GFP_KERNEL); + + /* convert error & descriptor address to strings */ + if (dma_error) { + sprintf(address, "Fault address is 0x%08x", fault_address); + } else { + sprintf(address, "Descriptor bus address is 0x%08x", + descriptor); + } + + if (return_code > FSL_INVALID_RETURN) { + return_code = FSL_INVALID_RETURN; + } + + error_log = os_alloc_memory(250, GFP_KERNEL); + + /* construct final log message */ + sprintf(error_log, "Error source = 0x%08x. Return = %s. %s. %s.", + error, sah_return_text[return_code], address, source_text); + + os_free_memory(source_text); + os_free_memory(address); + + /* log standard messages */ + LOG_KDIAG(error_log); + os_free_memory(error_log); + + /* add additional information if available */ + if (cha_error) { + LOG_KDIAG(cha_log); + os_free_memory(cha_log); + } + + if (dma_error) { + LOG_KDIAG(dma_log); + os_free_memory(dma_log); + } + + return; +} /* sah_Log_Error */ + +#endif /* DIAG_DRV_STATUS */ + +/* End of sah_queue_manager.c */ diff --git a/drivers/mxc/security/sahara2/sah_status_manager.c b/drivers/mxc/security/sahara2/sah_status_manager.c new file mode 100644 index 000000000000..7791f5c45c93 --- /dev/null +++ b/drivers/mxc/security/sahara2/sah_status_manager.c @@ -0,0 +1,734 @@ +/* + * Copyright 2004-2009 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 sah_status_manager.c +* +* @brief Status Manager Function +* +* This file contains the function which processes the Sahara status register +* during an interrupt. +* +* This file does not need porting. +*/ + +#include "portable_os.h" + +#include <sah_status_manager.h> +#include <sah_hardware_interface.h> +#include <sah_queue_manager.h> +#include <sah_memory_mapper.h> +#include <sah_kernel.h> + +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) +#include <diagnostic.h> +#endif + +/*! Compile-time flag to count various interrupt types. */ +#define DIAG_INT_COUNT + +/*! + * Number of interrupts processed with Done1Done2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1_count; + +/*! + * Number of interrupts processed with Done1Busy2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1busy2_count; + +/*! + * Number of interrupts processed with Done1Done2 status. Updates to this + * value should only be done in interrupt processing. + */ +uint32_t done1done2_count; + +/*! + * the dynameic power management flag is false when power management is not + * asserted and true when dpm is. + */ +#ifdef SAHARA_POWER_MANAGEMENT +bool sah_dpm_flag = FALSE; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_suspend(struct device *dev, uint32_t state, uint32_t level); +static int sah_dpm_resume(struct device *dev, uint32_t level); +#else +static int sah_dpm_suspend(struct platform_device *dev, pm_message_t state); +static int sah_dpm_resume(struct platform_device *dev); +#endif +#endif + +#ifndef SAHARA_POLL_MODE +/*! +******************************************************************************* +* This functionx processes the status register of the Sahara, updates the state +* of the finished queue entry, and then tries to find more work for Sahara to +* do. +* +* @brief The bulk of the interrupt handling code. +* +* @param hw_status The status register of Sahara at time of interrupt. +* The Clear interrupt bit is already handled by this +* register read prior to entry into this function. +* @return void +*/ +unsigned long sah_Handle_Interrupt(sah_Execute_Status hw_status) +{ + unsigned long reset_flag = 0; /* assume no SAHARA reset needed */ + os_lock_context_t lock_flags; + sah_Head_Desc *current_entry; + + /* HW status at time of interrupt */ + sah_Execute_Status state = hw_status & SAH_EXEC_STATE_MASK; + + do { + uint32_t dar; + +#ifdef DIAG_INT_COUNT + if (state == SAH_EXEC_DONE1) { + done1_count++; + } else if (state == SAH_EXEC_DONE1_BUSY2) { + done1busy2_count++; + } else if (state == SAH_EXEC_DONE1_DONE2) { + done1done2_count++; + } +#endif + + /* if the first entry on sahara has completed... */ + if ((state & SAH_EXEC_DONE1_BIT) || + (state == SAH_EXEC_ERROR1)) { + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + current_entry = + sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* an active descriptor was not found */ + if (current_entry == NULL) { + /* change state to avoid an infinite loop (possible if + * state is SAH_EXEC_DONE1_BUSY2 first time into loop) */ + hw_status = SAH_EXEC_IDLE; +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG + ("Interrupt received with nothing on queue."); +#endif + } else { + /* SAHARA has completed its work on this descriptor chain */ + current_entry->status = SAH_STATE_OFF_SAHARA; + + if (state == SAH_EXEC_ERROR1) { + if (hw_status & STATUS_ERROR) { + /* Gather extra diagnostic information */ + current_entry->fault_address = + sah_HW_Read_Fault_Address(); + /* Read this last - it clears the error */ + current_entry->error_status = + sah_HW_Read_Error_Status(); + current_entry->op_status = 0; +#ifdef FSL_HAVE_SAHARA4 + } else { + current_entry->op_status = + sah_HW_Read_Op_Status(); + current_entry->error_status = 0; +#endif + } + + } else { + /* indicate that no errors were found with descriptor + * chain 1 */ + current_entry->error_status = 0; + current_entry->op_status = 0; + + /* is there a second, successfully, completed descriptor + * chain? (done1/error2 processing is handled later) */ + if (state == SAH_EXEC_DONE1_DONE2) { + os_lock_save_context + (desc_queue_lock, + lock_flags); + current_entry = + sah_Find_With_State + (SAH_STATE_ON_SAHARA); + os_unlock_restore_context + (desc_queue_lock, + lock_flags); + + if (current_entry == NULL) { +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + LOG_KDIAG + ("Done1_Done2 Interrupt received with " + "one entry on queue."); +#endif + } else { + /* indicate no errors in descriptor chain 2 */ + current_entry-> + error_status = 0; + current_entry->status = + SAH_STATE_OFF_SAHARA; + } + } + } + } + +#ifdef SAHARA_POWER_MANAGEMENT + /* check dynamic power management is not asserted */ + if (!sah_dpm_flag) { +#endif + do { + /* protect DAR and main_queue */ + os_lock_save_context(desc_queue_lock, + lock_flags); + dar = sah_HW_Read_DAR(); + /* check if SAHARA has space for another descriptor. SAHARA + * only accepts up to the DAR queue size number of DAR + * entries, after that 'dar' will not be zero until the + * pending interrupt is serviced */ + if (dar == 0) { + current_entry = + sah_Find_With_State + (SAH_STATE_PENDING); + if (current_entry != NULL) { +#ifndef SUBMIT_MULTIPLE_DARS + /* BUG FIX: state machine can transition from Done1 + * Busy2 directly to Idle. To fix that problem, + * only one DAR is being allowed on SAHARA at a + * time. If a high level interrupt has happened, + * there could * be an active descriptor chain */ + if (sah_Find_With_State + (SAH_STATE_ON_SAHARA) + == NULL) { +#endif +#if defined(DIAG_DRV_IF) && defined(DIAG_DURING_INTERRUPT) + sah_Dump_Chain + (¤t_entry-> + desc, + current_entry-> + desc. + dma_addr); +#endif /* DIAG_DRV_IF */ + sah_HW_Write_DAR + (current_entry-> + desc. + dma_addr); + current_entry-> + status = + SAH_STATE_ON_SAHARA; +#ifndef SUBMIT_MULTIPLE_DARS + } + current_entry = NULL; /* exit loop */ +#endif + } + } + os_unlock_restore_context + (desc_queue_lock, lock_flags); + } while ((dar == 0) && (current_entry != NULL)); +#ifdef SAHARA_POWER_MANAGEMENT + } /* sah_device_power_manager */ +#endif + } else { + if (state == SAH_EXEC_FAULT) { + sah_Head_Desc *previous_entry; /* point to chain 1 */ + /* Address of request when fault occured */ + uint32_t bad_dar = sah_HW_Read_IDAR(); + + reset_flag = 1; /* SAHARA needs to be reset */ + + /* get first of possible two descriptor chain that was + * on SAHARA */ + os_lock_save_context(desc_queue_lock, + lock_flags); + previous_entry = + sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, + lock_flags); + + /* if it exists, continue processing the fault */ + if (previous_entry) { + /* assume this chain didn't complete correctly */ + previous_entry->error_status = -1; + previous_entry->status = + SAH_STATE_OFF_SAHARA; + + /* get the second descriptor chain */ + os_lock_save_context(desc_queue_lock, + lock_flags); + current_entry = + sah_Find_With_State + (SAH_STATE_ON_SAHARA); + os_unlock_restore_context + (desc_queue_lock, lock_flags); + + /* if it exists, continue processing both chains */ + if (current_entry) { + /* assume this chain didn't complete correctly */ + current_entry->error_status = + -1; + current_entry->status = + SAH_STATE_OFF_SAHARA; + + /* now see if either can be identified as the one + * in progress when the fault occured */ + if (current_entry->desc. + dma_addr == bad_dar) { + /* the second descriptor chain was active when the + * fault occured, so the first descriptor chain + * was successfull */ + previous_entry-> + error_status = 0; + } else { + if (previous_entry-> + desc.dma_addr == + bad_dar) { + /* if the first chain was in progress when the + * fault occured, the second has not yet been + * touched, so reset it to PENDING */ + current_entry-> + status = + SAH_STATE_PENDING; + } + } + } + } +#if defined(DIAG_DRV_INTERRUPT) && defined(DIAG_DURING_INTERRUPT) + } else { + /* shouldn't ever get here */ + if (state == SAH_EXEC_BUSY) { + LOG_KDIAG + ("Got Sahara interrupt in Busy state"); + } else { + if (state == SAH_EXEC_IDLE) { + LOG_KDIAG + ("Got Sahara interrupt in Idle state"); + } else { + LOG_KDIAG + ("Got Sahara interrupt in unknown state"); + } + } +#endif + } + } + + /* haven't handled the done1/error2 (the error 2 part), so setup to + * do that now. Otherwise, exit loop */ + state = (state == SAH_EXEC_DONE1_ERROR2) ? + SAH_EXEC_ERROR1 : SAH_EXEC_IDLE; + + /* Keep going while further status is available. */ + } while (state == SAH_EXEC_ERROR1); + + /* Disabling Sahara Clock only if the hardware is in idle state and + the DAR queue is empty.*/ + os_lock_save_context(desc_queue_lock, lock_flags); + current_entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + if ((current_entry == NULL) && (state == SAH_EXEC_IDLE)) { + +#ifdef DIAG_DRV_IF + LOG_KDIAG("SAHARA : Disabling the clocks\n") +#endif /* DIAG_DRV_IF */ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SAHARA2_CLK); +#else + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) + clk_disable(clk); + clk_put(clk); + } +#endif + + } + + return reset_flag; +} + +#endif /* ifndef SAHARA_POLL_MODE */ + +#ifdef SAHARA_POLL_MODE +/*! +******************************************************************************* +* Submits descriptor chain to SAHARA, polls on SAHARA for completion, process +* results, and dephysicalizes chain +* +* @brief Handle poll mode. +* +* @param entry Virtual address of a physicalized chain +* +* @return 0 this function is always successful +*/ + +unsigned long sah_Handle_Poll(sah_Head_Desc * entry) +{ + sah_Execute_Status hw_status; /* Sahara's status register */ + os_lock_context_t lock_flags; + + /* lock SARAHA */ + os_lock_save_context(desc_queue_lock, lock_flags); + +#ifdef SAHARA_POWER_MANAGEMENT + /* check if the dynamic power management is asserted */ + if (sah_dpm_flag) { + /* return that request failed to be processed */ + entry->result = FSL_RETURN_ERROR_S; + entry->fault_address = 0xBAD; + entry->op_status= 0xBAD; + entry->error_status = 0xBAD; + } else { +#endif /* SAHARA_POWER_MANAGEMENT */ + +#if defined(DIAG_DRV_IF) + sah_Dump_Chain(&entry->desc, entry->desc.dma_addr); +#endif /* DIAG_DRV_IF */ + /* Nothing can be in the dar if we got the lock */ + sah_HW_Write_DAR((uint32_t) (entry->desc.dma_addr)); + + /* Wait for SAHARA to finish with this entry */ + hw_status = sah_Wait_On_Sahara(); + + /* if entry completed successfully, mark it as such */ + /**** HARDWARE ERROR WORK AROUND (hw_status == SAH_EXEC_IDLE) *****/ + if ( +#ifndef SUBMIT_MULTIPLE_DARS + (hw_status == SAH_EXEC_IDLE) || (hw_status == SAH_EXEC_DONE1_BUSY2) || /* should not happen */ +#endif + (hw_status == SAH_EXEC_DONE1) + ) { + entry->error_status = 0; + entry->result = FSL_RETURN_OK_S; + } else { + /* SAHARA is reporting an error with entry */ + if (hw_status == SAH_EXEC_ERROR1) { + /* Gather extra diagnostic information */ + entry->fault_address = + sah_HW_Read_Fault_Address(); + /* Read this register last - it clears the error */ + entry->error_status = + sah_HW_Read_Error_Status(); + entry->op_status = 0; + /* translate from SAHARA error status to fsl_shw return values */ + entry->result = + sah_convert_error_status(entry-> + error_status); +#ifdef DIAG_DRV_STATUS + sah_Log_Error(entry->op_status, + entry->error_status, + entry->fault_address); +#endif + } else if (hw_status == SAH_EXEC_OPSTAT1) { + entry->op_status = sah_HW_Read_Op_Status(); + entry->error_status = 0; + entry->result = + sah_convert_op_status(op_status); + } else { + /* SAHARA entered FAULT state (or something bazaar has + * happened) */ + pr_debug + ("Sahara: hw_status = 0x%x; Stat: 0x%08x; IDAR: 0x%08x; " + "CDAR: 0x%08x; FltAdr: 0x%08x; Estat: 0x%08x\n", + hw_status, sah_HW_Read_Status(), + sah_HW_Read_IDAR(), sah_HW_Read_CDAR(), + sah_HW_Read_Fault_Address(), + sah_HW_Read_Error_Status()); +#ifdef DIAG_DRV_IF + { + int old_level = console_loglevel; + console_loglevel = 8; + sah_Dump_Chain(&(entry->desc), + entry->desc.dma_addr); + console_loglevel = old_level; + } +#endif + + entry->error_status = -1; + entry->result = FSL_RETURN_ERROR_S; + sah_HW_Reset(); + } + } +#ifdef SAHARA_POWER_MANAGEMENT + } +#endif + + if (!(entry->uco_flags & FSL_UCO_BLOCKING_MODE)) { + /* put it in results pool to allow get_results to work */ + sah_Queue_Append_Entry(&entry->user_info->result_pool, entry); + if (entry->uco_flags & FSL_UCO_CALLBACK_MODE) { + /* invoke callback */ + entry->user_info->callback(entry->user_info); + } + } else { + /* convert the descriptor link back to virtual memory, mark dirty pages + * if they are from user mode, and release the page cache for user + * pages + */ + entry = sah_DePhysicalise_Descriptors(entry); + } + + os_unlock_restore_context(desc_queue_lock, lock_flags); + + return 0; +} + +#endif /* SAHARA_POLL_MODE */ + +/****************************************************************************** +* The following is the implementation of the Dynamic Power Management +* functionality. +******************************************************************************/ +#ifdef SAHARA_POWER_MANAGEMENT + +static bool sah_dpm_init_flag; + +/* dynamic power management information for the sahara driver */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static struct device_driver sah_dpm_driver = { + .name = "sahara_", + .bus = &platform_bus_type, +#else +static struct platform_driver sah_dpm_driver = { + .driver.name = "sahara_", + .driver.bus = &platform_bus_type, +#endif + .suspend = sah_dpm_suspend, + .resume = sah_dpm_resume +}; + +/* dynamic power management information for the sahara HW device */ +static struct platform_device sah_dpm_device = { + .name = "sahara_", + .id = 1, +}; + +/*! +******************************************************************************* +* Initilaizes the dynamic power managment functionality +* +* @brief Initialization of the Dynamic Power Management functionality +* +* @return 0 = success; failed otherwise +*/ +int sah_dpm_init() +{ + int status; + + /* dpm is not asserted */ + sah_dpm_flag = FALSE; + + /* register the driver to the kernel */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + status = os_register_to_driver(&sah_dpm_driver); +#else + status = os_register_to_driver(&sah_dpm_driver.driver); +#endif + + if (status == 0) { + /* register a single sahara chip */ + /*status = platform_device_register(&sah_dpm_device); */ + status = os_register_a_device(&sah_dpm_device); + + /* if something went awry, unregister the driver */ + if (status != 0) { + /*driver_unregister(&sah_dpm_driver); */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + os_unregister_from_driver(&sah_dpm_driver); +#else + os_unregister_from_driver(&sah_dpm_driver.driver); +#endif + sah_dpm_init_flag = FALSE; + } else { + /* if everything went okay, flag that life is good */ + sah_dpm_init_flag = TRUE; + } + } + + /* let the kernel know how it went */ + return status; + +} + +/*! +******************************************************************************* +* Unregister the dynamic power managment functionality +* +* @brief Unregister the Dynamic Power Management functionality +* +*/ +void sah_dpm_close() +{ + /* if dynamic power management was initilaized, kill it */ + if (sah_dpm_init_flag == TRUE) { + /*driver_unregister(&sah_dpm_driver); */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + os_unregister_from_driver(&sah_dpm_driver); +#else + os_unregister_from_driver(&sah_dpm_driver.driver); +#endif + /*platform_device_register(&sah_dpm_device); */ + os_unregister_a_device(&sah_dpm_device); + } +} + +/*! +******************************************************************************* +* Callback routine defined by the Linux Device Model / Dynamic Power management +* extension. It sets a global flag to disallow the driver to enter queued items +* into Sahara's DAR. +* +* It allows the current active descriptor chains to complete before it returns +* +* @brief Suspends the driver +* +* @param dev contains device information +* @param state contains state information +* @param level level of shutdown +* +* @return 0 = success; failed otherwise +*/ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_suspend(struct device *dev, uint32_t state, uint32_t level) +#else +static int sah_dpm_suspend(struct platform_device *dev, pm_message_t state) +#endif +{ + sah_Head_Desc *entry = NULL; + os_lock_context_t lock_flags; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + switch (level) { + case SUSPEND_DISABLE: + /* Assert dynamic power management. This stops the driver from + * entering queued requests to Sahara */ + sah_dpm_flag = TRUE; + break; + + case SUSPEND_SAVE_STATE: + break; + + case SUSPEND_POWER_DOWN: + /* hopefully between the DISABLE call and this one, the outstanding + * work Sahara was doing complete. this checks (and waits) for + * those entries that were already active on Sahara to complete */ + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + do { + entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + } while (entry != NULL); + os_unlock_restore_context(desc_queue_lock, lock_flags); + + /* now we kill the clock so the control circuitry isn't sucking + * any power */ + mxc_clks_disable(SAHARA2_CLK); + break; + } +#else + /* Assert dynamic power management. This stops the driver from + * entering queued requests to Sahara */ + sah_dpm_flag = TRUE; + + /* Now wait for any outstanding work Sahara was doing to complete. + * this checks (and waits) for + * those entries that were already active on Sahara to complete */ + do { + /* lock queue while searching */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_ON_SAHARA); + os_unlock_restore_context(desc_queue_lock, lock_flags); + } while (entry != NULL); + + /* now we kill the clock so the control circuitry isn't sucking + * any power */ + { + struct clk *clk = clk_get(NULL, "sahara_clk"); + if (clk != ERR_PTR(ENOENT)) { + clk_disable(clk); + } + } +#endif + + return 0; +} + +/*! +******************************************************************************* +* Callback routine defined by the Linux Device Model / Dynamic Power management +* extension. It cleears a global flag to allow the driver to enter queued items +* into Sahara's DAR. +* +* It primes the mechanism to start depleting the queue +* +* @brief Resumes the driver +* +* @param dev contains device information +* @param level level of resumption +* +* @return 0 = success; failed otherwise +*/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) +static int sah_dpm_resume(struct device *dev, uint32_t level) +#else +static int sah_dpm_resume(struct platform_device *dev) +#endif +{ + sah_Head_Desc *entry = NULL; + os_lock_context_t lock_flags; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) + switch (level) { + case RESUME_POWER_ON: + /* enable Sahara's clock */ + mxc_clks_enable(SAHARA2_CLK); + break; + + case RESUME_RESTORE_STATE: + break; + + case RESUME_ENABLE: + /* Disable dynamic power managment. This allows the driver to put + * entries into Sahara's DAR */ + sah_dpm_flag = FALSE; + + /* find a pending entry to prime the pump */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_PENDING); + if (entry != NULL) { + sah_Queue_Manager_Prime(entry); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); + break; + } +#else + { + /* enable Sahara's clock */ + struct clk *clk = clk_get(NULL, "sahara_clk"); + + if (clk != ERR_PTR(ENOENT)) { + clk_enable(clk); + } + } + sah_dpm_flag = FALSE; + + /* find a pending entry to prime the pump */ + os_lock_save_context(desc_queue_lock, lock_flags); + entry = sah_Find_With_State(SAH_STATE_PENDING); + if (entry != NULL) { + sah_Queue_Manager_Prime(entry); + } + os_unlock_restore_context(desc_queue_lock, lock_flags); +#endif + return 0; +} + +#endif /* SAHARA_POWER_MANAGEMENT */ diff --git a/drivers/mxc/security/sahara2/sf_util.c b/drivers/mxc/security/sahara2/sf_util.c new file mode 100644 index 000000000000..b1cc2597f183 --- /dev/null +++ b/drivers/mxc/security/sahara2/sf_util.c @@ -0,0 +1,1390 @@ +/* + * Copyright 2004-2009 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 sf_util.c +* +* @brief Security Functions component API - Utility functions +* +* These are the 'Sahara api' functions which are used by the higher-level +* FSL SHW API to build and then execute descriptor chains. +*/ + + +#include "sf_util.h" +#include <adaptor.h> + +#ifdef DIAG_SECURITY_FUNC +#include <diagnostic.h> +#endif /*DIAG_SECURITY_FUNC*/ + + +#ifdef __KERNEL__ +EXPORT_SYMBOL(sah_Append_Desc); +EXPORT_SYMBOL(sah_Append_Link); +EXPORT_SYMBOL(sah_Create_Link); +EXPORT_SYMBOL(sah_Create_Key_Link); +EXPORT_SYMBOL(sah_Destroy_Link); +EXPORT_SYMBOL(sah_Descriptor_Chain_Execute); +EXPORT_SYMBOL(sah_insert_mdha_algorithm); +EXPORT_SYMBOL(sah_insert_skha_algorithm); +EXPORT_SYMBOL(sah_insert_skha_mode); +EXPORT_SYMBOL(sah_insert_skha_modulus); +EXPORT_SYMBOL(sah_Descriptor_Chain_Destroy); +EXPORT_SYMBOL(sah_add_two_in_desc); +EXPORT_SYMBOL(sah_add_in_key_desc); +EXPORT_SYMBOL(sah_add_two_out_desc); +EXPORT_SYMBOL(sah_add_in_out_desc); +EXPORT_SYMBOL(sah_add_key_out_desc); +#endif + +#ifdef DEBUG_REWORK +#ifndef __KERNEL__ +#include <stdio.h> +#define os_printk printf +#endif +#endif + +/** + * Convert fsl_shw_hash_alg_t to mdha mode bits. + * + * Index must be maintained in order of fsl_shw_hash_alg_t enumeration!!! + */ +const uint32_t sah_insert_mdha_algorithm[] = +{ + [FSL_HASH_ALG_MD5] = sah_insert_mdha_algorithm_md5, + [FSL_HASH_ALG_SHA1] = sah_insert_mdha_algorithm_sha1, + [FSL_HASH_ALG_SHA224] = sah_insert_mdha_algorithm_sha224, + [FSL_HASH_ALG_SHA256] = sah_insert_mdha_algorithm_sha256, +}; + +/** + * Header bits for Algorithm field of SKHA header + * + * Index value must be kept in sync with fsl_shw_key_alg_t + */ +const uint32_t sah_insert_skha_algorithm[] = +{ + [FSL_KEY_ALG_HMAC] = 0x00000040, + [FSL_KEY_ALG_AES] = sah_insert_skha_algorithm_aes, + [FSL_KEY_ALG_DES] = sah_insert_skha_algorithm_des, + [FSL_KEY_ALG_TDES] = sah_insert_skha_algorithm_tdes, + [FSL_KEY_ALG_ARC4] = sah_insert_skha_algorithm_arc4, +}; + + +/** + * Header bits for MODE field of SKHA header + * + * Index value must be kept in sync with fsl_shw_sym_mod_t + */ +const uint32_t sah_insert_skha_mode[] = +{ + [FSL_SYM_MODE_STREAM] = sah_insert_skha_mode_ecb, + [FSL_SYM_MODE_ECB] = sah_insert_skha_mode_ecb, + [FSL_SYM_MODE_CBC] = sah_insert_skha_mode_cbc, + [FSL_SYM_MODE_CTR] = sah_insert_skha_mode_ctr, +}; + + +/** + * Header bits to set CTR modulus size. These have parity + * included to allow XOR insertion of values. + * + * @note Must be kept in sync with fsl_shw_ctr_mod_t + */ +const uint32_t sah_insert_skha_modulus[] = +{ + [FSL_CTR_MOD_8] = 0x00000000, /**< 2**8 */ + [FSL_CTR_MOD_16] = 0x80000200, /**< 2**16 */ + [FSL_CTR_MOD_24] = 0x80000400, /**< 2**24 */ + [FSL_CTR_MOD_32] = 0x00000600, /**< 2**32 */ + [FSL_CTR_MOD_40] = 0x80000800, /**< 2**40 */ + [FSL_CTR_MOD_48] = 0x00000a00, /**< 2**48 */ + [FSL_CTR_MOD_56] = 0x00000c00, /**< 2**56 */ + [FSL_CTR_MOD_64] = 0x80000e00, /**< 2**64 */ + [FSL_CTR_MOD_72] = 0x80001000, /**< 2**72 */ + [FSL_CTR_MOD_80] = 0x00001200, /**< 2**80 */ + [FSL_CTR_MOD_88] = 0x00001400, /**< 2**88 */ + [FSL_CTR_MOD_96] = 0x80001600, /**< 2**96 */ + [FSL_CTR_MOD_104] = 0x00001800, /**< 2**104 */ + [FSL_CTR_MOD_112] = 0x80001a00, /**< 2**112 */ + [FSL_CTR_MOD_120] = 0x80001c00, /**< 2**120 */ + [FSL_CTR_MOD_128] = 0x00001e00 /**< 2**128 */ +}; + + +/****************************************************************************** +* Internal function declarations +******************************************************************************/ +static fsl_shw_return_t sah_Create_Desc( + const sah_Mem_Util *mu, + sah_Desc ** desc, + int head, + uint32_t header, + sah_Link * link1, + sah_Link * link2); + + +/** + * Create a descriptor chain using the the header and links passed in as + * parameters. The newly created descriptor will be added to the end of + * the descriptor chain passed. + * + * If @a desc_head points to a NULL value, then a sah_Head_Desc will be created + * as the first descriptor. Otherwise a sah_Desc will be created and appended. + * + * @pre + * + * - None + * + * @post + * + * - A descriptor has been created from the header, link1 and link2. + * + * - The newly created descriptor has been appended to the end of + * desc_head, or its location stored into the location pointed to by + * @a desc_head. + * + * - On allocation failure, @a link1 and @a link2 will be destroyed., and + * @a desc_head will be untouched. + * + * @brief Create and append descriptor chain, inserting header and links + * pointing to link1 and link2 + * + * @param mu Memory functions + * @param header Value of descriptor header to be added + * @param desc_head Pointer to head of descriptor chain to append new desc + * @param link1 Pointer to sah_Link 1 (or NULL) + * @param link2 Pointer to sah_Link 2 (or NULL) + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Append_Desc( + const sah_Mem_Util *mu, + sah_Head_Desc **desc_head, + const uint32_t header, + sah_Link *link1, + sah_Link *link2) +{ + fsl_shw_return_t status; + sah_Desc *desc; + sah_Desc *desc_ptr; + + + status = sah_Create_Desc(mu, (sah_Desc**)&desc, (*desc_head == NULL), + header, link1, link2); + /* append newly created descriptor to end of current chain */ + if (status == FSL_RETURN_OK_S) { + if (*desc_head == NULL) { + (*desc_head) = (sah_Head_Desc*)desc; + (*desc_head)->out1_ptr = NULL; + (*desc_head)->out2_ptr = NULL; + + } else { + desc_ptr = (sah_Desc*)*desc_head; + while (desc_ptr->next != NULL) { + desc_ptr = desc_ptr->next; + } + desc_ptr->next = desc; + } + } + + return status; +} + + +/** + * Releases the memory allocated by the Security Function library for + * descriptors, links and any internally allocated memory referenced in the + * given chain. Note that memory allocated by user applications is not + * released. + * + * @post The @a desc_head pointer will be set to NULL to prevent further use. + * + * @brief Destroy a descriptor chain and free memory of associated links + * + * @param mu Memory functions + * @param desc_head Pointer to head of descriptor chain to be freed + * + * @return none + */ +void sah_Descriptor_Chain_Destroy ( + const sah_Mem_Util *mu, + sah_Head_Desc **desc_head) +{ + sah_Desc *desc_ptr = &(*desc_head)->desc; + sah_Head_Desc *desc_head_ptr = (sah_Head_Desc *)desc_ptr; + + while (desc_ptr != NULL) { + register sah_Desc *next_desc_ptr; + + if (desc_ptr->ptr1 != NULL) { + sah_Destroy_Link(mu, desc_ptr->ptr1); + } + if (desc_ptr->ptr2 != NULL) { + sah_Destroy_Link(mu, desc_ptr->ptr2); + } + + next_desc_ptr = desc_ptr->next; + + /* Be sure to free head descriptor as such */ + if (desc_ptr == (sah_Desc*)desc_head_ptr) { + mu->mu_free_head_desc(mu->mu_ref, desc_head_ptr); + } else { + mu->mu_free_desc(mu->mu_ref, desc_ptr); + } + + desc_ptr = next_desc_ptr; + } + + *desc_head = NULL; +} + + +#ifndef NO_INPUT_WORKAROUND +/** + * Reworks the link chain + * + * @brief Reworks the link chain + * + * @param mu Memory functions + * @param link Pointer to head of link chain to be reworked + * + * @return none + */ +static fsl_shw_return_t sah_rework_link_chain( + const sah_Mem_Util *mu, + sah_Link* link) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + int found_potential_problem = 0; + uint32_t total_data = 0; +#ifdef DEBUG_REWORK + sah_Link* first_link = link; +#endif + + if ((link->flags & SAH_OUTPUT_LINK)) { + return status; + } + + while (link != NULL) { + total_data += link->len; + + /* Only non-key Input Links are affected by the DMA flush-to-FIFO + * problem */ + + /* If have seen problem and at end of chain... */ + if (found_potential_problem && (link->next == NULL) && + (total_data > 16)) { + /* insert new 1-byte link */ + sah_Link* new_tail_link = mu->mu_alloc_link(mu->mu_ref); + if (new_tail_link == NULL) { + status = FSL_RETURN_NO_RESOURCE_S; + } else { +#ifdef DEBUG_REWORK + sah_Link* dump_link = first_link; + while (dump_link != NULL) { + uint32_t i; + unsigned bytes_to_dump = (dump_link->len > 32) ? + 32 : dump_link->len; + os_printk("(rework)Link %p: %p/%u/%p\n", dump_link, + dump_link->data, dump_link->len, + dump_link->next); + if (!(dump_link->flags & SAH_STORED_KEY_INFO)) { + os_printk("(rework)Data %p: ", dump_link->data); + for (i = 0; i < bytes_to_dump; i++) { + os_printk("%02X ", dump_link->data[i]); + } + os_printk("\n"); + } + else { + os_printk("rework)Data %p: Red key data\n", dump_link); + } + dump_link = dump_link->next; + } +#endif + link->len--; + link->next = new_tail_link; + new_tail_link->len = 1; + new_tail_link->data = link->data+link->len; + new_tail_link->flags = link->flags & ~(SAH_OWNS_LINK_DATA); + new_tail_link->next = NULL; + link = new_tail_link; +#ifdef DEBUG_REWORK + os_printk("(rework)New link chain:\n"); + dump_link = first_link; + while (dump_link != NULL) { + uint32_t i; + unsigned bytes_to_dump = (dump_link->len > 32) ? + 32 : dump_link->len; + + os_printk("Link %p: %p/%u/%p\n", dump_link, + dump_link->data, dump_link->len, + dump_link->next); + if (!(dump_link->flags & SAH_STORED_KEY_INFO)) { + os_printk("Data %p: ", dump_link->data); + for (i = 0; i < bytes_to_dump; i++) { + os_printk("%02X ", dump_link->data[i]); + } + os_printk("\n"); + } + else { + os_printk("Data %p: Red key data\n", dump_link); + } + dump_link = dump_link->next; + } +#endif + } + } else if ((link->len % 4) || ((uint32_t)link->data % 4)) { + found_potential_problem = 1; + } + + link = link->next; + } + + return status; +} + + +/** + * Rework links to avoid H/W bug + * + * @param head Beginning of descriptor chain + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t sah_rework_links( + const sah_Mem_Util *mu, + sah_Head_Desc *head) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Desc* desc = &head->desc; + + while ((status == FSL_RETURN_OK_S) && (desc != NULL)) { + if (desc->header & SAH_HDR_LLO) { + status = FSL_RETURN_ERROR_S; + break; + } + if (desc->ptr1 != NULL) { + status = sah_rework_link_chain(mu, desc->ptr1); + } + if ((status == FSL_RETURN_OK_S) && (desc->ptr2 != NULL)) { + status = sah_rework_link_chain(mu, desc->ptr2); + } + desc = desc->next; + } + + return status; +} +#endif /* NO_INPUT_WORKAROUND */ + + +/** + * Send a descriptor chain to the SAHARA driver for processing. + * + * Note that SAHARA will read the input data from and write the output data + * directly to the locations indicated during construction of the chain. + * + * @pre + * + * - None + * + * @post + * + * - @a head will have been executed on SAHARA + * - @a head Will be freed unless a SAVE flag is set + * + * @brief Execute a descriptor chain + * + * @param head Pointer to head of descriptor chain to be executed + * @param user_ctx The user context on which to execute the descriptor chain. + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Descriptor_Chain_Execute( + sah_Head_Desc *head, + fsl_shw_uco_t *user_ctx) +{ + fsl_shw_return_t status; + + /* Check for null pointer or non-multiple-of-four value */ + if ((head == NULL) || ((uint32_t)head & 0x3)) { + status = FSL_RETURN_ERROR_S; + goto out; + } + +#ifndef NO_INPUT_WORKAROUND + status = sah_rework_links(user_ctx->mem_util, head); + if (status != FSL_RETURN_OK_S) { + goto out; + } +#endif + + /* complete the information in the descriptor chain head node */ + head->user_ref = user_ctx->user_ref; + head->uco_flags = user_ctx->flags; + head->next = NULL; /* driver will use this to link chain heads */ + + status = adaptor_Exec_Descriptor_Chain(head, user_ctx); + +#ifdef DIAG_SECURITY_FUNC + if (status == FSL_RETURN_OK_S) + LOG_DIAG("after exec desc chain: status is ok\n"); + else + LOG_DIAG("after exec desc chain: status is not ok\n"); +#endif + + out: + return status; +} + + +/** + * Create Link + * + * @brief Allocate Memory for Link structure and populate using input + * parameters + * + * @post On allocation failure, @a p will be freed if #SAH_OWNS_LINK_DATA is + * p set in @a flags. + + * @param mu Memory functions + * @param link Pointer to link to be created + * @param p Pointer to data to use in link + * @param length Length of buffer 'p' in bytes + * @param flags Indicates whether memory has been allocated by the calling + * function or the security function + * + * @return FSL_RETURN_OK_S or FSL_RETURN_NO_RESOURCE_S + */ +fsl_shw_return_t sah_Create_Link( + const sah_Mem_Util *mu, + sah_Link **link, + uint8_t *p, + const size_t length, + const sah_Link_Flags flags) +{ + +#ifdef DIAG_SECURITY_FUNC + + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; + + + *link = mu->mu_alloc_link(mu->mu_ref); + + /* populate link if memory allocation successful */ + if (*link != NULL) { + (*link)->len = length; + (*link)->data = p; + (*link)->next = NULL; + (*link)->flags = flags; + status = FSL_RETURN_OK_S; + +#ifdef DIAG_SECURITY_FUNC + + LOG_DIAG("Created Link"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x", (int) *link); + LOG_DIAG(diag); + sprintf(diag," link->len = %d",(*link)->len); + LOG_DIAG(diag); + sprintf(diag," link->data = 0x%x",(int) (*link)->data); + LOG_DIAG(diag); + sprintf(diag," link->flags = 0x%x",(*link)->flags); + LOG_DIAG(diag); + LOG_DIAG(" link->next = NULL"); +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Link failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + + /* Free memory previously allocated by the security function layer for + link data. Note that the memory being pointed to will be zeroed before + being freed, for security reasons. */ + if (flags & SAH_OWNS_LINK_DATA) { + mu->mu_memset(mu->mu_ref, p, 0x00, length); + mu->mu_free(mu->mu_ref, p); + } + } + + return status; +} + + +/** + * Create Key Link + * + * @brief Allocate Memory for Link structure and populate using key info + * object + * + * @param mu Memory functions + * @param link Pointer to store address of link to be created + * @param key_info Pointer to Key Info object to be referenced + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Create_Key_Link( + const sah_Mem_Util *mu, + sah_Link **link, + fsl_shw_sko_t *key_info) +{ +#ifdef DIAG_SECURITY_FUNC_UGLY + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; + sah_Link_Flags flags = 0; + + + *link = mu->mu_alloc_link(mu->mu_ref); + + /* populate link if memory allocation successful */ + if (*link != NULL) { + (*link)->len = key_info->key_length; + + if (key_info->flags & FSL_SKO_KEY_PRESENT) { + (*link)->data = key_info->key; + status = FSL_RETURN_OK_S; + } else { + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + + if (key_info->keystore == NULL) { + /* System Keystore */ + (*link)->slot = key_info->handle; + (*link)->ownerid = key_info->userid; + (*link)->data = 0; + flags |= SAH_STORED_KEY_INFO; + status = FSL_RETURN_OK_S; + } else { +#ifdef FSL_HAVE_SCC2 + /* User Keystore */ + fsl_shw_kso_t *keystore = key_info->keystore; + /* Note: the key data is stored here, but the address has to + * be converted to a partition and offset in the kernel. + * This will be calculated in kernel space, based on the + * list of partitions held by the users context. + */ + (*link)->data = + keystore->slot_get_address(keystore->user_data, + key_info->handle); + + flags |= SAH_IN_USER_KEYSTORE; + status = FSL_RETURN_OK_S; +#else + /* User keystores only supported in SCC2 */ + status = FSL_RETURN_BAD_FLAG_S; +#endif /* FSL_HAVE_SCC2 */ + + } + } else { + /* the flag is bad. Should never get here */ + status = FSL_RETURN_BAD_FLAG_S; + } + } + + (*link)->next = NULL; + (*link)->flags = flags; + +#ifdef DIAG_SECURITY_FUNC_UGLY + if (status == FSL_RETURN_OK_S) { + LOG_DIAG("Created Link"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x", (int) *link); + LOG_DIAG(diag); + sprintf(diag," link->len = %d", (*link)->len); + LOG_DIAG(diag); + if (key_info->flags & FSL_SKO_KEY_ESTABLISHED) { + sprintf(diag," link->slot = 0x%x", (*link)->slot); + LOG_DIAG(diag); + } else { + sprintf(diag," link->data = 0x%x", (int)(*link)->data); + LOG_DIAG(diag); + } + sprintf(diag," link->flags = 0x%x", (*link)->flags); + LOG_DIAG(diag); + LOG_DIAG(" link->next = NULL"); + } +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + if (status == FSL_RETURN_BAD_FLAG_S) { + mu->mu_free_link(mu->mu_ref, *link); + *link = NULL; +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Creation of sah_Key_Link failed due to bad key flag!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + } + + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Key_Link failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + } + + return status; +} + + +/** + * Append Link + * + * @brief Allocate Memory for Link structure and append it to the end of + * the link chain. + * + * @post On allocation failure, @a p will be freed if #SAH_OWNS_LINK_DATA is + * p set in @a flags. + * + * @param mu Memory functions + * @param link_head Pointer to (head of) existing link chain + * @param p Pointer to data to use in link + * @param length Length of buffer 'p' in bytes + * @param flags Indicates whether memory has been allocated by the calling + * function or the security function + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_Append_Link( + const sah_Mem_Util *mu, + sah_Link *link_head, + uint8_t *p, + const size_t length, + const sah_Link_Flags flags) +{ + sah_Link* new_link; + fsl_shw_return_t status; + + + status = sah_Create_Link(mu, &new_link, p, length, flags); + + if (status == FSL_RETURN_OK_S) { + /* chase for the tail */ + while (link_head->next != NULL) { + link_head = link_head->next; + } + + /* and add new tail */ + link_head->next = new_link; + } + + return status; +} + + +/** + * Create and populate a single descriptor + * + * The pointer and length fields will be be set based on the chains passed in + * as @a link1 and @a link2. + * + * @param mu Memory utility suite + * @param desc Location to store pointer of new descriptor + * @param head_desc Non-zero if this will be first in chain; zero otherwise + * @param header The Sahara header value to store in the descriptor + * @param link1 A value (or NULL) for the first ptr + * @param link2 A value (or NULL) for the second ptr + * + * @post If allocation succeeded, the @a link1 and @link2 will be linked into + * the descriptor. If allocation failed, those link structues will be + * freed, and the @a desc will be unchanged. + * + * @return A return code of type #fsl_shw_return_t. + */ +static fsl_shw_return_t sah_Create_Desc( + const sah_Mem_Util *mu, + sah_Desc ** desc, + int head_desc, + uint32_t header, + sah_Link * link1, + sah_Link * link2) +{ + fsl_shw_return_t status = FSL_RETURN_NO_RESOURCE_S; +#ifdef DIAG_SECURITY_FUNC_UGLY + char diag[50]; +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + + + if (head_desc != 0) { + *desc = (sah_Desc *)mu->mu_alloc_head_desc(mu->mu_ref); + } else { + *desc = mu->mu_alloc_desc(mu->mu_ref); + } + + /* populate descriptor if memory allocation successful */ + if ((*desc) != NULL) { + sah_Link* temp_link; + + status = FSL_RETURN_OK_S; + (*desc)->header = header; + + temp_link = (*desc)->ptr1 = link1; + (*desc)->len1 = 0; + while (temp_link != NULL) { + (*desc)->len1 += temp_link->len; + temp_link = temp_link->next; + } + + temp_link = (*desc)->ptr2 = link2; + (*desc)->len2 = 0; + while (temp_link != NULL) { + (*desc)->len2 += temp_link->len; + temp_link = temp_link->next; + } + + (*desc)->next = NULL; + +#ifdef DIAG_SECURITY_FUNC_UGLY + LOG_DIAG("Created Desc"); + LOG_DIAG("------------"); + sprintf(diag," address = 0x%x",(int) *desc); + LOG_DIAG(diag); + sprintf(diag," desc->header = 0x%x",(*desc)->header); + LOG_DIAG(diag); + sprintf(diag," desc->len1 = %d",(*desc)->len1); + LOG_DIAG(diag); + sprintf(diag," desc->ptr1 = 0x%x",(int) ((*desc)->ptr1)); + LOG_DIAG(diag); + sprintf(diag," desc->len2 = %d",(*desc)->len2); + LOG_DIAG(diag); + sprintf(diag," desc->ptr2 = 0x%x",(int) ((*desc)->ptr2)); + LOG_DIAG(diag); + sprintf(diag," desc->next = 0x%x",(int) ((*desc)->next)); + LOG_DIAG(diag); +#endif /*DIAG_SECURITY_FUNC_UGLY*/ + } else { +#ifdef DIAG_SECURITY_FUNC + LOG_DIAG("Allocation of memory for sah_Desc failed!\n"); +#endif /*DIAG_SECURITY_FUNC*/ + + /* Destroy the links, otherwise the memory allocated by the Security + Function layer for the links (and possibly the data within the links) + will be lost */ + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + + +/** + * Destroy a link (chain) and free memory + * + * @param mu memory utility suite + * @param link The Link to destroy + * + * @return none + */ +void sah_Destroy_Link( + const sah_Mem_Util *mu, + sah_Link * link) +{ + + while (link != NULL) { + sah_Link * next_link = link->next; + + if (link->flags & SAH_OWNS_LINK_DATA) { + /* zero data for security purposes */ + mu->mu_memset(mu->mu_ref, link->data, 0x00, link->len); + mu->mu_free(mu->mu_ref, link->data); + } + + link->data = NULL; + link->next = NULL; + mu->mu_free_link(mu->mu_ref, link); + + link = next_link; + } +} + + +/** + * Add descriptor where both links are inputs. + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_in_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + const uint8_t* in2, + uint32_t in2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link* link1 = NULL; + sah_Link* link2 = NULL; + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, SAH_USES_LINK_DATA); + } + + if ( (in2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + +/*! + * Add descriptor where neither link needs sync + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_d_desc(uint32_t header, + const uint8_t * in1, + uint32_t in1_length, + const uint8_t * in2, + uint32_t in2_length, + const sah_Mem_Util * mu, + sah_Head_Desc ** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + printk("Entering sah_add_two_d_desc \n"); + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if ((in2 != NULL) && (status == FSL_RETURN_OK_S)) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} /* sah_add_two_d_desc() */ + +/** + * Add descriptor where both links are inputs, the second one being a key. + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_key_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + fsl_shw_sko_t* key_info, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + goto out; + } + + status = sah_Create_Key_Link(mu, &link2, key_info); + + + if (status != FSL_RETURN_OK_S) { + goto out; + } + + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + +out: + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + + +/** + * Create two links using keys allocated in the scc + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The second input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_key_key_desc(uint32_t header, + fsl_shw_sko_t *key_info1, + fsl_shw_sko_t *key_info2, + const sah_Mem_Util *mu, + sah_Head_Desc **desc_chain) +{ + fsl_shw_return_t status; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + status = sah_Create_Key_Link(mu, &link1, key_info1); + + if (status == FSL_RETURN_OK_S) { + status = sah_Create_Key_Link(mu, &link2, key_info2); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where first link is input, the second is a changing key + * + * @param header The Sahara header value for the descriptor. + * @param in1 The first input buffer (or NULL) + * @param in1_length Size of @a in1 + * @param[out] in2 The key for output + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_keyout_desc(uint32_t header, + const uint8_t* in1, + uint32_t in1_length, + fsl_shw_sko_t* key_info, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + if (in1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in1, in1_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + goto out; + } + + status = sah_Create_Key_Link(mu, &link2, key_info); + + if (status != FSL_RETURN_OK_S) { + goto out; + } + +link2->flags |= SAH_OUTPUT_LINK; /* mark key for output */ +status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + +out: + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + +/** + * Add descriptor where both links are outputs. + * + * @param header The Sahara header value for the descriptor. + * @param out1 The first output buffer (or NULL) + * @param out1_length Size of @a out1 + * @param[out] out2 The second output buffer (or NULL) + * @param out2_length Size of @a out2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_two_out_desc(uint32_t header, + uint8_t* out1, + uint32_t out1_length, + uint8_t* out2, + uint32_t out2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (out1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) out1, out1_length, + SAH_OUTPUT_LINK | SAH_USES_LINK_DATA); + } + + if ( (out2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out2, out2_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where first link is output, second is output + * + * @param header The Sahara header value for the descriptor. + * @param out1 The first output buffer (or NULL) + * @param out1_length Size of @a out1 + * @param[out] in2 The input buffer (or NULL) + * @param in2_length Size of @a in2 + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_out_in_desc(uint32_t header, + uint8_t* out1, + uint32_t out1_length, + const uint8_t* in2, + uint32_t in2_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + + if (out1 != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) out1, out1_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if ( (in2 != NULL) && (status == FSL_RETURN_OK_S) ) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) in2, in2_length, + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where link1 is input buffer, link2 is output buffer. + * + * @param header The Sahara header value for the descriptor. + * @param in The input buffer + * @param in_length Size of the input buffer + * @param[out] out The output buffer + * @param out_length Size of the output buffer + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_in_out_desc(uint32_t header, + const uint8_t* in, uint32_t in_length, + uint8_t* out, uint32_t out_length, + const sah_Mem_Util* mu, + sah_Head_Desc** desc_chain) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + if (in != NULL) { + status = sah_Create_Link(mu, &link1, + (sah_Oct_Str) in, in_length, + SAH_USES_LINK_DATA); + } + + if ((status == FSL_RETURN_OK_S) && (out != NULL)) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out, out_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + } + + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } else { + status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + } + + return status; +} + + +/** + * Add descriptor where link1 is a key, link2 is output buffer. + * + * @param header The Sahara header value for the descriptor. + * @param key_info Key information + * @param[out] out The output buffer + * @param out_length Size of the output buffer + * @param mu Memory functions + * @param[in,out] desc_chain Chain to start or append to + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_add_key_out_desc(uint32_t header, + const fsl_shw_sko_t *key_info, + uint8_t* out, uint32_t out_length, + const sah_Mem_Util *mu, + sah_Head_Desc **desc_chain) +{ + fsl_shw_return_t status; + sah_Link *link1 = NULL; + sah_Link *link2 = NULL; + + status = sah_Create_Key_Link(mu, &link1, (fsl_shw_sko_t *) key_info); + if (status != FSL_RETURN_OK_S) { + goto out; + } + + + if (out != NULL) { + status = sah_Create_Link(mu, &link2, + (sah_Oct_Str) out, out_length, + SAH_OUTPUT_LINK | + SAH_USES_LINK_DATA); + if (status != FSL_RETURN_OK_S) { + goto out; + } + } +status = sah_Append_Desc(mu, desc_chain, header, link1, link2); + +out: + if (status != FSL_RETURN_OK_S) { + if (link1 != NULL) { + sah_Destroy_Link(mu, link1); + } + if (link2 != NULL) { + sah_Destroy_Link(mu, link2); + } + } + + return status; +} + + +/** + * Sanity checks the user context object fields to ensure that they make some + * sense before passing the uco as a parameter + * + * @brief Verify the user context object + * + * @param uco user context object + * + * @return A return code of type #fsl_shw_return_t. + */ +fsl_shw_return_t sah_validate_uco(fsl_shw_uco_t *uco) +{ + fsl_shw_return_t status = FSL_RETURN_OK_S; + + + /* check if file is opened */ + if (uco->sahara_openfd < 0) { + status = FSL_RETURN_NO_RESOURCE_S; + } else { + /* check flag combination: the only invalid setting of the + * blocking and callback flags is blocking with callback. So check + * for that + */ + if ((uco->flags & (FSL_UCO_BLOCKING_MODE | FSL_UCO_CALLBACK_MODE)) == + (FSL_UCO_BLOCKING_MODE | FSL_UCO_CALLBACK_MODE)) { + status = FSL_RETURN_BAD_FLAG_S; + } else { + /* check that memory utilities have been attached */ + if (uco->mem_util == NULL) { + status = FSL_RETURN_MEMORY_ERROR_S; + } else { + /* must have pool of at least 1, even for blocking mode */ + if (uco->pool_size == 0) { + status = FSL_RETURN_ERROR_S; + } else { + /* if callback flag is set, it better have a callback + * routine */ + if (uco->flags & FSL_UCO_CALLBACK_MODE) { + if (uco->callback == NULL) { + status = FSL_RETURN_INTERNAL_ERROR_S; + } + } + } + } + } + } + + return status; +} + + +/** + * Perform any post-processing on non-blocking results. + * + * For instance, free descriptor chains, compare authentication codes, ... + * + * @param user_ctx User context object + * @param result_info A set of results + */ +void sah_Postprocess_Results(fsl_shw_uco_t* user_ctx, sah_results* result_info) +{ + unsigned i; + + /* for each result returned */ + for (i = 0; i < *result_info->actual; i++) { + sah_Head_Desc* desc = result_info->results[i].user_desc; + uint8_t* out1 = desc->out1_ptr; + uint8_t* out2 = desc->out2_ptr; + uint32_t len = desc->out_len; + + /* + * For now, tne only post-processing besides freeing the + * chain is the need to check the auth code for fsl_shw_auth_decrypt(). + * + * If other uses are required in the future, this code will probably + * need a flag in the sah_Head_Desc (or a function pointer!) to + * determine what needs to be done. + */ + if ((out1 != NULL) && (out2 != NULL)) { + unsigned j; + for (j = 0; j < len; j++) { + if (out1[j] != out2[j]) { + /* Problem detected. Change result. */ + result_info->results[i].code = FSL_RETURN_AUTH_FAILED_S; + break; + } + } + /* free allocated 'calced_auth' */ + user_ctx->mem_util-> + mu_free(user_ctx->mem_util->mu_ref, out1); + } + + /* Free the API-created chain, unless tagged as not-from-API */ + if (! (desc->uco_flags & FSL_UCO_SAVE_DESC_CHAIN)) { + sah_Descriptor_Chain_Destroy(user_ctx->mem_util, &desc); + } + } +} + + +/* End of sf_util.c */ diff --git a/drivers/mxc/security/scc2_driver.c b/drivers/mxc/security/scc2_driver.c new file mode 100644 index 000000000000..5ebd6022f1a5 --- /dev/null +++ b/drivers/mxc/security/scc2_driver.c @@ -0,0 +1,2306 @@ +/* + * Copyright 2004-2009 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 scc2_driver.c + * + * This is the driver code for the Security Controller version 2 (SCC2). It's + * interaction with the Linux kernel is from calls to #scc_init() when the + * driver is loaded, and #scc_cleanup() should the driver be unloaded. The + * driver uses locking and (task-sleep/task-wakeup) functions from the kernel. + * It also registers itself to handle the interrupt line(s) from the SCC. New + * to this version of the driver is an interface providing access to the secure + * partitions. This is in turn exposed to the API user through the + * fsl_shw_smalloc() series of functions. Other drivers in the kernel may use + * the remaining API functions to get at the services of the SCC. The main + * service provided is the Secure Memory, which allows encoding and decoding of + * secrets with a per-chip secret key. + * + * The SCC is single-threaded, and so is this module. When the scc_crypt() + * routine is called, it will lock out other accesses to the function. If + * another task is already in the module, the subsequent caller will spin on a + * lock waiting for the other access to finish. + * + * Note that long crypto operations could cause a task to spin for a while, + * preventing other kernel work (other than interrupt processing) to get done. + * + * The external (kernel module) interface is through the following functions: + * @li scc_get_configuration() @li scc_crypt() @li scc_zeroize_memories() @li + * scc_monitor_security_failure() @li scc_stop_monitoring_security_failure() + * @li scc_set_sw_alarm() @li scc_read_register() @li scc_write_register() @li + * scc_allocate_partition() @li scc_initialize_partition @li + * scc_release_partition() @li scc_diminish_permissions @li + * scc_encrypt_region() @li scc_decrypt_region() @li scc_virt_to_phys + * + * All other functions are internal to the driver. + */ + +#include "sahara2/include/portable_os.h" +#include "scc2_internals.h" +#include <linux/delay.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + +#include <linux/device.h> +#include <mach/clock.h> +#include <linux/device.h> + +#else + +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> + +#endif + +#include <linux/dmapool.h> + +/** + * This is the set of errors which signal that access to the SCM RAM has + * failed or will fail. + */ +#define SCM_ACCESS_ERRORS \ + (SCM_ERRSTAT_ILM | SCM_ERRSTAT_SUP | SCM_ERRSTAT_ERC_MASK) + +/****************************************************************************** + * + * Global / Static Variables + * + *****************************************************************************/ + +#ifdef SCC_REGISTER_DEBUG + +#define REG_PRINT_BUFFER_SIZE 200 + +static char reg_print_buffer[REG_PRINT_BUFFER_SIZE]; + +typedef char *(*reg_print_routine_t) (uint32_t value, char *print_buffer, + int buf_size); + +#endif + +/** + * This is type void* so that a) it cannot directly be dereferenced, + * and b) pointer arithmetic on it will function in a 'normal way' for + * the offsets in scc_defines.h + * + * scc_base is the location in the iomap where the SCC's registers + * (and memory) start. + * + * The referenced data is declared volatile so that the compiler will + * not make any assumptions about the value of registers in the SCC, + * and thus will always reload the register into CPU memory before + * using it (i.e. wherever it is referenced in the driver). + * + * This value should only be referenced by the #SCC_READ_REGISTER and + * #SCC_WRITE_REGISTER macros and their ilk. All dereferences must be + * 32 bits wide. + */ +static volatile void *scc_base; + +/** Array to hold function pointers registered by + #scc_monitor_security_failure() and processed by + #scc_perform_callbacks() */ +static void (*scc_callbacks[SCC_CALLBACK_SIZE]) (void); +/*SCC need IRAM's base address but use only the partitions allocated for it.*/ +uint32_t scm_ram_phys_base = IRAM_BASE_ADDR; + +void *scm_ram_base = NULL; + +/** Calculated once for quick reference to size of the unreserved space in + * RAM in SCM. + */ +uint32_t scm_memory_size_bytes; + +/** Structure returned by #scc_get_configuration() */ +static scc_config_t scc_configuration = { + .driver_major_version = SCC_DRIVER_MAJOR_VERSION, + .driver_minor_version = SCC_DRIVER_MINOR_VERSION_2, + .scm_version = -1, + .smn_version = -1, + .block_size_bytes = -1, + .partition_size_bytes = -1, + .partition_count = -1, +}; + +/** Internal flag to know whether SCC is in Failed state (and thus many + * registers are unavailable). Once it goes failed, it never leaves it. */ +static volatile enum scc_status scc_availability = SCC_STATUS_INITIAL; + +/** Flag to say whether interrupt handler has been registered for + * SMN interrupt */ +static int smn_irq_set = 0; + +/** Flag to say whether interrupt handler has been registered for + * SCM interrupt */ +static int scm_irq_set = 0; + +/** This lock protects the #scc_callbacks list as well as the @c + * callbacks_performed flag in #scc_perform_callbacks. Since the data this + * protects may be read or written from either interrupt or base level, all + * operations should use the irqsave/irqrestore or similar to make sure that + * interrupts are inhibited when locking from base level. + */ +static os_lock_t scc_callbacks_lock = NULL; + +/** + * Ownership of this lock prevents conflicts on the crypto operation in the + * SCC. + */ +static os_lock_t scc_crypto_lock = NULL; + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)) +/** Pointer to SCC's clock information. Initialized during scc_init(). */ +static struct clk *scc_clk = NULL; +#endif + +/** The lookup table for an 8-bit value. Calculated once + * by #scc_init_ccitt_crc(). + */ +static uint16_t scc_crc_lookup_table[256]; + +/****************************************************************************** + * + * Function Implementations - Externally Accessible + * + *****************************************************************************/ + +/** + * Allocate a partition of secure memory + * + * @param smid_value Value to use for the SMID register. Must be 0 for + * kernel mode access. + * @param[out] part_no (If successful) Assigned partition number. + * @param[out] part_base Kernel virtual address of the partition. + * @param[out] part_phys Physical address of the partition. + * + * @return + */ +scc_return_t scc_allocate_partition(uint32_t smid_value, + int *part_no, + void **part_base, uint32_t *part_phys) +{ + uint32_t i; + os_lock_context_t irq_flags = 0; /* for IRQ save/restore */ + int local_part; + scc_return_t retval = SCC_RET_FAIL; + void *base_addr = NULL; + uint32_t reg_value; + + local_part = -1; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + if (scc_availability == SCC_STATUS_UNIMPLEMENTED) { + goto out; + } + + /* ACQUIRE LOCK to prevent others from using crypto or acquiring a + * partition. Note that crypto operations could take a long time, so the + * calling process could potentially spin for some time. + */ + os_lock_save_context(scc_crypto_lock, irq_flags); + + do { + /* Find current state of partition ownership */ + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + + /* Search for a free one */ + for (i = 0; i < scc_configuration.partition_count; i++) { + if (((reg_value >> (SCM_POWN_SHIFT * i)) + & SCM_POWN_MASK) == SCM_POWN_PART_FREE) { + break; /* found a free one */ + } + } + if (i == local_part) { + /* found this one last time, and failed to allocated it */ + pr_debug(KERN_ERR "Partition %d cannot be allocated\n", + i); + goto out; + } + if (i >= scc_configuration.partition_count) { + retval = SCC_RET_INSUFFICIENT_SPACE; /* all used up */ + goto out; + } + + pr_debug + ("SCC2: Attempting to allocate partition %i, owners:%08x\n", + i, SCC_READ_REGISTER(SCM_PART_OWNERS_REG)); + + local_part = i; + /* Store SMID to grab a partition */ + SCC_WRITE_REGISTER(SCM_SMID0_REG + + SCM_SMID_WIDTH * (local_part), smid_value); + mdelay(2); + + /* Now make sure it is ours... ? */ + reg_value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + + if (((reg_value >> (SCM_POWN_SHIFT * (local_part))) + & SCM_POWN_MASK) != SCM_POWN_PART_OWNED) { + continue; /* try for another */ + } + base_addr = scm_ram_base + + (local_part * scc_configuration.partition_size_bytes); + break; + } while (1); + +out: + + /* Free the lock */ + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + /* If the base address was assigned, then a partition was successfully + * acquired. + */ + if (base_addr != NULL) { + pr_debug("SCC2 Part owners: %08x, engaged: %08x\n", + reg_value, SCC_READ_REGISTER(SCM_PART_ENGAGED_REG)); + pr_debug("SCC2 MAP for part %d: %08x\n", + local_part, + SCC_READ_REGISTER(SCM_ACC0_REG + 8 * local_part)); + + /* Copy the partition information to the data structures passed by the + * user. + */ + *part_no = local_part; + *part_base = base_addr; + *part_phys = (uint32_t) scm_ram_phys_base + + (local_part * scc_configuration.partition_size_bytes); + retval = SCC_RET_OK; + + pr_debug + ("SCC2 partition engaged. Kernel address: %p. Physical " + "address: %p, pfn: %08x\n", *part_base, (void *)*part_phys, + __phys_to_pfn(*part_phys)); + } + + return retval; +} /* allocate_partition() */ + +/** + * Release a partition of secure memory + * + * @param part_base Kernel virtual address of the partition to be released. + * + * @return SCC_RET_OK if successful. + */ +scc_return_t scc_release_partition(void *part_base) +{ + uint32_t partition_no; + + if (part_base == NULL) { + return SCC_RET_FAIL; + } + + /* Ensure that this is a proper partition location */ + partition_no = SCM_PART_NUMBER((uint32_t) part_base); + + pr_debug("SCC2: Attempting to release partition %i, owners:%08x\n", + partition_no, SCC_READ_REGISTER(SCM_PART_OWNERS_REG)); + + /* check that the partition is ours to de-establish */ + if (!host_owns_partition(partition_no)) { + return SCC_RET_FAIL; + } + + /* TODO: The state of the zeroize engine (SRS field in the Command Status + * Register) should be examined before issuing the zeroize command here. + * To make the driver thread-safe, a lock should be taken out before + * issuing the check and released after the zeroize command has been + * issued. + */ + + /* Zero the partition to release it */ + scc_write_register(SCM_ZCMD_REG, + (partition_no << SCM_ZCMD_PART_SHIFT) | + (ZCMD_DEALLOC_PART << SCM_ZCMD_CCMD_SHIFT)); + mdelay(2); + + pr_debug("SCC2: done releasing partition %i, owners:%08x\n", + partition_no, SCC_READ_REGISTER(SCM_PART_OWNERS_REG)); + + /* Check that the de-assignment went correctly */ + if (host_owns_partition(partition_no)) { + return SCC_RET_FAIL; + } + + return SCC_RET_OK; +} + +/** + * Diminish the permissions on a partition of secure memory + * + * @param part_base Kernel virtual address of the partition. + * @param permissions ORed values of the type SCM_PERM_* which will be used as + * initial partition permissions. SHW API users should use + * the FSL_PERM_* definitions instead. + * + * @return SCC_RET_OK if successful. + */ +scc_return_t scc_diminish_permissions(void *part_base, uint32_t permissions) +{ + uint32_t partition_no; + uint32_t permissions_requested; + permissions_requested = permissions; + + /* ensure that this is a proper partition location */ + partition_no = SCM_PART_NUMBER((uint32_t) part_base); + + /* invert the permissions, masking out unused bits */ + permissions = (~permissions) & SCM_PERM_MASK; + + /* attempt to diminish the permissions */ + scc_write_register(SCM_ACC0_REG + 8 * partition_no, permissions); + mdelay(2); + + /* Reading it back puts it into the original form */ + permissions = SCC_READ_REGISTER(SCM_ACC0_REG + 8 * partition_no); + if (permissions == permissions_requested) { + pr_debug("scc_partition_diminish_perms: successful\n"); + pr_debug("scc_partition_diminish_perms: successful\n"); + return SCC_RET_OK; + } + pr_debug("scc_partition_diminish_perms: not successful\n"); + + return SCC_RET_FAIL; +} + +extern scc_partition_status_t scc_partition_status(void *part_base) +{ + uint32_t part_no; + uint32_t part_owner; + + /* Determine the partition number from the address */ + part_no = SCM_PART_NUMBER((uint32_t) part_base); + + /* Check if the partition is implemented */ + if (part_no >= scc_configuration.partition_count) { + return SCC_PART_S_UNUSABLE; + } + + /* Determine the value of the partition owners register */ + part_owner = (SCC_READ_REGISTER(SCM_PART_OWNERS_REG) + >> (part_no * SCM_POWN_SHIFT)) & SCM_POWN_MASK; + + switch (part_owner) { + case SCM_POWN_PART_OTHER: + return SCC_PART_S_UNAVAILABLE; + break; + case SCM_POWN_PART_FREE: + return SCC_PART_S_AVAILABLE; + break; + case SCM_POWN_PART_OWNED: + /* could be allocated or engaged*/ + if (partition_engaged(part_no)) { + return SCC_PART_S_ENGAGED; + } else { + return SCC_PART_S_ALLOCATED; + } + break; + case SCM_POWN_PART_UNUSABLE: + default: + return SCC_PART_S_UNUSABLE; + break; + } +} + +/** + * Calculate the physical address from the kernel virtual address. + * + * @param address Kernel virtual address of data in an Secure Partition. + * @return Physical address of said data. + */ +uint32_t scc_virt_to_phys(void *address) +{ + return (uint32_t) address - (uint32_t) scm_ram_base + + (uint32_t) scm_ram_phys_base; +} + +/** + * Engage partition of secure memory + * + * @param part_base (kernel) Virtual + * @param UMID NULL, or 16-byte UMID for partition security + * @param permissions ORed values from fsl_shw_permission_t which + * will be used as initial partiition permissions. + * + * @return SCC_RET_OK if successful. + */ + +scc_return_t +scc_engage_partition(void *part_base, + const uint8_t *UMID, uint32_t permissions) +{ + uint32_t partition_no; + uint8_t *UMID_base = part_base + 0x10; + uint32_t *MAP_base = part_base; + uint8_t i; + + partition_no = SCM_PART_NUMBER((uint32_t) part_base); + + if (!host_owns_partition(partition_no) || + partition_engaged(partition_no) || + !(SCC_READ_REGISTER(SCM_SMID0_REG + (partition_no * 8)) == 0)) { + + return SCC_RET_FAIL; + } + + if (UMID != NULL) { + for (i = 0; i < 16; i++) { + UMID_base[i] = UMID[i]; + } + } + + MAP_base[0] = permissions; + + udelay(20); + + /* Check that the partition was engaged correctly, and that it has the + * proper permissions. + */ + + if ((!partition_engaged(partition_no)) || + (permissions != + SCC_READ_REGISTER(SCM_ACC0_REG + 8 * partition_no))) { + return SCC_RET_FAIL; + } + + return SCC_RET_OK; +} + +/*****************************************************************************/ +/* fn scc_init() */ +/*****************************************************************************/ +/** + * Initialize the driver at boot time or module load time. + * + * Register with the kernel as the interrupt handler for the SCC interrupt + * line(s). + * + * Map the SCC's register space into the driver's memory space. + * + * Query the SCC for its configuration and status. Save the configuration in + * #scc_configuration and save the status in #scc_availability. Called by the + * kernel. + * + * Do any locking/wait queue initialization which may be necessary. + * + * The availability fuse may be checked, depending on platform. + */ +static int scc_init(void) +{ + uint32_t smn_status; + int i; + int return_value = -EIO; /* assume error */ + + if (scc_availability == SCC_STATUS_INITIAL) { + + /* Set this until we get an initial reading */ + scc_availability = SCC_STATUS_CHECKING; + + /* Initialize the constant for the CRC function */ + scc_init_ccitt_crc(); + + /* initialize the callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)) + mxc_clks_enable(SCC_CLK); +#else + scc_clk = clk_get(NULL, "scc_clk"); + if (scc_clk != ERR_PTR(ENOENT)) { + clk_enable(scc_clk); + } +#endif + + /* Set up the hardware access locks */ + scc_callbacks_lock = os_lock_alloc_init(); + scc_crypto_lock = os_lock_alloc_init(); + if (scc_callbacks_lock == NULL || scc_crypto_lock == NULL) { + os_printk(KERN_ERR + "SCC2: Failed to allocate context locks. Exiting.\n"); + goto out; + } + + /* See whether there is an SCC available */ + if (0 && !SCC_ENABLED()) { + os_printk(KERN_ERR + "SCC2: Fuse for SCC is set to disabled. Exiting.\n"); + goto out; + } + /* Map the SCC (SCM and SMN) memory on the internal bus into + kernel address space */ + scc_base = (void *)IO_ADDRESS(SCC_BASE); + if (scc_base == NULL) { + os_printk(KERN_ERR + "SCC2: Register mapping failed. Exiting.\n"); + goto out; + } + + /* If that worked, we can try to use the SCC */ + /* Get SCM into 'clean' condition w/interrupts cleared & + disabled */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + + /* Clear error status register */ + (void)SCC_READ_REGISTER(SCM_ERR_STATUS_REG); + + /* + * There is an SCC. Determine its current state. Side effect + * is to populate scc_config and scc_availability + */ + smn_status = scc_grab_config_values(); + + /* Try to set up interrupt handler(s) */ + if (scc_availability != SCC_STATUS_OK) { + goto out; + } + + if (cpu_is_mx51_rev(CHIP_REV_2_0) < 0) + scm_ram_phys_base += 0x8000; + + scm_ram_base = (void *)ioremap_nocache(scm_ram_phys_base, + scc_configuration. + partition_count * + scc_configuration. + partition_size_bytes); + if (scm_ram_base == NULL) { + os_printk(KERN_ERR + "SCC2: RAM failed to remap: %p for %d bytes\n", + (void *)scm_ram_phys_base, + scc_configuration.partition_count * + scc_configuration.partition_size_bytes); + goto out; + } + pr_debug("SCC2: RAM at Physical %p / Virtual %p\n", + (void *)scm_ram_phys_base, scm_ram_base); + + pr_debug("Secure Partition Table: Found %i partitions\n", + scc_configuration.partition_count); + + if (setup_interrupt_handling() != 0) { + unsigned err_cond; + /** + * The error could be only that the SCM interrupt was + * not set up. This interrupt is always masked, so + * that is not an issue. + * The SMN's interrupt may be shared on that line, it + * may be separate, or it may not be wired. Do what + * is necessary to check its status. + * Although the driver is coded for possibility of not + * having SMN interrupt, the fact that there is one + * means it should be available and used. + */ +#ifdef USE_SMN_INTERRUPT + err_cond = !smn_irq_set; /* Separate. Check SMN binding */ +#elif !defined(NO_SMN_INTERRUPT) + err_cond = !scm_irq_set; /* Shared. Check SCM binding */ +#else + err_cond = FALSE; /* SMN not wired at all. Ignore. */ +#endif + if (err_cond) { + /* setup was not able to set up SMN interrupt */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + goto out; + } + } + + /* interrupt handling returned non-zero */ + /* Get SMN into 'clean' condition w/interrupts cleared & + enabled */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT + | SMN_COMMAND_ENABLE_INTERRUPT); + + out: + /* + * If status is SCC_STATUS_UNIMPLEMENTED or is still + * SCC_STATUS_CHECKING, could be leaving here with the driver partially + * initialized. In either case, cleanup (which will mark the SCC as + * UNIMPLEMENTED). + */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_UNIMPLEMENTED) { + scc_cleanup(); + } else { + return_value = 0; /* All is well */ + } + } + /* ! STATUS_INITIAL */ + os_printk(KERN_ALERT "SCC2: Driver Status is %s\n", + (scc_availability == SCC_STATUS_INITIAL) ? "INITIAL" : + (scc_availability == SCC_STATUS_CHECKING) ? "CHECKING" : + (scc_availability == + SCC_STATUS_UNIMPLEMENTED) ? "UNIMPLEMENTED" + : (scc_availability == + SCC_STATUS_OK) ? "OK" : (scc_availability == + SCC_STATUS_FAILED) ? "FAILED" : + "UNKNOWN"); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); +#endif + + return return_value; +} /* scc_init */ + +/*****************************************************************************/ +/* fn scc_cleanup() */ +/*****************************************************************************/ +/** + * Perform cleanup before driver/module is unloaded by setting the machine + * state close to what it was when the driver was loaded. This function is + * called when the kernel is shutting down or when this driver is being + * unloaded. + * + * A driver like this should probably never be unloaded, especially if there + * are other module relying upon the callback feature for monitoring the SCC + * status. + * + * In any case, cleanup the callback table (by clearing out all of the + * pointers). Deregister the interrupt handler(s). Unmap SCC registers. + * + * Note that this will not release any partitions that have been allocated. + * + */ +static void scc_cleanup(void) +{ + int i; + + /******************************************************/ + + /* Mark the driver / SCC as unusable. */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; + + /* Clear out callback table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + scc_callbacks[i] = 0; + } + + /* If SCC has been mapped in, clean it up and unmap it */ + if (scc_base) { + /* For the SCM, disable interrupts. */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + + /* For the SMN, clear and disable interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT); + } + + /* Now that interrupts cannot occur, disassociate driver from the interrupt + * lines. + */ + + /* Deregister SCM interrupt handler */ + if (scm_irq_set) { + os_deregister_interrupt(INT_SCC_SCM); + } + + /* Deregister SMN interrupt handler */ + if (smn_irq_set) { +#ifdef USE_SMN_INTERRUPT + os_deregister_interrupt(INT_SCC_SMN); +#endif + } + + /* Finally, release the mapped memory */ + iounmap(scm_ram_base); + + if (scc_callbacks_lock != NULL) + os_lock_deallocate(scc_callbacks_lock); + + if (scc_crypto_lock != NULL) + os_lock_deallocate(scc_crypto_lock); + + /*Disabling SCC Clock*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); + clk_put(scc_clk); +#endif + pr_debug("SCC2 driver cleaned up.\n"); + +} /* scc_cleanup */ + +/*****************************************************************************/ +/* fn scc_get_configuration() */ +/*****************************************************************************/ +scc_config_t *scc_get_configuration(void) +{ + /* + * If some other driver calls scc before the kernel does, make sure that + * this driver's initialization is performed. + */ + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /** + * If there is no SCC, yet the driver exists, the value -1 will be in + * the #scc_config_t fields for other than the driver versions. + */ + return &scc_configuration; +} /* scc_get_configuration */ + +/*****************************************************************************/ +/* fn scc_zeroize_memories() */ +/*****************************************************************************/ +scc_return_t scc_zeroize_memories(void) +{ + scc_return_t return_status = SCC_RET_FAIL; + + return return_status; +} /* scc_zeroize_memories */ + +/*****************************************************************************/ +/* fn scc_set_sw_alarm() */ +/*****************************************************************************/ +void scc_set_sw_alarm(void) +{ + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Update scc_availability based on current SMN status. This might + * perform callbacks. + */ + (void)scc_update_state(); + + /* if everything is OK, make it fail */ + if (scc_availability == SCC_STATUS_OK) { + + /* sound the alarm (and disable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_SET_SOFTWARE_ALARM); + + scc_availability = SCC_STATUS_FAILED; /* Remember what we've done */ + + /* In case SMN interrupt is not available, tell the world */ + scc_perform_callbacks(); + } + + return; +} /* scc_set_sw_alarm */ + +/*****************************************************************************/ +/* fn scc_monitor_security_failure() */ +/*****************************************************************************/ +scc_return_t scc_monitor_security_failure(void callback_func(void)) +{ + int i; + os_lock_context_t irq_flags; /* for IRQ save/restore */ + scc_return_t return_status = SCC_RET_TOO_MANY_FUNCTIONS; + int function_stored = FALSE; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + os_lock_save_context(scc_callbacks_lock, irq_flags); + + /* Search through table looking for empty slot */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + if (function_stored) { + /* Saved duplicate earlier. Clear this later one. */ + scc_callbacks[i] = NULL; + } + /* Exactly one copy is now stored */ + return_status = SCC_RET_OK; + break; + } else if (scc_callbacks[i] == NULL && !function_stored) { + /* Found open slot. Save it and remember */ + scc_callbacks[i] = callback_func; + return_status = SCC_RET_OK; + function_stored = TRUE; + } + } + + /* Free the lock */ + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + return return_status; +} /* scc_monitor_security_failure */ + +/*****************************************************************************/ +/* fn scc_stop_monitoring_security_failure() */ +/*****************************************************************************/ +void scc_stop_monitoring_security_failure(void callback_func(void)) +{ + os_lock_context_t irq_flags; /* for IRQ save/restore */ + int i; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* Acquire lock of callbacks table. Could be spin_lock_irq() if this + * routine were just called from base (not interrupt) level + */ + os_lock_save_context(scc_callbacks_lock, irq_flags); + + /* Search every entry of the table for this function */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + if (scc_callbacks[i] == callback_func) { + scc_callbacks[i] = NULL; /* found instance - clear it out */ + break; + } + } + + /* Free the lock */ + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + return; +} /* scc_stop_monitoring_security_failure */ + +/*****************************************************************************/ +/* fn scc_read_register() */ +/*****************************************************************************/ +scc_return_t scc_read_register(int register_offset, uint32_t * value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (register_offset != SMN_BB_DEC_REG && /* write only! */ + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if ((return_status = + check_register_accessible(register_offset, + smn_status, + scm_status)) == + SCC_RET_OK) { + *value = SCC_READ_REGISTER(register_offset); + } + } + } + + return return_status; +} /* scc_read_register */ + +/*****************************************************************************/ +/* fn scc_write_register() */ +/*****************************************************************************/ +scc_return_t scc_write_register(int register_offset, uint32_t value) +{ + scc_return_t return_status = SCC_RET_FAIL; + uint32_t smn_status; + uint32_t scm_status; + + if (scc_availability == SCC_STATUS_INITIAL) { + scc_init(); + } + + /* First layer of protection -- completely unaccessible SCC */ + if (scc_availability != SCC_STATUS_UNIMPLEMENTED) { + + /* Second layer -- that offset is valid */ + if (!((register_offset == SCM_STATUS_REG) || /* These registers are */ + (register_offset == SCM_VERSION_REG) || /* Read Only */ + (register_offset == SMN_BB_CNT_REG) || + (register_offset == SMN_TIMER_REG)) && + check_register_offset(register_offset) == SCC_RET_OK) { + + /* Get current status / update local state */ + smn_status = scc_update_state(); + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* + * Third layer - verify that the register being requested is + * available in the current state of the SCC. + */ + if (check_register_accessible + (register_offset, smn_status, scm_status) == 0) { + SCC_WRITE_REGISTER(register_offset, value); + return_status = SCC_RET_OK; + } + } + } + + return return_status; +} /* scc_write_register() */ + +/****************************************************************************** + * + * Function Implementations - Internal + * + *****************************************************************************/ + +/*****************************************************************************/ +/* fn scc_irq() */ +/*****************************************************************************/ +/** + * This is the interrupt handler for the SCC. + * + * This function checks the SMN Status register to see whether it + * generated the interrupt, then it checks the SCM Status register to + * see whether it needs attention. + * + * If an SMN Interrupt is active, then the SCC state set to failure, and + * #scc_perform_callbacks() is invoked to notify any interested parties. + * + * The SCM Interrupt should be masked, as this driver uses polling to determine + * when the SCM has completed a crypto or zeroing operation. Therefore, if the + * interrupt is active, the driver will just clear the interrupt and (re)mask. + */ +OS_DEV_ISR(scc_irq) +{ + uint32_t smn_status; + uint32_t scm_status; + int handled = 0; /* assume interrupt isn't from SMN */ +#if defined(USE_SMN_INTERRUPT) + int smn_irq = INT_SCC_SMN; /* SMN interrupt is on a line by itself */ +#elif defined (NO_SMN_INTERRUPT) + int smn_irq = -1; /* not wired to CPU at all */ +#else + int smn_irq = INT_SCC_SCM; /* SMN interrupt shares a line with SCM */ +#endif + + /* Update current state... This will perform callbacks... */ + smn_status = scc_update_state(); + + /* SMN is on its own interrupt line. Verify the IRQ was triggered + * before clearing the interrupt and marking it handled. */ + if ((os_dev_get_irq() == smn_irq) && + (smn_status & SMN_STATUS_SMN_STATUS_IRQ)) { + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT); + handled++; /* tell kernel that interrupt was handled */ + } + + /* Check on the health of the SCM */ + scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + + /* The driver masks interrupts, so this should never happen. */ + if (os_dev_get_irq() == INT_SCC_SCM) { + /* but if it does, try to prevent it in the future */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + handled++; + } + + /* Any non-zero value of handled lets kernel know we got something */ + os_dev_isr_return(handled); +} + +/*****************************************************************************/ +/* fn scc_perform_callbacks() */ +/*****************************************************************************/ +/** Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void) +{ + static int callbacks_performed = 0; + unsigned long irq_flags; /* for IRQ save/restore */ + int i; + + /* Acquire lock of callbacks table and callbacks_performed flag */ + os_lock_save_context(scc_callbacks_lock, irq_flags); + + if (!callbacks_performed) { + callbacks_performed = 1; + + /* Loop over all of the entries in the table */ + for (i = 0; i < SCC_CALLBACK_SIZE; i++) { + /* If not null, ... */ + if (scc_callbacks[i]) { + scc_callbacks[i] (); /* invoke the callback routine */ + } + } + } + + os_unlock_restore_context(scc_callbacks_lock, irq_flags); + + return; +} + +/*****************************************************************************/ +/* fn scc_update_state() */ +/*****************************************************************************/ +/** + * Make certain SCC is still running. + * + * Side effect is to update #scc_availability and, if the state goes to failed, + * run #scc_perform_callbacks(). + * + * (If #SCC_BRINGUP is defined, bring SCC to secure state if it is found to be + * in health check state) + * + * @return Current value of #SMN_STATUS_REG register. + */ +static uint32_t scc_update_state(void) +{ + uint32_t smn_status_register = SMN_STATE_FAIL; + int smn_state; + + /* if FAIL or UNIMPLEMENTED, don't bother */ + if (scc_availability == SCC_STATUS_CHECKING || + scc_availability == SCC_STATUS_OK) { + + smn_status_register = SCC_READ_REGISTER(SMN_STATUS_REG); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + +#ifdef SCC_BRINGUP + /* If in Health Check while booting, try to 'bringup' to Secure mode */ + if (scc_availability == SCC_STATUS_CHECKING && + smn_state == SMN_STATE_HEALTH_CHECK) { + /* Code up a simple algorithm for the ASC */ + SCC_WRITE_REGISTER(SMN_SEQ_START_REG, 0xaaaa); + SCC_WRITE_REGISTER(SMN_SEQ_END_REG, 0x5555); + SCC_WRITE_REGISTER(SMN_SEQ_CHECK_REG, 0x5555); + /* State should be SECURE now */ + smn_status_register = SCC_READ_REGISTER(SMN_STATUS); + smn_state = smn_status_register & SMN_STATUS_STATE_MASK; + } +#endif + + /* + * State should be SECURE or NON_SECURE for operation of the part. If + * FAIL, mark failed (i.e. limited access to registers). Any other + * state, mark unimplemented, as the SCC is unuseable. + */ + if (smn_state == SMN_STATE_SECURE + || smn_state == SMN_STATE_NON_SECURE) { + /* Healthy */ + scc_availability = SCC_STATUS_OK; + } else if (smn_state == SMN_STATE_FAIL) { + scc_availability = SCC_STATUS_FAILED; /* uh oh - unhealthy */ + scc_perform_callbacks(); + os_printk(KERN_ERR "SCC2: SCC went into FAILED mode\n"); + } else { + /* START, ZEROIZE RAM, HEALTH CHECK, or unknown */ + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* unuseable */ + os_printk(KERN_ERR + "SCC2: SCC declared UNIMPLEMENTED\n"); + } + } + /* if availability is initial or ok */ + return smn_status_register; +} + +/*****************************************************************************/ +/* fn scc_init_ccitt_crc() */ +/*****************************************************************************/ +/** + * Populate the partial CRC lookup table. + * + * @return none + * + */ +static void scc_init_ccitt_crc(void) +{ + int dividend; /* index for lookup table */ + uint16_t remainder; /* partial value for a given dividend */ + int bit; /* index into bits of a byte */ + + /* + * Compute the remainder of each possible dividend. + */ + for (dividend = 0; dividend < 256; ++dividend) { + /* + * Start with the dividend followed by zeros. + */ + remainder = dividend << (8); + + /* + * Perform modulo-2 division, a bit at a time. + */ + for (bit = 8; bit > 0; --bit) { + /* + * Try to divide the current data bit. + */ + if (remainder & 0x8000) { + remainder = (remainder << 1) ^ CRC_POLYNOMIAL; + } else { + remainder = (remainder << 1); + } + } + + /* + * Store the result into the table. + */ + scc_crc_lookup_table[dividend] = remainder; + } + +} /* scc_init_ccitt_crc() */ + +/*****************************************************************************/ +/* fn grab_config_values() */ +/*****************************************************************************/ +/** + * grab_config_values() will read the SCM Configuration and SMN Status + * registers and store away version and size information for later use. + * + * @return The current value of the SMN Status register. + */ +static uint32_t scc_grab_config_values(void) +{ + uint32_t scm_version_register; + uint32_t smn_status_register = SMN_STATE_FAIL; + + if (scc_availability != SCC_STATUS_CHECKING) { + goto out; + } + scm_version_register = SCC_READ_REGISTER(SCM_VERSION_REG); + pr_debug("SCC2 Driver: SCM version is 0x%08x\n", scm_version_register); + + /* Get SMN status and update scc_availability */ + smn_status_register = scc_update_state(); + pr_debug("SCC2 Driver: SMN status is 0x%08x\n", smn_status_register); + + /* save sizes and versions information for later use */ + scc_configuration.block_size_bytes = 16; /* BPCP ? */ + scc_configuration.partition_count = + 1 + ((scm_version_register & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT); + scc_configuration.partition_size_bytes = + 1 << ((scm_version_register & SCM_VER_BPP_MASK) >> + SCM_VER_BPP_SHIFT); + scc_configuration.scm_version = + (scm_version_register & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT; + scc_configuration.smn_version = + (smn_status_register & SMN_STATUS_VERSION_ID_MASK) + >> SMN_STATUS_VERSION_ID_SHIFT; + if (scc_configuration.scm_version != SCM_MAJOR_VERSION_2) { + scc_availability = SCC_STATUS_UNIMPLEMENTED; /* Unknown version */ + } + + out: + return smn_status_register; +} /* grab_config_values */ + +/*****************************************************************************/ +/* fn setup_interrupt_handling() */ +/*****************************************************************************/ +/** + * Register the SCM and SMN interrupt handlers. + * + * Called from #scc_init() + * + * @return 0 on success + */ +static int setup_interrupt_handling(void) +{ + int smn_error_code = -1; + int scm_error_code = -1; + + /* Disnable SCM interrupts */ + SCC_WRITE_REGISTER(SCM_INT_CTL_REG, 0); + +#ifdef USE_SMN_INTERRUPT + /* Install interrupt service routine for SMN. */ + smn_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SMN, scc_irq); + if (smn_error_code != 0) { + os_printk(KERN_ERR + "SCC2 Driver: Error installing SMN Interrupt Handler: %d\n", + smn_error_code); + } else { + smn_irq_set = 1; /* remember this for cleanup */ + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); + } +#else + smn_error_code = 0; /* no problems... will handle later */ +#endif + + /* + * Install interrupt service routine for SCM (or both together). + */ + scm_error_code = os_register_interrupt(SCC_DRIVER_NAME, + INT_SCC_SCM, scc_irq); + if (scm_error_code != 0) { +#ifndef MXC + os_printk(KERN_ERR + "SCC2 Driver: Error installing SCM Interrupt Handler: %d\n", + scm_error_code); +#else + os_printk(KERN_ERR + "SCC2 Driver: Error installing SCC Interrupt Handler: %d\n", + scm_error_code); +#endif + } else { + scm_irq_set = 1; /* remember this for cleanup */ +#if defined(USE_SMN_INTERRUPT) && !defined(NO_SMN_INTERRUPT) + /* Enable SMN interrupts */ + SCC_WRITE_REGISTER(SMN_COMMAND_REG, + SMN_COMMAND_CLEAR_INTERRUPT | + SMN_COMMAND_ENABLE_INTERRUPT); +#endif + } + + /* Return an error if one was encountered */ + return scm_error_code ? scm_error_code : smn_error_code; +} /* setup_interrupt_handling */ + +/*****************************************************************************/ +/* fn scc_do_crypto() */ +/*****************************************************************************/ +/** Have the SCM perform the crypto function. + * + * Set up length register, and the store @c scm_control into control register + * to kick off the operation. Wait for completion, gather status, clear + * interrupt / status. + * + * @param byte_count number of bytes to perform in this operation + * @param scm_command Bit values to be set in @c SCM_CCMD_REG register + * + * @return 0 on success, value of #SCM_ERR_STATUS_REG on failure + */ +static uint32_t scc_do_crypto(int byte_count, uint32_t scm_command) +{ + int block_count = byte_count / SCC_BLOCK_SIZE_BYTES(); + uint32_t crypto_status; + scc_return_t ret; + + /* This seems to be necessary in order to allow subsequent cipher + * operations to succeed when a partition is deallocated/reallocated! + */ + (void)SCC_READ_REGISTER(SCM_STATUS_REG); + + /* In length register, 0 means 1, etc. */ + scm_command |= (block_count - 1) << SCM_CCMD_LENGTH_SHIFT; + + /* set modes and kick off the operation */ + SCC_WRITE_REGISTER(SCM_CCMD_REG, scm_command); + + ret = scc_wait_completion(&crypto_status); + + /* Only done bit should be on */ + if (crypto_status & SCM_STATUS_ERR) { + /* Replace with error status instead */ + crypto_status = SCC_READ_REGISTER(SCM_ERR_STATUS_REG); + pr_debug("SCM Failure: 0x%x\n", crypto_status); + if (crypto_status == 0) { + /* That came up 0. Turn on arbitrary bit to signal error. */ + crypto_status = SCM_ERRSTAT_ILM; + } + } else { + crypto_status = 0; + } + pr_debug("SCC2: Done waiting.\n"); + + return crypto_status; +} + +/** + * Encrypt a region of secure memory. + * + * @param part_base Kernel virtual address of the partition. + * @param offset_bytes Offset from the start of the partition to the plaintext + * data. + * @param byte_count Length of the region (octets). + * @param black_data Physical location to store the encrypted data. + * @param IV Value to use for the IV. + * @param cypher_mode Cyphering mode to use, specified by type + * #scc_cypher_mode_t + * + * @return SCC_RET_OK if successful. + */ +scc_return_t +scc_encrypt_region(uint32_t part_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t *black_data, + uint32_t *IV, scc_cypher_mode_t cypher_mode) +{ + os_lock_context_t irq_flags; /* for IRQ save/restore */ + scc_return_t status = SCC_RET_OK; + uint32_t crypto_status; + uint32_t scm_command; + int offset_blocks = offset_bytes / SCC_BLOCK_SIZE_BYTES(); + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_enable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_enable(scc_clk); +#endif + + scm_command = ((offset_blocks << SCM_CCMD_OFFSET_SHIFT) | + (SCM_PART_NUMBER(part_base) << SCM_CCMD_PART_SHIFT)); + + switch (cypher_mode) { + case SCC_CYPHER_MODE_CBC: + scm_command |= SCM_CCMD_AES_ENC_CBC; + break; + case SCC_CYPHER_MODE_ECB: + scm_command |= SCM_CCMD_AES_ENC_ECB; + break; + default: + status = SCC_RET_FAIL; + break; + } + + pr_debug("Received encrypt request. SCM_C_BLACK_ST_REG: %p, " + "scm_Command: %08x, length: %i (part_base: %08x, " + "offset: %i)\n", + black_data, scm_command, byte_count, part_base, offset_blocks); + + if (status != SCC_RET_OK) + goto out; + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + os_lock_save_context(scc_crypto_lock, irq_flags); + + if (status == SCC_RET_OK) { + SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, (uint32_t) black_data); + + /* Only write the IV if it will actually be used */ + if (cypher_mode == SCC_CYPHER_MODE_CBC) { + /* Write the IV register */ + SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, *(IV)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, *(IV + 1)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, *(IV + 2)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, *(IV + 3)); + } + + /* Set modes and kick off the encryption */ + crypto_status = scc_do_crypto(byte_count, scm_command); + + if (crypto_status != 0) { + pr_debug("SCM encrypt red crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + pr_debug("SCC2: Encrypted %d bytes\n", byte_count); + } + } + + os_unlock_restore_context(scc_crypto_lock, irq_flags); + +out: +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); +#endif + + return status; +} + +/* Decrypt a region into secure memory + * + * @param part_base Kernel virtual address of the partition. + * @param offset_bytes Offset from the start of the partition to store the + * plaintext data. + * @param byte_counts Length of the region (octets). + * @param black_data Physical location of the encrypted data. + * @param IV Value to use for the IV. + * @param cypher_mode Cyphering mode to use, specified by type + * #scc_cypher_mode_t + * + * @return SCC_RET_OK if successful. + */ +scc_return_t +scc_decrypt_region(uint32_t part_base, uint32_t offset_bytes, + uint32_t byte_count, uint8_t *black_data, + uint32_t *IV, scc_cypher_mode_t cypher_mode) +{ + os_lock_context_t irq_flags; /* for IRQ save/restore */ + scc_return_t status = SCC_RET_OK; + uint32_t crypto_status; + uint32_t scm_command; + int offset_blocks = offset_bytes / SCC_BLOCK_SIZE_BYTES(); + + /*Enabling SCC clock.*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_enable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_enable(scc_clk); +#endif + scm_command = ((offset_blocks << SCM_CCMD_OFFSET_SHIFT) | + (SCM_PART_NUMBER(part_base) << SCM_CCMD_PART_SHIFT)); + + switch (cypher_mode) { + case SCC_CYPHER_MODE_CBC: + scm_command |= SCM_CCMD_AES_DEC_CBC; + break; + case SCC_CYPHER_MODE_ECB: + scm_command |= SCM_CCMD_AES_DEC_ECB; + break; + default: + status = SCC_RET_FAIL; + break; + } + + pr_debug("Received decrypt request. SCM_C_BLACK_ST_REG: %p, " + "scm_Command: %08x, length: %i (part_base: %08x, " + "offset: %i)\n", + black_data, scm_command, byte_count, part_base, offset_blocks); + + if (status != SCC_RET_OK) + goto out; + + /* ACQUIRE LOCK to prevent others from using crypto or releasing slot */ + os_lock_save_context(scc_crypto_lock, irq_flags); + + if (status == SCC_RET_OK) { + status = SCC_RET_FAIL; /* reset expectations */ + SCC_WRITE_REGISTER(SCM_C_BLACK_ST_REG, (uint32_t) black_data); + + /* Write the IV register */ + SCC_WRITE_REGISTER(SCM_AES_CBC_IV0_REG, *(IV)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV1_REG, *(IV + 1)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV2_REG, *(IV + 2)); + SCC_WRITE_REGISTER(SCM_AES_CBC_IV3_REG, *(IV + 3)); + + /* Set modes and kick off the decryption */ + crypto_status = scc_do_crypto(byte_count, scm_command); + + if (crypto_status != 0) { + pr_debug("SCM decrypt black crypto failure: 0x%x\n", + crypto_status); + } else { + status = SCC_RET_OK; + pr_debug("SCC2: Decrypted %d bytes\n", byte_count); + } + } + + os_unlock_restore_context(scc_crypto_lock, irq_flags); +out: + /*Disabling the Clock when the driver is not in use.*/ +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)) + mxc_clks_disable(SCC_CLK); +#else + if (scc_clk != ERR_PTR(ENOENT)) + clk_disable(scc_clk); +#endif + return status; +} + +/*****************************************************************************/ +/* fn host_owns_partition() */ +/*****************************************************************************/ +/** + * Determine if the host owns a given partition. + * + * @internal + * + * @param part_no Partition number to query + * + * @return TRUE if the host owns the partition, FALSE otherwise. + */ + +static uint32_t host_owns_partition(uint32_t part_no) +{ + uint32_t value; + + if (part_no < scc_configuration.partition_count) { + + /* Check the partition owners register */ + value = SCC_READ_REGISTER(SCM_PART_OWNERS_REG); + if (((value >> (part_no * SCM_POWN_SHIFT)) & SCM_POWN_MASK) + == SCM_POWN_PART_OWNED) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ +/* fn partition_engaged() */ +/*****************************************************************************/ +/** + * Determine if the given partition is engaged. + * + * @internal + * + * @param part_no Partition number to query + * + * @return TRUE if the partition is engaged, FALSE otherwise. + */ + +static uint32_t partition_engaged(uint32_t part_no) +{ + uint32_t value; + + if (part_no < scc_configuration.partition_count) { + + /* Check the partition engaged register */ + value = SCC_READ_REGISTER(SCM_PART_ENGAGED_REG); + if (((value >> (part_no * SCM_PENG_SHIFT)) & 0x1) + == SCM_PENG_ENGAGED) + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ +/* fn scc_wait_completion() */ +/*****************************************************************************/ +/** + * Poll looking for end-of-cipher indication. Only used + * if @c SCC_SCM_SLEEP is not defined. + * + * @internal + * + * On a Tahiti, crypto under 230 or so bytes is done after the first loop, all + * the way up to five sets of spins for 1024 bytes. (8- and 16-byte functions + * are done when we first look. Zeroizing takes one pass around. + * + * @param scm_status Address of the SCM_STATUS register + * + * @return A return code of type #scc_return_t + */ +static scc_return_t scc_wait_completion(uint32_t * scm_status) +{ + scc_return_t ret; + int done; + int i = 0; + + /* check for completion by polling */ + do { + done = is_cipher_done(scm_status); + if (done) + break; + /* TODO: shorten this delay */ + udelay(1000); + } while (i++ < SCC_CIPHER_MAX_POLL_COUNT); + + pr_debug("SCC2: Polled DONE %d times\n", i); + if (!done) { + ret = SCC_RET_FAIL; + } + + return ret; +} /* scc_wait_completion() */ + +/*****************************************************************************/ +/* fn is_cipher_done() */ +/*****************************************************************************/ +/** + * This function returns non-zero if SCM Status register indicates + * that a cipher has terminated or some other interrupt-generating + * condition has occurred. + * + * @param scm_status Address of the SCM STATUS register + * + * @return 0 if cipher operations are finished + */ +static int is_cipher_done(uint32_t * scm_status) +{ + register unsigned status; + register int cipher_done; + + *scm_status = SCC_READ_REGISTER(SCM_STATUS_REG); + status = (*scm_status & SCM_STATUS_SRS_MASK) >> SCM_STATUS_SRS_SHIFT; + + /* + * Done when SCM is not in 'currently performing a function' states. + */ + cipher_done = ((status != SCM_STATUS_SRS_ZBUSY) + && (status != SCM_STATUS_SRS_CBUSY) + && (status != SCM_STATUS_SRS_ABUSY)); + + return cipher_done; +} /* is_cipher_done() */ + +/*****************************************************************************/ +/* fn offset_within_smn() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SMN register set. + * + * @param[in] register_offset register offset of SMN. + * + * @return 1 if true, 0 if false (not within SMN) + */ +static inline int offset_within_smn(uint32_t register_offset) +{ + return ((register_offset >= SMN_STATUS_REG) + && (register_offset <= SMN_HAC_REG)); +} + +/*****************************************************************************/ +/* fn offset_within_scm() */ +/*****************************************************************************/ +/*! + * Check that the offset is with the bounds of the SCM register set. + * + * @param[in] register_offset Register offset of SCM + * + * @return 1 if true, 0 if false (not within SCM) + */ +static inline int offset_within_scm(uint32_t register_offset) +{ + return 1; /* (register_offset >= SCM_RED_START) + && (register_offset < scm_highest_memory_address); */ +/* Although this would cause trouble for zeroize testing, this change would + * close a security hole which currently allows any kernel program to access + * any location in RED RAM. Perhaps enforce in non-SCC_DEBUG compiles? + && (register_offset <= SCM_INIT_VECTOR_1); */ +} + +/*****************************************************************************/ +/* fn check_register_accessible() */ +/*****************************************************************************/ +/** + * Given the current SCM and SMN status, verify that access to the requested + * register should be OK. + * + * @param[in] register_offset register offset within SCC + * @param[in] smn_status recent value from #SMN_STATUS_REG + * @param[in] scm_status recent value from #SCM_STATUS_REG + * + * @return #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t +check_register_accessible(uint32_t register_offset, uint32_t smn_status, + uint32_t scm_status) +{ + int error_code = SCC_RET_FAIL; + + /* Verify that the register offset passed in is not among the verboten set + * if the SMN is in Fail mode. + */ + if (offset_within_smn(register_offset)) { + if ((smn_status & SMN_STATUS_STATE_MASK) == SMN_STATE_FAIL) { + if (!((register_offset == SMN_STATUS_REG) || + (register_offset == SMN_COMMAND_REG) || + (register_offset == SMN_SEC_VIO_REG))) { + pr_debug + ("SCC2 Driver: Note: Security State is in FAIL state.\n"); + } /* register not a safe one */ + else { + /* SMN is in FAIL, but register is a safe one */ + error_code = SCC_RET_OK; + } + } /* State is FAIL */ + else { + /* State is not fail. All registers accessible. */ + error_code = SCC_RET_OK; + } + } + /* offset within SMN */ + /* Not SCM register. Check for SCM busy. */ + else if (offset_within_scm(register_offset)) { + /* This is the 'cannot access' condition in the SCM */ + if (0 /* (scm_status & SCM_STATUS_BUSY) */ + /* these are always available - rest fail on busy */ + && !((register_offset == SCM_STATUS_REG) || + (register_offset == SCM_ERR_STATUS_REG) || + (register_offset == SCM_INT_CTL_REG) || + (register_offset == SCM_VERSION_REG))) { + pr_debug + ("SCC2 Driver: Note: Secure Memory is in BUSY state.\n"); + } /* status is busy & register inaccessible */ + else { + error_code = SCC_RET_OK; + } + } + /* offset within SCM */ + return error_code; + +} /* check_register_accessible() */ + +/*****************************************************************************/ +/* fn check_register_offset() */ +/*****************************************************************************/ +/** + * Check that the offset is with the bounds of the SCC register set. + * + * @param[in] register_offset register offset of SMN. + * + * #SCC_RET_OK if ok, #SCC_RET_FAIL if not + */ +static scc_return_t check_register_offset(uint32_t register_offset) +{ + int return_value = SCC_RET_FAIL; + + /* Is it valid word offset ? */ + if (SCC_BYTE_OFFSET(register_offset) == 0) { + /* Yes. Is register within SCM? */ + if (offset_within_scm(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + /* Not in SCM. Now look within the SMN */ + else if (offset_within_smn(register_offset)) { + return_value = SCC_RET_OK; /* yes, all ok */ + } + } + + return return_value; +} + +#ifdef SCC_REGISTER_DEBUG + +/** + * Names of the SCC Registers, indexed by register number + */ +static char *scc_regnames[] = { + "SCM_VERSION_REG", + "0x04", + "SCM_INT_CTL_REG", + "SCM_STATUS_REG", + "SCM_ERR_STATUS_REG", + "SCM_FAULT_ADR_REG", + "SCM_PART_OWNERS_REG", + "SCM_PART_ENGAGED_REG", + "SCM_UNIQUE_ID0_REG", + "SCM_UNIQUE_ID1_REG", + "SCM_UNIQUE_ID2_REG", + "SCM_UNIQUE_ID3_REG", + "0x30", + "0x34", + "0x38", + "0x3C", + "0x40", + "0x44", + "0x48", + "0x4C", + "SCM_ZCMD_REG", + "SCM_CCMD_REG", + "SCM_C_BLACK_ST_REG", + "SCM_DBG_STATUS_REG", + "SCM_AES_CBC_IV0_REG", + "SCM_AES_CBC_IV1_REG", + "SCM_AES_CBC_IV2_REG", + "SCM_AES_CBC_IV3_REG", + "0x70", + "0x74", + "0x78", + "0x7C", + "SCM_SMID0_REG", + "SCM_ACC0_REG", + "SCM_SMID1_REG", + "SCM_ACC1_REG", + "SCM_SMID2_REG", + "SCM_ACC2_REG", + "SCM_SMID3_REG", + "SCM_ACC3_REG", + "SCM_SMID4_REG", + "SCM_ACC4_REG", + "SCM_SMID5_REG", + "SCM_ACC5_REG", + "SCM_SMID6_REG", + "SCM_ACC6_REG", + "SCM_SMID7_REG", + "SCM_ACC7_REG", + "SCM_SMID8_REG", + "SCM_ACC8_REG", + "SCM_SMID9_REG", + "SCM_ACC9_REG", + "SCM_SMID10_REG", + "SCM_ACC10_REG", + "SCM_SMID11_REG", + "SCM_ACC11_REG", + "SCM_SMID12_REG", + "SCM_ACC12_REG", + "SCM_SMID13_REG", + "SCM_ACC13_REG", + "SCM_SMID14_REG", + "SCM_ACC14_REG", + "SCM_SMID15_REG", + "SCM_ACC15_REG", + "SMN_STATUS_REG", + "SMN_COMMAND_REG", + "SMN_SEQ_START_REG", + "SMN_SEQ_END_REG", + "SMN_SEQ_CHECK_REG", + "SMN_BB_CNT_REG", + "SMN_BB_INC_REG", + "SMN_BB_DEC_REG", + "SMN_COMPARE_REG", + "SMN_PT_CHK_REG", + "SMN_CT_CHK_REG", + "SMN_TIMER_IV_REG", + "SMN_TIMER_CTL_REG", + "SMN_SEC_VIO_REG", + "SMN_TIMER_REG", + "SMN_HAC_REG" +}; + +/** + * Names of the Secure RAM States + */ +static char *srs_names[] = { + "SRS_Reset", + "SRS_All_Ready", + "SRS_ZeroizeBusy", + "SRS_CipherBusy", + "SRS_AllBusy", + "SRS_ZeroizeDoneCipherReady", + "SRS_CipherDoneZeroizeReady", + "SRS_ZeroizeDoneCipherBusy", + "SRS_CipherDoneZeroizeBusy", + "SRS_UNKNOWN_STATE_9", + "SRS_TransitionalA", + "SRS_TransitionalB", + "SRS_TransitionalC", + "SRS_TransitionalD", + "SRS_AllDone", + "SRS_UNKNOWN_STATE_E", + "SRS_FAIL" +}; + +/** + * Create a text interpretation of the SCM Version Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_version_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "Bpp: %u, Bpcb: %u, np: %u, maj: %u, min: %u", + (value & SCM_VER_BPP_MASK) >> SCM_VER_BPP_SHIFT, + ((value & SCM_VER_BPCB_MASK) >> SCM_VER_BPCB_SHIFT) + 1, + ((value & SCM_VER_NP_MASK) >> SCM_VER_NP_SHIFT) + 1, + (value & SCM_VER_MAJ_MASK) >> SCM_VER_MAJ_SHIFT, + (value & SCM_VER_MIN_MASK) >> SCM_VER_MIN_SHIFT); + + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & SCM_STATUS_KST_DEFAULT_KEY) ? "KST_DefaultKey " : "", + /* reserved */ + (value & SCM_STATUS_KST_WRONG_KEY) ? "KST_WrongKey " : "", + (value & SCM_STATUS_KST_BAD_KEY) ? "KST_BadKey " : "", + (value & SCM_STATUS_ERR) ? "Error " : "", + (value & SCM_STATUS_MSS_FAIL) ? "MSS_FailState " : "", + (value & SCM_STATUS_MSS_SEC) ? "MSS_SecureState " : "", + (value & SCM_STATUS_RSS_FAIL) ? "RSS_FailState " : "", + (value & SCM_STATUS_RSS_SEC) ? "RSS_SecureState " : "", + (value & SCM_STATUS_RSS_INIT) ? "RSS_Initializing " : "", + (value & SCM_STATUS_UNV) ? "UID_Invalid " : "", + (value & SCM_STATUS_BIG) ? "BigEndian " : "", + (value & SCM_STATUS_USK) ? "SecretKey " : "", + srs_names[(value & SCM_STATUS_SRS_MASK) >> + SCM_STATUS_SRS_SHIFT]); + + return print_buffer; +} + +/** + * Names of the SCM Error Codes + */ +static +char *scm_err_code[] = { + "Unknown_0", + "UnknownAddress", + "UnknownCommand", + "ReadPermErr", + "WritePermErr", + "DMAErr", + "EncBlockLenOvfl", + "KeyNotEngaged", + "ZeroizeCmdQOvfl", + "CipherCmdQOvfl", + "ProcessIntr", + "WrongKey", + "DeviceBusy", + "DMAUnalignedAddr", + "Unknown_E", + "Unknown_F", +}; + +/** + * Names of the SMN States + */ +static char *smn_state_name[] = { + "Start", + "Invalid_01", + "Invalid_02", + "Invalid_03", + "Zeroizing_04", + "Zeroizing", + "HealthCheck", + "HealthCheck_07", + "Invalid_08", + "Fail", + "Secure", + "Invalid_0B", + "NonSecure", + "Invalid_0D", + "Invalid_0E", + "Invalid_0F", + "Invalid_10", + "Invalid_11", + "Invalid_12", + "Invalid_13", + "Invalid_14", + "Invalid_15", + "Invalid_16", + "Invalid_17", + "Invalid_18", + "FailHard", + "Invalid_1A", + "Invalid_1B", + "Invalid_1C", + "Invalid_1D", + "Invalid_1E", + "Invalid_1F" +}; + +/** + * Create a text interpretation of the SCM Error Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_err_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "MID: 0x%x, %s%s ErrorCode: %s, SMSState: %s, SCMState: %s", + (value & SCM_ERRSTAT_MID_MASK) >> SCM_ERRSTAT_MID_SHIFT, + (value & SCM_ERRSTAT_ILM) ? "ILM, " : "", + (value & SCM_ERRSTAT_SUP) ? "SUP, " : "", + scm_err_code[(value & SCM_ERRSTAT_ERC_MASK) >> + SCM_ERRSTAT_ERC_SHIFT], + smn_state_name[(value & SCM_ERRSTAT_SMS_MASK) >> + SCM_ERRSTAT_SMS_SHIFT], + srs_names[(value & SCM_ERRSTAT_SRS_MASK) >> + SCM_ERRSTAT_SRS_SHIFT]); + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Zeroize Command Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_zcmd_reg(uint32_t value, char *print_buffer, int buf_size) +{ + unsigned cmd = (value & SCM_ZCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT; + + snprintf(print_buffer, buf_size, "%s %u", + (cmd == + ZCMD_DEALLOC_PART) ? "DeallocPartition" : + "(unknown function)", + (value & SCM_ZCMD_PART_MASK) >> SCM_ZCMD_PART_SHIFT); + + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Cipher Command Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_ccmd_reg(uint32_t value, char *print_buffer, int buf_size) +{ + unsigned cmd = (value & SCM_CCMD_CCMD_MASK) >> SCM_CCMD_CCMD_SHIFT; + + snprintf(print_buffer, buf_size, + "%s %u bytes, %s offset 0x%x, in partition %u", + (cmd == SCM_CCMD_AES_DEC_ECB) ? "ECB Decrypt" : (cmd == + SCM_CCMD_AES_ENC_ECB) + ? "ECB Encrypt" : (cmd == + SCM_CCMD_AES_DEC_CBC) ? "CBC Decrypt" : (cmd + == + SCM_CCMD_AES_ENC_CBC) + ? "CBC Encrypt" : "(unknown function)", + 16 + + 16 * ((value & SCM_CCMD_LENGTH_MASK) >> SCM_CCMD_LENGTH_SHIFT), + ((cmd == SCM_CCMD_AES_ENC_CBC) + || (cmd == SCM_CCMD_AES_ENC_ECB)) ? "at" : "to", + 16 * ((value & SCM_CCMD_OFFSET_MASK) >> SCM_CCMD_OFFSET_SHIFT), + (value & SCM_CCMD_PART_MASK) >> SCM_CCMD_PART_SHIFT); + + return print_buffer; +} + +/** + * Create a text interpretation of an SCM Access Permissions Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_acc_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s", + (value & SCM_PERM_NO_ZEROIZE) ? "NO_ZERO " : "", + (value & SCM_PERM_HD_SUP_DISABLE) ? "SUP_DIS " : "", + (value & SCM_PERM_HD_READ) ? "HD_RD " : "", + (value & SCM_PERM_HD_WRITE) ? "HD_WR " : "", + (value & SCM_PERM_HD_EXECUTE) ? "HD_EX " : "", + (value & SCM_PERM_TH_READ) ? "TH_RD " : "", + (value & SCM_PERM_TH_WRITE) ? "TH_WR " : "", + (value & SCM_PERM_OT_READ) ? "OT_RD " : "", + (value & SCM_PERM_OT_WRITE) ? "OT_WR " : "", + (value & SCM_PERM_OT_EXECUTE) ? "OT_EX" : ""); + + return print_buffer; +} + +/** + * Create a text interpretation of the SCM Partitions Engaged Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *scm_print_part_eng_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & 0x8000) ? "15 " : "", + (value & 0x4000) ? "14 " : "", + (value & 0x2000) ? "13 " : "", + (value & 0x1000) ? "12 " : "", + (value & 0x0800) ? "11 " : "", + (value & 0x0400) ? "10 " : "", + (value & 0x0200) ? "9 " : "", + (value & 0x0100) ? "8 " : "", + (value & 0x0080) ? "7 " : "", + (value & 0x0040) ? "6 " : "", + (value & 0x0020) ? "5 " : "", + (value & 0x0010) ? "4 " : "", + (value & 0x0008) ? "3 " : "", + (value & 0x0004) ? "2 " : "", + (value & 0x0002) ? "1 " : "", (value & 0x0001) ? "0" : ""); + + return print_buffer; +} + +/** + * Create a text interpretation of the SMN Status Register + * + * @param value The value of the register + * @param[out] print_buffer Place to store the interpretation + * @param buf_size Number of bytes available at print_buffer + * + * @return The print_buffer + */ +static +char *smn_print_status_reg(uint32_t value, char *print_buffer, int buf_size) +{ + snprintf(print_buffer, buf_size, + "Version %d %s%s%s%s%s%s%s%s%s%s%s%s%s", + (value & SMN_STATUS_VERSION_ID_MASK) >> + SMN_STATUS_VERSION_ID_SHIFT, + (value & SMN_STATUS_ILLEGAL_MASTER) ? "IllMaster " : "", + (value & SMN_STATUS_SCAN_EXIT) ? "ScanExit " : "", + (value & SMN_STATUS_PERIP_INIT) ? "PeripInit " : "", + (value & SMN_STATUS_SMN_ERROR) ? "SMNError " : "", + (value & SMN_STATUS_SOFTWARE_ALARM) ? "SWAlarm " : "", + (value & SMN_STATUS_TIMER_ERROR) ? "TimerErr " : "", + (value & SMN_STATUS_PC_ERROR) ? "PTCTErr " : "", + (value & SMN_STATUS_BITBANK_ERROR) ? "BitbankErr " : "", + (value & SMN_STATUS_ASC_ERROR) ? "ASCErr " : "", + (value & SMN_STATUS_SECURITY_POLICY_ERROR) ? "SecPlcyErr " : + "", + (value & SMN_STATUS_SEC_VIO_ACTIVE_ERROR) ? "SecVioAct " : "", + (value & SMN_STATUS_INTERNAL_BOOT) ? "IntBoot " : "", + smn_state_name[(value & SMN_STATUS_STATE_MASK) >> + SMN_STATUS_STATE_SHIFT]); + + return print_buffer; +} + +/** + * The array, indexed by register number (byte-offset / 4), of print routines + * for the SCC (SCM and SMN) registers. + */ +static reg_print_routine_t reg_printers[] = { + scm_print_version_reg, + NULL, /* 0x04 */ + NULL, /* SCM_INT_CTL_REG */ + scm_print_status_reg, + scm_print_err_status_reg, + NULL, /* SCM_FAULT_ADR_REG */ + NULL, /* SCM_PART_OWNERS_REG */ + scm_print_part_eng_reg, + NULL, /* SCM_UNIQUE_ID0_REG */ + NULL, /* SCM_UNIQUE_ID1_REG */ + NULL, /* SCM_UNIQUE_ID2_REG */ + NULL, /* SCM_UNIQUE_ID3_REG */ + NULL, /* 0x30 */ + NULL, /* 0x34 */ + NULL, /* 0x38 */ + NULL, /* 0x3C */ + NULL, /* 0x40 */ + NULL, /* 0x44 */ + NULL, /* 0x48 */ + NULL, /* 0x4C */ + scm_print_zcmd_reg, + scm_print_ccmd_reg, + NULL, /* SCM_C_BLACK_ST_REG */ + NULL, /* SCM_DBG_STATUS_REG */ + NULL, /* SCM_AES_CBC_IV0_REG */ + NULL, /* SCM_AES_CBC_IV1_REG */ + NULL, /* SCM_AES_CBC_IV2_REG */ + NULL, /* SCM_AES_CBC_IV3_REG */ + NULL, /* 0x70 */ + NULL, /* 0x74 */ + NULL, /* 0x78 */ + NULL, /* 0x7C */ + NULL, /* SCM_SMID0_REG */ + scm_print_acc_reg, /* ACC0 */ + NULL, /* SCM_SMID1_REG */ + scm_print_acc_reg, /* ACC1 */ + NULL, /* SCM_SMID2_REG */ + scm_print_acc_reg, /* ACC2 */ + NULL, /* SCM_SMID3_REG */ + scm_print_acc_reg, /* ACC3 */ + NULL, /* SCM_SMID4_REG */ + scm_print_acc_reg, /* ACC4 */ + NULL, /* SCM_SMID5_REG */ + scm_print_acc_reg, /* ACC5 */ + NULL, /* SCM_SMID6_REG */ + scm_print_acc_reg, /* ACC6 */ + NULL, /* SCM_SMID7_REG */ + scm_print_acc_reg, /* ACC7 */ + NULL, /* SCM_SMID8_REG */ + scm_print_acc_reg, /* ACC8 */ + NULL, /* SCM_SMID9_REG */ + scm_print_acc_reg, /* ACC9 */ + NULL, /* SCM_SMID10_REG */ + scm_print_acc_reg, /* ACC10 */ + NULL, /* SCM_SMID11_REG */ + scm_print_acc_reg, /* ACC11 */ + NULL, /* SCM_SMID12_REG */ + scm_print_acc_reg, /* ACC12 */ + NULL, /* SCM_SMID13_REG */ + scm_print_acc_reg, /* ACC13 */ + NULL, /* SCM_SMID14_REG */ + scm_print_acc_reg, /* ACC14 */ + NULL, /* SCM_SMID15_REG */ + scm_print_acc_reg, /* ACC15 */ + smn_print_status_reg, + NULL, /* SMN_COMMAND_REG */ + NULL, /* SMN_SEQ_START_REG */ + NULL, /* SMN_SEQ_END_REG */ + NULL, /* SMN_SEQ_CHECK_REG */ + NULL, /* SMN_BB_CNT_REG */ + NULL, /* SMN_BB_INC_REG */ + NULL, /* SMN_BB_DEC_REG */ + NULL, /* SMN_COMPARE_REG */ + NULL, /* SMN_PT_CHK_REG */ + NULL, /* SMN_CT_CHK_REG */ + NULL, /* SMN_TIMER_IV_REG */ + NULL, /* SMN_TIMER_CTL_REG */ + NULL, /* SMN_SEC_VIO_REG */ + NULL, /* SMN_TIMER_REG */ + NULL, /* SMN_HAC_REG */ +}; + +/*****************************************************************************/ +/* fn dbg_scc_read_register() */ +/*****************************************************************************/ +/** + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to read. + * + * @return The register value + * */ +uint32_t dbg_scc_read_register(uint32_t offset) +{ + uint32_t value; + char *regname = scc_regnames[offset / 4]; + + value = __raw_readl(scc_base + offset); + pr_debug("SCC2 RD: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname, + reg_printers[offset / 4] + ? reg_printers[offset / 4] (value, reg_print_buffer, + REG_PRINT_BUFFER_SIZE) + : ""); + + return value; +} + +/*****************************************************************************/ +/* fn dbg_scc_write_register() */ +/*****************************************************************************/ +/* + * Noisily read a 32-bit value to an SCC register. + * @param offset The address of the register to written. + * + * @param value The new register value + */ +void dbg_scc_write_register(uint32_t offset, uint32_t value) +{ + char *regname = scc_regnames[offset / 4]; + + pr_debug("SCC2 WR: 0x%03x : 0x%08x (%s) %s\n", offset, value, regname, + reg_printers[offset / 4] + ? reg_printers[offset / 4] (value, reg_print_buffer, + REG_PRINT_BUFFER_SIZE) + : ""); + (void)__raw_writel(value, scc_base + offset); + +} + +#endif /* SCC_REGISTER_DEBUG */ diff --git a/drivers/mxc/security/scc2_internals.h b/drivers/mxc/security/scc2_internals.h new file mode 100644 index 000000000000..475c8dec5a1b --- /dev/null +++ b/drivers/mxc/security/scc2_internals.h @@ -0,0 +1,527 @@ +/* + * Copyright 2004-2009 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 + */ +#ifndef SCC_INTERNALS_H +#define SCC_INTERNALS_H + +/** @file scc2_internals.h + * + * @brief This is intended to be the file which contains most or all of the + * code or changes need to port the driver. It also includes other definitions + * needed by the driver. + * + * This header file should only ever be included by scc2_driver.c + * + * Compile-time flags minimally needed: + * + * @li Some sort of platform flag. Currently TAHITI and MXC are understood. + * @li Some start-of-SCC consideration, such as SCC_BASE_ADDR + * + * Some changes which could be made when porting this driver: + * #SCC_SPIN_COUNT + * + */ + +#include <linux/version.h> /* Current version Linux kernel */ +#include <linux/module.h> /* Basic support for loadable modules, + printk */ +#include <linux/init.h> /* module_init, module_exit */ +#include <linux/kernel.h> /* General kernel system calls */ +#include <linux/sched.h> /* for interrupt.h */ +#include <linux/spinlock.h> + +#include <linux/io.h> /* ioremap() */ +#include <linux/interrupt.h> /* IRQ / interrupt definitions */ + + +#include <linux/mxc_scc2_driver.h> + +#if defined(MXC) + +#include <mach/iim.h> +#include <mach/mxc_scc.h> + + +/** + * This macro is used to determine whether the SCC is enabled/available + * on the platform. This macro may need to be ported. + */ +#define SCC_FUSE __raw_readl(IO_ADDRESS(IIM_BASE_ADDR + MXC_IIMHWV1)) +#define SCC_ENABLED() ((SCC_FUSE & MXC_IIMHWV1_SCC_DISABLE) == 0) + +#else /* neither TAHITI nor MXC */ + +#error Do not understand target architecture + +#endif /* TAHITI */ +/** + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +/*#define SCC_KEY_SLOTS 20*/ + + +/* Temporarily define compile-time flags to make Doxygen happy. */ +#ifdef DOXYGEN_HACK +/** @addtogroup scccompileflags */ +/** @{ */ + + +/** @def NO_SMN_INTERRUPT + * The SMN interrupt is not wired to the CPU at all. + */ +#define NO_SMN_INTERRUPT + + +/** + * Register an interrupt handler for the SMN as well as + * the SCM. In some implementations, the SMN is not connected at all (see + * #NO_SMN_INTERRUPT), and in others, it is on the same interrupt line as the + * SCM. When defining this flag, the SMN interrupt should be on a separate + * line from the SCM interrupt. + */ + +#define USE_SMN_INTERRUPT + + +/** + * Turn on generation of run-time operational, debug, and error messages + */ +#define SCC_DEBUG + + +/** + * Turn on generation of run-time logging of access to the SCM and SMN + * registers. + */ +#define SCC_REGISTER_DEBUG + + +/** + * Turn on generation of run-time logging of access to the SCM Red and + * Black memories. Will only work if #SCC_REGISTER_DEBUG is also defined. + */ +#define SCC_RAM_DEBUG + + +/** + * If the driver finds the SCC in HEALTH_CHECK state, go ahead and + * run a quick ASC to bring it to SECURE state. + */ +#define SCC_BRINGUP + + +/** + * Expected to come from platform header files or compile command line. + * This symbol must be the address of the SCC + */ +#define SCC_BASE + +/** + * This must be the interrupt line number of the SCM interrupt. + */ +#define INT_SCM + +/** + * if #USE_SMN_INTERRUPT is defined, this must be the interrupt line number of + * the SMN interrupt. + */ +#define INT_SMN + +/** + * Define the number of Stored Keys which the SCC driver will make available. + * Value shall be from 0 to 20. Default is zero (0). + */ +#define SCC_KEY_SLOTS + +/** + * Make sure that this flag is defined if compiling for a Little-Endian + * platform. Linux Kernel builds provide this flag. + */ +#define __LITTLE_ENDIAN + +/** + * Make sure that this flag is defined if compiling for a Big-Endian platform. + * Linux Kernel builds provide this flag. + */ +#define __BIG_ENDIAN + +/** + * Read a 32-bit register value from a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param offset Bus address of register to be read + * + * @return The value of the register + */ +#define readl(offset) + + +/** + * Write a 32-bit value to a register in a 'peripheral'. Standard Linux/Unix + * macro. + * + * @param value The 32-bit value to store + * @param offset Bus address of register to be written + * + * return (none) + */ +#define writel(value,offset) + + +/** @} */ /* end group scccompileflags */ + +#endif /* DOXYGEN_HACK */ + + +#ifndef SCC_KEY_SLOTS +#define SCC_KEY_SLOTS 0 + +#else + +#if (SCC_KEY_SLOTS < 0) || (SCC_KEY_SLOTS > 20) +#error Bad value for SCC_KEY_SLOTS +#endif + +#endif + + +/** + * Maximum length of key/secret value which can be stored in SCC. + */ +#define SCC_MAX_KEY_SIZE 256 + + +/** + * This is the size, in bytes, of each key slot, and therefore the maximum size + * of the wrapped key. + */ +#define SCC_KEY_SLOT_SIZE 32 + + +/* These come for free with Linux, but may need to be set in a port. */ +#ifndef __BIG_ENDIAN +#ifndef __LITTLE_ENDIAN +#error One of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#else +#ifdef __LITTLE_ENDIAN +#error Exactly one of __LITTLE_ENDIAN or __BIG_ENDIAN must be #defined +#endif +#endif + + +#ifndef SCC_CALLBACK_SIZE +/** The number of function pointers which can be stored in #scc_callbacks. + * Defaults to 4, can be overridden with compile-line argument. + */ +#define SCC_CALLBACK_SIZE 4 +#endif + + +/** Initial CRC value for CCITT-CRC calculation. */ +#define CRC_CCITT_START 0xFFFF + + +#ifdef TAHITI + +/** + * The SCC_BASE has to be SMN_BASE_ADDR on TAHITI, as the banks of + * registers are swapped in place. + */ +#define SCC_BASE SMN_BASE_ADDR + + +/** The interrupt number for the SCC (SCM only!) on Tahiti */ +#define INT_SCC_SCM 62 + + +/** Tahiti does not have the SMN interrupt wired to the CPU. */ +#define NO_SMN_INTERRUPT + + +#endif /* TAHITI */ + + +/** Number of times to spin between polling of SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_CIPHER_MAX_POLL_COUNT. */ +#define SCC_SPIN_COUNT 1000 + + +/** Number of times to polling SCC while waiting for cipher + * or zeroizing function to complete. See also #SCC_SPIN_COUNT. */ +#define SCC_CIPHER_MAX_POLL_COUNT 100 + + +/** + * @def SCC_READ_REGISTER + * Read a 32-bit value from an SCC register. Macro which depends upon + * #scc_base. Linux readl()/writel() macros operate on 32-bit quantities, as + * do SCC register reads/writes. + * + * @param offset Register offset within SCC. + * + * @return The value from the SCC's register. + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_READ_REGISTER(offset) __raw_readl(scc_base+(offset)) +#else +#define SCC_READ_REGISTER(offset) dbg_scc_read_register(offset) +#endif + + +/** + * Write a 32-bit value to an SCC register. Macro depends upon #scc_base. + * Linux readl()/writel() macros operate on 32-bit quantities, as do SCC + * register reads/writes. + * + * @param offset Register offset within SCC. + * @param value 32-bit value to store into the register + * + * @return (void) + */ +#ifndef SCC_REGISTER_DEBUG +#define SCC_WRITE_REGISTER(offset,value) \ + (void)__raw_writel(value, scc_base+(offset)) +#else +#define SCC_WRITE_REGISTER(offset,value) \ + dbg_scc_write_register(offset, value) +#endif + +/** + * Calculate the physical address of a partition from the partition number. + */ +#define SCM_PART_PHYS_ADDRESS(part) \ + ((uint32_t)scm_ram_phys_base + (part*scc_configuration.partition_size_bytes)) + +/** + * Calculate the kernel virtual address of a partition from the partition number. + */ +#define SCM_PART_ADDRESS(part) \ + (scm_ram_base + (part*scc_configuration.partition_size_bytes)) + +/** + * Calculate the partition number from the kernel virtual address. + */ +#define SCM_PART_NUMBER(address) \ + ((address - (uint32_t)scm_ram_base)/scc_configuration.partition_size_bytes) + +/** + * Calculates the byte offset into a word + * @param bp The byte (char*) pointer + * @return The offset (0, 1, 2, or 3) + */ +#define SCC_BYTE_OFFSET(bp) ((uint32_t)(bp) % sizeof(uint32_t)) + + +/** + * Converts (by rounding down) a byte pointer into a word pointer + * @param bp The byte (char*) pointer + * @return The word (uint32_t) as though it were an aligned (uint32_t*) + */ +#define SCC_WORD_PTR(bp) (((uint32_t)(bp)) & ~(sizeof(uint32_t)-1)) + + +/** + * Determine number of bytes in an SCC block + * + * @return Bytes / block + */ +#define SCC_BLOCK_SIZE_BYTES() scc_configuration.block_size_bytes + + +/** + * Maximum number of additional bytes which may be added in CRC+padding mode. + */ +#define PADDING_BUFFER_MAX_BYTES (CRC_SIZE_BYTES + sizeof(scc_block_padding)) + +/** + * Shorthand (clearer, anyway) for number of bytes in a CRC. + */ +#define CRC_SIZE_BYTES (sizeof(crc_t)) + +/** + * The polynomial used in CCITT-CRC calculation + */ +#define CRC_POLYNOMIAL 0x1021 + +/** + * Calculate CRC on one byte of data + * + * @param[in,out] running_crc A value of type crc_t where CRC is kept. This + * must be an rvalue and an lvalue. + * @param[in] byte_value The byte (uint8_t, char) to be put in the CRC + * + * @return none + */ +#define CALC_CRC(byte_value,running_crc) { \ + uint8_t data; \ + data = (0xff&(byte_value)) ^ (running_crc >> 8); \ + running_crc = scc_crc_lookup_table[data] ^ (running_crc << 8); \ +} + +/** Value of 'beginning of padding' marker in driver-provided padding */ +#define SCC_DRIVER_PAD_CHAR 0x80 + + +/** Name of the driver. Used (on Linux, anyway) when registering interrupts */ +#define SCC_DRIVER_NAME "scc" + + +/* Port -- these symbols are defined in Linux 2.6 and later. They are defined + * here for backwards compatibility because this started life as a 2.4 + * driver, and as a guide to portation to other platforms. + */ + +#if !defined(LINUX_VERSION_CODE) || LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + +#define irqreturn_t void /* Return type of an interrupt handler */ + +#define IRQ_HANDLED /* Would be '1' for handled -- as in return IRQ_HANDLED; */ + +#define IRQ_NONE /* would be '0' for not handled -- as in return IRQ_NONE; */ + +#define IRQ_RETVAL(x) /* Return x==0 (not handled) or non-zero (handled) */ + +#endif /* LINUX earlier than 2.5 */ + + +/* These are nice to have around */ +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/** Provide a typedef for the CRC which can be used in encrypt/decrypt */ +typedef uint16_t crc_t; + + +/** Gives high-level view of state of the SCC */ +enum scc_status { + SCC_STATUS_INITIAL, /**< State of driver before ever checking */ + SCC_STATUS_CHECKING, /**< Transient state while driver loading */ + SCC_STATUS_UNIMPLEMENTED, /**< SCC is non-existent or unuseable */ + SCC_STATUS_OK, /**< SCC is in Secure or Default state */ + SCC_STATUS_FAILED /**< In Failed state */ +}; + +/** + * Information about a key slot. + */ +struct scc_key_slot +{ + uint64_t owner_id; /**< Access control value. */ + uint32_t length; /**< Length of value in slot. */ + uint32_t offset; /**< Offset of value from start of RAM. */ + uint32_t status; /**< 0 = unassigned, 1 = assigned. */ + uint32_t part_ctl; /**< for the CCMD register */ +}; + +/* Forward-declare a number routines which are not part of user api */ +static int scc_init(void); +static void scc_cleanup(void); + +/* Forward defines of internal functions */ +OS_DEV_ISR(scc_irq); +/*static irqreturn_t scc_irq(int irq, void *dev_id);*/ +/** Perform callbacks registered by #scc_monitor_security_failure(). + * + * Make sure callbacks only happen once... Since there may be some reason why + * the interrupt isn't generated, this routine could be called from base(task) + * level. + * + * One at a time, go through #scc_callbacks[] and call any non-null pointers. + */ +static void scc_perform_callbacks(void); +/*static uint32_t copy_to_scc(const uint8_t* from, uint32_t to, unsigned long count_bytes, uint16_t* crc); +static uint32_t copy_from_scc(const uint32_t from, uint8_t* to,unsigned long count_bytes, uint16_t* crc); +static scc_return_t scc_strip_padding(uint8_t* from,unsigned* count_bytes_stripped);*/ +static uint32_t scc_update_state(void); +static void scc_init_ccitt_crc(void); +static uint32_t scc_grab_config_values(void); +static int setup_interrupt_handling(void); +/** + * Perform an encryption on the input. If @c verify_crc is true, a CRC must be + * calculated on the plaintext, and appended, with padding, before computing + * the ciphertext. + * + * @param[in] count_in_bytes Count of bytes of plaintext + * @param[in] data_in Pointer to the plaintext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing ciphertext + * @param[in] add_crc Flag for computing CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + */ +/*static scc_return_t scc_encrypt(uint32_t count_in_bytes, uint8_t* data_in, uint32_t scm_control, uint8_t* data_out,int add_crc, unsigned long* count_out_bytes);*/ +/** + * Perform a decryption on the input. If @c verify_crc is true, the last block + * (maybe the two last blocks) is special - it should contain a CRC and + * padding. These must be stripped and verified. + * + * @param[in] count_in_bytes Count of bytes of ciphertext + * @param[in] data_in Pointer to the ciphertext + * @param[in] scm_control Bit values for the SCM_CONTROL register + * @param[in,out] data_out Pointer for storing plaintext + * @param[in] verify_crc Flag for running CRC - 0 no, else yes + * @param[in,out] count_out_bytes Number of bytes available at @c data_out + + */ +/*static scc_return_t scc_decrypt(uint32_t count_in_bytes, uint8_t* data_in, uint32_t scm_control, uint8_t* data_out, int verify_crc, unsigned long* count_out_bytes);*/ +static uint32_t host_owns_partition(uint32_t part_no); +static uint32_t partition_engaged(uint32_t part_no); + +static scc_return_t scc_wait_completion(uint32_t* scm_status); +static int is_cipher_done(uint32_t* scm_status); +static scc_return_t check_register_accessible (uint32_t offset, + uint32_t smn_status, + uint32_t scm_status); +static scc_return_t check_register_offset(uint32_t offset); +/*uint8_t make_vpu_partition(void);*/ + +#ifdef SCC_REGISTER_DEBUG +static uint32_t dbg_scc_read_register(uint32_t offset); +static void dbg_scc_write_register(uint32_t offset, uint32_t value); +#endif + + +/* For Linux kernel, export the API functions to other kernel modules */ +EXPORT_SYMBOL(scc_get_configuration); +EXPORT_SYMBOL(scc_zeroize_memories); +/*EXPORT_SYMBOL(scc_crypt);*/ +EXPORT_SYMBOL(scc_set_sw_alarm); +EXPORT_SYMBOL(scc_monitor_security_failure); +EXPORT_SYMBOL(scc_stop_monitoring_security_failure); +EXPORT_SYMBOL(scc_read_register); +EXPORT_SYMBOL(scc_write_register); +EXPORT_SYMBOL(scc_allocate_partition); +EXPORT_SYMBOL(scc_engage_partition); +EXPORT_SYMBOL(scc_release_partition); +EXPORT_SYMBOL(scc_diminish_permissions); +EXPORT_SYMBOL(scc_encrypt_region); +EXPORT_SYMBOL(scc_decrypt_region); +/*EXPORT_SYMBOL(make_vpu_partition);*/ +/* Tell Linux where to invoke driver at boot/module load time */ +module_init(scc_init); +/* Tell Linux where to invoke driver on module unload */ +module_exit(scc_cleanup); + + +/* Tell Linux this is not GPL code */ +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Device Driver for SCC (SMN/SCM)"); + + +#endif /* SCC_INTERNALS_H */ diff --git a/drivers/mxc/ssi/Kconfig b/drivers/mxc/ssi/Kconfig new file mode 100644 index 000000000000..4cb581c2f945 --- /dev/null +++ b/drivers/mxc/ssi/Kconfig @@ -0,0 +1,12 @@ +# +# SPI device configuration +# + +menu "MXC SSI support" + +config MXC_SSI + tristate "SSI support" + ---help--- + Say Y to get the SSI services API available on MXC platform. + +endmenu diff --git a/drivers/mxc/ssi/Makefile b/drivers/mxc/ssi/Makefile new file mode 100644 index 000000000000..f9bb4419fc19 --- /dev/null +++ b/drivers/mxc/ssi/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the kernel SSI device drivers. +# + +obj-$(CONFIG_MXC_SSI) += ssimod.o + +ssimod-objs := ssi.o diff --git a/drivers/mxc/ssi/registers.h b/drivers/mxc/ssi/registers.h new file mode 100644 index 000000000000..ba98d96c5274 --- /dev/null +++ b/drivers/mxc/ssi/registers.h @@ -0,0 +1,208 @@ +/* + * Copyright 2004-2009 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 ../ssi/registers.h + * @brief This header file contains SSI driver low level definition to access module registers. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_REGISTERS_H__ +#define __MXC_SSI_REGISTERS_H__ + +/*! + * This include to define bool type, false and true definitions. + */ +#include <mach/hardware.h> + +#define SPBA_CPU_SSI 0x07 + +#define MXC_SSISTX0 0x00 +#define MXC_SSISTX1 0x04 +#define MXC_SSISRX0 0x08 +#define MXC_SSISRX1 0x0C +#define MXC_SSISCR 0x10 +#define MXC_SSISISR 0x14 +#define MXC_SSISIER 0x18 +#define MXC_SSISTCR 0x1C +#define MXC_SSISRCR 0x20 +#define MXC_SSISTCCR 0x24 +#define MXC_SSISRCCR 0x28 +#define MXC_SSISFCSR 0x2C +#define MXC_SSISTR 0x30 +#define MXC_SSISOR 0x34 +#define MXC_SSISACNT 0x38 +#define MXC_SSISACADD 0x3C +#define MXC_SSISACDAT 0x40 +#define MXC_SSISATAG 0x44 +#define MXC_SSISTMSK 0x48 +#define MXC_SSISRMSK 0x4C + +/* MXC 91221 only */ +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/*! SSI1 registers offset*/ +#define MXC_SSI1STX0 0x00 +#define MXC_SSI1STX1 0x04 +#define MXC_SSI1SRX0 0x08 +#define MXC_SSI1SRX1 0x0C +#define MXC_SSI1SCR 0x10 +#define MXC_SSI1SISR 0x14 +#define MXC_SSI1SIER 0x18 +#define MXC_SSI1STCR 0x1C +#define MXC_SSI1SRCR 0x20 +#define MXC_SSI1STCCR 0x24 +#define MXC_SSI1SRCCR 0x28 +#define MXC_SSI1SFCSR 0x2C +#define MXC_SSI1STR 0x30 +#define MXC_SSI1SOR 0x34 +#define MXC_SSI1SACNT 0x38 +#define MXC_SSI1SACADD 0x3C +#define MXC_SSI1SACDAT 0x40 +#define MXC_SSI1SATAG 0x44 +#define MXC_SSI1STMSK 0x48 +#define MXC_SSI1SRMSK 0x4C + +/* MXC91221 only */ + +#define MXC_SSISACCST 0x50 +#define MXC_SSISACCEN 0x54 +#define MXC_SSISACCDIS 0x58 + +/* Not on MXC91221 */ +/*! SSI2 registers offset*/ +#define MXC_SSI2STX0 0x00 +#define MXC_SSI2STX1 0x04 +#define MXC_SSI2SRX0 0x08 +#define MXC_SSI2SRX1 0x0C +#define MXC_SSI2SCR 0x10 +#define MXC_SSI2SISR 0x14 +#define MXC_SSI2SIER 0x18 +#define MXC_SSI2STCR 0x1C +#define MXC_SSI2SRCR 0x20 +#define MXC_SSI2STCCR 0x24 +#define MXC_SSI2SRCCR 0x28 +#define MXC_SSI2SFCSR 0x2C +#define MXC_SSI2STR 0x30 +#define MXC_SSI2SOR 0x34 +#define MXC_SSI2SACNT 0x38 +#define MXC_SSI2SACADD 0x3C +#define MXC_SSI2SACDAT 0x40 +#define MXC_SSI2SATAG 0x44 +#define MXC_SSI2STMSK 0x48 +#define MXC_SSI2SRMSK 0x4C + +/*! + * SCR Register bit shift definitions + */ +#define SSI_ENABLE_SHIFT 0 +#define SSI_TRANSMIT_ENABLE_SHIFT 1 +#define SSI_RECEIVE_ENABLE_SHIFT 2 +#define SSI_NETWORK_MODE_SHIFT 3 +#define SSI_SYNCHRONOUS_MODE_SHIFT 4 +#define SSI_I2S_MODE_SHIFT 5 +#define SSI_SYSTEM_CLOCK_SHIFT 7 +#define SSI_TWO_CHANNEL_SHIFT 8 +#define SSI_CLOCK_IDLE_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_FRAME_CLOCK_DISABLE_SHIFT 11 + +/*! + * STCR & SRCR Registers bit shift definitions + */ +#define SSI_EARLY_FRAME_SYNC_SHIFT 0 +#define SSI_FRAME_SYNC_LENGTH_SHIFT 1 +#define SSI_FRAME_SYNC_INVERT_SHIFT 2 +#define SSI_CLOCK_POLARITY_SHIFT 3 +#define SSI_SHIFT_DIRECTION_SHIFT 4 +#define SSI_CLOCK_DIRECTION_SHIFT 5 +#define SSI_FRAME_DIRECTION_SHIFT 6 +#define SSI_FIFO_ENABLE_0_SHIFT 7 +#define SSI_FIFO_ENABLE_1_SHIFT 8 +#define SSI_BIT_0_SHIFT 9 + +/* MXC91221 only*/ +#define SSI_TX_FRAME_CLOCK_DISABLE_SHIFT 10 +#define SSI_RX_DATA_EXTENSION_SHIFT 10 /*SRCR only */ +/*! + * STCCR & SRCCR Registers bit shift definitions + */ +#define SSI_PRESCALER_MODULUS_SHIFT 0 +#define SSI_FRAME_RATE_DIVIDER_SHIFT 8 +#define SSI_WORD_LENGTH_SHIFT 13 +#define SSI_PRESCALER_RANGE_SHIFT 17 +#define SSI_DIVIDE_BY_TWO_SHIFT 18 +#define SSI_FRAME_DIVIDER_MASK 31 +#define SSI_MIN_FRAME_DIVIDER_RATIO 1 +#define SSI_MAX_FRAME_DIVIDER_RATIO 32 +#define SSI_PRESCALER_MODULUS_MASK 255 +#define SSI_MIN_PRESCALER_MODULUS_RATIO 1 +#define SSI_MAX_PRESCALER_MODULUS_RATIO 256 +#define SSI_WORD_LENGTH_MASK 15 + +#define SSI_IRQ_STATUS_NUMBER 25 + +/*! + * SFCSR Register bit shift definitions + */ +#define SSI_RX_FIFO_1_COUNT_SHIFT 28 +#define SSI_TX_FIFO_1_COUNT_SHIFT 24 +#define SSI_RX_FIFO_1_WATERMARK_SHIFT 20 +#define SSI_TX_FIFO_1_WATERMARK_SHIFT 16 +#define SSI_RX_FIFO_0_COUNT_SHIFT 12 +#define SSI_TX_FIFO_0_COUNT_SHIFT 8 +#define SSI_RX_FIFO_0_WATERMARK_SHIFT 4 +#define SSI_TX_FIFO_0_WATERMARK_SHIFT 0 +#define SSI_MIN_FIFO_WATERMARK 0 +#define SSI_MAX_FIFO_WATERMARK 8 + +/*! + * SSI Option Register (SOR) bit shift definitions + */ +#define SSI_FRAME_SYN_RESET_SHIFT 0 +#define SSI_WAIT_SHIFT 1 +#define SSI_INIT_SHIFT 3 +#define SSI_TRANSMITTER_CLEAR_SHIFT 4 +#define SSI_RECEIVER_CLEAR_SHIFT 5 +#define SSI_CLOCK_OFF_SHIFT 6 +#define SSI_WAIT_STATE_MASK 0x3 + +/*! + * SSI AC97 Control Register (SACNT) bit shift definitions + */ +#define AC97_MODE_ENABLE_SHIFT 0 +#define AC97_VARIABLE_OPERATION_SHIFT 1 +#define AC97_TAG_IN_FIFO_SHIFT 2 +#define AC97_READ_COMMAND_SHIFT 3 +#define AC97_WRITE_COMMAND_SHIFT 4 +#define AC97_FRAME_RATE_DIVIDER_SHIFT 5 +#define AC97_FRAME_RATE_MASK 0x3F + +/*! + * SSI Test Register (STR) bit shift definitions + */ +#define SSI_TEST_MODE_SHIFT 15 +#define SSI_RCK2TCK_SHIFT 14 +#define SSI_RFS2TFS_SHIFT 13 +#define SSI_RXSTATE_SHIFT 8 +#define SSI_TXD2RXD_SHIFT 7 +#define SSI_TCK2RCK_SHIFT 6 +#define SSI_TFS2RFS_SHIFT 5 +#define SSI_TXSTATE_SHIFT 0 + +#endif /* __MXC_SSI_REGISTERS_H__ */ diff --git a/drivers/mxc/ssi/ssi.c b/drivers/mxc/ssi/ssi.c new file mode 100644 index 000000000000..1b3a2729b174 --- /dev/null +++ b/drivers/mxc/ssi/ssi.c @@ -0,0 +1,1221 @@ +/* + * Copyright 2004-2009 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 ssi.c + * @brief This file contains the implementation of the SSI driver main services + * + * + * @ingroup SSI + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +#include <mach/clock.h> + +#include "registers.h" +#include "ssi.h" + +static spinlock_t ssi_lock; +struct mxc_audio_platform_data *ssi_platform_data; + +EXPORT_SYMBOL(ssi_ac97_frame_rate_divider); +EXPORT_SYMBOL(ssi_ac97_get_command_address_register); +EXPORT_SYMBOL(ssi_ac97_get_command_data_register); +EXPORT_SYMBOL(ssi_ac97_get_tag_register); +EXPORT_SYMBOL(ssi_ac97_mode_enable); +EXPORT_SYMBOL(ssi_ac97_tag_in_fifo); +EXPORT_SYMBOL(ssi_ac97_read_command); +EXPORT_SYMBOL(ssi_ac97_set_command_address_register); +EXPORT_SYMBOL(ssi_ac97_set_command_data_register); +EXPORT_SYMBOL(ssi_ac97_set_tag_register); +EXPORT_SYMBOL(ssi_ac97_variable_mode); +EXPORT_SYMBOL(ssi_ac97_write_command); +EXPORT_SYMBOL(ssi_clock_idle_state); +EXPORT_SYMBOL(ssi_clock_off); +EXPORT_SYMBOL(ssi_enable); +EXPORT_SYMBOL(ssi_get_data); +EXPORT_SYMBOL(ssi_get_status); +EXPORT_SYMBOL(ssi_i2s_mode); +EXPORT_SYMBOL(ssi_interrupt_disable); +EXPORT_SYMBOL(ssi_interrupt_enable); +EXPORT_SYMBOL(ssi_network_mode); +EXPORT_SYMBOL(ssi_receive_enable); +EXPORT_SYMBOL(ssi_rx_bit0); +EXPORT_SYMBOL(ssi_rx_clock_direction); +EXPORT_SYMBOL(ssi_rx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_rx_clock_polarity); +EXPORT_SYMBOL(ssi_rx_clock_prescaler); +EXPORT_SYMBOL(ssi_rx_early_frame_sync); +EXPORT_SYMBOL(ssi_rx_fifo_counter); +EXPORT_SYMBOL(ssi_rx_fifo_enable); +EXPORT_SYMBOL(ssi_rx_fifo_full_watermark); +EXPORT_SYMBOL(ssi_rx_flush_fifo); +EXPORT_SYMBOL(ssi_rx_frame_direction); +EXPORT_SYMBOL(ssi_rx_frame_rate); +EXPORT_SYMBOL(ssi_rx_frame_sync_active); +EXPORT_SYMBOL(ssi_rx_frame_sync_length); +EXPORT_SYMBOL(ssi_rx_mask_time_slot); +EXPORT_SYMBOL(ssi_rx_prescaler_modulus); +EXPORT_SYMBOL(ssi_rx_shift_direction); +EXPORT_SYMBOL(ssi_rx_word_length); +EXPORT_SYMBOL(ssi_set_data); +EXPORT_SYMBOL(ssi_set_wait_states); +EXPORT_SYMBOL(ssi_synchronous_mode); +EXPORT_SYMBOL(ssi_system_clock); +EXPORT_SYMBOL(ssi_transmit_enable); +EXPORT_SYMBOL(ssi_two_channel_mode); +EXPORT_SYMBOL(ssi_tx_bit0); +EXPORT_SYMBOL(ssi_tx_clock_direction); +EXPORT_SYMBOL(ssi_tx_clock_divide_by_two); +EXPORT_SYMBOL(ssi_tx_clock_polarity); +EXPORT_SYMBOL(ssi_tx_clock_prescaler); +EXPORT_SYMBOL(ssi_tx_early_frame_sync); +EXPORT_SYMBOL(ssi_tx_fifo_counter); +EXPORT_SYMBOL(ssi_tx_fifo_empty_watermark); +EXPORT_SYMBOL(ssi_tx_fifo_enable); +EXPORT_SYMBOL(ssi_tx_flush_fifo); +EXPORT_SYMBOL(ssi_tx_frame_direction); +EXPORT_SYMBOL(ssi_tx_frame_rate); +EXPORT_SYMBOL(ssi_tx_frame_sync_active); +EXPORT_SYMBOL(ssi_tx_frame_sync_length); +EXPORT_SYMBOL(ssi_tx_mask_time_slot); +EXPORT_SYMBOL(ssi_tx_prescaler_modulus); +EXPORT_SYMBOL(ssi_tx_shift_direction); +EXPORT_SYMBOL(ssi_tx_word_length); +EXPORT_SYMBOL(get_ssi_fifo_addr); + +struct resource *res; +unsigned long base_addr_1; +unsigned long base_addr_2; + +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction) +{ + unsigned int fifo_addr; + if (direction == 1) { + if (ssi_platform_data->ssi_num == 2) { + fifo_addr = + (ssi == + SSI1) ? (int)(base_addr_1 + + MXC_SSI1STX0) : (int)(base_addr_2 + + MXC_SSI2STX0); + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1STX0); + } + } else { + fifo_addr = (int)(base_addr_1 + MXC_SSI1SRX0); + } + return fifo_addr; +} + +void * get_ssi_base_addr(unsigned int ssi) +{ + if (ssi_platform_data->ssi_num == 2) { + if (ssi == SSI1) + return IO_ADDRESS(base_addr_1); + else + return IO_ADDRESS(base_addr_2); + } + return IO_ADDRESS(base_addr_1); +} + +void set_register_bits(unsigned int mask, unsigned int data, + unsigned int offset, unsigned int ssi) +{ + volatile unsigned long reg = 0; + void *base_addr = get_ssi_base_addr(ssi); + unsigned long flags = 0; + + spin_lock_irqsave(&ssi_lock, flags); + reg = __raw_readl(base_addr + offset); + reg = (reg & (~mask)) | data; + __raw_writel(reg, base_addr + offset); + spin_unlock_irqrestore(&ssi_lock, flags); +} + +unsigned long getreg_value(unsigned int offset, unsigned int ssi) +{ + void *base_addr = get_ssi_base_addr(ssi); + return __raw_readl(base_addr + offset); +} + +void set_register(unsigned int data, unsigned int offset, unsigned int ssi) +{ + void *base_addr = get_ssi_base_addr(ssi); + __raw_writel(data, base_addr + offset); +} + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + reg |= ((frame_rate_divider & AC97_FRAME_RATE_MASK) + << AC97_FRAME_RATE_DIVIDER_SHIFT); + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACADD, module); +} + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module) +{ + return getreg_value(MXC_SSISACDAT, module); +} + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module) +{ + return getreg_value(MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_MODE_ENABLE_SHIFT); + } else { + reg &= ~(1 << AC97_MODE_ENABLE_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if true, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_TAG_IN_FIFO_SHIFT); + } else { + reg &= ~(1 << AC97_TAG_IN_FIFO_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_READ_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_READ_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, unsigned int address) +{ + set_register(address, MXC_SSISACADD, module); +} + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data) +{ + set_register(data, MXC_SSISACDAT, module); +} + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag) +{ + set_register(tag, MXC_SSISATAG, module); +} + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ void ssi_ac97_variable_mode(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_VARIABLE_OPERATION_SHIFT); + } else { + reg &= ~(1 << AC97_VARIABLE_OPERATION_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state) +{ + unsigned int reg = 0; + + reg = getreg_value(MXC_SSISACNT, module); + if (state == true) { + reg |= (1 << AC97_WRITE_COMMAND_SHIFT); + } else { + reg &= ~(1 << AC97_WRITE_COMMAND_SHIFT); + } + + set_register(reg, MXC_SSISACNT, module); +} + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state) +{ + set_register_bits(1 << SSI_CLOCK_IDLE_SHIFT, + state << SSI_CLOCK_IDLE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_CLOCK_OFF_SHIFT, + state << SSI_CLOCK_OFF_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_ENABLE_SHIFT, state << SSI_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo) +{ + unsigned int result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISRX0, module); + } else { + result = getreg_value(MXC_SSISRX1, module); + } + + return result; +} + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module) +{ + unsigned int result; + + result = getreg_value(MXC_SSISISR, module); + result &= ((1 << SSI_IRQ_STATUS_NUMBER) - 1); + + return (ssi_status_enable_mask) result; +} + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode) +{ + set_register_bits(3 << SSI_I2S_MODE_SHIFT, mode << SSI_I2S_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(mask, 0, MXC_SSISIER, module); +} + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask) +{ + set_register_bits(0, mask, MXC_SSISIER, module); +} + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_NETWORK_MODE_SHIFT, + state << SSI_NETWORK_MODE_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_RECEIVE_ENABLE_SHIFT, + state << SSI_RECEIVE_ENABLE_SHIFT, MXC_SSISCR, + module); +} + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, state << SSI_BIT_0_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, MXC_SSISRCCR, + module); +} + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, MXC_SSISRCR, + module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result; + result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_0_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_RX_FIFO_1_COUNT_SHIFT); + result = result >> SSI_RX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + volatile unsigned int reg; + + reg = getreg_value(MXC_SSISRCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISRCR, module); +} + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_RX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_RX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_RX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_RECEIVER_CLEAR_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISRMSK, module); +} + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISRCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISRCR, module); +} + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISRCCR, module); +} + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ + +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data) +{ + if (ssi_fifo_0 == fifo) { + set_register(data, MXC_SSISTX0, module); + } else { + set_register(data, MXC_SSISTX1, module); + } +} + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait) +{ + set_register_bits(SSI_WAIT_STATE_MASK << SSI_WAIT_SHIFT, + wait << SSI_WAIT_SHIFT, MXC_SSISOR, module); +} + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYNCHRONOUS_MODE_SHIFT, + state << SSI_SYNCHRONOUS_MODE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_SYSTEM_CLOCK_SHIFT, + state << SSI_SYSTEM_CLOCK_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TRANSMIT_ENABLE_SHIFT, + state << SSI_TRANSMIT_ENABLE_SHIFT, + MXC_SSISCR, module); +} + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_TWO_CHANNEL_SHIFT, + state << SSI_TWO_CHANNEL_SHIFT, MXC_SSISCR, module); +} + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_BIT_0_SHIFT, + state << SSI_BIT_0_SHIFT, MXC_SSISTCR, module); +} + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_CLOCK_DIRECTION_SHIFT, + direction << SSI_CLOCK_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_DIVIDE_BY_TWO_SHIFT, + state << SSI_DIVIDE_BY_TWO_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity) +{ + set_register_bits(1 << SSI_CLOCK_POLARITY_SHIFT, + polarity << SSI_CLOCK_POLARITY_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state) +{ + set_register_bits(1 << SSI_PRESCALER_RANGE_SHIFT, + state << SSI_PRESCALER_RANGE_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early) +{ + set_register_bits(1 << SSI_EARLY_FRAME_SYNC_SHIFT, + early << SSI_EARLY_FRAME_SYNC_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo) +{ + unsigned char result = 0; + + if (ssi_fifo_0 == fifo) { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_0_COUNT_SHIFT); + result >>= SSI_TX_FIFO_0_COUNT_SHIFT; + } else { + result = getreg_value(MXC_SSISFCSR, module); + result &= (0xF << SSI_TX_FIFO_1_COUNT_SHIFT); + result >>= SSI_TX_FIFO_1_COUNT_SHIFT; + } + + return result; +} + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark) +{ + int result = -1; + + if ((watermark > SSI_MIN_FIFO_WATERMARK) && + (watermark <= SSI_MAX_FIFO_WATERMARK)) { + if (ssi_fifo_0 == fifo) { + set_register_bits(0xf << SSI_TX_FIFO_0_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_0_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } else { + set_register_bits(0xf << SSI_TX_FIFO_1_WATERMARK_SHIFT, + watermark << + SSI_TX_FIFO_1_WATERMARK_SHIFT, + MXC_SSISFCSR, module); + } + + result = 0; + } + + return result; +} + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the fifo, enabled or disabled + */ + +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable) +{ + unsigned int reg; + + reg = getreg_value(MXC_SSISTCR, module); + if (enable == true) { + reg |= ((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } else { + reg &= ~((1 + fifo) << SSI_FIFO_ENABLE_0_SHIFT); + } + + set_register(reg, MXC_SSISTCR, module); +} + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module) +{ + set_register_bits(0, 1 << SSI_TRANSMITTER_CLEAR_SHIFT, + MXC_SSISOR, module); +} + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction) +{ + set_register_bits(1 << SSI_FRAME_DIRECTION_SHIFT, + direction << SSI_FRAME_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio) +{ + int result = -1; + + if ((ratio >= SSI_MIN_FRAME_DIVIDER_RATIO) && + (ratio <= SSI_MAX_FRAME_DIVIDER_RATIO)) { + + set_register_bits(SSI_FRAME_DIVIDER_MASK << + SSI_FRAME_RATE_DIVIDER_SHIFT, + (ratio - 1) << SSI_FRAME_RATE_DIVIDER_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active) +{ + set_register_bits(1 << SSI_FRAME_SYNC_INVERT_SHIFT, + active << SSI_FRAME_SYNC_INVERT_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length) +{ + set_register_bits(1 << SSI_FRAME_SYNC_LENGTH_SHIFT, + length << SSI_FRAME_SYNC_LENGTH_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask) +{ + set_register_bits(0xFFFFFFFF, mask, MXC_SSISTMSK, module); +} + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider) +{ + int result = -1; + + if ((divider >= SSI_MIN_PRESCALER_MODULUS_RATIO) && + (divider <= SSI_MAX_PRESCALER_MODULUS_RATIO)) { + + set_register_bits(SSI_PRESCALER_MODULUS_MASK << + SSI_PRESCALER_MODULUS_SHIFT, + (divider - 1) << SSI_PRESCALER_MODULUS_SHIFT, + MXC_SSISTCCR, module); + result = 0; + } + + return result; +} + +/*! + * This function controls whether the MSB or LSB will be transmitted first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, ssi_tx_rx_shift_direction direction) +{ + set_register_bits(1 << SSI_SHIFT_DIRECTION_SHIFT, + direction << SSI_SHIFT_DIRECTION_SHIFT, + MXC_SSISTCR, module); +} + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length) +{ + set_register_bits(SSI_WORD_LENGTH_MASK << SSI_WORD_LENGTH_SHIFT, + length << SSI_WORD_LENGTH_SHIFT, + MXC_SSISTCCR, module); +} + +/*! + * This function initializes the driver in terms of memory of the soundcard + * and some basic HW clock settings. + * + * @return 0 on success, -1 otherwise. + */ +static int __init ssi_probe(struct platform_device *pdev) +{ + int ret = -1; + ssi_platform_data = + (struct mxc_audio_platform_data *)pdev->dev.platform_data; + if (!ssi_platform_data) { + dev_err(&pdev->dev, "can't get the platform data for SSI\n"); + return -EINVAL; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(&pdev->dev, "can't get platform resource - SSI\n"); + ret = -ENOMEM; + goto err; + } + + if (pdev->id == 0) { + base_addr_1 = res->start; + } else if (pdev->id == 1) { + base_addr_2 = res->start; + } + + printk(KERN_INFO "SSI %d module loaded successfully \n", pdev->id + 1); + + return 0; + err: + return -1; + +} + +static int ssi_remove(struct platform_device *dev) +{ + return 0; +} + +static struct platform_driver mxc_ssi_driver = { + .probe = ssi_probe, + .remove = ssi_remove, + .driver = { + .name = "mxc_ssi", + }, +}; + +/*! + * This function implements the init function of the SSI device. + * This function is called when the module is loaded. + * + * @return This function returns 0. + */ +static int __init ssi_init(void) +{ + spin_lock_init(&ssi_lock); + return platform_driver_register(&mxc_ssi_driver); + +} + +/*! + * This function implements the exit function of the SPI device. + * This function is called when the module is unloaded. + * + */ +static void __exit ssi_exit(void) +{ + platform_driver_unregister(&mxc_ssi_driver); + printk(KERN_INFO "SSI module unloaded successfully\n"); +} + +module_init(ssi_init); +module_exit(ssi_exit); + +MODULE_DESCRIPTION("SSI char device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/ssi/ssi.h b/drivers/mxc/ssi/ssi.h new file mode 100644 index 000000000000..22032b6616fa --- /dev/null +++ b/drivers/mxc/ssi/ssi.h @@ -0,0 +1,574 @@ +/* + * 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 + */ + + /*! + * @defgroup SSI Synchronous Serial Interface (SSI) Driver + */ + + /*! + * @file ssi.h + * @brief This header file contains SSI driver functions prototypes. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_H__ +#define __MXC_SSI_H__ + +#include "ssi_types.h" + +/*! + * This function gets the SSI fifo address. + * + * @param ssi ssi number + * @param direction To indicate playback / recording + * @return This function returns the SSI fifo address. + */ +unsigned int get_ssi_fifo_addr(unsigned int ssi, int direction); + +/*! + * This function controls the AC97 frame rate divider. + * + * @param module the module number + * @param frame_rate_divider the AC97 frame rate divider + */ +void ssi_ac97_frame_rate_divider(ssi_mod module, + unsigned char frame_rate_divider); + +/*! + * This function gets the AC97 command address register. + * + * @param module the module number + * @return This function returns the command address slot information. + */ +unsigned int ssi_ac97_get_command_address_register(ssi_mod module); + +/*! + * This function gets the AC97 command data register. + * + * @param module the module number + * @return This function returns the command data slot information. + */ +unsigned int ssi_ac97_get_command_data_register(ssi_mod module); + +/*! + * This function gets the AC97 tag register. + * + * @param module the module number + * @return This function returns the tag information. + */ +unsigned int ssi_ac97_get_tag_register(ssi_mod module); + +/*! + * This function controls the AC97 mode. + * + * @param module the module number + * @param state the AC97 mode state (enabled or disabled) + */ +void ssi_ac97_mode_enable(ssi_mod module, bool state); + +/*! + * This function controls the AC97 tag in FIFO behavior. + * + * @param module the module number + * @param state the tag in fifo behavior (Tag info stored in Rx FIFO 0 if TRUE, + * Tag info stored in SATAG register otherwise) + */ +void ssi_ac97_tag_in_fifo(ssi_mod module, bool state); + +/*! + * This function controls the AC97 read command. + * + * @param module the module number + * @param state the next AC97 command is a read command or not + */ +void ssi_ac97_read_command(ssi_mod module, bool state); + +/*! + * This function sets the AC97 command address register. + * + * @param module the module number + * @param address the command address slot information + */ +void ssi_ac97_set_command_address_register(ssi_mod module, + unsigned int address); + +/*! + * This function sets the AC97 command data register. + * + * @param module the module number + * @param data the command data slot information + */ +void ssi_ac97_set_command_data_register(ssi_mod module, unsigned int data); + +/*! + * This function sets the AC97 tag register. + * + * @param module the module number + * @param tag the tag information + */ +void ssi_ac97_set_tag_register(ssi_mod module, unsigned int tag); + +/*! + * This function controls the AC97 variable mode. + * + * @param module the module number + * @param state the AC97 variable mode state (enabled or disabled) + */ +void ssi_ac97_variable_mode(ssi_mod module, bool state); + +/*! + * This function controls the AC97 write command. + * + * @param module the module number + * @param state the next AC97 command is a write command or not + */ +void ssi_ac97_write_command(ssi_mod module, bool state); + +/*! + * This function controls the idle state of the transmit clock port during SSI internal gated mode. + * + * @param module the module number + * @param state the clock idle state + */ +void ssi_clock_idle_state(ssi_mod module, idle_state state); + +/*! + * This function turns off/on the ccm_ssi_clk to reduce power consumption. + * + * @param module the module number + * @param state the state for ccm_ssi_clk (true: turn off, else:turn on) + */ +void ssi_clock_off(ssi_mod module, bool state); + +/*! + * This function enables/disables the SSI module. + * + * @param module the module number + * @param state the state for SSI module + */ +void ssi_enable(ssi_mod module, bool state); + +/*! + * This function gets the data word in the Receive FIFO of the SSI module. + * + * @param module the module number + * @param fifo the Receive FIFO to read + * @return This function returns the read data. + */ +unsigned int ssi_get_data(ssi_mod module, fifo_nb fifo); + +/*! + * This function returns the status of the SSI module (SISR register) as a combination of status. + * + * @param module the module number + * @return This function returns the status of the SSI module. + */ +ssi_status_enable_mask ssi_get_status(ssi_mod module); + +/*! + * This function selects the I2S mode of the SSI module. + * + * @param module the module number + * @param mode the I2S mode + */ +void ssi_i2s_mode(ssi_mod module, mode_i2s mode); + +/*! + * This function disables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to disable + */ +void ssi_interrupt_disable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables the interrupts of the SSI module. + * + * @param module the module number + * @param mask the mask of the interrupts to enable + */ +void ssi_interrupt_enable(ssi_mod module, ssi_status_enable_mask mask); + +/*! + * This function enables/disables the network mode of the SSI module. + * + * @param module the module number + * @param state the network mode state + */ +void ssi_network_mode(ssi_mod module, bool state); + +/*! + * This function enables/disables the receive section of the SSI module. + * + * @param module the module number + * @param state the receive section state + */ +void ssi_receive_enable(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to receive data word at bit position 0 or 23 in the Receive shift register. + * + * @param module the module number + * @param state the state to receive at bit 0 + */ +void ssi_rx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the source of the clock signal used to clock the Receive shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_rx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the receive section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_rx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock in data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_rx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the receive section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_rx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_rx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Rx FIFO. + */ +unsigned char ssi_rx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function enables the Receive FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enabled the state of the fifo, enabled or disabled + */ +void ssi_rx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enabled); + +/*! + * This function controls the threshold at which the RFFx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_fifo_full_watermark(ssi_mod module, + fifo_nb fifo, unsigned char watermark); + +/*! + * This function flushes the Receive FIFOs. + * + * @param module the module number + */ +void ssi_rx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the receive section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_rx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Receive frame rate divider for the receive section. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the receive section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_rx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the receive section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_rx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the receive section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_rx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the receive section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_rx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be received first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_rx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Receive word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_rx_word_length(ssi_mod module, ssi_word_length length); + +/*! + * This function sets the data word in the Transmit FIFO of the SSI module. + * + * @param module the module number + * @param fifo the FIFO number + * @param data the data to load in the FIFO + */ +void ssi_set_data(ssi_mod module, fifo_nb fifo, unsigned int data); + +/*! + * This function controls the number of wait states between the core and SSI. + * + * @param module the module number + * @param wait the number of wait state(s) + */ +void ssi_set_wait_states(ssi_mod module, ssi_wait_states wait); + +/*! + * This function enables/disables the synchronous mode of the SSI module. + * + * @param module the module number + * @param state the synchronous mode state + */ +void ssi_synchronous_mode(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to output the SYS_CLK at the SRCK port. + * + * @param module the module number + * @param state the system clock state + */ +void ssi_system_clock(ssi_mod module, bool state); + +/*! + * This function enables/disables the transmit section of the SSI module. + * + * @param module the module number + * @param state the transmit section state + */ +void ssi_transmit_enable(ssi_mod module, bool state); + +/*! + * This function allows the SSI module to operate in the two channel mode. + * + * @param module the module number + * @param state the two channel mode state + */ +void ssi_two_channel_mode(ssi_mod module, bool state); + +/*! + * This function configures the SSI module to transmit data word from bit position 0 or 23 in the Transmit shift register. + * + * @param module the module number + * @param state the transmit from bit 0 state + */ +void ssi_tx_bit0(ssi_mod module, bool state); + +/*! + * This function controls the direction of the clock signal used to clock the Transmit shift register. + * + * @param module the module number + * @param direction the clock signal direction + */ +void ssi_tx_clock_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the divide-by-two divider of the SSI module for the transmit section. + * + * @param module the module number + * @param state the divider state + */ +void ssi_tx_clock_divide_by_two(ssi_mod module, bool state); + +/*! + * This function controls which bit clock edge is used to clock out data. + * + * @param module the module number + * @param polarity the clock polarity + */ +void ssi_tx_clock_polarity(ssi_mod module, ssi_tx_rx_clock_polarity polarity); + +/*! + * This function configures a fixed divide-by-eight clock prescaler divider of the SSI module in series with the variable prescaler for the transmit section. + * + * @param module the module number + * @param state the prescaler state + */ +void ssi_tx_clock_prescaler(ssi_mod module, bool state); + +/*! + * This function controls the early frame sync configuration for the transmit section. + * + * @param module the module number + * @param early the early frame sync configuration + */ +void ssi_tx_early_frame_sync(ssi_mod module, ssi_tx_rx_early_frame_sync early); + +/*! + * This function gets the number of data words in the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo + * @return This function returns the number of words in the Tx FIFO. + */ +unsigned char ssi_tx_fifo_counter(ssi_mod module, fifo_nb fifo); + +/*! + * This function controls the threshold at which the TFEx flag will be set. + * + * @param module the module number + * @param fifo the fifo to enable + * @param watermark the watermark + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_fifo_empty_watermark(ssi_mod module, fifo_nb fifo, + unsigned char watermark); + +/*! + * This function enables the Transmit FIFO. + * + * @param module the module number + * @param fifo the fifo to enable + * @param enable the state of the FIFO, enabled or disabled + */ +void ssi_tx_fifo_enable(ssi_mod module, fifo_nb fifo, bool enable); + +/*! + * This function flushes the Transmit FIFOs. + * + * @param module the module number + */ +void ssi_tx_flush_fifo(ssi_mod module); + +/*! + * This function controls the direction of the Frame Sync signal for the transmit section. + * + * @param module the module number + * @param direction the Frame Sync signal direction + */ +void ssi_tx_frame_direction(ssi_mod module, ssi_tx_rx_direction direction); + +/*! + * This function configures the Transmit frame rate divider. + * + * @param module the module number + * @param ratio the divide ratio from 1 to 32 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_frame_rate(ssi_mod module, unsigned char ratio); + +/*! + * This function controls the Frame Sync active polarity for the transmit section. + * + * @param module the module number + * @param active the Frame Sync active polarity + */ +void ssi_tx_frame_sync_active(ssi_mod module, + ssi_tx_rx_frame_sync_active active); + +/*! + * This function controls the Frame Sync length (one word or one bit long) for the transmit section. + * + * @param module the module number + * @param length the Frame Sync length + */ +void ssi_tx_frame_sync_length(ssi_mod module, + ssi_tx_rx_frame_sync_length length); + +/*! + * This function configures the time slot(s) to mask for the transmit section. + * + * @param module the module number + * @param mask the mask to indicate the time slot(s) masked + */ +void ssi_tx_mask_time_slot(ssi_mod module, unsigned int mask); + +/*! + * This function configures the Prescale divider for the transmit section. + * + * @param module the module number + * @param divider the divide ratio from 1 to 256 + * @return This function returns the result of the operation (0 if successful, -1 otherwise). + */ +int ssi_tx_prescaler_modulus(ssi_mod module, unsigned int divider); + +/*! + * This function controls whether the MSB or LSB will be transmited first in a sample. + * + * @param module the module number + * @param direction the shift direction + */ +void ssi_tx_shift_direction(ssi_mod module, + ssi_tx_rx_shift_direction direction); + +/*! + * This function configures the Transmit word length. + * + * @param module the module number + * @param length the word length + */ +void ssi_tx_word_length(ssi_mod module, ssi_word_length length); + +#endif /* __MXC_SSI_H__ */ diff --git a/drivers/mxc/ssi/ssi_types.h b/drivers/mxc/ssi/ssi_types.h new file mode 100644 index 000000000000..015e65a6d2d2 --- /dev/null +++ b/drivers/mxc/ssi/ssi_types.h @@ -0,0 +1,367 @@ +/* + * 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 ssi_types.h + * @brief This header file contains SSI types. + * + * @ingroup SSI + */ + +#ifndef __MXC_SSI_TYPES_H__ +#define __MXC_SSI_TYPES_H__ + +/*! + * This enumeration describes the FIFO number. + */ +typedef enum { + /*! + * FIFO 0 + */ + ssi_fifo_0 = 0, + /*! + * FIFO 1 + */ + ssi_fifo_1 = 1 +} fifo_nb; + +/*! + * This enumeration describes the clock idle state. + */ +typedef enum { + /*! + * Clock idle state is 1 + */ + clock_idle_state_1 = 0, + /*! + * Clock idle state is 0 + */ + clock_idle_state_0 = 1 +} idle_state; + +/*! + * This enumeration describes I2S mode. + */ +typedef enum { + /*! + * Normal mode + */ + i2s_normal = 0, + /*! + * Master mode + */ + i2s_master = 1, + /*! + * Slave mode + */ + i2s_slave = 2 +} mode_i2s; + +/*! + * This enumeration describes index for both SSI1 and SSI2 modules. + */ +typedef enum { + /*! + * SSI1 index + */ + SSI1 = 0, + /*! + * SSI2 index not present on MXC 91221 and MXC91311 + */ + SSI2 = 1 +} ssi_mod; + +/*! + * This enumeration describes the status/enable bits for interrupt source of the SSI module. + */ +typedef enum { + /*! + * SSI Transmit FIFO 0 empty bit + */ + ssi_tx_fifo_0_empty = 0x00000001, + /*! + * SSI Transmit FIFO 1 empty bit + */ + ssi_tx_fifo_1_empty = 0x00000002, + /*! + * SSI Receive FIFO 0 full bit + */ + ssi_rx_fifo_0_full = 0x00000004, + /*! + * SSI Receive FIFO 1 full bit + */ + ssi_rx_fifo_1_full = 0x00000008, + /*! + * SSI Receive Last Time Slot bit + */ + ssi_rls = 0x00000010, + /*! + * SSI Transmit Last Time Slot bit + */ + ssi_tls = 0x00000020, + /*! + * SSI Receive Frame Sync bit + */ + ssi_rfs = 0x00000040, + /*! + * SSI Transmit Frame Sync bit + */ + ssi_tfs = 0x00000080, + /*! + * SSI Transmitter underrun 0 bit + */ + ssi_transmitter_underrun_0 = 0x00000100, + /*! + * SSI Transmitter underrun 1 bit + */ + ssi_transmitter_underrun_1 = 0x00000200, + /*! + * SSI Receiver overrun 0 bit + */ + ssi_receiver_overrun_0 = 0x00000400, + /*! + * SSI Receiver overrun 1 bit + */ + ssi_receiver_overrun_1 = 0x00000800, + /*! + * SSI Transmit Data register empty 0 bit + */ + ssi_tx_data_reg_empty_0 = 0x00001000, + /*! + * SSI Transmit Data register empty 1 bit + */ + ssi_tx_data_reg_empty_1 = 0x00002000, + + /*! + * SSI Receive Data Ready 0 bit + */ + ssi_rx_data_ready_0 = 0x00004000, + /*! + * SSI Receive Data Ready 1 bit + */ + ssi_rx_data_ready_1 = 0x00008000, + /*! + * SSI Receive tag updated bit + */ + ssi_rx_tag_updated = 0x00010000, + /*! + * SSI Command data register updated bit + */ + ssi_cmd_data_reg_updated = 0x00020000, + /*! + * SSI Command address register updated bit + */ + ssi_cmd_address_reg_updated = 0x00040000, + /*! + * SSI Transmit interrupt enable bit + */ + ssi_tx_interrupt_enable = 0x00080000, + /*! + * SSI Transmit DMA enable bit + */ + ssi_tx_dma_interrupt_enable = 0x00100000, + /*! + * SSI Receive interrupt enable bit + */ + ssi_rx_interrupt_enable = 0x00200000, + /*! + * SSI Receive DMA enable bit + */ + ssi_rx_dma_interrupt_enable = 0x00400000, + /*! + * SSI Tx frame complete enable bit on MXC91221 & MXC91311 only + */ + ssi_tx_frame_complete = 0x00800000, + /*! + * SSI Rx frame complete on MXC91221 & MXC91311 only + */ + ssi_rx_frame_complete = 0x001000000 +} ssi_status_enable_mask; + +/*! + * This enumeration describes the clock edge to clock in or clock out data. + */ +typedef enum { + /*! + * Clock on rising edge + */ + ssi_clock_on_rising_edge = 0, + /*! + * Clock on falling edge + */ + ssi_clock_on_falling_edge = 1 +} ssi_tx_rx_clock_polarity; + +/*! + * This enumeration describes the clock direction. + */ +typedef enum { + /*! + * Clock is external + */ + ssi_tx_rx_externally = 0, + /*! + * Clock is generated internally + */ + ssi_tx_rx_internally = 1 +} ssi_tx_rx_direction; + +/*! + * This enumeration describes the early frame sync behavior. + */ +typedef enum { + /*! + * Frame Sync starts on the first data bit + */ + ssi_frame_sync_first_bit = 0, + /*! + * Frame Sync starts one bit before the first data bit + */ + ssi_frame_sync_one_bit_before = 1 +} ssi_tx_rx_early_frame_sync; + +/*! + * This enumeration describes the Frame Sync active value. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_active_high = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_active_low = 1 +} ssi_tx_rx_frame_sync_active; + +/*! + * This enumeration describes the Frame Sync active length. + */ +typedef enum { + /*! + * Frame Sync is active when high + */ + ssi_frame_sync_one_word = 0, + /*! + * Frame Sync is active when low + */ + ssi_frame_sync_one_bit = 1 +} ssi_tx_rx_frame_sync_length; + +/*! + * This enumeration describes the Tx/Rx frame shift direction. + */ +typedef enum { + /*! + * MSB first + */ + ssi_msb_first = 0, + /*! + * LSB first + */ + ssi_lsb_first = 1 +} ssi_tx_rx_shift_direction; + +/*! + * This enumeration describes the wait state number. + */ +typedef enum { + /*! + * 0 wait state + */ + ssi_waitstates0 = 0x0, + /*! + * 1 wait state + */ + ssi_waitstates1 = 0x1, + /*! + * 2 wait states + */ + ssi_waitstates2 = 0x2, + /*! + * 3 wait states + */ + ssi_waitstates3 = 0x3 +} ssi_wait_states; + +/*! + * This enumeration describes the word length. + */ +typedef enum { + /*! + * 2 bits long + */ + ssi_2_bits = 0x0, + /*! + * 4 bits long + */ + ssi_4_bits = 0x1, + /*! + * 6 bits long + */ + ssi_6_bits = 0x2, + /*! + * 8 bits long + */ + ssi_8_bits = 0x3, + /*! + * 10 bits long + */ + ssi_10_bits = 0x4, + /*! + * 12 bits long + */ + ssi_12_bits = 0x5, + /*! + * 14 bits long + */ + ssi_14_bits = 0x6, + /*! + * 16 bits long + */ + ssi_16_bits = 0x7, + /*! + * 18 bits long + */ + ssi_18_bits = 0x8, + /*! + * 20 bits long + */ + ssi_20_bits = 0x9, + /*! + * 22 bits long + */ + ssi_22_bits = 0xA, + /*! + * 24 bits long + */ + ssi_24_bits = 0xB, + /*! + * 26 bits long + */ + ssi_26_bits = 0xC, + /*! + * 28 bits long + */ + ssi_28_bits = 0xD, + /*! + * 30 bits long + */ + ssi_30_bits = 0xE, + /*! + * 32 bits long + */ + ssi_32_bits = 0xF +} ssi_word_length; + +#endif /* __MXC_SSI_TYPES_H__ */ diff --git a/drivers/mxc/vpu/Kconfig b/drivers/mxc/vpu/Kconfig new file mode 100644 index 000000000000..e41974591b86 --- /dev/null +++ b/drivers/mxc/vpu/Kconfig @@ -0,0 +1,30 @@ +# +# Codec configuration +# + +menu "MXC VPU(Video Processing Unit) support" + +config MXC_VPU + tristate "Support for MXC VPU(Video Processing Unit)" + depends on (ARCH_MX3 || ARCH_MX27 || ARCH_MXC92323 || ARCH_MX37 || ARCH_MX51) + default y + ---help--- + The VPU codec device provides codec function for H.264/MPEG4/H.263, + as well as MPEG2/VC-1/DivX on some platforms. + +config MXC_VPU_IRAM + tristate "Use IRAM as temporary buffer for VPU to enhance performace" + depends on (ARCH_MX37 || ARCH_MX51) + default y + ---help--- + The VPU can use internal RAM as temporary buffer to save external + memroy bandwith, thus to enhance video performance. + +config MXC_VPU_DEBUG + bool "MXC VPU debugging" + depends on MXC_VPU != n + help + This is an option for the developers; most people should + say N here. This enables MXC VPU driver debugging. + +endmenu diff --git a/drivers/mxc/vpu/Makefile b/drivers/mxc/vpu/Makefile new file mode 100644 index 000000000000..88e8f2c084a0 --- /dev/null +++ b/drivers/mxc/vpu/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the VPU drivers. +# + +obj-$(CONFIG_MXC_VPU) += vpu.o +vpu-objs := mxc_vpu.o mxc_vl2cc.o + +ifeq ($(CONFIG_MXC_VPU_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif diff --git a/drivers/mxc/vpu/mxc_vl2cc.c b/drivers/mxc/vpu/mxc_vl2cc.c new file mode 100644 index 000000000000..acc40dd01689 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vl2cc.c @@ -0,0 +1,123 @@ +/* + * Copyright 2007-2009 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 mxc_vl2cc.c + * + * @brief VL2CC initialization and flush operation implementation + * + * @ingroup VL2CC + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <mach/hardware.h> +#include <asm/io.h> + +#define VL2CC_CTRL_OFFSET (0x100) +#define VL2CC_AUXCTRL_OFFSET (0x104) +#define VL2CC_INVWAY_OFFSET (0x77C) +#define VL2CC_CLEANWAY_OFFSET (0x7BC) + +/*! VL2CC clock handle. */ +static struct clk *vl2cc_clk; +static u32 *vl2cc_base; + +/*! + * Initialization function of VL2CC. Remap the VL2CC base address. + * + * @return status 0 success. + */ +int vl2cc_init(u32 vl2cc_hw_base) +{ + vl2cc_base = ioremap(vl2cc_hw_base, SZ_8K - 1); + if (vl2cc_base == NULL) { + printk(KERN_INFO "vl2cc: Unable to ioremap\n"); + return -ENOMEM; + } + + vl2cc_clk = clk_get(NULL, "vl2cc_clk"); + if (IS_ERR(vl2cc_clk)) { + printk(KERN_INFO "vl2cc: Unable to get clock\n"); + iounmap(vl2cc_base); + return -EIO; + } + + printk(KERN_INFO "VL2CC initialized\n"); + return 0; +} + +/*! + * Enable VL2CC hardware + */ +void vl2cc_enable(void) +{ + volatile u32 reg; + + clk_enable(vl2cc_clk); + + /* Disable VL2CC */ + reg = __raw_readl(vl2cc_base + VL2CC_CTRL_OFFSET); + reg &= 0xFFFFFFFE; + __raw_writel(reg, vl2cc_base + VL2CC_CTRL_OFFSET); + + /* Set the latency for data RAM reads, data RAM writes, tag RAM and + * dirty RAM to 1 cycle - write 0x0 to AUX CTRL [11:0] and also + * configure the number of ways to 8 - write 8 to AUX CTRL [16:13] + */ + reg = __raw_readl(vl2cc_base + VL2CC_AUXCTRL_OFFSET); + reg &= 0xFFFE1000; /* Clear [16:13] too */ + reg |= (0x8 << 13); /* [16:13] = 8; */ + __raw_writel(reg, vl2cc_base + VL2CC_AUXCTRL_OFFSET); + + /* Invalidate the VL2CC ways - write 0xff to INV BY WAY and poll the + * register until its value is 0x0 + */ + __raw_writel(0xff, vl2cc_base + VL2CC_INVWAY_OFFSET); + while (__raw_readl(vl2cc_base + VL2CC_INVWAY_OFFSET) != 0x0) ; + + /* Enable VL2CC */ + reg = __raw_readl(vl2cc_base + VL2CC_CTRL_OFFSET); + reg |= 0x1; + __raw_writel(reg, vl2cc_base + VL2CC_CTRL_OFFSET); +} + +/*! + * Flush VL2CC + */ +void vl2cc_flush(void) +{ + __raw_writel(0xff, vl2cc_base + VL2CC_CLEANWAY_OFFSET); + while (__raw_readl(vl2cc_base + VL2CC_CLEANWAY_OFFSET) != 0x0) ; +} + +/*! + * Disable VL2CC + */ +void vl2cc_disable(void) +{ + __raw_writel(0, vl2cc_base + VL2CC_CTRL_OFFSET); + clk_disable(vl2cc_clk); +} + +/*! + * Cleanup VL2CC + */ +void vl2cc_cleanup(void) +{ + clk_put(vl2cc_clk); + iounmap(vl2cc_base); +} diff --git a/drivers/mxc/vpu/mxc_vpu.c b/drivers/mxc/vpu/mxc_vpu.c new file mode 100644 index 000000000000..fa2824e85f77 --- /dev/null +++ b/drivers/mxc/vpu/mxc_vpu.c @@ -0,0 +1,817 @@ +/* + * Copyright 2006-2009 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 mxc_vpu.c + * + * @brief VPU system initialization and file operation implementation + * + * @ingroup VPU + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/autoconf.h> +#include <linux/ioport.h> +#include <linux/stat.h> +#include <linux/platform_device.h> +#include <linux/kdev_t.h> +#include <linux/dma-mapping.h> +#include <linux/wait.h> +#include <linux/list.h> +#include <linux/clk.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/sizes.h> +#include <asm/dma-mapping.h> +#include <mach/hardware.h> + +#include <mach/mxc_vpu.h> + +struct vpu_priv { + struct fasync_struct *async_queue; +}; + +/* To track the allocated memory buffer */ +typedef struct memalloc_record { + struct list_head list; + struct vpu_mem_desc mem; +} memalloc_record; + +struct iram_setting { + u32 start; + u32 end; +}; + +static DEFINE_SPINLOCK(vpu_lock); +static LIST_HEAD(head); + +static int vpu_major = 0; +static struct class *vpu_class; +static struct vpu_priv vpu_data; +static u8 open_count = 0; +static struct clk *vpu_clk; +static struct vpu_mem_desc bitwork_mem = { 0 }; +static struct vpu_mem_desc pic_para_mem = { 0 }; +static struct vpu_mem_desc user_data_mem = { 0 }; +static struct vpu_mem_desc share_mem = { 0 }; + +/* IRAM setting */ +static struct iram_setting iram; +/* store SRC base addr */ +static u32 src_base_addr; + +/* implement the blocking ioctl */ +static int codec_done = 0; +static wait_queue_head_t vpu_queue; + +static u32 workctrl_regsave[6]; +static u32 rd_ptr_regsave[4]; +static u32 wr_ptr_regsave[4]; +static u32 dis_flag_regsave[4]; + +#define READ_REG(x) __raw_readl(IO_ADDRESS(VPU_BASE_ADDR+(x))) +#define WRITE_REG(val, x) \ + __raw_writel((val), IO_ADDRESS(VPU_BASE_ADDR+(x))) + +#define SAVE_WORK_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++) \ + workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define RESTORE_WORK_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(workctrl_regsave)/2; i++) \ + WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define SAVE_CTRL_REGS do { \ + int i; \ + for (i = ARRAY_SIZE(workctrl_regsave)/2; \ + i < ARRAY_SIZE(workctrl_regsave); i++) \ + workctrl_regsave[i] = READ_REG(BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define RESTORE_CTRL_REGS do { \ + int i; \ + for (i = ARRAY_SIZE(workctrl_regsave)/2; \ + i < ARRAY_SIZE(workctrl_regsave); i++) \ + WRITE_REG(workctrl_regsave[i], BIT_WORK_CTRL_BUF_REG(i));\ +} while (0) +#define SAVE_RDWR_PTR_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++) \ + rd_ptr_regsave[i] = READ_REG(BIT_RD_PTR_REG(i)); \ + for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++) \ + wr_ptr_regsave[i] = READ_REG(BIT_WR_PTR_REG(i)); \ +} while (0) +#define RESTORE_RDWR_PTR_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(rd_ptr_regsave); i++) \ + WRITE_REG(rd_ptr_regsave[i], BIT_RD_PTR_REG(i)); \ + for (i = 0; i < ARRAY_SIZE(wr_ptr_regsave); i++) \ + WRITE_REG(wr_ptr_regsave[i], BIT_WR_PTR_REG(i)); \ +} while (0) +#define SAVE_DIS_FLAG_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++) \ + dis_flag_regsave[i] = READ_REG(BIT_FRM_DIS_FLG_REG(i)); \ +} while (0) +#define RESTORE_DIS_FLAG_REGS do { \ + int i; \ + for (i = 0; i < ARRAY_SIZE(dis_flag_regsave); i++) \ + WRITE_REG(dis_flag_regsave[i], BIT_FRM_DIS_FLG_REG(i)); \ +} while (0) + +/*! + * Private function to alloc dma buffer + * @return status 0 success. + */ +static int vpu_alloc_dma_buffer(struct vpu_mem_desc *mem) +{ + mem->cpu_addr = (unsigned long) + dma_alloc_coherent(NULL, PAGE_ALIGN(mem->size), + (dma_addr_t *) (&mem->phy_addr), + GFP_DMA | GFP_KERNEL); + pr_debug("[ALLOC] mem alloc cpu_addr = 0x%x\n", mem->cpu_addr); + if ((void *)(mem->cpu_addr) == NULL) { + printk(KERN_ERR "Physical memory allocation error!\n"); + return -1; + } + return 0; +} + +/*! + * Private function to free dma buffer + */ +static void vpu_free_dma_buffer(struct vpu_mem_desc *mem) +{ + if (mem->cpu_addr != 0) { + dma_free_coherent(0, PAGE_ALIGN(mem->size), + (void *)mem->cpu_addr, mem->phy_addr); + } +} + +/*! + * Private function to free buffers + * @return status 0 success. + */ +static int vpu_free_buffers(void) +{ + struct memalloc_record *rec, *n; + struct vpu_mem_desc mem; + + list_for_each_entry_safe(rec, n, &head, list) { + mem = rec->mem; + if (mem.cpu_addr != 0) { + vpu_free_dma_buffer(&mem); + pr_debug("[FREE] freed paddr=0x%08X\n", mem.phy_addr); + /* delete from list */ + list_del(&rec->list); + kfree(rec); + } + } + + return 0; +} + +/*! + * @brief vpu interrupt handler + */ +static irqreturn_t vpu_irq_handler(int irq, void *dev_id) +{ + struct vpu_priv *dev = dev_id; + + READ_REG(BIT_INT_STATUS); + WRITE_REG(0x1, BIT_INT_CLEAR); + + if (dev->async_queue) + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + + /* + * Clock is gated on when dec/enc started, gate it off when + * interrupt is received. + */ + clk_disable(vpu_clk); + + codec_done = 1; + wake_up_interruptible(&vpu_queue); + + return IRQ_HANDLED; +} + +/*! + * @brief open function for vpu file operation + * + * @return 0 on success or negative error code on error + */ +static int vpu_open(struct inode *inode, struct file *filp) +{ + spin_lock(&vpu_lock); + if ((open_count++ == 0) && cpu_is_mx32()) + vl2cc_enable(); + filp->private_data = (void *)(&vpu_data); + spin_unlock(&vpu_lock); + return 0; +} + +/*! + * @brief IO ctrl function for vpu file operation + * @param cmd IO ctrl command + * @return 0 on success or negative error code on error + */ +static int vpu_ioctl(struct inode *inode, struct file *filp, u_int cmd, + u_long arg) +{ + int ret = 0; + + switch (cmd) { + case VPU_IOC_PHYMEM_ALLOC: + { + struct memalloc_record *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return -ENOMEM; + + ret = copy_from_user(&(rec->mem), + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc)); + if (ret) { + kfree(rec); + return -EFAULT; + } + + pr_debug("[ALLOC] mem alloc size = 0x%x\n", + rec->mem.size); + + ret = vpu_alloc_dma_buffer(&(rec->mem)); + if (ret == -1) { + kfree(rec); + printk(KERN_ERR + "Physical memory allocation error!\n"); + break; + } + ret = copy_to_user((void __user *)arg, &(rec->mem), + sizeof(struct vpu_mem_desc)); + if (ret) { + kfree(rec); + ret = -EFAULT; + break; + } + + spin_lock(&vpu_lock); + list_add(&rec->list, &head); + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_PHYMEM_FREE: + { + struct memalloc_record *rec, *n; + struct vpu_mem_desc vpu_mem; + + ret = copy_from_user(&vpu_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc)); + if (ret) + return -EACCES; + + pr_debug("[FREE] mem freed cpu_addr = 0x%x\n", + vpu_mem.cpu_addr); + if ((void *)vpu_mem.cpu_addr != NULL) { + vpu_free_dma_buffer(&vpu_mem); + } + + spin_lock(&vpu_lock); + list_for_each_entry_safe(rec, n, &head, list) { + if (rec->mem.cpu_addr == vpu_mem.cpu_addr) { + /* delete from list */ + list_del(&rec->list); + kfree(rec); + break; + } + } + spin_unlock(&vpu_lock); + + break; + } + case VPU_IOC_WAIT4INT: + { + u_long timeout = (u_long) arg; + if (!wait_event_interruptible_timeout + (vpu_queue, codec_done != 0, + msecs_to_jiffies(timeout))) { + printk(KERN_WARNING "VPU blocking: timeout.\n"); + ret = -ETIME; + } else if (signal_pending(current)) { + printk(KERN_WARNING + "VPU interrupt received.\n"); + ret = -ERESTARTSYS; + } + + codec_done = 0; + break; + } + case VPU_IOC_VL2CC_FLUSH: + if (cpu_is_mx32()) { + vl2cc_flush(); + } + break; + case VPU_IOC_IRAM_SETTING: + { + ret = copy_to_user((void __user *)arg, &iram, + sizeof(struct iram_setting)); + if (ret) + ret = -EFAULT; + + break; + } + case VPU_IOC_CLKGATE_SETTING: + { + u32 clkgate_en; + + if (get_user(clkgate_en, (u32 __user *) arg)) + return -EFAULT; + + if (clkgate_en) { + clk_enable(vpu_clk); + } else { + clk_disable(vpu_clk); + } + + break; + } + case VPU_IOC_GET_SHARE_MEM: + { + spin_lock(&vpu_lock); + if (share_mem.cpu_addr != 0) { + ret = copy_to_user((void __user *)arg, + &share_mem, + sizeof(struct vpu_mem_desc)); + spin_unlock(&vpu_lock); + break; + } else { + if (copy_from_user(&share_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) { + spin_unlock(&vpu_lock); + return -EFAULT; + } + if (vpu_alloc_dma_buffer(&share_mem) == -1) + ret = -EFAULT; + else { + if (copy_to_user((void __user *)arg, + &share_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + } + spin_unlock(&vpu_lock); + break; + } + case VPU_IOC_GET_WORK_ADDR: + { + if (bitwork_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &bitwork_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&bitwork_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&bitwork_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &bitwork_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_GET_PIC_PARA_ADDR: + { + if (pic_para_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &pic_para_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&pic_para_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&pic_para_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &pic_para_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_GET_USER_DATA_ADDR: + { + if (user_data_mem.cpu_addr != 0) { + ret = + copy_to_user((void __user *)arg, + &user_data_mem, + sizeof(struct vpu_mem_desc)); + break; + } else { + if (copy_from_user(&user_data_mem, + (struct vpu_mem_desc *)arg, + sizeof(struct vpu_mem_desc))) + return -EFAULT; + + if (vpu_alloc_dma_buffer(&user_data_mem) == -1) + ret = -EFAULT; + else if (copy_to_user((void __user *)arg, + &user_data_mem, + sizeof(struct + vpu_mem_desc))) + ret = -EFAULT; + } + break; + } + case VPU_IOC_SYS_SW_RESET: + { + if (cpu_is_mx37() || cpu_is_mx51()) { + u32 reg; + + reg = __raw_readl(src_base_addr); + reg |= 0x02; /* SW_VPU_RST_BIT */ + __raw_writel(reg, src_base_addr); + while (__raw_readl(src_base_addr) & 0x02) + ; + } + break; + } + case VPU_IOC_REG_DUMP: + break; + case VPU_IOC_PHYMEM_DUMP: + break; + default: + { + printk(KERN_ERR "No such IOCTL, cmd is %d\n", cmd); + break; + } + } + return ret; +} + +/*! + * @brief Release function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_release(struct inode *inode, struct file *filp) +{ + spin_lock(&vpu_lock); + if (open_count > 0 && !(--open_count)) { + vpu_free_buffers(); + + if (cpu_is_mx32()) + vl2cc_disable(); + + /* Free shared memory when vpu device is idle */ + vpu_free_dma_buffer(&share_mem); + share_mem.cpu_addr = 0; + } + spin_unlock(&vpu_lock); + + return 0; +} + +/*! + * @brief fasync function for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_fasync(int fd, struct file *filp, int mode) +{ + struct vpu_priv *dev = (struct vpu_priv *)filp->private_data; + return fasync_helper(fd, filp, mode, &dev->async_queue); +} + +/*! + * @brief memory map function of harware registers for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_map_hwregs(struct file *fp, struct vm_area_struct *vm) +{ + unsigned long pfn; + + vm->vm_flags |= VM_IO | VM_RESERVED; + vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); + pfn = VPU_BASE_ADDR >> PAGE_SHIFT; + pr_debug("size=0x%x, page no.=0x%x\n", + (int)(vm->vm_end - vm->vm_start), (int)pfn); + return remap_pfn_range(vm, vm->vm_start, pfn, vm->vm_end - vm->vm_start, + vm->vm_page_prot) ? -EAGAIN : 0; +} + +/*! + * @brief memory map function of memory for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_map_mem(struct file *fp, struct vm_area_struct *vm) +{ + int request_size; + request_size = vm->vm_end - vm->vm_start; + + pr_debug(" start=0x%x, pgoff=0x%x, size=0x%x\n", + (unsigned int)(vm->vm_start), (unsigned int)(vm->vm_pgoff), + request_size); + + vm->vm_flags |= VM_IO | VM_RESERVED; + vm->vm_page_prot = pgprot_noncached(vm->vm_page_prot); + + return remap_pfn_range(vm, vm->vm_start, vm->vm_pgoff, + request_size, vm->vm_page_prot) ? -EAGAIN : 0; + +} + +/*! + * @brief memory map interface for vpu file operation + * @return 0 on success or negative error code on error + */ +static int vpu_mmap(struct file *fp, struct vm_area_struct *vm) +{ + if (vm->vm_pgoff) + return vpu_map_mem(fp, vm); + else + return vpu_map_hwregs(fp, vm); +} + +struct file_operations vpu_fops = { + .owner = THIS_MODULE, + .open = vpu_open, + .ioctl = vpu_ioctl, + .release = vpu_release, + .fasync = vpu_fasync, + .mmap = vpu_mmap, +}; + +/*! + * This function is called by the driver framework to initialize the vpu device. + * @param dev The device structure for the vpu passed in by the framework. + * @return 0 on success or negative error code on error + */ +static int vpu_dev_probe(struct platform_device *pdev) +{ + int err = 0; + struct device *temp_class; + struct resource *res; + + if (cpu_is_mx32()) { + /* Obtain VL2CC base address */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ERR "vpu: unable to get VL2CC base\n"); + return -ENODEV; + } + + err = vl2cc_init(res->start); + if (err != 0) + return err; + } + + if (cpu_is_mx37() || cpu_is_mx51()) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ERR "vpu: unable to get VPU IRAM base\n"); + return -ENODEV; + } + iram.start = res->start; + iram.end = res->end; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + printk(KERN_ERR "vpu: unable to get src base addr\n"); + return -ENODEV; + } + src_base_addr = res->start; + } + + vpu_major = register_chrdev(vpu_major, "mxc_vpu", &vpu_fops); + if (vpu_major < 0) { + printk(KERN_ERR "vpu: unable to get a major for VPU\n"); + err = -EBUSY; + goto error; + } + + vpu_class = class_create(THIS_MODULE, "mxc_vpu"); + if (IS_ERR(vpu_class)) { + err = PTR_ERR(vpu_class); + goto err_out_chrdev; + } + + temp_class = device_create(vpu_class, NULL, MKDEV(vpu_major, 0), + NULL, "mxc_vpu"); + if (IS_ERR(temp_class)) { + err = PTR_ERR(temp_class); + goto err_out_class; + } + + vpu_clk = clk_get(&pdev->dev, "vpu_clk"); + if (IS_ERR(vpu_clk)) { + err = -ENOENT; + goto err_out_class; + } + + err = request_irq(MXC_INT_VPU, vpu_irq_handler, 0, "VPU_CODEC_IRQ", + (void *)(&vpu_data)); + if (err) + goto err_out_class; + + printk(KERN_INFO "VPU initialized\n"); + goto out; + + err_out_class: + device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + err_out_chrdev: + unregister_chrdev(vpu_major, "mxc_vpu"); + error: + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + out: + return err; +} + +#ifdef CONFIG_PM +static int vpu_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (codec_done == 1) + return -EAGAIN; + + clk_enable(vpu_clk); + if (bitwork_mem.cpu_addr != 0) { + SAVE_WORK_REGS; + SAVE_CTRL_REGS; + SAVE_RDWR_PTR_REGS; + SAVE_DIS_FLAG_REGS; + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(VPU_SLEEP_REG_VALUE, BIT_RUN_COMMAND); + while (READ_REG(BIT_BUSY_FLAG)) ; + } + + clk_disable(vpu_clk); + + if (cpu_is_mx37() || cpu_is_mx51()) + mxc_pg_enable(pdev); + + return 0; +} + +static int vpu_resume(struct platform_device *pdev) +{ + if (cpu_is_mx37() || cpu_is_mx51()) + mxc_pg_disable(pdev); + + clk_enable(vpu_clk); + + if (bitwork_mem.cpu_addr != 0) { + u32 *p = (u32 *) bitwork_mem.cpu_addr; + u32 data; + u16 data_hi; + u16 data_lo; + int i; + + RESTORE_WORK_REGS; + + WRITE_REG(0x0, BIT_RESET_CTRL); + WRITE_REG(0x0, BIT_CODE_RUN); + + /* + * Re-load boot code, from the codebuffer in external RAM. + * Thankfully, we only need 4096 bytes, same for all platforms. + */ + if (cpu_is_mx51()) { + for (i = 0; i < 2048; i += 4) { + data = p[(i / 2) + 1]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN); + WRITE_REG(((i + 1) << 16) | data_lo, + BIT_CODE_DOWN); + + data = p[i / 2]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + WRITE_REG(((i + 2) << 16) | data_hi, + BIT_CODE_DOWN); + WRITE_REG(((i + 3) << 16) | data_lo, + BIT_CODE_DOWN); + } + } else { + for (i = 0; i < 2048; i += 2) { + if (cpu_is_mx37()) + data = swab32(p[i / 2]); + else + data = p[i / 2]; + data_hi = (data >> 16) & 0xFFFF; + data_lo = data & 0xFFFF; + + WRITE_REG((i << 16) | data_hi, BIT_CODE_DOWN); + WRITE_REG(((i + 1) << 16) | data_lo, + BIT_CODE_DOWN); + } + } + + RESTORE_CTRL_REGS; + + WRITE_REG(BITVAL_PIC_RUN, BIT_INT_ENABLE); + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(0x1, BIT_CODE_RUN); + while (READ_REG(BIT_BUSY_FLAG)) ; + + RESTORE_RDWR_PTR_REGS; + RESTORE_DIS_FLAG_REGS; + + WRITE_REG(0x1, BIT_BUSY_FLAG); + WRITE_REG(VPU_WAKE_REG_VALUE, BIT_RUN_COMMAND); + while (READ_REG(BIT_BUSY_FLAG)) ; + } + + clk_disable(vpu_clk); + + return 0; +} +#else +#define vpu_suspend NULL +#define vpu_resume NULL +#endif /* !CONFIG_PM */ + +/*! Driver definition + * + */ +static struct platform_driver mxcvpu_driver = { + .driver = { + .name = "mxc_vpu", + }, + .probe = vpu_dev_probe, + .suspend = vpu_suspend, + .resume = vpu_resume, +}; + +static int __init vpu_init(void) +{ + int ret = platform_driver_register(&mxcvpu_driver); + + init_waitqueue_head(&vpu_queue); + + return ret; +} + +static void __exit vpu_exit(void) +{ + free_irq(MXC_INT_VPU, (void *)(&vpu_data)); + if (vpu_major > 0) { + device_destroy(vpu_class, MKDEV(vpu_major, 0)); + class_destroy(vpu_class); + unregister_chrdev(vpu_major, "mxc_vpu"); + vpu_major = 0; + } + + if (cpu_is_mx32()) { + vl2cc_cleanup(); + } + + vpu_free_dma_buffer(&bitwork_mem); + vpu_free_dma_buffer(&pic_para_mem); + vpu_free_dma_buffer(&user_data_mem); + + clk_put(vpu_clk); + + platform_driver_unregister(&mxcvpu_driver); + return; +} + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX/MXC"); +MODULE_LICENSE("GPL"); + +module_init(vpu_init); +module_exit(vpu_exit); |