diff options
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 46 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 4 | ||||
-rw-r--r-- | drivers/mfd/ab5500-core.c | 72 | ||||
-rw-r--r-- | drivers/mfd/ab5500-gpadc.c | 1224 | ||||
-rw-r--r-- | drivers/mfd/ab5500-power.c | 96 | ||||
-rw-r--r-- | drivers/mfd/ab8500-core.c | 63 | ||||
-rw-r--r-- | drivers/mfd/ab8500-debugfs.c | 1057 | ||||
-rw-r--r-- | drivers/mfd/ab8500-denc.c | 539 | ||||
-rw-r--r-- | drivers/mfd/ab8500-gpadc.c | 12 | ||||
-rw-r--r-- | drivers/mfd/ab8500-i2c.c | 1 | ||||
-rw-r--r-- | drivers/mfd/ab8500-sysctrl.c | 135 | ||||
-rw-r--r-- | drivers/mfd/db5500-prcmu-regs.h | 141 | ||||
-rw-r--r-- | drivers/mfd/db5500-prcmu.c | 2050 | ||||
-rw-r--r-- | drivers/mfd/db8500-prcmu.c | 925 | ||||
-rw-r--r-- | drivers/mfd/dbx500-prcmu-regs.h | 131 | ||||
-rw-r--r-- | drivers/mfd/stmpe.c | 18 | ||||
-rw-r--r-- | drivers/mfd/tc35892.c | 503 | ||||
-rw-r--r-- | drivers/mfd/tc3589x.c | 131 | ||||
-rw-r--r-- | drivers/mfd/tps6105x.c | 1 |
19 files changed, 6799 insertions, 350 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index f147395bac9..2e9c2d81d80 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -307,6 +307,17 @@ config MFD_TC3589X additional drivers must be enabled in order to use the functionality of the device. +config MFD_TC35892 + bool "Support Toshiba TC35892" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Support for the Toshiba TC35892 I/O Expander. + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config MFD_TMIO bool default n @@ -335,6 +346,27 @@ config MFD_TC6393XB help Support for Toshiba Mobile IO Controller TC6393XB +config AB5500_CORE + bool "ST-Ericsson AB5500 Mixed Signal Circuit core functions" + select MFD_CORE + depends on GENERIC_HARDIRQS && ABX500_CORE + help + Select this to enable the AB5500 Mixed Signal IC core + functionality. This connects to a AB5500 chip on the I2C bus via + the Power and Reset Management Unit (PRCMU). It exposes a number + of symbols needed for dependent devices to read and write + registers and subscribe to events from this multi-functional IC. + This is needed to use other features of the AB5500 such as + battery-backed RTC, charging control, Regulators, LEDs, vibrator, + system power and temperature, power management and ALSA sound. + +config AB5500_GPADC + bool "AB5500 GPADC driver" + depends on AB5500_CORE + default y + help + AB5500 GPADC driver used to convert battery/usb voltage. + config PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" depends on I2C=y @@ -654,7 +686,7 @@ config AB8500_CORE config AB8500_I2C_CORE bool "AB8500 register access via PRCMU I2C" - depends on AB8500_CORE && MFD_DB8500_PRCMU + depends on AB8500_CORE default y help This enables register access to the AB8500 chip via PRCMU I2C. @@ -662,6 +694,14 @@ config AB8500_I2C_CORE the I2C bus is connected to the Power Reset and Mangagement Unit, PRCMU. +config AB8500_DENC + bool "AB8500_DENC driver support(CVBS)" + depends on AB8500_CORE + help + Select this option to add driver support for analog TV out through + AB8500. + + config AB8500_DEBUG bool "Enable debug info via debugfs" depends on AB8500_CORE && DEBUG_FS @@ -672,10 +712,10 @@ config AB8500_DEBUG config AB8500_GPADC bool "AB8500 GPADC driver" - depends on AB8500_CORE && REGULATOR_AB8500 + depends on AB8500_CORE default y help - AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage + AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage. config MFD_DB8500_PRCMU bool "ST-Ericsson DB8500 Power Reset Control Management Unit" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b953bab934f..2b31af7dc8c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -2,6 +2,7 @@ # Makefile for multifunction miscellaneous devices # +obj-$(CONFIG_AB5500_CORE) += ab5500-core.o ab5500-power.o 88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o @@ -19,6 +20,7 @@ obj-$(CONFIG_MFD_STMPE) += stmpe.o obj-$(CONFIG_STMPE_I2C) += stmpe-i2c.o obj-$(CONFIG_STMPE_SPI) += stmpe-spi.o obj-$(CONFIG_MFD_TC3589X) += tc3589x.o +obj-$(CONFIG_MFD_TC35892) += tc35892.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o @@ -90,11 +92,13 @@ obj-$(CONFIG_AB5500_CORE) += ab5500-core.o obj-$(CONFIG_AB5500_DEBUG) += ab5500-debugfs.o obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o +obj-$(CONFIG_AB8500_DENC) += ab8500-denc.o obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o # ab8500-i2c need to come after db8500-prcmu (which provides the channel) obj-$(CONFIG_AB8500_I2C_CORE) += ab8500-i2c.o obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o +obj-$(CONFIG_AB5500_GPADC) += ab5500-gpadc.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index bd56a764dea..7da5fa3ba35 100644 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -992,6 +992,74 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }, }, + [AB5500_DEVID_TEMPMON] = { + .name = "abx500-temp", + .id = AB5500_DEVID_TEMPMON, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "ABX500_TEMP_WARM", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 2), + .end = AB5500_IRQ(2, 2), + }, + }, + }, + [AB5500_DEVID_ACCDET] = { + .name = "ab5500-acc-det", + .id = AB5500_DEVID_ACCDET, + .num_resources = 8, + .resources = (struct resource[]) { + { + .name = "acc_detedt22db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 7), + .end = AB5500_IRQ(2, 7), + }, + { + .name = "acc_detedt21db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 6), + .end = AB5500_IRQ(2, 6), + }, + { + .name = "acc_detedt21db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 5), + .end = AB5500_IRQ(2, 5), + }, + { + .name = "acc_detedt3db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 4), + .end = AB5500_IRQ(3, 4), + }, + { + .name = "acc_detedt3db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 3), + .end = AB5500_IRQ(3, 3), + }, + { + .name = "acc_detedt1db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 2), + .end = AB5500_IRQ(3, 2), + }, + { + .name = "acc_detedt1db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 1), + .end = AB5500_IRQ(3, 1), + }, + { + .name = "acc_detedt22db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 0), + .end = AB5500_IRQ(3, 0), + }, + }, + }, }; /* @@ -1302,6 +1370,10 @@ static const struct ab_family_id ids[] __initdata = { .id = AB5500_1_1, .name = "1.1" }, + { + .id = AB5500_2_0, + .name = "2.0" + }, /* Terminator */ { .id = 0x00, diff --git a/drivers/mfd/ab5500-gpadc.c b/drivers/mfd/ab5500-gpadc.c new file mode 100644 index 00000000000..d099f1b9d73 --- /dev/null +++ b/drivers/mfd/ab5500-gpadc.c @@ -0,0 +1,1224 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Vijaya Kumar K <vijay.kilari@stericsson.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> + +/* + * Manual mode ADC registers + */ +#define AB5500_GPADC_MANUAL_STAT_REG 0x1F +#define AB5500_GPADC_MANDATAL_REG 0x21 +#define AB5500_GPADC_MANDATAH_REG 0x20 +#define AB5500_GPADC_MANUAL_MUX_CTRL 0x22 +#define AB5500_GPADC_MANUAL_MODE_CTRL 0x23 +#define AB5500_GPADC_MANUAL_MODE_CTRL2 0x24 +/* + * Auto/Polling mode ADC registers + */ +#define AB5500_GPADC_AUTO_VBAT_MAX 0x26 +#define AB5500_GPADC_AUTO_VBAT_MIN_TXON 0x27 +#define AB5500_GPADC_AUTO_VBAT_MIN_NOTX 0x28 +#define AB5500_GPADC_AUTO_VBAT_AVGH 0x29 +#define AB5500_GPADC_AUTO_VBAT_AVGL 0x2A +#define AB5500_GPADC_AUTO_ICHAR_MAX 0x2B +#define AB5500_GPADC_AUTO_ICHAR_MIN 0x2C +#define AB5500_GPADC_AUTO_ICHAR_AVG 0x2D +#define AB5500_GPADC_AUTO_CTRL2 0x2F +#define AB5500_GPADC_AUTO_CTRL1 0x30 +#define AB5500_GPADC_AUTO_PWR_CTRL 0x31 +#define AB5500_GPADC_AUTO_TRIG_VBAT_MIN_TXON 0x32 +#define AB5500_GPADC_AUTO_TRIG_VBAT_MIN_NOTX 0x33 +#define AB5500_GPADC_AUTO_TRIG_ADOUT0_CTRL 0x34 +#define AB5500_GPADC_AUTO_TRIG_ADOUT1_CTRL 0x35 +#define AB5500_GPADC_AUTO_TRIG0_MUX_CTRL 0x37 +#define AB5500_GPADC_AUTO_XTALTEMP_CTRL 0x57 +#define AB5500_GPADC_KELVIN_CTRL 0xFE + +/* gpadc constants */ +#define AB5500_INT_ADC_TRIG0 0x0 +#define AB5500_INT_ADC_TRIG1 0x1 +#define AB5500_INT_ADC_TRIG2 0x2 +#define AB5500_INT_ADC_TRIG3 0x3 +#define AB5500_INT_ADC_TRIG4 0x4 +#define AB5500_INT_ADC_TRIG5 0x5 +#define AB5500_INT_ADC_TRIG6 0x6 +#define AB5500_INT_ADC_TRIG7 0x7 + +#define AB5500_GPADC_AUTO_TRIG_INDEX AB5500_GPADC_AUTO_TRIG0_MUX_CTRL +#define GPADC_MANUAL_READY 0x01 +#define GPADC_MANUAL_ADOUT0_MASK 0x30 +#define GPADC_MANUAL_ADOUT1_MASK 0xC0 +#define GPADC_MANUAL_ADOUT0_ON 0x10 +#define GPADC_MANUAL_ADOUT1_ON 0x40 +#define MUX_SCALE_GPADC0_MASK 0x08 +#define MUX_SCALE_VBAT_MASK 0x02 +#define MUX_SCALE_45 0x02 +#define MUX_SCALE_BDATA_MASK 0x01 +#define MUX_SCALE_BDATA27 0x00 +#define MUX_SCALE_BDATA18 0x01 +#define MUX_SCALE_ACCDET2_MASK 0x01 +#define MUX_SCALE_ACCDET3_MASK 0x02 +#define GPADC0_SCALE_VOL27 0x00 +#define GPADC0_SCALE_VOL18 0x01 +#define ACCDET2_SCALE_VOL27 0x00 +#define ACCDET3_SCALE_VOL27 0x00 +#define TRIGX_FREQ_MASK 0x07 +#define AUTO_VBAT_MASK 0x10 +#define AUTO_VBAT_ON 0x10 +#define TRIG_VBAT_TXON_ARM_MASK 0x08 +#define TRIG_VBAT_NOTX_ARM_MASK 0x04 +#define TRIGX_ARM_MASK 0x20 +#define TRIGX_ARM 0x20 +#define TRIGX_MUX_SELECT 0x1F +#define ADC_CAL_OFF_MASK 0x04 +#define ADC_ON_MODE_MASK 0x03 +#define ADC_CAL_ON 0x00 +#define ADC_FULLPWR 0x03 +#define ADC_XTAL_FORCE_MASK 0x80 +#define ADC_XTAL_FORCE_EN 0x80 +#define ADC_XTAL_FORCE_DI 0x00 +#define ADOUT0 0x01 +#define ADOUT1 0x02 +#define MIN_INDEX 0x02 +#define MAX_INDEX 0x03 +#define CTRL_INDEX 0x01 + +/* GPADC constants from AB5500 spec */ +#define GPADC0_MIN 0 +#define GPADC0_MAX 1800 +#define BTEMP_MIN 0 +#define BTEMP_MAX 1800 +#define BDATA_MIN 0 +#define BDATA_MAX 2750 +#define PCBTEMP_MIN 0 +#define PCBTEMP_MAX 1800 +#define XTALTEMP_MIN 0 +#define XTALTEMP_MAX 1800 +#define DIETEMP_MIN 0 +#define DIETEMP_MAX 1800 +#define VBUS_I_MIN 0 +#define VBUS_I_MAX 1600 +#define VBUS_V_MIN 0 +#define VBUS_V_MAX 20000 +#define ACCDET2_MIN 0 +#define ACCDET2_MAX 2500 +#define ACCDET3_MIN 0 +#define ACCDET3_MAX 2500 +#define VBAT_MIN 2300 +#define VBAT_MAX 4500 +#define BKBAT_MIN 0 +#define BKBAT_MAX 2750 +#define USBID_MIN 0 +#define USBID_MAX 1800 +#define KELVIN_MIN 0 +#define KELVIN_MAX 4500 + +/* This is used for calibration */ +#define ADC_RESOLUTION 1023 +#define AUTO_ADC_RESOLUTION 255 + +enum adc_auto_channels { + ADC_INPUT_TRIG0 = 0, + ADC_INPUT_TRIG1, + ADC_INPUT_TRIG2, + ADC_INPUT_TRIG3, + ADC_INPUT_TRIG4, + ADC_INPUT_TRIG5, + ADC_INPUT_TRIG6, + ADC_INPUT_TRIG7, + ADC_INPUT_VBAT_TXOFF, + ADC_INPUT_VBAT_TXON, + N_AUTO_TRIGGER +}; + +/** + * struct adc_auto_trigger - AB5500 GPADC auto trigger + * @adc_mux Mux input + * @flag Status of trigger + * @freq Frequency of conversion + * @adout Adout to pull + * @trig_min trigger minimum value + * @trig_max trigger maximum value + * @auto_adc_callback notification callback + */ +struct adc_auto_trigger { + u8 auto_mux; + u8 flag; + u8 freq; + u8 adout; + u8 trig_min; + u8 trig_max; + int (*auto_callb)(int mux); +}; + +/** + * struct ab5500_btemp_interrupts - ab5500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_adc_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +/** + * struct ab5500_gpadc - AB5500 GPADC device information + * @chip_id ABB chip id + * @dev: pointer to the struct device + * @node: a list of AB5500 GPADCs, hence prepared for + reentrance + * @ab5500_gpadc_complete: pointer to the struct completion, to indicate + * the completion of gpadc conversion + * @ab5500_gpadc_lock: structure of type mutex + * @regu: pointer to the struct regulator + * @irq: interrupt number that is used by gpadc + * @cal_data array of ADC calibration data structs + * @auto_trig auto trigger channel + * @gpadc_trigX_work work items for trigger channels + */ +struct ab5500_gpadc { + u8 chip_id; + struct device *dev; + struct list_head node; + struct mutex ab5500_gpadc_lock; + struct regulator *regu; + int irq; + int prev_bdata; + spinlock_t gpadc_auto_lock; + struct adc_auto_trigger adc_trig[N_AUTO_TRIGGER]; + struct workqueue_struct *gpadc_wq; + struct work_struct gpadc_trig0_work; + struct work_struct gpadc_trig1_work; + struct work_struct gpadc_trig2_work; + struct work_struct gpadc_trig3_work; + struct work_struct gpadc_trig4_work; + struct work_struct gpadc_trig5_work; + struct work_struct gpadc_trig6_work; + struct work_struct gpadc_trig7_work; + struct work_struct gpadc_trig_vbat_txon_work; + struct work_struct gpadc_trig_vbat_txoff_work; +}; + +static LIST_HEAD(ab5500_gpadc_list); + +struct adc_data { + u8 mux; + int min; + int max; + int adout; +}; + +#define ADC_DATA(_id, _mux, _min, _max, _adout) \ + [_id] = { \ + .mux = _mux, \ + .min = _min, \ + .max = _max, \ + .adout = _adout \ + } + +struct adc_data adc_tab[] = { + ADC_DATA(GPADC0_V, 0x00, GPADC0_MIN, GPADC0_MAX, 0), + ADC_DATA(BTEMP_BALL, 0x0D, BTEMP_MIN, BTEMP_MAX, ADOUT0), + ADC_DATA(BAT_CTRL, 0x0D, BDATA_MIN, BDATA_MAX, 0), + ADC_DATA(MAIN_BAT_V, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(MAIN_BAT_V_TXON, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(VBUS_V, 0x10, VBUS_V_MIN, VBUS_V_MAX, 0), + ADC_DATA(USB_CHARGER_C, 0x0A, VBUS_I_MIN, VBUS_I_MAX, 0), + ADC_DATA(BK_BAT_V, 0x07, BKBAT_MIN, BKBAT_MAX, 0), + ADC_DATA(DIE_TEMP, 0x0F, DIETEMP_MIN, DIETEMP_MAX, ADOUT0), + ADC_DATA(PCB_TEMP, 0x13, PCBTEMP_MIN, PCBTEMP_MAX, ADOUT0), + ADC_DATA(XTAL_TEMP, 0x06, XTALTEMP_MIN, XTALTEMP_MAX, ADOUT0), + ADC_DATA(USB_ID, 0x1A, USBID_MIN, USBID_MAX, 0), + ADC_DATA(ACC_DETECT2, 0x18, ACCDET2_MIN, ACCDET2_MAX, 0), + ADC_DATA(ACC_DETECT3, 0x19, ACCDET3_MIN, ACCDET3_MAX, 0), + ADC_DATA(MAIN_BAT_V_TRIG_MIN, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(MAIN_BAT_V_TXON_TRIG_MIN, 0x0C, VBAT_MIN, VBAT_MAX, 0), +}; +/** + * ab5500_gpadc_get() - returns a reference to the primary AB5500 GPADC + * (i.e. the first GPADC in the instance list) + */ +struct ab5500_gpadc *ab5500_gpadc_get(const char *name) +{ + struct ab5500_gpadc *gpadc; + list_for_each_entry(gpadc, &ab5500_gpadc_list, node) { + if (!strcmp(name, dev_name(gpadc->dev))) + return gpadc; + } + + return ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL(ab5500_gpadc_get); + +#define CONV(min, max, x)\ + ((min) + ((((max)-(min))*(x))/ADC_RESOLUTION)) + +static int ab5500_gpadc_ad_to_voltage(struct ab5500_gpadc *gpadc, + u8 in, u16 ad_val) +{ + int res; + + switch (in) { + case GPADC0_V: + case PCB_TEMP: + case BTEMP_BALL: + case MAIN_BAT_V: + case MAIN_BAT_V_TXON: + case ACC_DETECT2: + case ACC_DETECT3: + case VBUS_V: + case USB_CHARGER_C: + case BK_BAT_V: + case XTAL_TEMP: + case USB_ID: + case BAT_CTRL: + res = CONV(adc_tab[in].min, adc_tab[in].max, ad_val); + break; + case DIE_TEMP: + /* + * From the AB5500 product specification + * T(deg cel) = 27 - ((ADCode - 709)/2.4213) + * 27 + 709/2.4213 - ADCode/2.4213 + * 320 - (ADCode/2.4213) + */ + res = 320 - (((unsigned long)ad_val * 10000) / 24213); + break; + default: + dev_err(gpadc->dev, + "unknown channel, not possible to convert\n"); + res = -EINVAL; + break; + } + return res; +} + +/** + * ab5500_gpadc_convert() - gpadc conversion + * @input: analog input to be converted to digital data + * + * This function converts the selected analog i/p to digital + * data. + */ +int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input) +{ + int result, ret = -EINVAL; + u16 data = 0; + u8 looplimit = 0; + u8 status = 0; + u8 low_data, high_data, adout_mask, adout_val; + + if (!gpadc) + return -ENODEV; + + mutex_lock(&gpadc->ab5500_gpadc_lock); + + switch (input) { + case MAIN_BAT_V: + case MAIN_BAT_V_TXON: + /* + * The value of mux scale volatage depends + * on the type of battery + * for LI-ion use MUX_SCALE_35 => 2.3-3.5V + * for LiFePo4 use MUX_SCALE_45 => 2.3-4.5V + * Check type of battery from platform data TODO ??? + */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to read status\n"); + goto out; + } + break; + case BTEMP_BALL: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_BDATA_MASK, MUX_SCALE_BDATA27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set mux scale\n"); + goto out; + } + break; + case BAT_CTRL: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_BDATA_MASK, MUX_SCALE_BDATA27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set mux scale\n"); + goto out; + } + break; + case XTAL_TEMP: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XTALTEMP_CTRL, + ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_EN); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set xtaltemp\n"); + goto out; + } + break; + case GPADC0_V: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_GPADC0_MASK, GPADC0_SCALE_VOL18); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set gpadc0\n"); + goto out; + } + break; + case ACC_DETECT2: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL2, + MUX_SCALE_ACCDET2_MASK, ACCDET2_SCALE_VOL27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set accdet2\n"); + goto out; + } + break; + case ACC_DETECT3: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL2, + MUX_SCALE_ACCDET3_MASK, ACCDET3_SCALE_VOL27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set accdet3\n"); + goto out; + } + break; + case USB_CHARGER_C: + case VBUS_V: + case BK_BAT_V: + case USB_ID: + case PCB_TEMP: + case DIE_TEMP: + break; + default: + dev_err(gpadc->dev, "gpadc: Wrong adc\n"); + goto out; + break; + } + if (adc_tab[input].adout) { + adout_mask = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_MASK : GPADC_MANUAL_ADOUT1_MASK; + adout_val = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_ON : GPADC_MANUAL_ADOUT1_ON; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + adout_mask, adout_val); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set ADOUT\n"); + goto out; + } + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANUAL_MUX_CTRL, adc_tab[input].mux); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to trigger manual conv\n"); + goto out; + } + /* wait for completion of conversion */ + looplimit = 0; + do { + msleep(1); + ret = abx500_get_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_STAT_REG, + &status); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to read status\n"); + goto out; + } + if (status & GPADC_MANUAL_READY) + break; + } while (++looplimit < 2); + if (looplimit >= 2) { + dev_err(gpadc->dev, "timeout:failed to complete conversion\n"); + ret = -EINVAL; + goto out; + } + + /* + * Disable ADOUT for measurement + */ + if (adc_tab[input].adout) { + adout_mask = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_MASK : GPADC_MANUAL_ADOUT1_MASK; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + adout_mask, 0x0); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to disable ADOUT\n"); + goto out; + } + } + /* + * Disable XTAL TEMP + */ + if (input == XTAL_TEMP) { + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XTALTEMP_CTRL, + ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_DI); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to disable xtaltemp\n"); + goto out; + } + } + /* Read the converted RAW data */ + ret = abx500_get_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANDATAL_REG, &low_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: read low data failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANDATAH_REG, &high_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: read high data failed\n"); + goto out; + } + + data = (high_data << 2) | (low_data >> 6); + if (input == BAT_CTRL || input == BTEMP_BALL) { + /* + * TODO: Re-check with h/w team + * discard null or value < 5, as there is some error + * in conversion + */ + if (data < 5) + data = gpadc->prev_bdata; + else + gpadc->prev_bdata = data; + } + result = ab5500_gpadc_ad_to_voltage(gpadc, input, data); + + mutex_unlock(&gpadc->ab5500_gpadc_lock); + return result; + +out: + mutex_unlock(&gpadc->ab5500_gpadc_lock); + dev_err(gpadc->dev, + "gpadc: Failed to AD convert channel %d\n", input); + return ret; +} +EXPORT_SYMBOL(ab5500_gpadc_convert); + +/** + * ab5500_gpadc_program_auto() - gpadc conversion auto conversion + * @trig_index: Generic trigger channel for conversion + * + * This function program the auto trigger channel + */ +static int ab5500_gpadc_program_auto(struct ab5500_gpadc *gpadc, int trig) +{ + int ret; + u8 adout; +#define MIN_INDEX 0x02 +#define MAX_INDEX 0x03 +#define CTRL_INDEX 0x01 + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + MIN_INDEX, + gpadc->adc_trig[trig].trig_min); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program min\n"); + return ret; + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + MAX_INDEX, + gpadc->adc_trig[trig].trig_max); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program max\n"); + return ret; + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2), + TRIGX_MUX_SELECT, gpadc->adc_trig[trig].auto_mux); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to select mux\n"); + return ret; + } + if (gpadc->adc_trig[trig].adout) { + adout = gpadc->adc_trig[trig].adout == ADOUT0 ? + gpadc->adc_trig[trig].adout << 6 : + gpadc->adc_trig[trig].adout << 5; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + CTRL_INDEX, + adout, adout); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program adout\n"); + return ret; + } + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + CTRL_INDEX, + TRIGX_FREQ_MASK, gpadc->adc_trig[trig].freq); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program freq\n"); + return ret; + } + return ret; + +} + +#define TRIG_V(trigval, min, max) \ + ((((trigval) - (min)) * AUTO_ADC_RESOLUTION) / ((max) - (min))) + +static int ab5500_gpadc_vbat_auto_conf(struct ab5500_gpadc *gpadc, + struct adc_auto_input *in) +{ + int trig_min, ret; + u8 trig_reg, trig_arm; + + /* Scale mux voltage */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to set vbat scale\n"); + return ret; + } + + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_CTRL1, + AUTO_VBAT_MASK, AUTO_VBAT_ON); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to set vbat on\n"); + return ret; + } + + trig_min = TRIG_V(in->min, adc_tab[in->mux].min, adc_tab[in->mux].max); + + if (in->mux == MAIN_BAT_V_TRIG_MIN) { + trig_reg = AB5500_GPADC_AUTO_TRIG_VBAT_MIN_NOTX; + trig_arm = TRIG_VBAT_NOTX_ARM_MASK; + } else { + trig_reg = AB5500_GPADC_AUTO_TRIG_VBAT_MIN_TXON; + trig_arm = TRIG_VBAT_TXON_ARM_MASK; + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + trig_reg, trig_min); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program vbat min\n"); + return ret; + } + /* + * arm the trigger + */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_CTRL1, trig_arm, trig_arm); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to trig vbat\n"); + return ret; + } + return ret; +} +/** + * ab5500_gpadc_convert_auto() - gpadc conversion + * @auto_input: input trigger for conversion + * + * This function converts the selected channel from + * analog to digital data in auto mode + */ + +int ab5500_gpadc_convert_auto(struct ab5500_gpadc *gpadc, + struct adc_auto_input *in) +{ + int ret, trig; + unsigned long flags; + + if (!gpadc) + return -ENODEV; + mutex_lock(&gpadc->ab5500_gpadc_lock); + + if (in->mux == MAIN_BAT_V_TXON_TRIG_MIN) { + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + if (gpadc->adc_trig[ADC_INPUT_VBAT_TXON].flag == true) { + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: Auto vbat txon busy"); + goto out; + } + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + + ret = ab5500_gpadc_vbat_auto_conf(gpadc, in); + if (ret < 0) + goto out; + + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].auto_mux = in->mux; + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].auto_callb = + in->auto_adc_callback; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } else if (in->mux == MAIN_BAT_V_TRIG_MIN) { + + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + if (gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].flag == true) { + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: Auto vbat busy"); + goto out; + } + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + + ret = ab5500_gpadc_vbat_auto_conf(gpadc, in); + if (ret < 0) + goto out; + + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].auto_mux = in->mux; + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].auto_callb = + in->auto_adc_callback; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } else { + /* + * check if free trigger is available + */ + trig = ADC_INPUT_TRIG0; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + while (gpadc->adc_trig[trig].flag == true && + trig <= ADC_INPUT_TRIG7) + trig++; + + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + if (trig > ADC_INPUT_TRIG7) { + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: no free channel\n"); + goto out; + } + switch (in->mux) { + case BTEMP_BALL: + case MAIN_BAT_V: + /* + * The value of mux scale volatage depends + * on the type of battery + * for LI-ion use MUX_SCALE_35 => 2.3-3.5V + * for LiFePo4 use MUX_SCALE_45 => 2.3-4.5V + * Check type of battery from platform data TODO ??? + */ + ret = abx500_mask_and_set_register_interruptible( + gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: failed to read status\n"); + goto out; + } + case ACC_DETECT2: + case ACC_DETECT3: + case VBUS_V: + case USB_CHARGER_C: + case BK_BAT_V: + case PCB_TEMP: + case USB_ID: + case BAT_CTRL: + gpadc->adc_trig[trig].trig_min = + (u8)TRIG_V(in->min, adc_tab[in->mux].min, + adc_tab[in->mux].max); + gpadc->adc_trig[trig].trig_max = + (u8)TRIG_V(in->max, adc_tab[in->mux].min, + adc_tab[in->mux].max); + gpadc->adc_trig[trig].adout = + adc_tab[in->mux].adout; + break; + case DIE_TEMP: + /* + * From the AB5500 product specification + * T(deg_cel) = 27 - (ADCode - 709)/2.4213) + * ADCode = 709 + (2.4213 * (27 - T)) + * Auto trigger min/max level is of 8bit precision. + * Hence use AB5500_GPADC_MANDATAH_REG value + * obtained by 2 bit right shift of ADCode. + */ + gpadc->adc_trig[trig].trig_min = + (709 + ((24213 * (27 - in->min))/10000))>>2; + gpadc->adc_trig[trig].trig_max = + (709 + ((24213 * (27 - in->max))/10000))>>2; + gpadc->adc_trig[trig].adout = + adc_tab[in->mux].adout; + break; + default: + dev_err(gpadc->dev, "Unknow GPADC request\n"); + break; + } + gpadc->adc_trig[trig].freq = in->freq; + gpadc->adc_trig[trig].auto_mux = + adc_tab[in->mux].mux; + gpadc->adc_trig[trig].auto_callb = in->auto_adc_callback; + + ret = ab5500_gpadc_program_auto(gpadc, trig); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to program auto ch\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig * 4), + TRIGX_ARM_MASK, TRIGX_ARM); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to trigger\n"); + goto out; + } + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[trig].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } +out: + mutex_unlock(&gpadc->ab5500_gpadc_lock); + return ret; + +} +EXPORT_SYMBOL(ab5500_gpadc_convert_auto); + +/* sysfs interface for GPADC0 */ +static ssize_t ab5500_gpadc0_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int voltage; + struct ab5500_gpadc *gpadc = dev_get_drvdata(dev); + + voltage = ab5500_gpadc_convert(gpadc, GPADC0_V); + + return sprintf(buf, "%d\n", voltage); +} +static DEVICE_ATTR(adc0volt, 0644, ab5500_gpadc0_get, NULL); + +static void ab5500_gpadc_trigx_work(struct ab5500_gpadc *gp, int trig) +{ + unsigned long flags; + if (gp->adc_trig[trig].auto_callb != NULL) { + gp->adc_trig[trig].auto_callb(gp->adc_trig[trig].auto_mux); + spin_lock_irqsave(&gp->gpadc_auto_lock, flags); + gp->adc_trig[trig].flag = false; + spin_unlock_irqrestore(&gp->gpadc_auto_lock, flags); + } else { + dev_err(gp->dev, "Unknown trig for %d\n", trig); + } +} +/** + * ab5500_gpadc_trig0_work() - work item for trig0 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 0 auto conversion. + */ +static void ab5500_gpadc_trig0_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig0_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG0); +} + +/** + * ab5500_gpadc_trig1_work() - work item for trig1 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig1 auto conversion. + */ +static void ab5500_gpadc_trig1_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig1_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG1); +} + +/** + * ab5500_gpadc_trig2_work() - work item for trig2 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 2 auto conversion. + */ +static void ab5500_gpadc_trig2_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig2_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG2); +} + +/** + * ab5500_gpadc_trig3_work() - work item for trig3 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 3 auto conversion. + */ +static void ab5500_gpadc_trig3_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig3_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG3); +} + +/** + * ab5500_gpadc_trig4_work() - work item for trig4 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 4 auto conversion. + */ +static void ab5500_gpadc_trig4_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig4_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG4); +} + +/** + * ab5500_gpadc_trig5_work() - work item for trig5 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 5 auto conversion. + */ +static void ab5500_gpadc_trig5_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig5_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG5); +} + +/** + * ab5500_gpadc_trig6_work() - work item for trig6 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 6 auto conversion. + */ +static void ab5500_gpadc_trig6_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig6_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG6); +} + +/** + * ab5500_gpadc_trig7_work() - work item for trig7 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 7 auto conversion. + */ +static void ab5500_gpadc_trig7_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig7_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG7); +} + +/** + * ab5500_gpadc_vbat_txon_work() - work item for vbat_txon trigger auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for vbat_txon trigger auto adc. + */ +static void ab5500_gpadc_vbat_txon_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig_vbat_txon_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_VBAT_TXON); +} + +/** + * ab5500_gpadc_vbat_txoff_work() - work item for vbat_txoff trigger auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for vbat_txoff trigger auto adc. + */ +static void ab5500_gpadc_vbat_txoff_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig_vbat_txoff_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_VBAT_TXOFF); +} + +/** + * ab5500_adc_trigx_handler() - isr for auto gpadc conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto gpadc conversion. + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_trigx_handler(int irq, void *_gpadc) +{ + struct ab5500_platform_data *plat; + struct ab5500_gpadc *gpadc = _gpadc; + int dev_irq; + + plat = dev_get_platdata(gpadc->dev->parent); + dev_irq = irq - plat->irq.base; + + switch (dev_irq) { + case AB5500_INT_ADC_TRIG0: + dev_dbg(gpadc->dev, "Trigger 0 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig0_work); + break; + case AB5500_INT_ADC_TRIG1: + dev_dbg(gpadc->dev, "Trigger 1 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig1_work); + break; + case AB5500_INT_ADC_TRIG2: + dev_dbg(gpadc->dev, "Trigger 2 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig2_work); + break; + case AB5500_INT_ADC_TRIG3: + dev_dbg(gpadc->dev, "Trigger 3 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig3_work); + break; + case AB5500_INT_ADC_TRIG4: + dev_dbg(gpadc->dev, "Trigger 4 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig4_work); + break; + case AB5500_INT_ADC_TRIG5: + dev_dbg(gpadc->dev, "Trigger 5 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig5_work); + break; + case AB5500_INT_ADC_TRIG6: + dev_dbg(gpadc->dev, "Trigger 6 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig6_work); + break; + case AB5500_INT_ADC_TRIG7: + dev_dbg(gpadc->dev, "Trigger 7 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig7_work); + break; + default: + dev_dbg(gpadc->dev, "unknown trigx handler input\n"); + break; + } + return IRQ_HANDLED; +} + +/** + * ab5500_adc_vbat_txon_handler() - isr for auto vbat_txon conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto vbat_txon conversion + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_vbat_txon_handler(int irq, void *_gpadc) +{ + struct ab5500_gpadc *gpadc = _gpadc; + + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig_vbat_txon_work); + return IRQ_HANDLED; +} + +/** + * ab5500_adc_vbat_txoff_handler() - isr for auto vbat_txoff conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto vbat_txoff conversion + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_vbat_txoff_handler(int irq, void *_gpadc) +{ + struct ab5500_gpadc *gpadc = _gpadc; + + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig_vbat_txoff_work); + return IRQ_HANDLED; +} + +/** + * ab5500_gpadc_configuration() - function for gpadc conversion + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This function configures the gpadc + */ +static int ab5500_gpadc_configuration(struct ab5500_gpadc *gpadc) +{ + int ret; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_CTRL2, + ADC_CAL_OFF_MASK | ADC_ON_MODE_MASK, + ADC_CAL_ON | ADC_FULLPWR); + return ret; +} + +/* ab5500 btemp driver interrupts and their respective isr */ +static struct ab5500_adc_interrupts ab5500_adc_irq[] = { + {"TRIGGER-0", ab5500_adc_trigx_handler}, + {"TRIGGER-1", ab5500_adc_trigx_handler}, + {"TRIGGER-2", ab5500_adc_trigx_handler}, + {"TRIGGER-3", ab5500_adc_trigx_handler}, + {"TRIGGER-4", ab5500_adc_trigx_handler}, + {"TRIGGER-5", ab5500_adc_trigx_handler}, + {"TRIGGER-6", ab5500_adc_trigx_handler}, + {"TRIGGER-7", ab5500_adc_trigx_handler}, + {"TRIGGER-VBAT-TXON", ab5500_adc_vbat_txon_handler}, + {"TRIGGER-VBAT", ab5500_adc_vbat_txoff_handler}, +}; + +static int __devinit ab5500_gpadc_probe(struct platform_device *pdev) +{ + int ret, irq, i, j; + struct ab5500_gpadc *gpadc; + + gpadc = kzalloc(sizeof(struct ab5500_gpadc), GFP_KERNEL); + if (!gpadc) { + dev_err(&pdev->dev, "Error: No memory\n"); + return -ENOMEM; + } + gpadc->dev = &pdev->dev; + mutex_init(&gpadc->ab5500_gpadc_lock); + spin_lock_init(&gpadc->gpadc_auto_lock); + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_adc_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab5500_adc_irq[i].isr, + IRQF_NO_SUSPEND, + ab5500_adc_irq[i].name, gpadc); + + if (ret) { + dev_err(gpadc->dev, "failed to request %s IRQ %d: %d\n" + , ab5500_adc_irq[i].name, irq, ret); + goto fail_irq; + } + dev_dbg(gpadc->dev, "Requested %s IRQ %d: %d\n", + ab5500_adc_irq[i].name, irq, ret); + } + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(gpadc->dev); + if (ret < 0) { + dev_err(gpadc->dev, "failed to get chip ID\n"); + goto fail_irq; + } + gpadc->chip_id = (u8) ret; + + /* Create a work queue for gpadc auto */ + gpadc->gpadc_wq = + create_singlethread_workqueue("ab5500_gpadc_wq"); + if (gpadc->gpadc_wq == NULL) { + dev_err(gpadc->dev, "failed to create work queue\n"); + goto fail_irq; + } + + INIT_WORK(&gpadc->gpadc_trig0_work, ab5500_gpadc_trig0_work); + INIT_WORK(&gpadc->gpadc_trig1_work, ab5500_gpadc_trig1_work); + INIT_WORK(&gpadc->gpadc_trig2_work, ab5500_gpadc_trig2_work); + INIT_WORK(&gpadc->gpadc_trig3_work, ab5500_gpadc_trig3_work); + INIT_WORK(&gpadc->gpadc_trig4_work, ab5500_gpadc_trig4_work); + INIT_WORK(&gpadc->gpadc_trig5_work, ab5500_gpadc_trig5_work); + INIT_WORK(&gpadc->gpadc_trig6_work, ab5500_gpadc_trig6_work); + INIT_WORK(&gpadc->gpadc_trig7_work, ab5500_gpadc_trig7_work); + INIT_WORK(&gpadc->gpadc_trig_vbat_txon_work, + ab5500_gpadc_vbat_txon_work); + INIT_WORK(&gpadc->gpadc_trig_vbat_txoff_work, + ab5500_gpadc_vbat_txoff_work); + + for (j = 0; j < N_AUTO_TRIGGER; j++) + gpadc->adc_trig[j].flag = false; + + ret = ab5500_gpadc_configuration(gpadc); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: configuration failed\n"); + goto free_wq; + } + + ret = device_create_file(gpadc->dev, &dev_attr_adc0volt); + if (ret < 0) { + dev_err(gpadc->dev, "File device creation failed: %d\n", ret); + ret = -ENODEV; + goto fail_sysfs; + } + list_add_tail(&gpadc->node, &ab5500_gpadc_list); + + platform_set_drvdata(pdev, gpadc); + + return 0; +fail_sysfs: +free_wq: + destroy_workqueue(gpadc->gpadc_wq); +fail_irq: + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + free_irq(irq, gpadc); + } + kfree(gpadc); + gpadc = NULL; + return ret; +} + +static int __devexit ab5500_gpadc_remove(struct platform_device *pdev) +{ + int i, irq; + struct ab5500_gpadc *gpadc = platform_get_drvdata(pdev); + + device_remove_file(gpadc->dev, &dev_attr_adc0volt); + + /* remove this gpadc entry from the list */ + list_del(&gpadc->node); + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_adc_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + free_irq(irq, gpadc); + } + /* Flush work */ + flush_workqueue(gpadc->gpadc_wq); + + /* Delete the work queue */ + destroy_workqueue(gpadc->gpadc_wq); + + kfree(gpadc); + gpadc = NULL; + return 0; +} + +static struct platform_driver ab5500_gpadc_driver = { + .probe = ab5500_gpadc_probe, + .remove = __devexit_p(ab5500_gpadc_remove), + .driver = { + .name = "ab5500-adc", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_gpadc_init(void) +{ + return platform_driver_register(&ab5500_gpadc_driver); +} + +static void __exit ab5500_gpadc_exit(void) +{ + platform_driver_unregister(&ab5500_gpadc_driver); +} + +subsys_initcall_sync(ab5500_gpadc_init); +module_exit(ab5500_gpadc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Vijaya Kumar K"); +MODULE_ALIAS("platform:ab5500_adc"); +MODULE_DESCRIPTION("AB5500 GPADC driver"); diff --git a/drivers/mfd/ab5500-power.c b/drivers/mfd/ab5500-power.c new file mode 100644 index 00000000000..9474c32809b --- /dev/null +++ b/drivers/mfd/ab5500-power.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/signal.h> +#include <linux/init.h> +#include <linux/platform_device.h> + +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> + +static struct device *dev; + +/* STARTUP */ +#define AB5500_SYSPOR_CONTROL 0x30 + +/* VINT IO I2C CLOCK */ +#define AB5500_RTC_VINT 0x01 + +int ab5500_clock_rtc_enable(int num, bool enable) +{ + /* RTC_CLK{0,1,2} are bits {4,3,2}, active low */ + u8 mask = BIT(4 - num); + u8 value = enable ? 0 : mask; + + /* Don't allow RTC_CLK0 to be controlled. */ + if (num < 1 || num > 2) + return -EINVAL; + + if (!dev) + return -EAGAIN; + + return abx500_mask_and_set(dev, AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + AB5500_RTC_VINT, mask, value); +} + +static void ab5500_power_off(void) +{ + sigset_t old; + sigset_t all; + + sigfillset(&all); + + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + /* Clear dbb_on */ + int ret = abx500_set(dev, AB5500_BANK_STARTUP, + AB5500_SYSPOR_CONTROL, 0); + WARN_ON(ret); + } +} + +static int __devinit ab5500_power_probe(struct platform_device *pdev) +{ + struct ab5500_platform_data *plat = dev_get_platdata(pdev->dev.parent); + + dev = &pdev->dev; + + if (plat->pm_power_off) + pm_power_off = ab5500_power_off; + + return 0; +} + +static int __devexit ab5500_power_remove(struct platform_device *pdev) +{ + struct ab5500_platform_data *plat = dev_get_platdata(pdev->dev.parent); + + if (plat->pm_power_off) + pm_power_off = NULL; + dev = NULL; + + return 0; +} + +static struct platform_driver ab5500_power_driver = { + .driver = { + .name = "ab5500-power", + .owner = THIS_MODULE, + }, + .probe = ab5500_power_probe, + .remove = __devexit_p(ab5500_power_remove), +}; + +static int __init ab8500_sysctrl_init(void) +{ + return platform_driver_register(&ab5500_power_driver); +} + +subsys_initcall(ab8500_sysctrl_init); + +MODULE_DESCRIPTION("AB5500 power driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index d295941c9a3..2537e7461f1 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -94,6 +94,9 @@ #define AB8500_TURN_ON_STATUS 0x00 +static bool no_bm; /* No battery management */ +module_param(no_bm, bool, S_IRUGO); + /* * Map interrupt numbers to the LATCH and MASK register offsets, Interrupt * numbers are indexed into this array with (num / 8). @@ -684,7 +687,7 @@ static struct resource __devinitdata ab8500_usb_resources[] = { static struct resource __devinitdata ab8500_temp_resources[] = { { - .name = "AB8500_TEMP_WARM", + .name = "ABX500_TEMP_WARM", .start = AB8500_INT_TEMP_WARM, .end = AB8500_INT_TEMP_WARM, .flags = IORESOURCE_IRQ, @@ -706,6 +709,9 @@ static struct mfd_cell __devinitdata ab8500_devs[] = { .name = "ab8500-regulator", }, { + .name = "ab8500-regulator-debug", + }, + { .name = "ab8500-gpio", .num_resources = ARRAY_SIZE(ab8500_gpio_resources), .resources = ab8500_gpio_resources, @@ -721,26 +727,6 @@ static struct mfd_cell __devinitdata ab8500_devs[] = { .resources = ab8500_rtc_resources, }, { - .name = "ab8500-charger", - .num_resources = ARRAY_SIZE(ab8500_charger_resources), - .resources = ab8500_charger_resources, - }, - { - .name = "ab8500-btemp", - .num_resources = ARRAY_SIZE(ab8500_btemp_resources), - .resources = ab8500_btemp_resources, - }, - { - .name = "ab8500-fg", - .num_resources = ARRAY_SIZE(ab8500_fg_resources), - .resources = ab8500_fg_resources, - }, - { - .name = "ab8500-chargalg", - .num_resources = ARRAY_SIZE(ab8500_chargalg_resources), - .resources = ab8500_chargalg_resources, - }, - { .name = "ab8500-acc-det", .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), .resources = ab8500_av_acc_detect_resources, @@ -775,12 +761,35 @@ static struct mfd_cell __devinitdata ab8500_devs[] = { .name = "ab8500-denc", }, { - .name = "ab8500-temp", + .name = "abx500-temp", .num_resources = ARRAY_SIZE(ab8500_temp_resources), .resources = ab8500_temp_resources, }, }; +static struct mfd_cell __devinitdata ab8500_bm_devs[] = { + { + .name = "ab8500-charger", + .num_resources = ARRAY_SIZE(ab8500_charger_resources), + .resources = ab8500_charger_resources, + }, + { + .name = "ab8500-btemp", + .num_resources = ARRAY_SIZE(ab8500_btemp_resources), + .resources = ab8500_btemp_resources, + }, + { + .name = "ab8500-fg", + .num_resources = ARRAY_SIZE(ab8500_fg_resources), + .resources = ab8500_fg_resources, + }, + { + .name = "ab8500-chargalg", + .num_resources = ARRAY_SIZE(ab8500_chargalg_resources), + .resources = ab8500_chargalg_resources, + }, +}; + static ssize_t show_chip_id(struct device *dev, struct device_attribute *attr, char *buf) { @@ -946,9 +955,19 @@ int __devinit ab8500_init(struct ab8500 *ab8500) ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs, ARRAY_SIZE(ab8500_devs), NULL, ab8500->irq_base); + if (ret) goto out_freeirq; + if (!no_bm) { + /* Add battery management devices */ + ret = mfd_add_devices(ab8500->dev, 0, ab8500_bm_devs, + ARRAY_SIZE(ab8500_bm_devs), NULL, + ab8500->irq_base); + if (ret) + dev_err(ab8500->dev, "error adding bm devices\n"); + } + ret = sysfs_create_group(&ab8500->dev->kobj, &ab8500_attr_group); if (ret) dev_err(ab8500->dev, "error creating sysfs entries\n"); diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 9a0211aa889..9521d738fd0 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -4,6 +4,72 @@ * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson. * License Terms: GNU General Public License v2 */ +/* + * AB8500 register access + * ====================== + * + * read: + * # echo BANK > <debugfs>/ab8500/register-bank + * # echo ADDR > <debugfs>/ab8500/register-address + * # cat <debugfs>/ab8500/register-value + * + * write: + * # echo BANK > <debugfs>/ab8500/register-bank + * # echo ADDR > <debugfs>/ab8500/register-address + * # echo VALUE > <debugfs>/ab8500/register-value + * + * read all registers from a bank: + * # echo BANK > <debugfs>/ab8500/register-bank + * # cat <debugfs>/ab8500/all-bank-register + * + * BANK target AB8500 register bank + * ADDR target AB8500 register address + * VALUE decimal or 0x-prefixed hexadecimal + * + * + * User Space notification on AB8500 IRQ + * ===================================== + * + * Allows user space entity to be notified when target AB8500 IRQ occurs. + * When subscribed, a sysfs entry is created in ab8500.i2c platform device. + * One can pool this file to get target IRQ occurence information. + * + * subscribe to an AB8500 IRQ: + * # echo IRQ > <debugfs>/ab8500/irq-subscribe + * + * unsubscribe from an AB8500 IRQ: + * # echo IRQ > <debugfs>/ab8500/irq-unsubscribe + * + * + * AB8500 register formated read/write access + * ========================================== + * + * Read: read data, data>>SHIFT, data&=MASK, output data + * [0xABCDEF98] shift=12 mask=0xFFF => 0x00000CDE + * Write: read data, data &= ~(MASK<<SHIFT), data |= (VALUE<<SHIFT), write data + * [0xABCDEF98] shift=12 mask=0xFFF value=0x123 => [0xAB123F98] + * + * Usage: + * # echo "CMD [OPTIONS] BANK ADRESS [VALUE]" > $debugfs/ab8500/hwreg + * + * CMD read read access + * write write access + * + * BANK target reg bank + * ADDRESS target reg address + * VALUE (write) value to be updated + * + * OPTIONS + * -d|-dec (read) output in decimal + * -h|-hexa (read) output in 0x-hexa (default) + * -l|-w|-b 32bit (default), 16bit or 8bit reg access + * -m|-mask MASK 0x-hexa mask (default 0xFFFFFFFF) + * -s|-shift SHIFT bit shift value (read:left, write:right) + * -o|-offset OFFSET address offset to add to ADDRESS value + * + * Warning: bit shift operation is applied to bit-mask. + * Warning: bit shift direction depends on read or right command. + */ #include <linux/seq_file.h> #include <linux/uaccess.h> @@ -11,13 +77,28 @@ #include <linux/module.h> #include <linux/debugfs.h> #include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/kobject.h> +#include <linux/slab.h> #include <linux/mfd/abx500.h> -#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> + +#ifdef CONFIG_DEBUG_FS +#include <linux/string.h> +#include <linux/ctype.h> +#endif static u32 debug_bank; static u32 debug_address; +static int irq_first; +static int irq_last; +static u32 irq_count[AB8500_NR_IRQS]; + +static struct device_attribute *dev_attr[AB8500_NR_IRQS]; +static char *event_name[AB8500_NR_IRQS]; + /** * struct ab8500_reg_range * @first: the first address of the range @@ -42,15 +123,35 @@ struct ab8500_i2c_ranges { const struct ab8500_reg_range *range; }; +/* hwreg- "mask" and "shift" entries ressources */ +struct hwreg_cfg { + u32 bank; /* target bank */ + u32 addr; /* target address */ + uint fmt; /* format */ + uint mask; /* read/write mask, applied before any bit shift */ + int shift; /* bit shift (read:right shift, write:left shift */ +}; +/* fmt bit #0: 0=hexa, 1=dec */ +#define REG_FMT_DEC(c) ((c)->fmt & 0x1) +#define REG_FMT_HEX(c) (!REG_FMT_DEC(c)) + +static struct hwreg_cfg hwreg_cfg = { + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ +}; + #define AB8500_NAME_STRING "ab8500" -#define AB8500_NUM_BANKS 22 +#define AB8500_ADC_NAME_STRING "gpadc" +#define AB8500_NUM_BANKS 24 #define AB8500_REV_REG 0x80 static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { [0x0] = { .num_ranges = 0, - .range = 0, + .range = NULL, }, [AB8500_SYS_CTRL1_BLOCK] = { .num_ranges = 3, @@ -215,7 +316,7 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }, [AB8500_CHARGER] = { - .num_ranges = 8, + .num_ranges = 9, .range = (struct ab8500_reg_range[]) { { .first = 0x00, @@ -249,6 +350,10 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { .first = 0xC0, .last = 0xC2, }, + { + .first = 0xf5, + .last = 0xf6, + }, }, }, [AB8500_GAS_GAUGE] = { @@ -268,6 +373,24 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }, }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x05, + .last = 0x07, + }, + }, + }, [AB8500_AUDIO] = { .num_ranges = 1, .range = (struct ab8500_reg_range[]) { @@ -354,6 +477,24 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }; +static irqreturn_t ab8500_debug_handler(int irq, void *data) +{ + char buf[16]; + struct kobject *kobj = (struct kobject *)data; + unsigned int irq_abb = irq - irq_first; + + if (irq_abb < AB8500_NR_IRQS) + irq_count[irq_abb]++; + /* + * This makes it possible to use poll for events (POLLPRI | POLLERR) + * from userspace on sysfs file named <irq-nr> + */ + sprintf(buf, "%d", irq); + sysfs_notify(kobj, NULL, buf); + + return IRQ_HANDLED; +} + static int ab8500_registers_print(struct seq_file *s, void *p) { struct device *dev = s->private; @@ -515,10 +656,732 @@ static ssize_t ab8500_val_write(struct file *file, printk(KERN_ERR "abx500_set_reg failed %d, %d", err, __LINE__); return -EINVAL; } + return count; +} + +/* + * - HWREG DB8500 formated routines + */ +static int ab8500_hwreg_print(struct seq_file *s, void *d) +{ + struct device *dev = s->private; + int ret; + u8 regvalue; + + ret = abx500_get_register_interruptible(dev, + (u8)hwreg_cfg.bank, (u8)hwreg_cfg.addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (hwreg_cfg.shift >= 0) + regvalue >>= hwreg_cfg.shift; + else + regvalue <<= -hwreg_cfg.shift; + regvalue &= hwreg_cfg.mask; + + if (REG_FMT_DEC(&hwreg_cfg)) + seq_printf(s, "%d\n", regvalue); + else + seq_printf(s, "0x%02X\n", regvalue); + return 0; +} + +static int ab8500_hwreg_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_hwreg_print, inode->i_private); +} + +static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) +{ + int bat_ctrl_raw; + int bat_ctrl_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL); + bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BAT_CTRL, bat_ctrl_raw); + + return seq_printf(s, "%d,0x%X\n", + bat_ctrl_convert, bat_ctrl_raw); +} + +static int ab8500_gpadc_bat_ctrl_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bat_ctrl_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bat_ctrl_fops = { + .open = ab8500_gpadc_bat_ctrl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_btemp_ball_print(struct seq_file *s, void *p) +{ + int btemp_ball_raw; + int btemp_ball_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL); + btemp_ball_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, + btemp_ball_raw); + + return seq_printf(s, + "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); +} + +static int ab8500_gpadc_btemp_ball_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_btemp_ball_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_btemp_ball_fops = { + .open = ab8500_gpadc_btemp_ball_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_v_print(struct seq_file *s, void *p) +{ + int main_charger_v_raw; + int main_charger_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V); + main_charger_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_V, main_charger_v_raw); + + return seq_printf(s, "%d,0x%X\n", + main_charger_v_convert, main_charger_v_raw); +} + +static int ab8500_gpadc_main_charger_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_v_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_v_fops = { + .open = ab8500_gpadc_main_charger_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect1_print(struct seq_file *s, void *p) +{ + int acc_detect1_raw; + int acc_detect1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1); + acc_detect1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ACC_DETECT1, + acc_detect1_raw); + + return seq_printf(s, "%d,0x%X\n", + acc_detect1_convert, acc_detect1_raw); +} + +static int ab8500_gpadc_acc_detect1_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect1_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect1_fops = { + .open = ab8500_gpadc_acc_detect1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect2_print(struct seq_file *s, void *p) +{ + int acc_detect2_raw; + int acc_detect2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2); + acc_detect2_convert = ab8500_gpadc_ad_to_voltage(gpadc, + ACC_DETECT2, acc_detect2_raw); + + return seq_printf(s, "%d,0x%X\n", + acc_detect2_convert, acc_detect2_raw); +} + +static int ab8500_gpadc_acc_detect2_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect2_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect2_fops = { + .open = ab8500_gpadc_acc_detect2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux1_print(struct seq_file *s, void *p) +{ + int aux1_raw; + int aux1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1); + aux1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX1, + aux1_raw); + + return seq_printf(s, "%d,0x%X\n", + aux1_convert, aux1_raw); +} + +static int ab8500_gpadc_aux1_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux1_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux1_fops = { + .open = ab8500_gpadc_aux1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux2_print(struct seq_file *s, void *p) +{ + int aux2_raw; + int aux2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2); + aux2_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX2, + aux2_raw); + + return seq_printf(s, "%d,0x%X\n", + aux2_convert, aux2_raw); +} + +static int ab8500_gpadc_aux2_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux2_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux2_fops = { + .open = ab8500_gpadc_aux2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_bat_v_print(struct seq_file *s, void *p) +{ + int main_bat_v_raw; + int main_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V); + main_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, + main_bat_v_raw); + + return seq_printf(s, "%d,0x%X\n", + main_bat_v_convert, main_bat_v_raw); +} + +static int ab8500_gpadc_main_bat_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_bat_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_bat_v_fops = { + .open = ab8500_gpadc_main_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_vbus_v_print(struct seq_file *s, void *p) +{ + int vbus_v_raw; + int vbus_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V); + vbus_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBUS_V, + vbus_v_raw); + + return seq_printf(s, "%d,0x%X\n", + vbus_v_convert, vbus_v_raw); +} + +static int ab8500_gpadc_vbus_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_vbus_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_vbus_v_fops = { + .open = ab8500_gpadc_vbus_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_c_print(struct seq_file *s, void *p) +{ + int main_charger_c_raw; + int main_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C); + main_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_C, main_charger_c_raw); + + return seq_printf(s, "%d,0x%X\n", + main_charger_c_convert, main_charger_c_raw); +} + +static int ab8500_gpadc_main_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_c_fops = { + .open = ab8500_gpadc_main_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_usb_charger_c_print(struct seq_file *s, void *p) +{ + int usb_charger_c_raw; + int usb_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C); + usb_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + USB_CHARGER_C, usb_charger_c_raw); + + return seq_printf(s, "%d,0x%X\n", + usb_charger_c_convert, usb_charger_c_raw); +} + +static int ab8500_gpadc_usb_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_usb_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_usb_charger_c_fops = { + .open = ab8500_gpadc_usb_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_bk_bat_v_print(struct seq_file *s, void *p) +{ + int bk_bat_v_raw; + int bk_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V); + bk_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BK_BAT_V, bk_bat_v_raw); + + return seq_printf(s, "%d,0x%X\n", + bk_bat_v_convert, bk_bat_v_raw); +} + +static int ab8500_gpadc_bk_bat_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bk_bat_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bk_bat_v_fops = { + .open = ab8500_gpadc_bk_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_die_temp_print(struct seq_file *s, void *p) +{ + int die_temp_raw; + int die_temp_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP); + die_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, DIE_TEMP, + die_temp_raw); + + return seq_printf(s, "%d,0x%X\n", + die_temp_convert, die_temp_raw); +} + +static int ab8500_gpadc_die_temp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_die_temp_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_die_temp_fops = { + .open = ab8500_gpadc_die_temp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +/* + * return length of an ASCII numerical value, 0 is string is not a + * numerical value. + * string shall start at value 1st char. + * string can be tailed with \0 or space or newline chars only. + * value can be decimal or hexadecimal (prefixed 0x or 0X). + */ +static int strval_len(char *b) +{ + char *s = b; + if ((*s == '0') && ((*(s+1) == 'x') || (*(s+1) == 'X'))) { + s += 2; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isxdigit(*s)) + return 0; + } + } else { + if (*s == '-') + s++; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isdigit(*s)) + return 0; + } + } + return (int) (s-b); +} + +/* + * parse hwreg input data. + * update global hwreg_cfg only if input data syntax is ok. + */ +static ssize_t hwreg_common_write(char *b, struct hwreg_cfg *cfg, + struct device *dev) +{ + uint write, val = 0; + struct hwreg_cfg loc = { + .bank = 0, /* default: invalid phys addr */ + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ + }; + + /* read or write ? */ + if (!strncmp(b, "read ", 5)) { + write = 0; + b += 5; + } else if (!strncmp(b, "write ", 6)) { + write = 1; + b += 6; + } else + return -EINVAL; + + /* OPTIONS -l|-w|-b -s -m -o */ + while ((*b == ' ') || (*b == '-')) { + if (*(b-1) != ' ') { + b++; + continue; + } + if ((!strncmp(b, "-d ", 3)) || + (!strncmp(b, "-dec ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt |= (1<<0); + } else if ((!strncmp(b, "-h ", 3)) || + (!strncmp(b, "-hex ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt &= ~(1<<0); + } else if ((!strncmp(b, "-m ", 3)) || + (!strncmp(b, "-mask ", 6))) { + b += (*(b+2) == ' ') ? 3 : 6; + if (strval_len(b) == 0) + return -EINVAL; + loc.mask = simple_strtoul(b, &b, 0); + } else if ((!strncmp(b, "-s ", 3)) || + (!strncmp(b, "-shift ", 7))) { + b += (*(b+2) == ' ') ? 3 : 7; + if (strval_len(b) == 0) + return -EINVAL; + loc.shift = simple_strtol(b, &b, 0); + } else { + return -EINVAL; + } + } + /* get arg BANK and ADDRESS */ + if (strval_len(b) == 0) + return -EINVAL; + loc.bank = simple_strtoul(b, &b, 0); + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + loc.addr = simple_strtoul(b, &b, 0); + + if (write) { + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + val = simple_strtoul(b, &b, 0); + } + + /* args are ok, update target cfg (mainly for read) */ + *cfg = loc; + +#ifdef ABB_HWREG_DEBUG + pr_warn("HWREG request: %s, %s, addr=0x%08X, mask=0x%X, shift=%d" + "value=0x%X\n", (write) ? "write" : "read", + REG_FMT_DEC(cfg) ? "decimal" : "hexa", + cfg->addr, cfg->mask, cfg->shift, val); +#endif + + if (write) { + u8 regvalue; + int ret = abx500_get_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (cfg->shift >= 0) { + regvalue &= ~(cfg->mask << (cfg->shift)); + val = (val & cfg->mask) << (cfg->shift); + } else { + regvalue &= ~(cfg->mask >> (-cfg->shift)); + val = (val & cfg->mask) >> (-cfg->shift); + } + val = val | regvalue; + + ret = abx500_set_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, (u8)val); + if (ret < 0) { + pr_err("abx500_set_reg failed %d, %d", ret, __LINE__); + return -EINVAL; + } + + } + return 0; +} + +static ssize_t ab8500_hwreg_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[128]; + int buf_size, ret; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* get args and process */ + ret = hwreg_common_write(buf, &hwreg_cfg, dev); + return (ret) ? ret : buf_size; +} + +/* + * - irq subscribe/unsubscribe stuff + */ +static int ab8500_subscribe_unsubscribe_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", irq_first); + + return 0; +} + +static int ab8500_subscribe_unsubscribe_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_subscribe_unsubscribe_print, + inode->i_private); +} + +/* + * Userspace should use poll() on this file. When an event occur + * the blocking poll will be released. + */ +static ssize_t show_irq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long name; + unsigned int irq_index; + int err; + + err = strict_strtoul(attr->attr.name, 0, &name); + if (err) + return err; + + irq_index = name - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + else + return sprintf(buf, "%u\n", irq_count[irq_index]); +} + +static ssize_t ab8500_subscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + unsigned int irq_index; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + + /* + * This will create a sysfs file named <irq-nr> which userspace can + * use to select or poll and get the AB8500 events + */ + dev_attr[irq_index] = kmalloc(sizeof(struct device_attribute), + GFP_KERNEL); + event_name[irq_index] = kmalloc(buf_size, GFP_KERNEL); + sprintf(event_name[irq_index], "%lu", user_val); + dev_attr[irq_index]->show = show_irq; + dev_attr[irq_index]->store = NULL; + dev_attr[irq_index]->attr.name = event_name[irq_index]; + dev_attr[irq_index]->attr.mode = S_IRUGO; + err = sysfs_create_file(&dev->kobj, &dev_attr[irq_index]->attr); + if (err < 0) { + printk(KERN_ERR "sysfs_create_file failed %d\n", err); + return err; + } + + err = request_threaded_irq(user_val, NULL, ab8500_debug_handler, + IRQF_SHARED | IRQF_NO_SUSPEND, "ab8500-debug", &dev->kobj); + if (err < 0) { + printk(KERN_ERR "request_threaded_irq failed %d, %lu\n", + err, user_val); + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + return err; + } + + return buf_size; +} + +static ssize_t ab8500_unsubscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + unsigned int irq_index; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + + /* Set irq count to 0 when unsubscribe */ + irq_count[irq_index] = 0; + + if (dev_attr[irq_index]) + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + + + free_irq(user_val, &dev->kobj); + kfree(event_name[irq_index]); + kfree(dev_attr[irq_index]); return count; } +/* + * - several deubgfs nodes fops + */ + static const struct file_operations ab8500_bank_fops = { .open = ab8500_bank_open, .write = ab8500_bank_write, @@ -546,65 +1409,177 @@ static const struct file_operations ab8500_val_fops = { .owner = THIS_MODULE, }; +static const struct file_operations ab8500_subscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_subscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_unsubscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_unsubscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_hwreg_fops = { + .open = ab8500_hwreg_open, + .write = ab8500_hwreg_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + static struct dentry *ab8500_dir; -static struct dentry *ab8500_reg_file; -static struct dentry *ab8500_bank_file; -static struct dentry *ab8500_address_file; -static struct dentry *ab8500_val_file; +static struct dentry *ab8500_gpadc_dir; static int __devinit ab8500_debug_probe(struct platform_device *plf) { + struct dentry *file; debug_bank = AB8500_MISC; debug_address = AB8500_REV_REG & 0x00FF; + irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); + if (irq_first < 0) { + dev_err(&plf->dev, "First irq not found, err %d\n", + irq_first); + return irq_first; + } + + irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); + if (irq_last < 0) { + dev_err(&plf->dev, "Last irq not found, err %d\n", + irq_last); + return irq_last; + } + ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); if (!ab8500_dir) - goto exit_no_debugfs; + goto err; + + ab8500_gpadc_dir = debugfs_create_dir(AB8500_ADC_NAME_STRING, + ab8500_dir); + if (!ab8500_gpadc_dir) + goto err; + + file = debugfs_create_file("all-bank-registers", S_IRUGO, + ab8500_dir, &plf->dev, &ab8500_registers_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_bank_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-address", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_address_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-value", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_val_fops); + if (!file) + goto err; + + file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_subscribe_fops); + if (!file) + goto err; - ab8500_reg_file = debugfs_create_file("all-bank-registers", - S_IRUGO, ab8500_dir, &plf->dev, &ab8500_registers_fops); - if (!ab8500_reg_file) - goto exit_destroy_dir; + file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); + if (!file) + goto err; - ab8500_bank_file = debugfs_create_file("register-bank", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_bank_fops); - if (!ab8500_bank_file) - goto exit_destroy_reg; + file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_hwreg_fops); + if (!file) + goto err; - ab8500_address_file = debugfs_create_file("register-address", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, - &ab8500_address_fops); - if (!ab8500_address_file) - goto exit_destroy_bank; + file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bat_ctrl_fops); + if (!file) + goto err; - ab8500_val_file = debugfs_create_file("register-value", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_val_fops); - if (!ab8500_val_file) - goto exit_destroy_address; + file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_btemp_ball_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect1", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect1_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect2", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect2_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux1_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux2_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_vbus_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_c", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_charger_c", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bk_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_die_temp_fops); + if (!file) + goto err; return 0; -exit_destroy_address: - debugfs_remove(ab8500_address_file); -exit_destroy_bank: - debugfs_remove(ab8500_bank_file); -exit_destroy_reg: - debugfs_remove(ab8500_reg_file); -exit_destroy_dir: - debugfs_remove(ab8500_dir); -exit_no_debugfs: +err: + if (ab8500_dir) + debugfs_remove_recursive(ab8500_dir); dev_err(&plf->dev, "failed to create debugfs entries.\n"); return -ENOMEM; } static int __devexit ab8500_debug_remove(struct platform_device *plf) { - debugfs_remove(ab8500_val_file); - debugfs_remove(ab8500_address_file); - debugfs_remove(ab8500_bank_file); - debugfs_remove(ab8500_reg_file); - debugfs_remove(ab8500_dir); - + debugfs_remove_recursive(ab8500_dir); return 0; } diff --git a/drivers/mfd/ab8500-denc.c b/drivers/mfd/ab8500-denc.c new file mode 100644 index 00000000000..17efee62110 --- /dev/null +++ b/drivers/mfd/ab8500-denc.c @@ -0,0 +1,539 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * ST-Ericsson AB8500 DENC base driver + * + * Author: Marcel Tunnissen <marcel.tuennissen@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <linux/uaccess.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/ab8500/denc-regs.h> +#include <linux/mfd/ab8500/denc.h> + +#define AB8500_NAME "ab8500" +#define AB8500_DENC_NAME "ab8500_denc" + +struct device_usage { + struct list_head list; + struct platform_device *pdev; + bool taken; +}; +static LIST_HEAD(device_list); + +/* To get rid of the extra bank parameter: */ +#define AB8500_REG_BANK_NR(__reg) ((0xff00 & (__reg)) >> 8) +static inline u8 ab8500_rreg(struct device *dev, u32 reg) +{ + u8 val; + if (abx500_get_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, &val) < 0) + return 0; + else + return val; +} + +static inline int ab8500_wreg(struct device *dev, u32 reg, u8 val) +{ + return abx500_set_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, val); +} + +/* Only use in the macro below: */ +static inline int _ab8500_wreg_fld(struct device *dev, u32 reg, u8 val, + u8 mask, u8 shift) +{ + int ret; + u8 org_val; + + ret = abx500_get_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, &org_val); + if (ret < 0) + return ret; + else + ab8500_wreg(dev, reg, + (org_val & ~mask) | ((val << shift) & mask)); + return 0; +} + +#define ab8500_wr_fld(__d, __reg, __fld, __val) \ + _ab8500_wreg_fld(__d, __reg, __val, __reg##_##__fld##_MASK, \ + __reg##_##__fld##_SHIFT) + +#define ab8500_set_fld(__cur_val, __reg, __fld, __val) \ + (((__cur_val) & ~__reg##_##__fld##_MASK) | \ + (((__val) << __reg##_##__fld##_SHIFT) & __reg##_##__fld##_MASK)) + +#define AB8500_DENC_TRACE(__pd) dev_dbg(&(__pd)->dev, "%s\n", __func__) + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_ab8500_denc_dir; +static struct dentry *debugfs_ab8500_dump_regs_file; +static void ab8500_denc_conf_ddr(struct platform_device *pdev); +static int debugfs_ab8500_open_file(struct inode *inode, struct file *file); +static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, + size_t count, loff_t *f_pos); + +static const struct file_operations debugfs_ab8500_dump_regs_fops = { + .owner = THIS_MODULE, + .open = debugfs_ab8500_open_file, + .read = debugfs_ab8500_dump_regs, +}; +#endif /* CONFIG_DEBUG_FS */ + +static int __devinit ab8500_denc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_platform_data *ab8500_pdata = + dev_get_platdata(pdev->dev.parent); + struct ab8500_denc_platform_data *pdata; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + + if (ab8500_pdata == NULL) { + dev_err(&pdev->dev, "AB8500 platform data missing\n"); + return -EINVAL; + } + + pdata = ab8500_pdata->denc; + if (pdata == NULL) { + dev_err(&pdev->dev, "Denc platform data missing\n"); + return -EINVAL; + } + + device_data = kzalloc(sizeof(struct device_usage), GFP_KERNEL); + if (!device_data) { + dev_err(&pdev->dev, "Failed to allocate device data\n"); + return -ENOMEM; + } + device_data->pdev = pdev; + list_add_tail(&device_data->list, &device_list); + +#ifdef CONFIG_DEBUG_FS + debugfs_ab8500_denc_dir = debugfs_create_dir(pdev->name, NULL); + debugfs_ab8500_dump_regs_file = debugfs_create_file( + "dumpregs", S_IRUGO, + debugfs_ab8500_denc_dir, &pdev->dev, + &debugfs_ab8500_dump_regs_fops + ); +#endif /* CONFIG_DEBUG_FS */ + return ret; +} + +static int __devexit ab8500_denc_remove(struct platform_device *pdev) +{ + struct list_head *element; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_ab8500_dump_regs_file); + debugfs_remove(debugfs_ab8500_denc_dir); +#endif /* CONFIG_DEBUG_FS */ + + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (device_data->pdev == pdev) { + list_del(element); + kzfree(device_data); + } + } + + return 0; +} + +static struct platform_driver ab8500_denc_driver = { + .probe = ab8500_denc_probe, + .remove = ab8500_denc_remove, + .driver = { + .name = "ab8500-denc", + }, +}; + +static void setup_27mhz(struct platform_device *pdev, bool enable) +{ + u8 data = ab8500_rreg(&pdev->dev, AB8500_SYS_ULP_CLK_CONF); + + AB8500_DENC_TRACE(pdev); + /* TODO: check if this field needs to be set */ + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_PD_ENA, + true); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_BUF_ENA, + enable); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_INV, + false); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_DE_IN, + false); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_STRE, + 1); + ab8500_wreg(&pdev->dev, AB8500_SYS_ULP_CLK_CONF, data); + + data = ab8500_rreg(&pdev->dev, AB8500_SYS_CLK_CTRL); + data = ab8500_set_fld(data, AB8500_SYS_CLK_CTRL, TVOUT_CLK_VALID, + enable); + data = ab8500_set_fld(data, AB8500_SYS_CLK_CTRL, TVOUT_PLL_ENA, + enable); + ab8500_wreg(&pdev->dev, AB8500_SYS_CLK_CTRL, data); +} + +static u32 map_tv_std(enum ab8500_denc_TV_std std) +{ + switch (std) { + case TV_STD_PAL_BDGHI: + return AB8500_DENC_CONF0_STD_PAL_BDGHI; + case TV_STD_PAL_N: + return AB8500_DENC_CONF0_STD_PAL_N; + case TV_STD_PAL_M: + return AB8500_DENC_CONF0_STD_PAL_M; + case TV_STD_NTSC_M: + return AB8500_DENC_CONF0_STD_NTSC_M; + default: + return 0; + } +} + +static u32 map_cr_filter(enum ab8500_denc_cr_filter_bandwidth bw) +{ + switch (bw) { + case TV_CR_NTSC_LOW_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_1MHZ; + case TV_CR_PAL_LOW_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_3MHZ; + case TV_CR_NTSC_HIGH_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_6MHZ; + case TV_CR_PAL_HIGH_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_9MHZ; + default: + return 0; + } +} + +static u32 map_phase_rst_mode(enum ab8500_denc_phase_reset_mode mode) +{ + switch (mode) { + case TV_PHASE_RST_MOD_DISABLE: + return AB8500_DENC_CONF8_PH_RST_MODE_DISABLED; + case TV_PHASE_RST_MOD_FROM_PHASE_BUF: + return AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_PHASE_BUF; + case TV_PHASE_RST_MOD_FROM_INC_DFS: + return AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_INC_DFS; + case TV_PHASE_RST_MOD_RST: + return AB8500_DENC_CONF8_PH_RST_MODE_RESET; + default: + return 0; + } +} + +static u32 map_plug_time(enum ab8500_denc_plug_time time) +{ + switch (time) { + case TV_PLUG_TIME_0_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_0_5S; + case TV_PLUG_TIME_1S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_1S; + case TV_PLUG_TIME_1_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_1_5S; + case TV_PLUG_TIME_2S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_2S; + case TV_PLUG_TIME_2_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_2_5S; + case TV_PLUG_TIME_3S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_3S; + default: + return 0; + } +} + +struct platform_device *ab8500_denc_get_device(void) +{ + struct list_head *element; + struct device_usage *device_data; + + pr_debug("%s\n", __func__); + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (!device_data->taken) { + device_data->taken = true; + return device_data->pdev; + } + } + return NULL; +} +EXPORT_SYMBOL(ab8500_denc_get_device); + +void ab8500_denc_put_device(struct platform_device *pdev) +{ + struct list_head *element; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (device_data->pdev == pdev) + device_data->taken = false; + } +} +EXPORT_SYMBOL(ab8500_denc_put_device); + +void ab8500_denc_reset(struct platform_device *pdev, bool hard) +{ + AB8500_DENC_TRACE(pdev); + if (hard) { + u8 data = ab8500_rreg(&pdev->dev, AB8500_CTRL3); + /* reset start */ + ab8500_wreg(&pdev->dev, AB8500_CTRL3, + ab8500_set_fld(data, AB8500_CTRL3, RESET_DENC_N, 0) + ); + /* reset done */ + ab8500_wreg(&pdev->dev, AB8500_CTRL3, + ab8500_set_fld(data, AB8500_CTRL3, RESET_DENC_N, 1) + ); + } else { + ab8500_wr_fld(&pdev->dev, AB8500_DENC_CONF6, SOFT_RESET, 1); + mdelay(10); + } +} +EXPORT_SYMBOL(ab8500_denc_reset); + +void ab8500_denc_power_up(struct platform_device *pdev) +{ + setup_27mhz(pdev, true); +} +EXPORT_SYMBOL(ab8500_denc_power_up); + +void ab8500_denc_power_down(struct platform_device *pdev) +{ + setup_27mhz(pdev, false); +} +EXPORT_SYMBOL(ab8500_denc_power_down); + +void ab8500_denc_conf(struct platform_device *pdev, + struct ab8500_denc_conf *conf) +{ + u8 data; + + AB8500_DENC_TRACE(pdev); + + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF0, + AB8500_VAL2REG(AB8500_DENC_CONF0, STD, map_tv_std(conf->TV_std)) + | + AB8500_VAL2REG(AB8500_DENC_CONF0, SYNC, + conf->test_pattern ? AB8500_DENC_CONF0_SYNC_AUTO_TEST : + AB8500_DENC_CONF0_SYNC_F_BASED_SLAVE + ) + ); + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF1, + AB8500_VAL2REG(AB8500_DENC_CONF1, BLK_LI, + !conf->partial_blanking) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, FLT, + map_cr_filter(conf->cr_filter)) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, CO_KI, conf->suppress_col) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, SETUP_MAIN, + conf->black_level_setup) + /* TODO: handle cc field: set to 0 now */ + ); + + data = ab8500_rreg(&pdev->dev, AB8500_DENC_CONF2); + data = ab8500_set_fld(data, AB8500_DENC_CONF2, N_INTRL, + conf->progressive); + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF2, data); + + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF8, + AB8500_VAL2REG(AB8500_DENC_CONF8, PH_RST_MODE, + map_phase_rst_mode(conf->phase_reset_mode)) + | + AB8500_VAL2REG(AB8500_DENC_CONF8, VAL_422_MUX, + conf->act_output) + | + AB8500_VAL2REG(AB8500_DENC_CONF8, BLK_ALL, + conf->blank_all) + ); + data = ab8500_rreg(&pdev->dev, AB8500_TVOUT_CTRL); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, DAC_CTRL0, + conf->dac_enable); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, DAC_CTRL1, + conf->act_dc_output); + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL, data); + + /* no support for DDR in early versions */ + if (AB8500_REG2VAL(AB8500_REV, FULL_MASK, + ab8500_rreg(&pdev->dev, AB8500_REV)) > 0) + ab8500_denc_conf_ddr(pdev); +} +EXPORT_SYMBOL(ab8500_denc_conf); + +void ab8500_denc_conf_plug_detect(struct platform_device *pdev, + bool enable, bool load_RC, + enum ab8500_denc_plug_time time) +{ + u8 data; + + AB8500_DENC_TRACE(pdev); + data = ab8500_rreg(&pdev->dev, AB8500_TVOUT_CTRL); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, TV_PLUG_ON, enable); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, TV_LOAD_RC, load_RC); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, PLUG_TV_TIME, + map_plug_time(time)); + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL, data); +} +EXPORT_SYMBOL(ab8500_denc_conf_plug_detect); + +void ab8500_denc_mask_int_plug_det(struct platform_device *pdev, bool plug, + bool unplug) +{ + u8 data = ab8500_rreg(&pdev->dev, AB8500_IT_MASK1); + + AB8500_DENC_TRACE(pdev); + data = ab8500_set_fld(data, AB8500_IT_MASK1, PLUG_TV_DET, plug); + data = ab8500_set_fld(data, AB8500_IT_MASK1, UNPLUG_TV_DET, unplug); + ab8500_wreg(&pdev->dev, AB8500_IT_MASK1, data); +} +EXPORT_SYMBOL(ab8500_denc_mask_int_plug_det); + +static void ab8500_denc_conf_ddr(struct platform_device *pdev) +{ + struct ab8500_platform_data *core_pdata; + struct ab8500_denc_platform_data *denc_pdata; + + AB8500_DENC_TRACE(pdev); + core_pdata = dev_get_platdata(pdev->dev.parent); + denc_pdata = core_pdata->denc; + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL2, + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, + DENC_DDR, denc_pdata->ddr_enable) | + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, SWAP_DDR_DATA_IN, + denc_pdata->ddr_little_endian)); +} + +#ifdef CONFIG_DEBUG_FS +static int debugfs_ab8500_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +#define DEBUG_BUF_SIZE 900 + +#define AB8500_GPIO_DIR5 0x1014 +#define AB8500_GPIO_DIR5_35_SHIFT 2 +#define AB8500_GPIO_DIR5_35_MASK (1 << AB8500_GPIO_DIR5_35_SHIFT) +#define AB8500_GPIO_OUT5 0x1024 +#define AB8500_GPIO_OUT5_35_SHIFT 2 +#define AB8500_GPIO_OUT5_35_MASK (1 << AB8500_GPIO_OUT5_35_SHIFT) +#define AB8500_GPIO_OUT5_35_VIDEO 0 +#define AB8500_GPIO_OUT5_35_AUDIO 1 +#define AB8500_GPIO_NPUD5 0x1034 +#define AB8500_GPIO_NPUD5_35_SHIFT 2 +#define AB8500_GPIO_NPUD5_35_MASK (1 << AB8500_GPIO_NPUD5_35_SHIFT) +#define AB8500_GPIO_NPUD5_35_ACTIVE 0 +#define AB8500_GPIO_NPUD5_35_INACTIVE 1 + +static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, + size_t count, loff_t *f_pos) +{ + int ret = 0; + size_t data_size = 0; + char buffer[DEBUG_BUF_SIZE]; + struct device *dev = file->private_data; + + data_size += sprintf(buffer + data_size, + "AB8500 DENC registers:\n" + "------Regulators etc ----------\n" + "CTRL3 : 0x%04x = 0x%02x\n" + "SYSULPCLK_CONF: 0x%04x = 0x%02x\n" + "SYSCLK_CTRL : 0x%04x = 0x%02x\n" + "REGU_MISC1 : 0x%04x = 0x%02x\n" + "VAUX12_REGU : 0x%04x = 0x%02x\n" + "VAUX1_SEL1 : 0x%04x = 0x%02x\n" + "------TVout only --------------\n" + "DENC_CONF0 : 0x%04x = 0x%02x\n" + "DENC_CONF1 : 0x%04x = 0x%02x\n" + "DENC_CONF2 : 0x%04x = 0x%02x\n" + "DENC_CONF6 : 0x%04x = 0x%02x\n" + "DENC_CONF8 : 0x%04x = 0x%02x\n" + "TVOUT_CTRL : 0x%04x = 0x%02x\n" + "TVOUT_CTRL2 : 0x%04x = 0x%02x\n" + "IT_MASK1 : 0x%04x = 0x%02x\n" + "------AV connector-------------\n" + "GPIO_DIR5 : 0x%04x = 0x%02x\n" + "GPIO_OUT5 : 0x%04x = 0x%02x\n" + "GPIO_NPUD5 : 0x%04x = 0x%02x\n" + , + AB8500_CTRL3, ab8500_rreg(dev, AB8500_CTRL3), + AB8500_SYS_ULP_CLK_CONF, ab8500_rreg(dev, + AB8500_SYS_ULP_CLK_CONF), + AB8500_SYS_CLK_CTRL, ab8500_rreg(dev, AB8500_SYS_CLK_CTRL), + AB8500_REGU_MISC1, ab8500_rreg(dev, AB8500_REGU_MISC1), + AB8500_VAUX12_REGU, ab8500_rreg(dev, AB8500_VAUX12_REGU), + AB8500_VAUX1_SEL, ab8500_rreg(dev, AB8500_VAUX1_SEL), + AB8500_DENC_CONF0, ab8500_rreg(dev, AB8500_DENC_CONF0), + AB8500_DENC_CONF1, ab8500_rreg(dev, AB8500_DENC_CONF1), + AB8500_DENC_CONF2, ab8500_rreg(dev, AB8500_DENC_CONF2), + AB8500_DENC_CONF6, ab8500_rreg(dev, AB8500_DENC_CONF6), + AB8500_DENC_CONF8, ab8500_rreg(dev, AB8500_DENC_CONF8), + AB8500_TVOUT_CTRL, ab8500_rreg(dev, AB8500_TVOUT_CTRL), + AB8500_TVOUT_CTRL2, ab8500_rreg(dev, AB8500_TVOUT_CTRL2), + AB8500_IT_MASK1, ab8500_rreg(dev, AB8500_IT_MASK1), + AB8500_GPIO_DIR5, ab8500_rreg(dev, AB8500_GPIO_DIR5), + AB8500_GPIO_OUT5, ab8500_rreg(dev, AB8500_GPIO_OUT5), + AB8500_GPIO_NPUD5, ab8500_rreg(dev, AB8500_GPIO_NPUD5) + ); + if (data_size >= DEBUG_BUF_SIZE) { + printk(KERN_EMERG "AB8500 DENC: Buffer overrun\n"); + ret = -EINVAL; + goto out; + } + + /* check if read done */ + if (*f_pos > data_size) + goto out; + + if (*f_pos + count > data_size) + count = data_size - *f_pos; + + if (copy_to_user(buf, buffer + *f_pos, count)) + ret = -EINVAL; + *f_pos += count; + ret = count; +out: + return ret; +} +#endif /* CONFIG_DEBUG_FS */ + +/* Module init */ +static int __init ab8500_denc_init(void) +{ + return platform_driver_register(&ab8500_denc_driver); +} +module_init(ab8500_denc_init); + +static void __exit ab8500_denc_exit(void) +{ + platform_driver_unregister(&ab8500_denc_driver); +} +module_exit(ab8500_denc_exit); + +MODULE_AUTHOR("Marcel Tunnissen <marcel.tuennissen@stericsson.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ST-Ericsson AB8500 DENC driver"); diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index c39fc716e1d..515e360fb09 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -130,16 +130,12 @@ static LIST_HEAD(ab8500_gpadc_list); * ab8500_gpadc_get() - returns a reference to the primary AB8500 GPADC * (i.e. the first GPADC in the instance list) */ -struct ab8500_gpadc *ab8500_gpadc_get(char *name) +struct ab8500_gpadc *ab8500_gpadc_get(void) { struct ab8500_gpadc *gpadc; + gpadc = list_first_entry(&ab8500_gpadc_list, struct ab8500_gpadc, node); - list_for_each_entry(gpadc, &ab8500_gpadc_list, node) { - if (!strcmp(name, dev_name(gpadc->dev))) - return gpadc; - } - - return ERR_PTR(-ENOENT); + return gpadc; } EXPORT_SYMBOL(ab8500_gpadc_get); @@ -344,7 +340,7 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) * Delay might be needed for ABB8500 cut 3.0, if not, remove * when hardware will be availible */ - msleep(1); + mdelay(1); break; } /* Intentional fallthrough */ diff --git a/drivers/mfd/ab8500-i2c.c b/drivers/mfd/ab8500-i2c.c index 087fecd71ce..e05836723ac 100644 --- a/drivers/mfd/ab8500-i2c.c +++ b/drivers/mfd/ab8500-i2c.c @@ -13,6 +13,7 @@ #include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/db8500-prcmu.h> + static int ab8500_i2c_write(struct ab8500 *ab8500, u16 addr, u8 data) { int ret; diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index c28d4eb1eff..d5865d41514 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -7,12 +7,114 @@ #include <linux/err.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/reboot.h> +#include <linux/signal.h> +#include <linux/power_supply.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500/ab8500-sysctrl.h> +#include <linux/time.h> +#include <linux/hwmon.h> static struct device *sysctrl_dev; +void ab8500_power_off(void) +{ + struct ab8500_platform_data *plat; + struct timespec ts; + sigset_t old; + sigset_t all; + static char *pss[] = {"ab8500_ac", "ab8500_usb"}; + int i; + bool charger_present = false; + union power_supply_propval val; + struct power_supply *psy; + int ret; + + /* + * If we have a charger connected and we're powering off, + * reboot into charge-only mode. + */ + + for (i = 0; i < ARRAY_SIZE(pss); i++) { + psy = power_supply_get_by_name(pss[i]); + if (!psy) + continue; + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &val); + + if (!ret && val.intval) { + charger_present = true; + break; + } + } + + if (!charger_present) + goto shutdown; + + /* Check if battery is known */ + psy = power_supply_get_by_name("ab8500_btemp"); + if (psy) { + ret = psy->get_property(psy, POWER_SUPPLY_PROP_TECHNOLOGY, + &val); + if (!ret && val.intval != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) { + printk(KERN_INFO + "Charger \"%s\" is connected with known battery." + " Rebooting.\n", + pss[i]); + machine_restart("charging"); + } + } + +shutdown: + sigfillset(&all); + + plat = dev_get_platdata(sysctrl_dev->parent); + getnstimeofday(&ts); + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + if (ts.tv_sec == 0 || + (ts.tv_sec - plat->thermal_set_time_sec > + plat->thermal_time_out)) + plat->thermal_power_off_pending = false; + if (!plat->thermal_power_off_pending) { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } else { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_THDB8500SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } + } +} + +static int ab8500_notifier_call(struct notifier_block *this, + unsigned long val, void *data) +{ + struct ab8500_platform_data *plat; + static struct timespec ts; + if (sysctrl_dev == NULL) + return -EAGAIN; + + plat = dev_get_platdata(sysctrl_dev->parent); + if (val) { + getnstimeofday(&ts); + plat->thermal_set_time_sec = ts.tv_sec; + plat->thermal_power_off_pending = true; + } else { + plat->thermal_set_time_sec = 0; + plat->thermal_power_off_pending = false; + } + return 0; +} + +static struct notifier_block ab8500_notifier = { + .notifier_call = ab8500_notifier_call, +}; + static inline bool valid_bank(u8 bank) { return ((bank == AB8500_SYS_CTRL1_BLOCK) || @@ -33,6 +135,7 @@ int ab8500_sysctrl_read(u16 reg, u8 *value) return abx500_get_register_interruptible(sysctrl_dev, bank, (u8)(reg & 0xFF), value); } +EXPORT_SYMBOL(ab8500_sysctrl_read); int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) { @@ -48,10 +151,42 @@ int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) return abx500_mask_and_set_register_interruptible(sysctrl_dev, bank, (u8)(reg & 0xFF), mask, value); } +EXPORT_SYMBOL(ab8500_sysctrl_write); static int __devinit ab8500_sysctrl_probe(struct platform_device *pdev) { + struct ab8500_platform_data *plat; + struct ab8500_sysctrl_platform_data *pdata; + sysctrl_dev = &pdev->dev; + plat = dev_get_platdata(pdev->dev.parent); + if (plat->pm_power_off) + pm_power_off = ab8500_power_off; + hwmon_notifier_register(&ab8500_notifier); + + pdata = plat->sysctrl; + + if (pdata) { + int ret; + int i; + int j; + for (i = AB8500_SYSCLKREQ1RFCLKBUF; + i <= AB8500_SYSCLKREQ8RFCLKBUF; i++) { + j = i - AB8500_SYSCLKREQ1RFCLKBUF; + ret = ab8500_sysctrl_write(i, 0xff, + pdata->initial_req_buf_config[j]); + dev_dbg(&pdev->dev, + "Setting SysClkReq%dRfClkBuf 0x%X\n", + j + 1, + pdata->initial_req_buf_config[j]); + if (ret < 0) { + dev_err(&pdev->dev, + "unable to set sysClkReq%dRfClkBuf: " + "%d\n", j + 1, ret); + } + } + } + return 0; } diff --git a/drivers/mfd/db5500-prcmu-regs.h b/drivers/mfd/db5500-prcmu-regs.h new file mode 100644 index 00000000000..0428b5e95ae --- /dev/null +++ b/drivers/mfd/db5500-prcmu-regs.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + */ + +#ifndef __MACH_PRCMU_REGS_DB5500_H +#define __MACH_PRCMU_REGS_DB5500_H + +#define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#define PRCM_TCR 0x1C8 +#define PRCM_TCR_TENSEL_MASK BITS(0, 7) +#define PRCM_TCR_STOP_TIMERS BIT(16) +#define PRCM_TCR_DOZE_MODE BIT(17) + +/* PRCMU HW semaphore */ +#define PRCM_SEM 0x400 +#define PRCM_SEM_PRCM_SEM BIT(0) + +#define DB5500_PRCM_ACLK_MGT 0x004 +#define DB5500_PRCM_SVACLK_MGT 0x008 +#define DB5500_PRCM_SIACLK_MGT 0x00C +#define DB5500_PRCM_SGACLK_MGT 0x014 +#define DB5500_PRCM_UARTCLK_MGT 0x018 +#define DB5500_PRCM_MSP02CLK_MGT 0x01C +#define DB5500_PRCM_I2CCLK_MGT 0x020 +#define DB5500_PRCM_SDMMCCLK_MGT 0x024 +#define DB5500_PRCM_PER1CLK_MGT 0x02C +#define DB5500_PRCM_PER2CLK_MGT 0x030 +#define DB5500_PRCM_PER3CLK_MGT 0x034 +#define DB5500_PRCM_PER5CLK_MGT 0x038 +#define DB5500_PRCM_PER6CLK_MGT 0x03C +#define DB5500_PRCM_IRDACLK_MGT 0x040 +#define DB5500_PRCM_PWMCLK_MGT 0x044 +#define DB5500_PRCM_SPARE1CLK_MGT 0x048 +#define DB5500_PRCM_IRRCCLK_MGT 0x04C +#define DB5500_PRCM_HDMICLK_MGT 0x058 +#define DB5500_PRCM_APEATCLK_MGT 0x05C +#define DB5500_PRCM_APETRACECLK_MGT 0x060 +#define DB5500_PRCM_MCDECLK_MGT 0x064 +#define DB5500_PRCM_DSIALTCLK_MGT 0x06C +#define DB5500_PRCM_DMACLK_MGT 0x074 +#define DB5500_PRCM_B2R2CLK_MGT 0x078 +#define DB5500_PRCM_TVCLK_MGT 0x07C +#define DB5500_PRCM_RNGCLK_MGT 0x284 + +#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) +#define PRCM_CLK_MGT_CLKPLLDIV_SHIFT 0 +#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) +#define PRCM_CLK_MGT_CLKEN BIT(8) + +#define PRCM_ARM_IT1_CLEAR 0x48C +#define PRCM_ARM_IT1_VAL 0x494 + +/* CPU mailbox registers */ +#define PRCM_MBOX_CPU_VAL 0x0FC +#define PRCM_MBOX_CPU_SET 0x100 + +/* System reset register */ +#define PRCM_APE_SOFTRST 0x228 + +/* PRCMU clock/PLL/reset registers */ +#define PRCM_PLLDSI_FREQ 0x500 +#define PRCM_PLLDSI_ENABLE 0x504 +#define PRCM_PLLDSI_LOCKP 0x508 +#define PRCM_DSI_PLLOUT_SEL 0x530 +#define PRCM_DSITVCLK_DIV 0x52C +#define PRCM_APE_RESETN_SET 0x1E4 +#define PRCM_APE_RESETN_CLR 0x1E8 + +/* CLKOUTx SEL0 settings */ +#define CLKOUT_SEL0_REF_CLK 0x01 /* 0b 0001 */ +#define CLKOUT_SEL0_RTC_CLK0 0x02 /* 0b 0010 */ +#define CLKOUT_SEL0_ULP_CLK 0x04 /* 0b 0100 */ +#define CLKOUT_SEL0_SEL_CLK 0x08 /* 0b 1000 */ + +/* CLKOUTx SEL settings */ +#define CLKOUT_SEL_STATIC0 0x0001 /* 0b 00 0000 0001 */ +#define CLKOUT_SEL_REFCLK 0x0002 /* 0b 00 0000 0010 */ +#define CLKOUT_SEL_ULPCLK 0x0004 /* 0b 00 0000 0100 */ +#define CLKOUT_SEL_ARMCLK 0x0008 /* 0b 00 0000 1000 */ +#define CLKOUT_SEL_SYSACC0CLK 0x0010 /* 0b 00 0001 0000 */ +#define CLKOUT_SEL_SOC0PLLCLK 0x0020 /* 0b 00 0010 0000 */ +#define CLKOUT_SEL_SOC1PLLCLK 0x0040 /* 0b 00 0100 0000 */ +#define CLKOUT_SEL_DDRPLLCLK 0x0080 /* 0b 00 1000 0000 */ +#define CLKOUT_SEL_TVCLK 0x0100 /* 0b 01 0000 0000 */ +#define CLKOUT_SEL_IRDACLK 0x0200 /* 0b 10 0000 0000 */ + +/* CLKOUTx dividers */ +#define CLKOUT_DIV_2 0x00 /* 0b 000 */ +#define CLKOUT_DIV_4 0x01 /* 0b 001 */ +#define CLKOUT_DIV_8 0x02 /* 0b 010 */ +#define CLKOUT_DIV_16 0x03 /* 0b 011 */ +#define CLKOUT_DIV_32 0x04 /* 0b 100 */ +#define CLKOUT_DIV_64 0x05 /* 0b 101 */ +/* Values 0x06 and 0x07 will also set the CLKOUTx divider to 64. */ + +/* PRCM_CLKOCR CLKOUTx Control registers */ +#define PRCM_CLKOCR 0x1CC +#define PRCM_CLKOCR_CLKOUT0_SEL0_SHIFT 0 +#define PRCM_CLKOCR_CLKOUT0_SEL0_MASK BITS(0, 3) +#define PRCM_CLKOCR_CLKOUT0_SEL_SHIFT 4 +#define PRCM_CLKOCR_CLKOUT0_SEL_MASK BITS(4, 13) +#define PRCM_CLKOCR_CLKOUT1_SEL0_SHIFT 16 +#define PRCM_CLKOCR_CLKOUT1_SEL0_MASK BITS(16, 19) +#define PRCM_CLKOCR_CLKOUT1_SEL_SHIFT 20 +#define PRCM_CLKOCR_CLKOUT1_SEL_MASK BITS(20, 29) + +/* PRCM_CLKODIV CLKOUTx Dividers */ +#define PRCM_CLKODIV 0x188 +#define PRCM_CLKODIV_CLKOUT0_DIV_SHIFT 0 +#define PRCM_CLKODIV_CLKOUT0_DIV_MASK BITS(0, 2) +#define PRCM_CLKODIV_CLKOUT1_DIV_SHIFT 16 +#define PRCM_CLKODIV_CLKOUT1_DIV_MASK BITS(16, 18) + +#define PRCM_MMIP_LS_CLAMP_SET 0x420 +#define PRCM_MMIP_LS_CLAMP_CLR 0x424 +#define PRCM_DDR_SUBSYS_APE_MINBW 0x438 + +/* Miscellaneous unit registers */ +#define PRCM_DSI_SW_RESET 0x324 +#define PRCM_RESOUTN_SET_OFFSET 0x214 +#define PRCM_RESOUTN_CLR_OFFSET 0x218 + +/* APE - Modem Registers */ +#define PRCM_HOSTACCESS_REQ 0x334 +/* APE - Modem register bit maipulation */ +#define PRCM_HOSTACCESS_REQ_BIT BIT(0) +#define PRCM_APE_ACK 0x49c +#define PRCM_APE_ACK_BIT 0x01 + +/* Watchdog - mtimer registers */ +#define PRCM_TIMER0_RTOS_COMP1_OFFSET 0x4C +#define PRCM_TIMER0_RTOS_COUNTER_OFFSET 0x40 +#define PRCM_TIMER0_IRQ_EN_SET_OFFSET 0x70 +#define PRCM_TIMER0_IRQ_EN_CLR_OFFSET 0x6C +#define PRCM_TIMER0_IRQ_RTOS1_SET 0x08 +#define PRCM_TIMER0_IRQ_RTOS1_CLR 0x08 + +#endif diff --git a/drivers/mfd/db5500-prcmu.c b/drivers/mfd/db5500-prcmu.c index bb115b2f04e..feb1ad1a328 100644 --- a/drivers/mfd/db5500-prcmu.c +++ b/drivers/mfd/db5500-prcmu.c @@ -19,12 +19,21 @@ #include <linux/irq.h> #include <linux/jiffies.h> #include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/regulator/db5500-prcmu.h> +#include <linux/regulator/machine.h> #include <linux/interrupt.h> #include <linux/mfd/dbx500-prcmu.h> #include <mach/hardware.h> #include <mach/irqs.h> #include <mach/db5500-regs.h> -#include "dbx500-prcmu-regs.h" +#include <mach/prcmu-debug.h> + +#include "db5500-prcmu-regs.h" + +#define PRCMU_FW_VERSION_OFFSET 0xA4 +#define PRCM_SW_RST_REASON (tcdm_base + 0xFF8) /* 2 bytes */ #define _PRCM_MB_HEADER (tcdm_base + 0xFE8) #define PRCM_REQ_MB0_HEADER (_PRCM_MB_HEADER + 0x0) @@ -64,6 +73,52 @@ #define PRCM_ACK_MB6 (tcdm_base + 0xF0C) #define PRCM_ACK_MB7 (tcdm_base + 0xF08) +/* Share info */ +#define PRCM_SHARE_INFO (tcdm_base + 0xEC8) + +#define PRCM_SHARE_INFO_HOTDOG (PRCM_SHARE_INFO + 62) + +/* Mailbox 0 REQs */ +#define PRCM_REQ_MB0_AP_POWER_STATE (PRCM_REQ_MB0 + 0x0) +#define PRCM_REQ_MB0_ULP_CLOCK_STATE (PRCM_REQ_MB0 + 0x1) +#define PRCM_REQ_MB0_AP_PLL_STATE (PRCM_REQ_MB0 + 0x2) +#define PRCM_REQ_MB0_DDR_STATE (PRCM_REQ_MB0 + 0x3) +#define PRCM_REQ_MB0_ESRAM0_STATE (PRCM_REQ_MB0 + 0x4) +#define PRCM_REQ_MB0_WAKEUP_DBB (PRCM_REQ_MB0 + 0x8) +#define PRCM_REQ_MB0_WAKEUP_ABB (PRCM_REQ_MB0 + 0xC) + +/* Mailbox 0 ACKs */ +#define PRCM_ACK_MB0_AP_PWRSTTR_STATUS (PRCM_ACK_MB0 + 0x0) +#define PRCM_ACK_MB0_READ_POINTER (PRCM_ACK_MB0 + 0x1) +#define PRCM_ACK_MB0_WAKEUP_0_DBB (PRCM_ACK_MB0 + 0x4) +#define PRCM_ACK_MB0_WAKEUP_0_ABB (PRCM_ACK_MB0 + 0x8) +#define PRCM_ACK_MB0_WAKEUP_1_DBB (PRCM_ACK_MB0 + 0x28) +#define PRCM_ACK_MB0_WAKEUP_1_ABB (PRCM_ACK_MB0 + 0x2C) +#define PRCM_ACK_MB0_EVENT_ABB_NUMBERS 20 + +/* Request mailbox 1 fields. */ +#define PRCM_REQ_MB1_ARM_OPP (PRCM_REQ_MB1 + 0x0) +#define PRCM_REQ_MB1_APE_OPP (PRCM_REQ_MB1 + 0x1) + +/* Mailbox 1 ACKs */ +#define PRCM_ACK_MB1_CURRENT_ARM_OPP (PRCM_ACK_MB1 + 0x0) +#define PRCM_ACK_MB1_CURRENT_APE_OPP (PRCM_ACK_MB1 + 0x1) +#define PRCM_ACK_MB1_ARM_VOLT_STATUS (PRCM_ACK_MB1 + 0x2) +#define PRCM_ACK_MB1_APE_VOLT_STATUS (PRCM_ACK_MB1 + 0x3) + +/* Mailbox 2 REQs */ +#define PRCM_REQ_MB2_EPOD_CLIENT (PRCM_REQ_MB2 + 0x0) +#define PRCM_REQ_MB2_EPOD_STATE (PRCM_REQ_MB2 + 0x1) +#define PRCM_REQ_MB2_CLK_CLIENT (PRCM_REQ_MB2 + 0x2) +#define PRCM_REQ_MB2_CLK_STATE (PRCM_REQ_MB2 + 0x3) +#define PRCM_REQ_MB2_PLL_CLIENT (PRCM_REQ_MB2 + 0x4) +#define PRCM_REQ_MB2_PLL_STATE (PRCM_REQ_MB2 + 0x5) + +/* Mailbox 2 ACKs */ +#define PRCM_ACK_MB2_EPOD_STATUS (PRCM_ACK_MB2 + 0x2) +#define PRCM_ACK_MB2_CLK_STATUS (PRCM_ACK_MB2 + 0x6) +#define PRCM_ACK_MB2_PLL_STATUS (PRCM_ACK_MB2 + 0xA) + enum mb_return_code { RC_SUCCESS, RC_FAIL, @@ -71,12 +126,58 @@ enum mb_return_code { /* Mailbox 0 headers. */ enum mb0_header { - /* request */ - RMB0H_PWR_STATE_TRANS = 1, - RMB0H_WAKE_UP_CFG, - RMB0H_RD_WAKE_UP_ACK, /* acknowledge */ - AMB0H_WAKE_UP = 1, + MB0H_WAKE_UP = 0, + /* request */ + MB0H_PWR_STATE_TRANS, + MB0H_WAKE_UP_CFG, + MB0H_RD_WAKE_UP_ACK, +}; + +/* Mailbox 1 headers.*/ +enum mb1_header { + MB1H_ARM_OPP = 1, + MB1H_APE_OPP, + MB1H_ARM_APE_OPP, +}; + +/* Mailbox 2 headers. */ +enum mb2_header { + MB2H_EPOD_REQUEST = 1, + MB2H_CLK_REQUEST, + MB2H_PLL_REQUEST, +}; + +/* Mailbox 3 headers. */ +enum mb3_header { + MB3H_REFCLK_REQUEST = 1, +}; + +enum sysclk_state { + SYSCLK_OFF, + SYSCLK_ON, +}; + +/* Mailbox 4 headers */ +enum mb4_header { + MB4H_CFG_HOTDOG = 7, + MB4H_CFG_HOTMON = 8, + MB4H_CFG_HOTPERIOD = 10, + MB4H_CGF_MODEM_RESET = 13, + MB4H_CGF_A9WDOG_EN_PREBARK = 14, + MB4H_CGF_A9WDOG_EN_NOPREBARK = 15, + MB4H_CGF_A9WDOG_DIS = 16, +}; + +/* Mailbox 4 ACK headers */ +enum mb4_ack_header { + MB4H_ACK_CFG_HOTDOG = 5, + MB4H_ACK_CFG_HOTMON = 6, + MB4H_ACK_CFG_HOTPERIOD = 8, + MB4H_ACK_CFG_MODEM_RESET = 11, + MB4H_ACK_CGF_A9WDOG_EN_PREBARK = 12, + MB4H_ACK_CGF_A9WDOG_EN_NOPREBARK = 13, + MB4H_ACK_CGF_A9WDOG_DIS = 14, }; /* Mailbox 5 headers. */ @@ -85,6 +186,69 @@ enum mb5_header { MB5H_I2C_READ, }; +enum db5500_arm_opp { + DB5500_ARM_100_OPP = 1, + DB5500_ARM_50_OPP, + DB5500_ARM_EXT_OPP, +}; + +enum db5500_ape_opp { + DB5500_APE_100_OPP = 1, + DB5500_APE_50_OPP +}; + +enum epod_state { + EPOD_OFF, + EPOD_ON, +}; +enum epod_onoffret_state { + EPOD_OOR_OFF, + EPOD_OOR_RET, + EPOD_OOR_ON, +}; +enum db5500_prcmu_pll { + DB5500_PLL_SOC0, + DB5500_PLL_SOC1, + DB5500_PLL_DDR, + DB5500_NUM_PLL_ID, +}; + +enum db5500_prcmu_clk { + DB5500_MSP1CLK, + DB5500_CDCLK, + DB5500_IRDACLK, + DB5500_TVCLK, + DB5500_NUM_CLK_CLIENTS, +}; + +enum on_off_ret { + OFF_ST, + RET_ST, + ON_ST, +}; + +enum db5500_ap_pwr_state { + DB5500_AP_SLEEP = 2, + DB5500_AP_DEEP_SLEEP, + DB5500_AP_IDLE, +}; + +/* Request mailbox 3 fields */ +#define PRCM_REQ_MB3_REFCLK_MGT (PRCM_REQ_MB3 + 0x0) + +/* Ack. mailbox 3 fields */ +#define PRCM_ACK_MB3_REFCLK_REQ (PRCM_ACK_MB3 + 0x0) + + +/* Request mailbox 4 fields */ +#define PRCM_REQ_MB4_HOTDOG_THRESHOLD (PRCM_REQ_MB4 + 32) +#define PRCM_REQ_MB4_HOT_PERIOD (PRCM_REQ_MB4 + 34) +#define PRCM_REQ_MB4_HOTMON_LOW (PRCM_REQ_MB4 + 36) +#define PRCM_REQ_MB4_HOTMON_HIGH (PRCM_REQ_MB4 + 38) + +/* Ack. mailbox 4 field */ +#define PRCM_ACK_MB4_REQUESTS (PRCM_ACK_MB4 + 0x0) + /* Request mailbox 5 fields. */ #define PRCM_REQ_MB5_I2C_SLAVE (PRCM_REQ_MB5 + 0) #define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 1) @@ -105,11 +269,12 @@ enum mb5_header { #define PRCMU_RESET_DSIPLL 0x00004000 #define PRCMU_UNCLAMP_DSIPLL 0x00400800 -/* HDMI CLK MGT PLLSW=001 (PLLSOC0), PLLDIV=0x8, = 50 Mhz*/ -#define PRCMU_DSI_CLOCK_SETTING 0x00000128 +/* HDMI CLK MGT PLLSW=001 (PLLSOC0), PLLDIV=0xC, = 33.33 Mhz*/ +#define PRCMU_DSI_CLOCK_SETTING 0x0000012C /* TVCLK_MGT PLLSW=001 (PLLSOC0) PLLDIV=0x13, = 19.05 MHZ */ #define PRCMU_DSI_LP_CLOCK_SETTING 0x00000135 -#define PRCMU_PLLDSI_FREQ_SETTING 0x00020121 +/* PRCM_PLLDSI_FREQ R=4, N=1, D= 0x65 */ +#define PRCMU_PLLDSI_FREQ_SETTING 0x00040165 #define PRCMU_DSI_PLLOUT_SEL_SETTING 0x00000002 #define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000201 #define PRCMU_DISABLE_ESCAPE_CLOCK_DIV 0x00000101 @@ -125,13 +290,176 @@ enum mb5_header { #define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 /* + * Wakeups/IRQs + */ + +#define WAKEUP_BIT_RTC BIT(0) +#define WAKEUP_BIT_RTT0 BIT(1) +#define WAKEUP_BIT_RTT1 BIT(2) +#define WAKEUP_BIT_CD_IRQ BIT(3) +#define WAKEUP_BIT_SRP_TIM BIT(4) +#define WAKEUP_BIT_APE_REQ BIT(5) +#define WAKEUP_BIT_USB BIT(6) +#define WAKEUP_BIT_ABB BIT(7) +#define WAKEUP_BIT_LOW_POWER_AUDIO BIT(8) +#define WAKEUP_BIT_TEMP_SENSOR_LOW BIT(9) +#define WAKEUP_BIT_ARM BIT(10) +#define WAKEUP_BIT_AC_WAKE_ACK BIT(11) +#define WAKEUP_BIT_TEMP_SENSOR_HIGH BIT(12) +#define WAKEUP_BIT_MODEM_SW_RESET_REQ BIT(20) +#define WAKEUP_BIT_GPIO0 BIT(23) +#define WAKEUP_BIT_GPIO1 BIT(24) +#define WAKEUP_BIT_GPIO2 BIT(25) +#define WAKEUP_BIT_GPIO3 BIT(26) +#define WAKEUP_BIT_GPIO4 BIT(27) +#define WAKEUP_BIT_GPIO5 BIT(28) +#define WAKEUP_BIT_GPIO6 BIT(29) +#define WAKEUP_BIT_GPIO7 BIT(30) +#define WAKEUP_BIT_AC_REL_ACK BIT(30) + +/* + * This vector maps irq numbers to the bits in the bit field used in + * communication with the PRCMU firmware. + * + * The reason for having this is to keep the irq numbers contiguous even though + * the bits in the bit field are not. (The bits also have a tendency to move + * around, to further complicate matters.) + */ +#define IRQ_INDEX(_name) ((IRQ_DB5500_PRCMU_##_name) - IRQ_DB5500_PRCMU_BASE) +#define IRQ_ENTRY(_name)[IRQ_INDEX(_name)] = (WAKEUP_BIT_##_name) +static u32 prcmu_irq_bit[NUM_DB5500_PRCMU_WAKEUPS] = { + IRQ_ENTRY(RTC), + IRQ_ENTRY(RTT0), + IRQ_ENTRY(RTT1), + IRQ_ENTRY(CD_IRQ), + IRQ_ENTRY(SRP_TIM), + IRQ_ENTRY(APE_REQ), + IRQ_ENTRY(USB), + IRQ_ENTRY(ABB), + IRQ_ENTRY(LOW_POWER_AUDIO), + IRQ_ENTRY(TEMP_SENSOR_LOW), + IRQ_ENTRY(TEMP_SENSOR_HIGH), + IRQ_ENTRY(ARM), + IRQ_ENTRY(AC_WAKE_ACK), + IRQ_ENTRY(MODEM_SW_RESET_REQ), + IRQ_ENTRY(GPIO0), + IRQ_ENTRY(GPIO1), + IRQ_ENTRY(GPIO2), + IRQ_ENTRY(GPIO3), + IRQ_ENTRY(GPIO4), + IRQ_ENTRY(GPIO5), + IRQ_ENTRY(GPIO6), + IRQ_ENTRY(GPIO7), + IRQ_ENTRY(AC_REL_ACK), +}; + +#define VALID_WAKEUPS (BIT(NUM_PRCMU_WAKEUP_INDICES) - 1) +#define WAKEUP_ENTRY(_name)[PRCMU_WAKEUP_INDEX_##_name] = (WAKEUP_BIT_##_name) +static u32 prcmu_wakeup_bit[NUM_PRCMU_WAKEUP_INDICES] = { + WAKEUP_ENTRY(RTC), + WAKEUP_ENTRY(RTT0), + WAKEUP_ENTRY(RTT1), + WAKEUP_ENTRY(CD_IRQ), + WAKEUP_ENTRY(USB), + WAKEUP_ENTRY(ABB), + WAKEUP_ENTRY(ARM) +}; + +/* * mb0_transfer - state needed for mailbox 0 communication. - * @lock: The transaction lock. + * @lock The transaction lock. + * @dbb_irqs_lock lock used for (un)masking DBB wakeup interrupts + * @mask_work: Work structure used for (un)masking wakeup interrupts. + * @ac_wake_lock: mutex to lock modem_req and modem_rel + * @req: Request data that need to persist between requests. */ static struct { spinlock_t lock; + spinlock_t dbb_irqs_lock; + struct work_struct mask_work; + struct mutex ac_wake_lock; + struct { + u32 dbb_irqs; + u32 dbb_wakeups; + u32 abb_events; + } req; } mb0_transfer; + +/* + * mb1_transfer - state needed for mailbox 1 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @req_arm_opp Requested arm opp + * @req_ape_opp Requested ape opp + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + u8 req_arm_opp; + u8 req_ape_opp; + struct { + u8 header; + u8 arm_opp; + u8 ape_opp; + u8 arm_voltage_st; + u8 ape_voltage_st; + } ack; +} mb1_transfer; + +/* + * mb2_transfer - state needed for mailbox 2 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @req: Request data that need to persist between requests. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 epod_st[DB5500_NUM_EPOD_ID]; + u8 pll_st[DB5500_NUM_PLL_ID]; + } req; + struct { + u8 header; + u8 status; + } ack; +} mb2_transfer; + +/* + * mb3_transfer - state needed for mailbox 3 communication. + * @sysclk_lock: A lock used to handle concurrent sysclk requests. + * @sysclk_work: Work structure used for sysclk requests. + * @req_st: Requested clock state. + * @ack: Acknowledgement data + */ +static struct { + struct mutex sysclk_lock; + struct completion sysclk_work; + enum sysclk_state req_st; + struct { + u8 header; + u8 status; + } ack; +} mb3_transfer; + +/* + * mb4_transfer - state needed for mailbox 4 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @ack: Acknowledgement data + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 header; + u8 status; + } ack; +} mb4_transfer; + /* * mb5_transfer - state needed for mailbox 5 communication. * @lock: The transaction lock. @@ -148,9 +476,825 @@ static struct { } ack; } mb5_transfer; -/* PRCMU TCDM base IO address. */ +/* Spinlocks */ +static DEFINE_SPINLOCK(clkout_lock); + +/* PRCMU TCDM base IO address */ static __iomem void *tcdm_base; +/* PRCMU MTIMER base IO address */ +static __iomem void *mtimer_base; + +struct clk_mgt { + unsigned int offset; + u32 pllsw; + u32 div; + bool scalable; + bool force50; +}; + +/* PRCMU Firmware Details */ +static struct { + u16 board; + u8 fw_version; + u8 api_version; +} prcmu_version; + +static struct { + u32 timeout; + bool enabled; +} a9wdog_timer; + +static DEFINE_SPINLOCK(clk_mgt_lock); + +#define CLK_MGT_ENTRY(_name, _scalable)[PRCMU_##_name] = { \ + .offset = DB5500_PRCM_##_name##_MGT, \ + .scalable = _scalable, \ +} + +static struct clk_mgt clk_mgt[PRCMU_NUM_REG_CLOCKS] = { + CLK_MGT_ENTRY(SGACLK, true), + CLK_MGT_ENTRY(UARTCLK, false), + CLK_MGT_ENTRY(MSP02CLK, false), + CLK_MGT_ENTRY(I2CCLK, false), + [PRCMU_SDMMCCLK] { + .offset = DB5500_PRCM_SDMMCCLK_MGT, + .force50 = true, + .scalable = false, + + }, + [PRCMU_SPARE1CLK] { + .offset = DB5500_PRCM_SPARE1CLK_MGT, + .force50 = true, + .scalable = false, + + }, + CLK_MGT_ENTRY(PER1CLK, false), + CLK_MGT_ENTRY(PER2CLK, true), + CLK_MGT_ENTRY(PER3CLK, true), + CLK_MGT_ENTRY(PER5CLK, false), /* used for SPI */ + CLK_MGT_ENTRY(PER6CLK, true), + CLK_MGT_ENTRY(PWMCLK, false), + CLK_MGT_ENTRY(IRDACLK, false), + CLK_MGT_ENTRY(IRRCCLK, false), + CLK_MGT_ENTRY(HDMICLK, false), + CLK_MGT_ENTRY(APEATCLK, false), + CLK_MGT_ENTRY(APETRACECLK, true), + CLK_MGT_ENTRY(MCDECLK, true), + CLK_MGT_ENTRY(DSIALTCLK, false), + CLK_MGT_ENTRY(DMACLK, true), + CLK_MGT_ENTRY(B2R2CLK, true), + CLK_MGT_ENTRY(TVCLK, false), + CLK_MGT_ENTRY(RNGCLK, false), + CLK_MGT_ENTRY(SIACLK, false), + CLK_MGT_ENTRY(SVACLK, false), + CLK_MGT_ENTRY(ACLK, true), +}; + +static atomic_t modem_req_state = ATOMIC_INIT(0); + +bool db5500_prcmu_is_modem_requested(void) +{ + return (atomic_read(&modem_req_state) != 0); +} + +/** + * prcmu_modem_req - APE requests Modem to wake up + * + * Whenever APE wants to send message to the modem, it will have to call this + * function to make sure that modem is awake. + */ +void prcmu_modem_req(void) +{ + u32 val; + + mutex_lock(&mb0_transfer.ac_wake_lock); + + val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + if (val & PRCM_HOSTACCESS_REQ_BIT) + goto unlock_and_return; + + writel((val | PRCM_HOSTACCESS_REQ_BIT), + (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); + atomic_set(&modem_req_state, 1); + +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); + +} + +/** + * prcmu_modem_rel - APE has no more messages to send and hence releases modem. + * + * APE to Modem communication is initiated by modem_req and once the + * communication is completed, APE sends modem_rel to complete the protocol. + */ +void prcmu_modem_rel(void) +{ + u32 val; + + mutex_lock(&mb0_transfer.ac_wake_lock); + + val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + if (!(val & PRCM_HOSTACCESS_REQ_BIT)) + goto unlock_and_return; + + writel((val & ~PRCM_HOSTACCESS_REQ_BIT), + (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); + + atomic_set(&modem_req_state, 0); + +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); +} + +/** + * prcm_ape_ack - send an acknowledgement to modem + * + * On ape receiving ape_req, APE will have to acknowledge for the interrupt + * received. This function will send the acknowledgement by writing to the + * prcmu register and an interrupt is trigerred to modem. + */ +void prcmu_ape_ack(void) +{ + writel(PRCM_APE_ACK_BIT, (_PRCMU_BASE + PRCM_APE_ACK)); +} + +/** + * db5500_prcmu_modem_reset - Assert a Reset on modem + * + * This function will assert a reset request to the modem. Prior to that + * PRCM_HOSTACCESS_REQ must be '0'. + */ +void db5500_prcmu_modem_reset(void) +{ + mutex_lock(&mb4_transfer.lock); + + /* PRCM_HOSTACCESS_REQ = 0, before asserting a reset */ + prcmu_modem_rel(); + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(MB4H_CGF_MODEM_RESET, PRCM_REQ_MB4_HEADER); + writel(MBOX_BIT(4), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + if (mb4_transfer.ack.status != RC_SUCCESS || + mb4_transfer.ack.header != MB4H_CGF_MODEM_RESET) + printk(KERN_ERR, + "ACK not received for modem reset interrupt\n"); + mutex_unlock(&mb4_transfer.lock); +} + +/** + * prcmu_config_clkout - Configure one of the programmable clock outputs. + * @clkout: The CLKOUT number (0 or 1). + * @source: Clock source. + * @div: The divider to be applied. + * + * Configures one of the programmable clock outputs (CLKOUTs). + */ +int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +{ + static bool configured[2] = {false, false}; + int r = 0; + unsigned long flags; + u32 sel_val; + u32 div_val; + u32 sel_bits; + u32 div_bits; + u32 sel_mask; + u32 div_mask; + u8 sel0 = CLKOUT_SEL0_SEL_CLK; + u16 sel = 0; + + BUG_ON(clkout > DB5500_CLKOUT1); + BUG_ON(source > DB5500_CLKOUT_IRDACLK); + BUG_ON(div > 7); + + switch (source) { + case DB5500_CLKOUT_REF_CLK_SEL0: + sel0 = CLKOUT_SEL0_REF_CLK; + break; + case DB5500_CLKOUT_RTC_CLK0_SEL0: + sel0 = CLKOUT_SEL0_RTC_CLK0; + break; + case DB5500_CLKOUT_ULP_CLK_SEL0: + sel0 = CLKOUT_SEL0_ULP_CLK; + break; + case DB5500_CLKOUT_STATIC0: + sel = CLKOUT_SEL_STATIC0; + break; + case DB5500_CLKOUT_REFCLK: + sel = CLKOUT_SEL_REFCLK; + break; + case DB5500_CLKOUT_ULPCLK: + sel = CLKOUT_SEL_ULPCLK; + break; + case DB5500_CLKOUT_ARMCLK: + sel = CLKOUT_SEL_ARMCLK; + break; + case DB5500_CLKOUT_SYSACC0CLK: + sel = CLKOUT_SEL_SYSACC0CLK; + break; + case DB5500_CLKOUT_SOC0PLLCLK: + sel = CLKOUT_SEL_SOC0PLLCLK; + break; + case DB5500_CLKOUT_SOC1PLLCLK: + sel = CLKOUT_SEL_SOC1PLLCLK; + break; + case DB5500_CLKOUT_DDRPLLCLK: + sel = CLKOUT_SEL_DDRPLLCLK; + break; + case DB5500_CLKOUT_TVCLK: + sel = CLKOUT_SEL_TVCLK; + break; + case DB5500_CLKOUT_IRDACLK: + sel = CLKOUT_SEL_IRDACLK; + break; + } + + switch (clkout) { + case DB5500_CLKOUT0: + sel_mask = PRCM_CLKOCR_CLKOUT0_SEL0_MASK | + PRCM_CLKOCR_CLKOUT0_SEL_MASK; + sel_bits = ((sel0 << PRCM_CLKOCR_CLKOUT0_SEL0_SHIFT) | + (sel << PRCM_CLKOCR_CLKOUT0_SEL_SHIFT)); + div_mask = PRCM_CLKODIV_CLKOUT0_DIV_MASK; + div_bits = div << PRCM_CLKODIV_CLKOUT0_DIV_SHIFT; + break; + case DB5500_CLKOUT1: + sel_mask = PRCM_CLKOCR_CLKOUT1_SEL0_MASK | + PRCM_CLKOCR_CLKOUT1_SEL_MASK; + sel_bits = ((sel0 << PRCM_CLKOCR_CLKOUT1_SEL0_SHIFT) | + (sel << PRCM_CLKOCR_CLKOUT1_SEL_SHIFT)); + div_mask = PRCM_CLKODIV_CLKOUT1_DIV_MASK; + div_bits = div << PRCM_CLKODIV_CLKOUT1_DIV_SHIFT; + break; + } + + spin_lock_irqsave(&clkout_lock, flags); + + if (configured[clkout]) { + r = -EINVAL; + goto unlock_and_return; + } + + sel_val = readl(_PRCMU_BASE + PRCM_CLKOCR); + writel((sel_bits | (sel_val & ~sel_mask)), + (_PRCMU_BASE + PRCM_CLKOCR)); + + div_val = readl(_PRCMU_BASE + PRCM_CLKODIV); + writel((div_bits | (div_val & ~div_mask)), + (_PRCMU_BASE + PRCM_CLKODIV)); + + configured[clkout] = true; + +unlock_and_return: + spin_unlock_irqrestore(&clkout_lock, flags); + + return r; +} + +static int request_sysclk(bool enable) +{ + int r; + + r = 0; + mutex_lock(&mb3_transfer.sysclk_lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(3)) + cpu_relax(); + + if (enable) + mb3_transfer.req_st = SYSCLK_ON; + else + mb3_transfer.req_st = SYSCLK_OFF; + + writeb(mb3_transfer.req_st, (PRCM_REQ_MB3_REFCLK_MGT)); + + writeb(MB3H_REFCLK_REQUEST, (PRCM_REQ_MB3_HEADER)); + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + /* + * The firmware only sends an ACK if we want to enable the + * SysClk, and it succeeds. + */ + if (!wait_for_completion_timeout(&mb3_transfer.sysclk_work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + WARN(1, "Failed to set sysclk"); + goto unlock_and_return; + } + + if ((mb3_transfer.ack.header != MB3H_REFCLK_REQUEST) || + (mb3_transfer.ack.status != mb3_transfer.req_st)) { + r = -EIO; + } + +unlock_and_return: + mutex_unlock(&mb3_transfer.sysclk_lock); + + return r; +} + +static int request_timclk(bool enable) +{ + u32 val = (PRCM_TCR_DOZE_MODE | PRCM_TCR_TENSEL_MASK); + + if (!enable) + val |= PRCM_TCR_STOP_TIMERS; + writel(val, _PRCMU_BASE + PRCM_TCR); + + return 0; +} + +static int request_clk(u8 clock, bool enable) +{ + int r = 0; + + BUG_ON(clock >= DB5500_NUM_CLK_CLIENTS); + + mutex_lock(&mb2_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* fill in mailbox */ + writeb(clock, PRCM_REQ_MB2_CLK_CLIENT); + writeb(enable, PRCM_REQ_MB2_CLK_STATE); + + writeb(MB2H_CLK_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: request_clk() failed.\n"); + r = -EIO; + WARN(1, "Failed in request_clk"); + goto unlock_and_return; + } + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_CLK_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + +static int request_reg_clock(u8 clock, bool enable) +{ + u32 val; + unsigned long flags; + + WARN_ON(!clk_mgt[clock].offset); + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(_PRCMU_BASE + clk_mgt[clock].offset); + if (enable) { + val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); + } else { + clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); + } + writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); + + /* Release the HW semaphore. */ + writel(0, _PRCMU_BASE + PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} + +/* + * request_pll() - Request for a pll to be enabled or disabled. + * @pll: The pll for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +static int request_pll(u8 pll, bool enable) +{ + int r = 0; + + BUG_ON(pll >= DB5500_NUM_PLL_ID); + mutex_lock(&mb2_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + mb2_transfer.req.pll_st[pll] = enable; + + /* fill in mailbox */ + writeb(pll, PRCM_REQ_MB2_PLL_CLIENT); + writeb(mb2_transfer.req.pll_st[pll], PRCM_REQ_MB2_PLL_STATE); + + writeb(MB2H_PLL_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: set_pll() failed.\n"); + r = -EIO; + WARN(1, "Failed to set pll"); + goto unlock_and_return; + } + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_PLL_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + + return r; +} + +/** + * db5500_prcmu_request_clock() - Request for a clock to be enabled or disabled. + * @clock: The clock for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +int db5500_prcmu_request_clock(u8 clock, bool enable) +{ + /* MSP1 & CD clocks are handled by FW */ + if (clock == PRCMU_MSP1CLK) + return request_clk(DB5500_MSP1CLK, enable); + else if (clock == PRCMU_CDCLK) + return request_clk(DB5500_CDCLK, enable); + else if (clock == PRCMU_IRDACLK) + return request_clk(DB5500_IRDACLK, enable); + else if (clock < PRCMU_NUM_REG_CLOCKS) + return request_reg_clock(clock, enable); + else if (clock == PRCMU_TIMCLK) + return request_timclk(enable); + else if (clock == PRCMU_PLLSOC0) + return request_pll(DB5500_PLL_SOC0, enable); + else if (clock == PRCMU_PLLSOC1) + return request_pll(DB5500_PLL_SOC1, enable); + else if (clock == PRCMU_PLLDDR) + return request_pll(DB5500_PLL_DDR, enable); + else if (clock == PRCMU_SYSCLK) + return request_sysclk(enable); + else + return -EINVAL; +} + +/* This function should only be called while mb0_transfer.lock is held. */ +static void config_wakeups(void) +{ + static u32 last_dbb_events; + static u32 last_abb_events; + u32 dbb_events; + u32 abb_events; + + dbb_events = mb0_transfer.req.dbb_irqs | mb0_transfer.req.dbb_wakeups; + + abb_events = mb0_transfer.req.abb_events; + + if ((dbb_events == last_dbb_events) && (abb_events == last_abb_events)) + return; + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + writel(dbb_events, PRCM_REQ_MB0_WAKEUP_DBB); + writel(abb_events, PRCM_REQ_MB0_WAKEUP_ABB); + writeb(MB0H_WAKE_UP_CFG, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + last_dbb_events = dbb_events; + last_abb_events = abb_events; +} + +int db5500_prcmu_config_esram0_deep_sleep(u8 state) +{ + unsigned long flags; + + if ((state > ESRAM0_DEEP_SLEEP_STATE_RET) || + (state < ESRAM0_DEEP_SLEEP_STATE_OFF)) + return -EINVAL; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + if (state == ESRAM0_DEEP_SLEEP_STATE_RET) + writeb(RET_ST, PRCM_REQ_MB0_ESRAM0_STATE); + else + writeb(OFF_ST, PRCM_REQ_MB0_ESRAM0_STATE); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return 0; +} + +int db5500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) +{ + int r = 0; + unsigned long flags; + + /* Deep Idle is not supported in DB5500 */ + BUG_ON((state < PRCMU_AP_SLEEP) || (state >= PRCMU_AP_DEEP_IDLE)); + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + switch (state) { + case PRCMU_AP_IDLE: + writeb(DB5500_AP_IDLE, PRCM_REQ_MB0_AP_POWER_STATE); + /* TODO: Can be high latency */ + writeb(DDR_PWR_STATE_UNCHANGED, PRCM_REQ_MB0_DDR_STATE); + break; + case PRCMU_AP_SLEEP: + writeb(DB5500_AP_SLEEP, PRCM_REQ_MB0_AP_POWER_STATE); + break; + case PRCMU_AP_DEEP_SLEEP: + writeb(DB5500_AP_DEEP_SLEEP, PRCM_REQ_MB0_AP_POWER_STATE); + break; + default: + r = -EINVAL; + goto unlock_return; + } + writeb((keep_ap_pll ? 1 : 0), PRCM_REQ_MB0_AP_PLL_STATE); + writeb((keep_ulp_clk ? 1 : 0), PRCM_REQ_MB0_ULP_CLOCK_STATE); + + writeb(MB0H_PWR_STATE_TRANS, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + +unlock_return: + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return r; +} + +u8 db5500_prcmu_get_power_state_result(void) +{ + u8 status = readb_relaxed(PRCM_ACK_MB0_AP_PWRSTTR_STATUS); + + /* + * Callers expect all the status values to match 8500. Adjust for + * PendingReq_Er (0x2b). + */ + if (status == 0x2b) + status = PRCMU_PRCMU2ARMPENDINGIT_ER; + + return status; +} + +void db5500_prcmu_enable_wakeups(u32 wakeups) +{ + unsigned long flags; + u32 bits; + int i; + + BUG_ON(wakeups != (wakeups & VALID_WAKEUPS)); + + for (i = 0, bits = 0; i < NUM_PRCMU_WAKEUP_INDICES; i++) { + if (wakeups & BIT(i)) { + if (prcmu_wakeup_bit[i] == 0) + WARN(1, "WAKEUP NOT SUPPORTED"); + else + bits |= prcmu_wakeup_bit[i]; + } + } + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.dbb_wakeups = bits; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db5500_prcmu_config_abb_event_readout(u32 abb_events) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.abb_events = abb_events; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db5500_prcmu_get_abb_event_buffer(void __iomem **buf) +{ + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + *buf = (PRCM_ACK_MB0_WAKEUP_1_ABB); + else + *buf = (PRCM_ACK_MB0_WAKEUP_0_ABB); +} + +/* This function should be called with lock */ +static int mailbox4_request(u8 mb4_request, u8 ack_request) +{ + int ret = 0; + + writeb(mb4_request, PRCM_REQ_MB4_HEADER); + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + if (!wait_for_completion_timeout(&mb4_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: MB4 request %d failed", mb4_request); + ret = -EIO; + WARN(1, "prcmu: failed mb4 request"); + goto failed; + } + + if (mb4_transfer.ack.header != ack_request || + mb4_transfer.ack.status != RC_SUCCESS) + ret = -EIO; +failed: + return ret; +} + +int db5500_prcmu_get_hotdog(void) +{ + return readw(PRCM_SHARE_INFO_HOTDOG); +} + +int db5500_prcmu_config_hotdog(u8 threshold) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(threshold, PRCM_REQ_MB4_HOTDOG_THRESHOLD); + r = mailbox4_request(MB4H_CFG_HOTDOG, MB4H_ACK_CFG_HOTDOG); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +int db5500_prcmu_config_hotmon(u8 low, u8 high) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(low, PRCM_REQ_MB4_HOTMON_LOW); + writew(high, PRCM_REQ_MB4_HOTMON_HIGH); + + r = mailbox4_request(MB4H_CFG_HOTMON, MB4H_ACK_CFG_HOTMON); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +static int config_hot_period(u16 val) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(val, PRCM_REQ_MB4_HOT_PERIOD); + r = mailbox4_request(MB4H_CFG_HOTPERIOD, MB4H_ACK_CFG_HOTPERIOD); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +/* + * period in milli seconds + */ +int db5500_prcmu_start_temp_sense(u16 period) +{ + if (period == 0xFFFF) + return -EINVAL; + + return config_hot_period(period); +} + +int db5500_prcmu_stop_temp_sense(void) +{ + return config_hot_period(0xFFFF); +} + +static int prcmu_a9wdog(u8 req, u8 ack) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + r = mailbox4_request(req, ack); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +static void prcmu_a9wdog_set_interrupt(bool enable) +{ + if (enable) { + writel(PRCM_TIMER0_IRQ_RTOS1_SET, + (mtimer_base + PRCM_TIMER0_IRQ_EN_SET_OFFSET)); + } else { + writel(PRCM_TIMER0_IRQ_RTOS1_CLR, + (mtimer_base + PRCM_TIMER0_IRQ_EN_CLR_OFFSET)); + } +} + +static void prcmu_a9wdog_set_timeout(u32 timeout) +{ + u32 comp_timeout; + + comp_timeout = readl(mtimer_base + PRCM_TIMER0_RTOS_COUNTER_OFFSET) + + timeout; + writel(comp_timeout, mtimer_base + PRCM_TIMER0_RTOS_COMP1_OFFSET); +} + +int db5500_prcmu_config_a9wdog(u8 num, bool sleep_auto_off) +{ + /* + * Sleep auto off feature is not supported. Resume and + * suspend will be handled by watchdog driver. + */ + return 0; +} + +int db5500_prcmu_enable_a9wdog(u8 id) +{ + int r = 0; + + if (a9wdog_timer.enabled) + return -EPERM; + + prcmu_a9wdog_set_interrupt(true); + + r = prcmu_a9wdog(MB4H_CGF_A9WDOG_EN_PREBARK, + MB4H_ACK_CGF_A9WDOG_EN_PREBARK); + if (!r) + a9wdog_timer.enabled = true; + else + prcmu_a9wdog_set_interrupt(false); + + return r; +} + +int db5500_prcmu_disable_a9wdog(u8 id) +{ + if (!a9wdog_timer.enabled) + return -EPERM; + + prcmu_a9wdog_set_interrupt(false); + + a9wdog_timer.enabled = false; + + return prcmu_a9wdog(MB4H_CGF_A9WDOG_DIS, + MB4H_ACK_CGF_A9WDOG_DIS); +} + +int db5500_prcmu_kick_a9wdog(u8 id) +{ + int r = 0; + + if (a9wdog_timer.enabled) + prcmu_a9wdog_set_timeout(a9wdog_timer.timeout); + else + r = -EPERM; + + return r; +} + +int db5500_prcmu_load_a9wdog(u8 id, u32 timeout) +{ + if (a9wdog_timer.enabled) + return -EPERM; + + prcmu_a9wdog_set_timeout(timeout); + a9wdog_timer.timeout = timeout; + + return 0; +} + /** * db5500_prcmu_abb_read() - Read register value(s) from the ABB. * @slave: The I2C slave address. @@ -170,14 +1314,14 @@ int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); writeb(reg, PRCM_REQ_MB5_I2C_REG); writeb(size, PRCM_REQ_MB5_I2C_SIZE); writeb(MB5H_I2C_READ, PRCM_REQ_MB5_HEADER); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_MBOX_CPU_SET); wait_for_completion(&mb5_transfer.work); r = 0; @@ -211,7 +1355,7 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); writeb(reg, PRCM_REQ_MB5_I2C_REG); @@ -219,7 +1363,7 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) memcpy_toio(PRCM_REQ_MB5_I2C_DATA, value, size); writeb(MB5H_I2C_WRITE, PRCM_REQ_MB5_HEADER); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_MBOX_CPU_SET); wait_for_completion(&mb5_transfer.work); if ((mb5_transfer.ack.header == MB5H_I2C_WRITE) && @@ -233,42 +1377,385 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) return r; } +/** + * db5500_prcmu_set_arm_opp - set the appropriate ARM OPP + * @opp: The new ARM operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function sets the the operating point of the ARM. + */ +int db5500_prcmu_set_arm_opp(u8 opp) +{ + int r; + u8 db5500_opp; + + r = 0; + + switch (opp) { + case ARM_EXTCLK: + db5500_opp = DB5500_ARM_EXT_OPP; + break; + case ARM_50_OPP: + db5500_opp = DB5500_ARM_50_OPP; + break; + case ARM_100_OPP: + db5500_opp = DB5500_ARM_100_OPP; + break; + default: + pr_err("prcmu: %s() received wrong opp value: %d\n", + __func__, opp); + r = -EINVAL; + goto bailout; + } + + mutex_lock(&mb1_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_ARM_OPP, PRCM_REQ_MB1_HEADER); + + writeb(db5500_opp, PRCM_REQ_MB1_ARM_OPP); + writel(MBOX_BIT(1), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb1_transfer.work, + msecs_to_jiffies(20000))) { + r = -EIO; + WARN(1, "prcmu: failed to set arm opp"); + goto unlock_and_return; + } + + if (mb1_transfer.ack.header != MB1H_ARM_OPP || + (mb1_transfer.ack.arm_opp != db5500_opp) || + (mb1_transfer.ack.arm_voltage_st != RC_SUCCESS)) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); +bailout: + if (!r) + prcmu_debug_arm_opp_log(opp); + return r; +} + +static void __init prcmu_ape_clocks_init(void) +{ + u8 opp = db5500_prcmu_get_ape_opp(); + unsigned long flags; + int i; + + WARN(opp != APE_100_OPP, "%s: Initial APE OPP (%u) not 100%%?\n", + __func__, opp); + + for (i = 0; i < PRCMU_NUM_REG_CLOCKS; i++) { + struct clk_mgt *clkmgt = &clk_mgt[i]; + u32 clkval; + u32 div; + + if (!clkmgt->scalable && !clkmgt->force50) + continue; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + clkval = readl(_PRCMU_BASE + clkmgt->offset); + div = clkval & PRCM_CLK_MGT_CLKPLLDIV_MASK; + div >>= PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + + if (clkmgt->force50) { + div *= 2; + + clkval &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + clkval |= div << PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + writel(clkval, _PRCMU_BASE + clkmgt->offset); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + continue; + } + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + clkmgt->div = div; + if (!div) + pr_err("%s: scalable clock at offset %#x has zero divisor\n", + __func__, clkmgt->offset); + } +} + +static void prcmu_ape_clocks_scale(u8 opp) +{ + unsigned long irqflags; + unsigned int i; + u32 clkval; + + /* + * Note: calling printk() under the following lock can cause lock + * recursion via clk_enable() for the console UART! + */ + spin_lock_irqsave(&clk_mgt_lock, irqflags); + + /* take a lock on HW (HWSEM)*/ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < PRCMU_NUM_REG_CLOCKS; i++) { + u32 divval; + + if (!clk_mgt[i].scalable) + continue; + + clkval = readl(_PRCMU_BASE + clk_mgt[i].offset); + divval = clk_mgt[i].div; + + pr_debug("PRCMU: reg %#x prev clk = 0x%x stored div = 0x%x\n", + clk_mgt[i].offset, clkval, divval); + + if (opp == DB5500_APE_50_OPP) + divval *= 2; + + clkval &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + clkval |= divval << PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + + pr_debug("PRCMU: wr 0x%x in reg 0x%x\n", + clkval, clk_mgt[i].offset); + + writel(clkval, _PRCMU_BASE + clk_mgt[i].offset); + } + + /* release lock */ + writel(0, (_PRCMU_BASE + PRCM_SEM)); + + spin_unlock_irqrestore(&clk_mgt_lock, irqflags); +} +/* Divide the frequency of certain clocks by 2 for APE_50_PARTLY_25_OPP. */ +static void request_even_slower_clocks(bool enable) +{ + void __iomem *clock_reg[] = { + (_PRCMU_BASE + DB5500_PRCM_ACLK_MGT), + (_PRCMU_BASE + DB5500_PRCM_DMACLK_MGT) + }; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < ARRAY_SIZE(clock_reg); i++) { + u32 val; + u32 div; + + val = readl(clock_reg[i]); + div = (val & PRCM_CLK_MGT_CLKPLLDIV_MASK); + if (enable) { + if ((div <= 1) || (div > 15)) { + pr_err("prcmu: Bad clock divider %d in %s\n", + div, __func__); + goto unlock_and_return; + } + div <<= 1; + } else { + if (div <= 2) + goto unlock_and_return; + div >>= 1; + } + val = ((val & ~PRCM_CLK_MGT_CLKPLLDIV_MASK) | + (div & PRCM_CLK_MGT_CLKPLLDIV_MASK)); + writel(val, clock_reg[i]); + } + +unlock_and_return: + /* Release the HW semaphore. */ + writel(0, _PRCMU_BASE + PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} +int db5500_prcmu_set_ape_opp(u8 opp) +{ + int ret = 0; + u8 db5500_opp; + if (opp == mb1_transfer.req_ape_opp) + return 0; + + switch (opp) { + case APE_100_OPP: + db5500_opp = DB5500_APE_100_OPP; + break; + case APE_50_OPP: + case APE_50_PARTLY_25_OPP: + db5500_opp = DB5500_APE_50_OPP; + break; + default: + pr_err("prcmu: %s() received wrong opp value: %d\n", + __func__, opp); + ret = -EINVAL; + goto bailout; + } + + mutex_lock(&mb1_transfer.lock); + if (mb1_transfer.req_ape_opp == APE_50_PARTLY_25_OPP) + request_even_slower_clocks(false); + if ((opp != APE_100_OPP) && (mb1_transfer.req_ape_opp != APE_100_OPP)) + goto skip_message; + + prcmu_ape_clocks_scale(db5500_opp); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_APE_OPP, PRCM_REQ_MB1_HEADER); + writeb(db5500_opp, PRCM_REQ_MB1_APE_OPP); + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + if (!wait_for_completion_timeout(&mb1_transfer.work, + msecs_to_jiffies(20000))) { + ret = -EIO; + WARN(1, "prcmu: failed to set ape opp to %u", opp); + goto unlock_and_return; + } + + if (mb1_transfer.ack.header != MB1H_APE_OPP || + (mb1_transfer.ack.ape_opp != db5500_opp) || + (mb1_transfer.ack.arm_voltage_st != RC_SUCCESS)) + ret = -EIO; + +skip_message: + if ((!ret && (opp == APE_50_PARTLY_25_OPP)) || + (ret && (mb1_transfer.req_ape_opp == APE_50_PARTLY_25_OPP))) + request_even_slower_clocks(true); + if (!ret) + mb1_transfer.req_ape_opp = opp; +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); +bailout: + return ret; +} + +int db5500_prcmu_get_ape_opp(void) +{ + u8 opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + + switch (opp) { + case DB5500_APE_100_OPP: + return APE_100_OPP; + case DB5500_APE_50_OPP: + return APE_50_OPP; + default: + pr_err("prcmu: %s() read unknown opp value: %d\n", + __func__, opp); + return APE_100_OPP; + } +} + +int db5500_prcmu_get_ddr_opp(void) +{ + return readb(_PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); +} + +int db5500_prcmu_set_ddr_opp(u8 opp) +{ + if (opp != DDR_100_OPP && opp != DDR_50_OPP) + return -EINVAL; + + writeb(opp, _PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); + + return 0; +} + +/** + * db5500_prcmu_get_arm_opp - get the current ARM OPP + * + * Returns: the current ARM OPP + */ +int db5500_prcmu_get_arm_opp(void) +{ + u8 opp = readb(PRCM_ACK_MB1_CURRENT_ARM_OPP); + + switch (opp) { + case DB5500_ARM_EXT_OPP: + return ARM_EXTCLK; + case DB5500_ARM_50_OPP: + return ARM_50_OPP; + case DB5500_ARM_100_OPP: + return ARM_100_OPP; + default: + pr_err("prcmu: %s() read unknown opp value: %d\n", + __func__, opp); + return ARM_100_OPP; + } +} + +int prcmu_resetout(u8 resoutn, u8 state) +{ + int offset; + int pin = -1; + + offset = state > 0 ? PRCM_RESOUTN_SET_OFFSET : PRCM_RESOUTN_CLR_OFFSET; + + switch (resoutn) { + case 0: + pin = PRCMU_RESOUTN0_PIN; + break; + case 1: + pin = PRCMU_RESOUTN1_PIN; + break; + case 2: + pin = PRCMU_RESOUTN2_PIN; + default: + break; + } + + if (pin > 0) + writel(pin, _PRCMU_BASE + offset); + else + return -EINVAL; + + return 0; +} + int db5500_prcmu_enable_dsipll(void) { int i; + int ret = 0; /* Enable DSIPLL_RESETN resets */ - writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); + writel(PRCMU_RESET_DSIPLL, _PRCMU_BASE + PRCM_APE_RESETN_CLR); /* Unclamp DSIPLL in/out */ - writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); + writel(PRCMU_UNCLAMP_DSIPLL, _PRCMU_BASE + PRCM_MMIP_LS_CLAMP_CLR); /* Set DSI PLL FREQ */ - writel(PRCMU_PLLDSI_FREQ_SETTING, PRCM_PLLDSI_FREQ); + writel(PRCMU_PLLDSI_FREQ_SETTING, _PRCMU_BASE + PRCM_PLLDSI_FREQ); writel(PRCMU_DSI_PLLOUT_SEL_SETTING, - PRCM_DSI_PLLOUT_SEL); + _PRCMU_BASE + PRCM_DSI_PLLOUT_SEL); /* Enable Escape clocks */ - writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, _PRCMU_BASE + PRCM_DSITVCLK_DIV); /* Start DSI PLL */ - writel(PRCMU_ENABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + writel(PRCMU_ENABLE_PLLDSI, _PRCMU_BASE + PRCM_PLLDSI_ENABLE); /* Reset DSI PLL */ - writel(PRCMU_DSI_RESET_SW, PRCM_DSI_SW_RESET); + writel(PRCMU_DSI_RESET_SW, _PRCMU_BASE + PRCM_DSI_SW_RESET); for (i = 0; i < 10; i++) { - if ((readl(PRCM_PLLDSI_LOCKP) & + if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & PRCMU_PLLDSI_LOCKP_LOCKED) == PRCMU_PLLDSI_LOCKP_LOCKED) break; udelay(100); } + + if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & + PRCMU_PLLDSI_LOCKP_LOCKED) + != PRCMU_PLLDSI_LOCKP_LOCKED) + ret = -EIO; /* Release DSIPLL_RESETN */ - writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_SET); - return 0; + writel(PRCMU_RESET_DSIPLL, _PRCMU_BASE + PRCM_APE_RESETN_SET); + return ret; } int db5500_prcmu_disable_dsipll(void) { /* Disable dsi pll */ - writel(PRCMU_DISABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + writel(PRCMU_DISABLE_PLLDSI, _PRCMU_BASE + PRCM_PLLDSI_ENABLE); /* Disable escapeclock */ - writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, _PRCMU_BASE + PRCM_DSITVCLK_DIV); return 0; } @@ -276,27 +1763,150 @@ int db5500_prcmu_set_display_clocks(void) { /* HDMI and TVCLK Should be handled somewhere else */ /* PLLDIV=8, PLLSW=2, CLKEN=1 */ - writel(PRCMU_DSI_CLOCK_SETTING, PRCM_HDMICLK_MGT); + writel(PRCMU_DSI_CLOCK_SETTING, _PRCMU_BASE + DB5500_PRCM_HDMICLK_MGT); /* PLLDIV=14, PLLSW=2, CLKEN=1 */ - writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); + writel(PRCMU_DSI_LP_CLOCK_SETTING, _PRCMU_BASE + DB5500_PRCM_TVCLK_MGT); return 0; } +u32 db5500_prcmu_read(unsigned int reg) +{ + return readl_relaxed(_PRCMU_BASE + reg); +} + +void db5500_prcmu_write(unsigned int reg, u32 value) +{ + writel_relaxed(value, _PRCMU_BASE + reg); +} + +void db5500_prcmu_write_masked(unsigned int reg, u32 mask, u32 value) +{ + u32 val; + + val = readl_relaxed(_PRCMU_BASE + reg); + val = (val & ~mask) | (value & mask); + writel_relaxed(val, _PRCMU_BASE + reg); +} + +/** + * db5500_prcmu_system_reset - System reset + * + * Saves the reset reason code and then sets the APE_SOFTRST register which + * fires an interrupt to fw + */ +void db5500_prcmu_system_reset(u16 reset_code) +{ + writew(reset_code, PRCM_SW_RST_REASON); + writel(1, _PRCMU_BASE + PRCM_APE_SOFTRST); +} + +/** + * db5500_prcmu_get_reset_code - Retrieve SW reset reason code + * + * Retrieves the reset reason code stored by prcmu_system_reset() before + * last restart. + */ +u16 db5500_prcmu_get_reset_code(void) +{ + return readw(PRCM_SW_RST_REASON); +} + static void ack_dbb_wakeup(void) { unsigned long flags; spin_lock_irqsave(&mb0_transfer.lock, flags); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) cpu_relax(); - writeb(RMB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); - writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); + writeb(MB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); spin_unlock_irqrestore(&mb0_transfer.lock, flags); } +int db5500_prcmu_set_epod(u16 epod, u8 epod_state) +{ + int r = 0; + bool ram_retention = false; + + /* check argument */ + BUG_ON(epod < DB5500_EPOD_ID_BASE); + BUG_ON(epod_state > EPOD_STATE_ON); + BUG_ON((epod - DB5500_EPOD_ID_BASE) >= DB5500_NUM_EPOD_ID); + + if (epod == DB5500_EPOD_ID_ESRAM12) + ram_retention = true; + + /* check argument */ + BUG_ON(epod_state == EPOD_STATE_RAMRET && !ram_retention); + + /* get lock */ + mutex_lock(&mb2_transfer.lock); + + /* wait for mailbox */ + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* Retention is allowed only for ESRAM12 */ + if (epod == DB5500_EPOD_ID_ESRAM12) { + switch (epod_state) { + case EPOD_STATE_ON: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_ON; + break; + case EPOD_STATE_OFF: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_OFF; + break; + case EPOD_STATE_RAMRET: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_RET; + break; + default: + r = -EINVAL; + goto unlock_and_return; + break; + } + } else { + if (epod_state == EPOD_STATE_ON) + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_ON; + else if (epod_state == EPOD_STATE_OFF) + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OFF; + else { + r = -EINVAL; + goto unlock_and_return; + } + } + /* fill in mailbox */ + writeb((epod - DB5500_EPOD_ID_BASE), PRCM_REQ_MB2_EPOD_CLIENT); + writeb(mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE], + PRCM_REQ_MB2_EPOD_STATE); + + writeb(MB2H_EPOD_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: set_epod() failed.\n"); + r = -EIO; + WARN(1, "Failed to set epod"); + goto unlock_and_return; + } + + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_EPOD_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + static inline void print_unknown_header_warning(u8 n, u8 header) { pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", @@ -306,11 +1916,27 @@ static inline void print_unknown_header_warning(u8 n, u8 header) static bool read_mailbox_0(void) { bool r; + u32 ev; + unsigned int n; + u8 header; header = readb(PRCM_ACK_MB0_HEADER); switch (header) { - case AMB0H_WAKE_UP: + case MB0H_WAKE_UP: + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + ev = readl(PRCM_ACK_MB0_WAKEUP_1_DBB); + else + ev = readl(PRCM_ACK_MB0_WAKEUP_0_DBB); + + ev &= mb0_transfer.req.dbb_irqs; + + for (n = 0; n < NUM_DB5500_PRCMU_WAKEUPS; n++) { + if (ev & prcmu_irq_bit[n]) { + if (n != IRQ_INDEX(ABB)) + generic_handle_irq(IRQ_DB5500_PRCMU_BASE + n); + } + } r = true; break; default: @@ -318,31 +1944,123 @@ static bool read_mailbox_0(void) r = false; break; } - writel(MBOX_BIT(0), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return r; } static bool read_mailbox_1(void) { - writel(MBOX_BIT(1), PRCM_ARM_IT1_CLR); + u8 header; + bool do_complete = true; + + header = mb1_transfer.ack.header = readb(PRCM_ACK_MB1_HEADER); + + switch (header) { + case MB1H_ARM_OPP: + mb1_transfer.ack.arm_opp = readb(PRCM_ACK_MB1_CURRENT_ARM_OPP); + mb1_transfer.ack.arm_voltage_st = + readb(PRCM_ACK_MB1_ARM_VOLT_STATUS); + break; + case MB1H_APE_OPP: + mb1_transfer.ack.ape_opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_st = + readb(PRCM_ACK_MB1_APE_VOLT_STATUS); + break; + case MB1H_ARM_APE_OPP: + mb1_transfer.ack.ape_opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_st = + readb(PRCM_ACK_MB1_APE_VOLT_STATUS); + break; + default: + print_unknown_header_warning(1, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(1), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + + if (do_complete) + complete(&mb1_transfer.work); + return false; } static bool read_mailbox_2(void) { - writel(MBOX_BIT(2), PRCM_ARM_IT1_CLR); + u8 header; + + header = readb(PRCM_ACK_MB2_HEADER); + mb2_transfer.ack.header = header; + switch (header) { + case MB2H_EPOD_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_EPOD_STATUS); + break; + case MB2H_CLK_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_CLK_STATUS); + break; + case MB2H_PLL_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_PLL_STATUS); + break; + default: + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + pr_err("prcmu: Wrong ACK received for MB2 request \n"); + return false; + break; + } + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + complete(&mb2_transfer.work); return false; } static bool read_mailbox_3(void) { - writel(MBOX_BIT(3), PRCM_ARM_IT1_CLR); + u8 header; + + header = readb(PRCM_ACK_MB3_HEADER); + mb3_transfer.ack.header = header; + switch (header) { + case MB3H_REFCLK_REQUEST: + mb3_transfer.ack.status = readb(PRCM_ACK_MB3_REFCLK_REQ); + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + complete(&mb3_transfer.sysclk_work); + break; + default: + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + pr_err("prcmu: wrong MB3 header\n"); + break; + } + return false; } static bool read_mailbox_4(void) { - writel(MBOX_BIT(4), PRCM_ARM_IT1_CLR); + u8 header; + bool do_complete = true; + + header = readb(PRCM_ACK_MB4_HEADER); + mb4_transfer.ack.header = header; + switch (header) { + case MB4H_ACK_CFG_HOTDOG: + case MB4H_ACK_CFG_HOTMON: + case MB4H_ACK_CFG_HOTPERIOD: + case MB4H_ACK_CFG_MODEM_RESET: + case MB4H_ACK_CGF_A9WDOG_EN_PREBARK: + case MB4H_ACK_CGF_A9WDOG_EN_NOPREBARK: + case MB4H_ACK_CGF_A9WDOG_DIS: + mb4_transfer.ack.status = readb(PRCM_ACK_MB4_REQUESTS); + break; + default: + print_unknown_header_warning(4, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_ARM_IT1_CLEAR)); + + if (do_complete) + complete(&mb4_transfer.work); + return false; } @@ -363,19 +2081,19 @@ static bool read_mailbox_5(void) print_unknown_header_warning(5, header); break; } - writel(MBOX_BIT(5), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } static bool read_mailbox_6(void) { - writel(MBOX_BIT(6), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(6), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } static bool read_mailbox_7(void) { - writel(MBOX_BIT(7), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(7), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } @@ -396,7 +2114,7 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) u8 n; irqreturn_t r; - bits = (readl(PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); + bits = (readl(_PRCMU_BASE + PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); if (unlikely(!bits)) return IRQ_NONE; @@ -413,39 +2131,271 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) static irqreturn_t prcmu_irq_thread_fn(int irq, void *data) { + u32 ev; + + /* + * ABB needs to be handled before the wakeup because + * the ping/pong buffers for ABB events could change + * after we acknowledge the wakeup. + */ + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + ev = readl(PRCM_ACK_MB0_WAKEUP_1_DBB); + else + ev = readl(PRCM_ACK_MB0_WAKEUP_0_DBB); + + ev &= mb0_transfer.req.dbb_irqs; + if (ev & WAKEUP_BIT_ABB) + handle_nested_irq(IRQ_DB5500_PRCMU_ABB); + ack_dbb_wakeup(); + return IRQ_HANDLED; } +static void prcmu_mask_work(struct work_struct *work) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +static void prcmu_irq_mask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs &= ~prcmu_irq_bit[d->irq - IRQ_DB5500_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + schedule_work(&mb0_transfer.mask_work); +} + +static void prcmu_irq_unmask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs |= prcmu_irq_bit[d->irq - IRQ_DB5500_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + schedule_work(&mb0_transfer.mask_work); +} + +static void noop(struct irq_data *d) +{ +} + +static struct irq_chip prcmu_irq_chip = { + .name = "prcmu", + .irq_disable = prcmu_irq_mask, + .irq_ack = noop, + .irq_mask = prcmu_irq_mask, + .irq_unmask = prcmu_irq_unmask, +}; + void __init db5500_prcmu_early_init(void) { + unsigned int i; + void *tcpm_base = ioremap_nocache(U5500_PRCMU_TCPM_BASE, SZ_4K); + + if (tcpm_base != NULL) { + int version_high, version_low; + + version_high = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET); + version_low = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET + 4); + prcmu_version.board = (version_high >> 24) & 0xFF; + prcmu_version.fw_version = version_high & 0xFF; + prcmu_version.api_version = version_low & 0xFF; + + pr_info("PRCMU Firmware Version: 0x%x\n", + prcmu_version.fw_version); + pr_info("PRCMU API Version: 0x%x\n", + prcmu_version.api_version); + + iounmap(tcpm_base); + } + tcdm_base = __io_address(U5500_PRCMU_TCDM_BASE); + mtimer_base = __io_address(U5500_MTIMER_BASE); spin_lock_init(&mb0_transfer.lock); + spin_lock_init(&mb0_transfer.dbb_irqs_lock); + mutex_init(&mb0_transfer.ac_wake_lock); + mutex_init(&mb1_transfer.lock); + init_completion(&mb1_transfer.work); + mutex_init(&mb2_transfer.lock); + init_completion(&mb2_transfer.work); + mutex_init(&mb3_transfer.sysclk_lock); + init_completion(&mb3_transfer.sysclk_work); + mutex_init(&mb4_transfer.lock); + init_completion(&mb4_transfer.work); mutex_init(&mb5_transfer.lock); init_completion(&mb5_transfer.work); + + INIT_WORK(&mb0_transfer.mask_work, prcmu_mask_work); + + /* Initalize irqs. */ + for (i = 0; i < NUM_DB5500_PRCMU_WAKEUPS; i++) { + unsigned int irq; + + irq = IRQ_DB5500_PRCMU_BASE + i; + irq_set_chip_and_handler(irq, &prcmu_irq_chip, + handle_simple_irq); + if (irq == IRQ_DB5500_PRCMU_ABB) + irq_set_nested_thread(irq, true); + set_irq_flags(irq, IRQF_VALID); + } + prcmu_ape_clocks_init(); +} + +/* + * Power domain switches (ePODs) modeled as regulators for the DB5500 SoC + */ +static struct regulator_consumer_supply db5500_vape_consumers[] = { + REGULATOR_SUPPLY("v-ape", NULL), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.0"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.1"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.2"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.3"), + REGULATOR_SUPPLY("vcore", "sdi0"), + REGULATOR_SUPPLY("vcore", "sdi1"), + REGULATOR_SUPPLY("vcore", "sdi2"), + REGULATOR_SUPPLY("vcore", "sdi3"), + REGULATOR_SUPPLY("vcore", "sdi4"), + REGULATOR_SUPPLY("v-uart", "uart0"), + REGULATOR_SUPPLY("v-uart", "uart1"), + REGULATOR_SUPPLY("v-uart", "uart2"), + REGULATOR_SUPPLY("v-uart", "uart3"), + REGULATOR_SUPPLY("v-ape", "db5500-keypad"), +}; + +static struct regulator_consumer_supply db5500_sga_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.0"), + REGULATOR_SUPPLY("v-mali", NULL), +}; + +static struct regulator_consumer_supply db5500_hva_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.1"), + REGULATOR_SUPPLY("v-hva", NULL), +}; + +static struct regulator_consumer_supply db5500_sia_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.2"), + REGULATOR_SUPPLY("v-sia", "mmio_camera"), +}; + +static struct regulator_consumer_supply db5500_disp_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.3"), + REGULATOR_SUPPLY("vsupply", "b2r2_bus"), + REGULATOR_SUPPLY("vsupply", "mcde"), +}; + +static struct regulator_consumer_supply db5500_esram12_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.4"), + REGULATOR_SUPPLY("v-esram12", "mcde"), + REGULATOR_SUPPLY("esram12", "hva"), +}; + +#define DB5500_REGULATOR_SWITCH(lower, upper) \ +[DB5500_REGULATOR_SWITCH_##upper] = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + }, \ + .consumer_supplies = db5500_##lower##_consumers, \ + .num_consumer_supplies = ARRAY_SIZE(db5500_##lower##_consumers),\ } +#define DB5500_REGULATOR_SWITCH_VAPE(lower, upper) \ +[DB5500_REGULATOR_SWITCH_##upper] = { \ + .supply_regulator = "db5500-vape", \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + }, \ + .consumer_supplies = db5500_##lower##_consumers, \ + .num_consumer_supplies = ARRAY_SIZE(db5500_##lower##_consumers),\ +} \ + +static struct regulator_init_data db5500_regulators[DB5500_NUM_REGULATORS] = { + [DB5500_REGULATOR_VAPE] = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db5500_vape_consumers, + .num_consumer_supplies = ARRAY_SIZE(db5500_vape_consumers), + }, + DB5500_REGULATOR_SWITCH_VAPE(sga, SGA), + DB5500_REGULATOR_SWITCH_VAPE(hva, HVA), + DB5500_REGULATOR_SWITCH_VAPE(sia, SIA), + DB5500_REGULATOR_SWITCH_VAPE(disp, DISP), + /* + * ESRAM12 is put in retention by the firmware when VAPE is + * turned off so there's no need to hold VAPE. + */ + DB5500_REGULATOR_SWITCH(esram12, ESRAM12), +}; + +static struct mfd_cell db5500_prcmu_devs[] = { + { + .name = "db5500-prcmu-regulators", + .platform_data = &db5500_regulators, + .pdata_size = sizeof(db5500_regulators), + }, + { + .name = "cpufreq-u5500", + }, +}; + /** * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic * */ -int __init db5500_prcmu_init(void) +static int __init db5500_prcmu_probe(struct platform_device *pdev) { - int r = 0; + int err = 0; if (ux500_is_svp() || !cpu_is_u5500()) return -ENODEV; /* Clean up the mailbox interrupts after pre-kernel code. */ - writel(ALL_MBOX_BITS, PRCM_ARM_IT1_CLR); + writel(ALL_MBOX_BITS, _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); - r = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, - prcmu_irq_thread_fn, 0, "prcmu", NULL); - if (r < 0) { + err = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, + prcmu_irq_thread_fn, IRQF_NO_SUSPEND, "prcmu", NULL); + if (err < 0) { pr_err("prcmu: Failed to allocate IRQ_DB5500_PRCMU1.\n"); - return -EBUSY; + err = -EBUSY; + goto no_irq_return; } - return 0; + + err = mfd_add_devices(&pdev->dev, 0, db5500_prcmu_devs, + ARRAY_SIZE(db5500_prcmu_devs), NULL, + 0); + + if (err) + pr_err("prcmu: Failed to add subdevices\n"); + else + pr_info("DB5500 PRCMU initialized\n"); + +no_irq_return: + return err; + +} + +static struct platform_driver db5500_prcmu_driver = { + .driver = { + .name = "db5500-prcmu", + .owner = THIS_MODULE, + }, +}; + +static int __init db5500_prcmu_init(void) +{ + return platform_driver_probe(&db5500_prcmu_driver, db5500_prcmu_probe); } arch_initcall(db5500_prcmu_init); diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index af8e0efedbe..76f2e24a94e 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -34,16 +34,13 @@ #include <mach/irqs.h> #include <mach/db8500-regs.h> #include <mach/id.h> +#include <mach/prcmu-debug.h> + #include "dbx500-prcmu-regs.h" /* Offset for the firmware version within the TCPM */ #define PRCMU_FW_VERSION_OFFSET 0xA4 -/* PRCMU project numbers, defined by PRCMU FW */ -#define PRCMU_PROJECT_ID_8500V1_0 1 -#define PRCMU_PROJECT_ID_8500V2_0 2 -#define PRCMU_PROJECT_ID_8400V2_0 3 - /* Index of different voltages to be used when accessing AVSData */ #define PRCM_AVS_BASE 0x2FC #define PRCM_AVS_VBB_RET (PRCM_AVS_BASE + 0x0) @@ -137,6 +134,8 @@ #define PRCM_REQ_MB1_ARM_OPP (PRCM_REQ_MB1 + 0x0) #define PRCM_REQ_MB1_APE_OPP (PRCM_REQ_MB1 + 0x1) #define PRCM_REQ_MB1_PLL_ON_OFF (PRCM_REQ_MB1 + 0x4) +#define PLL_SOC0_OFF 0x1 +#define PLL_SOC0_ON 0x2 #define PLL_SOC1_OFF 0x4 #define PLL_SOC1_ON 0x8 @@ -266,6 +265,11 @@ #define WAKEUP_BIT_GPIO7 BIT(30) #define WAKEUP_BIT_GPIO8 BIT(31) +static struct { + bool valid; + struct prcmu_fw_version version; +} fw_info; + /* * This vector maps irq numbers to the bits in the bit field used in * communication with the PRCMU firmware. @@ -341,11 +345,13 @@ static struct { * mb1_transfer - state needed for mailbox 1 communication. * @lock: The transaction lock. * @work: The transaction completion structure. + * @ape_opp: The current APE OPP. * @ack: Reply ("acknowledge") data. */ static struct { struct mutex lock; struct completion work; + u8 ape_opp; struct { u8 header; u8 arm_opp; @@ -413,50 +419,102 @@ static struct { static atomic_t ac_wake_req_state = ATOMIC_INIT(0); /* Spinlocks */ +static DEFINE_SPINLOCK(prcmu_lock); static DEFINE_SPINLOCK(clkout_lock); -static DEFINE_SPINLOCK(gpiocr_lock); /* Global var to runtime determine TCDM base for v2 or v1 */ static __iomem void *tcdm_base; struct clk_mgt { - unsigned int offset; + void __iomem *reg; u32 pllsw; + int branch; + bool clk38div; +}; + +enum { + PLL_RAW, + PLL_FIX, + PLL_DIV }; static DEFINE_SPINLOCK(clk_mgt_lock); -#define CLK_MGT_ENTRY(_name)[PRCMU_##_name] = { (PRCM_##_name##_MGT_OFF), 0 } +#define CLK_MGT_ENTRY(_name, _branch, _clk38div)[PRCMU_##_name] = \ + { (PRCM_##_name##_MGT), 0 , _branch, _clk38div} struct clk_mgt clk_mgt[PRCMU_NUM_REG_CLOCKS] = { - CLK_MGT_ENTRY(SGACLK), - CLK_MGT_ENTRY(UARTCLK), - CLK_MGT_ENTRY(MSP02CLK), - CLK_MGT_ENTRY(MSP1CLK), - CLK_MGT_ENTRY(I2CCLK), - CLK_MGT_ENTRY(SDMMCCLK), - CLK_MGT_ENTRY(SLIMCLK), - CLK_MGT_ENTRY(PER1CLK), - CLK_MGT_ENTRY(PER2CLK), - CLK_MGT_ENTRY(PER3CLK), - CLK_MGT_ENTRY(PER5CLK), - CLK_MGT_ENTRY(PER6CLK), - CLK_MGT_ENTRY(PER7CLK), - CLK_MGT_ENTRY(LCDCLK), - CLK_MGT_ENTRY(BMLCLK), - CLK_MGT_ENTRY(HSITXCLK), - CLK_MGT_ENTRY(HSIRXCLK), - CLK_MGT_ENTRY(HDMICLK), - CLK_MGT_ENTRY(APEATCLK), - CLK_MGT_ENTRY(APETRACECLK), - CLK_MGT_ENTRY(MCDECLK), - CLK_MGT_ENTRY(IPI2CCLK), - CLK_MGT_ENTRY(DSIALTCLK), - CLK_MGT_ENTRY(DMACLK), - CLK_MGT_ENTRY(B2R2CLK), - CLK_MGT_ENTRY(TVCLK), - CLK_MGT_ENTRY(SSPCLK), - CLK_MGT_ENTRY(RNGCLK), - CLK_MGT_ENTRY(UICCCLK), + CLK_MGT_ENTRY(SGACLK, PLL_DIV, false), + CLK_MGT_ENTRY(UARTCLK, PLL_FIX, true), + CLK_MGT_ENTRY(MSP02CLK, PLL_FIX, true), + CLK_MGT_ENTRY(MSP1CLK, PLL_FIX, true), + CLK_MGT_ENTRY(I2CCLK, PLL_FIX, true), + CLK_MGT_ENTRY(SDMMCCLK, PLL_DIV, true), + CLK_MGT_ENTRY(SLIMCLK, PLL_FIX, true), + CLK_MGT_ENTRY(PER1CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER2CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER3CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER5CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER6CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER7CLK, PLL_DIV, true), + CLK_MGT_ENTRY(LCDCLK, PLL_FIX, true), + CLK_MGT_ENTRY(BMLCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HSITXCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HSIRXCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HDMICLK, PLL_FIX, false), + CLK_MGT_ENTRY(APEATCLK, PLL_DIV, true), + CLK_MGT_ENTRY(APETRACECLK, PLL_DIV, true), + CLK_MGT_ENTRY(MCDECLK, PLL_DIV, true), + CLK_MGT_ENTRY(IPI2CCLK, PLL_FIX, true), + CLK_MGT_ENTRY(DSIALTCLK, PLL_FIX, false), + CLK_MGT_ENTRY(DMACLK, PLL_DIV, true), + CLK_MGT_ENTRY(B2R2CLK, PLL_DIV, true), + CLK_MGT_ENTRY(TVCLK, PLL_FIX, true), + CLK_MGT_ENTRY(SSPCLK, PLL_FIX, true), + CLK_MGT_ENTRY(RNGCLK, PLL_FIX, true), + CLK_MGT_ENTRY(UICCCLK, PLL_FIX, false), +}; + +struct dsiclk { + u32 divsel_mask; + u32 divsel_shift; + u32 divsel; +}; + +static struct dsiclk dsiclk[2] = { + { + .divsel_mask = PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_MASK, + .divsel_shift = PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_SHIFT, + .divsel = PRCM_DSI_PLLOUT_SEL_PHI, + }, + { + .divsel_mask = PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_MASK, + .divsel_shift = PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_SHIFT, + .divsel = PRCM_DSI_PLLOUT_SEL_PHI, + } +}; + +struct dsiescclk { + u32 en; + u32 div_mask; + u32 div_shift; +}; + +static struct dsiescclk dsiescclk[3] = { + { + .en = PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_EN, + .div_mask = PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_MASK, + .div_shift = PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_SHIFT, + }, + { + .en = PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_EN, + .div_mask = PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_MASK, + .div_shift = PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_SHIFT, + }, + { + .en = PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_EN, + .div_mask = PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_MASK, + .div_shift = PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_SHIFT, + } }; static struct regulator *hwacc_regulator[NUM_HW_ACC]; @@ -503,9 +561,6 @@ static const char *hwacc_ret_regulator_name[NUM_HW_ACC] = { /* PLLDIV=12, PLLSW=4 (PLLDDR) */ #define PRCMU_DSI_CLOCK_SETTING 0x0000008C -/* PLLDIV=8, PLLSW=4 (PLLDDR) */ -#define PRCMU_DSI_CLOCK_SETTING_U8400 0x00000088 - /* DPI 50000000 Hz */ #define PRCMU_DPI_CLOCK_SETTING ((1 << PRCMU_CLK_PLL_SW_SHIFT) | \ (16 << PRCMU_CLK_PLL_DIV_SHIFT)) @@ -514,9 +569,6 @@ static const char *hwacc_ret_regulator_name[NUM_HW_ACC] = { /* D=101, N=1, R=4, SELDIV2=0 */ #define PRCMU_PLLDSI_FREQ_SETTING 0x00040165 -/* D=70, N=1, R=3, SELDIV2=0 */ -#define PRCMU_PLLDSI_FREQ_SETTING_U8400 0x00030146 - #define PRCMU_ENABLE_PLLDSI 0x00000001 #define PRCMU_DISABLE_PLLDSI 0x00000000 #define PRCMU_RELEASE_RESET_DSS 0x0000400C @@ -528,30 +580,17 @@ static const char *hwacc_ret_regulator_name[NUM_HW_ACC] = { #define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 -static struct { - u8 project_number; - u8 api_version; - u8 func_version; - u8 errata; -} prcmu_version; - - int db8500_prcmu_enable_dsipll(void) { int i; - unsigned int plldsifreq; /* Clear DSIPLL_RESETN */ writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); /* Unclamp DSIPLL in/out */ writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); - if (prcmu_is_u8400()) - plldsifreq = PRCMU_PLLDSI_FREQ_SETTING_U8400; - else - plldsifreq = PRCMU_PLLDSI_FREQ_SETTING; /* Set DSI PLL FREQ */ - writel(plldsifreq, PRCM_PLLDSI_FREQ); + writel(PRCMU_PLLDSI_FREQ_SETTING, PRCM_PLLDSI_FREQ); writel(PRCMU_DSI_PLLOUT_SEL_SETTING, PRCM_DSI_PLLOUT_SEL); /* Enable Escape clocks */ writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); @@ -583,12 +622,6 @@ int db8500_prcmu_disable_dsipll(void) int db8500_prcmu_set_display_clocks(void) { unsigned long flags; - unsigned int dsiclk; - - if (prcmu_is_u8400()) - dsiclk = PRCMU_DSI_CLOCK_SETTING_U8400; - else - dsiclk = PRCMU_DSI_CLOCK_SETTING; spin_lock_irqsave(&clk_mgt_lock, flags); @@ -596,7 +629,7 @@ int db8500_prcmu_set_display_clocks(void) while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) cpu_relax(); - writel(dsiclk, PRCM_HDMICLK_MGT); + writel(PRCMU_DSI_CLOCK_SETTING, PRCM_HDMICLK_MGT); writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); writel(PRCMU_DPI_CLOCK_SETTING, PRCM_LCDCLK_MGT); @@ -608,43 +641,41 @@ int db8500_prcmu_set_display_clocks(void) return 0; } -/** - * prcmu_enable_spi2 - Enables pin muxing for SPI2 on OtherAlternateC1. - */ -void prcmu_enable_spi2(void) +u32 db8500_prcmu_read(unsigned int reg) +{ + return readl(_PRCMU_BASE + reg); +} + +void db8500_prcmu_write(unsigned int reg, u32 value) { - u32 reg; unsigned long flags; - spin_lock_irqsave(&gpiocr_lock, flags); - reg = readl(PRCM_GPIOCR); - writel(reg | PRCM_GPIOCR_SPI2_SELECT, PRCM_GPIOCR); - spin_unlock_irqrestore(&gpiocr_lock, flags); + spin_lock_irqsave(&prcmu_lock, flags); + writel(value, (_PRCMU_BASE + reg)); + spin_unlock_irqrestore(&prcmu_lock, flags); } -/** - * prcmu_disable_spi2 - Disables pin muxing for SPI2 on OtherAlternateC1. - */ -void prcmu_disable_spi2(void) +void db8500_prcmu_write_masked(unsigned int reg, u32 mask, u32 value) { - u32 reg; + u32 val; unsigned long flags; - spin_lock_irqsave(&gpiocr_lock, flags); - reg = readl(PRCM_GPIOCR); - writel(reg & ~PRCM_GPIOCR_SPI2_SELECT, PRCM_GPIOCR); - spin_unlock_irqrestore(&gpiocr_lock, flags); + spin_lock_irqsave(&prcmu_lock, flags); + val = readl(_PRCMU_BASE + reg); + val = ((val & ~mask) | (value & mask)); + writel(val, (_PRCMU_BASE + reg)); + spin_unlock_irqrestore(&prcmu_lock, flags); } -bool prcmu_has_arm_maxopp(void) +struct prcmu_fw_version *prcmu_get_fw_version(void) { - return (readb(tcdm_base + PRCM_AVS_VARM_MAX_OPP) & - PRCM_AVS_ISMODEENABLE_MASK) == PRCM_AVS_ISMODEENABLE_MASK; + return fw_info.valid ? &fw_info.version : NULL; } -bool prcmu_is_u8400(void) +bool prcmu_has_arm_maxopp(void) { - return prcmu_version.project_number == PRCMU_PROJECT_ID_8400V2_0; + return (readb(tcdm_base + PRCM_AVS_VARM_MAX_OPP) & + PRCM_AVS_ISMODEENABLE_MASK) == PRCM_AVS_ISMODEENABLE_MASK; } /** @@ -787,6 +818,11 @@ int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) return 0; } +u8 db8500_prcmu_get_power_state_result(void) +{ + return readb(tcdm_base + PRCM_ACK_MB0_AP_PWRSTTR_STATUS); +} + /* This function should only be called while mb0_transfer.lock is held. */ static void config_wakeups(void) { @@ -895,6 +931,8 @@ int db8500_prcmu_set_arm_opp(u8 opp) mutex_unlock(&mb1_transfer.lock); + prcmu_debug_arm_opp_log(opp); + return r; } @@ -909,23 +947,23 @@ int db8500_prcmu_get_arm_opp(void) } /** - * prcmu_get_ddr_opp - get the current DDR OPP + * db8500_prcmu_get_ddr_opp - get the current DDR OPP * * Returns: the current DDR OPP */ -int prcmu_get_ddr_opp(void) +int db8500_prcmu_get_ddr_opp(void) { return readb(PRCM_DDR_SUBSYS_APE_MINBW); } /** - * set_ddr_opp - set the appropriate DDR OPP + * db8500_set_ddr_opp - set the appropriate DDR OPP * @opp: The new DDR operating point to which transition is to be made * Returns: 0 on success, non-zero on failure * * This function sets the operating point of the DDR. */ -int prcmu_set_ddr_opp(u8 opp) +int db8500_prcmu_set_ddr_opp(u8 opp) { if (opp < DDR_100_OPP || opp > DDR_25_OPP) return -EINVAL; @@ -935,25 +973,82 @@ int prcmu_set_ddr_opp(u8 opp) return 0; } + +/* Divide the frequency of certain clocks by 2 for APE_50_PARTLY_25_OPP. */ +static void request_even_slower_clocks(bool enable) +{ + void __iomem *clock_reg[] = { + PRCM_ACLK_MGT, + PRCM_DMACLK_MGT + }; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < ARRAY_SIZE(clock_reg); i++) { + u32 val; + u32 div; + + val = readl(clock_reg[i]); + div = (val & PRCM_CLK_MGT_CLKPLLDIV_MASK); + if (enable) { + if ((div <= 1) || (div > 15)) { + pr_err("prcmu: Bad clock divider %d in %s\n", + div, __func__); + goto unlock_and_return; + } + div <<= 1; + } else { + if (div <= 2) + goto unlock_and_return; + div >>= 1; + } + val = ((val & ~PRCM_CLK_MGT_CLKPLLDIV_MASK) | + (div & PRCM_CLK_MGT_CLKPLLDIV_MASK)); + writel(val, clock_reg[i]); + } + +unlock_and_return: + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} + /** - * set_ape_opp - set the appropriate APE OPP + * db8500_set_ape_opp - set the appropriate APE OPP * @opp: The new APE operating point to which transition is to be made * Returns: 0 on success, non-zero on failure * * This function sets the operating point of the APE. */ -int prcmu_set_ape_opp(u8 opp) +int db8500_prcmu_set_ape_opp(u8 opp) { int r = 0; + if (opp == mb1_transfer.ape_opp) + return 0; + mutex_lock(&mb1_transfer.lock); + if (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP) + request_even_slower_clocks(false); + + if ((opp != APE_100_OPP) && (mb1_transfer.ape_opp != APE_100_OPP)) + goto skip_message; + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) cpu_relax(); writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); writeb(ARM_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_ARM_OPP)); - writeb(opp, (tcdm_base + PRCM_REQ_MB1_APE_OPP)); + writeb(((opp == APE_50_PARTLY_25_OPP) ? APE_50_OPP : opp), + (tcdm_base + PRCM_REQ_MB1_APE_OPP)); writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); wait_for_completion(&mb1_transfer.work); @@ -962,17 +1057,24 @@ int prcmu_set_ape_opp(u8 opp) (mb1_transfer.ack.ape_opp != opp)) r = -EIO; +skip_message: + if ((!r && (opp == APE_50_PARTLY_25_OPP)) || + (r && (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP))) + request_even_slower_clocks(true); + if (!r) + mb1_transfer.ape_opp = opp; + mutex_unlock(&mb1_transfer.lock); return r; } /** - * prcmu_get_ape_opp - get the current APE OPP + * db8500_prcmu_get_ape_opp - get the current APE OPP * * Returns: the current APE OPP */ -int prcmu_get_ape_opp(void) +int db8500_prcmu_get_ape_opp(void) { return readb(tcdm_base + PRCM_ACK_MB1_CURRENT_APE_OPP); } @@ -1056,7 +1158,9 @@ static int request_pll(u8 clock, bool enable) { int r = 0; - if (clock == PRCMU_PLLSOC1) + if (clock == PRCMU_PLLSOC0) + clock = (enable ? PLL_SOC0_ON : PLL_SOC0_OFF); + else if (clock == PRCMU_PLLSOC1) clock = (enable ? PLL_SOC1_ON : PLL_SOC1_OFF); else return -EINVAL; @@ -1375,7 +1479,7 @@ static int request_timclk(bool enable) return 0; } -static int request_reg_clock(u8 clock, bool enable) +static int request_clock(u8 clock, bool enable) { u32 val; unsigned long flags; @@ -1386,14 +1490,14 @@ static int request_reg_clock(u8 clock, bool enable) while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) cpu_relax(); - val = readl(_PRCMU_BASE + clk_mgt[clock].offset); + val = readl(clk_mgt[clock].reg); if (enable) { val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); } else { clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); } - writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); + writel(val, clk_mgt[clock].reg); /* Release the HW semaphore. */ writel(0, PRCM_SEM); @@ -1413,7 +1517,7 @@ static int request_sga_clock(u8 clock, bool enable) writel(val | PRCM_CGATING_BYPASS_ICN2, PRCM_CGATING_BYPASS); } - ret = request_reg_clock(clock, enable); + ret = request_clock(clock, enable); if (!ret && !enable) { val = readl(PRCM_CGATING_BYPASS); @@ -1423,6 +1527,78 @@ static int request_sga_clock(u8 clock, bool enable) return ret; } +static inline bool plldsi_locked(void) +{ + return (readl(PRCM_PLLDSI_LOCKP) & + (PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP10 | + PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP3)) == + (PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP10 | + PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP3); +} + +static int request_plldsi(bool enable) +{ + int r = 0; + u32 val; + + writel((PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMP | + PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMPI), (enable ? + PRCM_MMIP_LS_CLAMP_CLR : PRCM_MMIP_LS_CLAMP_SET)); + + val = readl(PRCM_PLLDSI_ENABLE); + if (enable) + val |= PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE; + else + val &= ~PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE; + writel(val, PRCM_PLLDSI_ENABLE); + + if (enable) { + unsigned int i; + bool locked = plldsi_locked(); + + for (i = 10; !locked && (i > 0); --i) { + udelay(100); + locked = plldsi_locked(); + } + if (locked) { + writel(PRCM_APE_RESETN_DSIPLL_RESETN, + PRCM_APE_RESETN_SET); + } else { + writel((PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMP | + PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMPI), + PRCM_MMIP_LS_CLAMP_SET); + val &= ~PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE; + writel(val, PRCM_PLLDSI_ENABLE); + r = -EAGAIN; + } + } else { + writel(PRCM_APE_RESETN_DSIPLL_RESETN, PRCM_APE_RESETN_CLR); + } + return r; +} + +static int request_dsiclk(u8 n, bool enable) +{ + u32 val; + + val = readl(PRCM_DSI_PLLOUT_SEL); + val &= ~dsiclk[n].divsel_mask; + val |= ((enable ? dsiclk[n].divsel : PRCM_DSI_PLLOUT_SEL_OFF) << + dsiclk[n].divsel_shift); + writel(val, PRCM_DSI_PLLOUT_SEL); + return 0; +} + +static int request_dsiescclk(u8 n, bool enable) +{ + u32 val; + + val = readl(PRCM_DSITVCLK_DIV); + enable ? (val |= dsiescclk[n].en) : (val &= ~dsiescclk[n].en); + writel(val, PRCM_DSITVCLK_DIV); + return 0; +} + /** * db8500_prcmu_request_clock() - Request for a clock to be enabled or disabled. * @clock: The clock for which the request is made. @@ -1440,16 +1616,434 @@ int db8500_prcmu_request_clock(u8 clock, bool enable) return request_timclk(enable); case PRCMU_SYSCLK: return request_sysclk(enable); + case PRCMU_PLLDSI: + return request_plldsi(enable); + case PRCMU_PLLSOC0: case PRCMU_PLLSOC1: return request_pll(clock, enable); default: break; } if (clock < PRCMU_NUM_REG_CLOCKS) - return request_reg_clock(clock, enable); + return request_clock(clock, enable); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + return request_dsiclk((clock - PRCMU_DSI0CLK), enable); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + return request_dsiescclk((clock - PRCMU_DSI0ESCCLK), enable); return -EINVAL; } +static unsigned long pll_rate(void __iomem *reg, unsigned long src_rate, + int branch) +{ + u64 rate; + u32 val; + u32 d; + u32 div = 1; + + val = readl(reg); + + rate = src_rate; + rate *= ((val & PRCM_PLL_FREQ_D_MASK) >> PRCM_PLL_FREQ_D_SHIFT); + + d = ((val & PRCM_PLL_FREQ_N_MASK) >> PRCM_PLL_FREQ_N_SHIFT); + if (d > 1) + div *= d; + + d = ((val & PRCM_PLL_FREQ_R_MASK) >> PRCM_PLL_FREQ_R_SHIFT); + if (d > 1) + div *= d; + + if (val & PRCM_PLL_FREQ_SELDIV2) + div *= 2; + + if ((branch == PLL_FIX) || ((branch == PLL_DIV) && + (val & PRCM_PLL_FREQ_DIV2EN) && + ((reg == PRCM_PLLSOC0_FREQ) || + (reg == PRCM_PLLDDR_FREQ)))) + div *= 2; + + (void)do_div(rate, div); + + return (unsigned long)rate; +} + +#define ROOT_CLOCK_RATE 38400000 + +static unsigned long clock_rate(u8 clock) +{ + u32 val; + u32 pllsw; + unsigned long rate = ROOT_CLOCK_RATE; + + val = readl(clk_mgt[clock].reg); + + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div && (val & PRCM_CLK_MGT_CLK38DIV)) + rate /= 2; + return rate; + } + + val |= clk_mgt[clock].pllsw; + pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + + if (pllsw == PRCM_CLK_MGT_CLKPLLSW_SOC0) + rate = pll_rate(PRCM_PLLSOC0_FREQ, rate, clk_mgt[clock].branch); + else if (pllsw == PRCM_CLK_MGT_CLKPLLSW_SOC1) + rate = pll_rate(PRCM_PLLSOC1_FREQ, rate, clk_mgt[clock].branch); + else if (pllsw == PRCM_CLK_MGT_CLKPLLSW_DDR) + rate = pll_rate(PRCM_PLLDDR_FREQ, rate, clk_mgt[clock].branch); + else + return 0; + + if ((clock == PRCMU_SGACLK) && + (val & PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN)) { + u64 r = (rate * 10); + + (void)do_div(r, 25); + return (unsigned long)r; + } + val &= PRCM_CLK_MGT_CLKPLLDIV_MASK; + if (val) + return rate / val; + else + return 0; +} + +static unsigned long dsiclk_rate(u8 n) +{ + u32 divsel; + u32 div = 1; + + divsel = readl(PRCM_DSI_PLLOUT_SEL); + divsel = ((divsel & dsiclk[n].divsel_mask) >> dsiclk[n].divsel_shift); + + if (divsel == PRCM_DSI_PLLOUT_SEL_OFF) + divsel = dsiclk[n].divsel; + + switch (divsel) { + case PRCM_DSI_PLLOUT_SEL_PHI_4: + div *= 2; + case PRCM_DSI_PLLOUT_SEL_PHI_2: + div *= 2; + case PRCM_DSI_PLLOUT_SEL_PHI: + return pll_rate(PRCM_PLLDSI_FREQ, clock_rate(PRCMU_HDMICLK), + PLL_RAW) / div; + default: + return 0; + } +} + +static unsigned long dsiescclk_rate(u8 n) +{ + u32 div; + + div = readl(PRCM_DSITVCLK_DIV); + div = ((div & dsiescclk[n].div_mask) >> (dsiescclk[n].div_shift)); + return clock_rate(PRCMU_TVCLK) / max((u32)1, div); +} + +unsigned long prcmu_clock_rate(u8 clock) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + return clock_rate(clock); + else if (clock == PRCMU_TIMCLK) + return ROOT_CLOCK_RATE / 16; + else if (clock == PRCMU_SYSCLK) + return ROOT_CLOCK_RATE; + else if (clock == PRCMU_PLLSOC0) + return pll_rate(PRCM_PLLSOC0_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_PLLSOC1) + return pll_rate(PRCM_PLLSOC1_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_PLLDDR) + return pll_rate(PRCM_PLLDDR_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_PLLDSI) + return pll_rate(PRCM_PLLDSI_FREQ, clock_rate(PRCMU_HDMICLK), + PLL_RAW); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + return dsiclk_rate(clock - PRCMU_DSI0CLK); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + return dsiescclk_rate(clock - PRCMU_DSI0ESCCLK); + else + return 0; +} + +static unsigned long clock_source_rate(u32 clk_mgt_val, int branch) +{ + if (clk_mgt_val & PRCM_CLK_MGT_CLK38) + return ROOT_CLOCK_RATE; + clk_mgt_val &= PRCM_CLK_MGT_CLKPLLSW_MASK; + if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_SOC0) + return pll_rate(PRCM_PLLSOC0_FREQ, ROOT_CLOCK_RATE, branch); + else if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_SOC1) + return pll_rate(PRCM_PLLSOC1_FREQ, ROOT_CLOCK_RATE, branch); + else if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_DDR) + return pll_rate(PRCM_PLLDDR_FREQ, ROOT_CLOCK_RATE, branch); + else + return 0; +} + +static u32 clock_divider(unsigned long src_rate, unsigned long rate) +{ + u32 div; + + div = (src_rate / rate); + if (div == 0) + return 1; + if (rate < (src_rate / div)) + div++; + return div; +} + +static long round_clock_rate(u8 clock, unsigned long rate) +{ + u32 val; + u32 div; + unsigned long src_rate; + long rounded_rate; + + val = readl(clk_mgt[clock].reg); + src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), + clk_mgt[clock].branch); + div = clock_divider(src_rate, rate); + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div) { + if (div > 2) + div = 2; + } else { + div = 1; + } + } else if ((clock == PRCMU_SGACLK) && (div == 3)) { + u64 r = (src_rate * 10); + + (void)do_div(r, 25); + if (r <= rate) + return (unsigned long)r; + } + rounded_rate = (src_rate / min(div, (u32)31)); + + return rounded_rate; +} + +#define MIN_PLL_VCO_RATE 600000000ULL +#define MAX_PLL_VCO_RATE 1680640000ULL + +static long round_plldsi_rate(unsigned long rate) +{ + long rounded_rate = 0; + unsigned long src_rate; + unsigned long rem; + u32 r; + + src_rate = clock_rate(PRCMU_HDMICLK); + rem = rate; + + for (r = 7; (rem > 0) && (r > 0); r--) { + u64 d; + + d = (r * rate); + (void)do_div(d, src_rate); + if (d < 6) + d = 6; + else if (d > 255) + d = 255; + d *= src_rate; + if (((2 * d) < (r * MIN_PLL_VCO_RATE)) || + ((r * MAX_PLL_VCO_RATE) < (2 * d))) + continue; + (void)do_div(d, r); + if (rate < d) { + if (rounded_rate == 0) + rounded_rate = (long)d; + break; + } + if ((rate - d) < rem) { + rem = (rate - d); + rounded_rate = (long)d; + } + } + return rounded_rate; +} + +static long round_dsiclk_rate(unsigned long rate) +{ + u32 div; + unsigned long src_rate; + long rounded_rate; + + src_rate = pll_rate(PRCM_PLLDSI_FREQ, clock_rate(PRCMU_HDMICLK), + PLL_RAW); + div = clock_divider(src_rate, rate); + rounded_rate = (src_rate / ((div > 2) ? 4 : div)); + + return rounded_rate; +} + +static long round_dsiescclk_rate(unsigned long rate) +{ + u32 div; + unsigned long src_rate; + long rounded_rate; + + src_rate = clock_rate(PRCMU_TVCLK); + div = clock_divider(src_rate, rate); + rounded_rate = (src_rate / min(div, (u32)255)); + + return rounded_rate; +} + +long prcmu_round_clock_rate(u8 clock, unsigned long rate) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + return round_clock_rate(clock, rate); + else if (clock == PRCMU_PLLDSI) + return round_plldsi_rate(rate); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + return round_dsiclk_rate(rate); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + return round_dsiescclk_rate(rate); + else + return (long)prcmu_clock_rate(clock); +} + +static void set_clock_rate(u8 clock, unsigned long rate) +{ + u32 val; + u32 div; + unsigned long src_rate; + unsigned long flags; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(clk_mgt[clock].reg); + src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), + clk_mgt[clock].branch); + div = clock_divider(src_rate, rate); + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div) { + if (div > 1) + val |= PRCM_CLK_MGT_CLK38DIV; + else + val &= ~PRCM_CLK_MGT_CLK38DIV; + } + } else if (clock == PRCMU_SGACLK) { + val &= ~(PRCM_CLK_MGT_CLKPLLDIV_MASK | + PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN); + if (div == 3) { + u64 r = (src_rate * 10); + + (void)do_div(r, 25); + if (r <= rate) { + val |= PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN; + div = 0; + } + } + val |= min(div, (u32)31); + } else { + val &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + val |= min(div, (u32)31); + } + writel(val, clk_mgt[clock].reg); + + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} + +static int set_plldsi_rate(unsigned long rate) +{ + unsigned long src_rate; + unsigned long rem; + u32 pll_freq = 0; + u32 r; + + src_rate = clock_rate(PRCMU_HDMICLK); + rem = rate; + + for (r = 7; (rem > 0) && (r > 0); r--) { + u64 d; + u64 hwrate; + + d = (r * rate); + (void)do_div(d, src_rate); + if (d < 6) + d = 6; + else if (d > 255) + d = 255; + hwrate = (d * src_rate); + if (((2 * hwrate) < (r * MIN_PLL_VCO_RATE)) || + ((r * MAX_PLL_VCO_RATE) < (2 * hwrate))) + continue; + (void)do_div(hwrate, r); + if (rate < hwrate) { + if (pll_freq == 0) + pll_freq = (((u32)d << PRCM_PLL_FREQ_D_SHIFT) | + (r << PRCM_PLL_FREQ_R_SHIFT)); + break; + } + if ((rate - hwrate) < rem) { + rem = (rate - hwrate); + pll_freq = (((u32)d << PRCM_PLL_FREQ_D_SHIFT) | + (r << PRCM_PLL_FREQ_R_SHIFT)); + } + } + if (pll_freq == 0) + return -EINVAL; + + pll_freq |= (1 << PRCM_PLL_FREQ_N_SHIFT); + writel(pll_freq, PRCM_PLLDSI_FREQ); + + return 0; +} + +static void set_dsiclk_rate(u8 n, unsigned long rate) +{ + u32 val; + u32 div; + + div = clock_divider(pll_rate(PRCM_PLLDSI_FREQ, + clock_rate(PRCMU_HDMICLK), PLL_RAW), rate); + + dsiclk[n].divsel = (div == 1) ? PRCM_DSI_PLLOUT_SEL_PHI : + (div == 2) ? PRCM_DSI_PLLOUT_SEL_PHI_2 : + /* else */ PRCM_DSI_PLLOUT_SEL_PHI_4; + + val = readl(PRCM_DSI_PLLOUT_SEL); + val &= ~dsiclk[n].divsel_mask; + val |= (dsiclk[n].divsel << dsiclk[n].divsel_shift); + writel(val, PRCM_DSI_PLLOUT_SEL); +} + +static void set_dsiescclk_rate(u8 n, unsigned long rate) +{ + u32 val; + u32 div; + + div = clock_divider(clock_rate(PRCMU_TVCLK), rate); + val = readl(PRCM_DSITVCLK_DIV); + val &= ~dsiescclk[n].div_mask; + val |= (min(div, (u32)255) << dsiescclk[n].div_shift); + writel(val, PRCM_DSITVCLK_DIV); +} + +int prcmu_set_clock_rate(u8 clock, unsigned long rate) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + set_clock_rate(clock, rate); + else if (clock == PRCMU_PLLDSI) + return set_plldsi_rate(rate); + else if ((clock == PRCMU_DSI0CLK) || (clock == PRCMU_DSI1CLK)) + set_dsiclk_rate((clock - PRCMU_DSI0CLK), rate); + else if ((PRCMU_DSI0ESCCLK <= clock) && (clock <= PRCMU_DSI2ESCCLK)) + set_dsiescclk_rate((clock - PRCMU_DSI0ESCCLK), rate); + return 0; +} + int db8500_prcmu_config_esram0_deep_sleep(u8 state) { if ((state > ESRAM0_DEEP_SLEEP_STATE_RET) || @@ -1476,7 +2070,7 @@ int db8500_prcmu_config_esram0_deep_sleep(u8 state) return 0; } -int prcmu_config_hotdog(u8 threshold) +int db8500_prcmu_config_hotdog(u8 threshold) { mutex_lock(&mb4_transfer.lock); @@ -1494,7 +2088,7 @@ int prcmu_config_hotdog(u8 threshold) return 0; } -int prcmu_config_hotmon(u8 low, u8 high) +int db8500_prcmu_config_hotmon(u8 low, u8 high) { mutex_lock(&mb4_transfer.lock); @@ -1533,7 +2127,7 @@ static int config_hot_period(u16 val) return 0; } -int prcmu_start_temp_sense(u16 cycles32k) +int db8500_prcmu_start_temp_sense(u16 cycles32k) { if (cycles32k == 0xFFFF) return -EINVAL; @@ -1541,7 +2135,7 @@ int prcmu_start_temp_sense(u16 cycles32k) return config_hot_period(cycles32k); } -int prcmu_stop_temp_sense(void) +int db8500_prcmu_stop_temp_sense(void) { return config_hot_period(0xFFFF); } @@ -1570,7 +2164,7 @@ static int prcmu_a9wdog(u8 cmd, u8 d0, u8 d1, u8 d2, u8 d3) } -int prcmu_config_a9wdog(u8 num, bool sleep_auto_off) +int db8500_prcmu_config_a9wdog(u8 num, bool sleep_auto_off) { BUG_ON(num == 0 || num > 0xf); return prcmu_a9wdog(MB4H_A9WDOG_CONF, num, 0, 0, @@ -1578,17 +2172,17 @@ int prcmu_config_a9wdog(u8 num, bool sleep_auto_off) A9WDOG_AUTO_OFF_DIS); } -int prcmu_enable_a9wdog(u8 id) +int db8500_prcmu_enable_a9wdog(u8 id) { return prcmu_a9wdog(MB4H_A9WDOG_EN, id, 0, 0, 0); } -int prcmu_disable_a9wdog(u8 id) +int db8500_prcmu_disable_a9wdog(u8 id) { return prcmu_a9wdog(MB4H_A9WDOG_DIS, id, 0, 0, 0); } -int prcmu_kick_a9wdog(u8 id) +int db8500_prcmu_kick_a9wdog(u8 id) { return prcmu_a9wdog(MB4H_A9WDOG_KICK, id, 0, 0, 0); } @@ -1596,16 +2190,8 @@ int prcmu_kick_a9wdog(u8 id) /* * timeout is 28 bit, in ms. */ -#define MAX_WATCHDOG_TIMEOUT 131000 -int prcmu_load_a9wdog(u8 id, u32 timeout) +int db8500_prcmu_load_a9wdog(u8 id, u32 timeout) { - if (timeout > MAX_WATCHDOG_TIMEOUT) - /* - * Due to calculation bug in prcmu fw, timeouts - * can't be bigger than 131 seconds. - */ - return -EINVAL; - return prcmu_a9wdog(MB4H_A9WDOG_LOAD, (id & A9WDOG_ID_MASK) | /* @@ -1619,41 +2205,6 @@ int prcmu_load_a9wdog(u8 id, u32 timeout) } /** - * prcmu_set_clock_divider() - Configure the clock divider. - * @clock: The clock for which the request is made. - * @divider: The clock divider. (< 32) - * - * This function should only be used by the clock implementation. - * Do not use it from any other place! - */ -int prcmu_set_clock_divider(u8 clock, u8 divider) -{ - u32 val; - unsigned long flags; - - if ((clock >= PRCMU_NUM_REG_CLOCKS) || (divider < 1) || (31 < divider)) - return -EINVAL; - - spin_lock_irqsave(&clk_mgt_lock, flags); - - /* Grab the HW semaphore. */ - while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) - cpu_relax(); - - val = readl(_PRCMU_BASE + clk_mgt[clock].offset); - val &= ~(PRCM_CLK_MGT_CLKPLLDIV_MASK); - val |= (u32)divider; - writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); - - /* Release the HW semaphore. */ - writel(0, PRCM_SEM); - - spin_unlock_irqrestore(&clk_mgt_lock, flags); - - return 0; -} - -/** * prcmu_abb_read() - Read register value(s) from the ABB. * @slave: The I2C slave address. * @reg: The (start) register address. @@ -1850,9 +2401,9 @@ u16 db8500_prcmu_get_reset_code(void) } /** - * prcmu_reset_modem - ask the PRCMU to reset modem + * db8500_prcmu_reset_modem - ask the PRCMU to reset modem */ -void prcmu_modem_reset(void) +void db8500_prcmu_modem_reset(void) { mutex_lock(&mb1_transfer.lock); @@ -2099,6 +2650,22 @@ static struct irq_chip prcmu_irq_chip = { .irq_unmask = prcmu_irq_unmask, }; +static char *fw_project_name(u8 project) +{ + switch (project) { + case PRCMU_FW_PROJECT_U8500: + return "U8500"; + case PRCMU_FW_PROJECT_U8500_C2: + return "U8500 C2"; + case PRCMU_FW_PROJECT_U9500: + return "U9500"; + case PRCMU_FW_PROJECT_U9500_C2: + return "U9500 C2"; + default: + return "Unknown"; + } +} + void __init db8500_prcmu_early_init(void) { unsigned int i; @@ -2108,11 +2675,13 @@ void __init db8500_prcmu_early_init(void) if (tcpm_base != NULL) { u32 version; version = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET); - prcmu_version.project_number = version & 0xFF; - prcmu_version.api_version = (version >> 8) & 0xFF; - prcmu_version.func_version = (version >> 16) & 0xFF; - prcmu_version.errata = (version >> 24) & 0xFF; - pr_info("PRCMU firmware version %d.%d.%d\n", + fw_info.version.project = version & 0xFF; + fw_info.version.api_version = (version >> 8) & 0xFF; + fw_info.version.func_version = (version >> 16) & 0xFF; + fw_info.version.errata = (version >> 24) & 0xFF; + fw_info.valid = true; + pr_info("PRCMU firmware: %s, version %d.%d.%d\n", + fw_project_name(fw_info.version.project), (version >> 8) & 0xFF, (version >> 16) & 0xFF, (version >> 24) & 0xFF); iounmap(tcpm_base); @@ -2130,6 +2699,7 @@ void __init db8500_prcmu_early_init(void) init_completion(&mb0_transfer.ac_wake_work); mutex_init(&mb1_transfer.lock); init_completion(&mb1_transfer.work); + mb1_transfer.ape_opp = APE_NO_CHANGE; mutex_init(&mb2_transfer.lock); init_completion(&mb2_transfer.work); spin_lock_init(&mb2_transfer.auto_pm_lock); @@ -2181,24 +2751,22 @@ static struct regulator_consumer_supply db8500_vape_consumers[] = { REGULATOR_SUPPLY("vcore", "sdi4"), REGULATOR_SUPPLY("v-dma", "dma40.0"), REGULATOR_SUPPLY("v-ape", "ab8500-usb.0"), - /* "v-uart" changed to "vcore" in the mainline kernel */ - REGULATOR_SUPPLY("vcore", "uart0"), - REGULATOR_SUPPLY("vcore", "uart1"), - REGULATOR_SUPPLY("vcore", "uart2"), + REGULATOR_SUPPLY("v-uart", "uart0"), + REGULATOR_SUPPLY("v-uart", "uart1"), + REGULATOR_SUPPLY("v-uart", "uart2"), REGULATOR_SUPPLY("v-ape", "nmk-ske-keypad.0"), + REGULATOR_SUPPLY("v-hsi", "ste_hsi.0"), + REGULATOR_SUPPLY("vddvario", "smsc911x.0"), }; static struct regulator_consumer_supply db8500_vsmps2_consumers[] = { - /* CG2900 and CW1200 power to off-chip peripherals */ - REGULATOR_SUPPLY("gbf_1v8", "cg2900-uart.0"), - REGULATOR_SUPPLY("wlan_1v8", "cw1200.0"), REGULATOR_SUPPLY("musb_1v8", "ab8500-usb.0"), /* AV8100 regulator */ REGULATOR_SUPPLY("hdmi_1v8", "0-0070"), }; static struct regulator_consumer_supply db8500_b2r2_mcde_consumers[] = { - REGULATOR_SUPPLY("vsupply", "b2r2.0"), + REGULATOR_SUPPLY("vsupply", "b2r2_bus"), REGULATOR_SUPPLY("vsupply", "mcde"), }; @@ -2235,6 +2803,7 @@ static struct regulator_consumer_supply db8500_esram12_consumers[] = { static struct regulator_consumer_supply db8500_esram34_consumers[] = { REGULATOR_SUPPLY("v-esram34", "mcde"), REGULATOR_SUPPLY("esram34", "cm_control"), + REGULATOR_SUPPLY("lcla_esram", "dma40.0"), }; static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { @@ -2291,7 +2860,7 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { }, }, [DB8500_REGULATOR_SWITCH_SVAMMDSP] = { - .supply_regulator = "db8500-vape", + /* dependency to u8500-vape is handled outside regulator framework */ .constraints = { .name = "db8500-sva-mmdsp", .valid_ops_mask = REGULATOR_CHANGE_STATUS, @@ -2307,7 +2876,7 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { }, }, [DB8500_REGULATOR_SWITCH_SVAPIPE] = { - .supply_regulator = "db8500-vape", + /* dependency to u8500-vape is handled outside regulator framework */ .constraints = { .name = "db8500-sva-pipe", .valid_ops_mask = REGULATOR_CHANGE_STATUS, @@ -2316,7 +2885,7 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .num_consumer_supplies = ARRAY_SIZE(db8500_svapipe_consumers), }, [DB8500_REGULATOR_SWITCH_SIAMMDSP] = { - .supply_regulator = "db8500-vape", + /* dependency to u8500-vape is handled outside regulator framework */ .constraints = { .name = "db8500-sia-mmdsp", .valid_ops_mask = REGULATOR_CHANGE_STATUS, @@ -2331,7 +2900,7 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { }, }, [DB8500_REGULATOR_SWITCH_SIAPIPE] = { - .supply_regulator = "db8500-vape", + /* dependency to u8500-vape is handled outside regulator framework */ .constraints = { .name = "db8500-sia-pipe", .valid_ops_mask = REGULATOR_CHANGE_STATUS, @@ -2359,7 +2928,10 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .num_consumer_supplies = ARRAY_SIZE(db8500_b2r2_mcde_consumers), }, [DB8500_REGULATOR_SWITCH_ESRAM12] = { - .supply_regulator = "db8500-vape", + /* + * esram12 is set in retention and supplied by Vsafe when Vape is off, + * no need to hold Vape + */ .constraints = { .name = "db8500-esram12", .valid_ops_mask = REGULATOR_CHANGE_STATUS, @@ -2374,7 +2946,10 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { }, }, [DB8500_REGULATOR_SWITCH_ESRAM34] = { - .supply_regulator = "db8500-vape", + /* + * esram34 is set in retention and supplied by Vsafe when Vape is off, + * no need to hold Vape + */ .constraints = { .name = "db8500-esram34", .valid_ops_mask = REGULATOR_CHANGE_STATUS, diff --git a/drivers/mfd/dbx500-prcmu-regs.h b/drivers/mfd/dbx500-prcmu-regs.h index ec22e9f15d3..4c5c2478c05 100644 --- a/drivers/mfd/dbx500-prcmu-regs.h +++ b/drivers/mfd/dbx500-prcmu-regs.h @@ -17,41 +17,41 @@ #define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) -#define PRCM_SVACLK_MGT_OFF 0x008 -#define PRCM_SIACLK_MGT_OFF 0x00C -#define PRCM_SGACLK_MGT_OFF 0x014 -#define PRCM_UARTCLK_MGT_OFF 0x018 -#define PRCM_MSP02CLK_MGT_OFF 0x01C -#define PRCM_I2CCLK_MGT_OFF 0x020 -#define PRCM_SDMMCCLK_MGT_OFF 0x024 -#define PRCM_SLIMCLK_MGT_OFF 0x028 -#define PRCM_PER1CLK_MGT_OFF 0x02C -#define PRCM_PER2CLK_MGT_OFF 0x030 -#define PRCM_PER3CLK_MGT_OFF 0x034 -#define PRCM_PER5CLK_MGT_OFF 0x038 -#define PRCM_PER6CLK_MGT_OFF 0x03C -#define PRCM_PER7CLK_MGT_OFF 0x040 -#define PRCM_PWMCLK_MGT_OFF 0x044 /* for DB5500 */ -#define PRCM_IRDACLK_MGT_OFF 0x048 /* for DB5500 */ -#define PRCM_IRRCCLK_MGT_OFF 0x04C /* for DB5500 */ -#define PRCM_LCDCLK_MGT_OFF 0x044 -#define PRCM_BMLCLK_MGT_OFF 0x04C -#define PRCM_HSITXCLK_MGT_OFF 0x050 -#define PRCM_HSIRXCLK_MGT_OFF 0x054 -#define PRCM_HDMICLK_MGT_OFF 0x058 -#define PRCM_APEATCLK_MGT_OFF 0x05C -#define PRCM_APETRACECLK_MGT_OFF 0x060 -#define PRCM_MCDECLK_MGT_OFF 0x064 -#define PRCM_IPI2CCLK_MGT_OFF 0x068 -#define PRCM_DSIALTCLK_MGT_OFF 0x06C -#define PRCM_DMACLK_MGT_OFF 0x074 -#define PRCM_B2R2CLK_MGT_OFF 0x078 -#define PRCM_TVCLK_MGT_OFF 0x07C -#define PRCM_UNIPROCLK_MGT_OFF 0x278 -#define PRCM_SSPCLK_MGT_OFF 0x280 -#define PRCM_RNGCLK_MGT_OFF 0x284 -#define PRCM_UICCCLK_MGT_OFF 0x27C -#define PRCM_MSP1CLK_MGT_OFF 0x288 +#define PRCM_CLK_MGT(_offset) (void __iomem *)(IO_ADDRESS(U8500_PRCMU_BASE) \ + + _offset) +#define PRCM_ACLK_MGT PRCM_CLK_MGT(0x004) +#define PRCM_SVACLK_MGT PRCM_CLK_MGT(0x008) +#define PRCM_SIACLK_MGT PRCM_CLK_MGT(0x00C) +#define PRCM_SGACLK_MGT PRCM_CLK_MGT(0x014) +#define PRCM_UARTCLK_MGT PRCM_CLK_MGT(0x018) +#define PRCM_MSP02CLK_MGT PRCM_CLK_MGT(0x01C) +#define PRCM_I2CCLK_MGT PRCM_CLK_MGT(0x020) +#define PRCM_SDMMCCLK_MGT PRCM_CLK_MGT(0x024) +#define PRCM_SLIMCLK_MGT PRCM_CLK_MGT(0x028) +#define PRCM_PER1CLK_MGT PRCM_CLK_MGT(0x02C) +#define PRCM_PER2CLK_MGT PRCM_CLK_MGT(0x030) +#define PRCM_PER3CLK_MGT PRCM_CLK_MGT(0x034) +#define PRCM_PER5CLK_MGT PRCM_CLK_MGT(0x038) +#define PRCM_PER6CLK_MGT PRCM_CLK_MGT(0x03C) +#define PRCM_PER7CLK_MGT PRCM_CLK_MGT(0x040) +#define PRCM_LCDCLK_MGT PRCM_CLK_MGT(0x044) +#define PRCM_BMLCLK_MGT PRCM_CLK_MGT(0x04C) +#define PRCM_HSITXCLK_MGT PRCM_CLK_MGT(0x050) +#define PRCM_HSIRXCLK_MGT PRCM_CLK_MGT(0x054) +#define PRCM_HDMICLK_MGT PRCM_CLK_MGT(0x058) +#define PRCM_APEATCLK_MGT PRCM_CLK_MGT(0x05C) +#define PRCM_APETRACECLK_MGT PRCM_CLK_MGT(0x060) +#define PRCM_MCDECLK_MGT PRCM_CLK_MGT(0x064) +#define PRCM_IPI2CCLK_MGT PRCM_CLK_MGT(0x068) +#define PRCM_DSIALTCLK_MGT PRCM_CLK_MGT(0x06C) +#define PRCM_DMACLK_MGT PRCM_CLK_MGT(0x074) +#define PRCM_B2R2CLK_MGT PRCM_CLK_MGT(0x078) +#define PRCM_TVCLK_MGT PRCM_CLK_MGT(0x07C) +#define PRCM_UNIPROCLK_MGT PRCM_CLK_MGT(0x278) +#define PRCM_SSPCLK_MGT PRCM_CLK_MGT(0x280) +#define PRCM_RNGCLK_MGT PRCM_CLK_MGT(0x284) +#define PRCM_UICCCLK_MGT PRCM_CLK_MGT(0x27C) +#define PRCM_MSP1CLK_MGT PRCM_CLK_MGT(0x288) #define PRCM_ARM_PLLDIVPS (_PRCMU_BASE + 0x118) #define PRCM_ARM_PLLDIVPS_ARM_BRM_RATE 0x3f @@ -131,20 +131,58 @@ #define PRCM_MMIP_LS_CLAMP_SET (_PRCMU_BASE + 0x420) #define PRCM_MMIP_LS_CLAMP_CLR (_PRCMU_BASE + 0x424) +#define PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMP BIT(11) +#define PRCM_MMIP_LS_CLAMP_DSIPLL_CLAMPI BIT(22) + /* PRCMU clock/PLL/reset registers */ +#define PRCM_PLLSOC0_FREQ (_PRCMU_BASE + 0x080) +#define PRCM_PLLSOC1_FREQ (_PRCMU_BASE + 0x084) +#define PRCM_PLLDDR_FREQ (_PRCMU_BASE + 0x08C) +#define PRCM_PLL_FREQ_D_SHIFT 0 +#define PRCM_PLL_FREQ_D_MASK BITS(0, 7) +#define PRCM_PLL_FREQ_N_SHIFT 8 +#define PRCM_PLL_FREQ_N_MASK BITS(8, 13) +#define PRCM_PLL_FREQ_R_SHIFT 16 +#define PRCM_PLL_FREQ_R_MASK BITS(16, 18) +#define PRCM_PLL_FREQ_SELDIV2 BIT(24) +#define PRCM_PLL_FREQ_DIV2EN BIT(25) + #define PRCM_PLLDSI_FREQ (_PRCMU_BASE + 0x500) #define PRCM_PLLDSI_ENABLE (_PRCMU_BASE + 0x504) #define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) -#define PRCM_LCDCLK_MGT (_PRCMU_BASE + PRCM_LCDCLK_MGT_OFF) -#define PRCM_MCDECLK_MGT (_PRCMU_BASE + PRCM_MCDECLK_MGT_OFF) -#define PRCM_HDMICLK_MGT (_PRCMU_BASE + PRCM_HDMICLK_MGT_OFF) -#define PRCM_TVCLK_MGT (_PRCMU_BASE + PRCM_TVCLK_MGT_OFF) #define PRCM_DSI_PLLOUT_SEL (_PRCMU_BASE + 0x530) #define PRCM_DSITVCLK_DIV (_PRCMU_BASE + 0x52C) #define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) #define PRCM_APE_RESETN_SET (_PRCMU_BASE + 0x1E4) #define PRCM_APE_RESETN_CLR (_PRCMU_BASE + 0x1E8) +#define PRCM_PLLDSI_ENABLE_PRCM_PLLDSI_ENABLE BIT(0) + +#define PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP10 BIT(0) +#define PRCM_PLLDSI_LOCKP_PRCM_PLLDSI_LOCKP3 BIT(1) + +#define PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_SHIFT 0 +#define PRCM_DSI_PLLOUT_SEL_DSI0_PLLOUT_DIVSEL_MASK BITS(0, 2) +#define PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_SHIFT 8 +#define PRCM_DSI_PLLOUT_SEL_DSI1_PLLOUT_DIVSEL_MASK BITS(8, 10) + +#define PRCM_DSI_PLLOUT_SEL_OFF 0 +#define PRCM_DSI_PLLOUT_SEL_PHI 1 +#define PRCM_DSI_PLLOUT_SEL_PHI_2 2 +#define PRCM_DSI_PLLOUT_SEL_PHI_4 3 + +#define PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_SHIFT 0 +#define PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_DIV_MASK BITS(0, 7) +#define PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_SHIFT 8 +#define PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_DIV_MASK BITS(8, 15) +#define PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_SHIFT 16 +#define PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_DIV_MASK BITS(16, 23) +#define PRCM_DSITVCLK_DIV_DSI0_ESC_CLK_EN BIT(24) +#define PRCM_DSITVCLK_DIV_DSI1_ESC_CLK_EN BIT(25) +#define PRCM_DSITVCLK_DIV_DSI2_ESC_CLK_EN BIT(26) + +#define PRCM_APE_RESETN_DSIPLL_RESETN BIT(14) + #define PRCM_CLKOCR (_PRCMU_BASE + 0x1CC) #define PRCM_CLKOCR_CLKOUT0_REF_CLK (1 << 0) #define PRCM_CLKOCR_CLKOUT0_MASK BITS(0, 13) @@ -183,12 +221,21 @@ #define PRCM_CLKOCR_CLKOSEL1_MASK BITS(22, 24) #define PRCM_CLKOCR_CLK1TYPE BIT(28) -#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) -#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) -#define PRCM_CLK_MGT_CLKEN BIT(8) +#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) +#define PRCM_CLK_MGT_CLKPLLSW_SOC0 BIT(5) +#define PRCM_CLK_MGT_CLKPLLSW_SOC1 BIT(6) +#define PRCM_CLK_MGT_CLKPLLSW_DDR BIT(7) +#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) +#define PRCM_CLK_MGT_CLKEN BIT(8) +#define PRCM_CLK_MGT_CLK38 BIT(9) +#define PRCM_CLK_MGT_CLK38DIV BIT(11) +#define PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN BIT(12) /* GPIOCR register */ #define PRCM_GPIOCR_SPI2_SELECT BIT(23) +#define PRCM_GPIOCR_DBG_STM_MOD_SELECT BIT(11) +#define PRCM_GPIOCR_DBG_STM_APE_SELECT BIT(9) +#define PRCM_GPIOCR_DBG_UARTMOD_SELECT BIT(0) #define PRCM_DDR_SUBSYS_APE_MINBW (_PRCMU_BASE + 0x438) #define PRCM_CGATING_BYPASS (_PRCMU_BASE + 0x134) diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index e07947e56b2..71b30b93f17 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -739,7 +739,7 @@ static irqreturn_t stmpe_irq(int irq, void *data) ret = stmpe_block_read(stmpe, israddr, num, isr); if (ret < 0) return IRQ_NONE; - +back: for (i = 0; i < num; i++) { int bank = num - i - 1; u8 status = isr[i]; @@ -761,6 +761,22 @@ static irqreturn_t stmpe_irq(int irq, void *data) stmpe_reg_write(stmpe, israddr + i, clear); } + /* + It may happen that on the first status read interrupt + sources may not showup, so read one more time. + */ + ret = stmpe_block_read(stmpe, israddr, num, isr); + if (ret >= 0) { + for (i = 0; i < num; i++) { + int bank = num - i - 1; + u8 status = isr[i]; + + status &= stmpe->ier[bank]; + if (status) + goto back; + } + } + return IRQ_HANDLED; } diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c new file mode 100644 index 00000000000..91211f29623 --- /dev/null +++ b/drivers/mfd/tc35892.c @@ -0,0 +1,503 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tc35892.h> + +#define TC35892_CLKMODE_MODCTL_SLEEP 0x0 +#define TC35892_CLKMODE_MODCTL_OPERATION (1 << 0) + +/** + * tc35892_reg_read() - read a single TC35892 register + * @tc35892: Device to read from + * @reg: Register to read + */ +int tc35892_reg_read(struct tc35892 *tc35892, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(tc35892->i2c, reg); + if (ret < 0) + dev_err(tc35892->dev, "failed to read reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_read); + +/** + * tc35892_reg_read() - write a single TC35892 register + * @tc35892: Device to write to + * @reg: Register to read + * @data: Value to write + */ +int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(tc35892->i2c, reg, data); + if (ret < 0) + dev_err(tc35892->dev, "failed to write reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_write); + +/** + * tc35892_block_read() - read multiple TC35892 registers + * @tc35892: Device to read from + * @reg: First register + * @length: Number of registers + * @values: Buffer to write to + */ +int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, u8 *values) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(tc35892->i2c, reg, length, values); + if (ret < 0) + dev_err(tc35892->dev, "failed to read regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_read); + +/** + * tc35892_block_write() - write multiple TC35892 registers + * @tc35892: Device to write to + * @reg: First register + * @length: Number of registers + * @values: Values to write + */ +int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(tc35892->i2c, reg, length, + values); + if (ret < 0) + dev_err(tc35892->dev, "failed to write regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_write); + +/** + * tc35892_set_bits() - set the value of a bitfield in a TC35892 register + * @tc35892: Device to write to + * @reg: Register to write + * @mask: Mask of bits to set + * @values: Value to set + */ +int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val) +{ + int ret; + + mutex_lock(&tc35892->lock); + + ret = tc35892_reg_read(tc35892, reg); + if (ret < 0) + goto out; + + ret &= ~mask; + ret |= val; + + ret = tc35892_reg_write(tc35892, reg, ret); + +out: + mutex_unlock(&tc35892->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_set_bits); + +static struct resource gpio_resources[] = { + { + .start = TC35892_INT_GPIIRQ, + .end = TC35892_INT_GPIIRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell tc35892_devs[] = { + { + .name = "tc35892-gpio", + .num_resources = ARRAY_SIZE(gpio_resources), + .resources = &gpio_resources[0], + }, +}; + +static irqreturn_t tc35892_irq(int irq, void *data) +{ + struct tc35892 *tc35892 = data; + int status; + +again: + status = tc35892_reg_read(tc35892, TC35892_IRQST); + if (status < 0) + return IRQ_NONE; + + while (status) { + int bit = __ffs(status); + + handle_nested_irq(tc35892->irq_base + bit); + status &= ~(1 << bit); + } + + /* + * A dummy read or write (to any register) appears to be necessary to + * have the last interrupt clear (for example, GPIO IC write) take + * effect. In such a case, recheck for any interrupt which is still + * pending. + */ + status = tc35892_reg_read(tc35892, TC35892_IRQST); + if (status) + goto again; + + return IRQ_HANDLED; +} + +static void tc35892_irq_dummy(unsigned int irq) +{ + /* No mask/unmask at this level */ +} + +static struct irq_chip tc35892_irq_chip = { + .name = "tc35892", + .irq_mask = tc35892_irq_dummy, + .irq_unmask = tc35892_irq_dummy, +}; + +static int tc35892_irq_init(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { + irq_set_chip_data(irq, tc35892); + irq_set_chip_and_handler(irq, &tc35892_irq_chip, + handle_edge_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void tc35892_irq_remove(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); + } +} + +static int tc35892_chip_init(struct tc35892 *tc35892) +{ + int manf, ver, ret; + + manf = tc35892_reg_read(tc35892, TC35892_MANFCODE); + if (manf < 0) + return manf; + + ver = tc35892_reg_read(tc35892, TC35892_VERSION); + if (ver < 0) + return ver; + + if (manf != TC35892_MANFCODE_MAGIC) { + dev_err(tc35892->dev, "unknown manufacturer: %#x\n", manf); + return -EINVAL; + } + + dev_info(tc35892->dev, "manufacturer: %#x, version: %#x\n", manf, ver); + + /* + * Put everything except the IRQ module into reset; + * also spare the GPIO module for any pin initialization + * done during pre-kernel boot + */ + ret = tc35892_reg_write(tc35892, TC35892_RSTCTRL, + TC35892_RSTCTRL_TIMRST + | TC35892_RSTCTRL_ROTRST + | TC35892_RSTCTRL_KBDRST); + if (ret < 0) + return ret; + + /* Clear the reset interrupt. */ + return tc35892_reg_write(tc35892, TC35892_RSTINTCLR, 0x1); +} + +static int __devinit tc35892_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tc35892_platform_data *pdata = i2c->dev.platform_data; + struct tc35892 *tc35892; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EIO; + + tc35892 = kzalloc(sizeof(struct tc35892), GFP_KERNEL); + if (!tc35892) + return -ENOMEM; + + mutex_init(&tc35892->lock); + + tc35892->dev = &i2c->dev; + tc35892->i2c = i2c; + tc35892->pdata = pdata; + tc35892->irq_base = pdata->irq_base; + tc35892->num_gpio = id->driver_data; + + i2c_set_clientdata(i2c, tc35892); + + ret = tc35892_chip_init(tc35892); + if (ret) + goto out_free; + + ret = tc35892_irq_init(tc35892); + if (ret) + goto out_free; + + ret = request_threaded_irq(tc35892->i2c->irq, NULL, tc35892_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "tc35892", tc35892); + if (ret) { + dev_err(tc35892->dev, "failed to request IRQ: %d\n", ret); + goto out_removeirq; + } + + ret = mfd_add_devices(tc35892->dev, -1, tc35892_devs, + ARRAY_SIZE(tc35892_devs), NULL, + tc35892->irq_base); + if (ret) { + dev_err(tc35892->dev, "failed to add children\n"); + goto out_freeirq; + } + + return 0; + +out_freeirq: + free_irq(tc35892->i2c->irq, tc35892); +out_removeirq: + tc35892_irq_remove(tc35892); +out_free: + kfree(tc35892); + return ret; +} + +static int __devexit tc35892_remove(struct i2c_client *client) +{ + struct tc35892 *tc35892 = i2c_get_clientdata(client); + + mfd_remove_devices(tc35892->dev); + + free_irq(tc35892->i2c->irq, tc35892); + tc35892_irq_remove(tc35892); + + kfree(tc35892); + + return 0; +} + +#ifdef CONFIG_PM + +static u32 sleep_regs[] = { + TC35892_IOPC0_L, + TC35892_IOPC0_H, + TC35892_IOPC1_L, + TC35892_IOPC1_H, + TC35892_IOPC2_L, + TC35892_IOPC2_H, + TC35892_DRIVE0_L, + TC35892_DRIVE0_H, + TC35892_DRIVE1_L, + TC35892_DRIVE1_H, + TC35892_DRIVE2_L, + TC35892_DRIVE2_H, + TC35892_DRIVE3, + TC35892_GPIODATA0, + TC35892_GPIOMASK0, + TC35892_GPIODATA1, + TC35892_GPIOMASK1, + TC35892_GPIODATA2, + TC35892_GPIOMASK2, + TC35892_GPIODIR0, + TC35892_GPIODIR1, + TC35892_GPIODIR2, + TC35892_GPIOIE0, + TC35892_GPIOIE1, + TC35892_GPIOIE2, + TC35892_RSTCTRL, + TC35892_CLKCFG, +}; + +static u8 sleep_regs_val[] = { + 0x00, /* TC35892_IOPC0_L */ + 0x00, /* TC35892_IOPC0_H */ + 0x00, /* TC35892_IOPC1_L */ + 0x00, /* TC35892_IOPC1_H */ + 0x00, /* TC35892_IOPC2_L */ + 0x00, /* TC35892_IOPC2_H */ + 0xff, /* TC35892_DRIVE0_L */ + 0xff, /* TC35892_DRIVE0_H */ + 0xff, /* TC35892_DRIVE1_L */ + 0xff, /* TC35892_DRIVE1_H */ + 0xff, /* TC35892_DRIVE2_L */ + 0xff, /* TC35892_DRIVE2_H */ + 0x0f, /* TC35892_DRIVE3 */ + 0x80, /* TC35892_GPIODATA0 */ + 0x80, /* TC35892_GPIOMASK0 */ + 0x80, /* TC35892_GPIODATA1 */ + 0x80, /* TC35892_GPIOMASK1 */ + 0x06, /* TC35892_GPIODATA2 */ + 0x06, /* TC35892_GPIOMASK2 */ + 0xf0, /* TC35892_GPIODIR0 */ + 0xe0, /* TC35892_GPIODIR1 */ + 0xee, /* TC35892_GPIODIR2 */ + 0x0f, /* TC35892_GPIOIE0 */ + 0x1f, /* TC35892_GPIOIE1 */ + 0x11, /* TC35892_GPIOIE2 */ + 0x0f, /* TC35892_RSTCTRL */ + 0xb0 /* TC35892_CLKCFG */ + +}; + +static u8 sleep_regs_backup[ARRAY_SIZE(sleep_regs)]; + +static int tc35892_suspend(struct device *dev) +{ + struct tc35892 *tc35892 = dev_get_drvdata(dev); + struct i2c_client *client = tc35892->i2c; + int ret = 0; + int i, j; + int val; + + /* Put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) { + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + val = tc35892_reg_read(tc35892, + sleep_regs[i]); + if (val < 0) + goto out; + + sleep_regs_backup[i] = (u8) (val & 0xff); + } + + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_val[i]); + if (ret < 0) + goto fail; + + } + + ret = tc35892_reg_write(tc35892, + TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_SLEEP); + } +out: + return ret; +fail: + for (j = 0; j <= i; j++) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_backup[i]); + if (ret < 0) + break; + } + return ret; +} + +static int tc35892_resume(struct device *dev) +{ + struct tc35892 *tc35892 = dev_get_drvdata(dev); + struct i2c_client *client = tc35892->i2c; + int ret = 0; + int i; + + /* Enable the system into operation */ + if (!device_may_wakeup(&client->dev)) + { + ret = tc35892_reg_write(tc35892, + TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_OPERATION); + if (ret < 0) + goto out; + + for (i = ARRAY_SIZE(sleep_regs) - 1; i >= 0; i--) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_backup[i]); + /* Not much to do here if we fail */ + if (ret < 0) + break; + } + } +out: + return ret; +} + +static const struct dev_pm_ops tc35892_dev_pm_ops = { + .suspend = tc35892_suspend, + .resume = tc35892_resume, +}; +#endif + +static const struct i2c_device_id tc35892_id[] = { + { "tc35892", 24 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc35892_id); + +static struct i2c_driver tc35892_driver = { + .driver.name = "tc35892", + .driver.owner = THIS_MODULE, +#ifdef CONFIG_PM + .driver.pm = &tc35892_dev_pm_ops, +#endif + .probe = tc35892_probe, + .remove = __devexit_p(tc35892_remove), + .id_table = tc35892_id, +}; + +static int __init tc35892_init(void) +{ + return i2c_add_driver(&tc35892_driver); +} +subsys_initcall(tc35892_init); + +static void __exit tc35892_exit(void) +{ + i2c_del_driver(&tc35892_driver); +} +module_exit(tc35892_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TC35892 MFD core driver"); +MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index de979742c6f..0e79fe2d214 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -358,16 +358,114 @@ static int __devexit tc3589x_remove(struct i2c_client *client) } #ifdef CONFIG_PM + +static u32 sleep_regs[] = { + TC3589x_IOPC0_L, + TC3589x_IOPC0_H, + TC3589x_IOPC1_L, + TC3589x_IOPC1_H, + TC3589x_IOPC2_L, + TC3589x_IOPC2_H, + TC3589x_DRIVE0_L, + TC3589x_DRIVE0_H, + TC3589x_DRIVE1_L, + TC3589x_DRIVE1_H, + TC3589x_DRIVE2_L, + TC3589x_DRIVE2_H, + TC3589x_DRIVE3, + TC3589x_GPIODATA0, + TC3589x_GPIOMASK0, + TC3589x_GPIODATA1, + TC3589x_GPIOMASK1, + TC3589x_GPIODATA2, + TC3589x_GPIOMASK2, + TC3589x_GPIODIR0, + TC3589x_GPIODIR1, + TC3589x_GPIODIR2, + TC3589x_GPIOIE0, + TC3589x_GPIOIE1, + TC3589x_GPIOIE2, + TC3589x_RSTCTRL, + TC3589x_CLKCFG, +}; + +static u8 sleep_regs_val[] = { + 0x00, /* TC3589x_IOPC0_L */ + 0x00, /* TC3589x_IOPC0_H */ + 0x00, /* TC3589x_IOPC1_L */ + 0x00, /* TC3589x_IOPC1_H */ + 0x00, /* TC3589x_IOPC2_L */ + 0x00, /* TC3589x_IOPC2_H */ + 0xff, /* TC3589x_DRIVE0_L */ + 0xff, /* TC3589x_DRIVE0_H */ + 0xff, /* TC3589x_DRIVE1_L */ + 0xff, /* TC3589x_DRIVE1_H */ + 0xff, /* TC3589x_DRIVE2_L */ + 0xff, /* TC3589x_DRIVE2_H */ + 0x0f, /* TC3589x_DRIVE3 */ + 0x80, /* TC3589x_GPIODATA0 */ + 0x80, /* TC3589x_GPIOMASK0 */ + 0x80, /* TC3589x_GPIODATA1 */ + 0x80, /* TC3589x_GPIOMASK1 */ + 0x06, /* TC3589x_GPIODATA2 */ + 0x06, /* TC3589x_GPIOMASK2 */ + 0xf0, /* TC3589x_GPIODIR0 */ + 0xe0, /* TC3589x_GPIODIR1 */ + 0xee, /* TC3589x_GPIODIR2 */ + 0x0f, /* TC3589x_GPIOIE0 */ + 0x1f, /* TC3589x_GPIOIE1 */ + 0x11, /* TC3589x_GPIOIE2 */ + 0x0f, /* TC3589x_RSTCTRL */ + 0xb0 /* TC3589x_CLKCFG */ + +}; + +static u8 sleep_regs_backup[ARRAY_SIZE(sleep_regs)]; + static int tc3589x_suspend(struct device *dev) { struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; + int i, j; + int val; + + /* Put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) { + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + val = tc3589x_reg_read(tc3589x, + sleep_regs[i]); + if (val < 0) + goto out; + + sleep_regs_backup[i] = (u8) (val & 0xff); + } - /* put the system to sleep mode */ - if (!device_may_wakeup(&client->dev)) - ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, - TC3589x_CLKMODE_MODCTL_SLEEP); + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_val[i]); + if (ret < 0) + goto fail; + + } + + ret = tc3589x_reg_write(tc3589x, + TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_SLEEP); + } else { + enable_irq_wake(client->irq); + } +out: + return ret; +fail: + for (j = 0; j <= i; j++) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_backup[i]); + if (ret < 0) + break; + } return ret; } @@ -377,12 +475,29 @@ static int tc3589x_resume(struct device *dev) struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; + int i; - /* enable the system into operation */ + /* Enable the system into operation */ if (!device_may_wakeup(&client->dev)) - ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, - TC3589x_CLKMODE_MODCTL_OPERATION); - + { + ret = tc3589x_reg_write(tc3589x, + TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_OPERATION); + if (ret < 0) + goto out; + + for (i = ARRAY_SIZE(sleep_regs) - 1; i >= 0; i--) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_backup[i]); + /* Not much to do here if we fail */ + if (ret < 0) + break; + } + } else { + disable_irq_wake(client->irq); + } +out: return ret; } diff --git a/drivers/mfd/tps6105x.c b/drivers/mfd/tps6105x.c index a293b978e27..d7b9e0c60ea 100644 --- a/drivers/mfd/tps6105x.c +++ b/drivers/mfd/tps6105x.c @@ -195,6 +195,7 @@ static int __devinit tps6105x_probe(struct i2c_client *client, return 0; fail: + i2c_set_clientdata(client, NULL); kfree(tps6105x); return ret; } |