diff options
Diffstat (limited to 'drivers/mxc')
-rw-r--r-- | drivers/mxc/pmic/Kconfig | 18 | ||||
-rw-r--r-- | drivers/mxc/pmic/Makefile | 2 | ||||
-rw-r--r-- | drivers/mxc/pmic/core/Makefile | 10 | ||||
-rw-r--r-- | drivers/mxc/pmic/core/max8660.c | 149 | ||||
-rw-r--r-- | drivers/mxc/pmic/core/max8660.h | 45 | ||||
-rw-r--r-- | drivers/mxc/pmic/core/mc9sdz60.c | 111 | ||||
-rw-r--r-- | drivers/mxc/pmic/core/mc9sdz60.h | 69 | ||||
-rw-r--r-- | drivers/mxc/pmic/core/mcu_pmic_core.c | 540 | ||||
-rw-r--r-- | drivers/mxc/pmic/mc9sdz60/Kconfig | 29 | ||||
-rw-r--r-- | drivers/mxc/pmic/mc9sdz60/Makefile | 12 | ||||
-rw-r--r-- | drivers/mxc/pmic/mc9sdz60/mcu_pmic_event.c | 220 | ||||
-rw-r--r-- | drivers/mxc/pmic/mc9sdz60/mcu_pmic_gpio.c | 111 | ||||
-rw-r--r-- | drivers/mxc/pmic/mc9sdz60/mcu_pmic_power.c | 465 | ||||
-rw-r--r-- | drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c | 599 |
14 files changed, 2379 insertions, 1 deletions
diff --git a/drivers/mxc/pmic/Kconfig b/drivers/mxc/pmic/Kconfig index 4a99b56202fa..f3194343e5b3 100644 --- a/drivers/mxc/pmic/Kconfig +++ b/drivers/mxc/pmic/Kconfig @@ -13,9 +13,16 @@ config MXC_SPI_PMIC_CORE SPI should be providing the interface between the PMIC and the MCU. You must select the SPI driver support to enable this option. +config MXC_I2C_MCU_PMIC_CORE + tristate "MCU PMIC Protocol support (I2C interface)" + depends on ARCH_MXC + default n + ---help--- + This is the PMIC core/protocol driver for the MX35 3DS MCU PMIC + config MXC_PMIC boolean - default MXC_SPI_PMIC_CORE + default (MXC_SPI_PMIC_CORE || MXC_I2C_MCU_PMIC_CORE) config MXC_PMIC_CHARDEV tristate "MXC PMIC device interface" @@ -58,5 +65,14 @@ config MXC_PMIC_FIXARB a hardware modification to the RF Deck that connects the Sphinx card (with the sc55112 PMIC) to the MXC91131 EVB. +config MXC_PMIC_MC9SDZ60 + tristate "MC9sDZ60 Client Drivers" + depends on MXC_I2C_MCU_PMIC_CORE + depends on !ARCH_MXC91131 + default n + ---help--- + This is the MXC MC9sDZ60(MCU) PMIC client drivers support. + +source "drivers/mxc/pmic/mc9sdz60/Kconfig" endmenu diff --git a/drivers/mxc/pmic/Makefile b/drivers/mxc/pmic/Makefile index b4312cca7080..89fb6729a663 100644 --- a/drivers/mxc/pmic/Makefile +++ b/drivers/mxc/pmic/Makefile @@ -4,3 +4,5 @@ obj-y += core/ obj-$(CONFIG_MXC_PMIC_MC13783) += mc13783/ +obj-$(CONFIG_MXC_PMIC_MC9SDZ60) += mc9sdz60/ + diff --git a/drivers/mxc/pmic/core/Makefile b/drivers/mxc/pmic/core/Makefile index 2fe13f764c61..2945dd3d411a 100644 --- a/drivers/mxc/pmic/core/Makefile +++ b/drivers/mxc/pmic/core/Makefile @@ -2,6 +2,11 @@ # Makefile for the PMIC core drivers. # obj-$(CONFIG_MXC_SPI_PMIC_CORE) += pmic_core_spi_mod.o + +obj-$(CONFIG_MXC_I2C_MCU_PMIC_CORE) += pmic_core_i2c_mod.o + +pmic_core_i2c_mod-objs := pmic_external.o pmic_event.o mcu_pmic_core.o + obj-$(CONFIG_MXC_PMIC_CHARDEV) += pmic-dev.o pmic_core_spi_mod-objs := pmic_external.o pmic_event.o pmic_core_spi.o @@ -13,3 +18,8 @@ endif ifeq ($(CONFIG_MXC_PMIC_SC55112),y) pmic_core_spi_mod-objs += sc55112.o endif + +ifeq ($(CONFIG_MXC_PMIC_MC9SDZ60), y) +pmic_core_i2c_mod-objs += mc9sdz60.o +pmic_core_i2c_mod-objs += max8660.o +endif diff --git a/drivers/mxc/pmic/core/max8660.c b/drivers/mxc/pmic/core/max8660.c new file mode 100644 index 000000000000..5214739d03b3 --- /dev/null +++ b/drivers/mxc/pmic/core/max8660.c @@ -0,0 +1,149 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file max8660.c + * @brief Driver for max8660 + * + * @ingroup pmic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> +#include <asm/arch/clock.h> +#include <asm/uaccess.h> +#include <asm/arch/pmic_external.h> +#include "max8660.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MAX8660_I2C_ADDR 0x68 + +#define DEBUG_MAX8660 1 +#if DEBUG_MAX8660 +#define DPRINTK(format, args...) printk(KERN_ERR "max8660"format"\n", ##args) +#else +#define DPRINTK(format, args...) +#endif + +static struct i2c_client *max8660_i2c_client; + + /* reg names for max8660 + REG_MAX8660_OUTPUT_ENABLE_1, + REG_MAX8660_OUTPUT_ENABLE_2, + REG_MAX8660_VOLT__CHANGE_1, + REG_MAX8660_V3_TARGET_VOLT_1, + REG_MAX8660_V3_TARGET_VOLT_2, + REG_MAX8660_V4_TARGET_VOLT_1, + REG_MAX8660_V4_TARGET_VOLT_2, + REG_MAX8660_V5_TARGET_VOLT_1, + REG_MAX8660_V5_TARGET_VOLT_2, + REG_MAX8660_V6V7_TARGET_VOLT, + REG_MAX8660_FORCE_PWM + */ + + /* save down the reg values for the device is write only */ +static u8 max8660_reg_value_table[] = + { 0x0, 0x0, 0x0, 0x17, 0x17, 0x1F, 0x1F, 0x04, 0x04, 0x0, 0x0 +}; +int max8660_get_buffered_reg_val(int reg_name, u8 *value) +{ + + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) { + DPRINTK("reg_name=%d outof range", reg_name); + return -1; + } + *value = + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1]; + return 0; +} +int max8660_save_buffered_reg_val(int reg_name, u8 value) +{ + + /* outof range */ + if (reg_name < REG_MAX8660_OUTPUT_ENABLE_1 + || reg_name > REG_MAX8660_FORCE_PWM) { + DPRINTK("reg_name=%d outof range", reg_name); + return -1; + } + max8660_reg_value_table[reg_name - REG_MAX8660_OUTPUT_ENABLE_1] = value; + return 0; +} + +int max8660_write_reg(u8 reg, u8 value) +{ + DPRINTK("max8660_i2c_client = %p", max8660_i2c_client); + if (i2c_smbus_write_byte_data(max8660_i2c_client, reg, value) < 0) { + printk(KERN_ERR "%s:write reg errorr:reg=%x,val=%x\n", + __func__, reg, value); + return -1; + } + return 0; +} + +/*! + * max8660 I2C attach function + * + * @param adapter struct i2c_client * + * @return Always 0 because max8660 is write-only and can not be detected + */ +static int max8660_probe(struct i2c_client *client) +{ + max8660_i2c_client = client; + DPRINTK("max8660_i2c_client = %p", max8660_i2c_client); + return 0; +} + +/*! + * max8660 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int max8660_remove(struct i2c_client *client) +{ + return 0; +} +static struct i2c_driver max8660_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max8660",}, + .probe = max8660_probe, + .remove = max8660_remove, +}; + +/* called by pmic core when init*/ +int max8660_init(void) +{ + int err; + DPRINTK("Freescale max8660 driver loaded\n"); + err = i2c_add_driver(&max8660_i2c_driver); + if (err) { + printk(KERN_ERR + "max8660: driver registration failed err = %d\n", err); + return err; + } + DPRINTK("max8660 inited\n"); + return 0; +} +void max8660_exit(void) +{ + i2c_del_driver(&max8660_i2c_driver); +} diff --git a/drivers/mxc/pmic/core/max8660.h b/drivers/mxc/pmic/core/max8660.h new file mode 100644 index 000000000000..54638a4ed2fa --- /dev/null +++ b/drivers/mxc/pmic/core/max8660.h @@ -0,0 +1,45 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file max8660.h + * @brief Driver for max8660 + * + * @ingroup pmic + */ +#ifndef _MAX8660_H_ +#define _MAX8660_H_ + +#ifdef __KERNEL__ + +#define MAX8660_OUTPUT_ENABLE_1 0x10 +#define MAX8660_OUTPUT_ENABLE_2 0x12 +#define MAX8660_VOLT_CHANGE_CONTROL 0x20 +#define MAX8660_V3_TARGET_VOLT_1 0x23 +#define MAX8660_V3_TARGET_VOLT_2 0x24 +#define MAX8660_V4_TARGET_VOLT_1 0x29 +#define MAX8660_V4_TARGET_VOLT_2 0x2A +#define MAX8660_V5_TARGET_VOLT_1 0x32 +#define MAX8660_V5_TARGET_VOLT_2 0x33 +#define MAX8660_V6V7_TARGET_VOLT 0x39 +#define MAX8660_FORCE_PWM 0x80 +int max8660_write_reg(u8 reg, u8 value); +int max8660_save_buffered_reg_val(int reg_name, u8 value); +int max8660_get_buffered_reg_val(int reg_name, u8 *value); +int max8660_init(void); + +extern int reg_max8660_probe(void); + +#endif /* __KERNEL__ */ + +#endif /* _MAX8660_H_ */ diff --git a/drivers/mxc/pmic/core/mc9sdz60.c b/drivers/mxc/pmic/core/mc9sdz60.c new file mode 100644 index 000000000000..e367cb5aee15 --- /dev/null +++ b/drivers/mxc/pmic/core/mc9sdz60.c @@ -0,0 +1,111 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file mc9sdz60.c + * @brief Driver for MC9sdz60 + * + * @ingroup pmic + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/proc_fs.h> +#include <linux/i2c.h> + +#include <asm/arch/clock.h> +#include <asm/uaccess.h> +#include "mc9sdz60.h" + +/* I2C bus id and device address of mcu */ +#define I2C1_BUS 0 +#define MC9SDZ60_I2C_ADDR 0xD2 /* 7bits I2C address */ +static struct i2c_client *mc9sdz60_i2c_client; + +#define DEBUG_MC9SDZ60 1 +#if DEBUG_MC9SDZ60 +#define DPRINTK(format, args...) printk(KERN_ERR "mc9sdz60: "format"\n", ##args) +#else +#define DPRINTK(format, args...) +#endif + +int mc9sdz60_read_reg(u8 reg, u8 *value) +{ + *value = (u8) i2c_smbus_read_byte_data(mc9sdz60_i2c_client, reg); + return 0; +} + +int mc9sdz60_write_reg(u8 reg, u8 value) +{ + if (i2c_smbus_write_byte_data(mc9sdz60_i2c_client, reg, value) < 0) { + printk(KERN_ERR "%s:write reg errorr:reg=%x,val=%x\n", + __func__, reg, value); + return -1; + } + return 0; +} + +/*! + * mc9sdz60 I2C attach function + * + * @param adapter struct i2c_adapter * + * @return 0 + */ +static int mc9sdz60_probe(struct i2c_client *client) +{ + mc9sdz60_i2c_client = client; + DPRINTK("mc9sdz60_i2c_client = %p", mc9sdz60_i2c_client); + return 0; +} + +/*! + * mc9sdz60 I2C detach function + * + * @param client struct i2c_client * + * @return 0 + */ +static int mc9sdz60_remove(struct i2c_client *client) +{ + return 0; +} +static struct i2c_driver mc9sdz60_i2c_driver = { + .driver = {.owner = THIS_MODULE, + .name = "mc9sdz60", + }, + .probe = mc9sdz60_probe, + .remove = mc9sdz60_remove, +}; + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +int mc9sdz60_init(void) +{ + int err; + DPRINTK("Freescale mc9sdz60 driver,\ + (c) 2008 Freescale Semiconductor, Inc.\n"); + err = i2c_add_driver(&mc9sdz60_i2c_driver); + if (err) { + printk(KERN_ERR "mc9sdz60: driver registration failed\n"); + return err; + } + DPRINTK("mc9sdz60 inited\n"); + return 0; +} +void mc9sdz60_exit(void) +{ + i2c_del_driver(&mc9sdz60_i2c_driver); +} diff --git a/drivers/mxc/pmic/core/mc9sdz60.h b/drivers/mxc/pmic/core/mc9sdz60.h new file mode 100644 index 000000000000..c1af168d88a9 --- /dev/null +++ b/drivers/mxc/pmic/core/mc9sdz60.h @@ -0,0 +1,69 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9sdz60.h + * @brief Driver for mc9sdz60 + * + * @ingroup pmic + */ +#ifndef _MC9SDZ60_H_ +#define _MC9SDZ60_H_ + +#define MCU_VERSION 0x00 +/*#define Reserved 0x01*/ +#define MCU_SECS 0x02 +#define MCU_MINS 0x03 +#define MCU_HRS 0x04 +#define MCU_DAY 0x05 +#define MCU_DATE 0x06 +#define MCU_MONTH 0x07 +#define MCU_YEAR 0x08 + +#define MCU_ALARM_SECS 0x09 +#define MCU_ALARM_MINS 0x0A +#define MCU_ALARM_HRS 0x0B +/* #define Reserved 0x0C*/ +/* #define Reserved 0x0D*/ +#define MCU_TS_CONTROL 0x0E +#define MCU_X_LOW 0x0F +#define MCU_Y_LOW 0x10 +#define MCU_XY_HIGH 0x11 +#define MCU_X_LEFT_LOW 0x12 +#define MCU_X_LEFT_HIGH 0x13 +#define MCU_X_RIGHT 0x14 +#define MCU_Y_TOP_LOW 0x15 +#define MCU_Y_TOP_HIGH 0x16 +#define MCU_Y_BOTTOM 0x17 +/* #define Reserved 0x18*/ +/* #define Reserved 0x19*/ +#define MCU_RESET_1 0x1A +#define MCU_RESET_2 0x1B +#define MCU_POWER_CTL 0x1C +#define MCU_DELAY_CONFIG 0x1D +/* #define Reserved 0x1E */ +/* #define Reserved 0x1F */ +#define MCU_GPIO_1 0x20 +#define MCU_GPIO_2 0x21 +#define MCU_KPD_1 0x22 +#define MCU_KPD_2 0x23 +#define MCU_KPD_CONTROL 0x24 +#define MCU_INT_ENABLE_1 0x25 +#define MCU_INT_ENABLE_2 0x26 +#define MCU_INT_FLAG_1 0x27 +#define MCU_INT_FLAG_2 0x28 +int mc9sdz60_read_reg(u8 reg, u8 *value); +int mc9sdz60_write_reg(u8 reg, u8 value); +int mc9sdz60_init(void); + +#endif /* _MC9SDZ60_H_ */ + diff --git a/drivers/mxc/pmic/core/mcu_pmic_core.c b/drivers/mxc/pmic/core/mcu_pmic_core.c new file mode 100644 index 000000000000..807e45eecd64 --- /dev/null +++ b/drivers/mxc/pmic/core/mcu_pmic_core.c @@ -0,0 +1,540 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9sdz60/mcu_pmic_core.c + * @brief This is the main file of mc9sdz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <asm/ioctl.h> +#include <asm/uaccess.h> +#include <asm/arch/gpio.h> + +#include <../arch/arm/mach-mx3/iomux.h> + +#include <asm/arch/pmic_status.h> +#include <asm/arch/pmic_external.h> +#include "pmic.h" +#include "mc9sdz60.h" +#include "max8660.h" + +/* bitfield macros for mcu pmic*/ +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +/* + * Global variables + */ +static pmic_version_t mxc_pmic_version; +unsigned int active_events[11]; + +/* default event-enable-value after reset*/ +static u8 events_enabled1 = 0x40; +static u8 events_enabled2; + +/* + * Platform device structure for PMIC client drivers + */ +static struct platform_device power_ldm = { + .name = "pmic_power", + .id = 1, +}; +static struct platform_device rtc_ldm = { + .name = "pmic_rtc", + .id = 1, +}; + +/* map reg names (enum pmic_reg in pmic_external.h) to real addr*/ +const static u8 mcu_pmic_reg_addr_table[] = { + MCU_VERSION, + MCU_SECS, + MCU_MINS, + MCU_HRS, + MCU_DAY, + MCU_DATE, + MCU_MONTH, + MCU_YEAR, + MCU_ALARM_SECS, + MCU_ALARM_MINS, + MCU_ALARM_HRS, + MCU_TS_CONTROL, + MCU_X_LOW, + MCU_Y_LOW, + MCU_XY_HIGH, + MCU_X_LEFT_LOW, + MCU_X_LEFT_HIGH, + MCU_X_RIGHT, + MCU_Y_TOP_LOW, + MCU_Y_TOP_HIGH, + MCU_Y_BOTTOM, + MCU_RESET_1, + MCU_RESET_2, + MCU_POWER_CTL, + MCU_DELAY_CONFIG, + MCU_GPIO_1, + MCU_GPIO_2, + MCU_KPD_1, + MCU_KPD_2, + MCU_KPD_CONTROL, + MCU_INT_ENABLE_1, + MCU_INT_ENABLE_2, + MCU_INT_FLAG_1, + MCU_INT_FLAG_2, + MAX8660_OUTPUT_ENABLE_1, + MAX8660_OUTPUT_ENABLE_2, + MAX8660_VOLT_CHANGE_CONTROL, + MAX8660_V3_TARGET_VOLT_1, + MAX8660_V3_TARGET_VOLT_2, + MAX8660_V4_TARGET_VOLT_1, + MAX8660_V4_TARGET_VOLT_2, + MAX8660_V5_TARGET_VOLT_1, + MAX8660_V5_TARGET_VOLT_2, + MAX8660_V6V7_TARGET_VOLT, + MAX8660_FORCE_PWM +}; + +#define DPRINTK(format, args...) printk(KERN_ERR "pmic-core "format"\n", ##args) + +int pmic_read(int reg_num, unsigned int *reg_val) +{ + int ret; + u8 value = 0; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_INT_FLAG_2) { + + ret = + mc9sdz60_read_reg(mcu_pmic_reg_addr_table[reg_num], &value); + if (ret < 0) + goto error1; + *reg_val = value; + } else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) { + ret = max8660_get_buffered_reg_val(reg_num, &value); + if (ret < 0) + goto error1; + *reg_val = value; + } else { + DPRINTK("reg_num=%d out of range", reg_num); + goto error1; + } + + return 0; + +error1: + return -1; +} + +int pmic_write(int reg_num, const unsigned int reg_val) +{ + int ret; + u8 value = reg_val; + /* mcu ops */ + if (reg_num >= REG_MCU_VERSION && reg_num <= REG_MCU_INT_FLAG_2) { + + ret = + mc9sdz60_write_reg(mcu_pmic_reg_addr_table[reg_num], value); + if (ret < 0) + goto error1; + } else if (reg_num >= REG_MAX8660_OUTPUT_ENABLE_1 + && reg_num <= REG_MAX8660_FORCE_PWM) { + ret = + max8660_write_reg(mcu_pmic_reg_addr_table[reg_num], value); + + if (ret < 0) + goto error1; + + ret = max8660_save_buffered_reg_val(reg_num, value); + } else { + DPRINTK("reg_num=%d out of range", reg_num); + goto error1; + } + + return 0; + +error1: + DPRINTK(" reg_num = %d, write failed", reg_num); + return -1; + +} + +/* for debug*/ +void dump_pmic_reg(void) +{ +#define REG_DUMP(reg) do {\ + pmic_read_reg(reg, ®_val, 0xff);\ + DPRINTK("%s = %x", #reg, reg_val); } while (0) + unsigned int reg_val; + + REG_DUMP(REG_MCU_VERSION); + REG_DUMP(REG_MCU_SECS); + REG_DUMP(REG_MCU_MINS); + REG_DUMP(REG_MCU_HRS); + REG_DUMP(REG_MCU_DAY); + REG_DUMP(REG_MCU_DATE); + REG_DUMP(REG_MCU_MONTH); + REG_DUMP(REG_MCU_YEAR); + REG_DUMP(REG_MCU_ALARM_SECS); + REG_DUMP(REG_MCU_ALARM_MINS); + REG_DUMP(REG_MCU_ALARM_HRS); + REG_DUMP(REG_MCU_TS_CONTROL); + REG_DUMP(REG_MCU_X_LOW); + REG_DUMP(REG_MCU_Y_LOW); + REG_DUMP(REG_MCU_XY_HIGH); + REG_DUMP(REG_MCU_X_LEFT_LOW); + REG_DUMP(REG_MCU_X_LEFT_HIGH); + REG_DUMP(REG_MCU_X_RIGHT); + REG_DUMP(REG_MCU_Y_TOP_LOW); + REG_DUMP(REG_MCU_Y_TOP_HIGH); + REG_DUMP(REG_MCU_Y_BOTTOM); + REG_DUMP(REG_MCU_RESET_1); + REG_DUMP(REG_MCU_RESET_2); + REG_DUMP(REG_MCU_POWER_CTL); + REG_DUMP(REG_MCU_DELAY_CONFIG); + REG_DUMP(REG_MCU_GPIO_1); + REG_DUMP(REG_MCU_GPIO_2); + REG_DUMP(REG_MCU_KPD_1); + REG_DUMP(REG_MCU_KPD_2); + REG_DUMP(REG_MCU_KPD_CONTROL); + REG_DUMP(REG_MCU_INT_ENABLE_1); + REG_DUMP(REG_MCU_INT_ENABLE_2); + REG_DUMP(REG_MCU_INT_FLAG_1); + REG_DUMP(REG_MCU_INT_FLAG_2); +} + +/*! + * This function unsets a bit in mask register of pmic to unmask an event IT. + * + * @param event the event to be unmasked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_unmask(type_event event) +{ + int reg_name; + u8 reg_mask = 0; + unsigned int event_bit = 0; + int ret = -1; + + switch (event) { + case EVENT_HEADPHONE_DET: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 0; + break; + case EVENT_SD1_DET: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 2; + break; + case EVENT_SD1_WP: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 3; + break; + case EVENT_SD2_DET: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 4; + break; + case EVENT_SD2_WP: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 5; + break; + case EVENT_GPS_INT: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 1; + break; + case EVENT_POWER_KEY: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 6; + break; + case EVENT_KEYPAD: + reg_name = REG_MCU_INT_ENABLE_2; + event_bit = 2; + break; + case EVENT_RTC: + reg_name = REG_MCU_INT_ENABLE_2; + event_bit = 0; + break; + case EVENT_TS_ADC: + reg_name = REG_MCU_INT_ENABLE_2; + event_bit = 1; + break; + default: + return PMIC_ERROR; + } + SET_BIT_IN_BYTE(reg_mask, event_bit); + ret = pmic_write_reg(reg_name, reg_mask, reg_mask); + if (PMIC_SUCCESS == ret) { + + if (REG_MCU_INT_ENABLE_2 == reg_name) + events_enabled2 |= reg_mask; + else if (REG_MCU_INT_ENABLE_1 == reg_name) + events_enabled1 |= reg_mask; + pr_debug("Enable Event : %d\n", event); + } + + return ret; +} + +/*! + * This function sets a bit in mask register of pmic to disable an event IT. + * + * @param event the event to be masked + * + * @return This function returns PMIC_SUCCESS on SUCCESS, error on FAILURE. + */ +int pmic_event_mask(type_event event) +{ + int reg_name; + u8 reg_mask = 0; + unsigned int event_bit = 0; + int ret = -1; + + switch (event) { + case EVENT_HEADPHONE_DET: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 0; + break; + case EVENT_SD1_DET: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 2; + break; + case EVENT_SD1_WP: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 3; + break; + case EVENT_SD2_DET: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 4; + break; + case EVENT_SD2_WP: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 5; + break; + case EVENT_GPS_INT: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 1; + break; + case EVENT_POWER_KEY: + reg_name = REG_MCU_INT_ENABLE_1; + event_bit = 6; + break; + case EVENT_KEYPAD: + reg_name = REG_MCU_INT_ENABLE_2; + event_bit = 2; + break; + case EVENT_RTC: + reg_name = REG_MCU_INT_ENABLE_2; + event_bit = 0; + break; + case EVENT_TS_ADC: + reg_name = REG_MCU_INT_ENABLE_2; + event_bit = 1; + break; + default: + return PMIC_ERROR; + } + SET_BIT_IN_BYTE(reg_mask, event_bit); + + ret = pmic_write_reg(reg_name, 0, reg_mask); + if (PMIC_SUCCESS == ret) { + + if (REG_MCU_INT_ENABLE_2 == reg_name) + events_enabled2 &= ~reg_mask; + else if (REG_MCU_INT_ENABLE_1 == reg_name) + events_enabled1 &= ~reg_mask; + pr_debug("Disable Event : %d\n", event); + } + + return ret; +} + +/*! + * This function reads the interrupt status registers of PMIC + * and determine the current active events. + * + * @param active_events array pointer to be used to return active + * event numbers. + * + * @return This function returns PMIC version. + */ +unsigned int pmic_get_active_events(unsigned int *active_events) +{ + unsigned int count = 0; + unsigned int flag1, flag2; + int bit_set; + + /* read int flags and ack int */ + pmic_read(REG_MCU_INT_FLAG_1, &flag1); + pmic_read(REG_MCU_INT_FLAG_2, &flag2); + pmic_write(REG_MCU_INT_FLAG_1, 0); + pmic_write(REG_MCU_INT_FLAG_2, 0); + DPRINTK("pmic interrupt flag1=%x, flag2=%x", flag1, flag2); + + flag1 &= events_enabled1; + flag2 &= events_enabled2; + + while (flag1) { + bit_set = ffs(flag1) - 1; + *(active_events + count) = bit_set; + count++; + flag1 ^= (1 << bit_set); + } + while (flag2) { + bit_set = ffs(flag2) - 1; + *(active_events + count) = bit_set + 7; + count++; + flag2 ^= (1 << bit_set); + } + + return count; +} + +void pmic_bh_handler(struct work_struct *work); + +/* + * External functions + */ +extern void pmic_event_list_init(void); +extern void pmic_event_callback(type_event event); +extern void gpio_pmic_active(void); + +/*! + * Bottom half handler of PMIC event handling. + */ +DECLARE_WORK(pmic_ws, pmic_bh_handler); + +/*! + * This function registers platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_register(void) +{ + platform_device_register(&rtc_ldm); + platform_device_register(&power_ldm); + reg_max8660_probe(); +} + +/*! + * This function unregisters platform device structures for + * PMIC client drivers. + */ +static void pmic_pdev_unregister(void) +{ + platform_device_unregister(&rtc_ldm); +} + +/*! + * This function is called when pmic interrupt occurs on the processor. + * It is the interrupt handler for the pmic module. + * + * @param irq the irq number + * @param dev_id the pointer on the device + * + * @return The function returns IRQ_HANDLED when handled. + */ +static irqreturn_t pmic_irq_handler(int irq, void *dev_id) +{ + /* prepare a task */ + schedule_work(&pmic_ws); + + return IRQ_HANDLED; +} + +/*! + * This function is the bottom half handler of the PMIC interrupt. + * It checks for active events and launches callback for the + * active events. + */ +void pmic_bh_handler(struct work_struct *work) +{ + unsigned int loop; + unsigned int count = 0; + + count = pmic_get_active_events(active_events); + + for (loop = 0; loop < count; loop++) + pmic_event_callback(active_events[loop]); +} + +/*! + * This function is used to determine the PMIC type and its revision. + * + * @return Returns the PMIC type and its revision. + */ + +pmic_version_t pmic_get_version(void) +{ + return mxc_pmic_version; +} +EXPORT_SYMBOL(pmic_get_version); + +static int __init mcu_pmic_init(void) +{ + int err; + + /* init chips */ + err = max8660_init(); + if (err) + goto fail1; + + err = mc9sdz60_init(); + if (err) + goto fail1; + + /* Initialize the PMIC event handling */ + pmic_event_list_init(); + + /* Set and install PMIC IRQ handler */ + mxc_request_iomux(MX35_PIN_GPIO1_0, OUTPUTCONFIG_GPIO, + INPUTCONFIG_GPIO); + mxc_iomux_set_pad(MX35_PIN_GPIO1_0, PAD_CTL_PKE_NONE); + mxc_set_gpio_direction(MX35_PIN_GPIO1_0, 1); /* input */ + + err = set_irq_type(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0), IRQT_RISING); + err = request_irq(IOMUX_TO_IRQ(MX35_PIN_GPIO1_0), pmic_irq_handler, + 0, "PMIC_IRQ", 0); + if (err) { + DPRINTK("mcu pmic request irq failed"); + goto fail1; + } + + pmic_pdev_register(); + + return 0; + +fail1: + return err; +} + +static void __exit mcu_pmic_exit(void) +{ + free_irq(IOMUX_TO_IRQ(MX35_PIN_GPIO1_1), 0); + pmic_pdev_unregister(); +} + +subsys_initcall_sync(mcu_pmic_init); +module_exit(mcu_pmic_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("mcu pmic driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc9sdz60/Kconfig b/drivers/mxc/pmic/mc9sdz60/Kconfig new file mode 100644 index 000000000000..b0e87653853e --- /dev/null +++ b/drivers/mxc/pmic/mc9sdz60/Kconfig @@ -0,0 +1,29 @@ +# +# PMIC Modules configuration +# + +config MXC_MC9SDZ60_ADC
+ tristate "MC9SDZ60 ADC support"
+ depends on MXC_PMIC_MC9SDZ60
+ default n + ---help--- + This is the MC9SDZ60 ADC module driver. This module provides kernel API
+ for the ADC system of MC9SDZ60.
+ It controls also the touch screen interface. + If you want MC9SDZ60 ADC support, you should say Y here
+ +config MXC_MC9SDZ60_RTC
+ tristate "MC9SDZ60 Real Time Clock (RTC) support"
+ depends on MXC_PMIC_MC9SDZ60
+ ---help--- + This is the MC9SDZ60 RTC module driver. This module provides kernel API
+ for RTC part of MC9SDZ60.
+ If you want MC9SDZ60 RTC support, you should say Y here
+
+config MXC_MC9SDZ60_POWER
+ tristate "MC9SDZ60 Power API support"
+ depends on MXC_PMIC_MC9SDZ60
+ ---help--- + This is the MC9SDZ60 power and supplies module driver. This module provides kernel API
+ for power and regulator control part of MC9SDZ60.
+ If you want MC9SDZ60 power support, you should say Y here
diff --git a/drivers/mxc/pmic/mc9sdz60/Makefile b/drivers/mxc/pmic/mc9sdz60/Makefile new file mode 100644 index 000000000000..ab2522632a36 --- /dev/null +++ b/drivers/mxc/pmic/mc9sdz60/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the mc9sdz60 pmic drivers.
+# + +obj-y += mcu_pmic_gpio.o +obj-$(CONFIG_MXC_MC9SDZ60_ADC) += pmic_adc-mod.o
+obj-$(CONFIG_MXC_MC9SDZ60_RTC) += pmic_rtc-mod.o
+#obj-$(CONFIG_MXC_MC9SDZ60_POWER) += pmic_power-mod.o
+pmic_adc-mod-objs := mcu_pmic_adc.o
+pmic_rtc-mod-objs := mcu_pmic_rtc.o
+#pmic_power-mod-objs := mcu_pmic_power.o
+
diff --git a/drivers/mxc/pmic/mc9sdz60/mcu_pmic_event.c b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_event.c new file mode 100644 index 000000000000..42be5656709b --- /dev/null +++ b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_event.c @@ -0,0 +1,220 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9sdz60/mcu_pmic_event.c + * @brief This is the main file of mc9sdz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <asm/ioctl.h> +#include <asm/arch/pmic_status.h> +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_power.h> +#include "../core/pmic_config.h" +#include "mc9sdz60.h" +#include "max8660.h" + +/*! + * This structure is used to keep a list of subscribed + * callbacks for an event. + */ +typedef struct { + /*! + * Keeps a list of subscribed clients to an event. + */ + struct list_head list; + + /*! + * Callback function with parameter, called when event occurs + */ + pmic_event_callback_t callback; +} pmic_event_callback_list_t; + +/* Create a mutex to be used to prevent concurrent access to the event list */ +static DECLARE_MUTEX(event_mutex); + +/* This is a pointer to the event handler array. It defines the currently + * active set of events and user-defined callback functions. + */ +static struct list_head pmic_events[PMIC_MAX_EVENTS]; + +/*! + * This function initializes event list for PMIC event handling. + * + */ +void pmic_event_list_init(void) +{ + int i; + + for (i = 0; i < PMIC_MAX_EVENTS; i++) + INIT_LIST_HEAD(&pmic_events[i]); + + sema_init(&event_mutex, 1); +} + +/*! + * This function is used to subscribe on an event. + * + * @param event the event number to be subscribed + * @param callback the callback funtion to be subscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_subscribe(type_event event, + pmic_event_callback_t callback) +{ + pmic_event_callback_list_t *new = NULL; + + pr_debug("Event:%d Subscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Create a new linked list entry */ + new = kmalloc(sizeof(pmic_event_callback_list_t), GFP_KERNEL); + if (NULL == new) + return -ENOMEM; + + /* Initialize the list node fields */ + new->callback.func = callback.func; + new->callback.param = callback.param; + INIT_LIST_HEAD(&new->list); + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) { + kfree(new); + return PMIC_SYSTEM_ERROR_EINTR; + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_unmask(event) != PMIC_SUCCESS) { + kfree(new); + up(&event_mutex); + return PMIC_ERROR; + } + } + + /* Add this entry to the event list */ + list_add_tail(&new->list, &pmic_events[event]); + + /* Release the lock */ + up(&event_mutex); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_event_subscribe); + +/*! + * This function is used to unsubscribe on an event. + * + * @param event the event number to be unsubscribed + * @param callback the callback funtion to be unsubscribed + * + * @return This function returns 0 on SUCCESS, error on FAILURE. + */ +PMIC_STATUS pmic_event_unsubscribe(type_event event, + pmic_event_callback_t callback) +{ + struct list_head *p; + struct list_head *n; + pmic_event_callback_list_t *temp = NULL; + int ret = PMIC_EVENT_NOT_SUBSCRIBED; + + pr_debug("Event:%d Unsubscribe\n", event); + + /* Check whether the event & callback are valid? */ + if (event >= PMIC_MAX_EVENTS) { + pr_debug("Invalid Event:%d\n", event); + return -EINVAL; + } + + if (NULL == callback.func) { + pr_debug("Null or Invalid Callback\n"); + return -EINVAL; + } + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) + return PMIC_SYSTEM_ERROR_EINTR; + + /* Find the entry in the list */ + list_for_each_safe(p, n, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + if (temp->callback.func == callback.func + && temp->callback.param == callback.param) { + /* Remove the entry from the list */ + list_del(p); + kfree(temp); + ret = PMIC_SUCCESS; + break; + } + } + + /* Unmask the requested event */ + if (list_empty(&pmic_events[event])) { + if (pmic_event_mask(event) != PMIC_SUCCESS) + ret = PMIC_UNSUBSCRIBE_ERROR; + } + + /* Release the lock */ + up(&event_mutex); + + return ret; +} +EXPORT_SYMBOL(pmic_event_unsubscribe); + +/*! + * This function calls all callback of a specific event. + * + * @param event the active event number + * + * @return None + */ +void pmic_event_callback(type_event event) +{ + struct list_head *p; + pmic_event_callback_list_t *temp = NULL; + + /* Obtain the lock to access the list */ + if (down_interruptible(&event_mutex)) + return; + if (list_empty(&pmic_events[event])) { + pr_debug("PMIC Event:%d detected. No callback subscribed\n", + event); + up(&event_mutex); + return; + } + + list_for_each(p, &pmic_events[event]) { + temp = list_entry(p, pmic_event_callback_list_t, list); + temp->callback.func(temp->callback.param); + } + + /* Release the lock */ + up(&event_mutex); +} + diff --git a/drivers/mxc/pmic/mc9sdz60/mcu_pmic_gpio.c b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_gpio.c new file mode 100644 index 000000000000..cdf2711ca564 --- /dev/null +++ b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_gpio.c @@ -0,0 +1,111 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9sdz60/mcu_pmic_gpio.c + * @brief This is the main file of mc9sdz60 Power Control driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <asm/ioctl.h> +#include <asm/arch/pmic_status.h> +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_power.h> + +#define SET_BIT_IN_BYTE(byte, pos) (byte |= (0x01 << pos)) +#define CLEAR_BIT_IN_BYTE(byte, pos) (byte &= ~(0x01 << pos)) + +PMIC_STATUS pmic_gpio_set_bit_val(t_mcu_gpio_reg reg, unsigned int bit, + unsigned int val) +{ + int reg_name; + u8 reg_mask = 0; + + if (bit > 7) + return PMIC_PARAMETER_ERROR; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + if (0 == val) + CHECK_ERROR(pmic_write_reg(reg_name, 0, reg_mask)); + else + CHECK_ERROR(pmic_write_reg(reg_name, reg_mask, reg_mask)); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_gpio_set_bit_val); + +PMIC_STATUS pmic_gpio_get_bit_val(t_mcu_gpio_reg reg, unsigned int bit, + unsigned int *val) +{ + int reg_name; + unsigned int reg_read_val; + u8 reg_mask = 0; + + if (bit > 7) + return PMIC_PARAMETER_ERROR; + + switch (reg) { + case MCU_GPIO_REG_RESET_1: + reg_name = REG_MCU_RESET_1; + break; + case MCU_GPIO_REG_RESET_2: + reg_name = REG_MCU_RESET_2; + break; + case MCU_GPIO_REG_POWER_CONTROL: + reg_name = REG_MCU_POWER_CTL; + break; + case MCU_GPIO_REG_GPIO_CONTROL_1: + reg_name = REG_MCU_GPIO_1; + break; + case MCU_GPIO_REG_GPIO_CONTROL_2: + reg_name = REG_MCU_GPIO_2; + break; + default: + return PMIC_PARAMETER_ERROR; + } + + SET_BIT_IN_BYTE(reg_mask, bit); + CHECK_ERROR(pmic_read_reg(reg_name, ®_read_val, reg_mask)); + if (0 == reg_read_val) + *val = 0; + else + *val = 1; + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_gpio_get_bit_val); diff --git a/drivers/mxc/pmic/mc9sdz60/mcu_pmic_power.c b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_power.c new file mode 100644 index 000000000000..6072eb9bc1a0 --- /dev/null +++ b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_power.c @@ -0,0 +1,465 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9sdz60/mcu_pmic_power.c + * @brief This is the main file of mc9sdz60 Power Control driver. + * @This driver is deprecated, replaced by Wolfson regulator architecture + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <asm/ioctl.h> +#include <asm/arch/pmic_status.h> +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_power.h> +#include "../core/pmic_config.h" + +/* + * PMIC Power Control API + */ + + +/*! + * This is the suspend of power management for Power Control + * API module. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_power_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +}; + +/*! + * This is the resume of power management API. + * It suports RESTORE state. + * + * @param pdev the device structure used to give information on which power + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_power_resume(struct platform_device *pdev) +{ + return 0; +}; + +/*! + * This function sets user power off in power control register and thus powers + * off the phone. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_off(void) +{ +#define POWER_OFF_DELAY_MS 200 + u8 mask = 0; + CHECK_ERROR(pmic_write_reg + (REG_MCU_DELAY_CONFIG, POWER_OFF_DELAY_MS, 0xff)); + SET_BIT_IN_BYTE(mask, 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_POWER_CTL, mask, mask)); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_power_off); + +/*! + * This function turns on a regulator. + * + * @param regulator The regulator to be truned on. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_on(t_pmic_regulator regulator) +{ + u8 reg_mask = 0; + int reg_num; + + if (regulator > MCU_LDO7 || regulator < MCU_SW1) + return PMIC_NOT_SUPPORTED; + + switch (regulator) { + case MCU_SW1: + SET_BIT_IN_BYTE(reg_mask, 0); + reg_num = REG_MCU_POWER_CTL; + break; + case MCU_SW2: + SET_BIT_IN_BYTE(reg_mask, 1); + reg_num = REG_MCU_POWER_CTL; + break; + case MCU_SW3: + SET_BIT_IN_BYTE(reg_mask, 0); + reg_num = REG_MAX8660_OUTPUT_ENABLE_1; + break; + case MCU_SW4: + SET_BIT_IN_BYTE(reg_mask, 2); + reg_num = REG_MAX8660_OUTPUT_ENABLE_1; + break; + case MCU_LDO5: + SET_BIT_IN_BYTE(reg_mask, 3); + reg_num = REG_MCU_POWER_CTL; + break; + case MCU_LDO6: + SET_BIT_IN_BYTE(reg_mask, 1); + reg_num = REG_MAX8660_OUTPUT_ENABLE_2; + break; + case MCU_LDO7: + SET_BIT_IN_BYTE(reg_mask, 2); + reg_num = REG_MAX8660_OUTPUT_ENABLE_2; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_num, reg_mask, reg_mask)); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_power_regulator_on); + +/*! + * This function turns off a regulator. + * + * @param regulator The regulator to be truned off. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_off(t_pmic_regulator regulator) +{ + u8 reg_mask = 0; + unsigned int reg_read = 0; + int reg_num; + + if (regulator > MCU_LDO7 || regulator < MCU_SW1) + return PMIC_NOT_SUPPORTED; + + switch (regulator) { + case MCU_SW1: + SET_BIT_IN_BYTE(reg_mask, 0); + reg_num = REG_MCU_POWER_CTL; + break; + case MCU_SW2: + SET_BIT_IN_BYTE(reg_mask, 1); + reg_num = REG_MCU_POWER_CTL; + break; + case MCU_SW3: + SET_BIT_IN_BYTE(reg_mask, 0); + reg_num = REG_MAX8660_OUTPUT_ENABLE_1; + break; + case MCU_SW4: + SET_BIT_IN_BYTE(reg_mask, 2); + reg_num = REG_MAX8660_OUTPUT_ENABLE_1; + break; + case MCU_LDO5: + SET_BIT_IN_BYTE(reg_mask, 3); + reg_num = REG_MCU_POWER_CTL; + break; + case MCU_LDO6: + SET_BIT_IN_BYTE(reg_mask, 1); + reg_num = REG_MAX8660_OUTPUT_ENABLE_2; + break; + case MCU_LDO7: + SET_BIT_IN_BYTE(reg_mask, 2); + reg_num = REG_MAX8660_OUTPUT_ENABLE_2; + break; + + default: + return PMIC_PARAMETER_ERROR; + } + + CHECK_ERROR(pmic_write_reg(reg_num, 0, reg_mask)); + + /* handle sw3,4 */ + switch (regulator) { + case MCU_SW3: + reg_mask = 0; + SET_BIT_IN_BYTE(reg_mask, 2); + CHECK_ERROR(pmic_read_reg + (REG_MCU_POWER_CTL, ®_read, reg_mask)); + + /* check if hw pin enable sw34 */ + if (0 != reg_read) { + + /* keep sw4 on */ + reg_mask = 0; + SET_BIT_IN_BYTE(reg_mask, 2); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_OUTPUT_ENABLE_1, reg_mask, + reg_mask)); + + /* disable hw pin to actually turn off sw3 */ + reg_mask = 0; + SET_BIT_IN_BYTE(reg_mask, 2); + CHECK_ERROR(pmic_write_reg + (REG_MCU_POWER_CTL, 0, reg_mask)); + } + break; + case MCU_SW4: + reg_mask = 0; + SET_BIT_IN_BYTE(reg_mask, 2); + CHECK_ERROR(pmic_read_reg + (REG_MCU_POWER_CTL, ®_read, reg_mask)); + + /* check if hw pin enable sw34 */ + if (0 != reg_read) { + + /* keep sw3 on */ + reg_mask = 0; + SET_BIT_IN_BYTE(reg_mask, 0); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_OUTPUT_ENABLE_1, reg_mask, + reg_mask)); + + /* disable hw pin to actually turn off sw4 */ + reg_mask = 0; + SET_BIT_IN_BYTE(reg_mask, 2); + CHECK_ERROR(pmic_write_reg + (REG_MCU_POWER_CTL, 0, reg_mask)); + } + break; + default: + break; + } + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_power_regulator_off); + +/*! + * This function sets the regulator output voltage. + * + * @param regulator The regulator to be truned off. + * @param voltage The regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_set_voltage(t_pmic_regulator regulator, + t_regulator_voltage voltage) +{ + u8 reg_mask = 0; + + if (regulator > MCU_LDO7 || regulator < MCU_SW3) + return PMIC_NOT_SUPPORTED; + + switch (regulator) { + case MCU_SW3: + if ((voltage.mcu_sw34 < MCU_SW34_0_725V) + || voltage.mcu_sw34 > MCU_SW34_1_8V) + return PMIC_PARAMETER_ERROR; + + /* hold on */ + SET_BIT_IN_BYTE(reg_mask, 0); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_VOLT_CHANGE_CONTROL_1, 0, reg_mask)); + + /* set volt */ + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_V3_TARGET_VOLT_1, voltage.mcu_sw34, + 0xff)); + + /* start ramp */ + SET_BIT_IN_BYTE(reg_mask, 0); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_VOLT_CHANGE_CONTROL_1, reg_mask, + reg_mask)); + break; + case MCU_SW4: + if ((voltage.mcu_sw34 < MCU_SW34_0_725V) + || voltage.mcu_sw34 > MCU_SW34_1_8V) + return PMIC_PARAMETER_ERROR; + + /* hold on */ + SET_BIT_IN_BYTE(reg_mask, 4); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_VOLT_CHANGE_CONTROL_1, 0, reg_mask)); + + /* set volt */ + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_V4_TARGET_VOLT_1, voltage.mcu_sw34, + 0xff)); + + /* start ramp */ + SET_BIT_IN_BYTE(reg_mask, 4); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_VOLT_CHANGE_CONTROL_1, reg_mask, + reg_mask)); + break; + case MCU_LDO5: + if (voltage.mcu_ldo5 < MCU_LDO5_1_725V + || voltage.mcu_ldo5 > MCU_LDO5_2_0V) + return PMIC_PARAMETER_ERROR; + + /* hold on */ + SET_BIT_IN_BYTE(reg_mask, 6); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_VOLT_CHANGE_CONTROL_1, 0, reg_mask)); + + /* set volt */ + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_V5_TARGET_VOLT_1, voltage.mcu_ldo5, + 0xff)); + + /* start ramp */ + SET_BIT_IN_BYTE(reg_mask, 6); + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_VOLT_CHANGE_CONTROL_1, reg_mask, + reg_mask)); + break; + + break; + case MCU_LDO6: + if (voltage.mcu_ldo67 < MCU_LDO67_1_8V + || voltage.mcu_ldo67 > MCU_LDO67_3_3V) + return PMIC_PARAMETER_ERROR; + + /* set volt */ + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_V6V7_TARGET_VOLT, voltage.mcu_ldo67, + 0x0f)); + + break; + case MCU_LDO7: + if (voltage.mcu_ldo67 < MCU_LDO67_1_8V + || voltage.mcu_ldo67 > MCU_LDO67_3_3V) + return PMIC_PARAMETER_ERROR; + break; + + /* set volt */ + CHECK_ERROR(pmic_write_reg + (REG_MAX8660_V6V7_TARGET_VOLT, + (voltage.mcu_ldo67 << 4), 0xf0)); + + default: + return PMIC_PARAMETER_ERROR; + } + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_power_regulator_set_voltage); + +/*! + * This function retrives the regulator output voltage. + * + * @param regulator The regulator to be truned off. + * @param voltage Pointer to regulator output voltage. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_power_regulator_get_voltage(t_pmic_regulator regulator, + t_regulator_voltage *voltage) +{ + unsigned int reg_val = 0; + + if (regulator > MCU_LDO7 || regulator < MCU_SW3) + return PMIC_NOT_SUPPORTED; + + switch (regulator) { + case MCU_SW3: + CHECK_ERROR(pmic_read_reg + (REG_MAX8660_V3_TARGET_VOLT_1, ®_val, 0xff)); + voltage->mcu_sw34 = reg_val; + break; + case MCU_SW4: + CHECK_ERROR(pmic_read_reg + (REG_MAX8660_V4_TARGET_VOLT_1, ®_val, 0xff)); + voltage->mcu_sw34 = reg_val; + break; + case MCU_LDO5: + CHECK_ERROR(pmic_read_reg + (REG_MAX8660_V5_TARGET_VOLT_1, ®_val, 0xff)); + voltage->mcu_ldo5 = reg_val; + break; + case MCU_LDO6: + CHECK_ERROR(pmic_read_reg + (REG_MAX8660_V6V7_TARGET_VOLT, ®_val, 0x0f)); + voltage->mcu_ldo67 = reg_val; + break; + case MCU_LDO7: + CHECK_ERROR(pmic_read_reg + (REG_MAX8660_V6V7_TARGET_VOLT, ®_val, 0xf0)); + voltage->mcu_ldo67 = (reg_val >> 4) & 0xf; + break; + default: + return PMIC_PARAMETER_ERROR; + } + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_power_regulator_get_voltage); + +/* + * Initialization and Exit + */ + +PMIC_STATUS pmic_power_regulator_init() +{ + t_regulator_voltage volt; + volt.mcu_sw34 = MCU_SW34_1_2V; + CHECK_ERROR(pmic_power_regulator_set_voltage(MCU_SW3, volt)); + volt.mcu_sw34 = MCU_SW34_1_5V; + CHECK_ERROR(pmic_power_regulator_set_voltage(MCU_SW4, volt)); + volt.mcu_ldo5 = MCU_LDO5_1_8V; + CHECK_ERROR(pmic_power_regulator_set_voltage(MCU_LDO5, volt)); + volt.mcu_ldo67 = MCU_LDO67_2_5V; + CHECK_ERROR(pmic_power_regulator_set_voltage(MCU_LDO6, volt)); + volt.mcu_ldo67 = MCU_LDO67_2_8V; + CHECK_ERROR(pmic_power_regulator_set_voltage(MCU_LDO7, volt)); + + return PMIC_SUCCESS; +} + +static int pmic_power_probe(struct platform_device *pdev) +{ + printk(KERN_INFO "PMIC Power successfully probed\n"); + pmic_power_regulator_init(); + return 0; +} + +static struct platform_driver pmic_power_driver_ldm = { + .driver = { + .name = "pmic_power", + }, + .suspend = pmic_power_suspend, + .resume = pmic_power_resume, + .probe = pmic_power_probe, + .remove = NULL, +}; + +static int __init pmic_power_init(void) +{ + pr_debug("PMIC Power driver loading..\n"); + return platform_driver_register(&pmic_power_driver_ldm); +} +static void __exit pmic_power_exit(void) +{ + platform_driver_unregister(&pmic_power_driver_ldm); + pr_debug("PMIC Power driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_power_init); +module_exit(pmic_power_exit); + +MODULE_DESCRIPTION("PMIC Power Control device driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); diff --git a/drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c new file mode 100644 index 000000000000..a83d13d435a4 --- /dev/null +++ b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c @@ -0,0 +1,599 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9sdz60/mcu_pmic_rtc.c + * @brief This is the main file of mc9sdz60 RTC driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <asm/ioctl.h> +#include <linux/rtc.h> +#include <linux/bcd.h> + +#include <asm/arch/pmic_status.h> +#include <asm/arch/pmic_rtc.h> +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_power.h> + +#define MCU_PMIC_RTC_NAME "pmic_rtc" +/* + * Global variables + */ +static int pmic_rtc_major; +static void callback_alarm_asynchronous(void *); +static void callback_alarm_synchronous(void *); +static unsigned int pmic_rtc_poll(struct file *file, poll_table *wait); +static DECLARE_WAIT_QUEUE_HEAD(queue_alarm); +static DECLARE_WAIT_QUEUE_HEAD(pmic_rtc_wait); +static pmic_event_callback_t alarm_callback; +static pmic_event_callback_t rtc_callback; +static int pmic_rtc_detected; +static bool pmic_rtc_done; +static struct class *pmic_rtc_class; +struct rtc_time alarm_greg_time; +static DECLARE_MUTEX(mutex); + +#define DEBUG_PMIC_RTC 1 +#if DEBUG_PMIC_RTC +#define DPRINTK(format, args...) printk(KERN_ERR "pmic_rtc"format"\n", ##args) +#else +#define DPRINTK(format, args...) +#endif + +/* + * Real Time Clock Pmic API + */ + +/*! + * This is the callback function called on TSI Pmic event, used in asynchronous + * call. + */ +static void callback_alarm_asynchronous(void *unused) +{ + pmic_rtc_done = true; +} + +/*! + * This is the callback function is used in test code for (un)sub. + */ +static void callback_test_sub(void) +{ + printk(KERN_INFO "*****************************************\n"); + printk(KERN_INFO "***** PMIC RTC 'Alarm IT CallBack' ******\n"); + printk(KERN_INFO "*****************************************\n"); +} + +/*! + * This is the callback function called on TSI Pmic event, used in synchronous + * call. + */ +static void callback_alarm_synchronous(void *unused) +{ + printk(KERN_INFO "*** Alarm IT Pmic ***\n"); + wake_up(&queue_alarm); +} + +/*! + * This function wait the Alarm event + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_wait_alarm(void) +{ + DEFINE_WAIT(wait); + alarm_callback.func = callback_alarm_synchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_RTC, alarm_callback)); + prepare_to_wait(&queue_alarm, &wait, TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&queue_alarm, &wait); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_RTC, alarm_callback)); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_wait_alarm); + +/*! + * This function set the real time clock of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time(struct timeval *pmic_time) +{ + u8 reg_val; + struct rtc_time greg_time; + + rtc_time_to_tm(pmic_time->tv_sec, &greg_time); + + reg_val = greg_time.tm_sec % 10; + reg_val |= ((greg_time.tm_sec / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_SECS, reg_val, 0x7f)); + + reg_val = greg_time.tm_min % 10; + reg_val |= ((greg_time.tm_min / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_MINS, reg_val, 0x7f)); + + reg_val = greg_time.tm_hour % 10; + reg_val |= ((greg_time.tm_hour / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_HRS, reg_val, 0x3f)); + + reg_val = greg_time.tm_wday; + CHECK_ERROR(pmic_write_reg(REG_MCU_DAY, reg_val, 0x3)); + + reg_val = greg_time.tm_mday % 10; + reg_val |= ((greg_time.tm_mday / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_DATE, reg_val, 0x3f)); + + reg_val = greg_time.tm_mon % 10; + reg_val |= ((greg_time.tm_mon / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_MONTH, reg_val, 0x1f)); + + reg_val = greg_time.tm_year % 10; + reg_val |= ((greg_time.tm_year / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_YEAR, reg_val, 0xff)); + + DPRINTK("set time = %d y, %d m, %d d, %d h, %d m, %d s", + greg_time.tm_year, greg_time.tm_mon, greg_time.tm_mday, + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_set_time); + +/*! + * This function get the real time clock of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time(struct timeval *pmic_time) +{ + unsigned int reg_val; + struct rtc_time greg_time; + unsigned long time = 0; + + CHECK_ERROR(pmic_read_reg(REG_MCU_SECS, ®_val, 0x7f)); + greg_time.tm_sec = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_MINS, ®_val, 0x7f)); + greg_time.tm_min = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_HRS, ®_val, 0x3f)); + greg_time.tm_hour = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_DAY, ®_val, 0x3)); + greg_time.tm_wday = reg_val; + + CHECK_ERROR(pmic_read_reg(REG_MCU_DATE, ®_val, 0x3f)); + greg_time.tm_mday = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_MONTH, ®_val, 0x1f)); + greg_time.tm_mon = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_YEAR, ®_val, 0xff)); + greg_time.tm_year = BCD2BIN(reg_val); + + DPRINTK("get time = %d y, %d m, %d d, %d h, %d m, %d s", + greg_time.tm_year, greg_time.tm_mon, greg_time.tm_mday, + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + + rtc_tm_to_time(&greg_time, &time); + pmic_time->tv_sec = time; + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_get_time); + +/*! + * This function set the real time clock alarm of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time_alarm(struct timeval *pmic_time) +{ + u8 reg_val; + struct rtc_time greg_time; + + down_interruptible(&mutex); + + rtc_time_to_tm(pmic_time->tv_sec, &greg_time); + rtc_time_to_tm(pmic_time->tv_sec, &alarm_greg_time); + + reg_val = greg_time.tm_sec % 10; + reg_val |= ((greg_time.tm_sec / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_SECS, reg_val, 0x7f)); + + reg_val = greg_time.tm_min % 10; + reg_val |= ((greg_time.tm_min / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_MINS, reg_val, 0x7f)); + + reg_val = greg_time.tm_hour % 10; + reg_val |= ((greg_time.tm_hour / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_HRS, reg_val, 0x3f)); + + /* enable alarm */ + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_SECS, 0x80, 0x80)); + DPRINTK("set alarm time = %dh, %dm, %ds\n", + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + + up(&mutex); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_set_time_alarm); + +/*! + * This function get the real time clock alarm of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time_alarm(struct timeval *pmic_time) +{ + unsigned int reg_val; + struct rtc_time greg_time; + unsigned long time = 0; + + memzero(&greg_time, sizeof(struct rtc_time)); + greg_time = alarm_greg_time; + + CHECK_ERROR(pmic_read_reg(REG_MCU_ALARM_SECS, ®_val, 0x7f)); + greg_time.tm_sec = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_ALARM_MINS, ®_val, 0x7f)); + greg_time.tm_min = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_ALARM_HRS, ®_val, 0x3f)); + greg_time.tm_hour = BCD2BIN(reg_val); + + rtc_tm_to_time(&greg_time, &time); + pmic_time->tv_sec = time; + DPRINTK("get alarm time = %dh, %dm, %ds\n", + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_get_time_alarm); + +/*! + * This function is used to un/subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event(t_rtc_int event, void *callback, bool sub) +{ + type_event rtc_event; + if (callback == NULL) { + return PMIC_ERROR; + } else { + rtc_callback.func = callback; + rtc_callback.param = NULL; + } + switch (event) { + case RTC_IT_ALARM: + rtc_event = EVENT_RTC; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub) + CHECK_ERROR(pmic_event_subscribe(rtc_event, rtc_callback)); + else + CHECK_ERROR(pmic_event_unsubscribe(rtc_event, rtc_callback)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_sub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, true)); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_event_sub); + +/*! + * This function is used to un subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_unsub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, false)); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_event_unsub); + +/* Called without the kernel lock - fine */ +static unsigned int pmic_rtc_poll(struct file *file, poll_table *wait) +{ + if (pmic_rtc_done) + return POLLIN | POLLRDNORM; + return 0; +} + +/*! + * This function implements IOCTL controls on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct timeval *pmic_time = NULL; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + if (arg) { + pmic_time = kmalloc(sizeof(struct timeval), GFP_KERNEL); + if (pmic_time == NULL) + return -ENOMEM; + + /* if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } */ + } + + switch (cmd) { + case PMIC_RTC_SET_TIME: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("SET RTC\n"); + CHECK_ERROR(pmic_rtc_set_time(pmic_time)); + break; + case PMIC_RTC_GET_TIME: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("GET RTC\n"); + CHECK_ERROR(pmic_rtc_get_time(pmic_time)); + break; + case PMIC_RTC_SET_ALARM: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("SET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_set_time_alarm(pmic_time)); + break; + case PMIC_RTC_GET_ALARM: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("GET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_get_time_alarm(pmic_time)); + break; + case PMIC_RTC_WAIT_ALARM: + printk(KERN_INFO "WAIT ALARM...\n"); + CHECK_ERROR(pmic_rtc_event_sub(RTC_IT_ALARM, + callback_test_sub)); + CHECK_ERROR(pmic_rtc_wait_alarm()); + printk(KERN_INFO "ALARM DONE\n"); + CHECK_ERROR(pmic_rtc_event_unsub(RTC_IT_ALARM, + callback_test_sub)); + break; + case PMIC_RTC_ALARM_REGISTER: + printk(KERN_INFO "PMIC RTC ALARM REGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_RTC, alarm_callback)); + break; + case PMIC_RTC_ALARM_UNREGISTER: + printk(KERN_INFO "PMIC RTC ALARM UNREGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_unsubscribe(EVENT_RTC, alarm_callback)); + pmic_rtc_done = false; + break; + default: + pr_debug("%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + if (arg) { + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) + return -EFAULT; + + kfree(pmic_time); + } + + return 0; +} + +/*! + * This function implements the open method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function implements the release method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function is called to put the RTC in a low power state. + * There is no need for power handlers for the RTC device. + * The RTC cannot be suspended. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +/*! + * This function is called to resume the RTC from a low power state. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_rtc_resume(struct platform_device *pdev) +{ + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ + +static struct file_operations pmic_rtc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_rtc_ioctl, + .poll = pmic_rtc_poll, + .open = pmic_rtc_open, + .release = pmic_rtc_release, +}; + +int pmic_rtc_loaded(void) +{ + return pmic_rtc_detected; +} +EXPORT_SYMBOL(pmic_rtc_loaded); + + +static int pmic_rtc_remove(struct platform_device *pdev) +{ + class_device_destroy(pmic_rtc_class, MKDEV(pmic_rtc_major, 0)); + class_destroy(pmic_rtc_class); + unregister_chrdev(pmic_rtc_major, MCU_PMIC_RTC_NAME); + return 0; +} + +static int pmic_rtc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct class_device *temp_class; + + pmic_rtc_major = register_chrdev(0, MCU_PMIC_RTC_NAME, &pmic_rtc_fops); + if (pmic_rtc_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_rtc\n"); + return pmic_rtc_major; + } + + pmic_rtc_class = class_create(THIS_MODULE, MCU_PMIC_RTC_NAME); + if (IS_ERR(pmic_rtc_class)) { + printk(KERN_ERR "Error creating pmic rtc class.\n"); + ret = PTR_ERR(pmic_rtc_class); + goto err_out1; + } + + temp_class = class_device_create(pmic_rtc_class, NULL, + MKDEV(pmic_rtc_major, 0), + NULL, MCU_PMIC_RTC_NAME); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic rtc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_rtc_detected = 1; + + /* start RTC */ + pmic_write_reg(REG_MCU_SECS, 0x80, 0x80); + + return ret; + +err_out2: + class_destroy(pmic_rtc_class); +err_out1: + unregister_chrdev(pmic_rtc_major, MCU_PMIC_RTC_NAME); + return ret; +} + +static struct platform_driver pmic_rtc_driver_ldm = { + .driver = { + .name = MCU_PMIC_RTC_NAME, + .owner = THIS_MODULE, + }, + .suspend = pmic_rtc_suspend, + .resume = pmic_rtc_resume, + .probe = pmic_rtc_probe, + .remove = pmic_rtc_remove, +}; + +static int __init pmic_rtc_init(void) +{ + return platform_driver_register(&pmic_rtc_driver_ldm); +} +static void __exit pmic_rtc_exit(void) +{ + platform_driver_unregister(&pmic_rtc_driver_ldm); + pr_debug("PMIC RTC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_rtc_init); +module_exit(pmic_rtc_exit); + +MODULE_DESCRIPTION("Pmic_rtc driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); |