From c658af067216c103bf0d9b399c565ea6ac2bf795 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 31 Mar 2011 09:07:20 +0200 Subject: mach-ux500: add HREFv60 Kconfig option This is necessary to have any use of the HREFv60 code. Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/Kconfig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index f8b9392ee34..96d546cef06 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -20,7 +20,7 @@ config UX500_SOC_DB8500 endmenu -menu "Ux500 target platform" +menu "Ux500 target platform (boards)" config MACH_U8500 bool "U8500 Development platform" @@ -29,6 +29,12 @@ config MACH_U8500 help Include support for the mop500 development platform. +config MACH_HREFV60 + bool "U85000 Development platform, HREFv60 version" + depends on UX500_SOC_DB8500 + help + Include support for the HREFv60 new development platform. + config MACH_U5500 bool "U5500 Development platform" depends on UX500_SOC_DB5500 -- cgit v1.2.3 From 9ac15a20225e9f88c6e6b10b9b207104fcdd988b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 27 Apr 2011 12:55:37 +0200 Subject: mach-ux500: fix HREFv60 regression This fixes a regression on the HREFv60 ux500 hardware: the wrong level shifter was addressed in the MMCI vdd handler, trying to reconfigure an unclaimed GPIO pin. Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/board-mop500-sdi.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-sdi.c b/arch/arm/mach-ux500/board-mop500-sdi.c index 7c6cb4fa47a..f8b195063b6 100644 --- a/arch/arm/mach-ux500/board-mop500-sdi.c +++ b/arch/arm/mach-ux500/board-mop500-sdi.c @@ -32,13 +32,32 @@ #define MCI_DATA31DIREN (1 << 5) #define MCI_FBCLKEN (1 << 7) +/* GPIO pins used by the sdi0 level shifter */ +static int sdi0_en = -1; +static int sdi0_vsel = -1; + static u32 mop500_sdi0_vdd_handler(struct device *dev, unsigned int vdd, unsigned char power_mode) { - if (power_mode == MMC_POWER_UP) - gpio_set_value_cansleep(GPIO_SDMMC_EN, 1); - else if (power_mode == MMC_POWER_OFF) - gpio_set_value_cansleep(GPIO_SDMMC_EN, 0); + switch (power_mode) { + case MMC_POWER_UP: + case MMC_POWER_ON: + /* + * Level shifter voltage should depend on vdd to when deciding + * on either 1.8V or 2.9V. Once the decision has been made the + * level shifter must be disabled and re-enabled with a changed + * select signal in order to switch the voltage. Since there is + * no framework support yet for indicating 1.8V in vdd, use the + * default 2.9V. + */ + gpio_direction_output(sdi0_vsel, 0); + gpio_direction_output(sdi0_en, 1); + break; + case MMC_POWER_OFF: + gpio_direction_output(sdi0_vsel, 0); + gpio_direction_output(sdi0_en, 0); + break; + } return MCI_FBCLKEN | MCI_CMDDIREN | MCI_DATA0DIREN | MCI_DATA2DIREN | MCI_DATA31DIREN; @@ -77,10 +96,6 @@ static struct mmci_platform_data mop500_sdi0_data = { #endif }; -/* GPIO pins used by the sdi0 level shifter */ -static int sdi0_en = -1; -static int sdi0_vsel = -1; - static void sdi0_configure(void) { int ret; @@ -210,6 +225,7 @@ void __init mop500_sdi_init(void) sdi0_vsel = HREFV60_SDMMC_1V8_3V_GPIO; sdi0_configure(); } + /* * On boards with the TC35892 GPIO expander, sdi0 will finally * be added when the TC35892 initializes and calls -- cgit v1.2.3 From ea3c5984635a4415337d62859ba48081b9af8ad5 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 30 Mar 2011 16:00:39 +0200 Subject: mach-ux500: correct MMC/SDI parameters We cannot clock the MMCI blocks more than 50 MHz. A bug prevented us from seeing the effect of actually driving them to 100 MHz, which indeed resulted failure, on the external SD card. Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/board-mop500-sdi.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-sdi.c b/arch/arm/mach-ux500/board-mop500-sdi.c index f8b195063b6..5fbd6bc63cb 100644 --- a/arch/arm/mach-ux500/board-mop500-sdi.c +++ b/arch/arm/mach-ux500/board-mop500-sdi.c @@ -86,8 +86,10 @@ static struct stedma40_chan_cfg mop500_sdi0_dma_cfg_tx = { static struct mmci_platform_data mop500_sdi0_data = { .vdd_handler = mop500_sdi0_vdd_handler, .ocr_mask = MMC_VDD_29_30, - .f_max = 100000000, - .capabilities = MMC_CAP_4_BIT_DATA, + .f_max = 50000000, + .capabilities = MMC_CAP_4_BIT_DATA | + MMC_CAP_SD_HIGHSPEED | + MMC_CAP_MMC_HIGHSPEED, .gpio_wp = -1, #ifdef CONFIG_STE_DMA40 .dma_filter = stedma40_filter, @@ -155,7 +157,7 @@ static struct stedma40_chan_cfg mop500_sdi2_dma_cfg_tx = { static struct mmci_platform_data mop500_sdi2_data = { .ocr_mask = MMC_VDD_165_195, - .f_max = 100000000, + .f_max = 50000000, .capabilities = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA, .gpio_cd = -1, .gpio_wp = -1, @@ -192,7 +194,7 @@ static struct stedma40_chan_cfg mop500_sdi4_dma_cfg_tx = { static struct mmci_platform_data mop500_sdi4_data = { .ocr_mask = MMC_VDD_29_30, - .f_max = 100000000, + .f_max = 50000000, .capabilities = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA | MMC_CAP_MMC_HIGHSPEED, .gpio_cd = -1, -- cgit v1.2.3 From 515db8eedddaab8d117d96042f6b1e536dfaeae1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 1 Mar 2011 16:09:52 +0100 Subject: mach-ux500: activate USB in the U8500 defconfig Activate the new USB stuff so we atleast get some compile coverage for this stuff. Signed-off-by: Linus Walleij --- arch/arm/configs/u8500_defconfig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index a5cce242a77..e1d602029a4 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -67,7 +67,11 @@ CONFIG_AB8500_CORE=y CONFIG_REGULATOR=y CONFIG_REGULATOR_AB8500=y # CONFIG_HID_SUPPORT is not set -# CONFIG_USB_SUPPORT is not set +CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_GADGET_MUSB_HDRC=y +CONFIG_MUSB_PIO_ONLY=y +CONFIG_USB_GADGET=y +CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y -- cgit v1.2.3 From c9bd510d515b1d08e13032d5bed35b823cdc8962 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 4 Apr 2011 10:44:51 +0200 Subject: mach-ux500: complete regulator constraints for MOP500 board This board now has complete regulation constraints and can turn off unused regulators. For the moment we need to wire VAUX1 (V-DISPLAY rail) always on since it somehow affects the external MMC. Cc: Liam Girdwood Cc: Mark Brown Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/board-mop500-regulators.c | 9 ++++++++- arch/arm/mach-ux500/board-mop500.c | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-ux500/board-mop500-regulators.c b/arch/arm/mach-ux500/board-mop500-regulators.c index 9ed0f90cfe2..c0bc833df90 100644 --- a/arch/arm/mach-ux500/board-mop500-regulators.c +++ b/arch/arm/mach-ux500/board-mop500-regulators.c @@ -272,7 +272,14 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { .max_uV = 2900000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, - .boot_on = 1, /* must be on for display */ + .boot_on = 1, /* display is on at boot */ + /* + * This voltage cannot be disabled right now because + * it is somehow affecting the external MMC + * functionality, though that typically will use + * AUX3. + */ + .always_on = 1, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux1_consumers), .consumer_supplies = ab8500_vaux1_consumers, diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 2a08c07dec6..096c41641aa 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -512,6 +512,9 @@ static void __init mop500_init_machine(void) i2c_register_board_info(0, mop500_i2c0_devices, i2c0_devs); i2c_register_board_info(2, mop500_i2c2_devices, ARRAY_SIZE(mop500_i2c2_devices)); + + /* This board has full regulator constraints */ + regulator_has_full_constraints(); } MACHINE_START(U8500, "ST-Ericsson MOP500 platform") -- cgit v1.2.3 From 444640b6e3d813a075fc8d24fc85a6be0db00410 Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Thu, 9 Jun 2011 11:50:35 +0200 Subject: mach-ux500: iomap PRCMU TCDM memory The PRCMU TCDM memory needs to be iomapped for the PRCMU to work properly. Signed-off-by: Mattias Wallin Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/cpu-db5500.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-ux500/cpu-db5500.c b/arch/arm/mach-ux500/cpu-db5500.c index c01bc19e3c5..22705d246fc 100644 --- a/arch/arm/mach-ux500/cpu-db5500.c +++ b/arch/arm/mach-ux500/cpu-db5500.c @@ -44,6 +44,7 @@ static struct map_desc u5500_io_desc[] __initdata = { __IO_DEV_DESC(U5500_GPIO3_BASE, SZ_4K), __IO_DEV_DESC(U5500_GPIO4_BASE, SZ_4K), __IO_DEV_DESC(U5500_PRCMU_BASE, SZ_4K), + __IO_DEV_DESC(U5500_PRCMU_TCDM_BASE, SZ_4K), }; static struct resource db5500_pmu_resources[] = { -- cgit v1.2.3 From c16efff24648f2e0a9e71216e5a124ab86da1b00 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 20 Jun 2011 15:55:46 +0200 Subject: mach-ux500: Add basic support for snowball board Based on work from Mathieu J. Poirier For more information on snowball please visit http://www.igloocommunity.org Signed-off-by: Robert Marklund --- arch/arm/mach-ux500/board-mop500.c | 154 ++++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 9 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 096c41641aa..cca0badeeaf 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -26,9 +26,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -47,6 +49,26 @@ #include "board-mop500.h" #include "board-mop500-regulators.h" +static struct gpio_led snowball_led_array[] = { + { + .name = "user_led", + .default_trigger = "none", + .gpio = 142, + }, +}; + +static struct gpio_led_platform_data snowball_led_data = { + .leds = snowball_led_array, + .num_leds = ARRAY_SIZE(snowball_led_array), +}; + +static struct platform_device snowball_led_dev = { + .name = "leds-gpio", + .dev = { + .platform_data = &snowball_led_data, + }, +}; + static struct ab8500_gpio_platform_data ab8500_gpio_pdata = { .gpio_base = MOP500_AB8500_GPIO(0), .irq_base = MOP500_AB8500_VIR_GPIO_IRQ_BASE, @@ -69,6 +91,97 @@ static struct ab8500_gpio_platform_data ab8500_gpio_pdata = { 0x7A, 0x00, 0x00}, }; +static struct gpio_keys_button snowball_key_array[] = { + { + .gpio = 32, + .type = EV_KEY, + .code = KEY_1, + .desc = "userpb", + .active_low = 1, + .debounce_interval = 50, + .wakeup = 1, + }, + { + .gpio = 151, + .type = EV_KEY, + .code = KEY_2, + .desc = "extkb1", + .active_low = 1, + .debounce_interval = 50, + .wakeup = 1, + }, + { + .gpio = 152, + .type = EV_KEY, + .code = KEY_3, + .desc = "extkb2", + .active_low = 1, + .debounce_interval = 50, + .wakeup = 1, + }, + { + .gpio = 161, + .type = EV_KEY, + .code = KEY_4, + .desc = "extkb3", + .active_low = 1, + .debounce_interval = 50, + .wakeup = 1, + }, + { + .gpio = 162, + .type = EV_KEY, + .code = KEY_5, + .desc = "extkb4", + .active_low = 1, + .debounce_interval = 50, + .wakeup = 1, + }, +}; + +static struct gpio_keys_platform_data snowball_key_data = { + .buttons = snowball_key_array, + .nbuttons = ARRAY_SIZE(snowball_key_array), +}; + +static struct platform_device snowball_key_dev = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &snowball_key_data, + } +}; + +static struct smsc911x_platform_config snowball_sbnet_cfg = { + .irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_HIGH, + .irq_type = SMSC911X_IRQ_TYPE_PUSH_PULL, + .flags = SMSC911X_USE_16BIT | SMSC911X_FORCE_INTERNAL_PHY, + .shift = 1, +}; + +static struct resource sbnet_res[] = { + { + .name = "smsc911x-memory", + .start = (0x5000 << 16), + .end = (0x5000 << 16) + 0xffff, + .flags = IORESOURCE_MEM, + }, + { + .start = NOMADIK_GPIO_TO_IRQ(140), + .end = NOMADIK_GPIO_TO_IRQ(140), + .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE, + }, +}; + +static struct platform_device snowball_sbnet_dev = { + .name = "smsc911x", + .num_resources = ARRAY_SIZE(sbnet_res), + .resource = sbnet_res, + .dev = { + .platform_data = &snowball_sbnet_cfg, + }, +}; + static struct ab8500_platform_data ab8500_platdata = { .irq_base = MOP500_AB8500_IRQ_BASE, .regulator_reg_init = ab8500_regulator_reg_init, @@ -295,8 +408,9 @@ static void mop500_prox_deactivate(struct device *dev) } /* add any platform devices here - TODO */ -static struct platform_device *platform_devs[] __initdata = { +static struct platform_device *mop500_platform_devs[] __initdata = { &mop500_gpio_keys_device, + &ab8500_device, }; #ifdef CONFIG_STE_DMA40 @@ -478,6 +592,13 @@ static void __init mop500_uart_init(void) db8500_add_uart2(&uart2_plat); } +static struct platform_device *snowball_platform_devs[] __initdata = { + &snowball_led_dev, + &snowball_key_dev, + &snowball_sbnet_dev, + &ab8500_device, +}; + static void __init mop500_init_machine(void) { int i2c0_devs; @@ -487,24 +608,30 @@ static void __init mop500_init_machine(void) * all these GPIO pins to the internal GPIO controller * instead. */ - if (machine_is_hrefv60()) - mop500_gpio_keys[0].gpio = HREFV60_PROX_SENSE_GPIO; - else - mop500_gpio_keys[0].gpio = GPIO_PROX_SENSOR; + if (!machine_is_snowball()) { + if (machine_is_hrefv60()) + mop500_gpio_keys[0].gpio = HREFV60_PROX_SENSE_GPIO; + else + mop500_gpio_keys[0].gpio = GPIO_PROX_SENSOR; + } u8500_init_devices(); mop500_pins_init(); - platform_add_devices(platform_devs, ARRAY_SIZE(platform_devs)); + if (machine_is_snowball()) + platform_add_devices(snowball_platform_devs, + ARRAY_SIZE(snowball_platform_devs)); + else + platform_add_devices(mop500_platform_devs, + ARRAY_SIZE(mop500_platform_devs)); mop500_i2c_init(); - mop500_sdi_init(); + if (!machine_is_snowball()) + mop500_sdi_init(); mop500_spi_init(); mop500_uart_init(); - platform_device_register(&ab8500_device); - i2c0_devs = ARRAY_SIZE(mop500_i2c0_devices); if (machine_is_hrefv60()) i2c0_devs -= NUM_PRE_V60_I2C0_DEVICES; @@ -534,3 +661,12 @@ MACHINE_START(HREFV60, "ST-Ericsson U8500 Platform HREFv60+") .timer = &ux500_timer, .init_machine = mop500_init_machine, MACHINE_END + +MACHINE_START(SNOWBALL, "Calao Systems Snowball platform") + .boot_params = 0x100, + .map_io = u8500_map_io, + .init_irq = ux500_init_irq, + /* we re-use nomadik timer here */ + .timer = &ux500_timer, + .init_machine = mop500_init_machine, +MACHINE_END -- cgit v1.2.3 From 379fdbb2c9488ce185296bfea487d8aa37cc92cd Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Fri, 25 Mar 2011 09:28:56 -0600 Subject: mach-ux500: setting proper uart for snowball Signed-off-by: Mathieu Poirier Signed-off-by: Linus Walleij Signed-off-by: Robert Marklund --- arch/arm/mach-ux500/include/mach/uncompress.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-ux500/include/mach/uncompress.h b/arch/arm/mach-ux500/include/mach/uncompress.h index 088b550c40d..7dd08074c37 100644 --- a/arch/arm/mach-ux500/include/mach/uncompress.h +++ b/arch/arm/mach-ux500/include/mach/uncompress.h @@ -54,7 +54,8 @@ static inline void arch_decomp_setup(void) if (machine_is_u8500() || machine_is_svp8500v1() || machine_is_svp8500v2() || - machine_is_hrefv60()) + machine_is_hrefv60() || + machine_is_snowball()) ux500_uart_base = U8500_UART2_BASE; else if (machine_is_u5500()) ux500_uart_base = U5500_UART0_BASE; -- cgit v1.2.3 From 4f1da0d897befc09e65feb379383a708aa70cd91 Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Fri, 25 Mar 2011 09:28:58 -0600 Subject: mach-ux500: Add SDI support for snowball board Signed-off-by: Mathieu Poirier Signed-off-by: Linus Walleij Signed-off-by: Robert Marklund --- arch/arm/mach-ux500/board-mop500-sdi.c | 19 ++++++++++++++----- arch/arm/mach-ux500/board-mop500.c | 3 +-- arch/arm/mach-ux500/board-mop500.h | 5 +++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-sdi.c b/arch/arm/mach-ux500/board-mop500-sdi.c index 5fbd6bc63cb..d0cb9e5eb87 100644 --- a/arch/arm/mach-ux500/board-mop500-sdi.c +++ b/arch/arm/mach-ux500/board-mop500-sdi.c @@ -216,15 +216,24 @@ void __init mop500_sdi_init(void) /* PoP:ed eMMC on top of DB8500 v1.0 has problems with high speed */ if (!cpu_is_u8500v10()) mop500_sdi2_data.capabilities |= MMC_CAP_MMC_HIGHSPEED; - db8500_add_sdi2(&mop500_sdi2_data, periphid); + /* sdi2 on snowball is in ATL_B mode for FSMC (LAN) */ + if (!machine_is_snowball()) + db8500_add_sdi2(&mop500_sdi2_data, periphid); /* On-board eMMC */ db8500_add_sdi4(&mop500_sdi4_data, periphid); - if (machine_is_hrefv60()) { - mop500_sdi0_data.gpio_cd = HREFV60_SDMMC_CD_GPIO; - sdi0_en = HREFV60_SDMMC_EN_GPIO; - sdi0_vsel = HREFV60_SDMMC_1V8_3V_GPIO; + if (machine_is_hrefv60() || machine_is_snowball()) { + if (machine_is_hrefv60()) { + mop500_sdi0_data.gpio_cd = HREFV60_SDMMC_CD_GPIO; + sdi0_en = HREFV60_SDMMC_EN_GPIO; + sdi0_vsel = HREFV60_SDMMC_1V8_3V_GPIO; + } else if (machine_is_snowball()) { + mop500_sdi0_data.gpio_cd = SNOWBALL_SDMMC_CD_GPIO; + mop500_sdi0_data.cd_invert = true; + sdi0_en = SNOWBALL_SDMMC_EN_GPIO; + sdi0_vsel = SNOWBALL_SDMMC_1V8_3V_GPIO; + } sdi0_configure(); } diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index cca0badeeaf..cd54abaccd9 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -627,8 +627,7 @@ static void __init mop500_init_machine(void) ARRAY_SIZE(mop500_platform_devs)); mop500_i2c_init(); - if (!machine_is_snowball()) - mop500_sdi_init(); + mop500_sdi_init(); mop500_spi_init(); mop500_uart_init(); diff --git a/arch/arm/mach-ux500/board-mop500.h b/arch/arm/mach-ux500/board-mop500.h index 03a31cc9b08..ee77a8970c3 100644 --- a/arch/arm/mach-ux500/board-mop500.h +++ b/arch/arm/mach-ux500/board-mop500.h @@ -7,6 +7,11 @@ #ifndef __BOARD_MOP500_H #define __BOARD_MOP500_H +/* snowball GPIO for MMC card */ +#define SNOWBALL_SDMMC_EN_GPIO 217 +#define SNOWBALL_SDMMC_1V8_3V_GPIO 228 +#define SNOWBALL_SDMMC_CD_GPIO 218 + /* HREFv60-specific GPIO assignments, this board has no GPIO expander */ #define HREFV60_TOUCH_RST_GPIO 143 #define HREFV60_PROX_SENSE_GPIO 217 -- cgit v1.2.3 From ae632d3c3e638b26b2eaa2d1e07c968ca5465de6 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Tue, 21 Jun 2011 09:39:13 +0200 Subject: mach-ux500: Add pin configuration for snowball board Based on work from Mathieu J. Poirier Signed-off-by: Robert Marklund --- arch/arm/mach-ux500/board-mop500-pins.c | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/arch/arm/mach-ux500/board-mop500-pins.c b/arch/arm/mach-ux500/board-mop500-pins.c index 70cdbd60596..f26fd76f72b 100644 --- a/arch/arm/mach-ux500/board-mop500-pins.c +++ b/arch/arm/mach-ux500/board-mop500-pins.c @@ -236,6 +236,46 @@ static pin_cfg_t mop500_pins_hrefv60[] = { }; +static pin_cfg_t snowball_pins[] = { + /* SSP0, to AB8500 */ + GPIO143_SSP0_CLK, + GPIO144_SSP0_FRM, + GPIO145_SSP0_RXD | PIN_PULL_DOWN, + GPIO146_SSP0_TXD, + + /* MMC0: MicroSD card */ + GPIO21_MC0_DAT31DIR | PIN_OUTPUT_HIGH, + + /* MMC2: LAN */ + GPIO86_SM_ADQ0, + GPIO87_SM_ADQ1, + GPIO88_SM_ADQ2, + GPIO89_SM_ADQ3, + GPIO90_SM_ADQ4, + GPIO91_SM_ADQ5, + GPIO92_SM_ADQ6, + GPIO93_SM_ADQ7, + + GPIO94_SM_ADVn, + GPIO95_SM_CS0n, + GPIO96_SM_OEn, + GPIO97_SM_WEn, + + GPIO128_SM_CKO, + GPIO130_SM_FBCLK, + GPIO131_SM_ADQ8, + GPIO132_SM_ADQ9, + GPIO133_SM_ADQ10, + GPIO134_SM_ADQ11, + GPIO135_SM_ADQ12, + GPIO136_SM_ADQ13, + GPIO137_SM_ADQ14, + GPIO138_SM_ADQ15, + + /* RSTn_LAN */ + GPIO141_GPIO | PIN_OUTPUT_HIGH, +}; + void __init mop500_pins_init(void) { nmk_config_pins(mop500_pins_common, @@ -243,6 +283,9 @@ void __init mop500_pins_init(void) if (machine_is_hrefv60()) nmk_config_pins(mop500_pins_hrefv60, ARRAY_SIZE(mop500_pins_hrefv60)); + else if (machine_is_snowball()) + nmk_config_pins(snowball_pins, + ARRAY_SIZE(snowball_pins)); else nmk_config_pins(mop500_pins_default, ARRAY_SIZE(mop500_pins_default)); -- cgit v1.2.3 From dcb2e184ee9e108f61c24cf16febceefd6aa37cb Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Fri, 25 Mar 2011 09:29:01 -0600 Subject: mach-ux500: Kconfig for snowball board Signed-off-by: Mathieu Poirier Signed-off-by: Robert Marklund --- arch/arm/mach-ux500/Kconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index 96d546cef06..4210cb434db 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -35,6 +35,13 @@ config MACH_HREFV60 help Include support for the HREFv60 new development platform. +config MACH_SNOWBALL + bool "U8500 Snowball platform" + depends on UX500_SOC_DB8500 + select MACH_U8500 + help + Include support for the snowball development platform. + config MACH_U5500 bool "U5500 Development platform" depends on UX500_SOC_DB5500 -- cgit v1.2.3 From 17b642d1911852ef7cb91bf7ade1b8981e06e798 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Tue, 21 Jun 2011 14:01:02 +0200 Subject: config: u8500: Add configs for snowball board Signed-off-by: Robert Marklund --- arch/arm/configs/u8500_defconfig | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index e1d602029a4..97d31a4663d 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -11,12 +11,12 @@ CONFIG_ARCH_U8500=y CONFIG_UX500_SOC_DB5500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_U8500=y +CONFIG_MACH_SNOWBALL=y CONFIG_MACH_U5500=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y CONFIG_NR_CPUS=2 -CONFIG_HOTPLUG_CPU=y CONFIG_PREEMPT=y CONFIG_AEABI=y CONFIG_CMDLINE="root=/dev/ram0 console=ttyAMA2,115200n8" @@ -25,8 +25,13 @@ CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y CONFIG_VFP=y CONFIG_NEON=y CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_NETFILTER=y CONFIG_PHONET=y -CONFIG_PHONET_PIPECTRLR=y # CONFIG_WIRELESS is not set CONFIG_CAIF=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" @@ -35,6 +40,13 @@ CONFIG_BLK_DEV_RAM_SIZE=65536 CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y +CONFIG_NETDEVICES=y +CONFIG_SMSC_PHY=y +CONFIG_NET_ETHERNET=y +CONFIG_SMSC911X=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_WLAN is not set # CONFIG_INPUT_MOUSEDEV_PSAUX is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set @@ -49,9 +61,9 @@ CONFIG_INPUT_MISC=y CONFIG_INPUT_AB8500_PONKEY=y # CONFIG_SERIO is not set CONFIG_VT_HW_CONSOLE_BINDING=y +# CONFIG_LEGACY_PTYS is not set CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y -# CONFIG_LEGACY_PTYS is not set CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_NOMADIK=y CONFIG_I2C=y @@ -64,7 +76,6 @@ CONFIG_GPIO_TC3589X=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y CONFIG_AB8500_CORE=y -CONFIG_REGULATOR=y CONFIG_REGULATOR_AB8500=y # CONFIG_HID_SUPPORT is not set CONFIG_USB_MUSB_HDRC=y @@ -73,9 +84,11 @@ CONFIG_MUSB_PIO_ONLY=y CONFIG_USB_GADGET=y CONFIG_AB8500_USB=y CONFIG_MMC=y +CONFIG_MMC_CLKGATE=y CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y +CONFIG_LEDS_LM3530=y CONFIG_LEDS_LP5521=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_AB8500=y @@ -83,7 +96,6 @@ CONFIG_RTC_DRV_PL031=y CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y -# CONFIG_STAGING_EXCLUDE_BUILD is not set CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y @@ -95,6 +107,8 @@ CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_CONFIGFS_FS=m # CONFIG_MISC_FILESYSTEMS is not set +CONFIG_NFS_FS=y +CONFIG_ROOT_NFS=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y CONFIG_MAGIC_SYSRQ=y @@ -103,7 +117,5 @@ CONFIG_DEBUG_KERNEL=y # CONFIG_SCHED_DEBUG is not set # CONFIG_DEBUG_PREEMPT is not set CONFIG_DEBUG_INFO=y -# CONFIG_RCU_CPU_STALL_DETECTOR is not set # CONFIG_FTRACE is not set CONFIG_DEBUG_USER=y -CONFIG_DEBUG_ERRORS=y -- cgit v1.2.3 From 5cf44fa4c756b18b72ea58a78d0801f1de04f8ca Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 20 Jun 2011 13:30:35 +0200 Subject: ux500: Fix SECTION warnings in uib Signed-off-by: Robert Marklund --- arch/arm/mach-ux500/board-mop500-uib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/mach-ux500/board-mop500-uib.c b/arch/arm/mach-ux500/board-mop500-uib.c index 69cce41f602..5af36aa56c0 100644 --- a/arch/arm/mach-ux500/board-mop500-uib.c +++ b/arch/arm/mach-ux500/board-mop500-uib.c @@ -25,7 +25,7 @@ struct uib { void (*init)(void); }; -static struct __initdata uib mop500_uibs[] = { +static struct uib __initdata mop500_uibs[] = { [STUIB] = { .name = "ST-UIB", .option = "stuib", -- cgit v1.2.3 From e4de5cdbbb3249cd41e8b40278f86dee9600fb2e Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Fri, 21 Jan 2011 15:50:36 +0530 Subject: staging: synaptics: Implement error handling for rmi4 touch regulator Implement the error handling for regulator in synaptics rmi4 touch screen Signed-off-by: Naveen Kumar Gaddipati --- drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c | 43 +++++++++++++-------------- drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h | 2 -- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c index 3e68d58fdff..36f4cb77567 100644 --- a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c +++ b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c @@ -926,17 +926,19 @@ static int __devinit synaptics_rmi4_probe goto err_input; } - if (platformdata->regulator_en) { - rmi4_data->regulator = regulator_get(&client->dev, "vdd"); - if (IS_ERR(rmi4_data->regulator)) { - dev_err(&client->dev, "%s:get regulator failed\n", - __func__); - retval = PTR_ERR(rmi4_data->regulator); - goto err_regulator; - } - regulator_enable(rmi4_data->regulator); + rmi4_data->regulator = regulator_get(&client->dev, "vdd"); + if (IS_ERR(rmi4_data->regulator)) { + dev_err(&client->dev, "%s:get regulator failed\n", + __func__); + retval = PTR_ERR(rmi4_data->regulator); + goto err_get_regulator; + } + retval = regulator_enable(rmi4_data->regulator); + if (retval < 0) { + dev_err(&client->dev, "%s:regulator enable failed\n", + __func__); + goto err_regulator_enable; } - init_waitqueue_head(&rmi4_data->wait); /* * Copy i2c_client pointer into RTID's i2c_client pointer for @@ -1011,11 +1013,10 @@ static int __devinit synaptics_rmi4_probe err_free_irq: free_irq(platformdata->irq_number, rmi4_data); err_query_dev: - if (platformdata->regulator_en) { - regulator_disable(rmi4_data->regulator); - regulator_put(rmi4_data->regulator); - } -err_regulator: + regulator_disable(rmi4_data->regulator); +err_regulator_enable: + regulator_put(rmi4_data->regulator); +err_get_regulator: input_free_device(rmi4_data->input_dev); rmi4_data->input_dev = NULL; err_input: @@ -1039,10 +1040,8 @@ static int __devexit synaptics_rmi4_remove(struct i2c_client *client) wake_up(&rmi4_data->wait); free_irq(pdata->irq_number, rmi4_data); input_unregister_device(rmi4_data->input_dev); - if (pdata->regulator_en) { - regulator_disable(rmi4_data->regulator); - regulator_put(rmi4_data->regulator); - } + regulator_disable(rmi4_data->regulator); + regulator_put(rmi4_data->regulator); kfree(rmi4_data); return 0; @@ -1080,8 +1079,7 @@ static int synaptics_rmi4_suspend(struct device *dev) if (retval < 0) return retval; - if (pdata->regulator_en) - regulator_disable(rmi4_data->regulator); + regulator_disable(rmi4_data->regulator); return 0; } @@ -1099,8 +1097,7 @@ static int synaptics_rmi4_resume(struct device *dev) struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; - if (pdata->regulator_en) - regulator_enable(rmi4_data->regulator); + regulator_enable(rmi4_data->regulator); enable_irq(pdata->irq_number); rmi4_data->touch_stopped = false; diff --git a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h index 3686a2ff596..384436ef806 100644 --- a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h +++ b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h @@ -34,7 +34,6 @@ * @irq_type: irq type * @x flip: x flip flag * @y flip: y flip flag - * @regulator_en: regulator enable flag * * This structure gives platform data for rmi4. */ @@ -43,7 +42,6 @@ struct synaptics_rmi4_platform_data { int irq_type; bool x_flip; bool y_flip; - bool regulator_en; }; #endif -- cgit v1.2.3 From f032d2dd9be6c8fcb6fa7b2e33c4bd572f9aa0db Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 25 Jan 2011 16:50:09 +0100 Subject: mach-ux500: remove invalid RMI4 data The platform data for ux500 RMI4 needs updating to match Naveens latest changes to the staging driver. Cc: Naveen Kumar G Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/board-mop500-u8500uib.c | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm/mach-ux500/board-mop500-u8500uib.c b/arch/arm/mach-ux500/board-mop500-u8500uib.c index d8a8734a0eb..1af72289303 100644 --- a/arch/arm/mach-ux500/board-mop500-u8500uib.c +++ b/arch/arm/mach-ux500/board-mop500-u8500uib.c @@ -32,7 +32,6 @@ static struct synaptics_rmi4_platform_data rmi4_i2c_dev_platformdata = { .irq_type = (IRQF_TRIGGER_FALLING | IRQF_SHARED), .x_flip = false, .y_flip = true, - .regulator_en = false, }; static struct i2c_board_info __initdata mop500_i2c3_devices_u8500[] = { -- cgit v1.2.3 From c53f6cc82fe6b728b460fa25994ebb0342ab02a0 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Wed, 12 May 2010 11:55:51 +0200 Subject: Added commandline partitions for block devices Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/305 Reviewed-by: Jonas ABERG Reviewed-by: Linus WALLEIJ Tested-by: Linus WALLEIJ Signed-off-by: Mian Yousaf Kaukab Change-Id: I3725676a9177b0072a435865ae578a6e17bd7168 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2206 --- fs/partitions/Kconfig | 19 +++++++ fs/partitions/Makefile | 1 + fs/partitions/blkdev_parts.c | 127 +++++++++++++++++++++++++++++++++++++++++++ fs/partitions/blkdev_parts.h | 14 +++++ fs/partitions/check.c | 4 ++ 5 files changed, 165 insertions(+) create mode 100755 fs/partitions/blkdev_parts.c create mode 100755 fs/partitions/blkdev_parts.h diff --git a/fs/partitions/Kconfig b/fs/partitions/Kconfig index cb5f0a3f1b0..097be1934ee 100644 --- a/fs/partitions/Kconfig +++ b/fs/partitions/Kconfig @@ -68,6 +68,25 @@ config ACORN_PARTITION_RISCIX of machines called RISCiX. If you say 'Y' here, Linux will be able to read disks partitioned under RISCiX. +config BLKDEV_PARTITION + bool "Blockdev commandline partition support" if PARTITION_ADVANCED + default n + help + Say Y if you like to setup partitions for block devices by reading + from the kernel command line (kernel boot arguments). + + The format of the partitions on the command line: + blkdevparts=[;] + := :[,] + := [@] + + := unique id used to map driver to blockdev name + := size in numbers of sectors + := offset in sectors for partition to start at + + Example: + blkdevparts=mmc0:1024@0,524288@1024;mmc1:8192@0,8192@8192 + config OSF_PARTITION bool "Alpha OSF partition support" if PARTITION_ADVANCED default y if ALPHA diff --git a/fs/partitions/Makefile b/fs/partitions/Makefile index 03af8eac51d..48b216c53db 100644 --- a/fs/partitions/Makefile +++ b/fs/partitions/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_BLOCK) := check.o obj-$(CONFIG_ACORN_PARTITION) += acorn.o obj-$(CONFIG_AMIGA_PARTITION) += amiga.o obj-$(CONFIG_ATARI_PARTITION) += atari.o +obj-$(CONFIG_BLKDEV_PARTITION) += blkdev_parts.o obj-$(CONFIG_MAC_PARTITION) += mac.o obj-$(CONFIG_LDM_PARTITION) += ldm.o obj-$(CONFIG_MSDOS_PARTITION) += msdos.o diff --git a/fs/partitions/blkdev_parts.c b/fs/partitions/blkdev_parts.c new file mode 100755 index 00000000000..48f4136d720 --- /dev/null +++ b/fs/partitions/blkdev_parts.c @@ -0,0 +1,127 @@ +/* + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ulf Hansson for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * + * Create partitions for block devices by reading from the kernel + * command line (kernel boot arguments). + * + */ + +#include "check.h" +#include "blkdev_parts.h" + +static char *cmdline; + +/* + * This is the handler for our kernel commandline parameter, + * called from main.c::checksetup(). + * Note that we can not yet kmalloc() anything, so we only save + * the commandline for later processing. + */ +static int cmdline_setup(char *s) +{ + cmdline = s; + return 1; +} +__setup("blkdevparts=", cmdline_setup); + +/* Parse for a matching blkdev-id and return pointer to partdef */ +static char *parse_blkdev_id(char *blkdev_name) +{ + int blkdev_id_len; + char *p, *blkdev_id; + + /* Start parsing for a matching blkdev-id */ + p = blkdev_id = cmdline; + while (blkdev_id != NULL) { + + /* Find the end of the blkdev-id string */ + p = strchr(blkdev_id, ':'); + if (p == NULL) + return NULL; + + /* Check if we found a matching blkdev-id */ + blkdev_id_len = p - blkdev_id; + if (strlen(blkdev_name) == blkdev_id_len) { + if (strncmp(blkdev_name, blkdev_id, blkdev_id_len) == 0) + return p; + } + + /* Move to next blkdev-id string if there is one */ + blkdev_id = strchr(p, ';'); + if (blkdev_id != NULL) + blkdev_id++; + } + return NULL; +} + +static int parse_partdef(char **part, struct parsed_partitions *state, int part_nbr) +{ + sector_t size, offset; + char *p = *part; + + /* Skip the beginning "," or ":" */ + p++; + + /* Fetch and verify size from partdef */ + size = simple_strtoull(p, &p, 10); + if ((size == 0) || (*p != '@')) + return 0; + + /* Skip the "@" */ + p++; + + /* Fetch offset from partdef and check if there are more parts */ + offset = simple_strtoull(p, &p, 10); + if (*p == ',') + *part = p; + else + *part = NULL; + + /* Add partition to state */ + put_partition(state, part_nbr, offset, size); + printk(KERN_INFO "\nPartition: size=%llu, offset=%llu\n", + (unsigned long long) size, + (unsigned long long) offset); + return 1; +} + +static int parse_blkdev_parts(char *blkdev_name, struct parsed_partitions *state) +{ + char *partdef; + int part_nbr = 0; + + /* Find partdef */ + partdef = parse_blkdev_id(blkdev_name); + + /* Add parts */ + while (partdef != NULL) { + /* Find next part and add it to state */ + part_nbr++; + if (!parse_partdef(&partdef, state, part_nbr)) + return 0; + } + return part_nbr; +} + +int blkdev_partition(struct parsed_partitions *state, struct block_device *bdev) +{ + char blkdev_name[BDEVNAME_SIZE]; + + /* Check if there are any partitions to handle */ + if (cmdline == NULL) + return 0; + + /* Get the name of the blockdevice we are operating upon */ + if (bdevname(bdev, blkdev_name) == NULL) { + printk(KERN_WARNING "Could not get a blkdev name\n"); + return 0; + } + + /* Parse for partitions and add them to the state */ + return parse_blkdev_parts(blkdev_name, state); +} + diff --git a/fs/partitions/blkdev_parts.h b/fs/partitions/blkdev_parts.h new file mode 100755 index 00000000000..e6c5d40ed76 --- /dev/null +++ b/fs/partitions/blkdev_parts.h @@ -0,0 +1,14 @@ +/* + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ulf Hansson for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * + * Create partitions for block devices by reading from the kernel + * command line (kernel boot arguments). + * + */ + +int blkdev_partition(struct parsed_partitions *state, struct block_device *bdev); + diff --git a/fs/partitions/check.c b/fs/partitions/check.c index d545e97d99c..f287f8608e1 100644 --- a/fs/partitions/check.c +++ b/fs/partitions/check.c @@ -27,6 +27,7 @@ #include "acorn.h" #include "amiga.h" #include "atari.h" +#include "blkdev_parts.h" #include "ldm.h" #include "mac.h" #include "msdos.h" @@ -50,6 +51,9 @@ static int (*check_part[])(struct parsed_partitions *) = { * Probe partition formats with tables at disk address 0 * that also have an ADFS boot block at 0xdc0. */ +#ifdef CONFIG_BLKDEV_PARTITION + blkdev_partition, +#endif #ifdef CONFIG_ACORN_PARTITION_ICS adfspart_check_ICS, #endif -- cgit v1.2.3 From 1360dbf0f15a9a2a1de669992203201492fbbd18 Mon Sep 17 00:00:00 2001 From: Robert Rosengren Date: Mon, 6 Dec 2010 13:58:42 +0100 Subject: fs/partitions: Adapt blkdevparts to new interface fs/partitions: Adapt blkdevparts to new interface of kernel 2.6.35 ST-Ericsson ID: ER282603 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I95fccf7f47fd5e4f716b6a0e6fec001d2c9428cf Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/10308 Tested-by: Robert ROSENGREN Reviewed-by: Ulf HANSSON Reviewed-by: Sebastian RASMUSSEN --- fs/partitions/blkdev_parts.c | 4 ++-- fs/partitions/blkdev_parts.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/partitions/blkdev_parts.c b/fs/partitions/blkdev_parts.c index 48f4136d720..030565b7ce7 100755 --- a/fs/partitions/blkdev_parts.c +++ b/fs/partitions/blkdev_parts.c @@ -107,7 +107,7 @@ static int parse_blkdev_parts(char *blkdev_name, struct parsed_partitions *state return part_nbr; } -int blkdev_partition(struct parsed_partitions *state, struct block_device *bdev) +int blkdev_partition(struct parsed_partitions *state) { char blkdev_name[BDEVNAME_SIZE]; @@ -116,7 +116,7 @@ int blkdev_partition(struct parsed_partitions *state, struct block_device *bdev) return 0; /* Get the name of the blockdevice we are operating upon */ - if (bdevname(bdev, blkdev_name) == NULL) { + if (bdevname(state->bdev, blkdev_name) == NULL) { printk(KERN_WARNING "Could not get a blkdev name\n"); return 0; } diff --git a/fs/partitions/blkdev_parts.h b/fs/partitions/blkdev_parts.h index e6c5d40ed76..16d2b571625 100755 --- a/fs/partitions/blkdev_parts.h +++ b/fs/partitions/blkdev_parts.h @@ -10,5 +10,5 @@ * */ -int blkdev_partition(struct parsed_partitions *state, struct block_device *bdev); +int blkdev_partition(struct parsed_partitions *state); -- cgit v1.2.3 From 2bbe3482faadadbfa008ae89c01ef8c92a86e888 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 28 Mar 2011 13:32:50 +0200 Subject: mach-ux500: rename PRCMU driver per SoC This renames the prcmu.c file to prcmu-db8500.c so as to indicate that it is for this one SoC. Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/Makefile | 2 + arch/arm/mach-ux500/prcmu-db8500.c | 394 +++++++++++++++++++++++++++++++++++++ 2 files changed, 396 insertions(+) create mode 100644 arch/arm/mach-ux500/prcmu-db8500.c diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 1694916e682..e95c4a3ae20 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -6,6 +6,7 @@ obj-y := clock.o cpu.o devices.o devices-common.o \ id.o usb.o obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o +obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o prcmu-db8500.o obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \ board-mop500-regulators.o \ board-mop500-uib.o board-mop500-stuib.o \ @@ -17,4 +18,5 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o obj-$(CONFIG_U5500_MODEM_IRQ) += modem-irq-db5500.o obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o +obj-$(CONFIG_CPU_FREQ) += cpufreq.o diff --git a/arch/arm/mach-ux500/prcmu-db8500.c b/arch/arm/mach-ux500/prcmu-db8500.c new file mode 100644 index 00000000000..c522d26ef34 --- /dev/null +++ b/arch/arm/mach-ux500/prcmu-db8500.c @@ -0,0 +1,394 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Kumar Sanghvi + * Author: Sundar Iyer + * Author: Mattias Nilsson + * + * U8500 PRCM Unit interface driver + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Global var to runtime determine TCDM base for v2 or v1 */ +static __iomem void *tcdm_base; + +#define _MBOX_HEADER (tcdm_base + 0xFE8) +#define MBOX_HEADER_REQ_MB0 (_MBOX_HEADER + 0x0) + +#define REQ_MB1 (tcdm_base + 0xFD0) +#define REQ_MB5 (tcdm_base + 0xE44) + +#define REQ_MB1_ARMOPP (REQ_MB1 + 0x0) +#define REQ_MB1_APEOPP (REQ_MB1 + 0x1) +#define REQ_MB1_BOOSTOPP (REQ_MB1 + 0x2) + +#define ACK_MB1 (tcdm_base + 0xE04) +#define ACK_MB5 (tcdm_base + 0xDF4) + +#define ACK_MB1_CURR_ARMOPP (ACK_MB1 + 0x0) +#define ACK_MB1_CURR_APEOPP (ACK_MB1 + 0x1) + +#define REQ_MB5_I2C_SLAVE_OP (REQ_MB5) +#define REQ_MB5_I2C_HW_BITS (REQ_MB5 + 1) +#define REQ_MB5_I2C_REG (REQ_MB5 + 2) +#define REQ_MB5_I2C_VAL (REQ_MB5 + 3) + +#define ACK_MB5_I2C_STATUS (ACK_MB5 + 1) +#define ACK_MB5_I2C_VAL (ACK_MB5 + 3) + +#define PRCM_AVS_VARM_MAX_OPP (tcdm_base + 0x2E4) +#define PRCM_AVS_ISMODEENABLE 7 +#define PRCM_AVS_ISMODEENABLE_MASK (1 << PRCM_AVS_ISMODEENABLE) + +#define I2C_WRITE(slave) \ + (((slave) << 1) | (cpu_is_u8500v2() ? BIT(6) : 0)) +#define I2C_READ(slave) \ + (((slave) << 1) | (cpu_is_u8500v2() ? BIT(6) : 0) | BIT(0)) +#define I2C_STOP_EN BIT(3) + +enum mb1_h { + MB1H_ARM_OPP = 1, + MB1H_APE_OPP, + MB1H_ARM_APE_OPP, +}; + +static struct { + struct mutex lock; + struct completion work; + struct { + u8 arm_opp; + u8 ape_opp; + u8 arm_status; + u8 ape_status; + } ack; +} mb1_transfer; + +enum ack_mb5_status { + I2C_WR_OK = 0x01, + I2C_RD_OK = 0x02, +}; + +#define MBOX_BIT BIT +#define NUM_MBOX 8 + +static struct { + struct mutex lock; + struct completion work; + bool failed; + struct { + u8 status; + u8 value; + } ack; +} mb5_transfer; + +/** + * prcmu_abb_read() - Read register value(s) from the ABB. + * @slave: The I2C slave address. + * @reg: The (start) register address. + * @value: The read out value(s). + * @size: The number of registers to read. + * + * Reads register value(s) from the ABB. + * @size has to be 1 for the current firmware version. + */ +int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) +{ + int r; + + if (size != 1) + return -EINVAL; + + r = mutex_lock_interruptible(&mb5_transfer.lock); + if (r) + return r; + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + cpu_relax(); + + writeb(I2C_READ(slave), REQ_MB5_I2C_SLAVE_OP); + writeb(I2C_STOP_EN, REQ_MB5_I2C_HW_BITS); + writeb(reg, REQ_MB5_I2C_REG); + + writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb5_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: prcmu_abb_read timed out.\n"); + r = -EIO; + goto unlock_and_return; + } + r = ((mb5_transfer.ack.status == I2C_RD_OK) ? 0 : -EIO); + if (!r) + *value = mb5_transfer.ack.value; + +unlock_and_return: + mutex_unlock(&mb5_transfer.lock); + return r; +} +EXPORT_SYMBOL(prcmu_abb_read); + +/** + * prcmu_abb_write() - Write register value(s) to the ABB. + * @slave: The I2C slave address. + * @reg: The (start) register address. + * @value: The value(s) to write. + * @size: The number of registers to write. + * + * Reads register value(s) from the ABB. + * @size has to be 1 for the current firmware version. + */ +int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) +{ + int r; + + if (size != 1) + return -EINVAL; + + r = mutex_lock_interruptible(&mb5_transfer.lock); + if (r) + return r; + + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + cpu_relax(); + + writeb(I2C_WRITE(slave), REQ_MB5_I2C_SLAVE_OP); + writeb(I2C_STOP_EN, REQ_MB5_I2C_HW_BITS); + writeb(reg, REQ_MB5_I2C_REG); + writeb(*value, REQ_MB5_I2C_VAL); + + writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb5_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: prcmu_abb_write timed out.\n"); + r = -EIO; + goto unlock_and_return; + } + r = ((mb5_transfer.ack.status == I2C_WR_OK) ? 0 : -EIO); + +unlock_and_return: + mutex_unlock(&mb5_transfer.lock); + return r; +} +EXPORT_SYMBOL(prcmu_abb_write); + +static int set_ape_cpu_opps(u8 header, enum prcmu_ape_opp ape_opp, + enum prcmu_cpu_opp cpu_opp) +{ + bool do_ape; + bool do_arm; + int err = 0; + + do_ape = ((header == MB1H_APE_OPP) || (header == MB1H_ARM_APE_OPP)); + do_arm = ((header == MB1H_ARM_OPP) || (header == MB1H_ARM_APE_OPP)); + + mutex_lock(&mb1_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(0, MBOX_HEADER_REQ_MB0); + writeb(cpu_opp, REQ_MB1_ARMOPP); + writeb(ape_opp, REQ_MB1_APEOPP); + writeb(0, REQ_MB1_BOOSTOPP); + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + if ((do_ape) && (mb1_transfer.ack.ape_status != 0)) + err = -EIO; + if ((do_arm) && (mb1_transfer.ack.arm_status != 0)) + err = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return err; +} + +/** + * prcmu_set_ape_opp() - Set the OPP of the APE. + * @opp: The OPP to set. + * + * This function sets the OPP of the APE. + */ +int prcmu_set_ape_opp(enum prcmu_ape_opp opp) +{ + return set_ape_cpu_opps(MB1H_APE_OPP, opp, APE_OPP_NO_CHANGE); +} +EXPORT_SYMBOL(prcmu_set_ape_opp); + +/** + * prcmu_set_cpu_opp() - Set the OPP of the CPU. + * @opp: The OPP to set. + * + * This function sets the OPP of the CPU. + */ +int prcmu_set_cpu_opp(enum prcmu_cpu_opp opp) +{ + return set_ape_cpu_opps(MB1H_ARM_OPP, CPU_OPP_NO_CHANGE, opp); +} +EXPORT_SYMBOL(prcmu_set_cpu_opp); + +/** + * prcmu_set_ape_cpu_opps() - Set the OPPs of the APE and the CPU. + * @ape_opp: The APE OPP to set. + * @cpu_opp: The CPU OPP to set. + * + * This function sets the OPPs of the APE and the CPU. + */ +int prcmu_set_ape_cpu_opps(enum prcmu_ape_opp ape_opp, + enum prcmu_cpu_opp cpu_opp) +{ + return set_ape_cpu_opps(MB1H_ARM_APE_OPP, ape_opp, cpu_opp); +} +EXPORT_SYMBOL(prcmu_set_ape_cpu_opps); + +/** + * prcmu_get_ape_opp() - Get the OPP of the APE. + * + * This function gets the OPP of the APE. + */ +enum prcmu_ape_opp prcmu_get_ape_opp(void) +{ + return readb(ACK_MB1_CURR_APEOPP); +} +EXPORT_SYMBOL(prcmu_get_ape_opp); + +/** + * prcmu_get_cpu_opp() - Get the OPP of the CPU. + * + * This function gets the OPP of the CPU. The OPP is specified in %%. + * PRCMU_OPP_EXT is a special OPP value, not specified in %%. + */ +int prcmu_get_cpu_opp(void) +{ + return readb(ACK_MB1_CURR_ARMOPP); +} +EXPORT_SYMBOL(prcmu_get_cpu_opp); + +bool prcmu_has_arm_maxopp(void) +{ + return (readb(PRCM_AVS_VARM_MAX_OPP) & PRCM_AVS_ISMODEENABLE_MASK) + == PRCM_AVS_ISMODEENABLE_MASK; +} + +static void read_mailbox_0(void) +{ + writel(MBOX_BIT(0), PRCM_ARM_IT1_CLEAR); +} + +static void read_mailbox_1(void) +{ + mb1_transfer.ack.arm_opp = readb(ACK_MB1_CURR_ARMOPP); + mb1_transfer.ack.ape_opp = readb(ACK_MB1_CURR_APEOPP); + complete(&mb1_transfer.work); + writel(MBOX_BIT(1), PRCM_ARM_IT1_CLEAR); +} + +static void read_mailbox_2(void) +{ + writel(MBOX_BIT(2), PRCM_ARM_IT1_CLEAR); +} + +static void read_mailbox_3(void) +{ + writel(MBOX_BIT(3), PRCM_ARM_IT1_CLEAR); +} + +static void read_mailbox_4(void) +{ + writel(MBOX_BIT(4), PRCM_ARM_IT1_CLEAR); +} + +static void read_mailbox_5(void) +{ + mb5_transfer.ack.status = readb(ACK_MB5_I2C_STATUS); + mb5_transfer.ack.value = readb(ACK_MB5_I2C_VAL); + complete(&mb5_transfer.work); + writel(MBOX_BIT(5), PRCM_ARM_IT1_CLEAR); +} + +static void read_mailbox_6(void) +{ + writel(MBOX_BIT(6), PRCM_ARM_IT1_CLEAR); +} + +static void read_mailbox_7(void) +{ + writel(MBOX_BIT(7), PRCM_ARM_IT1_CLEAR); +} + +static void (* const read_mailbox[NUM_MBOX])(void) = { + read_mailbox_0, + read_mailbox_1, + read_mailbox_2, + read_mailbox_3, + read_mailbox_4, + read_mailbox_5, + read_mailbox_6, + read_mailbox_7 +}; + +static irqreturn_t prcmu_irq_handler(int irq, void *data) +{ + u32 bits; + u8 n; + + bits = (readl(PRCM_ARM_IT1_VAL) & (MBOX_BIT(NUM_MBOX) - 1)); + if (unlikely(!bits)) + return IRQ_NONE; + + for (n = 0; bits; n++) { + if (bits & MBOX_BIT(n)) { + bits -= MBOX_BIT(n); + read_mailbox[n](); + } + } + return IRQ_HANDLED; +} + +void __init prcmu_early_init(void) +{ + if (cpu_is_u8500v11() || cpu_is_u8500ed()) { + tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE_V1); + } else if (cpu_is_u8500v2()) { + tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); + } else { + pr_err("prcmu: Unsupported chip version\n"); + BUG(); + } +} + +static int __init prcmu_init(void) +{ + if (cpu_is_u8500ed()) { + pr_err("prcmu: Unsupported chip version\n"); + return 0; + } + + mutex_init(&mb1_transfer.lock); + init_completion(&mb1_transfer.work); + mutex_init(&mb5_transfer.lock); + init_completion(&mb5_transfer.work); + + /* Clean up the mailbox interrupts after pre-kernel code. */ + writel((MBOX_BIT(NUM_MBOX) - 1), PRCM_ARM_IT1_CLEAR); + + return request_irq(IRQ_DB8500_PRCMU1, prcmu_irq_handler, 0, + "prcmu", NULL); +} + +arch_initcall(prcmu_init); -- cgit v1.2.3 From ca6ce87e82bc09f8d6257806fb512aad2a38f4d7 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 29 Mar 2011 22:25:20 +0200 Subject: mach-ux500: create a PM subdirectory Create a PM (Power Management) subdirectory in mach-ux500 and move the cpufreq driver there as a beginning. We will add more PM files there like CPUidle, suspend, resume and runtime PM support when as we work on this machine. Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/Kconfig | 1 + arch/arm/mach-ux500/Makefile | 5 +- arch/arm/mach-ux500/pm/Kconfig | 6 ++ arch/arm/mach-ux500/pm/Makefile | 4 + arch/arm/mach-ux500/pm/cpufreq.c | 211 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 arch/arm/mach-ux500/pm/Kconfig create mode 100644 arch/arm/mach-ux500/pm/Makefile create mode 100644 arch/arm/mach-ux500/pm/cpufreq.c diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index 4210cb434db..35d7965b8cc 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -70,4 +70,5 @@ config U5500_MBOX help Add support for U5500 mailbox communication with modem side +source "arch/arm/mach-ux500/pm/Kconfig" endif diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index e95c4a3ae20..cef883beb72 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -4,6 +4,7 @@ obj-y := clock.o cpu.o devices.o devices-common.o \ id.o usb.o +obj-y += pm/ obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o prcmu-db8500.o @@ -17,6 +18,4 @@ obj-$(CONFIG_SMP) += platsmp.o headsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o obj-$(CONFIG_U5500_MODEM_IRQ) += modem-irq-db5500.o -obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o -obj-$(CONFIG_CPU_FREQ) += cpufreq.o - +obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o \ No newline at end of file diff --git a/arch/arm/mach-ux500/pm/Kconfig b/arch/arm/mach-ux500/pm/Kconfig new file mode 100644 index 00000000000..22171f843ba --- /dev/null +++ b/arch/arm/mach-ux500/pm/Kconfig @@ -0,0 +1,6 @@ +config U8500_CPUFREQ + tristate "CPUFreq support" + depends on UX500_SOC_DB8500 && CPU_FREQ + default y + help + Add support for CPU Frequency scaling for U8500. diff --git a/arch/arm/mach-ux500/pm/Makefile b/arch/arm/mach-ux500/pm/Makefile new file mode 100644 index 00000000000..1e843409489 --- /dev/null +++ b/arch/arm/mach-ux500/pm/Makefile @@ -0,0 +1,4 @@ +# +# Power save related files +# +obj-$(CONFIG_U8500_CPUFREQ) += cpufreq.o diff --git a/arch/arm/mach-ux500/pm/cpufreq.c b/arch/arm/mach-ux500/pm/cpufreq.c new file mode 100644 index 00000000000..5c5b747f134 --- /dev/null +++ b/arch/arm/mach-ux500/pm/cpufreq.c @@ -0,0 +1,211 @@ +/* + * CPU frequency scaling for u8500 + * Inspired by linux/arch/arm/mach-davinci/cpufreq.c + * + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Author: Sundar Iyer + * Author: Martin Persson + * Author: Jonas Aaberg + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#define DRIVER_NAME "cpufreq-u8500" +#define CPUFREQ_NAME "u8500" + +static struct device *dev; + +static struct cpufreq_frequency_table freq_table[] = { + [0] = { + .index = 0, + .frequency = 200000, + }, + [1] = { + .index = 1, + .frequency = 300000, + }, + [2] = { + .index = 2, + .frequency = 600000, + }, + [3] = { + /* Used for CPU_OPP_MAX, if available */ + .index = 3, + .frequency = CPUFREQ_TABLE_END, + }, + [4] = { + .index = 4, + .frequency = CPUFREQ_TABLE_END, + }, +}; + +static enum prcmu_cpu_opp index2opp[] = { + CPU_OPP_EXT_CLK, + CPU_OPP_50, + CPU_OPP_100, + CPU_OPP_MAX +}; + +static int u8500_cpufreq_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static int u8500_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct cpufreq_freqs freqs; + unsigned int index; + int ret = 0; + + /* + * Ensure desired rate is within allowed range. Some govenors + * (ondemand) will just pass target_freq=0 to get the minimum. + */ + if (target_freq < policy->cpuinfo.min_freq) + target_freq = policy->cpuinfo.min_freq; + if (target_freq > policy->cpuinfo.max_freq) + target_freq = policy->cpuinfo.max_freq; + + ret = cpufreq_frequency_table_target(policy, freq_table, + target_freq, relation, &index); + if (ret < 0) { + dev_err(dev, "Could not look up next frequency\n"); + return ret; + } + + freqs.old = policy->cur; + freqs.new = freq_table[index].frequency; + freqs.cpu = policy->cpu; + + if (freqs.old == freqs.new) { + dev_dbg(dev, "Current and target frequencies are equal\n"); + return 0; + } + + dev_dbg(dev, "transition: %u --> %u\n", freqs.old, freqs.new); + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + ret = prcmu_set_cpu_opp(index2opp[index]); + if (ret < 0) { + dev_err(dev, "Failed to set OPP level\n"); + return ret; + } + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +static unsigned int u8500_cpufreq_getspeed(unsigned int cpu) +{ + int i; + + for (i = 0; prcmu_get_cpu_opp() != index2opp[i]; i++) + ; + return freq_table[i].frequency; +} + +static int __cpuinit u8500_cpu_init(struct cpufreq_policy *policy) +{ + int res; + + BUILD_BUG_ON(ARRAY_SIZE(index2opp) + 1 != ARRAY_SIZE(freq_table)); + + if (cpu_is_u8500v2()) { + freq_table[1].frequency = 400000; + freq_table[2].frequency = 800000; + if (prcmu_has_arm_maxopp()) + freq_table[3].frequency = 1000000; + } + + /* get policy fields based on the table */ + res = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (!res) + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + else { + dev_err(dev, "u8500-cpufreq : Failed to read policy table\n"); + return res; + } + + policy->min = policy->cpuinfo.min_freq; + policy->max = policy->cpuinfo.max_freq; + policy->cur = u8500_cpufreq_getspeed(policy->cpu); + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + + /* + * FIXME : Need to take time measurement across the target() + * function with no/some/all drivers in the notification + * list. + */ + policy->cpuinfo.transition_latency = 200 * 1000; /* in ns */ + + /* policy sharing between dual CPUs */ + cpumask_copy(policy->cpus, &cpu_present_map); + + policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; + + return res; +} + +static struct freq_attr *u8500_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; +static int u8500_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + return 0; +} + +static struct cpufreq_driver u8500_driver = { + .owner = THIS_MODULE, + .flags = CPUFREQ_STICKY, + .verify = u8500_cpufreq_verify_speed, + .target = u8500_cpufreq_target, + .get = u8500_cpufreq_getspeed, + .init = u8500_cpu_init, + .exit = u8500_cpu_exit, + .name = CPUFREQ_NAME, + .attr = u8500_cpufreq_attr, +}; + +static int __init u8500_cpufreq_probe(struct platform_device *pdev) +{ + dev = &pdev->dev; + return cpufreq_register_driver(&u8500_driver); +} + +static int __exit u8500_cpufreq_remove(struct platform_device *pdev) +{ + return cpufreq_unregister_driver(&u8500_driver); +} + +static struct platform_driver u8500_cpufreq_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .remove = __exit_p(u8500_cpufreq_remove), +}; + +static int __init u8500_cpufreq_init(void) +{ + return platform_driver_probe(&u8500_cpufreq_driver, + &u8500_cpufreq_probe); +} + +device_initcall(u8500_cpufreq_init); -- cgit v1.2.3 From b55636cccd1d7d83a6fe5200f83f1d536bfbf2f1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 29 Mar 2011 22:32:38 +0200 Subject: mach-ux500: new DB8500 PRCMU firmware API This is a snapshot of the current PRCMU firmware API as it looks right now. The PRCMU firmware is still subject to change. This also updates the CPUfreq driver to a newer version that will utilize the new API. Signed-off-by: Mattias Nilsson Signed-off-by: Martin Persson Signed-off-by: Per Fransson Signed-off-by: Jonas Aaberg Signed-off-by: Sebastien Rault Signed-off-by: Bengt Jonsson Signed-off-by: Rickard Andersson Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/cpu.c | 1 + arch/arm/mach-ux500/include/mach/prcmu-fw-api.h | 523 +++++ .../arm/mach-ux500/include/mach/prcmu-fw-defs_v1.h | 467 +++++ arch/arm/mach-ux500/pm/cpufreq.c | 156 +- arch/arm/mach-ux500/prcmu-db8500.c | 2007 ++++++++++++++++++-- arch/arm/mach-ux500/prcmu-regs-db8500.h | 162 ++ drivers/mfd/ab8500-i2c.c | 1 + 7 files changed, 3027 insertions(+), 290 deletions(-) create mode 100644 arch/arm/mach-ux500/include/mach/prcmu-fw-api.h create mode 100644 arch/arm/mach-ux500/include/mach/prcmu-fw-defs_v1.h create mode 100644 arch/arm/mach-ux500/prcmu-regs-db8500.h diff --git a/arch/arm/mach-ux500/cpu.c b/arch/arm/mach-ux500/cpu.c index 1da23bb87c1..b33b4f5622c 100644 --- a/arch/arm/mach-ux500/cpu.c +++ b/arch/arm/mach-ux500/cpu.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "clock.h" diff --git a/arch/arm/mach-ux500/include/mach/prcmu-fw-api.h b/arch/arm/mach-ux500/include/mach/prcmu-fw-api.h new file mode 100644 index 00000000000..a671ed02ae5 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/prcmu-fw-api.h @@ -0,0 +1,523 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Kumar Sanghvi + * + * PRCMU f/w APIs + */ +#ifndef __MACH_PRCMU_FW_API_H +#define __MACH_PRCMU_FW_API_H + +#include +#include +#include "prcmu-fw-defs_v1.h" + +/* PRCMU Wakeup defines */ +enum prcmu_wakeup_index { + PRCMU_WAKEUP_INDEX_RTC, + PRCMU_WAKEUP_INDEX_RTT0, + PRCMU_WAKEUP_INDEX_RTT1, + PRCMU_WAKEUP_INDEX_HSI0, + PRCMU_WAKEUP_INDEX_HSI1, + PRCMU_WAKEUP_INDEX_USB, + PRCMU_WAKEUP_INDEX_ABB, + PRCMU_WAKEUP_INDEX_ABB_FIFO, + PRCMU_WAKEUP_INDEX_ARM, + NUM_PRCMU_WAKEUP_INDICES +}; +#define PRCMU_WAKEUP(_name) (BIT(PRCMU_WAKEUP_INDEX_##_name)) + +/* PRCMU QoS APE OPP class */ +#define PRCMU_QOS_APE_OPP 1 +#define PRCMU_QOS_DDR_OPP 2 +#define PRCMU_QOS_DEFAULT_VALUE -1 + +/** + * enum hw_acc_dev - enum for hw accelerators + * @HW_ACC_SVAMMDSP: for SVAMMDSP + * @HW_ACC_SVAPIPE: for SVAPIPE + * @HW_ACC_SIAMMDSP: for SIAMMDSP + * @HW_ACC_SIAPIPE: for SIAPIPE + * @HW_ACC_SGA: for SGA + * @HW_ACC_B2R2: for B2R2 + * @HW_ACC_MCDE: for MCDE + * @HW_ACC_ESRAM1: for ESRAM1 + * @HW_ACC_ESRAM2: for ESRAM2 + * @HW_ACC_ESRAM3: for ESRAM3 + * @HW_ACC_ESRAM4: for ESRAM4 + * @NUM_HW_ACC: number of hardware accelerators + * + * Different hw accelerators which can be turned ON/ + * OFF or put into retention (MMDSPs and ESRAMs). + * Used with EPOD API. + * + * NOTE! Deprecated, to be removed when all users switched over to use the + * regulator API. + */ +enum hw_acc_dev{ + HW_ACC_SVAMMDSP, + HW_ACC_SVAPIPE, + HW_ACC_SIAMMDSP, + HW_ACC_SIAPIPE, + HW_ACC_SGA, + HW_ACC_B2R2, + HW_ACC_MCDE, + HW_ACC_ESRAM1, + HW_ACC_ESRAM2, + HW_ACC_ESRAM3, + HW_ACC_ESRAM4, + NUM_HW_ACC +}; + +/* + * Ids for all EPODs (power domains) + * - EPOD_ID_SVAMMDSP: power domain for SVA MMDSP + * - EPOD_ID_SVAPIPE: power domain for SVA pipe + * - EPOD_ID_SIAMMDSP: power domain for SIA MMDSP + * - EPOD_ID_SIAPIPE: power domain for SIA pipe + * - EPOD_ID_SGA: power domain for SGA + * - EPOD_ID_B2R2_MCDE: power domain for B2R2 and MCDE + * - EPOD_ID_ESRAM12: power domain for ESRAM 1 and 2 + * - EPOD_ID_ESRAM34: power domain for ESRAM 3 and 4 + * - NUM_EPOD_ID: number of power domains + */ +#define EPOD_ID_SVAMMDSP 0 +#define EPOD_ID_SVAPIPE 1 +#define EPOD_ID_SIAMMDSP 2 +#define EPOD_ID_SIAPIPE 3 +#define EPOD_ID_SGA 4 +#define EPOD_ID_B2R2_MCDE 5 +#define EPOD_ID_ESRAM12 6 +#define EPOD_ID_ESRAM34 7 +#define NUM_EPOD_ID 8 + +/* + * state definition for EPOD (power domain) + * - EPOD_STATE_NO_CHANGE: The EPOD should remain unchanged + * - EPOD_STATE_OFF: The EPOD is switched off + * - EPOD_STATE_RAMRET: The EPOD is switched off with its internal RAM in + * retention + * - EPOD_STATE_ON_CLK_OFF: The EPOD is switched on, clock is still off + * - EPOD_STATE_ON: Same as above, but with clock enabled + */ +#define EPOD_STATE_NO_CHANGE 0x00 +#define EPOD_STATE_OFF 0x01 +#define EPOD_STATE_RAMRET 0x02 +#define EPOD_STATE_ON_CLK_OFF 0x03 +#define EPOD_STATE_ON 0x04 + +/* + * CLKOUT sources + */ +#define PRCMU_CLKSRC_CLK38M 0x00 +#define PRCMU_CLKSRC_ACLK 0x01 +#define PRCMU_CLKSRC_SYSCLK 0x02 +#define PRCMU_CLKSRC_LCDCLK 0x03 +#define PRCMU_CLKSRC_SDMMCCLK 0x04 +#define PRCMU_CLKSRC_TVCLK 0x05 +#define PRCMU_CLKSRC_TIMCLK 0x06 +#define PRCMU_CLKSRC_CLK009 0x07 +/* These are only valid for CLKOUT1: */ +#define PRCMU_CLKSRC_SIAMMDSPCLK 0x40 +#define PRCMU_CLKSRC_I2CCLK 0x41 +#define PRCMU_CLKSRC_MSP02CLK 0x42 +#define PRCMU_CLKSRC_ARMPLL_OBSCLK 0x43 +#define PRCMU_CLKSRC_HSIRXCLK 0x44 +#define PRCMU_CLKSRC_HSITXCLK 0x45 +#define PRCMU_CLKSRC_ARMCLKFIX 0x46 +#define PRCMU_CLKSRC_HDMICLK 0x47 + +/* + * Definitions for autonomous power management configuration. + */ + +#define PRCMU_AUTO_PM_OFF 0 +#define PRCMU_AUTO_PM_ON 1 + +#define PRCMU_AUTO_PM_POWER_ON_HSEM BIT(0) +#define PRCMU_AUTO_PM_POWER_ON_ABB_FIFO_IT BIT(1) + +enum prcmu_auto_pm_policy { + PRCMU_AUTO_PM_POLICY_NO_CHANGE, + PRCMU_AUTO_PM_POLICY_DSP_OFF_HWP_OFF, + PRCMU_AUTO_PM_POLICY_DSP_OFF_RAMRET_HWP_OFF, + PRCMU_AUTO_PM_POLICY_DSP_CLK_OFF_HWP_OFF, + PRCMU_AUTO_PM_POLICY_DSP_CLK_OFF_HWP_CLK_OFF, +}; + +/** + * struct prcmu_auto_pm_config - Autonomous power management configuration. + * @sia_auto_pm_enable: SIA autonomous pm enable. (PRCMU_AUTO_PM_{OFF,ON}) + * @sia_power_on: SIA power ON enable. (PRCMU_AUTO_PM_POWER_ON_* bitmask) + * @sia_policy: SIA power policy. (enum prcmu_auto_pm_policy) + * @sva_auto_pm_enable: SVA autonomous pm enable. (PRCMU_AUTO_PM_{OFF,ON}) + * @sva_power_on: SVA power ON enable. (PRCMU_AUTO_PM_POWER_ON_* bitmask) + * @sva_policy: SVA power policy. (enum prcmu_auto_pm_policy) + */ +struct prcmu_auto_pm_config { + u8 sia_auto_pm_enable; + u8 sia_power_on; + u8 sia_policy; + u8 sva_auto_pm_enable; + u8 sva_power_on; + u8 sva_policy; +}; + +/** + * enum ddr_opp - DDR OPP states definition + * @DDR_100_OPP: The new DDR operating point is ddr100opp + * @DDR_50_OPP: The new DDR operating point is ddr50opp + * @DDR_25_OPP: The new DDR operating point is ddr25opp + */ +enum ddr_opp { + DDR_100_OPP = 0x00, + DDR_50_OPP = 0x01, + DDR_25_OPP = 0x02, +}; + +/* + * Clock identifiers. + */ +enum prcmu_clock { + PRCMU_SGACLK, + PRCMU_UARTCLK, + PRCMU_MSP02CLK, + PRCMU_MSP1CLK, + PRCMU_I2CCLK, + PRCMU_SDMMCCLK, + PRCMU_SLIMCLK, + PRCMU_PER1CLK, + PRCMU_PER2CLK, + PRCMU_PER3CLK, + PRCMU_PER5CLK, + PRCMU_PER6CLK, + PRCMU_PER7CLK, + PRCMU_LCDCLK, + PRCMU_BMLCLK, + PRCMU_HSITXCLK, + PRCMU_HSIRXCLK, + PRCMU_HDMICLK, + PRCMU_APEATCLK, + PRCMU_APETRACECLK, + PRCMU_MCDECLK, + PRCMU_IPI2CCLK, + PRCMU_DSIALTCLK, + PRCMU_DMACLK, + PRCMU_B2R2CLK, + PRCMU_TVCLK, + PRCMU_SSPCLK, + PRCMU_RNGCLK, + PRCMU_UICCCLK, + PRCMU_NUM_REG_CLOCKS, + PRCMU_SYSCLK = PRCMU_NUM_REG_CLOCKS, + PRCMU_TIMCLK, +}; + +/* + * Definitions for controlling ESRAM0 in deep sleep. + */ +#define ESRAM0_DEEP_SLEEP_STATE_OFF 1 +#define ESRAM0_DEEP_SLEEP_STATE_RET 2 + +#if defined(CONFIG_UX500_SOC_DB8500) || defined(CONFIG_UX500_SOC_DB5500) +void __init prcmu_early_init(void); +int prcmu_set_display_clocks(void); +int prcmu_disable_dsipll(void); +int prcmu_enable_dsipll(void); +#else +static inline void __init prcmu_early_init(void) {} +#endif + +#ifdef CONFIG_UX500_SOC_DB8500 + +int prcmu_set_rc_a2p(enum romcode_write); +enum romcode_read prcmu_get_rc_p2a(void); +enum ap_pwrst prcmu_get_xp70_current_state(void); +int prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll); + +void prcmu_enable_wakeups(u32 wakeups); +static inline void prcmu_disable_wakeups(void) +{ + prcmu_enable_wakeups(0); +} + +void prcmu_config_abb_event_readout(u32 abb_events); +void prcmu_get_abb_event_buffer(void __iomem **buf); +int prcmu_set_arm_opp(u8 opp); +int prcmu_get_arm_opp(void); +bool prcmu_has_arm_maxopp(void); +bool prcmu_is_u8400(void); +int prcmu_set_ape_opp(u8 opp); +int prcmu_get_ape_opp(void); +int prcmu_request_ape_opp_100_voltage(bool enable); +int prcmu_release_usb_wakeup_state(void); +int prcmu_set_ddr_opp(u8 opp); +int prcmu_get_ddr_opp(void); +unsigned long prcmu_qos_get_cpufreq_opp_delay(void); +void prcmu_qos_set_cpufreq_opp_delay(unsigned long); +/* NOTE! Use regulator framework instead */ +int prcmu_set_hwacc(u16 hw_acc_dev, u8 state); +int prcmu_set_epod(u16 epod_id, u8 epod_state); +void prcmu_configure_auto_pm(struct prcmu_auto_pm_config *sleep, + struct prcmu_auto_pm_config *idle); +bool prcmu_is_auto_pm_enabled(void); + +int prcmu_config_clkout(u8 clkout, u8 source, u8 div); +int prcmu_request_clock(u8 clock, bool enable); +int prcmu_set_clock_divider(u8 clock, u8 divider); +int prcmu_config_esram0_deep_sleep(u8 state); +int prcmu_config_hotdog(u8 threshold); +int prcmu_config_hotmon(u8 low, u8 high); +int prcmu_start_temp_sense(u16 cycles32k); +int prcmu_stop_temp_sense(void); +int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); +int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size); + +void prcmu_ac_wake_req(void); +void prcmu_ac_sleep_req(void); +void prcmu_system_reset(void); +void prcmu_modem_reset(void); +bool prcmu_is_ac_wake_requested(void); +void prcmu_enable_spi2(void); +void prcmu_disable_spi2(void); + +#else /* !CONFIG_UX500_SOC_DB8500 */ + +static inline int prcmu_set_rc_a2p(enum romcode_write code) +{ + return 0; +} + +static inline enum romcode_read prcmu_get_rc_p2a(void) +{ + return INIT; +} + +static inline enum ap_pwrst prcmu_get_xp70_current_state(void) +{ + return AP_EXECUTE; +} + +static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, + bool keep_ap_pll) +{ + return 0; +} + +static inline void prcmu_enable_wakeups(u32 wakeups) {} + +static inline void prcmu_disable_wakeups(void) {} + +static inline void prcmu_config_abb_event_readout(u32 abb_events) {} + +static inline int prcmu_set_arm_opp(u8 opp) +{ + return 0; +} + +static inline int prcmu_get_arm_opp(void) +{ + return ARM_100_OPP; +} + +static bool prcmu_has_arm_maxopp(void) +{ + return false; +} + +static bool prcmu_is_u8400(void) +{ + return false; +} + +static inline int prcmu_set_ape_opp(u8 opp) +{ + return 0; +} + +static inline int prcmu_get_ape_opp(void) +{ + return APE_100_OPP; +} + +static inline int prcmu_request_ape_opp_100_voltage(bool enable) +{ + return 0; +} + +static inline int prcmu_release_usb_wakeup_state(void) +{ + return 0; +} + +static inline int prcmu_set_ddr_opp(u8 opp) +{ + return 0; +} + +static inline int prcmu_get_ddr_opp(void) +{ + return DDR_100_OPP; +} + +static inline unsigned long prcmu_qos_get_cpufreq_opp_delay(void) +{ + return 0; +} + +static inline void prcmu_qos_set_cpufreq_opp_delay(unsigned long n) {} + +static inline int prcmu_set_hwacc(u16 hw_acc_dev, u8 state) +{ + return 0; +} + +static inline void prcmu_configure_auto_pm(struct prcmu_auto_pm_config *sleep, + struct prcmu_auto_pm_config *idle) +{ +} + +static inline bool prcmu_is_auto_pm_enabled(void) +{ + return false; +} + +static inline int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +{ + return 0; +} + +static inline int prcmu_request_clock(u8 clock, bool enable) +{ + return 0; +} + +static inline int prcmu_set_clock_divider(u8 clock, u8 divider) +{ + return 0; +} + +int prcmu_config_esram0_deep_sleep(u8 state) +{ + return 0; +} + +static inline int prcmu_config_hotdog(u8 threshold) +{ + return 0; +} + +static inline int prcmu_config_hotmon(u8 low, u8 high) +{ + return 0; +} + +static inline int prcmu_start_temp_sense(u16 cycles32k) +{ + return 0; +} + +static inline int prcmu_stop_temp_sense(void) +{ + return 0; +} + +static inline int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) +{ + return -ENOSYS; +} + +static inline int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) +{ + return -ENOSYS; +} + +static inline void prcmu_ac_wake_req(void) {} + +static inline void prcmu_ac_sleep_req(void) {} + +static inline void prcmu_system_reset(void) {} + +static inline void prcmu_modem_reset(void) {} + +static inline bool prcmu_is_ac_wake_requested(void) +{ + return false; +} + +#ifndef CONFIG_UX500_SOC_DB5500 +static inline int prcmu_set_display_clocks(void) +{ + return 0; +} + +static inline int prcmu_disable_dsipll(void) +{ + return 0; +} + +static inline int prcmu_enable_dsipll(void) +{ + return 0; +} +#endif + +static inline int prcmu_enable_spi2(void) +{ + return 0; +} + +static inline int prcmu_disable_spi2(void) +{ + return 0; +} + +#endif /* !CONFIG_UX500_SOC_DB5500 */ + +#ifdef CONFIG_UX500_PRCMU_QOS_POWER +int prcmu_qos_requirement(int pm_qos_class); +int prcmu_qos_add_requirement(int pm_qos_class, char *name, s32 value); +int prcmu_qos_update_requirement(int pm_qos_class, char *name, s32 new_value); +void prcmu_qos_remove_requirement(int pm_qos_class, char *name); +int prcmu_qos_add_notifier(int prcmu_qos_class, + struct notifier_block *notifier); +int prcmu_qos_remove_notifier(int prcmu_qos_class, + struct notifier_block *notifier); +#else +static inline int prcmu_qos_requirement(int prcmu_qos_class) +{ + return 0; +} + +static inline int prcmu_qos_add_requirement(int prcmu_qos_class, + char *name, s32 value) +{ + return 0; +} + +static inline int prcmu_qos_update_requirement(int prcmu_qos_class, + char *name, s32 new_value) +{ + return 0; +} + +static inline void prcmu_qos_remove_requirement(int prcmu_qos_class, char *name) +{ +} + +static inline int prcmu_qos_add_notifier(int prcmu_qos_class, + struct notifier_block *notifier) +{ + return 0; +} +static inline int prcmu_qos_remove_notifier(int prcmu_qos_class, + struct notifier_block *notifier) +{ + return 0; +} + +#endif + +#endif /* __MACH_PRCMU_FW_API_V1_H */ diff --git a/arch/arm/mach-ux500/include/mach/prcmu-fw-defs_v1.h b/arch/arm/mach-ux500/include/mach/prcmu-fw-defs_v1.h new file mode 100644 index 00000000000..42a8cd2310f --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/prcmu-fw-defs_v1.h @@ -0,0 +1,467 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Kumar Sanghvi + * + * PRCMU definitions for U8500 v1.0 cut + */ +#ifndef __MACH_PRCMU_FW_DEFS_V1_H +#define __MACH_PRCMU_FW_DEFS_V1_H + +#include + +/** + * enum state - ON/OFF state definition + * @OFF: State is ON + * @ON: State is OFF + * + */ +enum state { + OFF = 0x0, + ON = 0x1, +}; + +/** + * enum ret_state - general purpose On/Off/Retention states + * + */ +enum ret_state { + OFFST = 0, + ONST = 1, + RETST = 2 +}; + +/** + * enum clk_arm - ARM Cortex A9 clock schemes + * @A9_OFF: + * @A9_BOOT: + * @A9_OPPT1: + * @A9_OPPT2: + * @A9_EXTCLK: + */ +enum clk_arm { + A9_OFF, + A9_BOOT, + A9_OPPT1, + A9_OPPT2, + A9_EXTCLK +}; + +/** + * enum clk_gen - GEN#0/GEN#1 clock schemes + * @GEN_OFF: + * @GEN_BOOT: + * @GEN_OPPT1: + */ +enum clk_gen { + GEN_OFF, + GEN_BOOT, + GEN_OPPT1, +}; + +/* some information between arm and xp70 */ + +/** + * enum romcode_write - Romcode message written by A9 AND read by XP70 + * @RDY_2_DS: Value set when ApDeepSleep state can be executed by XP70 + * @RDY_2_XP70_RST: Value set when 0x0F has been successfully polled by the + * romcode. The xp70 will go into self-reset + */ +enum romcode_write { + RDY_2_DS = 0x09, + RDY_2_XP70_RST = 0x10 +}; + +/** + * enum romcode_read - Romcode message written by XP70 and read by A9 + * @INIT: Init value when romcode field is not used + * @FS_2_DS: Value set when power state is going from ApExecute to + * ApDeepSleep + * @END_DS: Value set when ApDeepSleep power state is reached coming from + * ApExecute state + * @DS_TO_FS: Value set when power state is going from ApDeepSleep to + * ApExecute + * @END_FS: Value set when ApExecute power state is reached coming from + * ApDeepSleep state + * @SWR: Value set when power state is going to ApReset + * @END_SWR: Value set when the xp70 finished executing ApReset actions and + * waits for romcode acknowledgment to go to self-reset + */ +enum romcode_read { + INIT = 0x00, + FS_2_DS = 0x0A, + END_DS = 0x0B, + DS_TO_FS = 0x0C, + END_FS = 0x0D, + SWR = 0x0E, + END_SWR = 0x0F +}; + +/** + * enum ap_pwrst - current power states defined in PRCMU firmware + * @NO_PWRST: Current power state init + * @AP_BOOT: Current power state is apBoot + * @AP_EXECUTE: Current power state is apExecute + * @AP_DEEP_SLEEP: Current power state is apDeepSleep + * @AP_SLEEP: Current power state is apSleep + * @AP_IDLE: Current power state is apIdle + * @AP_RESET: Current power state is apReset + */ +enum ap_pwrst { + NO_PWRST = 0x00, + AP_BOOT = 0x01, + AP_EXECUTE = 0x02, + AP_DEEP_SLEEP = 0x03, + AP_SLEEP = 0x04, + AP_IDLE = 0x05, + AP_RESET = 0x06 +}; + +/** + * enum ap_pwrst_trans - Transition states defined in PRCMU firmware + * @NO_TRANSITION: No power state transition + * @APEXECUTE_TO_APSLEEP: Power state transition from ApExecute to ApSleep + * @APIDLE_TO_APSLEEP: Power state transition from ApIdle to ApSleep + * @APBOOT_TO_APEXECUTE: Power state transition from ApBoot to ApExecute + * @APEXECUTE_TO_APDEEPSLEEP: Power state transition from ApExecute to + * ApDeepSleep + * @APEXECUTE_TO_APIDLE: Power state transition from ApExecute to ApIdle + */ +enum ap_pwrst_trans { + NO_TRANSITION = 0x00, + APEXECUTE_TO_APSLEEP = 0x01, + APIDLE_TO_APSLEEP = 0x02, /* To be removed */ + PRCMU_AP_SLEEP = 0x01, + APBOOT_TO_APEXECUTE = 0x03, + APEXECUTE_TO_APDEEPSLEEP = 0x04, /* To be removed */ + PRCMU_AP_DEEP_SLEEP = 0x04, + APEXECUTE_TO_APIDLE = 0x05, /* To be removed */ + PRCMU_AP_IDLE = 0x05, + PRCMU_AP_DEEP_IDLE = 0x07, +}; + +/** + * enum ddr_pwrst - DDR power states definition + * @DDR_PWR_STATE_UNCHANGED: SDRAM and DDR controller state is unchanged + * @DDR_PWR_STATE_ON: + * @DDR_PWR_STATE_OFFLOWLAT: + * @DDR_PWR_STATE_OFFHIGHLAT: + */ +enum ddr_pwrst { + DDR_PWR_STATE_UNCHANGED = 0x00, + DDR_PWR_STATE_ON = 0x01, + DDR_PWR_STATE_OFFLOWLAT = 0x02, + DDR_PWR_STATE_OFFHIGHLAT = 0x03 +}; + +/** + * enum arm_opp - ARM OPP states definition + * @ARM_OPP_INIT: + * @ARM_NO_CHANGE: The ARM operating point is unchanged + * @ARM_100_OPP: The new ARM operating point is arm100opp + * @ARM_50_OPP: The new ARM operating point is arm50opp + * @ARM_MAX_OPP: Operating point is "max" (more than 100) + * @ARM_MAX_FREQ100OPP: Set max opp if available, else 100 + * @ARM_EXTCLK: The new ARM operating point is armExtClk + */ +enum arm_opp { + ARM_OPP_INIT = 0x00, + ARM_NO_CHANGE = 0x01, + ARM_100_OPP = 0x02, + ARM_50_OPP = 0x03, + ARM_MAX_OPP = 0x04, + ARM_MAX_FREQ100OPP = 0x05, + ARM_EXTCLK = 0x07 +}; + +/** + * enum ape_opp - APE OPP states definition + * @APE_OPP_INIT: + * @APE_NO_CHANGE: The APE operating point is unchanged + * @APE_100_OPP: The new APE operating point is ape100opp + * @APE_50_OPP: 50% + */ +enum ape_opp { + APE_OPP_INIT = 0x00, + APE_NO_CHANGE = 0x01, + APE_100_OPP = 0x02, + APE_50_OPP = 0x03 +}; + +/** + * enum hw_acc_state - State definition for hardware accelerator + * @HW_NO_CHANGE: The hardware accelerator state must remain unchanged + * @HW_OFF: The hardware accelerator must be switched off + * @HW_OFF_RAMRET: The hardware accelerator must be switched off with its + * internal RAM in retention + * @HW_ON: The hwa hardware accelerator hwa must be switched on + * + * NOTE! Deprecated, to be removed when all users switched over to use the + * regulator API. + */ +enum hw_acc_state { + HW_NO_CHANGE = 0x00, + HW_OFF = 0x01, + HW_OFF_RAMRET = 0x02, + HW_ON = 0x04 +}; + +/** + * enum mbox_2_arm_stat - Status messages definition for mbox_arm + * @BOOT_TO_EXECUTEOK: The apBoot to apExecute state transition has been + * completed + * @DEEPSLEEPOK: The apExecute to apDeepSleep state transition has been + * completed + * @SLEEPOK: The apExecute to apSleep state transition has been completed + * @IDLEOK: The apExecute to apIdle state transition has been completed + * @SOFTRESETOK: The A9 watchdog/ SoftReset state has been completed + * @SOFTRESETGO : The A9 watchdog/SoftReset state is on going + * @BOOT_TO_EXECUTE: The apBoot to apExecute state transition is on going + * @EXECUTE_TO_DEEPSLEEP: The apExecute to apDeepSleep state transition is on + * going + * @DEEPSLEEP_TO_EXECUTE: The apDeepSleep to apExecute state transition is on + * going + * @DEEPSLEEP_TO_EXECUTEOK: The apDeepSleep to apExecute state transition has + * been completed + * @EXECUTE_TO_SLEEP: The apExecute to apSleep state transition is on going + * @SLEEP_TO_EXECUTE: The apSleep to apExecute state transition is on going + * @SLEEP_TO_EXECUTEOK: The apSleep to apExecute state transition has been + * completed + * @EXECUTE_TO_IDLE: The apExecute to apIdle state transition is on going + * @IDLE_TO_EXECUTE: The apIdle to apExecute state transition is on going + * @IDLE_TO_EXECUTEOK: The apIdle to apExecute state transition has been + * completed + * @INIT_STATUS: Status init + */ +enum ap_pwrsttr_status { + BOOT_TO_EXECUTEOK = 0xFF, + DEEPSLEEPOK = 0xFE, + SLEEPOK = 0xFD, + IDLEOK = 0xFC, + SOFTRESETOK = 0xFB, + SOFTRESETGO = 0xFA, + BOOT_TO_EXECUTE = 0xF9, + EXECUTE_TO_DEEPSLEEP = 0xF8, + DEEPSLEEP_TO_EXECUTE = 0xF7, + DEEPSLEEP_TO_EXECUTEOK = 0xF6, + EXECUTE_TO_SLEEP = 0xF5, + SLEEP_TO_EXECUTE = 0xF4, + SLEEP_TO_EXECUTEOK = 0xF3, + EXECUTE_TO_IDLE = 0xF2, + IDLE_TO_EXECUTE = 0xF1, + IDLE_TO_EXECUTEOK = 0xF0, + RDYTODS_RETURNTOEXE = 0xEF, + NORDYTODS_RETURNTOEXE = 0xEE, + EXETOSLEEP_RETURNTOEXE = 0xED, + EXETOIDLE_RETURNTOEXE = 0xEC, + INIT_STATUS = 0xEB, + + /*error messages */ + INITERROR = 0x00, + PLLARMLOCKP_ER = 0x01, + PLLDDRLOCKP_ER = 0x02, + PLLSOCLOCKP_ER = 0x03, + PLLSOCK1LOCKP_ER = 0x04, + ARMWFI_ER = 0x05, + SYSCLKOK_ER = 0x06, + I2C_NACK_DATA_ER = 0x07, + BOOT_ER = 0x08, + I2C_STATUS_ALWAYS_1 = 0x0A, + I2C_NACK_REG_ADDR_ER = 0x0B, + I2C_NACK_DATA0123_ER = 0x1B, + I2C_NACK_ADDR_ER = 0x1F, + CURAPPWRSTISNOT_BOOT = 0x20, + CURAPPWRSTISNOT_EXECUTE = 0x21, + CURAPPWRSTISNOT_SLEEPMODE = 0x22, + CURAPPWRSTISNOT_CORRECTFORIT10 = 0x23, + FIFO4500WUISNOT_WUPEVENT = 0x24, + PLL32KLOCKP_ER = 0x29, + DDRDEEPSLEEPOK_ER = 0x2A, + ROMCODEREADY_ER = 0x50, + WUPBEFOREDS = 0x51, + DDRCONFIG_ER = 0x52, + WUPBEFORESLEEP = 0x53, + WUPBEFOREIDLE = 0x54 +}; /* earlier called as mbox_2_arm_stat */ + +/** + * enum dvfs_stat - DVFS status messages definition + * @DVFS_GO: A state transition DVFS is on going + * @DVFS_ARM100OPPOK: The state transition DVFS has been completed for 100OPP + * @DVFS_ARM50OPPOK: The state transition DVFS has been completed for 50OPP + * @DVFS_ARMEXTCLKOK: The state transition DVFS has been completed for EXTCLK + * @DVFS_NOCHGTCLKOK: The state transition DVFS has been completed for + * NOCHGCLK + * @DVFS_INITSTATUS: Value init + */ +enum dvfs_stat { + DVFS_GO = 0xFF, + DVFS_ARM100OPPOK = 0xFE, + DVFS_ARM50OPPOK = 0xFD, + DVFS_ARMEXTCLKOK = 0xFC, + DVFS_NOCHGTCLKOK = 0xFB, + DVFS_INITSTATUS = 0x00 +}; + +/** + * enum sva_mmdsp_stat - SVA MMDSP status messages + * @SVA_MMDSP_GO: SVAMMDSP interrupt has happened + * @SVA_MMDSP_INIT: Status init + */ +enum sva_mmdsp_stat { + SVA_MMDSP_GO = 0xFF, + SVA_MMDSP_INIT = 0x00 +}; + +/** + * enum sia_mmdsp_stat - SIA MMDSP status messages + * @SIA_MMDSP_GO: SIAMMDSP interrupt has happened + * @SIA_MMDSP_INIT: Status init + */ +enum sia_mmdsp_stat { + SIA_MMDSP_GO = 0xFF, + SIA_MMDSP_INIT = 0x00 +}; + +/** + * enum mbox_to_arm_err - Error messages definition + * @INIT_ERR: Init value + * @PLLARMLOCKP_ERR: PLLARM has not been correctly locked in given time + * @PLLDDRLOCKP_ERR: PLLDDR has not been correctly locked in the given time + * @PLLSOC0LOCKP_ERR: PLLSOC0 has not been correctly locked in the given time + * @PLLSOC1LOCKP_ERR: PLLSOC1 has not been correctly locked in the given time + * @ARMWFI_ERR: The ARM WFI has not been correctly executed in the given time + * @SYSCLKOK_ERR: The SYSCLK is not available in the given time + * @BOOT_ERR: Romcode has not validated the XP70 self reset in the given time + * @ROMCODESAVECONTEXT: The Romcode didn.t correctly save it secure context + * @VARMHIGHSPEEDVALTO_ERR: The ARM high speed supply value transfered + * through I2C has not been correctly executed in the given time + * @VARMHIGHSPEEDACCESS_ERR: The command value of VarmHighSpeedVal transfered + * through I2C has not been correctly executed in the given time + * @VARMLOWSPEEDVALTO_ERR:The ARM low speed supply value transfered through + * I2C has not been correctly executed in the given time + * @VARMLOWSPEEDACCESS_ERR: The command value of VarmLowSpeedVal transfered + * through I2C has not been correctly executed in the given time + * @VARMRETENTIONVALTO_ERR: The ARM retention supply value transfered through + * I2C has not been correctly executed in the given time + * @VARMRETENTIONACCESS_ERR: The command value of VarmRetentionVal transfered + * through I2C has not been correctly executed in the given time + * @VAPEHIGHSPEEDVALTO_ERR: The APE highspeed supply value transfered through + * I2C has not been correctly executed in the given time + * @VSAFEHPVALTO_ERR: The SAFE high power supply value transfered through I2C + * has not been correctly executed in the given time + * @VMODSEL1VALTO_ERR: The MODEM sel1 supply value transfered through I2C has + * not been correctly executed in the given time + * @VMODSEL2VALTO_ERR: The MODEM sel2 supply value transfered through I2C has + * not been correctly executed in the given time + * @VARMOFFACCESS_ERR: The command value of Varm ON/OFF transfered through + * I2C has not been correctly executed in the given time + * @VAPEOFFACCESS_ERR: The command value of Vape ON/OFF transfered through + * I2C has not been correctly executed in the given time + * @VARMRETACCES_ERR: The command value of Varm retention ON/OFF transfered + * through I2C has not been correctly executed in the given time + * @CURAPPWRSTISNOTBOOT:Generated when Arm want to do power state transition + * ApBoot to ApExecute but the power current state is not Apboot + * @CURAPPWRSTISNOTEXECUTE: Generated when Arm want to do power state + * transition from ApExecute to others power state but the + * power current state is not ApExecute + * @CURAPPWRSTISNOTSLEEPMODE: Generated when wake up events are transmitted + * but the power current state is not ApDeepSleep/ApSleep/ApIdle + * @CURAPPWRSTISNOTCORRECTDBG: Generated when wake up events are transmitted + * but the power current state is not correct + * @ARMREGU1VALTO_ERR:The ArmRegu1 value transferred through I2C has not + * been correctly executed in the given time + * @ARMREGU2VALTO_ERR: The ArmRegu2 value transferred through I2C has not + * been correctly executed in the given time + * @VAPEREGUVALTO_ERR: The VApeRegu value transfered through I2C has not + * been correctly executed in the given time + * @VSMPS3REGUVALTO_ERR: The VSmps3Regu value transfered through I2C has not + * been correctly executed in the given time + * @VMODREGUVALTO_ERR: The VModemRegu value transfered through I2C has not + * been correctly executed in the given time + */ +enum mbox_to_arm_err { + INIT_ERR = 0x00, + PLLARMLOCKP_ERR = 0x01, + PLLDDRLOCKP_ERR = 0x02, + PLLSOC0LOCKP_ERR = 0x03, + PLLSOC1LOCKP_ERR = 0x04, + ARMWFI_ERR = 0x05, + SYSCLKOK_ERR = 0x06, + BOOT_ERR = 0x07, + ROMCODESAVECONTEXT = 0x08, + VARMHIGHSPEEDVALTO_ERR = 0x10, + VARMHIGHSPEEDACCESS_ERR = 0x11, + VARMLOWSPEEDVALTO_ERR = 0x12, + VARMLOWSPEEDACCESS_ERR = 0x13, + VARMRETENTIONVALTO_ERR = 0x14, + VARMRETENTIONACCESS_ERR = 0x15, + VAPEHIGHSPEEDVALTO_ERR = 0x16, + VSAFEHPVALTO_ERR = 0x17, + VMODSEL1VALTO_ERR = 0x18, + VMODSEL2VALTO_ERR = 0x19, + VARMOFFACCESS_ERR = 0x1A, + VAPEOFFACCESS_ERR = 0x1B, + VARMRETACCES_ERR = 0x1C, + CURAPPWRSTISNOTBOOT = 0x20, + CURAPPWRSTISNOTEXECUTE = 0x21, + CURAPPWRSTISNOTSLEEPMODE = 0x22, + CURAPPWRSTISNOTCORRECTDBG = 0x23, + ARMREGU1VALTO_ERR = 0x24, + ARMREGU2VALTO_ERR = 0x25, + VAPEREGUVALTO_ERR = 0x26, + VSMPS3REGUVALTO_ERR = 0x27, + VMODREGUVALTO_ERR = 0x28 +}; + +enum hw_acc { + SVAMMDSP = 0, + SVAPIPE = 1, + SIAMMDSP = 2, + SIAPIPE = 3, + SGA = 4, + B2R2MCDE = 5, + ESRAM12 = 6, + ESRAM34 = 7, +}; + +enum cs_pwrmgt { + PWRDNCS0 = 0, + WKUPCS0 = 1, + PWRDNCS1 = 2, + WKUPCS1 = 3 +}; + +/* Defs related to autonomous power management */ + +/** + * enum sia_sva_pwr_policy - Power policy + * @NO_CHGT: No change + * @DSPOFF_HWPOFF: + * @DSPOFFRAMRET_HWPOFF: + * @DSPCLKOFF_HWPOFF: + * @DSPCLKOFF_HWPCLKOFF: + * + */ +enum sia_sva_pwr_policy { + NO_CHGT = 0x0, + DSPOFF_HWPOFF = 0x1, + DSPOFFRAMRET_HWPOFF = 0x2, + DSPCLKOFF_HWPOFF = 0x3, + DSPCLKOFF_HWPCLKOFF = 0x4, +}; + +/** + * enum auto_enable - Auto Power enable + * @AUTO_OFF: + * @AUTO_ON: + * + */ +enum auto_enable { + AUTO_OFF = 0x0, + AUTO_ON = 0x1, +}; + +#endif /* __MACH_PRCMU_FW_DEFS_V1_H */ diff --git a/arch/arm/mach-ux500/pm/cpufreq.c b/arch/arm/mach-ux500/pm/cpufreq.c index 5c5b747f134..8d4818a9d7e 100644 --- a/arch/arm/mach-ux500/pm/cpufreq.c +++ b/arch/arm/mach-ux500/pm/cpufreq.c @@ -1,61 +1,51 @@ /* - * CPU frequency scaling for u8500 - * Inspired by linux/arch/arm/mach-davinci/cpufreq.c - * * Copyright (C) STMicroelectronics 2009 * Copyright (C) ST-Ericsson SA 2010 * * License Terms: GNU General Public License v2 - * * Author: Sundar Iyer * Author: Martin Persson * Author: Jonas Aaberg * */ - -#include #include #include #include +#include #include -#include -#include - -#define DRIVER_NAME "cpufreq-u8500" -#define CPUFREQ_NAME "u8500" - -static struct device *dev; +#include +#include static struct cpufreq_frequency_table freq_table[] = { [0] = { .index = 0, - .frequency = 200000, + .frequency = 300000, }, [1] = { .index = 1, - .frequency = 300000, + .frequency = 600000, }, [2] = { + /* Used for MAX_OPP, if available */ .index = 2, - .frequency = 600000, + .frequency = CPUFREQ_TABLE_END, }, [3] = { - /* Used for CPU_OPP_MAX, if available */ .index = 3, .frequency = CPUFREQ_TABLE_END, }, - [4] = { - .index = 4, - .frequency = CPUFREQ_TABLE_END, - }, }; -static enum prcmu_cpu_opp index2opp[] = { - CPU_OPP_EXT_CLK, - CPU_OPP_50, - CPU_OPP_100, - CPU_OPP_MAX +static enum arm_opp idx2opp[] = { + ARM_50_OPP, + ARM_100_OPP, + ARM_MAX_OPP +}; + +static struct freq_attr *u8500_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, }; static int u8500_cpufreq_verify_speed(struct cpufreq_policy *policy) @@ -68,68 +58,63 @@ static int u8500_cpufreq_target(struct cpufreq_policy *policy, unsigned int relation) { struct cpufreq_freqs freqs; - unsigned int index; - int ret = 0; + unsigned int idx; - /* - * Ensure desired rate is within allowed range. Some govenors - * (ondemand) will just pass target_freq=0 to get the minimum. - */ + /* scale the target frequency to one of the extremes supported */ if (target_freq < policy->cpuinfo.min_freq) target_freq = policy->cpuinfo.min_freq; if (target_freq > policy->cpuinfo.max_freq) target_freq = policy->cpuinfo.max_freq; - ret = cpufreq_frequency_table_target(policy, freq_table, - target_freq, relation, &index); - if (ret < 0) { - dev_err(dev, "Could not look up next frequency\n"); - return ret; + /* Lookup the next frequency */ + if (cpufreq_frequency_table_target + (policy, freq_table, target_freq, relation, &idx)) { + return -EINVAL; } freqs.old = policy->cur; - freqs.new = freq_table[index].frequency; + freqs.new = freq_table[idx].frequency; freqs.cpu = policy->cpu; - if (freqs.old == freqs.new) { - dev_dbg(dev, "Current and target frequencies are equal\n"); + if (freqs.old == freqs.new) return 0; - } - dev_dbg(dev, "transition: %u --> %u\n", freqs.old, freqs.new); + /* pre-change notification */ cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - ret = prcmu_set_cpu_opp(index2opp[index]); - if (ret < 0) { - dev_err(dev, "Failed to set OPP level\n"); - return ret; + /* request the PRCM unit for opp change */ + if (prcmu_set_arm_opp(idx2opp[idx])) { + pr_err("u8500-cpufreq: Failed to set OPP level\n"); + return -EINVAL; } + /* post change notification */ cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - return ret; + return 0; } static unsigned int u8500_cpufreq_getspeed(unsigned int cpu) { int i; - - for (i = 0; prcmu_get_cpu_opp() != index2opp[i]; i++) + /* request the prcm to get the current ARM opp */ + for (i = 0; prcmu_get_arm_opp() != idx2opp[i]; i++) ; return freq_table[i].frequency; } -static int __cpuinit u8500_cpu_init(struct cpufreq_policy *policy) +static int __cpuinit u8500_cpufreq_init(struct cpufreq_policy *policy) { int res; + int i; - BUILD_BUG_ON(ARRAY_SIZE(index2opp) + 1 != ARRAY_SIZE(freq_table)); + BUILD_BUG_ON(ARRAY_SIZE(idx2opp) + 1 != ARRAY_SIZE(freq_table)); - if (cpu_is_u8500v2()) { - freq_table[1].frequency = 400000; - freq_table[2].frequency = 800000; + if (cpu_is_u8500v2() && !prcmu_is_u8400()) { + freq_table[0].frequency = 400000; + freq_table[1].frequency = 800000; if (prcmu_has_arm_maxopp()) - freq_table[3].frequency = 1000000; + freq_table[2].frequency = 1000000; } /* get policy fields based on the table */ @@ -137,13 +122,17 @@ static int __cpuinit u8500_cpu_init(struct cpufreq_policy *policy) if (!res) cpufreq_frequency_table_get_attr(freq_table, policy->cpu); else { - dev_err(dev, "u8500-cpufreq : Failed to read policy table\n"); + pr_err("u8500-cpufreq : Failed to read policy table\n"); return res; } policy->min = policy->cpuinfo.min_freq; policy->max = policy->cpuinfo.max_freq; policy->cur = u8500_cpufreq_getspeed(policy->cpu); + + for (i = 0; freq_table[i].frequency != policy->cur; i++) + ; + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; /* @@ -151,61 +140,32 @@ static int __cpuinit u8500_cpu_init(struct cpufreq_policy *policy) * function with no/some/all drivers in the notification * list. */ - policy->cpuinfo.transition_latency = 200 * 1000; /* in ns */ + policy->cpuinfo.transition_latency = 20 * 1000; /* in ns */ /* policy sharing between dual CPUs */ cpumask_copy(policy->cpus, &cpu_present_map); policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; - return res; -} - -static struct freq_attr *u8500_cpufreq_attr[] = { - &cpufreq_freq_attr_scaling_available_freqs, - NULL, -}; -static int u8500_cpu_exit(struct cpufreq_policy *policy) -{ - cpufreq_frequency_table_put_attr(policy->cpu); return 0; } -static struct cpufreq_driver u8500_driver = { - .owner = THIS_MODULE, - .flags = CPUFREQ_STICKY, +static struct cpufreq_driver u8500_cpufreq_driver = { + .flags = CPUFREQ_STICKY, .verify = u8500_cpufreq_verify_speed, .target = u8500_cpufreq_target, - .get = u8500_cpufreq_getspeed, - .init = u8500_cpu_init, - .exit = u8500_cpu_exit, - .name = CPUFREQ_NAME, - .attr = u8500_cpufreq_attr, + .get = u8500_cpufreq_getspeed, + .init = u8500_cpufreq_init, + .name = "U8500", + .attr = u8500_cpufreq_attr, }; -static int __init u8500_cpufreq_probe(struct platform_device *pdev) +static int __init u8500_cpufreq_register(void) { - dev = &pdev->dev; - return cpufreq_register_driver(&u8500_driver); -} - -static int __exit u8500_cpufreq_remove(struct platform_device *pdev) -{ - return cpufreq_unregister_driver(&u8500_driver); -} + if (ux500_is_svp()) + return -ENODEV; -static struct platform_driver u8500_cpufreq_driver = { - .driver = { - .name = DRIVER_NAME, - .owner = THIS_MODULE, - }, - .remove = __exit_p(u8500_cpufreq_remove), -}; - -static int __init u8500_cpufreq_init(void) -{ - return platform_driver_probe(&u8500_cpufreq_driver, - &u8500_cpufreq_probe); + pr_info("cpufreq for u8500 started\n"); + return cpufreq_register_driver(&u8500_cpufreq_driver); } - -device_initcall(u8500_cpufreq_init); +device_initcall(u8500_cpufreq_register); diff --git a/arch/arm/mach-ux500/prcmu-db8500.c b/arch/arm/mach-ux500/prcmu-db8500.c index c522d26ef34..ad908117679 100644 --- a/arch/arm/mach-ux500/prcmu-db8500.c +++ b/arch/arm/mach-ux500/prcmu-db8500.c @@ -10,92 +10,1518 @@ * U8500 PRCM Unit interface driver * */ -#include #include +#include +#include #include #include +#include #include +#include #include #include +#include #include #include -#include +#include +#include +#include +#include + +/* + * NOTE! Temporary until all users of set_hwacc() are using the regulator + * framework API + */ +#include + +#include +#include +#include "prcmu-regs-db8500.h" +#include +#include +#include + +/* 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) +#define PRCM_AVS_VBB_MAX_OPP (PRCM_AVS_BASE + 0x1) +#define PRCM_AVS_VBB_100_OPP (PRCM_AVS_BASE + 0x2) +#define PRCM_AVS_VBB_50_OPP (PRCM_AVS_BASE + 0x3) +#define PRCM_AVS_VARM_MAX_OPP (PRCM_AVS_BASE + 0x4) +#define PRCM_AVS_VARM_100_OPP (PRCM_AVS_BASE + 0x5) +#define PRCM_AVS_VARM_50_OPP (PRCM_AVS_BASE + 0x6) +#define PRCM_AVS_VARM_RET (PRCM_AVS_BASE + 0x7) +#define PRCM_AVS_VAPE_100_OPP (PRCM_AVS_BASE + 0x8) +#define PRCM_AVS_VAPE_50_OPP (PRCM_AVS_BASE + 0x9) +#define PRCM_AVS_VMOD_100_OPP (PRCM_AVS_BASE + 0xA) +#define PRCM_AVS_VMOD_50_OPP (PRCM_AVS_BASE + 0xB) +#define PRCM_AVS_VSAFE (PRCM_AVS_BASE + 0xC) + +#define PRCM_AVS_VOLTAGE 0 +#define PRCM_AVS_VOLTAGE_MASK 0x3f +#define PRCM_AVS_ISSLOWSTARTUP 6 +#define PRCM_AVS_ISSLOWSTARTUP_MASK (1 << PRCM_AVS_ISSLOWSTARTUP) +#define PRCM_AVS_ISMODEENABLE 7 +#define PRCM_AVS_ISMODEENABLE_MASK (1 << PRCM_AVS_ISMODEENABLE) + +#define PRCM_BOOT_STATUS 0xFFF +#define PRCM_ROMCODE_A2P 0xFFE +#define PRCM_ROMCODE_P2A 0xFFD +#define PRCM_XP70_CUR_PWR_STATE 0xFFC /* 4 BYTES */ + +#define _PRCM_MBOX_HEADER 0xFE8 /* 16 bytes */ +#define PRCM_MBOX_HEADER_REQ_MB0 (_PRCM_MBOX_HEADER + 0x0) +#define PRCM_MBOX_HEADER_REQ_MB1 (_PRCM_MBOX_HEADER + 0x1) +#define PRCM_MBOX_HEADER_REQ_MB2 (_PRCM_MBOX_HEADER + 0x2) +#define PRCM_MBOX_HEADER_REQ_MB3 (_PRCM_MBOX_HEADER + 0x3) +#define PRCM_MBOX_HEADER_REQ_MB4 (_PRCM_MBOX_HEADER + 0x4) +#define PRCM_MBOX_HEADER_REQ_MB5 (_PRCM_MBOX_HEADER + 0x5) +#define PRCM_MBOX_HEADER_ACK_MB0 (_PRCM_MBOX_HEADER + 0x8) + +/* Req Mailboxes */ +#define PRCM_REQ_MB0 0xFDC /* 12 bytes */ +#define PRCM_REQ_MB1 0xFD0 /* 12 bytes */ +#define PRCM_REQ_MB2 0xFC0 /* 16 bytes */ +#define PRCM_REQ_MB3 0xE4C /* 372 bytes */ +#define PRCM_REQ_MB4 0xE48 /* 4 bytes */ +#define PRCM_REQ_MB5 0xE44 /* 4 bytes */ + +/* Ack Mailboxes */ +#define PRCM_ACK_MB0 0xE08 /* 52 bytes */ +#define PRCM_ACK_MB1 0xE04 /* 4 bytes */ +#define PRCM_ACK_MB2 0xE00 /* 4 bytes */ +#define PRCM_ACK_MB3 0xDFC /* 4 bytes */ +#define PRCM_ACK_MB4 0xDF8 /* 4 bytes */ +#define PRCM_ACK_MB5 0xDF4 /* 4 bytes */ + +/* Mailbox 0 headers */ +#define MB0H_POWER_STATE_TRANS 0 +#define MB0H_CONFIG_WAKEUPS_EXE 1 +#define MB0H_READ_WAKEUP_ACK 3 +#define MB0H_CONFIG_WAKEUPS_SLEEP 4 + +#define MB0H_WAKEUP_EXE 2 +#define MB0H_WAKEUP_SLEEP 5 + +/* Mailbox 0 REQs */ +#define PRCM_REQ_MB0_AP_POWER_STATE (PRCM_REQ_MB0 + 0x0) +#define PRCM_REQ_MB0_AP_PLL_STATE (PRCM_REQ_MB0 + 0x1) +#define PRCM_REQ_MB0_ULP_CLOCK_STATE (PRCM_REQ_MB0 + 0x2) +#define PRCM_REQ_MB0_DO_NOT_WFI (PRCM_REQ_MB0 + 0x3) +#define PRCM_REQ_MB0_WAKEUP_8500 (PRCM_REQ_MB0 + 0x4) +#define PRCM_REQ_MB0_WAKEUP_4500 (PRCM_REQ_MB0 + 0x8) + +/* 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_8500 (PRCM_ACK_MB0 + 0x4) +#define PRCM_ACK_MB0_WAKEUP_0_4500 (PRCM_ACK_MB0 + 0x8) +#define PRCM_ACK_MB0_WAKEUP_1_8500 (PRCM_ACK_MB0 + 0x1C) +#define PRCM_ACK_MB0_WAKEUP_1_4500 (PRCM_ACK_MB0 + 0x20) +#define PRCM_ACK_MB0_EVENT_4500_NUMBERS 20 + +/* Mailbox 1 headers */ +#define MB1H_ARM_APE_OPP 0x0 +#define MB1H_RESET_MODEM 0x2 +#define MB1H_REQUEST_APE_OPP_100_VOLT 0x3 +#define MB1H_RELEASE_APE_OPP_100_VOLT 0x4 +#define MB1H_RELEASE_USB_WAKEUP 0x5 + +/* Mailbox 1 Requests */ +#define PRCM_REQ_MB1_ARM_OPP (PRCM_REQ_MB1 + 0x0) +#define PRCM_REQ_MB1_APE_OPP (PRCM_REQ_MB1 + 0x1) +#define PRCM_REQ_MB1_APE_OPP_100_RESTORE (PRCM_REQ_MB1 + 0x4) +#define PRCM_REQ_MB1_ARM_OPP_100_RESTORE (PRCM_REQ_MB1 + 0x8) + +/* 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_APE_VOLTAGE_STATUS (PRCM_ACK_MB1 + 0x2) +#define PRCM_ACK_MB1_DVFS_STATUS (PRCM_ACK_MB1 + 0x3) + +/* Mailbox 2 headers */ +#define MB2H_DPS 0x0 +#define MB2H_AUTO_PWR 0x1 + +/* Mailbox 2 REQs */ +#define PRCM_REQ_MB2_SVA_MMDSP (PRCM_REQ_MB2 + 0x0) +#define PRCM_REQ_MB2_SVA_PIPE (PRCM_REQ_MB2 + 0x1) +#define PRCM_REQ_MB2_SIA_MMDSP (PRCM_REQ_MB2 + 0x2) +#define PRCM_REQ_MB2_SIA_PIPE (PRCM_REQ_MB2 + 0x3) +#define PRCM_REQ_MB2_SGA (PRCM_REQ_MB2 + 0x4) +#define PRCM_REQ_MB2_B2R2_MCDE (PRCM_REQ_MB2 + 0x5) +#define PRCM_REQ_MB2_ESRAM12 (PRCM_REQ_MB2 + 0x6) +#define PRCM_REQ_MB2_ESRAM34 (PRCM_REQ_MB2 + 0x7) +#define PRCM_REQ_MB2_AUTO_PM_SLEEP (PRCM_REQ_MB2 + 0x8) +#define PRCM_REQ_MB2_AUTO_PM_IDLE (PRCM_REQ_MB2 + 0xC) + +/* Mailbox 2 ACKs */ +#define PRCM_ACK_MB2_DPS_STATUS (PRCM_ACK_MB2 + 0x0) +#define HWACC_PWR_ST_OK 0xFE + +/* Mailbox 3 headers */ +#define MB3H_ANC 0x0 +#define MB3H_SIDETONE 0x1 +#define MB3H_SYSCLK 0xE + +/* Mailbox 3 Requests */ +#define PRCM_REQ_MB3_ANC_FIR_COEFF (PRCM_REQ_MB3 + 0x0) +#define PRCM_REQ_MB3_ANC_IIR_COEFF (PRCM_REQ_MB3 + 0x20) +#define PRCM_REQ_MB3_ANC_SHIFTER (PRCM_REQ_MB3 + 0x60) +#define PRCM_REQ_MB3_ANC_WARP (PRCM_REQ_MB3 + 0x64) +#define PRCM_REQ_MB3_SIDETONE_FIR_GAIN (PRCM_REQ_MB3 + 0x68) +#define PRCM_REQ_MB3_SIDETONE_FIR_COEFF (PRCM_REQ_MB3 + 0x6C) +#define PRCM_REQ_MB3_SYSCLK_MGT (PRCM_REQ_MB3 + 0x16C) + +/* Mailbox 4 headers */ +#define MB4H_DDR_INIT 0x0 +#define MB4H_MEM_ST 0x1 +#define MB4H_HOTDOG 0x12 +#define MB4H_HOTMON 0x13 +#define MB4H_HOT_PERIOD 0x14 + +/* Mailbox 4 Requests */ +#define PRCM_REQ_MB4_DDR_ST_AP_SLEEP_IDLE (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_DDR_ST_AP_DEEP_IDLE (PRCM_REQ_MB4 + 0x1) +#define PRCM_REQ_MB4_ESRAM0_ST (PRCM_REQ_MB4 + 0x3) +#define PRCM_REQ_MB4_HOTDOG_THRESHOLD (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_HOTMON_LOW (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_HOTMON_HIGH (PRCM_REQ_MB4 + 0x1) +#define PRCM_REQ_MB4_HOTMON_CONFIG (PRCM_REQ_MB4 + 0x2) +#define PRCM_REQ_MB4_HOT_PERIOD (PRCM_REQ_MB4 + 0x0) +#define HOTMON_CONFIG_LOW BIT(0) +#define HOTMON_CONFIG_HIGH BIT(1) + +/* Mailbox 5 Requests */ +#define PRCM_REQ_MB5_I2C_SLAVE_OP (PRCM_REQ_MB5 + 0x0) +#define PRCM_REQ_MB5_I2C_HW_BITS (PRCM_REQ_MB5 + 0x1) +#define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 0x2) +#define PRCM_REQ_MB5_I2C_VAL (PRCM_REQ_MB5 + 0x3) +#define PRCMU_I2C_WRITE(slave) \ + (((slave) << 1) | (cpu_is_u8500v2() ? BIT(6) : 0)) +#define PRCMU_I2C_READ(slave) \ + (((slave) << 1) | BIT(0) | (cpu_is_u8500v2() ? BIT(6) : 0)) +#define PRCMU_I2C_STOP_EN BIT(3) + +/* Mailbox 5 ACKs */ +#define PRCM_ACK_MB5_I2C_STATUS (PRCM_ACK_MB5 + 0x1) +#define PRCM_ACK_MB5_I2C_VAL (PRCM_ACK_MB5 + 0x3) +#define I2C_WR_OK 0x1 +#define I2C_RD_OK 0x2 + +#define NUM_MB 8 +#define MBOX_BIT BIT +#define ALL_MBOX_BITS (MBOX_BIT(NUM_MB) - 1) + +/* + * Wakeups/IRQs + */ + +#define WAKEUP_BIT_RTC BIT(0) +#define WAKEUP_BIT_RTT0 BIT(1) +#define WAKEUP_BIT_RTT1 BIT(2) +#define WAKEUP_BIT_HSI0 BIT(3) +#define WAKEUP_BIT_HSI1 BIT(4) +#define WAKEUP_BIT_CA_WAKE BIT(5) +#define WAKEUP_BIT_USB BIT(6) +#define WAKEUP_BIT_ABB BIT(7) +#define WAKEUP_BIT_ABB_FIFO BIT(8) +#define WAKEUP_BIT_SYSCLK_OK BIT(9) +#define WAKEUP_BIT_CA_SLEEP BIT(10) +#define WAKEUP_BIT_AC_WAKE_ACK BIT(11) +#define WAKEUP_BIT_SIDE_TONE_OK BIT(12) +#define WAKEUP_BIT_ANC_OK BIT(13) +#define WAKEUP_BIT_SW_ERROR BIT(14) +#define WAKEUP_BIT_AC_SLEEP_ACK BIT(15) +#define WAKEUP_BIT_ARM BIT(17) +#define WAKEUP_BIT_HOTMON_LOW BIT(18) +#define WAKEUP_BIT_HOTMON_HIGH BIT(19) +#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_GPIO8 BIT(31) + +/* + * 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_PRCMU_##_name) - IRQ_PRCMU_BASE) +#define IRQ_ENTRY(_name)[IRQ_INDEX(_name)] = (WAKEUP_BIT_##_name) +static u32 prcmu_irq_bit[NUM_PRCMU_WAKEUPS] = { + IRQ_ENTRY(RTC), + IRQ_ENTRY(RTT0), + IRQ_ENTRY(RTT1), + IRQ_ENTRY(HSI0), + IRQ_ENTRY(HSI1), + IRQ_ENTRY(CA_WAKE), + IRQ_ENTRY(USB), + IRQ_ENTRY(ABB), + IRQ_ENTRY(ABB_FIFO), + IRQ_ENTRY(CA_SLEEP), + IRQ_ENTRY(ARM), + IRQ_ENTRY(HOTMON_LOW), + IRQ_ENTRY(HOTMON_HIGH), + 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(GPIO8) +}; + +#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(HSI0), + WAKEUP_ENTRY(HSI1), + WAKEUP_ENTRY(USB), + WAKEUP_ENTRY(ABB), + WAKEUP_ENTRY(ABB_FIFO), + WAKEUP_ENTRY(ARM) +}; + +/* + * mb0_transfer - state needed for mailbox 0 communication. + * @lock: The transaction lock. + * @dbb_events_lock: A lock used to handle concurrent access to (parts of) + * the request data. + * @mask_work: Work structure used for (un)masking wakeup interrupts. + * @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 completion ac_wake_work; + 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. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 header; + u8 arm_opp; + u8 ape_opp; + u8 ape_voltage_status; + } ack; +} mb1_transfer; + +/* + * mb2_transfer - state needed for mailbox 2 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @auto_pm_lock: The autonomous power management configuration lock. + * @auto_pm_enabled: A flag indicating whether autonomous PM is enabled. + * @req: Request data that need to persist between requests. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + spinlock_t auto_pm_lock; + bool auto_pm_enabled; + struct { + u8 status; + } ack; +} mb2_transfer; + +/* + * mb3_transfer - state needed for mailbox 3 communication. + * @lock: The request lock. + * @sysclk_lock: A lock used to handle concurrent sysclk requests. + * @sysclk_work: Work structure used for sysclk requests. + */ +static struct { + spinlock_t lock; + struct mutex sysclk_lock; + struct completion sysclk_work; +} mb3_transfer; + +/* + * mb4_transfer - state needed for mailbox 4 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + */ +static struct { + struct mutex lock; + struct completion work; +} mb4_transfer; + +/* + * mb5_transfer - state needed for mailbox 5 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 status; + u8 value; + } ack; +} mb5_transfer; + +static atomic_t ac_wake_req_state = ATOMIC_INIT(0); + +/* Spinlocks */ +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; + u32 pllsw; +}; + +static DEFINE_SPINLOCK(clk_mgt_lock); + +#define CLK_MGT_ENTRY(_name)[PRCMU_##_name] = { (PRCM_##_name##_MGT), 0 } +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), +}; + +/* + * NOTE! Temporary until all users of set_hwacc() are using the regulator + * framework API + */ +static struct regulator *hwacc_regulator[NUM_HW_ACC]; +static struct regulator *hwacc_ret_regulator[NUM_HW_ACC]; + +static bool hwacc_enabled[NUM_HW_ACC]; +static bool hwacc_ret_enabled[NUM_HW_ACC]; + +static const char *hwacc_regulator_name[NUM_HW_ACC] = { + [HW_ACC_SVAMMDSP] = "hwacc-sva-mmdsp", + [HW_ACC_SVAPIPE] = "hwacc-sva-pipe", + [HW_ACC_SIAMMDSP] = "hwacc-sia-mmdsp", + [HW_ACC_SIAPIPE] = "hwacc-sia-pipe", + [HW_ACC_SGA] = "hwacc-sga", + [HW_ACC_B2R2] = "hwacc-b2r2", + [HW_ACC_MCDE] = "hwacc-mcde", + [HW_ACC_ESRAM1] = "hwacc-esram1", + [HW_ACC_ESRAM2] = "hwacc-esram2", + [HW_ACC_ESRAM3] = "hwacc-esram3", + [HW_ACC_ESRAM4] = "hwacc-esram4", +}; + +static const char *hwacc_ret_regulator_name[NUM_HW_ACC] = { + [HW_ACC_SVAMMDSP] = "hwacc-sva-mmdsp-ret", + [HW_ACC_SIAMMDSP] = "hwacc-sia-mmdsp-ret", + [HW_ACC_ESRAM1] = "hwacc-esram1-ret", + [HW_ACC_ESRAM2] = "hwacc-esram2-ret", + [HW_ACC_ESRAM3] = "hwacc-esram3-ret", + [HW_ACC_ESRAM4] = "hwacc-esram4-ret", +}; + +/* +* Used by MCDE to setup all necessary PRCMU registers +*/ +#define PRCMU_RESET_DSIPLL 0x00004000 +#define PRCMU_UNCLAMP_DSIPLL 0x00400800 + +#define PRCMU_CLK_PLL_DIV_SHIFT 0 +#define PRCMU_CLK_PLL_SW_SHIFT 5 +#define PRCMU_CLK_38 (1 << 9) +#define PRCMU_CLK_38_SRC (1 << 10) +#define PRCMU_CLK_38_DIV (1 << 11) + +/* 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)) +#define PRCMU_DSI_LP_CLOCK_SETTING 0x00000E00 + +/* 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 +#define PRCMU_DSI_PLLOUT_SEL_SETTING 0x00000202 +/* ESC clk, div0=1, div1=1, div2=3 */ +#define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x07030101 +#define PRCMU_DISABLE_ESCAPE_CLOCK_DIV 0x00030101 +#define PRCMU_DSI_RESET_SW 0x00000007 + +#define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 + +static struct { + u8 project_number; + u8 api_version; + u8 func_version; + u8 errata; +} prcmu_version; + + +int prcmu_enable_dsipll(void) +{ + int i; + unsigned int plldsifreq; + + /* Clear DSIPLL_RESETN */ + writel(PRCMU_RESET_DSIPLL, (_PRCMU_BASE + PRCM_APE_RESETN_CLR)); + /* Unclamp DSIPLL in/out */ + writel(PRCMU_UNCLAMP_DSIPLL, (_PRCMU_BASE + 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, (_PRCMU_BASE + PRCM_PLLDSI_FREQ)); + writel(PRCMU_DSI_PLLOUT_SEL_SETTING, + (_PRCMU_BASE + PRCM_DSI_PLLOUT_SEL)); + /* Enable Escape clocks */ + writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, + (_PRCMU_BASE + PRCM_DSITVCLK_DIV)); + + /* Start DSI PLL */ + writel(PRCMU_ENABLE_PLLDSI, (_PRCMU_BASE + PRCM_PLLDSI_ENABLE)); + /* Reset DSI PLL */ + writel(PRCMU_DSI_RESET_SW, (_PRCMU_BASE + PRCM_DSI_SW_RESET)); + for (i = 0; i < 10; i++) { + if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & + PRCMU_PLLDSI_LOCKP_LOCKED) + == PRCMU_PLLDSI_LOCKP_LOCKED) + break; + udelay(100); + } + /* Set DSIPLL_RESETN */ + writel(PRCMU_RESET_DSIPLL, (_PRCMU_BASE + PRCM_APE_RESETN_SET)); + return 0; +} + +int prcmu_disable_dsipll(void) +{ + /* Disable dsi pll */ + writel(PRCMU_DISABLE_PLLDSI, (_PRCMU_BASE + PRCM_PLLDSI_ENABLE)); + /* Disable escapeclock */ + writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, + (_PRCMU_BASE + PRCM_DSITVCLK_DIV)); + return 0; +} + +int 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); + + /* Grab the HW semaphore. */ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + writel(dsiclk, (_PRCMU_BASE + PRCM_HDMICLK_MGT)); + writel(PRCMU_DSI_LP_CLOCK_SETTING, (_PRCMU_BASE + PRCM_TVCLK_MGT)); + writel(PRCMU_DPI_CLOCK_SETTING, (_PRCMU_BASE + PRCM_LCDCLK_MGT)); + + /* Release the HW semaphore. */ + writel(0, (_PRCMU_BASE + PRCM_SEM)); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} + +/** + * prcmu_enable_spi2 - Enables pin muxing for SPI2 on OtherAlternateC1. + */ +void prcmu_enable_spi2(void) +{ + u32 reg; + unsigned long flags; + + spin_lock_irqsave(&gpiocr_lock, flags); + reg = readl(_PRCMU_BASE + PRCM_GPIOCR); + writel(reg | PRCM_GPIOCR_SPI2_SELECT, _PRCMU_BASE + PRCM_GPIOCR); + spin_unlock_irqrestore(&gpiocr_lock, flags); +} + +/** + * prcmu_disable_spi2 - Disables pin muxing for SPI2 on OtherAlternateC1. + */ +void prcmu_disable_spi2(void) +{ + u32 reg; + unsigned long flags; + + spin_lock_irqsave(&gpiocr_lock, flags); + reg = readl(_PRCMU_BASE + PRCM_GPIOCR); + writel(reg & ~PRCM_GPIOCR_SPI2_SELECT, _PRCMU_BASE + PRCM_GPIOCR); + spin_unlock_irqrestore(&gpiocr_lock, flags); +} + +bool prcmu_has_arm_maxopp(void) +{ + return (readb(tcdm_base + PRCM_AVS_VARM_MAX_OPP) & + PRCM_AVS_ISMODEENABLE_MASK) == PRCM_AVS_ISMODEENABLE_MASK; +} + +bool prcmu_is_u8400(void) +{ + return prcmu_version.project_number == PRCMU_PROJECT_ID_8400V2_0; +} + +/** + * prcmu_get_boot_status - PRCMU boot status checking + * Returns: the current PRCMU boot status + */ +int prcmu_get_boot_status(void) +{ + return readb(tcdm_base + PRCM_BOOT_STATUS); +} + +/** + * prcmu_set_rc_a2p - This function is used to run few power state sequences + * @val: Value to be set, i.e. transition requested + * Returns: 0 on success, -EINVAL on invalid argument + * + * This function is used to run the following power state sequences - + * any state to ApReset, ApDeepSleep to ApExecute, ApExecute to ApDeepSleep + */ +int prcmu_set_rc_a2p(enum romcode_write val) +{ + if (val < RDY_2_DS || val > RDY_2_XP70_RST) + return -EINVAL; + writeb(val, (tcdm_base + PRCM_ROMCODE_A2P)); + return 0; +} + +/** + * prcmu_get_rc_p2a - This function is used to get power state sequences + * Returns: the power transition that has last happened + * + * This function can return the following transitions- + * any state to ApReset, ApDeepSleep to ApExecute, ApExecute to ApDeepSleep + */ +enum romcode_read prcmu_get_rc_p2a(void) +{ + return readb(tcdm_base + PRCM_ROMCODE_P2A); +} + +/** + * prcmu_get_current_mode - Return the current XP70 power mode + * Returns: Returns the current AP(ARM) power mode: init, + * apBoot, apExecute, apDeepSleep, apSleep, apIdle, apReset + */ +enum ap_pwrst prcmu_get_xp70_current_state(void) +{ + return readb(tcdm_base + PRCM_XP70_CUR_PWR_STATE); +} + +/** + * prcmu_config_clkout - Configure one of the programmable clock outputs. + * @clkout: The CLKOUT number (0 or 1). + * @source: The clock to be used (one of the PRCMU_CLKSRC_*). + * @div: The divider to be applied. + * + * Configures one of the programmable clock outputs (CLKOUTs). + * @div should be in the range [1,63] to request a configuration, or 0 to + * inform that the configuration is no longer requested. + */ +int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +{ + static int requests[2]; + int r = 0; + unsigned long flags; + u32 val; + u32 bits; + u32 mask; + u32 div_mask; + + BUG_ON(clkout > 1); + BUG_ON(div > 63); + BUG_ON((clkout == 0) && (source > PRCMU_CLKSRC_CLK009)); + + if (!div && !requests[clkout]) + return -EINVAL; + + switch (clkout) { + case 0: + div_mask = PRCM_CLKOCR_CLKODIV0_MASK; + mask = (PRCM_CLKOCR_CLKODIV0_MASK | PRCM_CLKOCR_CLKOSEL0_MASK); + bits = ((source << PRCM_CLKOCR_CLKOSEL0_SHIFT) | + (div << PRCM_CLKOCR_CLKODIV0_SHIFT)); + break; + case 1: + div_mask = PRCM_CLKOCR_CLKODIV1_MASK; + mask = (PRCM_CLKOCR_CLKODIV1_MASK | PRCM_CLKOCR_CLKOSEL1_MASK | + PRCM_CLKOCR_CLK1TYPE); + bits = ((source << PRCM_CLKOCR_CLKOSEL1_SHIFT) | + (div << PRCM_CLKOCR_CLKODIV1_SHIFT)); + break; + } + bits &= mask; + + spin_lock_irqsave(&clkout_lock, flags); + + val = readl(_PRCMU_BASE + PRCM_CLKOCR); + if (val & div_mask) { + if (div) { + if ((val & mask) != bits) { + r = -EBUSY; + goto unlock_and_return; + } + } else { + if ((val & mask & ~div_mask) != bits) { + r = -EINVAL; + goto unlock_and_return; + } + } + } + writel((bits | (val & ~mask)), (_PRCMU_BASE + PRCM_CLKOCR)); + requests[clkout] += (div ? 1 : -1); + +unlock_and_return: + spin_unlock_irqrestore(&clkout_lock, flags); + + return r; +} + +int prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) +{ + unsigned long flags; + + BUG_ON((state < PRCMU_AP_SLEEP) || (PRCMU_AP_DEEP_IDLE < state)); + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + writeb(MB0H_POWER_STATE_TRANS, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); + writeb(state, (tcdm_base + PRCM_REQ_MB0_AP_POWER_STATE)); + writeb((keep_ap_pll ? 1 : 0), (tcdm_base + PRCM_REQ_MB0_AP_PLL_STATE)); + writeb((keep_ulp_clk ? 1 : 0), + (tcdm_base + PRCM_REQ_MB0_ULP_CLOCK_STATE)); + writeb(0, (tcdm_base + PRCM_REQ_MB0_DO_NOT_WFI)); + writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return 0; +} + +/* This function should only be called while mb0_transfer.lock is held. */ +static void config_wakeups(void) +{ + const u8 header[2] = { + MB0H_CONFIG_WAKEUPS_EXE, + MB0H_CONFIG_WAKEUPS_SLEEP + }; + static u32 last_dbb_events; + static u32 last_abb_events; + u32 dbb_events; + u32 abb_events; + unsigned int i; + + dbb_events = mb0_transfer.req.dbb_irqs | mb0_transfer.req.dbb_wakeups; + dbb_events |= (WAKEUP_BIT_AC_WAKE_ACK | WAKEUP_BIT_AC_SLEEP_ACK); + + abb_events = mb0_transfer.req.abb_events; + + if ((dbb_events == last_dbb_events) && (abb_events == last_abb_events)) + return; + + for (i = 0; i < 2; i++) { + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + writel(dbb_events, (tcdm_base + PRCM_REQ_MB0_WAKEUP_8500)); + writel(abb_events, (tcdm_base + PRCM_REQ_MB0_WAKEUP_4500)); + writeb(header[i], (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); + writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + } + last_dbb_events = dbb_events; + last_abb_events = abb_events; +} + +void 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)) + 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 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 prcmu_get_abb_event_buffer(void __iomem **buf) +{ + if (readb(tcdm_base + PRCM_ACK_MB0_READ_POINTER) & 1) + *buf = (tcdm_base + PRCM_ACK_MB0_WAKEUP_1_4500); + else + *buf = (tcdm_base + PRCM_ACK_MB0_WAKEUP_0_4500); +} + +/** + * 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 prcmu_set_arm_opp(u8 opp) +{ + int r; + + if (opp < ARM_NO_CHANGE || opp > ARM_EXTCLK) + return -EINVAL; + + r = 0; + + mutex_lock(&mb1_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + writeb(opp, (tcdm_base + PRCM_REQ_MB1_ARM_OPP)); + writeb(APE_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_APE_OPP)); + + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != MB1H_ARM_APE_OPP) || + (mb1_transfer.ack.arm_opp != opp)) + r = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * prcmu_get_arm_opp - get the current ARM OPP + * + * Returns: the current ARM OPP + */ +int prcmu_get_arm_opp(void) +{ + return readb(tcdm_base + PRCM_ACK_MB1_CURRENT_ARM_OPP); +} + +/** + * prcmu_get_ddr_opp - get the current DDR OPP + * + * Returns: the current DDR OPP + */ +int prcmu_get_ddr_opp(void) +{ + return readb(_PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); +} + +/** + * 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) +{ + if (opp < DDR_100_OPP || opp > DDR_25_OPP) + return -EINVAL; + /* Changing the DDR OPP can hang the hardware pre-v21 */ + if (cpu_is_u8500v20_or_later() && !cpu_is_u8500v20()) + writeb(opp, (_PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW)); + + return 0; +} +/** + * 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 r = 0; + + mutex_lock(&mb1_transfer.lock); + + while (readl(_PRCMU_BASE + 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)); + + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != MB1H_ARM_APE_OPP) || + (mb1_transfer.ack.ape_opp != opp)) + r = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * prcmu_get_ape_opp - get the current APE OPP + * + * Returns: the current APE OPP + */ +int prcmu_get_ape_opp(void) +{ + return readb(tcdm_base + PRCM_ACK_MB1_CURRENT_APE_OPP); +} + +/** + * prcmu_request_ape_opp_100_voltage - Request APE OPP 100% voltage + * @enable: true to request the higher voltage, false to drop a request. + * + * Calls to this function to enable and disable requests must be balanced. + */ +int prcmu_request_ape_opp_100_voltage(bool enable) +{ + int r = 0; + u8 header; + static unsigned int requests; + + mutex_lock(&mb1_transfer.lock); + + if (enable) { + if (0 != requests++) + goto unlock_and_return; + header = MB1H_REQUEST_APE_OPP_100_VOLT; + } else { + if (requests == 0) { + r = -EIO; + goto unlock_and_return; + } else if (1 != requests--) { + goto unlock_and_return; + } + header = MB1H_RELEASE_APE_OPP_100_VOLT; + } + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(header, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != header) || + ((mb1_transfer.ack.ape_voltage_status & BIT(0)) != 0)) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * prcmu_release_usb_wakeup_state - release the state required by a USB wakeup + * + * This function releases the power state requirements of a USB wakeup. + */ +int prcmu_release_usb_wakeup_state(void) +{ + int r = 0; + + mutex_lock(&mb1_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_RELEASE_USB_WAKEUP, + (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb1_transfer.work); + + if ((mb1_transfer.ack.header != MB1H_RELEASE_USB_WAKEUP) || + ((mb1_transfer.ack.ape_voltage_status & BIT(0)) != 0)) + r = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * prcmu_set_hwacc - set the power state of a h/w accelerator + * @hwacc_dev: The hardware accelerator (enum hw_acc_dev). + * @state: The new power state (enum hw_acc_state). + * + * This function sets the power state of a hardware accelerator. + * This function should not be called from interrupt context. + * + * NOTE! Deprecated, to be removed when all users switched over to use the + * regulator framework API. + */ +int prcmu_set_hwacc(u16 hwacc_dev, u8 state) +{ + int r = 0; + bool ram_retention = false; + bool enable, enable_ret; + + /* check argument */ + BUG_ON(hwacc_dev >= NUM_HW_ACC); + + /* get state of switches */ + enable = hwacc_enabled[hwacc_dev]; + enable_ret = hwacc_ret_enabled[hwacc_dev]; + + /* set flag if retention is possible */ + switch (hwacc_dev) { + case HW_ACC_SVAMMDSP: + case HW_ACC_SIAMMDSP: + case HW_ACC_ESRAM1: + case HW_ACC_ESRAM2: + case HW_ACC_ESRAM3: + case HW_ACC_ESRAM4: + ram_retention = true; + break; + } + + /* check argument */ + BUG_ON(state > HW_ON); + BUG_ON(state == HW_OFF_RAMRET && !ram_retention); + + /* modify enable flags */ + switch (state) { + case HW_OFF: + enable_ret = false; + enable = false; + break; + case HW_ON: + enable = true; + break; + case HW_OFF_RAMRET: + enable_ret = true; + enable = false; + break; + } + + /* get regulator (lazy) */ + if (hwacc_regulator[hwacc_dev] == NULL) { + hwacc_regulator[hwacc_dev] = regulator_get(NULL, + hwacc_regulator_name[hwacc_dev]); + if (IS_ERR(hwacc_regulator[hwacc_dev])) { + pr_err("prcmu: failed to get supply %s\n", + hwacc_regulator_name[hwacc_dev]); + r = PTR_ERR(hwacc_regulator[hwacc_dev]); + goto out; + } + } + + if (ram_retention) { + if (hwacc_ret_regulator[hwacc_dev] == NULL) { + hwacc_ret_regulator[hwacc_dev] = regulator_get(NULL, + hwacc_ret_regulator_name[hwacc_dev]); + if (IS_ERR(hwacc_ret_regulator[hwacc_dev])) { + pr_err("prcmu: failed to get supply %s\n", + hwacc_ret_regulator_name[hwacc_dev]); + r = PTR_ERR(hwacc_ret_regulator[hwacc_dev]); + goto out; + } + } + } + + /* set regulators */ + if (ram_retention) { + if (enable_ret && !hwacc_ret_enabled[hwacc_dev]) { + r = regulator_enable(hwacc_ret_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: ret enable failed\n"); + goto out; + } + hwacc_ret_enabled[hwacc_dev] = true; + } + } + + if (enable && !hwacc_enabled[hwacc_dev]) { + r = regulator_enable(hwacc_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: enable failed\n"); + goto out; + } + hwacc_enabled[hwacc_dev] = true; + } + + if (!enable && hwacc_enabled[hwacc_dev]) { + r = regulator_disable(hwacc_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: disable failed\n"); + goto out; + } + hwacc_enabled[hwacc_dev] = false; + } + + if (ram_retention) { + if (!enable_ret && hwacc_ret_enabled[hwacc_dev]) { + r = regulator_disable(hwacc_ret_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: ret disable failed\n"); + goto out; + } + hwacc_ret_enabled[hwacc_dev] = false; + } + } + +out: + return r; +} +EXPORT_SYMBOL(prcmu_set_hwacc); + +/** + * prcmu_set_epod - set the state of a EPOD (power domain) + * @epod_id: The EPOD to set + * @epod_state: The new EPOD state + * + * This function sets the state of a EPOD (power domain). It may not be called + * from interrupt context. + */ +int prcmu_set_epod(u16 epod_id, u8 epod_state) +{ + int r = 0; + bool ram_retention = false; + int i; + + /* check argument */ + BUG_ON(epod_id >= NUM_EPOD_ID); + + /* set flag if retention is possible */ + switch (epod_id) { + case EPOD_ID_SVAMMDSP: + case EPOD_ID_SIAMMDSP: + case EPOD_ID_ESRAM12: + case EPOD_ID_ESRAM34: + ram_retention = true; + break; + } + + /* check argument */ + BUG_ON(epod_state > EPOD_STATE_ON); + 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(); + + /* fill in mailbox */ + for (i = 0; i < NUM_EPOD_ID; i++) + writeb(EPOD_STATE_NO_CHANGE, (tcdm_base + PRCM_REQ_MB2 + i)); + writeb(epod_state, (tcdm_base + PRCM_REQ_MB2 + epod_id)); + + writeb(MB2H_DPS, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB2)); + + writel(MBOX_BIT(2), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + /* + * The current firmware version does not handle errors correctly, + * and we cannot recover if there is an error. + * This is expected to change when the firmware is updated. + */ + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + goto unlock_and_return; + } -#include -#include -#include + if (mb2_transfer.ack.status != HWACC_PWR_ST_OK) + r = -EIO; -/* Global var to runtime determine TCDM base for v2 or v1 */ -static __iomem void *tcdm_base; +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} -#define _MBOX_HEADER (tcdm_base + 0xFE8) -#define MBOX_HEADER_REQ_MB0 (_MBOX_HEADER + 0x0) +/** + * prcmu_configure_auto_pm - Configure autonomous power management. + * @sleep: Configuration for ApSleep. + * @idle: Configuration for ApIdle. + */ +void prcmu_configure_auto_pm(struct prcmu_auto_pm_config *sleep, + struct prcmu_auto_pm_config *idle) +{ + u32 sleep_cfg; + u32 idle_cfg; + unsigned long flags; -#define REQ_MB1 (tcdm_base + 0xFD0) -#define REQ_MB5 (tcdm_base + 0xE44) + BUG_ON((sleep == NULL) || (idle == NULL)); -#define REQ_MB1_ARMOPP (REQ_MB1 + 0x0) -#define REQ_MB1_APEOPP (REQ_MB1 + 0x1) -#define REQ_MB1_BOOSTOPP (REQ_MB1 + 0x2) + sleep_cfg = (sleep->sva_auto_pm_enable & 0xF); + sleep_cfg = ((sleep_cfg << 4) | (sleep->sia_auto_pm_enable & 0xF)); + sleep_cfg = ((sleep_cfg << 8) | (sleep->sva_power_on & 0xFF)); + sleep_cfg = ((sleep_cfg << 8) | (sleep->sia_power_on & 0xFF)); + sleep_cfg = ((sleep_cfg << 4) | (sleep->sva_policy & 0xF)); + sleep_cfg = ((sleep_cfg << 4) | (sleep->sia_policy & 0xF)); -#define ACK_MB1 (tcdm_base + 0xE04) -#define ACK_MB5 (tcdm_base + 0xDF4) + idle_cfg = (idle->sva_auto_pm_enable & 0xF); + idle_cfg = ((idle_cfg << 4) | (idle->sia_auto_pm_enable & 0xF)); + idle_cfg = ((idle_cfg << 8) | (idle->sva_power_on & 0xFF)); + idle_cfg = ((idle_cfg << 8) | (idle->sia_power_on & 0xFF)); + idle_cfg = ((idle_cfg << 4) | (idle->sva_policy & 0xF)); + idle_cfg = ((idle_cfg << 4) | (idle->sia_policy & 0xF)); -#define ACK_MB1_CURR_ARMOPP (ACK_MB1 + 0x0) -#define ACK_MB1_CURR_APEOPP (ACK_MB1 + 0x1) + spin_lock_irqsave(&mb2_transfer.auto_pm_lock, flags); -#define REQ_MB5_I2C_SLAVE_OP (REQ_MB5) -#define REQ_MB5_I2C_HW_BITS (REQ_MB5 + 1) -#define REQ_MB5_I2C_REG (REQ_MB5 + 2) -#define REQ_MB5_I2C_VAL (REQ_MB5 + 3) + /* + * The autonomous power management configuration is done through + * fields in mailbox 2, but these fields are only used as shared + * variables - i.e. there is no need to send a message. + */ + writel(sleep_cfg, (tcdm_base + PRCM_REQ_MB2_AUTO_PM_SLEEP)); + writel(idle_cfg, (tcdm_base + PRCM_REQ_MB2_AUTO_PM_IDLE)); -#define ACK_MB5_I2C_STATUS (ACK_MB5 + 1) -#define ACK_MB5_I2C_VAL (ACK_MB5 + 3) + mb2_transfer.auto_pm_enabled = + ((sleep->sva_auto_pm_enable == PRCMU_AUTO_PM_ON) || + (sleep->sia_auto_pm_enable == PRCMU_AUTO_PM_ON) || + (idle->sva_auto_pm_enable == PRCMU_AUTO_PM_ON) || + (idle->sia_auto_pm_enable == PRCMU_AUTO_PM_ON)); -#define PRCM_AVS_VARM_MAX_OPP (tcdm_base + 0x2E4) -#define PRCM_AVS_ISMODEENABLE 7 -#define PRCM_AVS_ISMODEENABLE_MASK (1 << PRCM_AVS_ISMODEENABLE) + spin_unlock_irqrestore(&mb2_transfer.auto_pm_lock, flags); +} +EXPORT_SYMBOL(prcmu_configure_auto_pm); -#define I2C_WRITE(slave) \ - (((slave) << 1) | (cpu_is_u8500v2() ? BIT(6) : 0)) -#define I2C_READ(slave) \ - (((slave) << 1) | (cpu_is_u8500v2() ? BIT(6) : 0) | BIT(0)) -#define I2C_STOP_EN BIT(3) - -enum mb1_h { - MB1H_ARM_OPP = 1, - MB1H_APE_OPP, - MB1H_ARM_APE_OPP, -}; +bool prcmu_is_auto_pm_enabled(void) +{ + return mb2_transfer.auto_pm_enabled; +} -static struct { - struct mutex lock; - struct completion work; - struct { - u8 arm_opp; - u8 ape_opp; - u8 arm_status; - u8 ape_status; - } ack; -} mb1_transfer; +static int request_sysclk(bool enable) +{ + int r; + unsigned long flags; -enum ack_mb5_status { - I2C_WR_OK = 0x01, - I2C_RD_OK = 0x02, -}; + r = 0; -#define MBOX_BIT BIT -#define NUM_MBOX 8 + mutex_lock(&mb3_transfer.sysclk_lock); -static struct { - struct mutex lock; - struct completion work; - bool failed; - struct { - u8 status; - u8 value; - } ack; -} mb5_transfer; + spin_lock_irqsave(&mb3_transfer.lock, flags); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(3)) + cpu_relax(); + + writeb((enable ? ON : OFF), (tcdm_base + PRCM_REQ_MB3_SYSCLK_MGT)); + + writeb(MB3H_SYSCLK, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB3)); + writel(MBOX_BIT(3), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + spin_unlock_irqrestore(&mb3_transfer.lock, flags); + + /* + * The firmware only sends an ACK if we want to enable the + * SysClk, and it succeeds. + */ + if (enable && !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; + } + + 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_reg_clock(u8 clock, bool enable) +{ + u32 val; + unsigned long flags; + + 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; +} + +/** + * 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 prcmu_request_clock(u8 clock, bool enable) +{ + 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_SYSCLK) + return request_sysclk(enable); + else + return -EINVAL; +} + +int prcmu_config_esram0_deep_sleep(u8 state) +{ + if ((state > ESRAM0_DEEP_SLEEP_STATE_RET) || + (state < ESRAM0_DEEP_SLEEP_STATE_OFF)) + return -EINVAL; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(MB4H_MEM_ST, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + writeb(((DDR_PWR_STATE_OFFHIGHLAT << 4) | DDR_PWR_STATE_ON), + (tcdm_base + PRCM_REQ_MB4_DDR_ST_AP_SLEEP_IDLE)); + writeb(DDR_PWR_STATE_ON, + (tcdm_base + PRCM_REQ_MB4_DDR_ST_AP_DEEP_IDLE)); + writeb(state, (tcdm_base + PRCM_REQ_MB4_ESRAM0_ST)); + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +int prcmu_config_hotdog(u8 threshold) +{ + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(threshold, (tcdm_base + PRCM_REQ_MB4_HOTDOG_THRESHOLD)); + writeb(MB4H_HOTDOG, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +int prcmu_config_hotmon(u8 low, u8 high) +{ + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(low, (tcdm_base + PRCM_REQ_MB4_HOTMON_LOW)); + writeb(high, (tcdm_base + PRCM_REQ_MB4_HOTMON_HIGH)); + writeb((HOTMON_CONFIG_LOW | HOTMON_CONFIG_HIGH), + (tcdm_base + PRCM_REQ_MB4_HOTMON_CONFIG)); + writeb(MB4H_HOTMON, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +static int config_hot_period(u16 val) +{ + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(val, (tcdm_base + PRCM_REQ_MB4_HOT_PERIOD)); + writeb(MB4H_HOT_PERIOD, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); + + return 0; +} + +int prcmu_start_temp_sense(u16 cycles32k) +{ + if (cycles32k == 0xFFFF) + return -EINVAL; + + return config_hot_period(cycles32k); +} + +int prcmu_stop_temp_sense(void) +{ + return config_hot_period(0xFFFF); +} + +/** + * 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(_PRCMU_BASE + 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, (_PRCMU_BASE + PRCM_SEM)); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} /** * prcmu_abb_read() - Read register value(s) from the ABB. @@ -114,33 +1540,34 @@ int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) if (size != 1) return -EINVAL; - r = mutex_lock_interruptible(&mb5_transfer.lock); - if (r) - return r; + 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(I2C_READ(slave), REQ_MB5_I2C_SLAVE_OP); - writeb(I2C_STOP_EN, REQ_MB5_I2C_HW_BITS); - writeb(reg, REQ_MB5_I2C_REG); + writeb(PRCMU_I2C_READ(slave), (tcdm_base + PRCM_REQ_MB5_I2C_SLAVE_OP)); + writeb(PRCMU_I2C_STOP_EN, (tcdm_base + PRCM_REQ_MB5_I2C_HW_BITS)); + writeb(reg, (tcdm_base + PRCM_REQ_MB5_I2C_REG)); + writeb(0, (tcdm_base + PRCM_REQ_MB5_I2C_VAL)); + + writel(MBOX_BIT(5), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); if (!wait_for_completion_timeout(&mb5_transfer.work, - msecs_to_jiffies(500))) { - pr_err("prcmu: prcmu_abb_read timed out.\n"); + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); r = -EIO; - goto unlock_and_return; + } else { + r = ((mb5_transfer.ack.status == I2C_RD_OK) ? 0 : -EIO); } - r = ((mb5_transfer.ack.status == I2C_RD_OK) ? 0 : -EIO); + if (!r) *value = mb5_transfer.ack.value; -unlock_and_return: mutex_unlock(&mb5_transfer.lock); + return r; } -EXPORT_SYMBOL(prcmu_abb_read); /** * prcmu_abb_write() - Write register value(s) to the ABB. @@ -159,179 +1586,260 @@ int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) if (size != 1) return -EINVAL; - r = mutex_lock_interruptible(&mb5_transfer.lock); - if (r) - return r; + 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(I2C_WRITE(slave), REQ_MB5_I2C_SLAVE_OP); - writeb(I2C_STOP_EN, REQ_MB5_I2C_HW_BITS); - writeb(reg, REQ_MB5_I2C_REG); - writeb(*value, REQ_MB5_I2C_VAL); + writeb(PRCMU_I2C_WRITE(slave), (tcdm_base + PRCM_REQ_MB5_I2C_SLAVE_OP)); + writeb(PRCMU_I2C_STOP_EN, (tcdm_base + PRCM_REQ_MB5_I2C_HW_BITS)); + writeb(reg, (tcdm_base + PRCM_REQ_MB5_I2C_REG)); + writeb(*value, (tcdm_base + PRCM_REQ_MB5_I2C_VAL)); + + writel(MBOX_BIT(5), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); if (!wait_for_completion_timeout(&mb5_transfer.work, - msecs_to_jiffies(500))) { - pr_err("prcmu: prcmu_abb_write timed out.\n"); + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); r = -EIO; - goto unlock_and_return; + } else { + r = ((mb5_transfer.ack.status == I2C_WR_OK) ? 0 : -EIO); } - r = ((mb5_transfer.ack.status == I2C_WR_OK) ? 0 : -EIO); -unlock_and_return: mutex_unlock(&mb5_transfer.lock); + return r; } -EXPORT_SYMBOL(prcmu_abb_write); -static int set_ape_cpu_opps(u8 header, enum prcmu_ape_opp ape_opp, - enum prcmu_cpu_opp cpu_opp) +/** + * prcmu_ac_wake_req - should be called whenever ARM wants to wakeup Modem + */ +void prcmu_ac_wake_req(void) { - bool do_ape; - bool do_arm; - int err = 0; + u32 val; - do_ape = ((header == MB1H_APE_OPP) || (header == MB1H_ARM_APE_OPP)); - do_arm = ((header == MB1H_ARM_OPP) || (header == MB1H_ARM_APE_OPP)); + mutex_lock(&mb0_transfer.ac_wake_lock); - mutex_lock(&mb1_transfer.lock); + val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + if (val & PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ) + goto unlock_and_return; - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) - cpu_relax(); + atomic_set(&ac_wake_req_state, 1); - writeb(0, MBOX_HEADER_REQ_MB0); - writeb(cpu_opp, REQ_MB1_ARMOPP); - writeb(ape_opp, REQ_MB1_APEOPP); - writeb(0, REQ_MB1_BOOSTOPP); - writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); - wait_for_completion(&mb1_transfer.work); - if ((do_ape) && (mb1_transfer.ack.ape_status != 0)) - err = -EIO; - if ((do_arm) && (mb1_transfer.ack.arm_status != 0)) - err = -EIO; + writel((val | PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ), + (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); - mutex_unlock(&mb1_transfer.lock); + if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + } - return err; +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); } /** - * prcmu_set_ape_opp() - Set the OPP of the APE. - * @opp: The OPP to set. - * - * This function sets the OPP of the APE. + * prcmu_ac_sleep_req - called when ARM no longer needs to talk to modem */ -int prcmu_set_ape_opp(enum prcmu_ape_opp opp) +void prcmu_ac_sleep_req() { - return set_ape_cpu_opps(MB1H_APE_OPP, opp, APE_OPP_NO_CHANGE); + u32 val; + + mutex_lock(&mb0_transfer.ac_wake_lock); + + val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + if (!(val & PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ)) + goto unlock_and_return; + + writel((val & ~PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ), + (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); + + if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + } + + atomic_set(&ac_wake_req_state, 0); + +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); } -EXPORT_SYMBOL(prcmu_set_ape_opp); -/** - * prcmu_set_cpu_opp() - Set the OPP of the CPU. - * @opp: The OPP to set. - * - * This function sets the OPP of the CPU. - */ -int prcmu_set_cpu_opp(enum prcmu_cpu_opp opp) +bool prcmu_is_ac_wake_requested(void) { - return set_ape_cpu_opps(MB1H_ARM_OPP, CPU_OPP_NO_CHANGE, opp); + return (atomic_read(&ac_wake_req_state) != 0); } -EXPORT_SYMBOL(prcmu_set_cpu_opp); /** - * prcmu_set_ape_cpu_opps() - Set the OPPs of the APE and the CPU. - * @ape_opp: The APE OPP to set. - * @cpu_opp: The CPU OPP to set. + * prcmu_system_reset - System reset * - * This function sets the OPPs of the APE and the CPU. + * Sets the APE_SOFRST register which fires interrupt to fw */ -int prcmu_set_ape_cpu_opps(enum prcmu_ape_opp ape_opp, - enum prcmu_cpu_opp cpu_opp) +void prcmu_system_reset(void) { - return set_ape_cpu_opps(MB1H_ARM_APE_OPP, ape_opp, cpu_opp); + writel(1, (_PRCMU_BASE + PRCM_APE_SOFTRST)); } -EXPORT_SYMBOL(prcmu_set_ape_cpu_opps); /** - * prcmu_get_ape_opp() - Get the OPP of the APE. - * - * This function gets the OPP of the APE. + * prcmu_reset_modem - ask the PRCMU to reset modem */ -enum prcmu_ape_opp prcmu_get_ape_opp(void) +void prcmu_modem_reset(void) { - return readb(ACK_MB1_CURR_APEOPP); + mutex_lock(&mb1_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_RESET_MODEM, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + wait_for_completion(&mb1_transfer.work); + + /* + * No need to check return from PRCMU as modem should go in reset state + * This state is already managed by upper layer + */ + + mutex_unlock(&mb1_transfer.lock); } -EXPORT_SYMBOL(prcmu_get_ape_opp); -/** - * prcmu_get_cpu_opp() - Get the OPP of the CPU. - * - * This function gets the OPP of the CPU. The OPP is specified in %%. - * PRCMU_OPP_EXT is a special OPP value, not specified in %%. - */ -int prcmu_get_cpu_opp(void) +static void ack_dbb_wakeup(void) { - return readb(ACK_MB1_CURR_ARMOPP); + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + writeb(MB0H_READ_WAKEUP_ACK, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); + writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); } -EXPORT_SYMBOL(prcmu_get_cpu_opp); -bool prcmu_has_arm_maxopp(void) +static inline void print_unknown_header_warning(u8 n, u8 header) { - return (readb(PRCM_AVS_VARM_MAX_OPP) & PRCM_AVS_ISMODEENABLE_MASK) - == PRCM_AVS_ISMODEENABLE_MASK; + pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", + header, n); } -static void read_mailbox_0(void) +static bool read_mailbox_0(void) { - writel(MBOX_BIT(0), PRCM_ARM_IT1_CLEAR); + bool r; + u32 ev; + unsigned int n; + u8 header; + + header = readb(tcdm_base + PRCM_MBOX_HEADER_ACK_MB0); + switch (header) { + case MB0H_WAKEUP_EXE: + case MB0H_WAKEUP_SLEEP: + if (readb(tcdm_base + PRCM_ACK_MB0_READ_POINTER) & 1) + ev = readl(tcdm_base + PRCM_ACK_MB0_WAKEUP_1_8500); + else + ev = readl(tcdm_base + PRCM_ACK_MB0_WAKEUP_0_8500); + + if (ev & (WAKEUP_BIT_AC_WAKE_ACK | WAKEUP_BIT_AC_SLEEP_ACK)) + complete(&mb0_transfer.ac_wake_work); + if (ev & WAKEUP_BIT_SYSCLK_OK) + complete(&mb3_transfer.sysclk_work); + + ev &= mb0_transfer.req.dbb_irqs; + + for (n = 0; n < NUM_PRCMU_WAKEUPS; n++) { + if (ev & prcmu_irq_bit[n]) + generic_handle_irq(IRQ_PRCMU_BASE + n); + } + r = true; + break; + default: + print_unknown_header_warning(0, header); + r = false; + break; + } + writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + return r; } -static void read_mailbox_1(void) +static bool read_mailbox_1(void) { - mb1_transfer.ack.arm_opp = readb(ACK_MB1_CURR_ARMOPP); - mb1_transfer.ack.ape_opp = readb(ACK_MB1_CURR_APEOPP); + mb1_transfer.ack.header = readb(tcdm_base + PRCM_MBOX_HEADER_REQ_MB1); + mb1_transfer.ack.arm_opp = readb(tcdm_base + + PRCM_ACK_MB1_CURRENT_ARM_OPP); + mb1_transfer.ack.ape_opp = readb(tcdm_base + + PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_status = readb(tcdm_base + + PRCM_ACK_MB1_APE_VOLTAGE_STATUS); + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); complete(&mb1_transfer.work); - writel(MBOX_BIT(1), PRCM_ARM_IT1_CLEAR); + return false; } -static void read_mailbox_2(void) +static bool read_mailbox_2(void) { - writel(MBOX_BIT(2), PRCM_ARM_IT1_CLEAR); + mb2_transfer.ack.status = readb(tcdm_base + PRCM_ACK_MB2_DPS_STATUS); + writel(MBOX_BIT(2), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + complete(&mb2_transfer.work); + return false; } -static void read_mailbox_3(void) +static bool read_mailbox_3(void) { - writel(MBOX_BIT(3), PRCM_ARM_IT1_CLEAR); + writel(MBOX_BIT(3), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + return false; } -static void read_mailbox_4(void) +static bool read_mailbox_4(void) { - writel(MBOX_BIT(4), PRCM_ARM_IT1_CLEAR); + u8 header; + bool do_complete = true; + + header = readb(tcdm_base + PRCM_MBOX_HEADER_REQ_MB4); + switch (header) { + case MB4H_MEM_ST: + case MB4H_HOTDOG: + case MB4H_HOTMON: + case MB4H_HOT_PERIOD: + break; + default: + print_unknown_header_warning(4, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + + if (do_complete) + complete(&mb4_transfer.work); + + return false; } -static void read_mailbox_5(void) +static bool read_mailbox_5(void) { - mb5_transfer.ack.status = readb(ACK_MB5_I2C_STATUS); - mb5_transfer.ack.value = readb(ACK_MB5_I2C_VAL); + mb5_transfer.ack.status = readb(tcdm_base + PRCM_ACK_MB5_I2C_STATUS); + mb5_transfer.ack.value = readb(tcdm_base + PRCM_ACK_MB5_I2C_VAL); + writel(MBOX_BIT(5), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); complete(&mb5_transfer.work); - writel(MBOX_BIT(5), PRCM_ARM_IT1_CLEAR); + return false; } -static void read_mailbox_6(void) +static bool read_mailbox_6(void) { - writel(MBOX_BIT(6), PRCM_ARM_IT1_CLEAR); + writel(MBOX_BIT(6), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + return false; } -static void read_mailbox_7(void) +static bool read_mailbox_7(void) { - writel(MBOX_BIT(7), PRCM_ARM_IT1_CLEAR); + writel(MBOX_BIT(7), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + return false; } -static void (* const read_mailbox[NUM_MBOX])(void) = { +static bool (* const read_mailbox[NUM_MB])(void) = { read_mailbox_0, read_mailbox_1, read_mailbox_2, @@ -346,49 +1854,164 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) { u32 bits; u8 n; + irqreturn_t r; - bits = (readl(PRCM_ARM_IT1_VAL) & (MBOX_BIT(NUM_MBOX) - 1)); + bits = (readl(_PRCMU_BASE + PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); if (unlikely(!bits)) return IRQ_NONE; + r = IRQ_HANDLED; for (n = 0; bits; n++) { if (bits & MBOX_BIT(n)) { bits -= MBOX_BIT(n); - read_mailbox[n](); + if (read_mailbox[n]()) + r = IRQ_WAKE_THREAD; } } + return r; +} + +static irqreturn_t prcmu_irq_thread_fn(int irq, void *data) +{ + 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(unsigned int irq) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs &= ~prcmu_irq_bit[irq - IRQ_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + + if (irq != IRQ_PRCMU_CA_SLEEP) + schedule_work(&mb0_transfer.mask_work); +} + +static void prcmu_irq_unmask(unsigned int irq) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs |= prcmu_irq_bit[irq - IRQ_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + + if (irq != IRQ_PRCMU_CA_SLEEP) + schedule_work(&mb0_transfer.mask_work); +} + +static void noop(unsigned int irq) +{ +} + +static struct irq_chip prcmu_irq_chip = { + .name = "prcmu", + .disable = prcmu_irq_mask, + .ack = noop, + .mask = prcmu_irq_mask, + .unmask = prcmu_irq_unmask, +}; + void __init prcmu_early_init(void) { - if (cpu_is_u8500v11() || cpu_is_u8500ed()) { + unsigned int i; + + if (cpu_is_u8500v1()) { tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE_V1); } else if (cpu_is_u8500v2()) { + void *tcpm_base = ioremap_nocache(U8500_PRCMU_TCPM_BASE, SZ_4K); + + if (tcpm_base != NULL) { + int 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", + (version >> 8) & 0xFF, (version >> 16) & 0xFF, + (version >> 24) & 0xFF); + iounmap(tcpm_base); + } + tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); } else { pr_err("prcmu: Unsupported chip version\n"); BUG(); } -} - -static int __init prcmu_init(void) -{ - if (cpu_is_u8500ed()) { - pr_err("prcmu: Unsupported chip version\n"); - return 0; - } + spin_lock_init(&mb0_transfer.lock); + spin_lock_init(&mb0_transfer.dbb_irqs_lock); + mutex_init(&mb0_transfer.ac_wake_lock); + init_completion(&mb0_transfer.ac_wake_work); mutex_init(&mb1_transfer.lock); init_completion(&mb1_transfer.work); + mutex_init(&mb2_transfer.lock); + init_completion(&mb2_transfer.work); + spin_lock_init(&mb2_transfer.auto_pm_lock); + spin_lock_init(&mb3_transfer.lock); + 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_PRCMU_WAKEUPS; i++) { + unsigned int irq; + + irq = IRQ_PRCMU_BASE + i; + set_irq_chip(irq, &prcmu_irq_chip); + set_irq_flags(irq, IRQF_VALID); + set_irq_handler(irq, handle_simple_irq); + } +} + +/** + * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic + * + */ +int __init prcmu_init(void) +{ + int err = 0; + + if (ux500_is_svp()) + return -ENODEV; + /* Clean up the mailbox interrupts after pre-kernel code. */ - writel((MBOX_BIT(NUM_MBOX) - 1), PRCM_ARM_IT1_CLEAR); + writel(ALL_MBOX_BITS, (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); - return request_irq(IRQ_DB8500_PRCMU1, prcmu_irq_handler, 0, - "prcmu", NULL); + err = request_threaded_irq(IRQ_DB8500_PRCMU1, prcmu_irq_handler, + prcmu_irq_thread_fn, IRQF_NO_SUSPEND, "prcmu", NULL); + if (err < 0) { + pr_err("prcmu: Failed to allocate IRQ_DB8500_PRCMU1.\n"); + err = -EBUSY; + goto no_irq_return; + } + + prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); + +no_irq_return: + return err; } arch_initcall(prcmu_init); diff --git a/arch/arm/mach-ux500/prcmu-regs-db8500.h b/arch/arm/mach-ux500/prcmu-regs-db8500.h new file mode 100644 index 00000000000..7ae1218f6ba --- /dev/null +++ b/arch/arm/mach-ux500/prcmu-regs-db8500.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2009 ST-Ericsson SA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ +#ifndef __MACH_PRCMU_REGS_H +#define __MACH_PRCMU_REGS_H + +#include +#include + +#define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#define PRCM_ARM_PLLDIVPS 0x118 +#define PRCM_ARM_PLLDIVPS_ARM_BRM_RATE BITS(0, 5) +#define PRCM_ARM_PLLDIVPS_MAX_MASK 0xF + +#define PRCM_PLLARM_LOCKP 0x0A8 +#define PRCM_PLLARM_LOCKP_PRCM_PLLARM_LOCKP3 BIT(1) + +#define PRCM_ARM_CHGCLKREQ 0x114 +#define PRCM_ARM_CHGCLKREQ_PRCM_ARM_CHGCLKREQ BIT(0) + +#define PRCM_PLLARM_ENABLE 0x98 +#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_ENABLE BIT(0) +#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_COUNTON BIT(8) + +#define PRCM_ARMCLKFIX_MGT 0x0 +#define PRCM_A9_RESETN_CLR 0x1f4 +#define PRCM_A9_RESETN_SET 0x1f0 +#define PRCM_ARM_LS_CLAMP 0x30C +#define PRCM_SRAM_A9 0x308 + +/* ARM WFI Standby signal register */ +#define PRCM_ARM_WFI_STANDBY 0x130 +#define PRCM_IOCR 0x310 +#define PRCM_IOCR_IOFORCE BIT(0) + +/* CPU mailbox registers */ +#define PRCM_MBOX_CPU_VAL 0x0FC +#define PRCM_MBOX_CPU_SET 0x100 + +/* Dual A9 core interrupt management unit registers */ +#define PRCM_A9_MASK_REQ 0x328 +#define PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ BIT(0) + +#define PRCM_A9_MASK_ACK 0x32C +#define PRCM_ARMITMSK31TO0 0x11C +#define PRCM_ARMITMSK63TO32 0x120 +#define PRCM_ARMITMSK95TO64 0x124 +#define PRCM_ARMITMSK127TO96 0x128 +#define PRCM_POWER_STATE_VAL 0x25C +#define PRCM_ARMITVAL31TO0 0x260 +#define PRCM_ARMITVAL63TO32 0x264 +#define PRCM_ARMITVAL95TO64 0x268 +#define PRCM_ARMITVAL127TO96 0x26C + +#define PRCM_HOSTACCESS_REQ 0x334 +#define PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ BIT(0) + +#define PRCM_ARM_IT1_CLR 0x48C +#define PRCM_ARM_IT1_VAL 0x494 + +#define PRCM_ITSTATUS0 0x148 +#define PRCM_ITSTATUS1 0x150 +#define PRCM_ITSTATUS2 0x158 +#define PRCM_ITSTATUS3 0x160 +#define PRCM_ITSTATUS4 0x168 +#define PRCM_ITSTATUS5 0x484 +#define PRCM_ITCLEAR5 0x488 +#define PRCM_ARMIT_MASKXP70_IT 0x1018 + +/* System reset register */ +#define PRCM_APE_SOFTRST 0x228 + +/* Level shifter and clamp control registers */ +#define PRCM_MMIP_LS_CLAMP_SET 0x420 +#define PRCM_MMIP_LS_CLAMP_CLR 0x424 + +/* PRCMU HW semaphore */ +#define PRCM_SEM 0x400 +#define PRCM_SEM_PRCM_SEM BIT(0) + +/* 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 + +#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) + +#define PRCM_CLKOCR 0x1CC +#define PRCM_CLKOCR_CLKODIV0_SHIFT 0 +#define PRCM_CLKOCR_CLKODIV0_MASK BITS(0, 5) +#define PRCM_CLKOCR_CLKOSEL0_SHIFT 6 +#define PRCM_CLKOCR_CLKOSEL0_MASK BITS(6, 8) +#define PRCM_CLKOCR_CLKODIV1_SHIFT 16 +#define PRCM_CLKOCR_CLKODIV1_MASK BITS(16, 21) +#define PRCM_CLKOCR_CLKOSEL1_SHIFT 22 +#define PRCM_CLKOCR_CLKOSEL1_MASK BITS(22, 24) +#define PRCM_CLKOCR_CLK1TYPE BIT(28) + +#define PRCM_SGACLK_MGT 0x014 +#define PRCM_UARTCLK_MGT 0x018 +#define PRCM_MSP02CLK_MGT 0x01C +#define PRCM_MSP1CLK_MGT 0x288 +#define PRCM_I2CCLK_MGT 0x020 +#define PRCM_SDMMCCLK_MGT 0x024 +#define PRCM_SLIMCLK_MGT 0x028 +#define PRCM_PER1CLK_MGT 0x02C +#define PRCM_PER2CLK_MGT 0x030 +#define PRCM_PER3CLK_MGT 0x034 +#define PRCM_PER5CLK_MGT 0x038 +#define PRCM_PER6CLK_MGT 0x03C +#define PRCM_PER7CLK_MGT 0x040 +#define PRCM_LCDCLK_MGT 0x044 +#define PRCM_BMLCLK_MGT 0x04C +#define PRCM_HSITXCLK_MGT 0x050 +#define PRCM_HSIRXCLK_MGT 0x054 +#define PRCM_HDMICLK_MGT 0x058 +#define PRCM_APEATCLK_MGT 0x05C +#define PRCM_APETRACECLK_MGT 0x060 +#define PRCM_MCDECLK_MGT 0x064 +#define PRCM_IPI2CCLK_MGT 0x068 +#define PRCM_DSIALTCLK_MGT 0x06C +#define PRCM_DMACLK_MGT 0x074 +#define PRCM_B2R2CLK_MGT 0x078 +#define PRCM_TVCLK_MGT 0x07C +#define PRCM_UNIPROCLK_MGT 0x278 +#define PRCM_SSPCLK_MGT 0x280 +#define PRCM_RNGCLK_MGT 0x284 +#define PRCM_UICCCLK_MGT 0x27C + +#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) + +/* ePOD and memory power signal control registers */ +#define PRCM_EPOD_C_SET 0x410 +#define PRCM_SRAM_LS_SLEEP 0x304 + +/* Debug power control unit registers */ +#define PRCM_POWER_STATE_SET 0x254 + +/* Miscellaneous unit registers */ +#define PRCM_DSI_SW_RESET 0x324 +#define PRCM_GPIOCR 0x138 + +/* GPIOCR register */ +#define PRCM_GPIOCR_SPI2_SELECT BIT(23) + +#define PRCM_DDR_SUBSYS_APE_MINBW 0x438 + +#endif /* __MACH_PRCMU__REGS_H */ diff --git a/drivers/mfd/ab8500-i2c.c b/drivers/mfd/ab8500-i2c.c index 9be541c6b00..bdfbdb5a558 100644 --- a/drivers/mfd/ab8500-i2c.c +++ b/drivers/mfd/ab8500-i2c.c @@ -12,6 +12,7 @@ #include #include #include +#include static int ab8500_i2c_write(struct ab8500 *ab8500, u16 addr, u8 data) { -- cgit v1.2.3 From a205bd16389dde6ca0029e245c2ce4e668a8fa1f Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 29 Mar 2011 23:10:18 +0200 Subject: mach-ux500: add DB5500 PRCMU interface This provides a snapshot of the DB5500 PRCMU interface, which is still in development, but enough to allow for AB5500 traffic to get through. Signed-off-by: Mattias Nilsson Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/Makefile | 5 +- arch/arm/mach-ux500/cpu.c | 1 + arch/arm/mach-ux500/include/mach/prcmu-db5500.h | 45 +++ arch/arm/mach-ux500/prcmu-db5500.c | 449 ++++++++++++++++++++++++ 4 files changed, 497 insertions(+), 3 deletions(-) create mode 100644 arch/arm/mach-ux500/include/mach/prcmu-db5500.h create mode 100644 arch/arm/mach-ux500/prcmu-db5500.c diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index cef883beb72..32240013414 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -5,8 +5,7 @@ obj-y := clock.o cpu.o devices.o devices-common.o \ id.o usb.o obj-y += pm/ -obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o -obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o +obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o prcmu-db5500.o obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o prcmu-db8500.o obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \ board-mop500-regulators.o \ @@ -18,4 +17,4 @@ obj-$(CONFIG_SMP) += platsmp.o headsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o obj-$(CONFIG_U5500_MODEM_IRQ) += modem-irq-db5500.o -obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o \ No newline at end of file +obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o diff --git a/arch/arm/mach-ux500/cpu.c b/arch/arm/mach-ux500/cpu.c index b33b4f5622c..9d0fcc8bb96 100644 --- a/arch/arm/mach-ux500/cpu.c +++ b/arch/arm/mach-ux500/cpu.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "clock.h" diff --git a/arch/arm/mach-ux500/include/mach/prcmu-db5500.h b/arch/arm/mach-ux500/include/mach/prcmu-db5500.h new file mode 100644 index 00000000000..f0977986402 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/prcmu-db5500.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * U5500 PRCMU API. + */ +#ifndef __MACH_PRCMU_U5500_H +#define __MACH_PRCMU_U5500_H + +#ifdef CONFIG_UX500_SOC_DB5500 + +void db5500_prcmu_early_init(void); + +int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); +int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size); + +#else /* !CONFIG_UX500_SOC_DB5500 */ + +static inline void db5500_prcmu_early_init(void) +{ +} + +static inline int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) +{ + return -ENOSYS; +} + +static inline int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) +{ + return -ENOSYS; +} + +#endif /* CONFIG_UX500_SOC_DB5500 */ + +static inline int db5500_prcmu_config_abb_event_readout(u32 abb_events) +{ +#ifdef CONFIG_MACH_U5500_SIMULATOR + return 0; +#else + return -1; +#endif +} + +#endif /* __MACH_PRCMU_U5500_H */ diff --git a/arch/arm/mach-ux500/prcmu-db5500.c b/arch/arm/mach-ux500/prcmu-db5500.c new file mode 100644 index 00000000000..f19c368f057 --- /dev/null +++ b/arch/arm/mach-ux500/prcmu-db5500.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Mattias Nilsson + * + * U5500 PRCM Unit interface driver + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define _PRCM_MB_HEADER (tcdm_base + 0xFE8) +#define PRCM_REQ_MB0_HEADER (_PRCM_MB_HEADER + 0x0) +#define PRCM_REQ_MB1_HEADER (_PRCM_MB_HEADER + 0x1) +#define PRCM_REQ_MB2_HEADER (_PRCM_MB_HEADER + 0x2) +#define PRCM_REQ_MB3_HEADER (_PRCM_MB_HEADER + 0x3) +#define PRCM_REQ_MB4_HEADER (_PRCM_MB_HEADER + 0x4) +#define PRCM_REQ_MB5_HEADER (_PRCM_MB_HEADER + 0x5) +#define PRCM_REQ_MB6_HEADER (_PRCM_MB_HEADER + 0x6) +#define PRCM_REQ_MB7_HEADER (_PRCM_MB_HEADER + 0x7) +#define PRCM_ACK_MB0_HEADER (_PRCM_MB_HEADER + 0x8) +#define PRCM_ACK_MB1_HEADER (_PRCM_MB_HEADER + 0x9) +#define PRCM_ACK_MB2_HEADER (_PRCM_MB_HEADER + 0xa) +#define PRCM_ACK_MB3_HEADER (_PRCM_MB_HEADER + 0xb) +#define PRCM_ACK_MB4_HEADER (_PRCM_MB_HEADER + 0xc) +#define PRCM_ACK_MB5_HEADER (_PRCM_MB_HEADER + 0xd) +#define PRCM_ACK_MB6_HEADER (_PRCM_MB_HEADER + 0xe) +#define PRCM_ACK_MB7_HEADER (_PRCM_MB_HEADER + 0xf) + +/* Req Mailboxes */ +#define PRCM_REQ_MB0 (tcdm_base + 0xFD8) +#define PRCM_REQ_MB1 (tcdm_base + 0xFCC) +#define PRCM_REQ_MB2 (tcdm_base + 0xFC4) +#define PRCM_REQ_MB3 (tcdm_base + 0xFC0) +#define PRCM_REQ_MB4 (tcdm_base + 0xF98) +#define PRCM_REQ_MB5 (tcdm_base + 0xF90) +#define PRCM_REQ_MB6 (tcdm_base + 0xF8C) +#define PRCM_REQ_MB7 (tcdm_base + 0xF84) + +/* Ack Mailboxes */ +#define PRCM_ACK_MB0 (tcdm_base + 0xF38) +#define PRCM_ACK_MB1 (tcdm_base + 0xF30) +#define PRCM_ACK_MB2 (tcdm_base + 0xF24) +#define PRCM_ACK_MB3 (tcdm_base + 0xF20) +#define PRCM_ACK_MB4 (tcdm_base + 0xF1C) +#define PRCM_ACK_MB5 (tcdm_base + 0xF14) +#define PRCM_ACK_MB6 (tcdm_base + 0xF0C) +#define PRCM_ACK_MB7 (tcdm_base + 0xF08) + +enum mb_return_code { + RC_SUCCESS, + RC_FAIL, +}; + +/* 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, +}; + +/* Mailbox 5 headers. */ +enum mb5_header { + MB5H_I2C_WRITE = 1, + MB5H_I2C_READ, +}; + +/* Request mailbox 5 fields. */ +#define PRCM_REQ_MB5_I2C_SLAVE (PRCM_REQ_MB5 + 0) +#define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 1) +#define PRCM_REQ_MB5_I2C_SIZE (PRCM_REQ_MB5 + 2) +#define PRCM_REQ_MB5_I2C_DATA (PRCM_REQ_MB5 + 4) + +/* Acknowledge mailbox 5 fields. */ +#define PRCM_ACK_MB5_RETURN_CODE (PRCM_ACK_MB5 + 0) +#define PRCM_ACK_MB5_I2C_DATA (PRCM_ACK_MB5 + 4) + +#define NUM_MB 8 +#define MBOX_BIT BIT +#define ALL_MBOX_BITS (MBOX_BIT(NUM_MB) - 1) + +/* +* Used by MCDE to setup all necessary PRCMU registers +*/ +#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 +/* TVCLK_MGT PLLSW=001 (PLLSOC0) PLLDIV=0x13, = 19.05 MHZ */ +#define PRCMU_DSI_LP_CLOCK_SETTING 0x00000135 +#define PRCMU_PLLDSI_FREQ_SETTING 0x0004013C +#define PRCMU_DSI_PLLOUT_SEL_SETTING 0x00000002 +#define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000101 +#define PRCMU_DISABLE_ESCAPE_CLOCK_DIV 0x00000101 + +#define PRCMU_ENABLE_PLLDSI 0x00000001 +#define PRCMU_DISABLE_PLLDSI 0x00000000 + +#define PRCMU_DSI_RESET_SW 0x00000003 + +#define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 + +/* + * mb0_transfer - state needed for mailbox 0 communication. + * @lock: The transaction lock. + */ +static struct { + spinlock_t lock; +} mb0_transfer; + +/* + * mb5_transfer - state needed for mailbox 5 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 header; + u8 status; + u8 value[4]; + } ack; +} mb5_transfer; + +/* PRCMU TCDM base IO address. */ +static __iomem void *tcdm_base; + +/** + * db5500_prcmu_abb_read() - Read register value(s) from the ABB. + * @slave: The I2C slave address. + * @reg: The (start) register address. + * @value: The read out value(s). + * @size: The number of registers to read. + * + * Reads register value(s) from the ABB. + * @size has to be <= 4. + */ +int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) +{ + int r; + + if ((size < 1) || (4 < size)) + return -EINVAL; + + mutex_lock(&mb5_transfer.lock); + + while (readl(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); + wait_for_completion(&mb5_transfer.work); + + r = 0; + if ((mb5_transfer.ack.header == MB5H_I2C_READ) && + (mb5_transfer.ack.status == RC_SUCCESS)) + memcpy(value, mb5_transfer.ack.value, (size_t)size); + else + r = -EIO; + + mutex_unlock(&mb5_transfer.lock); + + return r; +} + +/** + * db5500_prcmu_abb_write() - Write register value(s) to the ABB. + * @slave: The I2C slave address. + * @reg: The (start) register address. + * @value: The value(s) to write. + * @size: The number of registers to write. + * + * Writes register value(s) to the ABB. + * @size has to be <= 4. + */ +int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) +{ + int r; + + if ((size < 1) || (4 < size)) + return -EINVAL; + + mutex_lock(&mb5_transfer.lock); + + while (readl(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); + 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); + wait_for_completion(&mb5_transfer.work); + + if ((mb5_transfer.ack.header == MB5H_I2C_WRITE) && + (mb5_transfer.ack.status == RC_SUCCESS)) + r = 0; + else + r = -EIO; + + mutex_unlock(&mb5_transfer.lock); + + return r; +} + +int db5500_prcmu_enable_dsipll(void) +{ + int i; + + /* Enable DSIPLL_RESETN resets */ + writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); + /* Unclamp DSIPLL in/out */ + writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); + /* Set DSI PLL 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); + + /* Start DSI PLL */ + writel(PRCMU_ENABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + /* Reset DSI PLL */ + writel(PRCMU_DSI_RESET_SW, PRCM_DSI_SW_RESET); + for (i = 0; i < 10; i++) { + if ((readl(PRCM_PLLDSI_LOCKP) & + PRCMU_PLLDSI_LOCKP_LOCKED) == PRCMU_PLLDSI_LOCKP_LOCKED) + break; + udelay(100); + } + /* Release DSIPLL_RESETN */ + writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_SET); + return 0; +} + +int db5500_prcmu_disable_dsipll(void) +{ + /* Disable dsi pll */ + writel(PRCMU_DISABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + /* Disable escapeclock */ + writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + return 0; +} + +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); + /* PLLDIV=14, PLLSW=2, CLKEN=1 */ + writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); + return 0; +} + +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)) + cpu_relax(); + + writeb(RMB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +static inline void print_unknown_header_warning(u8 n, u8 header) +{ + pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", + header, n); +} + +static bool read_mailbox_0(void) +{ + bool r; + u8 header; + + header = readb(PRCM_ACK_MB0_HEADER); + switch (header) { + case AMB0H_WAKE_UP: + r = true; + break; + default: + print_unknown_header_warning(0, header); + r = false; + break; + } + writel(MBOX_BIT(0), PRCM_ARM_IT1_CLEAR); + return r; +} + +static bool read_mailbox_1(void) +{ + writel(MBOX_BIT(1), PRCM_ARM_IT1_CLEAR); + return false; +} + +static bool read_mailbox_2(void) +{ + writel(MBOX_BIT(2), PRCM_ARM_IT1_CLEAR); + return false; +} + +static bool read_mailbox_3(void) +{ + writel(MBOX_BIT(3), PRCM_ARM_IT1_CLEAR); + return false; +} + +static bool read_mailbox_4(void) +{ + writel(MBOX_BIT(4), PRCM_ARM_IT1_CLEAR); + return false; +} + +static bool read_mailbox_5(void) +{ + u8 header; + + header = readb(PRCM_ACK_MB5_HEADER); + switch (header) { + case MB5H_I2C_READ: + memcpy_fromio(mb5_transfer.ack.value, PRCM_ACK_MB5_I2C_DATA, 4); + case MB5H_I2C_WRITE: + mb5_transfer.ack.header = header; + mb5_transfer.ack.status = readb(PRCM_ACK_MB5_RETURN_CODE); + complete(&mb5_transfer.work); + break; + default: + print_unknown_header_warning(5, header); + break; + } + writel(MBOX_BIT(5), PRCM_ARM_IT1_CLEAR); + return false; +} + +static bool read_mailbox_6(void) +{ + writel(MBOX_BIT(6), PRCM_ARM_IT1_CLEAR); + return false; +} + +static bool read_mailbox_7(void) +{ + writel(MBOX_BIT(7), PRCM_ARM_IT1_CLEAR); + return false; +} + +static bool (* const read_mailbox[NUM_MB])(void) = { + read_mailbox_0, + read_mailbox_1, + read_mailbox_2, + read_mailbox_3, + read_mailbox_4, + read_mailbox_5, + read_mailbox_6, + read_mailbox_7 +}; + +static irqreturn_t prcmu_irq_handler(int irq, void *data) +{ + u32 bits; + u8 n; + irqreturn_t r; + + bits = (readl(PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); + if (unlikely(!bits)) + return IRQ_NONE; + + r = IRQ_HANDLED; + for (n = 0; bits; n++) { + if (bits & MBOX_BIT(n)) { + bits -= MBOX_BIT(n); + if (read_mailbox[n]()) + r = IRQ_WAKE_THREAD; + } + } + return r; +} + +static irqreturn_t prcmu_irq_thread_fn(int irq, void *data) +{ + ack_dbb_wakeup(); + return IRQ_HANDLED; +} + +void __init db5500_prcmu_early_init(void) +{ + tcdm_base = __io_address(U5500_PRCMU_TCDM_BASE); + spin_lock_init(&mb0_transfer.lock); + mutex_init(&mb5_transfer.lock); + init_completion(&mb5_transfer.work); +} + +/** + * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic + * + */ +int __init db5500_prcmu_init(void) +{ + int r = 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_CLEAR); + + r = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, + prcmu_irq_thread_fn, 0, "prcmu", NULL); + if (r < 0) { + pr_err("prcmu: Failed to allocate IRQ_DB5500_PRCMU1.\n"); + return -EBUSY; + } + return 0; +} + +arch_initcall(db5500_prcmu_init); -- cgit v1.2.3 From f4ac3e2c1418447b2f610c9e366eb4e541ec1c3e Mon Sep 17 00:00:00 2001 From: Fredrik Svensson Date: Thu, 31 Mar 2011 13:55:40 +0200 Subject: plat-nomadik: remove pull-pinconfig and add SPI2 Remove PIN_CFG_PULL in pincfg.h for plat-nomadik, PIN_CFG_INPUT already takes care of this. Added support for SPI2. Signed-off-by: Fredrik Svensson Signed-off-by: Daniel Willerud Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/pins-db8500.h | 142 ++++++++++++++-------------- arch/arm/plat-nomadik/include/plat/pincfg.h | 5 - 2 files changed, 73 insertions(+), 74 deletions(-) diff --git a/arch/arm/mach-ux500/pins-db8500.h b/arch/arm/mach-ux500/pins-db8500.h index f923764ee16..8b1d1a7a679 100644 --- a/arch/arm/mach-ux500/pins-db8500.h +++ b/arch/arm/mach-ux500/pins-db8500.h @@ -35,40 +35,40 @@ #define GPIO4_GPIO PIN_CFG(4, GPIO) #define GPIO4_U1_RXD PIN_CFG(4, ALT_A) -#define GPIO4_I2C4_SCL PIN_CFG_PULL(4, ALT_B, UP) +#define GPIO4_I2C4_SCL PIN_CFG_INPUT(4, ALT_B, PULLUP) #define GPIO4_IP_TRSTn PIN_CFG(4, ALT_C) #define GPIO5_GPIO PIN_CFG(5, GPIO) #define GPIO5_U1_TXD PIN_CFG(5, ALT_A) -#define GPIO5_I2C4_SDA PIN_CFG_PULL(5, ALT_B, UP) +#define GPIO5_I2C4_SDA PIN_CFG_INPUT(5, ALT_B, PULLUP) #define GPIO5_IP_GPIO6 PIN_CFG(5, ALT_C) #define GPIO6_GPIO PIN_CFG(6, GPIO) #define GPIO6_U1_CTSn PIN_CFG(6, ALT_A) -#define GPIO6_I2C1_SCL PIN_CFG_PULL(6, ALT_B, UP) +#define GPIO6_I2C1_SCL PIN_CFG_INPUT(6, ALT_B, PULLUP) #define GPIO6_IP_GPIO0 PIN_CFG(6, ALT_C) #define GPIO7_GPIO PIN_CFG(7, GPIO) #define GPIO7_U1_RTSn PIN_CFG(7, ALT_A) -#define GPIO7_I2C1_SDA PIN_CFG_PULL(7, ALT_B, UP) +#define GPIO7_I2C1_SDA PIN_CFG_INPUT(7, ALT_B, PULLUP) #define GPIO7_IP_GPIO1 PIN_CFG(7, ALT_C) #define GPIO8_GPIO PIN_CFG(8, GPIO) -#define GPIO8_IPI2C_SDA PIN_CFG_PULL(8, ALT_A, UP) -#define GPIO8_I2C2_SDA PIN_CFG_PULL(8, ALT_B, UP) +#define GPIO8_IPI2C_SDA PIN_CFG_INPUT(8, ALT_A, PULLUP) +#define GPIO8_I2C2_SDA PIN_CFG_INPUT(8, ALT_B, PULLUP) #define GPIO9_GPIO PIN_CFG(9, GPIO) -#define GPIO9_IPI2C_SCL PIN_CFG_PULL(9, ALT_A, UP) -#define GPIO9_I2C2_SCL PIN_CFG_PULL(9, ALT_B, UP) +#define GPIO9_IPI2C_SCL PIN_CFG_INPUT(9, ALT_A, PULLUP) +#define GPIO9_I2C2_SCL PIN_CFG_INPUT(9, ALT_B, PULLUP) #define GPIO10_GPIO PIN_CFG(10, GPIO) -#define GPIO10_IPI2C_SDA PIN_CFG_PULL(10, ALT_A, UP) -#define GPIO10_I2C2_SDA PIN_CFG_PULL(10, ALT_B, UP) +#define GPIO10_IPI2C_SDA PIN_CFG_INPUT(10, ALT_A, PULLUP) +#define GPIO10_I2C2_SDA PIN_CFG_INPUT(10, ALT_B, PULLUP) #define GPIO10_IP_GPIO3 PIN_CFG(10, ALT_C) #define GPIO11_GPIO PIN_CFG(11, GPIO) -#define GPIO11_IPI2C_SCL PIN_CFG_PULL(11, ALT_A, UP) -#define GPIO11_I2C2_SCL PIN_CFG_PULL(11, ALT_B, UP) +#define GPIO11_IPI2C_SCL PIN_CFG_INPUT(11, ALT_A, PULLUP) +#define GPIO11_I2C2_SCL PIN_CFG_INPUT(11, ALT_B, PULLUP) #define GPIO11_IP_GPIO2 PIN_CFG(11, ALT_C) #define GPIO12_GPIO PIN_CFG(12, GPIO) @@ -87,66 +87,66 @@ #define GPIO16_GPIO PIN_CFG(16, GPIO) #define GPIO16_MSP0_RFS PIN_CFG(16, ALT_A) -#define GPIO16_I2C1_SCL PIN_CFG_PULL(16, ALT_B, UP) +#define GPIO16_I2C1_SCL PIN_CFG_INPUT(16, ALT_B, PULLUP) #define GPIO16_SLIM0_DAT PIN_CFG(16, ALT_C) #define GPIO17_GPIO PIN_CFG(17, GPIO) #define GPIO17_MSP0_RCK PIN_CFG(17, ALT_A) -#define GPIO17_I2C1_SDA PIN_CFG_PULL(17, ALT_B, UP) +#define GPIO17_I2C1_SDA PIN_CFG_INPUT(17, ALT_B, PULLUP) #define GPIO17_SLIM0_CLK PIN_CFG(17, ALT_C) #define GPIO18_GPIO PIN_CFG(18, GPIO) -#define GPIO18_MC0_CMDDIR PIN_CFG_PULL(18, ALT_A, UP) +#define GPIO18_MC0_CMDDIR PIN_CFG_INPUT(18, ALT_A, PULLUP) #define GPIO18_U2_RXD PIN_CFG(18, ALT_B) #define GPIO18_MS_IEP PIN_CFG(18, ALT_C) #define GPIO19_GPIO PIN_CFG(19, GPIO) -#define GPIO19_MC0_DAT0DIR PIN_CFG_PULL(19, ALT_A, UP) +#define GPIO19_MC0_DAT0DIR PIN_CFG_INPUT(19, ALT_A, PULLUP) #define GPIO19_U2_TXD PIN_CFG(19, ALT_B) #define GPIO19_MS_DAT0DIR PIN_CFG(19, ALT_C) #define GPIO20_GPIO PIN_CFG(20, GPIO) -#define GPIO20_MC0_DAT2DIR PIN_CFG_PULL(20, ALT_A, UP) +#define GPIO20_MC0_DAT2DIR PIN_CFG_INPUT(20, ALT_A, PULLUP) #define GPIO20_UARTMOD_TXD PIN_CFG(20, ALT_B) #define GPIO20_IP_TRIGOUT PIN_CFG(20, ALT_C) #define GPIO21_GPIO PIN_CFG(21, GPIO) -#define GPIO21_MC0_DAT31DIR PIN_CFG_PULL(21, ALT_A, UP) +#define GPIO21_MC0_DAT31DIR PIN_CFG_INPUT(21, ALT_A, PULLUP) #define GPIO21_MSP0_SCK PIN_CFG(21, ALT_B) #define GPIO21_MS_DAT31DIR PIN_CFG(21, ALT_C) #define GPIO22_GPIO PIN_CFG(22, GPIO) -#define GPIO22_MC0_FBCLK PIN_CFG_PULL(22, ALT_A, UP) +#define GPIO22_MC0_FBCLK PIN_CFG_INPUT(22, ALT_A, PULLUP) #define GPIO22_UARTMOD_RXD PIN_CFG(22, ALT_B) #define GPIO22_MS_FBCLK PIN_CFG(22, ALT_C) #define GPIO23_GPIO PIN_CFG(23, GPIO) -#define GPIO23_MC0_CLK PIN_CFG_PULL(23, ALT_A, UP) +#define GPIO23_MC0_CLK PIN_CFG_INPUT(23, ALT_A, PULLUP) #define GPIO23_STMMOD_CLK PIN_CFG(23, ALT_B) #define GPIO23_MS_CLK PIN_CFG(23, ALT_C) #define GPIO24_GPIO PIN_CFG(24, GPIO) -#define GPIO24_MC0_CMD PIN_CFG_PULL(24, ALT_A, UP) +#define GPIO24_MC0_CMD PIN_CFG_INPUT(24, ALT_A, PULLUP) #define GPIO24_UARTMOD_RXD PIN_CFG(24, ALT_B) #define GPIO24_MS_BS PIN_CFG(24, ALT_C) #define GPIO25_GPIO PIN_CFG(25, GPIO) -#define GPIO25_MC0_DAT0 PIN_CFG_PULL(25, ALT_A, UP) +#define GPIO25_MC0_DAT0 PIN_CFG_INPUT(25, ALT_A, PULLUP) #define GPIO25_STMMOD_DAT0 PIN_CFG(25, ALT_B) #define GPIO25_MS_DAT0 PIN_CFG(25, ALT_C) #define GPIO26_GPIO PIN_CFG(26, GPIO) -#define GPIO26_MC0_DAT1 PIN_CFG_PULL(26, ALT_A, UP) +#define GPIO26_MC0_DAT1 PIN_CFG_INPUT(26, ALT_A, PULLUP) #define GPIO26_STMMOD_DAT1 PIN_CFG(26, ALT_B) #define GPIO26_MS_DAT1 PIN_CFG(26, ALT_C) #define GPIO27_GPIO PIN_CFG(27, GPIO) -#define GPIO27_MC0_DAT2 PIN_CFG_PULL(27, ALT_A, UP) +#define GPIO27_MC0_DAT2 PIN_CFG_INPUT(27, ALT_A, PULLUP) #define GPIO27_STMMOD_DAT2 PIN_CFG(27, ALT_B) #define GPIO27_MS_DAT2 PIN_CFG(27, ALT_C) #define GPIO28_GPIO PIN_CFG(28, GPIO) -#define GPIO28_MC0_DAT3 PIN_CFG_PULL(28, ALT_A, UP) +#define GPIO28_MC0_DAT3 PIN_CFG_INPUT(28, ALT_A, PULLUP) #define GPIO28_STMMOD_DAT3 PIN_CFG(28, ALT_B) #define GPIO28_MS_DAT3 PIN_CFG(28, ALT_C) @@ -357,48 +357,48 @@ #define GPIO97_MC5_DAT7 PIN_CFG(97, ALT_C) #define GPIO128_GPIO PIN_CFG(128, GPIO) -#define GPIO128_MC2_CLK PIN_CFG_PULL(128, ALT_A, UP) +#define GPIO128_MC2_CLK PIN_CFG_INPUT(128, ALT_A, PULLUP) #define GPIO128_SM_CKO PIN_CFG(128, ALT_B) #define GPIO129_GPIO PIN_CFG(129, GPIO) -#define GPIO129_MC2_CMD PIN_CFG_PULL(129, ALT_A, UP) +#define GPIO129_MC2_CMD PIN_CFG_INPUT(129, ALT_A, PULLUP) #define GPIO129_SM_WAIT0n PIN_CFG(129, ALT_B) #define GPIO130_GPIO PIN_CFG(130, GPIO) -#define GPIO130_MC2_FBCLK PIN_CFG_PULL(130, ALT_A, UP) +#define GPIO130_MC2_FBCLK PIN_CFG_INPUT(130, ALT_A, PULLUP) #define GPIO130_SM_FBCLK PIN_CFG(130, ALT_B) #define GPIO130_MC2_RSTN PIN_CFG(130, ALT_C) #define GPIO131_GPIO PIN_CFG(131, GPIO) -#define GPIO131_MC2_DAT0 PIN_CFG_PULL(131, ALT_A, UP) +#define GPIO131_MC2_DAT0 PIN_CFG_INPUT(131, ALT_A, PULLUP) #define GPIO131_SM_ADQ8 PIN_CFG(131, ALT_B) #define GPIO132_GPIO PIN_CFG(132, GPIO) -#define GPIO132_MC2_DAT1 PIN_CFG_PULL(132, ALT_A, UP) +#define GPIO132_MC2_DAT1 PIN_CFG_INPUT(132, ALT_A, PULLUP) #define GPIO132_SM_ADQ9 PIN_CFG(132, ALT_B) #define GPIO133_GPIO PIN_CFG(133, GPIO) -#define GPIO133_MC2_DAT2 PIN_CFG_PULL(133, ALT_A, UP) +#define GPIO133_MC2_DAT2 PIN_CFG_INPUT(133, ALT_A, PULLUP) #define GPIO133_SM_ADQ10 PIN_CFG(133, ALT_B) #define GPIO134_GPIO PIN_CFG(134, GPIO) -#define GPIO134_MC2_DAT3 PIN_CFG_PULL(134, ALT_A, UP) +#define GPIO134_MC2_DAT3 PIN_CFG_INPUT(134, ALT_A, PULLUP) #define GPIO134_SM_ADQ11 PIN_CFG(134, ALT_B) #define GPIO135_GPIO PIN_CFG(135, GPIO) -#define GPIO135_MC2_DAT4 PIN_CFG_PULL(135, ALT_A, UP) +#define GPIO135_MC2_DAT4 PIN_CFG_INPUT(135, ALT_A, PULLUP) #define GPIO135_SM_ADQ12 PIN_CFG(135, ALT_B) #define GPIO136_GPIO PIN_CFG(136, GPIO) -#define GPIO136_MC2_DAT5 PIN_CFG_PULL(136, ALT_A, UP) +#define GPIO136_MC2_DAT5 PIN_CFG_INPUT(136, ALT_A, PULLUP) #define GPIO136_SM_ADQ13 PIN_CFG(136, ALT_B) #define GPIO137_GPIO PIN_CFG(137, GPIO) -#define GPIO137_MC2_DAT6 PIN_CFG_PULL(137, ALT_A, UP) +#define GPIO137_MC2_DAT6 PIN_CFG_INPUT(137, ALT_A, PULLUP) #define GPIO137_SM_ADQ14 PIN_CFG(137, ALT_B) #define GPIO138_GPIO PIN_CFG(138, GPIO) -#define GPIO138_MC2_DAT7 PIN_CFG_PULL(138, ALT_A, UP) +#define GPIO138_MC2_DAT7 PIN_CFG_INPUT(138, ALT_A, PULLUP) #define GPIO138_SM_ADQ15 PIN_CFG(138, ALT_B) #define GPIO139_GPIO PIN_CFG(139, GPIO) @@ -434,10 +434,10 @@ #define GPIO146_SSP0_TXD PIN_CFG(146, ALT_A) #define GPIO147_GPIO PIN_CFG(147, GPIO) -#define GPIO147_I2C0_SCL PIN_CFG_PULL(147, ALT_A, UP) +#define GPIO147_I2C0_SCL PIN_CFG_INPUT(147, ALT_A, PULLUP) #define GPIO148_GPIO PIN_CFG(148, GPIO) -#define GPIO148_I2C0_SDA PIN_CFG_PULL(148, ALT_A, UP) +#define GPIO148_I2C0_SDA PIN_CFG_INPUT(148, ALT_A, PULLUP) #define GPIO149_GPIO PIN_CFG(149, GPIO) #define GPIO149_IP_GPIO0 PIN_CFG(149, ALT_A) @@ -459,82 +459,82 @@ #define GPIO152_KP_O9 PIN_CFG(152, ALT_C) #define GPIO153_GPIO PIN_CFG(153, GPIO) -#define GPIO153_KP_I7 PIN_CFG_PULL(153, ALT_A, DOWN) +#define GPIO153_KP_I7 PIN_CFG_INPUT(153, ALT_A, PULLDOWN) #define GPIO153_LCD_D24 PIN_CFG(153, ALT_B) #define GPIO153_U2_RXD PIN_CFG(153, ALT_C) #define GPIO154_GPIO PIN_CFG(154, GPIO) -#define GPIO154_KP_I6 PIN_CFG_PULL(154, ALT_A, DOWN) +#define GPIO154_KP_I6 PIN_CFG_INPUT(154, ALT_A, PULLDOWN) #define GPIO154_LCD_D25 PIN_CFG(154, ALT_B) #define GPIO154_U2_TXD PIN_CFG(154, ALT_C) #define GPIO155_GPIO PIN_CFG(155, GPIO) -#define GPIO155_KP_I5 PIN_CFG_PULL(155, ALT_A, DOWN) +#define GPIO155_KP_I5 PIN_CFG_INPUT(155, ALT_A, PULLDOWN) #define GPIO155_LCD_D26 PIN_CFG(155, ALT_B) #define GPIO155_STMAPE_CLK PIN_CFG(155, ALT_C) #define GPIO156_GPIO PIN_CFG(156, GPIO) -#define GPIO156_KP_I4 PIN_CFG_PULL(156, ALT_A, DOWN) +#define GPIO156_KP_I4 PIN_CFG_INPUT(156, ALT_A, PULLDOWN) #define GPIO156_LCD_D27 PIN_CFG(156, ALT_B) #define GPIO156_STMAPE_DAT3 PIN_CFG(156, ALT_C) #define GPIO157_GPIO PIN_CFG(157, GPIO) -#define GPIO157_KP_O7 PIN_CFG_PULL(157, ALT_A, UP) +#define GPIO157_KP_O7 PIN_CFG_INPUT(157, ALT_A, PULLUP) #define GPIO157_LCD_D28 PIN_CFG(157, ALT_B) #define GPIO157_STMAPE_DAT2 PIN_CFG(157, ALT_C) #define GPIO158_GPIO PIN_CFG(158, GPIO) -#define GPIO158_KP_O6 PIN_CFG_PULL(158, ALT_A, UP) +#define GPIO158_KP_O6 PIN_CFG_INPUT(158, ALT_A, PULLUP) #define GPIO158_LCD_D29 PIN_CFG(158, ALT_B) #define GPIO158_STMAPE_DAT1 PIN_CFG(158, ALT_C) #define GPIO159_GPIO PIN_CFG(159, GPIO) -#define GPIO159_KP_O5 PIN_CFG_PULL(159, ALT_A, UP) +#define GPIO159_KP_O5 PIN_CFG_INPUT(159, ALT_A, PULLUP) #define GPIO159_LCD_D30 PIN_CFG(159, ALT_B) #define GPIO159_STMAPE_DAT0 PIN_CFG(159, ALT_C) #define GPIO160_GPIO PIN_CFG(160, GPIO) -#define GPIO160_KP_O4 PIN_CFG_PULL(160, ALT_A, UP) +#define GPIO160_KP_O4 PIN_CFG_INPUT(160, ALT_A, PULLUP) #define GPIO160_LCD_D31 PIN_CFG(160, ALT_B) #define GPIO160_NONE PIN_CFG(160, ALT_C) #define GPIO161_GPIO PIN_CFG(161, GPIO) -#define GPIO161_KP_I3 PIN_CFG_PULL(161, ALT_A, DOWN) +#define GPIO161_KP_I3 PIN_CFG_INPUT(161, ALT_A, PULLDOWN) #define GPIO161_LCD_D32 PIN_CFG(161, ALT_B) #define GPIO161_UARTMOD_RXD PIN_CFG(161, ALT_C) #define GPIO162_GPIO PIN_CFG(162, GPIO) -#define GPIO162_KP_I2 PIN_CFG_PULL(162, ALT_A, DOWN) +#define GPIO162_KP_I2 PIN_CFG_INPUT(162, ALT_A, PULLDOWN) #define GPIO162_LCD_D33 PIN_CFG(162, ALT_B) #define GPIO162_UARTMOD_TXD PIN_CFG(162, ALT_C) #define GPIO163_GPIO PIN_CFG(163, GPIO) -#define GPIO163_KP_I1 PIN_CFG_PULL(163, ALT_A, DOWN) +#define GPIO163_KP_I1 PIN_CFG_INPUT(163, ALT_A, PULLDOWN) #define GPIO163_LCD_D34 PIN_CFG(163, ALT_B) #define GPIO163_STMMOD_CLK PIN_CFG(163, ALT_C) #define GPIO164_GPIO PIN_CFG(164, GPIO) -#define GPIO164_KP_I0 PIN_CFG_PULL(164, ALT_A, UP) +#define GPIO164_KP_I0 PIN_CFG_INPUT(164, ALT_A, PULLUP) #define GPIO164_LCD_D35 PIN_CFG(164, ALT_B) #define GPIO164_STMMOD_DAT3 PIN_CFG(164, ALT_C) #define GPIO165_GPIO PIN_CFG(165, GPIO) -#define GPIO165_KP_O3 PIN_CFG_PULL(165, ALT_A, UP) +#define GPIO165_KP_O3 PIN_CFG_INPUT(165, ALT_A, PULLUP) #define GPIO165_LCD_D36 PIN_CFG(165, ALT_B) #define GPIO165_STMMOD_DAT2 PIN_CFG(165, ALT_C) #define GPIO166_GPIO PIN_CFG(166, GPIO) -#define GPIO166_KP_O2 PIN_CFG_PULL(166, ALT_A, UP) +#define GPIO166_KP_O2 PIN_CFG_INPUT(166, ALT_A, PULLUP) #define GPIO166_LCD_D37 PIN_CFG(166, ALT_B) #define GPIO166_STMMOD_DAT1 PIN_CFG(166, ALT_C) #define GPIO167_GPIO PIN_CFG(167, GPIO) -#define GPIO167_KP_O1 PIN_CFG_PULL(167, ALT_A, UP) +#define GPIO167_KP_O1 PIN_CFG_INPUT(167, ALT_A, PULLUP) #define GPIO167_LCD_D38 PIN_CFG(167, ALT_B) #define GPIO167_STMMOD_DAT0 PIN_CFG(167, ALT_C) #define GPIO168_GPIO PIN_CFG(168, GPIO) -#define GPIO168_KP_O0 PIN_CFG_PULL(168, ALT_A, UP) +#define GPIO168_KP_O0 PIN_CFG_INPUT(168, ALT_A, PULLUP) #define GPIO168_LCD_D39 PIN_CFG(168, ALT_B) #define GPIO168_NONE PIN_CFG(168, ALT_C) @@ -569,39 +569,39 @@ #define GPIO196_MSP2_RXD PIN_CFG(196, ALT_A) #define GPIO197_GPIO PIN_CFG(197, GPIO) -#define GPIO197_MC4_DAT3 PIN_CFG_PULL(197, ALT_A, UP) +#define GPIO197_MC4_DAT3 PIN_CFG_INPUT(197, ALT_A, PULLUP) #define GPIO198_GPIO PIN_CFG(198, GPIO) -#define GPIO198_MC4_DAT2 PIN_CFG_PULL(198, ALT_A, UP) +#define GPIO198_MC4_DAT2 PIN_CFG_INPUT(198, ALT_A, PULLUP) #define GPIO199_GPIO PIN_CFG(199, GPIO) -#define GPIO199_MC4_DAT1 PIN_CFG_PULL(199, ALT_A, UP) +#define GPIO199_MC4_DAT1 PIN_CFG_INPUT(199, ALT_A, PULLUP) #define GPIO200_GPIO PIN_CFG(200, GPIO) -#define GPIO200_MC4_DAT0 PIN_CFG_PULL(200, ALT_A, UP) +#define GPIO200_MC4_DAT0 PIN_CFG_INPUT(200, ALT_A, PULLUP) #define GPIO201_GPIO PIN_CFG(201, GPIO) -#define GPIO201_MC4_CMD PIN_CFG_PULL(201, ALT_A, UP) +#define GPIO201_MC4_CMD PIN_CFG_INPUT(201, ALT_A, PULLUP) #define GPIO202_GPIO PIN_CFG(202, GPIO) -#define GPIO202_MC4_FBCLK PIN_CFG_PULL(202, ALT_A, UP) +#define GPIO202_MC4_FBCLK PIN_CFG_INPUT(202, ALT_A, PULLUP) #define GPIO202_PWL PIN_CFG(202, ALT_B) #define GPIO202_MC4_RSTN PIN_CFG(202, ALT_C) #define GPIO203_GPIO PIN_CFG(203, GPIO) -#define GPIO203_MC4_CLK PIN_CFG_PULL(203, ALT_A, UP) +#define GPIO203_MC4_CLK PIN_CFG_INPUT(203, ALT_A, PULLUP) #define GPIO204_GPIO PIN_CFG(204, GPIO) -#define GPIO204_MC4_DAT7 PIN_CFG_PULL(204, ALT_A, UP) +#define GPIO204_MC4_DAT7 PIN_CFG_INPUT(204, ALT_A, PULLUP) #define GPIO205_GPIO PIN_CFG(205, GPIO) -#define GPIO205_MC4_DAT6 PIN_CFG_PULL(205, ALT_A, UP) +#define GPIO205_MC4_DAT6 PIN_CFG_INPUT(205, ALT_A, PULLUP) #define GPIO206_GPIO PIN_CFG(206, GPIO) -#define GPIO206_MC4_DAT5 PIN_CFG_PULL(206, ALT_A, UP) +#define GPIO206_MC4_DAT5 PIN_CFG_INPUT(206, ALT_A, PULLUP) #define GPIO207_GPIO PIN_CFG(207, GPIO) -#define GPIO207_MC4_DAT4 PIN_CFG_PULL(207, ALT_A, UP) +#define GPIO207_MC4_DAT4 PIN_CFG_INPUT(207, ALT_A, PULLUP) #define GPIO208_GPIO PIN_CFG(208, GPIO) #define GPIO208_MC1_CLK PIN_CFG(208, ALT_A) @@ -632,21 +632,25 @@ #define GPIO215_MC1_CMDDIR PIN_CFG(215, ALT_A) #define GPIO215_MC3_DAT2DIR PIN_CFG(215, ALT_B) #define GPIO215_CLKOUT1 PIN_CFG(215, ALT_C) +#define GPIO215_SPI2_TXD PIN_CFG(215, ALT_C) #define GPIO216_GPIO PIN_CFG(216, GPIO) #define GPIO216_MC1_DAT2DIR PIN_CFG(216, ALT_A) #define GPIO216_MC3_CMDDIR PIN_CFG(216, ALT_B) -#define GPIO216_I2C3_SDA PIN_CFG_PULL(216, ALT_C, UP) +#define GPIO216_I2C3_SDA PIN_CFG_INPUT(216, ALT_C, PULLUP) +#define GPIO216_SPI2_FRM PIN_CFG(216, ALT_C) #define GPIO217_GPIO PIN_CFG(217, GPIO) #define GPIO217_MC1_DAT0DIR PIN_CFG(217, ALT_A) #define GPIO217_MC3_DAT31DIR PIN_CFG(217, ALT_B) #define GPIO217_CLKOUT2 PIN_CFG(217, ALT_C) +#define GPIO217_SPI2_CLK PIN_CFG(217, ALT_C) #define GPIO218_GPIO PIN_CFG(218, GPIO) #define GPIO218_MC1_DAT31DIR PIN_CFG(218, ALT_A) #define GPIO218_MC3_DAT0DIR PIN_CFG(218, ALT_B) -#define GPIO218_I2C3_SCL PIN_CFG_PULL(218, ALT_C, UP) +#define GPIO218_I2C3_SCL PIN_CFG_INPUT(218, ALT_C, PULLUP) +#define GPIO218_SPI2_RXD PIN_CFG(218, ALT_C) #define GPIO219_GPIO PIN_CFG(219, GPIO) #define GPIO219_HSIR_FLA0 PIN_CFG(219, ALT_A) @@ -694,12 +698,12 @@ #define GPIO229_GPIO PIN_CFG(229, GPIO) #define GPIO229_CLKOUT1 PIN_CFG(229, ALT_A) #define GPIO229_PWL PIN_CFG(229, ALT_B) -#define GPIO229_I2C3_SDA PIN_CFG_PULL(229, ALT_C, UP) +#define GPIO229_I2C3_SDA PIN_CFG_INPUT(229, ALT_C, PULLUP) #define GPIO230_GPIO PIN_CFG(230, GPIO) #define GPIO230_CLKOUT2 PIN_CFG(230, ALT_A) #define GPIO230_PWL PIN_CFG(230, ALT_B) -#define GPIO230_I2C3_SCL PIN_CFG_PULL(230, ALT_C, UP) +#define GPIO230_I2C3_SCL PIN_CFG_INPUT(230, ALT_C, PULLUP) #define GPIO256_GPIO PIN_CFG(256, GPIO) #define GPIO256_USB_NXT PIN_CFG(256, ALT_A) diff --git a/arch/arm/plat-nomadik/include/plat/pincfg.h b/arch/arm/plat-nomadik/include/plat/pincfg.h index 05a3936ae6d..22cb97d2d8a 100644 --- a/arch/arm/plat-nomadik/include/plat/pincfg.h +++ b/arch/arm/plat-nomadik/include/plat/pincfg.h @@ -37,7 +37,6 @@ * SLPM value = same as normal * * PIN_CFG - default config with alternate function - * PIN_CFG_PULL - default config with alternate function and pull up/down */ typedef unsigned long pin_cfg_t; @@ -133,10 +132,6 @@ typedef unsigned long pin_cfg_t; (PIN_CFG_DEFAULT |\ (PIN_NUM(num) | PIN_##alt | PIN_OUTPUT_##val)) -#define PIN_CFG_PULL(num, alt, pull) \ - ((PIN_CFG_DEFAULT & ~PIN_PULL_MASK) |\ - (PIN_NUM(num) | PIN_##alt | PIN_PULL_##pull)) - extern int nmk_config_pin(pin_cfg_t cfg, bool sleep); extern int nmk_config_pins(pin_cfg_t *cfgs, int num); extern int nmk_config_pins_sleep(pin_cfg_t *cfgs, int num); -- cgit v1.2.3 From dc35901b397e14cc214a6adcbf2a82ab0249ab78 Mon Sep 17 00:00:00 2001 From: Rickard Evertsson Date: Thu, 31 Mar 2011 15:13:39 +0200 Subject: mach-ux500: split per-SoC clock trees This splits the ux500 clock tree in per-chip variants, previously the DB8500 clock tree was used also for DB5500 which was not good. This new version makes use of the new PRCMU interface submitted in the previous patch series. Signed-off-by: Rickard Evertsson Signed-off-by: Daniel Willerud Signed-off-by: Deepak Karda Signed-off-by: Bengt Jonsson Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/Makefile | 3 +- arch/arm/mach-ux500/clock-db8500.c | 1291 ++++++++++++++++++++++++++++++++++++ arch/arm/mach-ux500/clock.c | 904 +++++++------------------ arch/arm/mach-ux500/clock.h | 225 +++---- 4 files changed, 1647 insertions(+), 776 deletions(-) create mode 100644 arch/arm/mach-ux500/clock-db8500.c diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 32240013414..fa57c6926f7 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -6,7 +6,8 @@ obj-y := clock.o cpu.o devices.o devices-common.o \ id.o usb.o obj-y += pm/ obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o prcmu-db5500.o -obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o prcmu-db8500.o +obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o \ + prcmu-db8500.o clock-db8500.o obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \ board-mop500-regulators.o \ board-mop500-uib.o board-mop500-stuib.o \ diff --git a/arch/arm/mach-ux500/clock-db8500.c b/arch/arm/mach-ux500/clock-db8500.c new file mode 100644 index 00000000000..50a9debbda1 --- /dev/null +++ b/arch/arm/mach-ux500/clock-db8500.c @@ -0,0 +1,1291 @@ +/* + * Copyright (C) 2009 ST-Ericsson SA + * Copyright (C) 2009 STMicroelectronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "clock.h" +#include "pins-db8500.h" + +#define PRCM_SDMMCCLK_MGT 0x024 +#define PRCM_TCR 0x1C8 +#define PRCM_TCR_STOPPED (1 << 16) +#define PRCM_TCR_DOZE_MODE (1 << 17) +#define SD_CLK_DIV_MASK 0x1F +#define SD_CLK_DIV_VAL 8 + +static DEFINE_MUTEX(sysclk_mutex); +static DEFINE_MUTEX(ab_ulpclk_mutex); +static DEFINE_MUTEX(audioclk_mutex); + +static struct delayed_work sysclk_disable_work; + +/* PLL operations. */ + +static int clk_pllsrc_enable(struct clk *clk) +{ + /* To enable pll */ + return 0; +} + +static void clk_pllsrc_disable(struct clk *clk) +{ + /* To disable pll */ +} + +static struct clkops pll_clk_ops = { + .enable = clk_pllsrc_enable, + .disable = clk_pllsrc_disable, +}; + +/* SysClk operations. */ + +static int request_sysclk(bool enable) +{ + static int requests; + + if ((enable && (requests++ == 0)) || (!enable && (--requests == 0))) + return prcmu_request_clock(PRCMU_SYSCLK, enable); + return 0; +} + +static int sysclk_enable(struct clk *clk) +{ + static bool swat_enable; + int r; + + if (!swat_enable) { + r = ab8500_sysctrl_set(AB8500_SWATCTRL, + AB8500_SWATCTRL_SWATENABLE); + if (r) + return r; + + swat_enable = true; + } + + r = request_sysclk(true); + if (r) + return r; + + if (clk->cg_sel) { + r = ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, (u8)clk->cg_sel); + if (r) + (void)request_sysclk(false); + } + return r; +} + +static void sysclk_disable(struct clk *clk) +{ + int r; + + if (clk->cg_sel) { + r = ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + (u8)clk->cg_sel); + if (r) + goto disable_failed; + } + r = request_sysclk(false); + if (r) + goto disable_failed; + return; + +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops sysclk_ops = { + .enable = sysclk_enable, + .disable = sysclk_disable, +}; + +/* AB8500 UlpClk operations */ + +static int ab_ulpclk_enable(struct clk *clk) +{ + int err; + + if (clk->regulator == NULL) { + struct regulator *reg; + + reg = regulator_get(NULL, "v-intcore"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + clk->regulator = reg; + } + err = regulator_enable(clk->regulator); + if (err) + return err; + err = ab8500_sysctrl_clear(AB8500_SYSULPCLKCONF, + AB8500_SYSULPCLKCONF_ULPCLKCONF_MASK); + if (err) + return err; + return ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_ULPCLKREQ); +} + +static void ab_ulpclk_disable(struct clk *clk) +{ + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_ULPCLKREQ)) + goto out_err; + if (clk->regulator != NULL) { + if (regulator_disable(clk->regulator)) + goto out_err; + } + return; + +out_err: + pr_err("clock: %s failed to disable %s.\n", __func__, clk->name); +} + +static struct clkops ab_ulpclk_ops = { + .enable = ab_ulpclk_enable, + .disable = ab_ulpclk_disable, +}; + +/* AB8500 audio clock operations */ + +static int audioclk_enable(struct clk *clk) +{ + return ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_AUDIOCLKENA); +} + +static void audioclk_disable(struct clk *clk) +{ + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_AUDIOCLKENA)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); + } +} + +static int audioclk_set_parent(struct clk *clk, struct clk *parent) +{ + if (parent->ops == &sysclk_ops) { + return ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK); + } else if (parent->ops == &ab_ulpclk_ops) { + return ab8500_sysctrl_write(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK, + (1 << AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_SHIFT)); + } else { + return -EINVAL; + } +} + +static struct clkops audioclk_ops = { + .enable = audioclk_enable, + .disable = audioclk_disable, + .set_parent = audioclk_set_parent, +}; + +/* Primary camera clock operations */ +static int clkout0_enable(struct clk *clk) +{ + int r; + + r = prcmu_config_clkout(0, PRCMU_CLKSRC_SYSCLK, 4); + if (r) + return r; + return nmk_config_pin(GPIO227_CLKOUT1, false); +} + +static void clkout0_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pin(GPIO227_GPIO, false); + if (r) + goto disable_failed; + r = prcmu_config_clkout(0, PRCMU_CLKSRC_SYSCLK, 0); + if (!r) + return; +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +/* Touch screen/secondary camera clock operations. */ +static int clkout1_enable(struct clk *clk) +{ + int r; + + r = prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 4); + if (r) + return r; + return nmk_config_pin(GPIO228_CLKOUT2, false); +} + +static void clkout1_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pin(GPIO228_GPIO, false); + if (r) + goto disable_failed; + r = prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 0); + if (!r) + return; +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops clkout0_ops = { + .enable = clkout0_enable, + .disable = clkout0_disable, +}; + +static struct clkops clkout1_ops = { + .enable = clkout1_enable, + .disable = clkout1_disable, +}; + +#define DEF_PER1_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST1_BASE, _cg_bit, &per1clk) +#define DEF_PER2_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST2_BASE, _cg_bit, &per2clk) +#define DEF_PER3_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST3_BASE, _cg_bit, &per3clk) +#define DEF_PER5_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST5_BASE, _cg_bit, &per5clk) +#define DEF_PER6_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST6_BASE, _cg_bit, &per6clk) +#define DEF_PER7_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST7_BASE_ED, _cg_bit, &per7clk) + +#define DEF_PER1_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST1_BASE, _cg_bit, _parent) +#define DEF_PER2_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST2_BASE, _cg_bit, _parent) +#define DEF_PER3_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST3_BASE, _cg_bit, _parent) +#define DEF_PER5_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST5_BASE, _cg_bit, _parent) +#define DEF_PER6_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST6_BASE, _cg_bit, _parent) +#define DEF_PER7_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST7_BASE_ED, _cg_bit, _parent) + +#define DEF_MTU_CLK(_cg_sel, _name, _bus_parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &mtu_clk_ops, \ + .cg_sel = _cg_sel, \ + .bus_parent = _bus_parent, \ + } + +/* Clock sources. */ + +static struct clk soc0_pll = { + .name = "soc0_pll", + .ops = &pll_clk_ops, +}; + +static struct clk soc1_pll = { + .name = "soc1_pll", + .ops = &pll_clk_ops, +}; + +static struct clk ddr_pll = { + .name = "ddr_pll", + .ops = &pll_clk_ops, +}; + +static struct clk ulp38m4 = { + .name = "ulp38m4", +}; + +static struct clk sysclk = { + .name = "sysclk", + .ops = &sysclk_ops, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk2 = { + .name = "sysclk2", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF2REQ, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk3 = { + .name = "sysclk3", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF3REQ, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk4 = { + .name = "sysclk4", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF4REQ, + .mutex = &sysclk_mutex, +}; + +static struct clk rtc32k = { + .name = "rtc32k", + .rate = 32768, +}; + +static struct clk clkout0 = { + .name = "clkout0", + .ops = &clkout0_ops, + .parent = &sysclk, + .rate = 9600000, + .mutex = &sysclk_mutex, +}; + +static struct clk clkout1 = { + .name = "clkout1", + .ops = &clkout1_ops, + .parent = &sysclk, + .rate = 9600000, + .mutex = &sysclk_mutex, +}; + +static struct clk ab_ulpclk = { + .name = "ab_ulpclk", + .ops = &ab_ulpclk_ops, + .rate = 38400000, + .mutex = &ab_ulpclk_mutex, +}; + +static struct clk *audioclk_parents[] = { &sysclk, &ab_ulpclk, NULL }; + +static struct clk audioclk = { + .name = "audioclk", + .ops = &audioclk_ops, + .mutex = &audioclk_mutex, + .parent = &sysclk, + .parents = audioclk_parents, +}; + +static DEF_PRCMU_CLK(sgaclk, PRCMU_SGACLK, 320000000); +static DEF_PRCMU_CLK(uartclk, PRCMU_UARTCLK, 38400000); +static DEF_PRCMU_CLK(msp02clk, PRCMU_MSP02CLK, 19200000); +static DEF_PRCMU_CLK(msp1clk, PRCMU_MSP1CLK, 19200000); +static DEF_PRCMU_CLK(i2cclk, PRCMU_I2CCLK, 24000000); +static DEF_PRCMU_CLK(slimclk, PRCMU_SLIMCLK, 19200000); +static DEF_PRCMU_CLK(per1clk, PRCMU_PER1CLK, 133330000); +static DEF_PRCMU_CLK(per2clk, PRCMU_PER2CLK, 133330000); +static DEF_PRCMU_CLK(per3clk, PRCMU_PER3CLK, 133330000); +static DEF_PRCMU_CLK(per5clk, PRCMU_PER5CLK, 133330000); +static DEF_PRCMU_CLK(per6clk, PRCMU_PER6CLK, 133330000); +static DEF_PRCMU_CLK(per7clk, PRCMU_PER7CLK, 100000000); +static DEF_PRCMU_CLK(lcdclk, PRCMU_LCDCLK, 48000000); +static DEF_PRCMU_OPP100_CLK(bmlclk, PRCMU_BMLCLK, 200000000); +static DEF_PRCMU_CLK(hsitxclk, PRCMU_HSITXCLK, 100000000); +static DEF_PRCMU_CLK(hsirxclk, PRCMU_HSIRXCLK, 200000000); +static DEF_PRCMU_CLK(hdmiclk, PRCMU_HDMICLK, 76800000); +static DEF_PRCMU_CLK(apeatclk, PRCMU_APEATCLK, 160000000); +static DEF_PRCMU_CLK(apetraceclk, PRCMU_APETRACECLK, 160000000); +static DEF_PRCMU_CLK(mcdeclk, PRCMU_MCDECLK, 160000000); +static DEF_PRCMU_OPP100_CLK(ipi2cclk, PRCMU_IPI2CCLK, 24000000); +static DEF_PRCMU_CLK(dsialtclk, PRCMU_DSIALTCLK, 384000000); +static DEF_PRCMU_CLK(dmaclk, PRCMU_DMACLK, 200000000); +static DEF_PRCMU_CLK(b2r2clk, PRCMU_B2R2CLK, 200000000); +static DEF_PRCMU_CLK(tvclk, PRCMU_TVCLK, 76800000); +/* TODO: For SSPCLK, the spec says 24MHz, while the old driver says 48MHz. */ +static DEF_PRCMU_CLK(sspclk, PRCMU_SSPCLK, 24000000); +static DEF_PRCMU_CLK(rngclk, PRCMU_RNGCLK, 19200000); +static DEF_PRCMU_CLK(uiccclk, PRCMU_UICCCLK, 48000000); +static DEF_PRCMU_CLK(timclk, PRCMU_TIMCLK, 2400000); +/* 100 MHz until 3.0.1, 50 MHz Since PRCMU FW 3.0.2 */ +static DEF_PRCMU_CLK(sdmmcclk, PRCMU_SDMMCCLK, 50000000); + +/* PRCC PClocks */ + +static DEF_PER1_PCLK(0, p1_pclk0); +static DEF_PER1_PCLK(1, p1_pclk1); +static DEF_PER1_PCLK(2, p1_pclk2); +static DEF_PER1_PCLK(3, p1_pclk3); +static DEF_PER1_PCLK(4, p1_pclk4); +static DEF_PER1_PCLK(5, p1_pclk5); +static DEF_PER1_PCLK(6, p1_pclk6); +static DEF_PER1_PCLK(7, p1_pclk7); +static DEF_PER1_PCLK(8, p1_pclk8); +static DEF_PER1_PCLK(9, p1_pclk9); +static DEF_PER1_PCLK(10, p1_pclk10); +static DEF_PER1_PCLK(11, p1_pclk11); + +static DEF_PER2_PCLK(0, p2_pclk0); +static DEF_PER2_PCLK(1, p2_pclk1); +static DEF_PER2_PCLK(2, p2_pclk2); +static DEF_PER2_PCLK(3, p2_pclk3); +static DEF_PER2_PCLK(4, p2_pclk4); +static DEF_PER2_PCLK(5, p2_pclk5); +static DEF_PER2_PCLK(6, p2_pclk6); +static DEF_PER2_PCLK(7, p2_pclk7); +static DEF_PER2_PCLK(8, p2_pclk8); +static DEF_PER2_PCLK(9, p2_pclk9); +static DEF_PER2_PCLK(10, p2_pclk10); +static DEF_PER2_PCLK(11, p2_pclk11); +static DEF_PER2_PCLK(12, p2_pclk12); + +static DEF_PER3_PCLK(0, p3_pclk0); +static DEF_PER3_PCLK(1, p3_pclk1); +static DEF_PER3_PCLK(2, p3_pclk2); +static DEF_PER3_PCLK(3, p3_pclk3); +static DEF_PER3_PCLK(4, p3_pclk4); +static DEF_PER3_PCLK(5, p3_pclk5); +static DEF_PER3_PCLK(6, p3_pclk6); +static DEF_PER3_PCLK(7, p3_pclk7); +static DEF_PER3_PCLK(8, p3_pclk8); + +static DEF_PER5_PCLK(0, p5_pclk0); +static DEF_PER5_PCLK(1, p5_pclk1); + +static DEF_PER6_PCLK(0, p6_pclk0); +static DEF_PER6_PCLK(1, p6_pclk1); +static DEF_PER6_PCLK(2, p6_pclk2); +static DEF_PER6_PCLK(3, p6_pclk3); +static DEF_PER6_PCLK(4, p6_pclk4); +static DEF_PER6_PCLK(5, p6_pclk5); +static DEF_PER6_PCLK(6, p6_pclk6); +static DEF_PER6_PCLK(7, p6_pclk7); + +static DEF_PER7_PCLK(0, p7_pclk0); +static DEF_PER7_PCLK(1, p7_pclk1); +static DEF_PER7_PCLK(2, p7_pclk2); +static DEF_PER7_PCLK(3, p7_pclk3); +static DEF_PER7_PCLK(4, p7_pclk4); + +/* UART0 */ +static DEF_PER1_KCLK(0, p1_uart0_kclk, &uartclk); +static DEF_PER_CLK(p1_uart0_clk, &p1_pclk0, &p1_uart0_kclk); + +/* UART1 */ +static DEF_PER1_KCLK(1, p1_uart1_kclk, &uartclk); +static DEF_PER_CLK(p1_uart1_clk, &p1_pclk1, &p1_uart1_kclk); + +/* I2C1 */ +static DEF_PER1_KCLK(2, p1_i2c1_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c1_clk, &p1_pclk2, &p1_i2c1_kclk); + +/* MSP0 */ +static DEF_PER1_KCLK(3, p1_msp0_kclk, &msp02clk); +static DEF_PER_CLK(p1_msp0_clk, &p1_pclk3, &p1_msp0_kclk); + +/* MSP1 */ +static DEF_PER1_KCLK(4, p1_msp1_kclk, &msp1clk); +static DEF_PER_CLK(p1_msp1_clk, &p1_pclk4, &p1_msp1_kclk); + +static DEF_PER1_KCLK(4, p1_msp1_ed_kclk, &msp02clk); +static DEF_PER_CLK(p1_msp1_ed_clk, &p1_pclk4, &p1_msp1_ed_kclk); + +/* SDI0 */ +static DEF_PER1_KCLK(5, p1_sdi0_kclk, &sdmmcclk); +static DEF_PER_CLK(p1_sdi0_clk, &p1_pclk5, &p1_sdi0_kclk); + +/* I2C2 */ +static DEF_PER1_KCLK(6, p1_i2c2_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c2_clk, &p1_pclk6, &p1_i2c2_kclk); + +/* SLIMBUS0 */ +static DEF_PER1_KCLK(3, p1_slimbus0_kclk, &slimclk); +static DEF_PER_CLK(p1_slimbus0_clk, &p1_pclk8, &p1_slimbus0_kclk); + +/* I2C4 */ +static DEF_PER1_KCLK(9, p1_i2c4_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c4_clk, &p1_pclk10, &p1_i2c4_kclk); + +/* MSP3 */ +static DEF_PER1_KCLK(10, p1_msp3_kclk, &msp1clk); +static DEF_PER_CLK(p1_msp3_clk, &p1_pclk11, &p1_msp3_kclk); + +/* I2C3 */ +static DEF_PER2_KCLK(0, p2_i2c3_kclk, &i2cclk); +static DEF_PER_CLK(p2_i2c3_clk, &p2_pclk0, &p2_i2c3_kclk); + +/* SDI4 */ +static DEF_PER2_KCLK(2, p2_sdi4_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi4_clk, &p2_pclk4, &p2_sdi4_kclk); + +/* MSP2 */ +static DEF_PER2_KCLK(3, p2_msp2_kclk, &msp02clk); +static DEF_PER_CLK(p2_msp2_clk, &p2_pclk5, &p2_msp2_kclk); + +static DEF_PER2_KCLK(4, p2_msp2_ed_kclk, &msp02clk); +static DEF_PER_CLK(p2_msp2_ed_clk, &p2_pclk6, &p2_msp2_ed_kclk); + +/* SDI1 */ +static DEF_PER2_KCLK(4, p2_sdi1_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi1_clk, &p2_pclk6, &p2_sdi1_kclk); + +/* These are probably broken now. */ +static DEF_PER2_KCLK(5, p2_sdi1_ed_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi1_ed_clk, &p2_pclk7, &p2_sdi1_ed_kclk); + +/* SDI3 */ +static DEF_PER2_KCLK(5, p2_sdi3_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi3_clk, &p2_pclk7, &p2_sdi3_kclk); + +/* These are probably broken now. */ +static DEF_PER2_KCLK(6, p2_sdi3_ed_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi3_ed_clk, &p2_pclk8, &p2_sdi3_ed_kclk); + +/* SSP0 */ +static DEF_PER3_KCLK(1, p3_ssp0_kclk, &sspclk); +static DEF_PER_CLK(p3_ssp0_clk, &p3_pclk1, &p3_ssp0_kclk); + +static DEF_PER3_KCLK(1, p3_ssp0_ed_kclk, &i2cclk); +static DEF_PER_CLK(p3_ssp0_ed_clk, &p3_pclk1, &p3_ssp0_ed_kclk); + +/* SSP1 */ +static DEF_PER3_KCLK(2, p3_ssp1_kclk, &sspclk); +static DEF_PER_CLK(p3_ssp1_clk, &p3_pclk2, &p3_ssp1_kclk); + +static DEF_PER3_KCLK(2, p3_ssp1_ed_kclk, &i2cclk); +static DEF_PER_CLK(p3_ssp1_ed_clk, &p3_pclk2, &p3_ssp1_ed_kclk); + +/* I2C0 */ +static DEF_PER3_KCLK(3, p3_i2c0_kclk, &i2cclk); +static DEF_PER_CLK(p3_i2c0_clk, &p3_pclk3, &p3_i2c0_kclk); + +/* SDI2 */ +static DEF_PER3_KCLK(4, p3_sdi2_kclk, &sdmmcclk); +static DEF_PER_CLK(p3_sdi2_clk, &p3_pclk4, &p3_sdi2_kclk); + +/* SKE */ +static DEF_PER3_KCLK(5, p3_ske_kclk, &rtc32k); +static DEF_PER_CLK(p3_ske_clk, &p3_pclk5, &p3_ske_kclk); + +/* UART2 */ +static DEF_PER3_KCLK(6, p3_uart2_kclk, &uartclk); +static DEF_PER_CLK(p3_uart2_clk, &p3_pclk6, &p3_uart2_kclk); + +/* SDI5 */ +static DEF_PER3_KCLK(7, p3_sdi5_kclk, &sdmmcclk); +static DEF_PER_CLK(p3_sdi5_clk, &p3_pclk7, &p3_sdi5_kclk); + +/* USB */ +static DEF_PER5_KCLK(0, p5_usb_ed_kclk, &i2cclk); +static DEF_PER_CLK(p5_usb_ed_clk, &p5_pclk0, &p5_usb_ed_kclk); + +/* RNG */ +static DEF_PER6_KCLK(0, p6_rng_kclk, &rngclk); +static DEF_PER_CLK(p6_rng_clk, &p6_pclk0, &p6_rng_kclk); + +static DEF_PER6_KCLK(0, p6_rng_ed_kclk, &i2cclk); +static DEF_PER_CLK(p6_rng_ed_clk, &p6_pclk0, &p6_rng_ed_kclk); + + +/* MTU:S */ + +/* MTU0 */ +static DEF_PER_CLK(p6_mtu0_clk, &p6_pclk6, &timclk); +static DEF_PER_CLK(p7_mtu0_ed_clk, &p7_pclk2, &timclk); + +/* MTU1 */ +static DEF_PER_CLK(p6_mtu1_clk, &p6_pclk7, &timclk); +static DEF_PER_CLK(p7_mtu1_ed_clk, &p7_pclk3, &timclk); + +#ifdef CONFIG_DEBUG_FS + +struct clk_debug_info { + struct clk *clk; + struct dentry *dir; + struct dentry *enable; + struct dentry *requests; + int enabled; +}; + +static struct dentry *clk_dir; +static struct dentry *clk_show; +static struct dentry *clk_show_enabled_only; + +static struct clk_debug_info dbg_clks[] = { + /* Clock sources */ + { .clk = &soc0_pll, }, + { .clk = &soc1_pll, }, + { .clk = &ddr_pll, }, + { .clk = &ulp38m4, }, + { .clk = &sysclk, }, + { .clk = &rtc32k, }, + /* PRCMU clocks */ + { .clk = &sgaclk, }, + { .clk = &uartclk, }, + { .clk = &msp02clk, }, + { .clk = &msp1clk, }, + { .clk = &i2cclk, }, + { .clk = &sdmmcclk, }, + { .clk = &slimclk, }, + { .clk = &per1clk, }, + { .clk = &per2clk, }, + { .clk = &per3clk, }, + { .clk = &per5clk, }, + { .clk = &per6clk, }, + { .clk = &per7clk, }, + { .clk = &lcdclk, }, + { .clk = &bmlclk, }, + { .clk = &hsitxclk, }, + { .clk = &hsirxclk, }, + { .clk = &hdmiclk, }, + { .clk = &apeatclk, }, + { .clk = &apetraceclk, }, + { .clk = &mcdeclk, }, + { .clk = &ipi2cclk, }, + { .clk = &dsialtclk, }, + { .clk = &dmaclk, }, + { .clk = &b2r2clk, }, + { .clk = &tvclk, }, + { .clk = &sspclk, }, + { .clk = &rngclk, }, + { .clk = &uiccclk, }, +}; + +static struct clk_debug_info dbg_clks_v2[] = { + /* Clock sources */ + { .clk = &sysclk2, }, + { .clk = &clkout0, }, + { .clk = &clkout1, }, +}; + +static int clk_show_print(struct seq_file *s, void *p) +{ + int i; + int enabled_only = (int)s->private; + + seq_printf(s, "\n%-20s %s\n", "name", "enabled (kernel + debug)"); + for (i = 0; i < ARRAY_SIZE(dbg_clks); i++) { + if (enabled_only && !dbg_clks[i].clk->enabled) + continue; + seq_printf(s, + "%-20s %5d + %d\n", + dbg_clks[i].clk->name, + dbg_clks[i].clk->enabled - dbg_clks[i].enabled, + dbg_clks[i].enabled); + } + if (cpu_is_u8500v2()) { + for (i = 0; i < ARRAY_SIZE(dbg_clks_v2); i++) { + if (enabled_only && !dbg_clks_v2[i].clk->enabled) + continue; + seq_printf(s, + "%-20s %5d + %d\n", + dbg_clks_v2[i].clk->name, + (dbg_clks_v2[i].clk->enabled - + dbg_clks_v2[i].enabled), + dbg_clks_v2[i].enabled); + } + } + + return 0; +} + +static int clk_show_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_show_print, inode->i_private); +} + +static int clk_enable_print(struct seq_file *s, void *p) +{ + struct clk_debug_info *cdi = s->private; + + return seq_printf(s, "%d\n", cdi->enabled); +} + +static int clk_enable_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_enable_print, inode->i_private); +} + +static ssize_t clk_enable_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct clk_debug_info *cdi; + char buf[32]; + ssize_t buf_size; + long user_val; + int err; + + cdi = ((struct seq_file *)(file->private_data))->private; + + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = '\0'; + + err = strict_strtol(buf, 0, &user_val); + if (err) + return -EINVAL; + if ((user_val > 0) && (!cdi->enabled)) { + err = clk_enable(cdi->clk); + if (err) { + pr_err("clock: clk_enable(%s) failed.\n", + cdi->clk->name); + return -EFAULT; + } + cdi->enabled = 1; + } else if ((user_val <= 0) && (cdi->enabled)) { + clk_disable(cdi->clk); + cdi->enabled = 0; + } + return buf_size; +} + +static int clk_requests_print(struct seq_file *s, void *p) +{ + struct clk_debug_info *cdi = s->private; + + return seq_printf(s, "%d\n", cdi->clk->enabled); +} + +static int clk_requests_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_requests_print, inode->i_private); +} + +static const struct file_operations clk_enable_fops = { + .open = clk_enable_open, + .write = clk_enable_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations clk_requests_fops = { + .open = clk_requests_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations clk_show_fops = { + .open = clk_show_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int create_clk_dirs(struct clk_debug_info *cdi, int size) +{ + int i; + + for (i = 0; i < size; i++) { + cdi[i].dir = debugfs_create_dir(cdi[i].clk->name, clk_dir); + if (!cdi[i].dir) + goto no_dir; + } + + for (i = 0; i < size; i++) { + cdi[i].enable = debugfs_create_file("enable", + (S_IRUGO | S_IWUGO), + cdi[i].dir, &cdi[i], + &clk_enable_fops); + if (!cdi[i].enable) + goto no_enable; + } + for (i = 0; i < size; i++) { + cdi[i].requests = debugfs_create_file("requests", S_IRUGO, + cdi[i].dir, &cdi[i], + &clk_requests_fops); + if (!cdi[i].requests) + goto no_requests; + } + return 0; + +no_requests: + while (i--) + debugfs_remove(cdi[i].requests); + i = size; +no_enable: + while (i--) + debugfs_remove(cdi[i].enable); + i = size; +no_dir: + while (i--) + debugfs_remove(cdi[i].dir); + + return -ENOMEM; +} + +static void remove_clk_dirs(struct clk_debug_info *cdi, int size) +{ + int i; + for (i = 0; i < size; i++) { + debugfs_remove(cdi[i].requests); + debugfs_remove(cdi[i].enable); + debugfs_remove(cdi[i].dir); + } +} + +static int __init clk_debug_init(void) +{ + clk_dir = debugfs_create_dir("clk", NULL); + if (!clk_dir) + goto no_dir; + + clk_show = debugfs_create_file("show", S_IRUGO, clk_dir, (void *)0, + &clk_show_fops); + if (!clk_show) + goto no_show; + + clk_show_enabled_only = debugfs_create_file("show-enabled-only", + S_IRUGO, clk_dir, (void *)1, + &clk_show_fops); + if (!clk_show_enabled_only) + goto no_enabled_only; + + if (create_clk_dirs(&dbg_clks[0], ARRAY_SIZE(dbg_clks))) + goto no_clks; + + if (cpu_is_u8500v2()) { + if (create_clk_dirs(&dbg_clks_v2[0], ARRAY_SIZE(dbg_clks_v2))) + goto common_clks; + } + return 0; + +common_clks: + remove_clk_dirs(&dbg_clks[0], ARRAY_SIZE(dbg_clks)); +no_clks: + debugfs_remove(clk_show_enabled_only); +no_enabled_only: + debugfs_remove(clk_show); +no_show: + debugfs_remove(clk_dir); +no_dir: + return -ENOMEM; +} + +static void __exit clk_debug_exit(void) +{ + remove_clk_dirs(&dbg_clks[0], ARRAY_SIZE(dbg_clks)); + if (cpu_is_u8500v2()) + remove_clk_dirs(&dbg_clks_v2[0], ARRAY_SIZE(dbg_clks_v2)); + + debugfs_remove(clk_show); + debugfs_remove(clk_show_enabled_only); + debugfs_remove(clk_dir); +} + +subsys_initcall(clk_debug_init); +module_exit(clk_debug_exit); +#endif /* CONFIG_DEBUG_FS */ + +/* + * TODO: Ensure names match with devices and then remove unnecessary entries + * when all drivers use the clk API. + */ + +#define CLK_LOOKUP(_clk, _dev_id, _con_id) \ + { .dev_id = _dev_id, .con_id = _con_id, .clk = &_clk } + +static struct clk_lookup u8500_common_clock_sources[] = { + CLK_LOOKUP(soc0_pll, NULL, "soc0_pll"), + CLK_LOOKUP(soc1_pll, NULL, "soc1_pll"), + CLK_LOOKUP(ddr_pll, NULL, "ddr_pll"), + CLK_LOOKUP(ulp38m4, NULL, "ulp38m4"), + CLK_LOOKUP(sysclk, NULL, "sysclk"), + CLK_LOOKUP(rtc32k, NULL, "clk32k"), + CLK_LOOKUP(sysclk, "ab8500-usb.0", "sysclk"), + CLK_LOOKUP(sysclk, "ab8500-codec.0", "sysclk"), + CLK_LOOKUP(ab_ulpclk, "ab8500-codec.0", "ulpclk"), + CLK_LOOKUP(audioclk, "ab8500-codec.0", "audioclk"), +}; + +static struct clk_lookup u8500_v2_sysclks[] = { + CLK_LOOKUP(sysclk2, NULL, "sysclk2"), + CLK_LOOKUP(sysclk3, NULL, "sysclk3"), + CLK_LOOKUP(sysclk4, NULL, "sysclk4"), +}; + +static struct clk_lookup u8500_common_prcmu_clocks[] = { + CLK_LOOKUP(sgaclk, "mali", NULL), + CLK_LOOKUP(uartclk, "UART", NULL), + CLK_LOOKUP(msp02clk, "MSP02", NULL), + CLK_LOOKUP(i2cclk, "I2C", NULL), + CLK_LOOKUP(sdmmcclk, "sdmmc", NULL), + CLK_LOOKUP(slimclk, "slim", NULL), + CLK_LOOKUP(per1clk, "PERIPH1", NULL), + CLK_LOOKUP(per2clk, "PERIPH2", NULL), + CLK_LOOKUP(per3clk, "PERIPH3", NULL), + CLK_LOOKUP(per5clk, "PERIPH5", NULL), + CLK_LOOKUP(per6clk, "PERIPH6", NULL), + CLK_LOOKUP(per7clk, "PERIPH7", NULL), + CLK_LOOKUP(lcdclk, "lcd", NULL), + CLK_LOOKUP(bmlclk, "bml", NULL), + CLK_LOOKUP(hsitxclk, "stm-hsi.0", NULL), + CLK_LOOKUP(hsirxclk, "stm-hsi.1", NULL), + CLK_LOOKUP(lcdclk, "mcde", "lcd"), + CLK_LOOKUP(hdmiclk, "hdmi", NULL), + CLK_LOOKUP(hdmiclk, "mcde", "hdmi"), + CLK_LOOKUP(apeatclk, "apeat", NULL), + CLK_LOOKUP(apetraceclk, "apetrace", NULL), + CLK_LOOKUP(mcdeclk, "mcde", NULL), + CLK_LOOKUP(mcdeclk, "mcde", "mcde"), + CLK_LOOKUP(ipi2cclk, "ipi2", NULL), + CLK_LOOKUP(dmaclk, "dma40.0", NULL), + CLK_LOOKUP(b2r2clk, "b2r2", NULL), + CLK_LOOKUP(b2r2clk, "b2r2_bus", NULL), + CLK_LOOKUP(b2r2clk, "U8500-B2R2.0", NULL), + CLK_LOOKUP(tvclk, "tv", NULL), + CLK_LOOKUP(tvclk, "mcde", "tv"), +}; + +static struct clk_lookup u8500_common_prcc_clocks[] = { + /* PERIPH 1 */ + CLK_LOOKUP(p1_uart0_clk, "uart0", NULL), + CLK_LOOKUP(p1_uart1_clk, "uart1", NULL), + CLK_LOOKUP(p1_i2c1_clk, "nmk-i2c.1", NULL), + CLK_LOOKUP(p1_msp0_clk, "msp0", NULL), + CLK_LOOKUP(p1_msp0_clk, "MSP_I2S.0", NULL), + CLK_LOOKUP(p1_sdi0_clk, "sdi0", NULL), + CLK_LOOKUP(p1_i2c2_clk, "nmk-i2c.2", NULL), + CLK_LOOKUP(p1_slimbus0_clk, "slimbus0", NULL), + CLK_LOOKUP(p1_pclk9, "gpio.0", NULL), + CLK_LOOKUP(p1_pclk9, "gpio.1", NULL), + CLK_LOOKUP(p1_pclk9, "gpioblock0", NULL), + + /* PERIPH 2 */ + CLK_LOOKUP(p2_i2c3_clk, "nmk-i2c.3", NULL), + CLK_LOOKUP(p2_pclk1, "spi2", NULL), + CLK_LOOKUP(p2_pclk2, "spi1", NULL), + CLK_LOOKUP(p2_pclk3, "pwl", NULL), + CLK_LOOKUP(p2_sdi4_clk, "sdi4", NULL), + + /* PERIPH 3 */ + CLK_LOOKUP(p3_pclk0, "fsmc", NULL), + CLK_LOOKUP(p3_i2c0_clk, "nmk-i2c.0", NULL), + CLK_LOOKUP(p3_sdi2_clk, "sdi2", NULL), + CLK_LOOKUP(p3_ske_clk, "ske", NULL), + CLK_LOOKUP(p3_ske_clk, "nmk-ske-keypad", NULL), + CLK_LOOKUP(p3_uart2_clk, "uart2", NULL), + CLK_LOOKUP(p3_sdi5_clk, "sdi5", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.2", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.3", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.4", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.5", NULL), + CLK_LOOKUP(p3_pclk8, "gpioblock2", NULL), + + /* PERIPH 5 */ + CLK_LOOKUP(p5_pclk1, "gpio.8", NULL), + CLK_LOOKUP(p5_pclk1, "gpioblock3", NULL), + + /* PERIPH 6 */ + CLK_LOOKUP(p6_pclk1, "cryp0", NULL), + CLK_LOOKUP(p6_pclk2, "hash0", NULL), + CLK_LOOKUP(p6_pclk3, "pka", NULL), +}; + +static struct clk_lookup u8500_ed_prcc_clocks[] = { + /* PERIPH 1 */ + CLK_LOOKUP(p1_msp1_ed_clk, "msp1", NULL), + CLK_LOOKUP(p1_msp1_ed_clk, "MSP_I2S.1", NULL), + CLK_LOOKUP(p1_pclk7, "spi3", NULL), + + /* PERIPH 2 */ + CLK_LOOKUP(p2_msp2_ed_clk, "msp2", NULL), + CLK_LOOKUP(p2_msp2_ed_clk, "MSP_I2S.2", NULL), + CLK_LOOKUP(p2_sdi1_ed_clk, "sdi1", NULL), + CLK_LOOKUP(p2_sdi3_ed_clk, "sdi3", NULL), + CLK_LOOKUP(p2_pclk9, "spi0", NULL), + CLK_LOOKUP(p2_pclk10, "ssirx", NULL), + CLK_LOOKUP(p2_pclk11, "ssitx", NULL), + CLK_LOOKUP(p2_pclk12, "gpio.6", NULL), + CLK_LOOKUP(p2_pclk12, "gpio.7", NULL), + CLK_LOOKUP(p2_pclk12, "gpioblock1", NULL), + + /* PERIPH 3 */ + CLK_LOOKUP(p3_ssp0_ed_clk, "ssp0", NULL), + CLK_LOOKUP(p3_ssp1_ed_clk, "ssp1", NULL), + + /* PERIPH 5 */ + CLK_LOOKUP(p5_usb_ed_clk, "musb_hdrc.0", "usb"), + + /* PERIPH 6 */ + CLK_LOOKUP(p6_rng_ed_clk, "rng", NULL), + CLK_LOOKUP(p6_pclk4, "cryp1", NULL), + CLK_LOOKUP(p6_pclk5, "hash1", NULL), + CLK_LOOKUP(p6_pclk6, "dmc", NULL), + + /* PERIPH 7 */ + CLK_LOOKUP(p7_pclk0, "cfgreg", NULL), + CLK_LOOKUP(p7_pclk1, "wdg", NULL), + CLK_LOOKUP(p7_mtu0_ed_clk, "mtu0", NULL), + CLK_LOOKUP(p7_mtu1_ed_clk, "mtu1", NULL), + CLK_LOOKUP(p7_pclk4, "tzpc0", NULL), +}; + +static struct clk_lookup u8500_v1_v2_prcmu_clocks[] = { + CLK_LOOKUP(msp1clk, "MSP1", NULL), + CLK_LOOKUP(dsialtclk, "dsialt", NULL), + CLK_LOOKUP(sspclk, "SSP", NULL), + CLK_LOOKUP(rngclk, "rngclk", NULL), + CLK_LOOKUP(uiccclk, "uicc", NULL), +}; + +static struct clk_lookup u8500_v1_v2_prcc_clocks[] = { + /* PERIPH 1 */ + CLK_LOOKUP(p1_msp1_clk, "msp1", NULL), + CLK_LOOKUP(p1_msp1_clk, "MSP_I2S.1", NULL), + CLK_LOOKUP(p1_pclk7, "spi3", NULL), + CLK_LOOKUP(p1_i2c4_clk, "nmk-i2c.4", NULL), + + /* PERIPH 2 */ + CLK_LOOKUP(p2_msp2_clk, "msp2", NULL), + CLK_LOOKUP(p2_msp2_clk, "MSP_I2S.2", NULL), + CLK_LOOKUP(p2_sdi1_clk, "sdi1", NULL), + CLK_LOOKUP(p2_sdi3_clk, "sdi3", NULL), + CLK_LOOKUP(p2_pclk8, "spi0", NULL), + CLK_LOOKUP(p2_pclk9, "ssirx", NULL), + CLK_LOOKUP(p2_pclk10, "ssitx", NULL), + CLK_LOOKUP(p2_pclk11, "gpio.6", NULL), + CLK_LOOKUP(p2_pclk11, "gpio.7", NULL), + CLK_LOOKUP(p2_pclk11, "gpioblock1", NULL), + + /* PERIPH 3 */ + CLK_LOOKUP(p3_ssp0_clk, "ssp0", NULL), + CLK_LOOKUP(p3_ssp1_clk, "ssp1", NULL), + + /* PERIPH 5 */ + CLK_LOOKUP(p5_pclk0, "musb_hdrc.0", "usb"), + + /* PERIPH 6 */ + CLK_LOOKUP(p6_pclk5, "hash1", NULL), + CLK_LOOKUP(p6_pclk4, "cryp1", NULL), + CLK_LOOKUP(p6_rng_clk, "rng", NULL), +}; + +static struct clk_lookup u8500_v2_prcmu_clocks[] = { + CLK_LOOKUP(clkout0, "pri-cam", NULL), + CLK_LOOKUP(clkout1, "3-005c", NULL), + CLK_LOOKUP(clkout1, "3-005d", NULL), + CLK_LOOKUP(clkout1, "sec-cam", NULL), +}; + +static struct clk_lookup u8500_v2_prcc_clocks[] = { + /* PERIPH 1 */ + CLK_LOOKUP(p1_msp3_clk, "msp3", NULL), + CLK_LOOKUP(p1_msp3_clk, "MSP_I2S.3", NULL), + + /* PERIPH 6 */ + CLK_LOOKUP(p6_pclk4, "hash1", NULL), + CLK_LOOKUP(p6_pclk4, "cryp1", NULL), + CLK_LOOKUP(p6_pclk5, "cfgreg", NULL), + CLK_LOOKUP(p6_mtu0_clk, "mtu0", NULL), + CLK_LOOKUP(p6_mtu1_clk, "mtu1", NULL), +}; + +/* these are the clocks which are default from the bootloader */ +static const char *u8500_boot_clk[] = { + "uart0", + "uart1", + "uart2", + "gpioblock0", + "gpioblock1", + "gpioblock2", + "gpioblock3", + "mtu0", + "mtu1", + "ssp0", + "ssp1", + "spi0", + "spi1", + "spi2", + "spi3", + "msp0", + "msp1", + "msp2", + "nmk-i2c.0", + "nmk-i2c.1", + "nmk-i2c.2", + "nmk-i2c.3", + "nmk-i2c.4", +}; + +static void sysclk_init_disable(struct work_struct *not_used) +{ + int i; + + mutex_lock(&sysclk_mutex); + + /* Enable SWAT */ + if (ab8500_sysctrl_set(AB8500_SWATCTRL, AB8500_SWATCTRL_SWATENABLE)) + goto err_swat; + + for (i = 0; i < ARRAY_SIZE(u8500_v2_sysclks); i++) { + struct clk *clk = u8500_v2_sysclks[i].clk; + + /* Disable sysclks */ + if (!clk->enabled && clk->cg_sel) { + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + (u8)clk->cg_sel)) + goto err_sysclk; + } + } + goto unlock_and_exit; + +err_sysclk: + pr_err("clock: Disable %s failed", u8500_v2_sysclks[i].clk->name); + ab8500_sysctrl_clear(AB8500_SWATCTRL, AB8500_SWATCTRL_SWATENABLE); + goto unlock_and_exit; + +err_swat: + pr_err("clock: Enable SWAT failed"); + +unlock_and_exit: + mutex_unlock(&sysclk_mutex); +} + +static struct clk *boot_clks[ARRAY_SIZE(u8500_boot_clk)]; + +/* we disable a majority of peripherals enabled by default + * but without drivers + */ +static int __init u8500_boot_clk_disable(void) +{ + unsigned int i = 0; + + for (i = 0; i < ARRAY_SIZE(u8500_boot_clk); i++) { + if (!boot_clks[i]) + continue; + + clk_disable(boot_clks[i]); + clk_put(boot_clks[i]); + } + + INIT_DELAYED_WORK(&sysclk_disable_work, sysclk_init_disable); + schedule_delayed_work(&sysclk_disable_work, 10 * HZ); + + return 0; +} +late_initcall_sync(u8500_boot_clk_disable); + +static void u8500_amba_clk_enable(void) +{ + unsigned int i = 0; + + writel(~0x0 & ~(1 << 9), __io_address(U8500_PER1_BASE + 0xF000 + + 0x04)); + writel(~0x0, __io_address(U8500_PER1_BASE + 0xF000 + 0x0C)); + + writel(~0x0 & ~(1 << 11), __io_address(U8500_PER2_BASE + 0xF000 + + 0x04)); + writel(~0x0, __io_address(U8500_PER2_BASE + 0xF000 + 0x0C)); + + /* GPIO,UART2 are enabled for booting */ + writel(0xBF, __io_address(U8500_PER3_BASE + 0xF000 + 0x04)); + writel(~0x0 & ~(1 << 6), __io_address(U8500_PER3_BASE + 0xF000 + + 0x0C)); + + for (i = 0; i < ARRAY_SIZE(u8500_boot_clk); i++) { + boot_clks[i] = clk_get_sys(u8500_boot_clk[i], NULL); + clk_enable(boot_clks[i]); + } +} + +int __init db8500_clk_init(void) +{ + if (cpu_is_u8500ed()) { + pr_err("clock: U8500 ED is no longer supported.\n"); + return -ENOSYS; + } else if (cpu_is_u8500v1()) { + pr_err("clock: U8500 V1 is no longer supported.\n"); + return -ENOSYS; + } else if (cpu_is_u5500()) { + per6clk.rate = 26000000; + uartclk.rate = 36360000; + } + + if (cpu_is_u5500() || ux500_is_svp()) { + sysclk_ops.enable = NULL; + sysclk_ops.disable = NULL; + prcmu_clk_ops.enable = NULL; + prcmu_clk_ops.disable = NULL; + prcmu_opp100_clk_ops.enable = NULL; + prcmu_opp100_clk_ops.disable = NULL; + prcc_pclk_ops.enable = NULL; + prcc_pclk_ops.disable = NULL; + prcc_kclk_ops.enable = NULL; + prcc_kclk_ops.disable = NULL; + clkout0_ops.enable = NULL; + clkout0_ops.disable = NULL; + clkout1_ops.enable = NULL; + clkout1_ops.disable = NULL; + } + + clks_register(u8500_common_clock_sources, + ARRAY_SIZE(u8500_common_clock_sources)); + clks_register(u8500_common_prcmu_clocks, + ARRAY_SIZE(u8500_common_prcmu_clocks)); + clks_register(u8500_common_prcc_clocks, + ARRAY_SIZE(u8500_common_prcc_clocks)); + + if (cpu_is_u5500()) { + clks_register(u8500_ed_prcc_clocks, + ARRAY_SIZE(u8500_ed_prcc_clocks)); + } else if (cpu_is_u8500v2()) { + clks_register(u8500_v2_sysclks, + ARRAY_SIZE(u8500_v2_sysclks)); + clks_register(u8500_v1_v2_prcmu_clocks, + ARRAY_SIZE(u8500_v1_v2_prcmu_clocks)); + clks_register(u8500_v2_prcmu_clocks, + ARRAY_SIZE(u8500_v2_prcmu_clocks)); + clks_register(u8500_v1_v2_prcc_clocks, + ARRAY_SIZE(u8500_v1_v2_prcc_clocks)); + clks_register(u8500_v2_prcc_clocks, + ARRAY_SIZE(u8500_v2_prcc_clocks)); + } + + if (cpu_is_u8500()) + u8500_amba_clk_enable(); + + /* + * The following clks are shared with secure world. + * Currently this leads to a limitation where we need to + * enable them at all times. + */ + clk_enable(&p6_pclk1); + clk_enable(&p6_pclk2); + clk_enable(&p6_pclk3); + if (cpu_is_u8500() && !ux500_is_svp()) + clk_enable(&p6_rng_clk); + + /* + * APEATCLK and APETRACECLK are enabled at boot and needed + * in order to debug with lauterbach + */ + clk_enable(&apeatclk); + clk_enable(&apetraceclk); +#ifdef CONFIG_DEBUG_NO_LAUTERBACH + clk_disable(&apeatclk); + clk_disable(&apetraceclk); +#endif + /* periph 7's clock is enabled at boot, but should be off */ + clk_enable(&per7clk); + clk_disable(&per7clk); + + /* the hsirx clock is enabled at boot, but should be off */ + clk_enable(&hsirxclk); + clk_disable(&hsirxclk); + + return 0; +} diff --git a/arch/arm/mach-ux500/clock.c b/arch/arm/mach-ux500/clock.c index 32ce90840ee..925937332d7 100644 --- a/arch/arm/mach-ux500/clock.c +++ b/arch/arm/mach-ux500/clock.c @@ -7,784 +7,374 @@ * published by the Free Software Foundation. */ #include -#include -#include #include -#include -#include #include -#include +#include +#include +#include -#include -#include #include "clock.h" -#ifdef CONFIG_DEBUG_FS -#include -#include /* for copy_from_user */ -static LIST_HEAD(clk_list); -#endif - -#define PRCC_PCKEN 0x00 -#define PRCC_PCKDIS 0x04 -#define PRCC_KCKEN 0x08 -#define PRCC_KCKDIS 0x0C - -#define PRCM_YYCLKEN0_MGT_SET 0x510 -#define PRCM_YYCLKEN1_MGT_SET 0x514 -#define PRCM_YYCLKEN0_MGT_CLR 0x518 -#define PRCM_YYCLKEN1_MGT_CLR 0x51C -#define PRCM_YYCLKEN0_MGT_VAL 0x520 -#define PRCM_YYCLKEN1_MGT_VAL 0x524 - -#define PRCM_SVAMMDSPCLK_MGT 0x008 -#define PRCM_SIAMMDSPCLK_MGT 0x00C -#define PRCM_SGACLK_MGT 0x014 -#define PRCM_UARTCLK_MGT 0x018 -#define PRCM_MSP02CLK_MGT 0x01C -#define PRCM_MSP1CLK_MGT 0x288 -#define PRCM_I2CCLK_MGT 0x020 -#define PRCM_SDMMCCLK_MGT 0x024 -#define PRCM_SLIMCLK_MGT 0x028 -#define PRCM_PER1CLK_MGT 0x02C -#define PRCM_PER2CLK_MGT 0x030 -#define PRCM_PER3CLK_MGT 0x034 -#define PRCM_PER5CLK_MGT 0x038 -#define PRCM_PER6CLK_MGT 0x03C -#define PRCM_PER7CLK_MGT 0x040 -#define PRCM_LCDCLK_MGT 0x044 -#define PRCM_BMLCLK_MGT 0x04C -#define PRCM_HSITXCLK_MGT 0x050 -#define PRCM_HSIRXCLK_MGT 0x054 -#define PRCM_HDMICLK_MGT 0x058 -#define PRCM_APEATCLK_MGT 0x05C -#define PRCM_APETRACECLK_MGT 0x060 -#define PRCM_MCDECLK_MGT 0x064 -#define PRCM_IPI2CCLK_MGT 0x068 -#define PRCM_DSIALTCLK_MGT 0x06C -#define PRCM_DMACLK_MGT 0x074 -#define PRCM_B2R2CLK_MGT 0x078 -#define PRCM_TVCLK_MGT 0x07C -#define PRCM_TCR 0x1C8 -#define PRCM_TCR_STOPPED (1 << 16) -#define PRCM_TCR_DOZE_MODE (1 << 17) -#define PRCM_UNIPROCLK_MGT 0x278 -#define PRCM_SSPCLK_MGT 0x280 -#define PRCM_RNGCLK_MGT 0x284 -#define PRCM_UICCCLK_MGT 0x27C - -#define PRCM_MGT_ENABLE (1 << 8) - -static DEFINE_SPINLOCK(clocks_lock); - -static void __clk_enable(struct clk *clk) -{ - if (clk->enabled++ == 0) { - if (clk->parent_cluster) - __clk_enable(clk->parent_cluster); +#define PRCC_PCKEN 0x0 +#define PRCC_PCKDIS 0x4 +#define PRCC_KCKEN 0x8 +#define PRCC_KCKDIS 0xC +#define PRCC_PCKSR 0x10 +#define PRCC_KCKSR 0x14 + +DEFINE_MUTEX(clk_opp100_mutex); +static DEFINE_SPINLOCK(clk_spin_lock); +#define NO_LOCK &clk_spin_lock - if (clk->parent_periph) - __clk_enable(clk->parent_periph); +static void __iomem *prcmu_base; - if (clk->ops && clk->ops->enable) - clk->ops->enable(clk); +static void __clk_lock(struct clk *clk, void *last_lock, unsigned long *flags) +{ + if (clk->mutex != last_lock) { + if (clk->mutex == NULL) + spin_lock_irqsave(&clk_spin_lock, *flags); + else + mutex_lock(clk->mutex); } } -int clk_enable(struct clk *clk) +static void __clk_unlock(struct clk *clk, void *last_lock, unsigned long flags) +{ + if (clk->mutex != last_lock) { + if (clk->mutex == NULL) + spin_unlock_irqrestore(&clk_spin_lock, flags); + else + mutex_unlock(clk->mutex); + } +} + +static void __clk_disable(struct clk *clk, void *current_lock) { unsigned long flags; - spin_lock_irqsave(&clocks_lock, flags); - __clk_enable(clk); - spin_unlock_irqrestore(&clocks_lock, flags); + if (clk == NULL) + return; - return 0; -} -EXPORT_SYMBOL(clk_enable); + __clk_lock(clk, current_lock, &flags); -static void __clk_disable(struct clk *clk) -{ - if (--clk->enabled == 0) { - if (clk->ops && clk->ops->disable) + if (clk->enabled && (--clk->enabled == 0)) { + if ((clk->ops != NULL) && (clk->ops->disable != NULL)) clk->ops->disable(clk); + __clk_disable(clk->parent, clk->mutex); + __clk_disable(clk->bus_parent, clk->mutex); + } - if (clk->parent_periph) - __clk_disable(clk->parent_periph); + __clk_unlock(clk, current_lock, flags); - if (clk->parent_cluster) - __clk_disable(clk->parent_cluster); - } + return; } -void clk_disable(struct clk *clk) +static int __clk_enable(struct clk *clk, void *current_lock) { + int err; unsigned long flags; - WARN_ON(!clk->enabled); + if (clk == NULL) + return 0; - spin_lock_irqsave(&clocks_lock, flags); - __clk_disable(clk); - spin_unlock_irqrestore(&clocks_lock, flags); -} -EXPORT_SYMBOL(clk_disable); + __clk_lock(clk, current_lock, &flags); -/* - * The MTU has a separate, rather complex muxing setup - * with alternative parents (peripheral cluster or - * ULP or fixed 32768 Hz) depending on settings - */ -static unsigned long clk_mtu_get_rate(struct clk *clk) -{ - void __iomem *addr; - u32 tcr; - int mtu = (int) clk->data; - /* - * One of these is selected eventually - * TODO: Replace the constant with a reference - * to the ULP source once this is modeled. - */ - unsigned long clk32k = 32768; - unsigned long mturate; - unsigned long retclk; - - if (cpu_is_u5500()) - addr = __io_address(U5500_PRCMU_BASE); - else if (cpu_is_u8500()) - addr = __io_address(U8500_PRCMU_BASE); - else - ux500_unknown_soc(); + if (!clk->enabled) { + err = __clk_enable(clk->bus_parent, clk->mutex); + if (unlikely(err)) + goto bus_parent_error; - /* - * On a startup, always conifgure the TCR to the doze mode; - * bootloaders do it for us. Do this in the kernel too. - */ - writel(PRCM_TCR_DOZE_MODE, addr + PRCM_TCR); + err = __clk_enable(clk->parent, clk->mutex); + if (unlikely(err)) + goto parent_error; - tcr = readl(addr + PRCM_TCR); + if ((clk->ops != NULL) && (clk->ops->enable != NULL)) { + err = clk->ops->enable(clk); + if (unlikely(err)) + goto enable_error; + } + } + clk->enabled++; - /* Get the rate from the parent as a default */ - if (clk->parent_periph) - mturate = clk_get_rate(clk->parent_periph); - else if (clk->parent_cluster) - mturate = clk_get_rate(clk->parent_cluster); - else - /* We need to be connected SOMEWHERE */ - BUG(); + __clk_unlock(clk, current_lock, flags); - /* Return the clock selected for this MTU */ - if (tcr & (1 << mtu)) - retclk = clk32k; - else - retclk = mturate; + return 0; - pr_info("MTU%d clock rate: %lu Hz\n", mtu, retclk); - return retclk; +enable_error: + __clk_disable(clk->parent, clk->mutex); +parent_error: + __clk_disable(clk->bus_parent, clk->mutex); +bus_parent_error: + + __clk_unlock(clk, current_lock, flags); + + return err; } -unsigned long clk_get_rate(struct clk *clk) +unsigned long __clk_get_rate(struct clk *clk, void *current_lock) { unsigned long rate; + unsigned long flags; - /* - * If there is a custom getrate callback for this clock, - * it will take precedence. - */ - if (clk->get_rate) - return clk->get_rate(clk); - - if (clk->ops && clk->ops->get_rate) - return clk->ops->get_rate(clk); - - rate = clk->rate; - if (!rate) { - if (clk->parent_periph) - rate = clk_get_rate(clk->parent_periph); - else if (clk->parent_cluster) - rate = clk_get_rate(clk->parent_cluster); - } + if (clk == NULL) + return 0; - return rate; -} -EXPORT_SYMBOL(clk_get_rate); + __clk_lock(clk, current_lock, &flags); + + if ((clk->ops != NULL) && (clk->ops->get_rate != NULL)) + rate = clk->ops->get_rate(clk); + else if (clk->rate) + rate = clk->rate; + else + rate = __clk_get_rate(clk->parent, clk->mutex); + + __clk_unlock(clk, current_lock, flags); -long clk_round_rate(struct clk *clk, unsigned long rate) -{ - /*TODO*/ return rate; } -EXPORT_SYMBOL(clk_round_rate); -int clk_set_rate(struct clk *clk, unsigned long rate) +static unsigned long __clk_round_rate(struct clk *clk, unsigned long rate) { - clk->rate = rate; - return 0; + if ((clk->ops != NULL) && (clk->ops->round_rate != NULL)) + return clk->ops->round_rate(clk, rate); + + return -ENOSYS; } -EXPORT_SYMBOL(clk_set_rate); -static void clk_prcmu_enable(struct clk *clk) +static int __clk_set_rate(struct clk *clk, unsigned long rate) { - void __iomem *cg_set_reg = __io_address(U8500_PRCMU_BASE) - + PRCM_YYCLKEN0_MGT_SET + clk->prcmu_cg_off; + if ((clk->ops != NULL) && (clk->ops->set_rate != NULL)) + return clk->ops->set_rate(clk, rate); - writel(1 << clk->prcmu_cg_bit, cg_set_reg); + return -ENOSYS; } -static void clk_prcmu_disable(struct clk *clk) +int clk_enable(struct clk *clk) { - void __iomem *cg_clr_reg = __io_address(U8500_PRCMU_BASE) - + PRCM_YYCLKEN0_MGT_CLR + clk->prcmu_cg_off; + if (clk == NULL) + return -EINVAL; - writel(1 << clk->prcmu_cg_bit, cg_clr_reg); + return __clk_enable(clk, NO_LOCK); } +EXPORT_SYMBOL(clk_enable); -/* ED doesn't have the combined set/clr registers */ -static void clk_prcmu_ed_enable(struct clk *clk) +void clk_disable(struct clk *clk) { - void __iomem *addr = __io_address(U8500_PRCMU_BASE) - + clk->prcmu_cg_mgt; - writel(readl(addr) | PRCM_MGT_ENABLE, addr); + if (clk == NULL) + return; + + WARN_ON(!clk->enabled); + __clk_disable(clk, NO_LOCK); } +EXPORT_SYMBOL(clk_disable); -static void clk_prcmu_ed_disable(struct clk *clk) +unsigned long clk_get_rate(struct clk *clk) { - void __iomem *addr = __io_address(U8500_PRCMU_BASE) - + clk->prcmu_cg_mgt; + if (clk == NULL) + return 0; - writel(readl(addr) & ~PRCM_MGT_ENABLE, addr); + return __clk_get_rate(clk, NO_LOCK); } +EXPORT_SYMBOL(clk_get_rate); -static struct clkops clk_prcmu_ops = { - .enable = clk_prcmu_enable, - .disable = clk_prcmu_disable, -}; +long clk_round_rate(struct clk *clk, unsigned long rate) +{ + unsigned long flags; -static unsigned int clkrst_base[] = { - [1] = U8500_CLKRST1_BASE, - [2] = U8500_CLKRST2_BASE, - [3] = U8500_CLKRST3_BASE, - [5] = U8500_CLKRST5_BASE, - [6] = U8500_CLKRST6_BASE, - [7] = U8500_CLKRST7_BASE_ED, -}; + if (clk == NULL) + return -EINVAL; -static void clk_prcc_enable(struct clk *clk) -{ - void __iomem *addr = __io_address(clkrst_base[clk->cluster]); + __clk_lock(clk, NO_LOCK, &flags); - if (clk->prcc_kernel != -1) - writel(1 << clk->prcc_kernel, addr + PRCC_KCKEN); + rate = __clk_round_rate(clk, rate); - if (clk->prcc_bus != -1) - writel(1 << clk->prcc_bus, addr + PRCC_PCKEN); + __clk_unlock(clk, NO_LOCK, flags); + + return rate; } +EXPORT_SYMBOL(clk_round_rate); -static void clk_prcc_disable(struct clk *clk) +int clk_set_rate(struct clk *clk, unsigned long rate) { - void __iomem *addr = __io_address(clkrst_base[clk->cluster]); + int err; + unsigned long flags; - if (clk->prcc_bus != -1) - writel(1 << clk->prcc_bus, addr + PRCC_PCKDIS); + if (clk == NULL) + return -EINVAL; - if (clk->prcc_kernel != -1) - writel(1 << clk->prcc_kernel, addr + PRCC_KCKDIS); -} + __clk_lock(clk, NO_LOCK, &flags); -static struct clkops clk_prcc_ops = { - .enable = clk_prcc_enable, - .disable = clk_prcc_disable, -}; + err = __clk_set_rate(clk, rate); -static struct clk clk_32khz = { - .name = "clk_32khz", - .rate = 32000, -}; + __clk_unlock(clk, NO_LOCK, flags); -/* - * PRCMU level clock gating - */ + return err; +} +EXPORT_SYMBOL(clk_set_rate); -/* Bank 0 */ -static DEFINE_PRCMU_CLK(svaclk, 0x0, 2, SVAMMDSPCLK); -static DEFINE_PRCMU_CLK(siaclk, 0x0, 3, SIAMMDSPCLK); -static DEFINE_PRCMU_CLK(sgaclk, 0x0, 4, SGACLK); -static DEFINE_PRCMU_CLK_RATE(uartclk, 0x0, 5, UARTCLK, 38400000); -static DEFINE_PRCMU_CLK(msp02clk, 0x0, 6, MSP02CLK); -static DEFINE_PRCMU_CLK(msp1clk, 0x0, 7, MSP1CLK); /* v1 */ -static DEFINE_PRCMU_CLK_RATE(i2cclk, 0x0, 8, I2CCLK, 48000000); -static DEFINE_PRCMU_CLK_RATE(sdmmcclk, 0x0, 9, SDMMCCLK, 100000000); -static DEFINE_PRCMU_CLK(slimclk, 0x0, 10, SLIMCLK); -static DEFINE_PRCMU_CLK(per1clk, 0x0, 11, PER1CLK); -static DEFINE_PRCMU_CLK(per2clk, 0x0, 12, PER2CLK); -static DEFINE_PRCMU_CLK(per3clk, 0x0, 13, PER3CLK); -static DEFINE_PRCMU_CLK(per5clk, 0x0, 14, PER5CLK); -static DEFINE_PRCMU_CLK_RATE(per6clk, 0x0, 15, PER6CLK, 133330000); -static DEFINE_PRCMU_CLK_RATE(per7clk, 0x0, 16, PER7CLK, 100000000); -static DEFINE_PRCMU_CLK(lcdclk, 0x0, 17, LCDCLK); -static DEFINE_PRCMU_CLK(bmlclk, 0x0, 18, BMLCLK); -static DEFINE_PRCMU_CLK(hsitxclk, 0x0, 19, HSITXCLK); -static DEFINE_PRCMU_CLK(hsirxclk, 0x0, 20, HSIRXCLK); -static DEFINE_PRCMU_CLK(hdmiclk, 0x0, 21, HDMICLK); -static DEFINE_PRCMU_CLK(apeatclk, 0x0, 22, APEATCLK); -static DEFINE_PRCMU_CLK(apetraceclk, 0x0, 23, APETRACECLK); -static DEFINE_PRCMU_CLK(mcdeclk, 0x0, 24, MCDECLK); -static DEFINE_PRCMU_CLK(ipi2clk, 0x0, 25, IPI2CCLK); -static DEFINE_PRCMU_CLK(dsialtclk, 0x0, 26, DSIALTCLK); /* v1 */ -static DEFINE_PRCMU_CLK(dmaclk, 0x0, 27, DMACLK); -static DEFINE_PRCMU_CLK(b2r2clk, 0x0, 28, B2R2CLK); -static DEFINE_PRCMU_CLK(tvclk, 0x0, 29, TVCLK); -static DEFINE_PRCMU_CLK(uniproclk, 0x0, 30, UNIPROCLK); /* v1 */ -static DEFINE_PRCMU_CLK_RATE(sspclk, 0x0, 31, SSPCLK, 48000000); /* v1 */ - -/* Bank 1 */ -static DEFINE_PRCMU_CLK(rngclk, 0x4, 0, RNGCLK); /* v1 */ -static DEFINE_PRCMU_CLK(uiccclk, 0x4, 1, UICCCLK); /* v1 */ +int clk_set_parent(struct clk *clk, struct clk *parent) +{ + int err = -EINVAL; + unsigned long flags; + struct clk **p; -/* - * PRCC level clock gating - * Format: per#, clk, PCKEN bit, KCKEN bit, parent - */ + if ((clk == NULL) || (clk->parents == NULL)) + return -EINVAL; + for (p = clk->parents; *p != parent; p++) { + if (*p == NULL) /* invalid parent */ + return -EINVAL; + } -/* Peripheral Cluster #1 */ -static DEFINE_PRCC_CLK(1, i2c4, 10, 9, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, gpio0, 9, -1, NULL); -static DEFINE_PRCC_CLK(1, slimbus0, 8, 8, &clk_slimclk); -static DEFINE_PRCC_CLK(1, spi3_ed, 7, 7, NULL); -static DEFINE_PRCC_CLK(1, spi3_v1, 7, -1, NULL); -static DEFINE_PRCC_CLK(1, i2c2, 6, 6, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, sdi0, 5, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(1, msp1_ed, 4, 4, &clk_msp02clk); -static DEFINE_PRCC_CLK(1, msp1_v1, 4, 4, &clk_msp1clk); -static DEFINE_PRCC_CLK(1, msp0, 3, 3, &clk_msp02clk); -static DEFINE_PRCC_CLK(1, i2c1, 2, 2, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, uart1, 1, 1, &clk_uartclk); -static DEFINE_PRCC_CLK(1, uart0, 0, 0, &clk_uartclk); - -/* Peripheral Cluster #2 */ - -static DEFINE_PRCC_CLK(2, gpio1_ed, 12, -1, NULL); -static DEFINE_PRCC_CLK(2, ssitx_ed, 11, -1, NULL); -static DEFINE_PRCC_CLK(2, ssirx_ed, 10, -1, NULL); -static DEFINE_PRCC_CLK(2, spi0_ed, 9, -1, NULL); -static DEFINE_PRCC_CLK(2, sdi3_ed, 8, 6, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, sdi1_ed, 7, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, msp2_ed, 6, 4, &clk_msp02clk); -static DEFINE_PRCC_CLK(2, sdi4_ed, 4, 2, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, pwl_ed, 3, 1, NULL); -static DEFINE_PRCC_CLK(2, spi1_ed, 2, -1, NULL); -static DEFINE_PRCC_CLK(2, spi2_ed, 1, -1, NULL); -static DEFINE_PRCC_CLK(2, i2c3_ed, 0, 0, &clk_i2cclk); - -static DEFINE_PRCC_CLK(2, gpio1_v1, 11, -1, NULL); -static DEFINE_PRCC_CLK(2, ssitx_v1, 10, 7, NULL); -static DEFINE_PRCC_CLK(2, ssirx_v1, 9, 6, NULL); -static DEFINE_PRCC_CLK(2, spi0_v1, 8, -1, NULL); -static DEFINE_PRCC_CLK(2, sdi3_v1, 7, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, sdi1_v1, 6, 4, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, msp2_v1, 5, 3, &clk_msp02clk); -static DEFINE_PRCC_CLK(2, sdi4_v1, 4, 2, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, pwl_v1, 3, 1, NULL); -static DEFINE_PRCC_CLK(2, spi1_v1, 2, -1, NULL); -static DEFINE_PRCC_CLK(2, spi2_v1, 1, -1, NULL); -static DEFINE_PRCC_CLK(2, i2c3_v1, 0, 0, &clk_i2cclk); - -/* Peripheral Cluster #3 */ -static DEFINE_PRCC_CLK(3, gpio2, 8, -1, NULL); -static DEFINE_PRCC_CLK(3, sdi5, 7, 7, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(3, uart2, 6, 6, &clk_uartclk); -static DEFINE_PRCC_CLK(3, ske, 5, 5, &clk_32khz); -static DEFINE_PRCC_CLK(3, sdi2, 4, 4, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(3, i2c0, 3, 3, &clk_i2cclk); -static DEFINE_PRCC_CLK(3, ssp1_ed, 2, 2, &clk_i2cclk); -static DEFINE_PRCC_CLK(3, ssp0_ed, 1, 1, &clk_i2cclk); -static DEFINE_PRCC_CLK(3, ssp1_v1, 2, 2, &clk_sspclk); -static DEFINE_PRCC_CLK(3, ssp0_v1, 1, 1, &clk_sspclk); -static DEFINE_PRCC_CLK(3, fsmc, 0, -1, NULL); - -/* Peripheral Cluster #4 is in the always on domain */ - -/* Peripheral Cluster #5 */ -static DEFINE_PRCC_CLK(5, gpio3, 1, -1, NULL); -static DEFINE_PRCC_CLK(5, usb_ed, 0, 0, &clk_i2cclk); -static DEFINE_PRCC_CLK(5, usb_v1, 0, 0, NULL); - -/* Peripheral Cluster #6 */ - -/* MTU ID in data */ -static DEFINE_PRCC_CLK_CUSTOM(6, mtu1_v1, 8, -1, NULL, clk_mtu_get_rate, 1); -static DEFINE_PRCC_CLK_CUSTOM(6, mtu0_v1, 7, -1, NULL, clk_mtu_get_rate, 0); -static DEFINE_PRCC_CLK(6, cfgreg_v1, 6, 6, NULL); -static DEFINE_PRCC_CLK(6, dmc_ed, 6, 6, NULL); -static DEFINE_PRCC_CLK(6, hash1, 5, -1, NULL); -static DEFINE_PRCC_CLK(6, unipro_v1, 4, 1, &clk_uniproclk); -static DEFINE_PRCC_CLK(6, cryp1_ed, 4, -1, NULL); -static DEFINE_PRCC_CLK(6, pka, 3, -1, NULL); -static DEFINE_PRCC_CLK(6, hash0, 2, -1, NULL); -static DEFINE_PRCC_CLK(6, cryp0, 1, -1, NULL); -static DEFINE_PRCC_CLK(6, rng_ed, 0, 0, &clk_i2cclk); -static DEFINE_PRCC_CLK(6, rng_v1, 0, 0, &clk_rngclk); - -/* Peripheral Cluster #7 */ - -static DEFINE_PRCC_CLK(7, tzpc0_ed, 4, -1, NULL); -/* MTU ID in data */ -static DEFINE_PRCC_CLK_CUSTOM(7, mtu1_ed, 3, -1, NULL, clk_mtu_get_rate, 1); -static DEFINE_PRCC_CLK_CUSTOM(7, mtu0_ed, 2, -1, NULL, clk_mtu_get_rate, 0); -static DEFINE_PRCC_CLK(7, wdg_ed, 1, -1, NULL); -static DEFINE_PRCC_CLK(7, cfgreg_ed, 0, -1, NULL); - -static struct clk clk_dummy_apb_pclk = { - .name = "apb_pclk", -}; + __clk_lock(clk, NO_LOCK, &flags); -static struct clk_lookup u8500_common_clks[] = { - CLK(dummy_apb_pclk, NULL, "apb_pclk"), - - /* Peripheral Cluster #1 */ - CLK(gpio0, "gpio.0", NULL), - CLK(gpio0, "gpio.1", NULL), - CLK(slimbus0, "slimbus0", NULL), - CLK(i2c2, "nmk-i2c.2", NULL), - CLK(sdi0, "sdi0", NULL), - CLK(msp0, "msp0", NULL), - CLK(i2c1, "nmk-i2c.1", NULL), - CLK(uart1, "uart1", NULL), - CLK(uart0, "uart0", NULL), - - /* Peripheral Cluster #3 */ - CLK(gpio2, "gpio.2", NULL), - CLK(gpio2, "gpio.3", NULL), - CLK(gpio2, "gpio.4", NULL), - CLK(gpio2, "gpio.5", NULL), - CLK(sdi5, "sdi5", NULL), - CLK(uart2, "uart2", NULL), - CLK(ske, "ske", NULL), - CLK(ske, "nmk-ske-keypad", NULL), - CLK(sdi2, "sdi2", NULL), - CLK(i2c0, "nmk-i2c.0", NULL), - CLK(fsmc, "fsmc", NULL), - - /* Peripheral Cluster #5 */ - CLK(gpio3, "gpio.8", NULL), - - /* Peripheral Cluster #6 */ - CLK(hash1, "hash1", NULL), - CLK(pka, "pka", NULL), - CLK(hash0, "hash0", NULL), - CLK(cryp0, "cryp0", NULL), - - /* PRCMU level clock gating */ - - /* Bank 0 */ - CLK(svaclk, "sva", NULL), - CLK(siaclk, "sia", NULL), - CLK(sgaclk, "sga", NULL), - CLK(slimclk, "slim", NULL), - CLK(lcdclk, "lcd", NULL), - CLK(bmlclk, "bml", NULL), - CLK(hsitxclk, "stm-hsi.0", NULL), - CLK(hsirxclk, "stm-hsi.1", NULL), - CLK(hdmiclk, "hdmi", NULL), - CLK(apeatclk, "apeat", NULL), - CLK(apetraceclk, "apetrace", NULL), - CLK(mcdeclk, "mcde", NULL), - CLK(ipi2clk, "ipi2", NULL), - CLK(dmaclk, "dma40.0", NULL), - CLK(b2r2clk, "b2r2", NULL), - CLK(tvclk, "tv", NULL), -}; + if (clk->enabled) { + err = -EINVAL; + } else { + if ((clk->ops != NULL) && (clk->ops->set_parent != NULL)) + err = clk->ops->set_parent(clk, parent); + if (!err) + clk->parent = parent; + } -static struct clk_lookup u8500_ed_clks[] = { - /* Peripheral Cluster #1 */ - CLK(spi3_ed, "spi3", NULL), - CLK(msp1_ed, "msp1", NULL), - - /* Peripheral Cluster #2 */ - CLK(gpio1_ed, "gpio.6", NULL), - CLK(gpio1_ed, "gpio.7", NULL), - CLK(ssitx_ed, "ssitx", NULL), - CLK(ssirx_ed, "ssirx", NULL), - CLK(spi0_ed, "spi0", NULL), - CLK(sdi3_ed, "sdi3", NULL), - CLK(sdi1_ed, "sdi1", NULL), - CLK(msp2_ed, "msp2", NULL), - CLK(sdi4_ed, "sdi4", NULL), - CLK(pwl_ed, "pwl", NULL), - CLK(spi1_ed, "spi1", NULL), - CLK(spi2_ed, "spi2", NULL), - CLK(i2c3_ed, "nmk-i2c.3", NULL), - - /* Peripheral Cluster #3 */ - CLK(ssp1_ed, "ssp1", NULL), - CLK(ssp0_ed, "ssp0", NULL), - - /* Peripheral Cluster #5 */ - CLK(usb_ed, "musb-ux500.0", "usb"), - - /* Peripheral Cluster #6 */ - CLK(dmc_ed, "dmc", NULL), - CLK(cryp1_ed, "cryp1", NULL), - CLK(rng_ed, "rng", NULL), - - /* Peripheral Cluster #7 */ - CLK(tzpc0_ed, "tzpc0", NULL), - CLK(mtu1_ed, "mtu1", NULL), - CLK(mtu0_ed, "mtu0", NULL), - CLK(wdg_ed, "wdg", NULL), - CLK(cfgreg_ed, "cfgreg", NULL), -}; + __clk_unlock(clk, NO_LOCK, flags); -static struct clk_lookup u8500_v1_clks[] = { - /* Peripheral Cluster #1 */ - CLK(i2c4, "nmk-i2c.4", NULL), - CLK(spi3_v1, "spi3", NULL), - CLK(msp1_v1, "msp1", NULL), - - /* Peripheral Cluster #2 */ - CLK(gpio1_v1, "gpio.6", NULL), - CLK(gpio1_v1, "gpio.7", NULL), - CLK(ssitx_v1, "ssitx", NULL), - CLK(ssirx_v1, "ssirx", NULL), - CLK(spi0_v1, "spi0", NULL), - CLK(sdi3_v1, "sdi3", NULL), - CLK(sdi1_v1, "sdi1", NULL), - CLK(msp2_v1, "msp2", NULL), - CLK(sdi4_v1, "sdi4", NULL), - CLK(pwl_v1, "pwl", NULL), - CLK(spi1_v1, "spi1", NULL), - CLK(spi2_v1, "spi2", NULL), - CLK(i2c3_v1, "nmk-i2c.3", NULL), - - /* Peripheral Cluster #3 */ - CLK(ssp1_v1, "ssp1", NULL), - CLK(ssp0_v1, "ssp0", NULL), - - /* Peripheral Cluster #5 */ - CLK(usb_v1, "musb-ux500.0", "usb"), - - /* Peripheral Cluster #6 */ - CLK(mtu1_v1, "mtu1", NULL), - CLK(mtu0_v1, "mtu0", NULL), - CLK(cfgreg_v1, "cfgreg", NULL), - CLK(hash1, "hash1", NULL), - CLK(unipro_v1, "unipro", NULL), - CLK(rng_v1, "rng", NULL), - - /* PRCMU level clock gating */ - - /* Bank 0 */ - CLK(uniproclk, "uniproclk", NULL), - CLK(dsialtclk, "dsialt", NULL), - - /* Bank 1 */ - CLK(rngclk, "rng", NULL), - CLK(uiccclk, "uicc", NULL), -}; + return err; +} -#ifdef CONFIG_DEBUG_FS -/* - * debugfs support to trace clock tree hierarchy and attributes with - * powerdebug - */ -static struct dentry *clk_debugfs_root; +/* PRCMU clock operations. */ -void __init clk_debugfs_add_table(struct clk_lookup *cl, size_t num) +static int prcmu_clk_enable(struct clk *clk) { - while (num--) { - /* Check that the clock has not been already registered */ - if (!(cl->clk->list.prev != cl->clk->list.next)) - list_add_tail(&cl->clk->list, &clk_list); + return prcmu_request_clock(clk->cg_sel, true); +} - cl++; +static void prcmu_clk_disable(struct clk *clk) +{ + if (prcmu_request_clock(clk->cg_sel, false)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); } } -static ssize_t usecount_dbg_read(struct file *file, char __user *buf, - size_t size, loff_t *off) +static int request_ape_opp100(bool enable) { - struct clk *clk = file->f_dentry->d_inode->i_private; - char cusecount[128]; - unsigned int len; + static unsigned int requests; + + if (enable) { + if (0 == requests++) { + return prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + "clock", 100); + } + } else if (1 == requests--) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "clock"); + } + return 0; +} + +static int prcmu_opp100_clk_enable(struct clk *clk) +{ + int r; - len = sprintf(cusecount, "%u\n", clk->enabled); - return simple_read_from_buffer(buf, size, off, cusecount, len); + r = request_ape_opp100(true); + if (r) { + pr_err("clock: %s failed to request APE OPP 100%% for %s.\n", + __func__, clk->name); + return r; + } + return prcmu_request_clock(clk->cg_sel, true); } -static ssize_t rate_dbg_read(struct file *file, char __user *buf, - size_t size, loff_t *off) +static void prcmu_opp100_clk_disable(struct clk *clk) { - struct clk *clk = file->f_dentry->d_inode->i_private; - char crate[128]; - unsigned int rate; - unsigned int len; - - rate = clk_get_rate(clk); - len = sprintf(crate, "%u\n", rate); - return simple_read_from_buffer(buf, size, off, crate, len); + if (prcmu_request_clock(clk->cg_sel, false)) + goto out_error; + if (request_ape_opp100(false)) + goto out_error; + return; + +out_error: + pr_err("clock: %s failed to disable %s.\n", __func__, clk->name); } -static const struct file_operations usecount_fops = { - .read = usecount_dbg_read, +struct clkops prcmu_clk_ops = { + .enable = prcmu_clk_enable, + .disable = prcmu_clk_disable, }; -static const struct file_operations set_rate_fops = { - .read = rate_dbg_read, +struct clkops prcmu_opp100_clk_ops = { + .enable = prcmu_opp100_clk_enable, + .disable = prcmu_opp100_clk_disable, }; -static struct dentry *clk_debugfs_register_dir(struct clk *c, - struct dentry *p_dentry) +/* PRCC clock operations. */ + +static int prcc_pclk_enable(struct clk *clk) { - struct dentry *d, *clk_d, *child, *child_tmp; - char s[255]; - char *p = s; + void __iomem *io_base = __io_address(clk->io_base); - if (c->name == NULL) - p += sprintf(p, "BUG"); - else - p += sprintf(p, "%s", c->name); - - clk_d = debugfs_create_dir(s, p_dentry); - if (!clk_d) - return NULL; - - d = debugfs_create_file("usecount", S_IRUGO, - clk_d, c, &usecount_fops); - if (!d) - goto err_out; - d = debugfs_create_file("rate", S_IRUGO, - clk_d, c, &set_rate_fops); - if (!d) - goto err_out; - /* - * TODO : not currently available in ux500 - * d = debugfs_create_x32("flags", S_IRUGO, clk_d, (u32 *)&c->flags); - * if (!d) - * goto err_out; - */ - - return clk_d; - -err_out: - d = clk_d; - list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) - debugfs_remove(child); - debugfs_remove(clk_d); - return NULL; + writel(clk->cg_sel, (io_base + PRCC_PCKEN)); + while (!(readl(io_base + PRCC_PCKSR) & clk->cg_sel)) + cpu_relax(); + return 0; } -static void clk_debugfs_remove_dir(struct dentry *cdentry) +static void prcc_pclk_disable(struct clk *clk) { - struct dentry *d, *child, *child_tmp; + void __iomem *io_base = __io_address(clk->io_base); - d = cdentry; - list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) - debugfs_remove(child); - debugfs_remove(cdentry); - return ; + writel(clk->cg_sel, (io_base + PRCC_PCKDIS)); } -static int clk_debugfs_register_one(struct clk *c) +struct clkops prcc_pclk_ops = { + .enable = prcc_pclk_enable, + .disable = prcc_pclk_disable, +}; + +static int prcc_kclk_enable(struct clk *clk) { - struct clk *pa = c->parent_periph; - struct clk *bpa = c->parent_cluster; - - if (!(bpa && !pa)) { - c->dent = clk_debugfs_register_dir(c, - pa ? pa->dent : clk_debugfs_root); - if (!c->dent) - return -ENOMEM; - } + void __iomem *io_base = __io_address(clk->io_base); - if (bpa) { - c->dent_bus = clk_debugfs_register_dir(c, - bpa->dent_bus ? bpa->dent_bus : bpa->dent); - if ((!c->dent_bus) && (c->dent)) { - clk_debugfs_remove_dir(c->dent); - c->dent = NULL; - return -ENOMEM; - } - } + writel(clk->cg_sel, (io_base + PRCC_KCKEN)); + while (!(readl(io_base + PRCC_KCKSR) & clk->cg_sel)) + cpu_relax(); return 0; } -static int clk_debugfs_register(struct clk *c) +static void prcc_kclk_disable(struct clk *clk) { - int err; - struct clk *pa = c->parent_periph; - struct clk *bpa = c->parent_cluster; - - if (pa && (!pa->dent && !pa->dent_bus)) { - err = clk_debugfs_register(pa); - if (err) - return err; - } - - if (bpa && (!bpa->dent && !bpa->dent_bus)) { - err = clk_debugfs_register(bpa); - if (err) - return err; - } + void __iomem *io_base = __io_address(clk->io_base); - if ((!c->dent) && (!c->dent_bus)) { - err = clk_debugfs_register_one(c); - if (err) - return err; - } - return 0; + writel(clk->cg_sel, (io_base + PRCC_KCKDIS)); } -static int __init clk_debugfs_init(void) -{ - struct clk *c; - struct dentry *d; - int err; +struct clkops prcc_kclk_ops = { + .enable = prcc_kclk_enable, + .disable = prcc_kclk_disable, +}; - d = debugfs_create_dir("clock", NULL); - if (!d) - return -ENOMEM; - clk_debugfs_root = d; +void clks_register(struct clk_lookup *clks, size_t num) +{ + unsigned int i; - list_for_each_entry(c, &clk_list, list) { - err = clk_debugfs_register(c); - if (err) - goto err_out; - } - return 0; -err_out: - debugfs_remove_recursive(clk_debugfs_root); - return err; + for (i = 0; i < num; i++) + clkdev_add(&clks[i]); } -late_initcall(clk_debugfs_init); -#endif /* defined(CONFIG_DEBUG_FS) */ - int __init clk_init(void) { - if (cpu_is_u8500ed()) { - clk_prcmu_ops.enable = clk_prcmu_ed_enable; - clk_prcmu_ops.disable = clk_prcmu_ed_disable; - clk_per6clk.rate = 100000000; + if (cpu_is_u8500()) { + prcmu_base = __io_address(U8500_PRCMU_BASE); } else if (cpu_is_u5500()) { - /* Clock tree for U5500 not implemented yet */ - clk_prcc_ops.enable = clk_prcc_ops.disable = NULL; - clk_prcmu_ops.enable = clk_prcmu_ops.disable = NULL; - clk_uartclk.rate = 36360000; - clk_sdmmcclk.rate = 99900000; + prcmu_base = __io_address(U5500_PRCMU_BASE); + } else { + pr_err("clock: Unknown DB Asic.\n"); + return -EIO; } - clkdev_add_table(u8500_common_clks, ARRAY_SIZE(u8500_common_clks)); - if (cpu_is_u8500ed()) - clkdev_add_table(u8500_ed_clks, ARRAY_SIZE(u8500_ed_clks)); - else - clkdev_add_table(u8500_v1_clks, ARRAY_SIZE(u8500_v1_clks)); + if (cpu_is_u8500()) + db8500_clk_init(); + /* Here the DB5500 clock tree will be added */ -#ifdef CONFIG_DEBUG_FS - clk_debugfs_add_table(u8500_common_clks, ARRAY_SIZE(u8500_common_clks)); - if (cpu_is_u8500ed()) - clk_debugfs_add_table(u8500_ed_clks, ARRAY_SIZE(u8500_ed_clks)); - else - clk_debugfs_add_table(u8500_v1_clks, ARRAY_SIZE(u8500_v1_clks)); -#endif return 0; } diff --git a/arch/arm/mach-ux500/clock.h b/arch/arm/mach-ux500/clock.h index 07449070522..e37e401b883 100644 --- a/arch/arm/mach-ux500/clock.h +++ b/arch/arm/mach-ux500/clock.h @@ -1,11 +1,53 @@ /* - * Copyright (C) 2010 ST-Ericsson + * Copyright (C) 2010 ST-Ericsson SA * Copyright (C) 2009 STMicroelectronics * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#ifndef UX500_CLOCK_H +#define UX500_CLOCK_H + +#include + +/** + * struct clk + * @ops: The hardware specific operations defined for the clock. + * @name: The name of the clock. + * @mutex: The mutex to lock when operating on the clock. %NULL means that + * the common clock spinlock will be used. + * @enabled: A reference counter of the enable requests for the clock. + * @opp100: A flag saying whether the clock is requested to run at the + * OPP 100%% frequency. + * @rate: The frequency of the clock. For scalable and scaling clocks, + * this is the OPP 100%% frequency. + * @io_base: An IO memory base address, meaningful only when considered + * together with the defined @ops. + * @cg_sel: Clock gate selector, meaningful only when considered together + * with the specified @ops. + * @parent: The current (or only) parent clock of the clock. + * @bus_parent: The (optional) auxiliary bus clock "parent" of the clock. + * @parents: A list of the possible parents the clock can have. This should + * be a %NULL-terminated &struct_clk array. Present if and only + * if clk_set_parent() is implemented for the clock. + * @regulator: The regulator needed to have the clock functional, if any. + */ +struct clk { + const struct clkops *ops; + const char *name; + struct mutex *mutex; + unsigned int enabled; + bool opp100; + unsigned long rate; + unsigned int io_base; + u32 cg_sel; + struct clk *parent; + struct clk *bus_parent; + struct clk **parents; + struct regulator *regulator; + struct list_head list; +}; /** * struct clkops - ux500 clock operations @@ -18,134 +60,81 @@ * NULL, the rate in the struct clk will be used. */ struct clkops { - void (*enable) (struct clk *); - void (*disable) (struct clk *); - unsigned long (*get_rate) (struct clk *); + int (*enable)(struct clk *); + void (*disable)(struct clk *); + unsigned long (*get_rate)(struct clk *); + int (*set_rate)(struct clk *, unsigned long); + unsigned long (*round_rate)(struct clk *, unsigned long); + int (*set_parent)(struct clk *, struct clk *); }; -/** - * struct clk - ux500 clock structure - * @ops: pointer to clkops struct used to control this clock - * @name: name, for debugging - * @enabled: refcount. positive if enabled, zero if disabled - * @get_rate: custom callback for getting the clock rate - * @data: custom per-clock data for example for the get_rate - * callback - * @rate: fixed rate for clocks which don't implement - * ops->getrate - * @prcmu_cg_off: address offset of the combined enable/disable register - * (used on u8500v1) - * @prcmu_cg_bit: bit in the combined enable/disable register (used on - * u8500v1) - * @prcmu_cg_mgt: address of the enable/disable register (used on - * u8500ed) - * @cluster: peripheral cluster number - * @prcc_bus: bit for the bus clock in the peripheral's CLKRST - * @prcc_kernel: bit for the kernel clock in the peripheral's CLKRST. - * -1 if no kernel clock exists. - * @parent_cluster: pointer to parent's cluster clk struct - * @parent_periph: pointer to parent's peripheral clk struct - * - * Peripherals are organised into clusters, and each cluster has an associated - * bus clock. Some peripherals also have a parent peripheral clock. - * - * In order to enable a clock for a peripheral, we need to enable: - * (1) the parent cluster (bus) clock at the PRCMU level - * (2) the parent peripheral clock (if any) at the PRCMU level - * (3) the peripheral's bus & kernel clock at the PRCC level - * - * (1) and (2) are handled by defining clk structs (DEFINE_PRCMU_CLK) for each - * of the cluster and peripheral clocks, and hooking these as the parents of - * the individual peripheral clocks. - * - * (3) is handled by specifying the bits in the PRCC control registers required - * to enable these clocks and modifying them in the ->enable and - * ->disable callbacks of the peripheral clocks (DEFINE_PRCC_CLK). - * - * This structure describes both the PRCMU-level clocks and PRCC-level clocks. - * The prcmu_* fields are only used for the PRCMU clocks, and the cluster, - * prcc, and parent pointers are only used for the PRCC-level clocks. - */ -struct clk { - const struct clkops *ops; - const char *name; - unsigned int enabled; - unsigned long (*get_rate)(struct clk *); - void *data; - - unsigned long rate; - struct list_head list; +extern struct clkops prcmu_clk_ops; +extern struct clkops prcmu_opp100_clk_ops; +extern struct mutex clk_opp100_mutex; +extern struct clkops prcc_pclk_ops; +extern struct clkops prcc_kclk_ops; +extern struct clkops sga_clk_ops; - /* These three are only for PRCMU clks */ - - unsigned int prcmu_cg_off; - unsigned int prcmu_cg_bit; - unsigned int prcmu_cg_mgt; - - /* The rest are only for PRCC clks */ - - int cluster; - unsigned int prcc_bus; - unsigned int prcc_kernel; - - struct clk *parent_cluster; - struct clk *parent_periph; -#if defined(CONFIG_DEBUG_FS) - struct dentry *dent; /* For visible tree hierarchy */ - struct dentry *dent_bus; /* For visible tree hierarchy */ -#endif -}; - -#define DEFINE_PRCMU_CLK(_name, _cg_off, _cg_bit, _reg) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcmu_ops, \ - .prcmu_cg_off = _cg_off, \ - .prcmu_cg_bit = _cg_bit, \ - .prcmu_cg_mgt = PRCM_##_reg##_MGT \ +/* Define PRCMU Clock */ +#define DEF_PRCMU_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ } -#define DEFINE_PRCMU_CLK_RATE(_name, _cg_off, _cg_bit, _reg, _rate) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcmu_ops, \ - .prcmu_cg_off = _cg_off, \ - .prcmu_cg_bit = _cg_bit, \ - .rate = _rate, \ - .prcmu_cg_mgt = PRCM_##_reg##_MGT \ +/* Use this for clocks that are only defined at OPP 100%. */ +#define DEF_PRCMU_OPP100_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_opp100_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + .mutex = &clk_opp100_mutex, \ } -#define DEFINE_PRCC_CLK(_pclust, _name, _bus_en, _kernel_en, _kernclk) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcc_ops, \ - .cluster = _pclust, \ - .prcc_bus = _bus_en, \ - .prcc_kernel = _kernel_en, \ - .parent_cluster = &clk_per##_pclust##clk, \ - .parent_periph = _kernclk \ +/* Define PRCC clock */ +#define DEF_PRCC_PCLK(_name, _io_base, _cg_bit, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcc_pclk_ops, \ + .io_base = _io_base, \ + .cg_sel = BIT(_cg_bit), \ + .parent = _parent, \ } -#define DEFINE_PRCC_CLK_CUSTOM(_pclust, _name, _bus_en, _kernel_en, _kernclk, _callback, _data) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcc_ops, \ - .cluster = _pclust, \ - .prcc_bus = _bus_en, \ - .prcc_kernel = _kernel_en, \ - .parent_cluster = &clk_per##_pclust##clk, \ - .parent_periph = _kernclk, \ - .get_rate = _callback, \ - .data = (void *) _data \ +#define DEF_PRCC_KCLK(_name, _io_base, _cg_bit, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcc_kclk_ops, \ + .io_base = _io_base, \ + .cg_sel = BIT(_cg_bit), \ + .parent = _parent, \ } - -#define CLK(_clk, _devname, _conname) \ - { \ - .clk = &clk_##_clk, \ - .dev_id = _devname, \ - .con_id = _conname, \ +#define DEF_PER_CLK(_name, _bus_parent, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .parent = _parent, \ + .bus_parent = _bus_parent, \ } -int __init clk_db8500_ed_fixup(void); +/* Functions defined in clock.c */ int __init clk_init(void); +void clks_register(struct clk_lookup *clks, size_t num); +unsigned long __clk_get_rate(struct clk *clk, void *current_lock); + +#ifdef CONFIG_UX500_SOC_DB8500 +int __init db8500_clk_init(void); +#else +static inline int db8500_clk_init(void) { return 0; } +#endif + +#ifdef CONFIG_UX500_SOC_DB5500 +int __init db5500_clk_init(void); +#else +static inline int db5500_clk_init(void) { return 0; } +#endif + +#endif -- cgit v1.2.3 From 5b3e8435db3e155967ebae1c28a88af964eb6d7c Mon Sep 17 00:00:00 2001 From: Bengt Jonsson Date: Fri, 1 Apr 2011 14:43:33 +0200 Subject: mach-ux500: voltage domain regulators for DB8500 The DB8500 has ePOD:s (electronic power domains) which are possible to switch on/off to deactivate silicon blocks on the DB8500 SoC by cutting their power without retention. We model these as simple regulators with one bit on/off settings. Cc: Mark Brown Cc: Liam Girdwood Signed-off-by: Bengt Jonsson Signed-off-by: Sundar Iyer Signed-off-by: Jonas Aberg Signed-off-by: Virupax Sadashivpetimath Signed-off-by: Martin Persson Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/Kconfig | 3 +- arch/arm/mach-ux500/Makefile | 3 +- arch/arm/mach-ux500/cpu-db8500.c | 189 ++++++++++- arch/arm/mach-ux500/regulator-db8500.c | 562 +++++++++++++++++++++++++++++++++ arch/arm/mach-ux500/regulator-db8500.h | 45 +++ 5 files changed, 796 insertions(+), 6 deletions(-) create mode 100644 arch/arm/mach-ux500/regulator-db8500.c create mode 100644 arch/arm/mach-ux500/regulator-db8500.h diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index 35d7965b8cc..b6d3c981217 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -15,8 +15,7 @@ config UX500_SOC_DB5500 config UX500_SOC_DB8500 bool "DB8500" - select MFD_DB8500_PRCMU - select REGULATOR_DB8500_PRCMU + select REGULATOR endmenu diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index fa57c6926f7..52c45e86b15 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -7,7 +7,8 @@ obj-y := clock.o cpu.o devices.o devices-common.o \ obj-y += pm/ obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o prcmu-db5500.o obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o \ - prcmu-db8500.o clock-db8500.o + prcmu-db8500.o clock-db8500.o \ + regulator-db8500.o obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \ board-mop500-regulators.o \ board-mop500-uib.o board-mop500-stuib.o \ diff --git a/arch/arm/mach-ux500/cpu-db8500.c b/arch/arm/mach-ux500/cpu-db8500.c index 4598b06c8c5..15bb1a085ce 100644 --- a/arch/arm/mach-ux500/cpu-db8500.c +++ b/arch/arm/mach-ux500/cpu-db8500.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,7 @@ #include "devices-db8500.h" #include "ste-dma40-db8500.h" +#include "regulator-db8500.h" /* minimum static i/o mapping required to boot U8500 platforms */ static struct map_desc u8500_uart_io_desc[] __initdata = { @@ -131,14 +133,195 @@ static struct platform_device db8500_pmu_device = { .dev.platform_data = &db8500_pmu_platdata, }; -static struct platform_device db8500_prcmu_device = { - .name = "db8500-prcmu", +/* + * Power domain switches (ePODs) modeled as regulators for the DB8500 SoC + */ + +static struct regulator_consumer_supply db8500_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"), + /* "v-mmc" changed to "vcore" in the mainline kernel */ + REGULATOR_SUPPLY("vcore", "sdi0"), + REGULATOR_SUPPLY("vcore", "sdi1"), + REGULATOR_SUPPLY("vcore", "sdi2"), + REGULATOR_SUPPLY("vcore", "sdi3"), + 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-ape", "nmk-ske-keypad.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", "mcde.0"), +}; + +static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { + [DB8500_REGULATOR_VAPE] = { + .constraints = { + .name = "db8500-vape", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_vape_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_vape_consumers), + }, + [DB8500_REGULATOR_VARM] = { + .constraints = { + .name = "db8500-varm", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VMODEM] = { + .constraints = { + .name = "db8500-vmodem", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VPLL] = { + .constraints = { + .name = "db8500-vpll", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VSMPS1] = { + .constraints = { + .name = "db8500-vsmps1", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VSMPS2] = { + .constraints = { + .name = "db8500-vsmps2", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_vsmps2_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_vsmps2_consumers), + }, + [DB8500_REGULATOR_VSMPS3] = { + .constraints = { + .name = "db8500-vsmps3", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_VRF1] = { + .constraints = { + .name = "db8500-vrf1", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSP] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-sva-mmdsp", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSPRET] = { + .constraints = { + /* "ret" means "retention" */ + .name = "db8500-sva-mmdsp-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SVAPIPE] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-sva-pipe", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSP] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-sia-mmdsp", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSPRET] = { + .constraints = { + .name = "db8500-sia-mmdsp-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SIAPIPE] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-sia-pipe", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_SGA] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-sga", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_B2R2_MCDE] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-b2r2-mcde", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db8500_b2r2_mcde_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_b2r2_mcde_consumers), + }, + [DB8500_REGULATOR_SWITCH_ESRAM12] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-esram12", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_ESRAM12RET] = { + .constraints = { + .name = "db8500-esram12-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_ESRAM34] = { + .supply_regulator = "db8500-vape", + .constraints = { + .name = "db8500-esram34", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, + [DB8500_REGULATOR_SWITCH_ESRAM34RET] = { + .constraints = { + .name = "db8500-esram34-ret", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + }, +}; + +static struct platform_device db8500_regulator_device = { + .name = "db8500-regulators", + .id = 0, + .dev = { + .platform_data = &db8500_regulators, + }, }; static struct platform_device *platform_devs[] __initdata = { &u8500_dma40_device, &db8500_pmu_device, - &db8500_prcmu_device, + &db8500_regulator_device, }; static resource_size_t __initdata db8500_gpio_base[] = { diff --git a/arch/arm/mach-ux500/regulator-db8500.c b/arch/arm/mach-ux500/regulator-db8500.c new file mode 100644 index 00000000000..edeef2def2c --- /dev/null +++ b/arch/arm/mach-ux500/regulator-db8500.c @@ -0,0 +1,562 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Authors: Sundar Iyer for ST-Ericsson + * Bengt Jonsson for ST-Ericsson + * + * Power domain regulators on DB8500 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "regulator-db8500.h" + +#include + +/* + * power state reference count + */ +static int power_state_active_cnt; /* will initialize to zero */ +static DEFINE_SPINLOCK(power_state_active_lock); + +static void power_state_active_enable(void) +{ + unsigned long flags; + + spin_lock_irqsave(&power_state_active_lock, flags); + power_state_active_cnt++; + spin_unlock_irqrestore(&power_state_active_lock, flags); +} + +static int power_state_active_disable(void) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&power_state_active_lock, flags); + if (power_state_active_cnt <= 0) { + pr_err("power state: unbalanced enable/disable calls\n"); + ret = -EINVAL; + goto out; + } + + power_state_active_cnt--; +out: + spin_unlock_irqrestore(&power_state_active_lock, flags); + return ret; +} + +/* + * Exported interface for CPUIdle only. This function is called when interrupts + * are turned off. Hence, no locking. + */ +int power_state_active_is_enabled(void) +{ + return (power_state_active_cnt > 0); +} + +/** + * struct db8500_regulator_info - db8500 regulator information + * @dev: device pointer + * @desc: regulator description + * @rdev: regulator device pointer + * @is_enabled: status of the regulator + * @epod_id: id for EPOD (power domain) + * @is_ramret: RAM retention switch for EPOD (power domain) + * @operating_point: operating point (only for vape, to be removed) + * + */ +struct db8500_regulator_info { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + bool is_enabled; + u16 epod_id; + bool is_ramret; + bool exclude_from_power_state; + unsigned int operating_point; +}; + +static int db8500_regulator_enable(struct regulator_dev *rdev) +{ + struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-enable\n", + info->desc.name); + + info->is_enabled = true; + if (!info->exclude_from_power_state) + power_state_active_enable(); + + return 0; +} + +static int db8500_regulator_disable(struct regulator_dev *rdev) +{ + struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-disable\n", + info->desc.name); + + info->is_enabled = false; + if (!info->exclude_from_power_state) + ret = power_state_active_disable(); + + return ret; +} + +static int db8500_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-is_enabled (is_enabled):" + " %i\n", info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +/* db8500 regulator operations */ +static struct regulator_ops db8500_regulator_ops = { + .enable = db8500_regulator_enable, + .disable = db8500_regulator_disable, + .is_enabled = db8500_regulator_is_enabled, +}; + +/* + * EPOD control + */ +static bool epod_on[NUM_EPOD_ID]; +static bool epod_ramret[NUM_EPOD_ID]; + +static int enable_epod(u16 epod_id, bool ramret) +{ + int ret; + + if (ramret) { + if (!epod_on[epod_id]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } + epod_ramret[epod_id] = true; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_ON); + if (ret < 0) + return ret; + epod_on[epod_id] = true; + } + + return 0; +} + +static int disable_epod(u16 epod_id, bool ramret) +{ + int ret; + + if (ramret) { + if (!epod_on[epod_id]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_ramret[epod_id] = false; + } else { + if (epod_ramret[epod_id]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_on[epod_id] = false; + } + + return 0; +} + +/* + * Regulator switch + */ +static int db8500_regulator_switch_enable(struct regulator_dev *rdev) +{ + struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-enable\n", + info->desc.name); + + ret = enable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator-switch-%s-enable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = true; +out: + return ret; +} + +static int db8500_regulator_switch_disable(struct regulator_dev *rdev) +{ + struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-disable\n", + info->desc.name); + + ret = disable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator_switch-%s-disable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = 0; +out: + return ret; +} + +static int db8500_regulator_switch_is_enabled(struct regulator_dev *rdev) +{ + struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), + "regulator-switch-%s-is_enabled (is_enabled): %i\n", + info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +static struct regulator_ops db8500_regulator_switch_ops = { + .enable = db8500_regulator_switch_enable, + .disable = db8500_regulator_switch_disable, + .is_enabled = db8500_regulator_switch_is_enabled, +}; + +/* + * Regulator information + */ +static struct db8500_regulator_info + db8500_regulator_info[DB8500_NUM_REGULATORS] = { + [DB8500_REGULATOR_VAPE] = { + .desc = { + .name = "db8500-vape", + .id = DB8500_REGULATOR_VAPE, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VARM] = { + .desc = { + .name = "db8500-varm", + .id = DB8500_REGULATOR_VARM, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VMODEM] = { + .desc = { + .name = "db8500-vmodem", + .id = DB8500_REGULATOR_VMODEM, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VPLL] = { + .desc = { + .name = "db8500-vpll", + .id = DB8500_REGULATOR_VPLL, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VSMPS1] = { + .desc = { + .name = "db8500-vsmps1", + .id = DB8500_REGULATOR_VSMPS1, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VSMPS2] = { + .desc = { + .name = "db8500-vsmps2", + .id = DB8500_REGULATOR_VSMPS2, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .exclude_from_power_state = true, + }, + [DB8500_REGULATOR_VSMPS3] = { + .desc = { + .name = "db8500-vsmps3", + .id = DB8500_REGULATOR_VSMPS3, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_VRF1] = { + .desc = { + .name = "db8500-vrf1", + .id = DB8500_REGULATOR_VRF1, + .ops = &db8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSP] = { + .desc = { + .name = "db8500-sva-mmdsp", + .id = DB8500_REGULATOR_SWITCH_SVAMMDSP, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SVAMMDSP, + }, + [DB8500_REGULATOR_SWITCH_SVAMMDSPRET] = { + .desc = { + .name = "db8500-sva-mmdsp-ret", + .id = DB8500_REGULATOR_SWITCH_SVAMMDSPRET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SVAMMDSP, + .is_ramret = true, + }, + [DB8500_REGULATOR_SWITCH_SVAPIPE] = { + .desc = { + .name = "db8500-sva-pipe", + .id = DB8500_REGULATOR_SWITCH_SVAPIPE, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SVAPIPE, + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSP] = { + .desc = { + .name = "db8500-sia-mmdsp", + .id = DB8500_REGULATOR_SWITCH_SIAMMDSP, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SIAMMDSP, + }, + [DB8500_REGULATOR_SWITCH_SIAMMDSPRET] = { + .desc = { + .name = "db8500-sia-mmdsp-ret", + .id = DB8500_REGULATOR_SWITCH_SIAMMDSPRET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SIAMMDSP, + .is_ramret = true, + }, + [DB8500_REGULATOR_SWITCH_SIAPIPE] = { + .desc = { + .name = "db8500-sia-pipe", + .id = DB8500_REGULATOR_SWITCH_SIAPIPE, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SIAPIPE, + }, + [DB8500_REGULATOR_SWITCH_SGA] = { + .desc = { + .name = "db8500-sga", + .id = DB8500_REGULATOR_SWITCH_SGA, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_SGA, + }, + [DB8500_REGULATOR_SWITCH_B2R2_MCDE] = { + .desc = { + .name = "db8500-b2r2-mcde", + .id = DB8500_REGULATOR_SWITCH_B2R2_MCDE, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_B2R2_MCDE, + }, + [DB8500_REGULATOR_SWITCH_ESRAM12] = { + .desc = { + .name = "db8500-esram12", + .id = DB8500_REGULATOR_SWITCH_ESRAM12, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM12, + .is_enabled = true, + }, + [DB8500_REGULATOR_SWITCH_ESRAM12RET] = { + .desc = { + .name = "db8500-esram12-ret", + .id = DB8500_REGULATOR_SWITCH_ESRAM12RET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM12, + .is_ramret = true, + }, + [DB8500_REGULATOR_SWITCH_ESRAM34] = { + .desc = { + .name = "db8500-esram34", + .id = DB8500_REGULATOR_SWITCH_ESRAM34, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM34, + .is_enabled = true, + }, + [DB8500_REGULATOR_SWITCH_ESRAM34RET] = { + .desc = { + .name = "db8500-esram34-ret", + .id = DB8500_REGULATOR_SWITCH_ESRAM34RET, + .ops = &db8500_regulator_switch_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + .epod_id = EPOD_ID_ESRAM34, + .is_ramret = true, + }, +}; + +static int __init db8500_regulator_probe(struct platform_device *pdev) +{ + struct regulator_init_data *db8500_init_data = + dev_get_platdata(&pdev->dev); + int i, err; + + /* register all regulators */ + for (i = 0; i < ARRAY_SIZE(db8500_regulator_info); i++) { + struct db8500_regulator_info *info; + struct regulator_init_data *init_data = &db8500_init_data[i]; + + /* assign per-regulator data */ + info = &db8500_regulator_info[i]; + info->dev = &pdev->dev; + + /* register with the regulator framework */ + info->rdev = regulator_register(&info->desc, &pdev->dev, + init_data, info); + if (IS_ERR(info->rdev)) { + err = PTR_ERR(info->rdev); + dev_err(&pdev->dev, "failed to register %s: err %i\n", + info->desc.name, err); + + /* if failing, unregister all earlier regulators */ + i--; + while (i >= 0) { + info = &db8500_regulator_info[i]; + regulator_unregister(info->rdev); + i--; + } + return err; + } + + dev_vdbg(rdev_get_dev(info->rdev), + "regulator-%s-probed\n", info->desc.name); + } + + return 0; +} + +static int __exit db8500_regulator_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(db8500_regulator_info); i++) { + struct db8500_regulator_info *info; + info = &db8500_regulator_info[i]; + + dev_vdbg(rdev_get_dev(info->rdev), + "regulator-%s-remove\n", info->desc.name); + + regulator_unregister(info->rdev); + } + + return 0; +} + +static struct platform_driver db8500_regulator_driver = { + .driver = { + .name = "db8500-regulators", + .owner = THIS_MODULE, + }, + .remove = __exit_p(db8500_regulator_remove), +}; + +static int __init db8500_regulator_init(void) +{ + int ret; + + ret = platform_driver_probe(&db8500_regulator_driver, + db8500_regulator_probe); + if (ret < 0) { + pr_info("db8500_regulator: platform_driver_register fails\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit db8500_regulator_exit(void) +{ + platform_driver_unregister(&db8500_regulator_driver); +} + +arch_initcall(db8500_regulator_init); +module_exit(db8500_regulator_exit); + +MODULE_AUTHOR("STMicroelectronics/ST-Ericsson"); +MODULE_DESCRIPTION("DB8500 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-ux500/regulator-db8500.h b/arch/arm/mach-ux500/regulator-db8500.h new file mode 100644 index 00000000000..612062313b6 --- /dev/null +++ b/arch/arm/mach-ux500/regulator-db8500.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Author: Bengt Jonsson for ST-Ericsson + * + * Interface to power domain regulators on DB8500 + */ + +#ifndef __REGULATOR_H__ +#define __REGULATOR_H__ + +/* Number of DB8500 regulators and regulator enumeration */ +enum db8500_regulator_id { + DB8500_REGULATOR_VAPE, + DB8500_REGULATOR_VARM, + DB8500_REGULATOR_VMODEM, + DB8500_REGULATOR_VPLL, + DB8500_REGULATOR_VSMPS1, + DB8500_REGULATOR_VSMPS2, + DB8500_REGULATOR_VSMPS3, + DB8500_REGULATOR_VRF1, + DB8500_REGULATOR_SWITCH_SVAMMDSP, + DB8500_REGULATOR_SWITCH_SVAMMDSPRET, + DB8500_REGULATOR_SWITCH_SVAPIPE, + DB8500_REGULATOR_SWITCH_SIAMMDSP, + DB8500_REGULATOR_SWITCH_SIAMMDSPRET, + DB8500_REGULATOR_SWITCH_SIAPIPE, + DB8500_REGULATOR_SWITCH_SGA, + DB8500_REGULATOR_SWITCH_B2R2_MCDE, + DB8500_REGULATOR_SWITCH_ESRAM12, + DB8500_REGULATOR_SWITCH_ESRAM12RET, + DB8500_REGULATOR_SWITCH_ESRAM34, + DB8500_REGULATOR_SWITCH_ESRAM34RET, + DB8500_NUM_REGULATORS +}; + +/* + * Exported interface for CPUIdle only. This function is called with all + * interrupts turned off. + */ +int power_state_active_is_enabled(void); + +#endif -- cgit v1.2.3 From 0961cf37ad292ed92e894367d21a43520c847930 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 4 Apr 2011 10:44:51 +0200 Subject: mach-ux500: complete regulator constraints for MOP500 board This board now has complete regulation constraints and can turn off unused regulators. For the moment we need to wire VAUX1 (V-DISPLAY rail) always on since it somehow affects the external MMC. Cc: Liam Girdwood Cc: Mark Brown Signed-off-by: Linus Walleij --- arch/arm/mach-ux500/board-mop500-regulators.c | 176 -------------------------- 1 file changed, 176 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-regulators.c b/arch/arm/mach-ux500/board-mop500-regulators.c index c0bc833df90..f1a601d3e5e 100644 --- a/arch/arm/mach-ux500/board-mop500-regulators.c +++ b/arch/arm/mach-ux500/board-mop500-regulators.c @@ -87,182 +87,6 @@ static struct regulator_consumer_supply ab8500_vana_consumers[] = { }; /* ab8500 regulator register initialization */ -struct ab8500_regulator_reg_init -ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { - /* - * VanaRequestCtrl = HP/LP depending on VxRequest - * VextSupply1RequestCtrl = HP/LP depending on VxRequest - */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL2, 0x00), - /* - * VextSupply2RequestCtrl = HP/LP depending on VxRequest - * VextSupply3RequestCtrl = HP/LP depending on VxRequest - * Vaux1RequestCtrl = HP/LP depending on VxRequest - * Vaux2RequestCtrl = HP/LP depending on VxRequest - */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL3, 0x00), - /* - * Vaux3RequestCtrl = HP/LP depending on VxRequest - * SwHPReq = Control through SWValid disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL4, 0x00), - /* - * VanaSysClkReq1HPValid = disabled - * Vaux1SysClkReq1HPValid = disabled - * Vaux2SysClkReq1HPValid = disabled - * Vaux3SysClkReq1HPValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID1, 0x00), - /* - * VextSupply1SysClkReq1HPValid = disabled - * VextSupply2SysClkReq1HPValid = disabled - * VextSupply3SysClkReq1HPValid = SysClkReq1 controlled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID2, 0x40), - /* - * VanaHwHPReq1Valid = disabled - * Vaux1HwHPreq1Valid = disabled - * Vaux2HwHPReq1Valid = disabled - * Vaux3HwHPReqValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID1, 0x00), - /* - * VextSupply1HwHPReq1Valid = disabled - * VextSupply2HwHPReq1Valid = disabled - * VextSupply3HwHPReq1Valid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID2, 0x00), - /* - * VanaHwHPReq2Valid = disabled - * Vaux1HwHPReq2Valid = disabled - * Vaux2HwHPReq2Valid = disabled - * Vaux3HwHPReq2Valid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID1, 0x00), - /* - * VextSupply1HwHPReq2Valid = disabled - * VextSupply2HwHPReq2Valid = disabled - * VextSupply3HwHPReq2Valid = HWReq2 controlled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID2, 0x04), - /* - * VanaSwHPReqValid = disabled - * Vaux1SwHPReqValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID1, 0x00), - /* - * Vaux2SwHPReqValid = disabled - * Vaux3SwHPReqValid = disabled - * VextSupply1SwHPReqValid = disabled - * VextSupply2SwHPReqValid = disabled - * VextSupply3SwHPReqValid = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID2, 0x00), - /* - * SysClkReq2Valid1 = SysClkReq2 controlled - * SysClkReq3Valid1 = disabled - * SysClkReq4Valid1 = SysClkReq4 controlled - * SysClkReq5Valid1 = disabled - * SysClkReq6Valid1 = SysClkReq6 controlled - * SysClkReq7Valid1 = disabled - * SysClkReq8Valid1 = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID1, 0x2a), - /* - * SysClkReq2Valid2 = disabled - * SysClkReq3Valid2 = disabled - * SysClkReq4Valid2 = disabled - * SysClkReq5Valid2 = disabled - * SysClkReq6Valid2 = SysClkReq6 controlled - * SysClkReq7Valid2 = disabled - * SysClkReq8Valid2 = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID2, 0x20), - /* - * VTVoutEna = disabled - * Vintcore12Ena = disabled - * Vintcore12Sel = 1.25 V - * Vintcore12LP = inactive (HP) - * VTVoutLP = inactive (HP) - */ - INIT_REGULATOR_REGISTER(AB8500_REGUMISC1, 0x10), - /* - * VaudioEna = disabled - * VdmicEna = disabled - * Vamic1Ena = disabled - * Vamic2Ena = disabled - */ - INIT_REGULATOR_REGISTER(AB8500_VAUDIOSUPPLY, 0x00), - /* - * Vamic1_dzout = high-Z when Vamic1 is disabled - * Vamic2_dzout = high-Z when Vamic2 is disabled - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL1VAMIC, 0x00), - /* - * VPll = Hw controlled - * VanaRegu = force off - */ - INIT_REGULATOR_REGISTER(AB8500_VPLLVANAREGU, 0x02), - /* - * VrefDDREna = disabled - * VrefDDRSleepMode = inactive (no pulldown) - */ - INIT_REGULATOR_REGISTER(AB8500_VREFDDR, 0x00), - /* - * VextSupply1Regu = HW control - * VextSupply2Regu = HW control - * VextSupply3Regu = HW control - * ExtSupply2Bypass = ExtSupply12LPn ball is 0 when Ena is 0 - * ExtSupply3Bypass = ExtSupply3LPn ball is 0 when Ena is 0 - */ - INIT_REGULATOR_REGISTER(AB8500_EXTSUPPLYREGU, 0x2a), - /* - * Vaux1Regu = force HP - * Vaux2Regu = force off - */ - INIT_REGULATOR_REGISTER(AB8500_VAUX12REGU, 0x01), - /* - * Vaux3regu = force off - */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3REGU, 0x00), - /* - * Vsmps1 = 1.15V - */ - INIT_REGULATOR_REGISTER(AB8500_VSMPS1SEL1, 0x24), - /* - * Vaux1Sel = 2.5 V - */ - INIT_REGULATOR_REGISTER(AB8500_VAUX1SEL, 0x08), - /* - * Vaux2Sel = 2.9 V - */ - INIT_REGULATOR_REGISTER(AB8500_VAUX2SEL, 0x0d), - /* - * Vaux3Sel = 2.91 V - */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3SEL, 0x07), - /* - * VextSupply12LP = disabled (no LP) - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL2SPARE, 0x00), - /* - * Vaux1Disch = short discharge time - * Vaux2Disch = short discharge time - * Vaux3Disch = short discharge time - * Vintcore12Disch = short discharge time - * VTVoutDisch = short discharge time - * VaudioDisch = short discharge time - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH, 0x00), - /* - * VanaDisch = short discharge time - * VdmicPullDownEna = pulldown disabled when Vdmic is disabled - * VdmicDisch = short discharge time - */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH2, 0x00), -}; - -/* AB8500 regulators */ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { /* supplies to the display/camera */ [AB8500_LDO_AUX1] = { -- cgit v1.2.3 From 45a39ca6118d3fdce236561d13c77738025501d0 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 18 Mar 2011 15:32:22 +0100 Subject: misc: Add audio_io_dev driver Signed-off-by: Robert Marklund --- .../mach-ux500/include/mach/ste_audio_io_ioctl.h | 224 + .../include/mach/ste_audio_io_vibrator.h | 37 + drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/audio_io_dev/Kconfig | 11 + drivers/misc/audio_io_dev/Makefile | 9 + .../audio_io_dev/ste_audio_io_ab8500_reg_defs.h | 349 ++ drivers/misc/audio_io_dev/ste_audio_io_core.c | 1464 +++++++ drivers/misc/audio_io_dev/ste_audio_io_core.h | 133 + drivers/misc/audio_io_dev/ste_audio_io_dev.c | 738 ++++ drivers/misc/audio_io_dev/ste_audio_io_dev.h | 32 + drivers/misc/audio_io_dev/ste_audio_io_func.c | 4371 ++++++++++++++++++++ drivers/misc/audio_io_dev/ste_audio_io_func.h | 359 ++ .../misc/audio_io_dev/ste_audio_io_hwctrl_common.c | 189 + .../misc/audio_io_dev/ste_audio_io_hwctrl_common.h | 50 + 15 files changed, 7968 insertions(+) create mode 100644 arch/arm/mach-ux500/include/mach/ste_audio_io_ioctl.h create mode 100644 arch/arm/mach-ux500/include/mach/ste_audio_io_vibrator.h create mode 100644 drivers/misc/audio_io_dev/Kconfig create mode 100644 drivers/misc/audio_io_dev/Makefile create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_ab8500_reg_defs.h create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_core.c create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_core.h create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_dev.c create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_dev.h create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_func.c create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_func.h create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.c create mode 100644 drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.h diff --git a/arch/arm/mach-ux500/include/mach/ste_audio_io_ioctl.h b/arch/arm/mach-ux500/include/mach/ste_audio_io_ioctl.h new file mode 100644 index 00000000000..73dc9d9ee7e --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ste_audio_io_ioctl.h @@ -0,0 +1,224 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#ifndef _AUDIOIO_IOCTL_H_ +#define _AUDIOIO_IOCTL_H_ + + +#define AUDIOIO_IOC_MAGIC 'N' +#define AUDIOIO_READ_REGISTER _IOWR(AUDIOIO_IOC_MAGIC, 1,\ + struct audioio_data_t) +#define AUDIOIO_WRITE_REGISTER _IOW(AUDIOIO_IOC_MAGIC, 2,\ + struct audioio_data_t) +#define AUDIOIO_PWR_CTRL_TRNSDR _IOW(AUDIOIO_IOC_MAGIC, 3,\ + struct audioio_pwr_ctrl_t) +#define AUDIOIO_PWR_STS_TRNSDR _IOR(AUDIOIO_IOC_MAGIC, 4,\ + struct audioio_pwr_ctrl_t) +#define AUDIOIO_LOOP_CTRL _IOW(AUDIOIO_IOC_MAGIC, 5,\ + struct audioio_loop_ctrl_t) +#define AUDIOIO_LOOP_STS _IOR(AUDIOIO_IOC_MAGIC, 6,\ + struct audioio_loop_ctrl_t) +#define AUDIOIO_GET_TRNSDR_GAIN_CAPABILITY _IOR(AUDIOIO_IOC_MAGIC, 7,\ + struct audioio_get_gain_t) +#define AUDIOIO_GAIN_CAP_LOOP _IOR(AUDIOIO_IOC_MAGIC, 8,\ + struct audioio_gain_loop_t) +#define AUDIOIO_SUPPORT_LOOP _IOR(AUDIOIO_IOC_MAGIC, 9,\ + struct audioio_support_loop_t) +#define AUDIOIO_GAIN_DESC_TRNSDR _IOR(AUDIOIO_IOC_MAGIC, 10,\ + struct audioio_gain_desc_trnsdr_t) +#define AUDIOIO_GAIN_CTRL_TRNSDR _IOW(AUDIOIO_IOC_MAGIC, 11,\ + struct audioio_gain_ctrl_trnsdr_t) +#define AUDIOIO_GAIN_QUERY_TRNSDR _IOR(AUDIOIO_IOC_MAGIC, 12,\ + struct audioio_gain_ctrl_trnsdr_t) +#define AUDIOIO_MUTE_CTRL_TRNSDR _IOW(AUDIOIO_IOC_MAGIC, 13,\ + struct audioio_mute_trnsdr_t) +#define AUDIOIO_MUTE_STS_TRNSDR _IOR(AUDIOIO_IOC_MAGIC, 14,\ + struct audioio_mute_trnsdr_t) +#define AUDIOIO_FADE_CTRL _IOW(AUDIOIO_IOC_MAGIC, 15,\ + struct audioio_fade_ctrl_t) +#define AUDIOIO_BURST_CTRL _IOW(AUDIOIO_IOC_MAGIC, 16,\ + struct audioio_burst_ctrl_t) +#define AUDIOIO_READ_ALL_ACODEC_REGS_CTRL _IOW(AUDIOIO_IOC_MAGIC, 17,\ + struct audioio_read_all_acodec_reg_ctrl_t) +#define AUDIOIO_FSBITCLK_CTRL _IOW(AUDIOIO_IOC_MAGIC, 18,\ + struct audioio_fsbitclk_ctrl_t) +#define AUDIOIO_PSEUDOBURST_CTRL _IOW(AUDIOIO_IOC_MAGIC, 19,\ + struct audioio_pseudoburst_ctrl_t) +#define AUDIOIO_AUDIOCODEC_PWR_CTRL _IOW(AUDIOIO_IOC_MAGIC, 20, \ + struct audioio_acodec_pwr_ctrl_t) +#define AUDIOIO_FIR_COEFFS_CTRL _IOW(AUDIOIO_IOC_MAGIC, 21, \ + struct audioio_fir_coefficients_t) +#define AUDIOIO_LOOP_GAIN_DESC_TRNSDR _IOR(AUDIOIO_IOC_MAGIC, 22,\ + struct audioio_gain_desc_trnsdr_t) +/* audio codec channel ids */ +#define EAR_CH 0 +#define HS_CH 1 +#define IHF_CH 2 +#define VIBL_CH 3 +#define VIBR_CH 4 +#define MIC1A_CH 5 +#define MIC1B_CH 6 +#define MIC2_CH 7 +#define LIN_CH 8 +#define DMIC12_CH 9 +#define DMIC34_CH 10 +#define DMIC56_CH 11 +#define MULTI_MIC_CH 12 +#define FMRX_CH 13 +#define FMTX_CH 14 +#define BLUETOOTH_CH 15 + +#define FIRST_CH EAR_CH +#define LAST_CH BLUETOOTH_CH + +#define MAX_NO_TRANSDUCERS 16 +#define STE_AUDIOIO_MAX_COEFFICIENTS 128 +#define MAX_NO_OF_LOOPS 19 + +#define AUDIOIO_TRUE 1 +#define AUDIOIO_FALSE 0 + +enum AUDIOIO_COMMON_SWITCH { + AUDIOIO_COMMON_OFF = 0, + AUDIOIO_COMMON_ON, + AUDIOIO_COMMON_ALLCHANNEL_UNSUPPORTED = 0xFFFF +}; + +enum AUDIOIO_HAL_HW_LOOPS { + AUDIOIO_NO_LOOP = 0x0, + AUDIOIO_SIDETONE_LOOP = 0x01, + AUDIOIO_MIC1B_TO_HFL = 0x02, + AUDIOIO_MIC1B_TO_HFR = 0x04, + AUDIOIO_MIC1B_TO_EAR = 0x08, + AUDIOIO_MIC1A_TO_HSL = 0x10, + AUDIOIO_MIC1A_TO_HSR = 0x20, + AUDIOIO_MIC1A_TO_HSR_HSL = 0x40, + AUDIOIO_LINEIN_TO_HF = 0x80, + AUDIOIO_DMIC12_TO_HSR_HSL = 0x100, + AUDIOIO_DIC34_TO_HSR_HSL = 0x200, + AUDIOIO_DIC56_TO_HSR_HSL = 0x400, + AUDIOIO_DMIC12_TO_ST = 0x800, + AUDIOIO_DMIC34_TO_ST = 0x1000, + AUDIOIO_DMIC56_TO_ST = 0x2000, + AUDIOIO_ANC_LOOP = 0x4000, + AUDIOIO_LININ_HS = 0x8000, + AUDIOIO_LININL_HSL = 0x10000, + AUDIOIO_LININ_HSR = 0x20000 +}; + + +enum AUDIOIO_FADE_PERIOD { + e_FADE_00, + e_FADE_01, + e_FADE_10, + e_FADE_11 +}; + +enum AUDIOIO_CH_INDEX { + e_CHANNEL_1 = 0x01, + e_CHANNEL_2 = 0x02, + e_CHANNEL_3 = 0x04, + e_CHANNEL_4 = 0x08, + e_CHANNEL_ALL = 0x0f +}; + +struct audioio_data_t { + unsigned char block; + unsigned char addr; + unsigned char data; +}; + +struct audioio_pwr_ctrl_t { + enum AUDIOIO_COMMON_SWITCH ctrl_switch; + int channel_type; + enum AUDIOIO_CH_INDEX channel_index; +}; + +struct audioio_acodec_pwr_ctrl_t { + enum AUDIOIO_COMMON_SWITCH ctrl_switch; +}; + +struct audioio_loop_ctrl_t { + enum AUDIOIO_HAL_HW_LOOPS hw_loop; + enum AUDIOIO_COMMON_SWITCH ctrl_switch; + int channel_type; + enum AUDIOIO_CH_INDEX channel_index; + int loop_gain; +}; + +struct audioio_get_gain_t { + unsigned int num_channels; + unsigned short max_num_gain; +}; + +struct audioio_gain_loop_t { + int channel_type; + unsigned short num_loop; + unsigned short max_gains; +}; + +struct audioio_support_loop_t { + int channel_type; + unsigned short spprtd_loop_index; +}; + +struct audioio_gain_desc_trnsdr_t { + enum AUDIOIO_CH_INDEX channel_index; + int channel_type; + unsigned short gain_index; + int min_gain; + int max_gain; + unsigned int gain_step; +}; + +struct audioio_gain_ctrl_trnsdr_t { + enum AUDIOIO_CH_INDEX channel_index; + int channel_type; + unsigned short gain_index; + int gain_value; + unsigned int linear; +}; + +struct audioio_mute_trnsdr_t { + int channel_type; + enum AUDIOIO_CH_INDEX channel_index; + enum AUDIOIO_COMMON_SWITCH ctrl_switch; +}; + +struct audioio_fade_ctrl_t { + enum AUDIOIO_COMMON_SWITCH ctrl_switch; + enum AUDIOIO_FADE_PERIOD fade_period; + int channel_type; + enum AUDIOIO_CH_INDEX channel_index; +}; + +struct audioio_burst_ctrl_t { + enum AUDIOIO_COMMON_SWITCH ctrl_switch; + int channel_type; + int burst_fifo_interrupt_sample_count; + int burst_fifo_length;/* BFIFOTx */ + int burst_fifo_switch_frame; + int burst_fifo_sample_number; +}; + +struct audioio_read_all_acodec_reg_ctrl_t { + unsigned char data[200]; +}; + +struct audioio_fsbitclk_ctrl_t { + enum AUDIOIO_COMMON_SWITCH ctrl_switch; +}; + +struct audioio_pseudoburst_ctrl_t { + enum AUDIOIO_COMMON_SWITCH ctrl_switch; +}; + +struct audioio_fir_coefficients_t { + unsigned char start_addr; + unsigned short coefficients[STE_AUDIOIO_MAX_COEFFICIENTS]; +}; + +#endif diff --git a/arch/arm/mach-ux500/include/mach/ste_audio_io_vibrator.h b/arch/arm/mach-ux500/include/mach/ste_audio_io_vibrator.h new file mode 100644 index 00000000000..6b6a558e90a --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ste_audio_io_vibrator.h @@ -0,0 +1,37 @@ +/* +* Overview: +* Header File defining vibrator kernel space interface +* +* Copyright (C) 2010 ST Ericsson +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +*/ + +#ifndef _STE_AUDIO_IO_VIBRATOR_H_ +#define _STE_AUDIO_IO_VIBRATOR_H_ + +/* Client definitions which can use vibrator, defined as bitmask */ +#define STE_AUDIOIO_CLIENT_AUDIO_L 1 +#define STE_AUDIOIO_CLIENT_AUDIO_R 2 +#define STE_AUDIOIO_CLIENT_FF_VIBRA 4 +#define STE_AUDIOIO_CLIENT_TIMED_VIBRA 8 + +/* + * Define vibrator's maximum speed allowed + * Duty cycle supported by vibrator's PWM is 0-100 + */ +#define STE_AUDIOIO_VIBRATOR_MAX_SPEED 100 + +/* Vibrator speed structure */ +struct ste_vibra_speed { + unsigned char positive; + unsigned char negative; +}; + +/* Vibrator control function - uses PWM source */ +int ste_audioio_vibrator_pwm_control(int client, + struct ste_vibra_speed left_speed, struct ste_vibra_speed right_speed); + +#endif diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4e349cd98bc..4cc1da58e80 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -497,5 +497,6 @@ source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" +source "drivers/misc/audio_io_dev/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 5f03172cc0b..1aa09309fc4 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -46,3 +46,4 @@ obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ +obj-$(CONFIG_STE_AUDIO_IO_DEV) += audio_io_dev/ diff --git a/drivers/misc/audio_io_dev/Kconfig b/drivers/misc/audio_io_dev/Kconfig new file mode 100644 index 00000000000..57bb77172f7 --- /dev/null +++ b/drivers/misc/audio_io_dev/Kconfig @@ -0,0 +1,11 @@ +# +# AB8500 Audio IO Device Driver configuration +# +config STE_AUDIO_IO_DEV + bool "AB8500 Audio IO device driver" + depends on ARCH_U8500 && AB8500_CORE && STM_MSP_I2S + default y + ---help--- + If you say Y here, you will enable the AB8500 Audio IO device driver. + + If unsure, say N. diff --git a/drivers/misc/audio_io_dev/Makefile b/drivers/misc/audio_io_dev/Makefile new file mode 100644 index 00000000000..44b21fcc573 --- /dev/null +++ b/drivers/misc/audio_io_dev/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for AB8500 device drivers +# +obj-$(CONFIG_STE_AUDIO_IO_DEV) += ste_audio_io.o +ste_audio_io-objs := ste_audio_io_dev.o\ + ste_audio_io_core.o\ + ste_audio_io_func.o\ + ste_audio_io_hwctrl_common.o + diff --git a/drivers/misc/audio_io_dev/ste_audio_io_ab8500_reg_defs.h b/drivers/misc/audio_io_dev/ste_audio_io_ab8500_reg_defs.h new file mode 100644 index 00000000000..1436430f7de --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_ab8500_reg_defs.h @@ -0,0 +1,349 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + + +#ifndef _AUDIOIO_REG_DEFS_H_ +#define _AUDIOIO_REG_DEFS_H_ + + + /* Registers */ +#define POWER_UP_CONTROL_REG 0x0D00 +#define SOFTWARE_RESET_REG 0x0D01 +#define DIGITAL_AD_CHANNELS_ENABLE_REG 0x0D02 +#define DIGITAL_DA_CHANNELS_ENABLE_REG 0x0D03 +#define LOW_POWER_HS_EAR_CONF_REG 0x0D04 +#define LINE_IN_MIC_CONF_REG 0x0D05 +#define DMIC_ENABLE_REG 0x0D06 +#define ADC_DAC_ENABLE_REG 0x0D07 +#define ANALOG_OUTPUT_ENABLE_REG 0x0D08 +#define DIGITAL_OUTPUT_ENABLE_REG 0x0D09 +#define MUTE_HS_EAR_REG 0x0D0A +#define SHORT_CIRCUIT_DISABLE_REG 0x0D0B +#define NCP_ENABLE_HS_AUTOSTART_REG 0x0D0C +#define ENVELOPE_THRESHOLD_REG 0x0D0D +#define ENVELOPE_DECAY_TIME_REG 0x0D0E +#define VIB_DRIVER_CONF_REG 0x0D0F +#define PWM_VIBNL_CONF_REG 0x0D10 +#define PWM_VIBPL_CONF_REG 0x0D11 +#define PWM_VIBNR_CONF_REG 0x0D12 +#define PWM_VIBPR_CONF_REG 0x0D13 +#define ANALOG_MIC1_GAIN_REG 0x0D14 +#define ANALOG_MIC2_GAIN_REG 0x0D15 +#define ANALOG_HS_GAIN_REG 0x0D16 +#define ANALOG_LINE_IN_GAIN_REG 0x0D17 +#define LINE_IN_TO_HSL_GAIN_REG 0x0D18 +#define LINE_IN_TO_HSR_GAIN_REG 0x0D19 +#define AD_FILTER_CONF_REG 0x0D1A +#define IF0_IF1_MASTER_CONF_REG 0x0D1B +#define IF0_CONF_REG 0x0D1C +#define TDM_IF_BYPASS_B_FIFO_REG 0x0D1D +#define IF1_CONF_REG 0x0D1E +#define AD_ALLOCATION_TO_SLOT0_1_REG 0x0D1F +#define AD_ALLOCATION_TO_SLOT2_3_REG 0x0D20 +#define AD_ALLOCATION_TO_SLOT4_5_REG 0x0D21 +#define AD_ALLOCATION_TO_SLOT6_7_REG 0x0D22 +#define AD_ALLOCATION_TO_SLOT8_9_REG 0x0D23 +#define AD_ALLOCATION_TO_SLOT10_11_REG 0x0D24 +#define AD_ALLOCATION_TO_SLOT12_13_REG 0x0D25 +#define AD_ALLOCATION_TO_SLOT14_15_REG 0x0D26 +#define AD_ALLOCATION_TO_SLOT16_17_REG 0x0D27 +#define AD_ALLOCATION_TO_SLOT18_19_REG 0x0D28 +#define AD_ALLOCATION_TO_SLOT20_21_REG 0x0D29 +#define AD_ALLOCATION_TO_SLOT22_23_REG 0x0D2A +#define AD_ALLOCATION_TO_SLOT24_25_REG 0x0D2B +#define AD_ALLOCATION_TO_SLOT26_27_REG 0x0D2C +#define AD_ALLOCATION_TO_SLOT28_29_REG 0x0D2D +#define AD_ALLOCATION_TO_SLOT30_31_REG 0x0D2E +#define AD_SLOT_0_TO_7_TRISTATE_REG 0x0D2F +#define AD_SLOT_8_TO_15_TRISTATE_REG 0x0D30 +#define AD_SLOT_16_TO_23_TRISTATE_REG 0x0D31 +#define AD_SLOT_24_TO_31_TRISTATE_REG 0x0D32 +#define SLOT_SELECTION_TO_DA1_REG 0x0D33 +#define SLOT_SELECTION_TO_DA2_REG 0x0D34 +#define SLOT_SELECTION_TO_DA3_REG 0x0D35 +#define SLOT_SELECTION_TO_DA4_REG 0x0D36 +#define SLOT_SELECTION_TO_DA5_REG 0x0D37 +#define SLOT_SELECTION_TO_DA6_REG 0x0D38 +#define SLOT_SELECTION_TO_DA7_REG 0x0D39 +#define SLOT_SELECTION_TO_DA8_REG 0x0D3A +#define CLASS_D_EMI_PARALLEL_CONF_REG 0x0D3B +#define CLASS_D_PATH_CONTROL_REG 0x0D3C +#define CLASS_D_DITHER_CONTROL_REG 0x0D3D +#define DMIC_DECIMATOR_FILTER_REG 0x0D3E +#define DIGITAL_MUXES_REG1 0x0D3F +#define DIGITAL_MUXES_REG2 0x0D40 +#define AD1_DIGITAL_GAIN_REG 0x0D41 +#define AD2_DIGITAL_GAIN_REG 0x0D42 +#define AD3_DIGITAL_GAIN_REG 0x0D43 +#define AD4_DIGITAL_GAIN_REG 0x0D44 +#define AD5_DIGITAL_GAIN_REG 0x0D45 +#define AD6_DIGITAL_GAIN_REG 0x0D46 +#define DA1_DIGITAL_GAIN_REG 0x0D47 +#define DA2_DIGITAL_GAIN_REG 0x0D48 +#define DA3_DIGITAL_GAIN_REG 0x0D49 +#define DA4_DIGITAL_GAIN_REG 0x0D4A +#define DA5_DIGITAL_GAIN_REG 0x0D4B +#define DA6_DIGITAL_GAIN_REG 0x0D4C +#define AD1_TO_HFL_DIGITAL_GAIN_REG 0x0D4D +#define AD2_TO_HFR_DIGITAL_GAIN_REG 0x0D4E +#define HSL_EAR_DIGITAL_GAIN_REG 0x0D4F +#define HSR_DIGITAL_GAIN_REG 0x0D50 +#define SIDETONE_FIR1_GAIN_REG 0x0D51 +#define SIDETONE_FIR2_GAIN_REG 0x0D52 +#define ANC_FILTER_CONTROL_REG 0x0D53 +#define ANC_WARPED_GAIN_REG 0x0D54 +#define ANC_FIR_OUTPUT_GAIN_REG 0x0D55 +#define ANC_IIR_OUTPUT_GAIN_REG 0x0D56 +#define ANC_FIR_COEFF_MSB_REG 0x0D57 +#define ANC_FIR_COEFF_LSB_REG 0x0D58 +#define ANC_IIR_COEFF_MSB_REG 0x0D59 +#define ANC_IIR_COEFF_LSB_REG 0x0D5A +#define ANC_WARP_DELAY_MSB_REG 0x0D5B +#define ANC_WARP_DELAY_LSB_REG 0x0D5C +#define ANC_FIR_PEAK_MSB_REG 0x0D5D +#define ANC_FIR_PEAK_LSB_REG 0x0D5E +#define ANC_IIR_PEAK_MSB_REG 0x0D5F +#define ANC_IIR_PEAK_LSB_REG 0x0D60 +#define SIDETONE_FIR_ADDR_REG 0x0D61 +#define SIDETONE_FIR_COEFF_MSB_REG 0x0D62 +#define SIDETONE_FIR_COEFF_LSB_REG 0x0D63 +#define FILTERS_CONTROL_REG 0x0D64 +#define IRQ_MASK_LSB_REG 0x0D65 +#define IRQ_STATUS_LSB_REG 0x0D66 +#define IRQ_MASK_MSB_REG 0x0D67 +#define IRQ_STATUS_MSB_REG 0x0D68 +#define BURST_FIFO_INT_CONTROL_REG 0x0D69 +#define BURST_FIFO_LENGTH_REG 0x0D6A +#define BURST_FIFO_CONTROL_REG 0x0D6B +#define BURST_FIFO_SWITCH_FRAME_REG 0x0D6C +#define BURST_FIFO_WAKE_UP_DELAY_REG 0x0D6D +#define BURST_FIFO_SAMPLES_REG 0x0D6E +#define REVISION_REG 0x0D6F + +/* POWER_UP_CONTROL_REG Masks */ +#define DEVICE_POWER_UP 0x80 +#define ANALOG_PARTS_POWER_UP 0x08 + +/* SOFTWARE_RESET_REG Masks */ +#define SW_RESET 0x80 + +/* DIGITAL_AD_CHANNELS_ENABLE_REG Masks */ +#define EN_AD1 0x80 +#define EN_AD2 0x80 +#define EN_AD3 0x20 +#define EN_AD4 0x20 +#define EN_AD5 0x08 +#define EN_AD6 0x04 + +/* DIGITAL_DA_CHANNELS_ENABLE_REG Masks */ +#define EN_DA1 0x80 +#define EN_DA2 0x40 +#define EN_DA3 0x20 +#define EN_DA4 0x10 +#define EN_DA5 0x08 +#define EN_DA6 0x04 + +/* LOW_POWER_HS_EAR_CONF_REG Masks */ +#define LOW_POWER_HS 0x80 +#define HS_DAC_DRIVER_LP 0x40 +#define HS_DAC_LP 0x20 +#define EAR_DAC_LP 0x10 + +/* LINE_IN_MIC_CONF_REG Masks */ +#define EN_MIC1 0x80 +#define EN_MIC2 0x40 +#define EN_LIN_IN_L 0x20 +#define EN_LIN_IN_R 0x10 +#define MUT_MIC1 0x08 +#define MUT_MIC2 0x04 +#define MUT_LIN_IN_L 0x02 +#define MUT_LIN_IN_R 0x01 + +/* DMIC_ENABLE_REG Masks */ +#define EN_DMIC1 0x80 +#define EN_DMIC2 0x40 +#define EN_DMIC3 0x20 +#define EN_DMIC4 0x10 +#define EN_DMIC5 0x08 +#define EN_DMIC6 0x04 + +/* ADC_DAC_ENABLE_REG Masks */ +#define SEL_MIC1B_CLR_MIC1A 0x80 +#define SEL_LINR_CLR_MIC2 0x40 +#define POWER_UP_HSL_DAC 0x20 +#define POWER_UP_HSR_DAC 0x10 +#define POWER_UP_ADC1 0x04 +#define POWER_UP_ADC3 0x02 +#define POWER_UP_ADC2 0x01 + +/* ANALOG_OUTPUT_ENABLE_REG and DIGITAL_OUTPUT_ENABLE_REG and + MUTE_HS_EAR_REG Masks */ +#define EN_EAR_DAC_MASK 0x04 +#define EN_HSL_DAC_MASK 0x02 +#define EN_HSR_DAC_MASK 0x01 +#define EN_EAR_MASK 0x40 +#define EN_HSL_MASK 0x20 +#define EN_HSR_MASK 0x10 +#define EN_HFL_MASK 0x08 +#define EN_HFR_MASK 0x04 +#define EN_VIBL_MASK 0x02 +#define EN_VIBR_MASK 0x01 + +/* SHORT_CIRCUIT_DISABLE_REG Masks */ +#define HS_SHORT_DIS 0x20 +#define HS_PULL_DOWN_EN 0x10 +#define HS_OSC_EN 0x04 +#define DIS_HS_FAD 0x02 +#define HS_ZCD_DIS 0x01 + +/* NCP_ENABLE_HS_AUTOSTART_REG Masks */ +#define EN_NEG_CP 0x80 +#define HS_AUTO_EN 0x01 + +/* ANALOG_MIC1_GAIN_REG and ANALOG_MIC1_GAIN_REG Masks */ +#define MIC_ANALOG_GAIN_MASK 0x1F + +/*ANALOG_HS_GAIN_REG and ANALOG_LINE_IN_GAIN_REG Masks*/ +#define L_ANALOG_GAIN_MASK 0xF0 +#define R_ANALOG_GAIN_MASK 0x0F + +/* IF0_IF1_MASTER_CONF_REG Masks */ +#define EN_MASTGEN 0x80 +#define BITCLK_OSR_N_64 0x02 +#define BITCLK_OSR_N_128 0x04 +#define BITCLK_OSR_N_256 0x06 +#define EN_FSYNC_BITCLK 0x01 +#define EN_FSYNC_BITCLK1 0x10 + +/* IF0_CONF_REG and IF1_CONF_REG Masks */ +#define FSYNC_FALLING_EDGE 0x40 +#define BITCLK_FALLING_EDGE 0x20 +#define IF_DELAYED 0x10 +#define I2S_LEFT_ALIGNED_FORMAT 0x08 +#define TDM_FORMAT 0x04 +#define WORD_LENGTH_32 0x03 +#define WORD_LENGTH_24 0x02 +#define WORD_LENGTH_20 0x01 +#define WORD_LENGTH_16 0x00 + +/* TDM_IF_BYPASS_B_FIFO_REG Masks */ +#define IF0_BFifoEn 0x01 +#define IF0_MASTER 0x02 + +#define IF1_MASTER 0x20 +/* + * AD_ALLOCATION_TO_SLOT0_1_REG and AD_ALLOCATION_TO_SLOT2_3_REG and + * AD_ALLOCATION_TO_SLOT4_5_REG and AD_ALLOCATION_TO_SLOT6_7_REG Masks + */ +#define DATA_FROM_AD_OUT1 0x00 +#define DATA_FROM_AD_OUT2 0x01 +#define DATA_FROM_AD_OUT3 0x02 +#define DATA_FROM_AD_OUT4 0x03 +#define DATA_FROM_AD_OUT5 0x04 +#define DATA_FROM_AD_OUT6 0x05 +#define DATA_FROM_AD_OUT7 0x06 +#define DATA_FROM_AD_OUT8 0x07 +#define TRISTATE 0x0C + +/* + * SLOT_SELECTION_TO_DA1_REG and SLOT_SELECTION_TO_DA2_REG and + * SLOT_SELECTION_TO_DA3_REG and SLOT_SELECTION_TO_DA4_REG Masks + * SLOT_SELECTION_TO_DA5_REG and SLOT_SELECTION_TO_DA6_REG Masks + */ +#define SLOT08_FOR_DA_PATH 0x08 +#define SLOT09_FOR_DA_PATH 0x09 +#define SLOT10_FOR_DA_PATH 0x0A +#define SLOT11_FOR_DA_PATH 0x0B +#define SLOT12_FOR_DA_PATH 0x0C +#define SLOT13_FOR_DA_PATH 0x0D +#define SLOT14_FOR_DA_PATH 0x0E +#define SLOT15_FOR_DA_PATH 0x0F + +/* DIGITAL_MUXES_REG1 Masks */ +#define DA1_TO_HSL 0x80 +#define DA2_TO_HSR 0x40 +#define SEL_DMIC1_FOR_AD_OUT1 0x20 +#define SEL_DMIC2_FOR_AD_OUT2 0x10 +#define SEL_DMIC3_FOR_AD_OUT3 0x08 +/*#define SEL_DMIC5_FOR_AD_OUT5 0x04*/ +/*#define SEL_DMIC6_FOR_AD_OUT6 0x02*/ +/*#define SEL_DMIC1_FOR_AD_OUT1 0x01*/ + +/* + * AD1_DIGITAL_GAIN_REG and AD2_DIGITAL_GAIN_REG & AD3_DIGITAL_GAIN_REG Masks + * AD4_DIGITAL_GAIN_REG and AD5_DIGITAL_GAIN_REG & AD6_DIGITAL_GAIN_REG Masks + * DA1_DIGITAL_GAIN_REG and DA2_DIGITAL_GAIN_REG & DA3_DIGITAL_GAIN_REG Masks + * DA4_DIGITAL_GAIN_REG and DA5_DIGITAL_GAIN_REG & DA6_DIGITAL_GAIN_REG Masks + */ +#define DIS_FADING 0x40 +#define DIGITAL_GAIN_MASK 0x3F + +/* + * HSL_EAR_DIGITAL_GAIN_REG and HSR_DIGITAL_GAIN_REG Masks + */ +#define FADE_SPEED_MASK 0xC0 +#define DIS_DIG_GAIN_FADING 0x10 +#define HS_DIGITAL_GAIN_MASK 0x0F + +/* FMRx/FMTx Masks */ +#define SLOT24_FOR_DA_PATH 0x18 +#define SEL_AD_OUT8_FROM_DAIN7 0x20 +#define SLOT25_FOR_DA_PATH 0x19 +#define SEL_AD_OUT6_FROM_DAIN8 0x20 +#define SEL_IF8_FROM_AD_OUT7 0x60 +#define SEL_IF17_FROM_AD_OUT7 0x60 +#define SEL_IF16_FROM_AD_OUT8 0x07 + +#define SEL_IF6_FROM_AD_OUT5 0x04 +#define SEL_IF7_FROM_AD_OUT6 0x50 +#define SEL_IF17_FROM_AD_OUT6 0x50 +#define SEL_AD_OUT5_FROM_DAIN7 0x20 + +/* Burst FIFO Control Masks */ +#define WAKEUP_SIGNAL_SAMPLE_COUNT 0x1B +#define BURST_FIFO_TRANSFER_LENGTH 0xC0 +#define BURST_FIFO_INF_RUNNING 0x01 +#define BURST_FIFO_INF_IN_MASTER_MODE 0x02 +#define PRE_BIT_CLK0_COUNT 0x1C +#define BURST_FIFO_WAKUP_DEALAY 0x70 + +/* Filter Control Masks */ +/* SideTone Masks */ +#define SIDETONE_DIGITAL_GAIN_MASK 0x1F +#define FIR1_FROMAD1 0x0C +#define FIR1_FROMAD2 0x03 +#define FIR1_FROMAD3 0x08 +#define FIR1_DAIN1 0x0C + +#define FIR2_FROMAD2 0x00 +#define FIR2_FROMAD3 0x01 +#define FIR2_FROMAD4 0x02 +#define FIR2_DAIN2 0x03 + +#define FIR2_ANDFIR1AD3 0x09 +#define FIR_FILTERCONTROL 0x04 +#define APPLY_FIR_COEFFS_MASK 0x80 + +/* IRQ status masks */ +#define NCP_READY_MASK 0x80 + +/* AB8500 power control Masks */ +#define AB8500_VER_1_0 0x10 +#define AB8500_VER_1_1 0x11 +#define CLK_32K_OUT2_DISABLE 0x01 +#define INACTIVE_RESET_AUDIO 0x02 +#define AB8500_REQ_SYS_CLK 0x08 +#define ENABLE_AUDIO_CLK_TO_AUDIO_BLK 0x10 +#define ENABLE_VINTCORE12_SUPPLY 0x04 +#define VAMIC2_ENABLE 0x10 +#define VAMIC1_ENABLE 0x08 +#define VDMIC_ENABLE 0x04 +#define VAUDIO_ENABLE 0x02 +#define GPIO27_DIR_OUTPUT 0x04 +#define GPIO29_DIR_OUTPUT 0x10 +#define GPIO31_DIR_OUTPUT 0x40 +#define GPIO35_DIR_OUTPUT 0X04 +#endif diff --git a/drivers/misc/audio_io_dev/ste_audio_io_core.c b/drivers/misc/audio_io_dev/ste_audio_io_core.c new file mode 100644 index 00000000000..129cd041e24 --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_core.c @@ -0,0 +1,1464 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#include +#include +#include +#include +#include +#include + +#include "ste_audio_io_core.h" +#include "ste_audio_io_hwctrl_common.h" +#include "ste_audio_io_ab8500_reg_defs.h" + +static struct audiocodec_context_t *ptr_audio_codec_cnxt; + +static struct clk *clk_ptr_msp1; +static struct clk *clk_ptr_msp3; +static struct clk *clk_ptr_sysclk; + +static struct regulator *regulator_vdmic; +static struct regulator *regulator_vaudio; +static struct regulator *regulator_vamic1; +static struct regulator *regulator_vamic2; +struct regulator *regulator_avsource; +static void ste_audio_io_init_transducer_cnxt(void); + +bool ste_audio_io_core_is_ready_for_suspend() +{ + bool err = false; + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + if ((!ptr_audio_codec_cnxt->power_client) && + (!ptr_audio_codec_cnxt->audio_codec_powerup)) + err = true; + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + return err; +} +int ste_audio_io_core_api_init_data(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_platform_data *pdata = dev_get_platdata(ab8500->dev); + int status = 0; + ptr_audio_codec_cnxt = kmalloc(sizeof(struct audiocodec_context_t), + GFP_KERNEL); + if (!ptr_audio_codec_cnxt) + return -ENOMEM; + + memset(ptr_audio_codec_cnxt, 0, sizeof(*ptr_audio_codec_cnxt)); + ptr_audio_codec_cnxt->dev = &pdev->dev; + mutex_init(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + if (pdata) { + if (pdata->audio) { + ptr_audio_codec_cnxt->gpio_altf_init = + pdata->audio->ste_gpio_altf_init; + ptr_audio_codec_cnxt->gpio_altf_exit = + pdata->audio->ste_gpio_altf_exit; + } + } + + regulator_vdmic = regulator_get(NULL, "v-dmic"); + if (IS_ERR(regulator_vdmic)) { + status = PTR_ERR(regulator_vdmic); + dev_err(ptr_audio_codec_cnxt->dev, + "Register error for v-dmic=%d", status); + goto free_audio_codec_cnxt; + } + regulator_vamic1 = regulator_get(NULL, "v-amic1"); + if (IS_ERR(regulator_vamic1)) { + status = PTR_ERR(regulator_vamic1); + dev_err(ptr_audio_codec_cnxt->dev, + "Register error for v-amic1=%d", status); + goto free_regulator_vdmic; + } + regulator_vamic2 = regulator_get(NULL, "v-amic2"); + if (IS_ERR(regulator_vamic2)) { + status = PTR_ERR(regulator_vamic2); + dev_err(ptr_audio_codec_cnxt->dev, + "Register error for v-amic2=%d", status); + goto free_regulator_vdmic_vamic1; + } + regulator_vaudio = regulator_get(NULL, "v-audio"); + if (IS_ERR(regulator_vaudio)) { + status = PTR_ERR(regulator_vaudio); + dev_err(ptr_audio_codec_cnxt->dev, + "Register error for v-audio=%d", status); + goto free_regulator_vdmic_vamic1_vamic2; + } + regulator_avsource = regulator_get(ptr_audio_codec_cnxt->dev, + "vcc-N2158"); + if (IS_ERR(regulator_avsource)) { + status = PTR_ERR(regulator_avsource); + dev_err(ptr_audio_codec_cnxt->dev, + "Register error for vcc-N2158=%d", status); + goto free_regulator_vdmic_vamic1_vamic2_vaudio; + } + + ste_audio_io_init_transducer_cnxt(); + return 0; + +free_regulator_vdmic_vamic1_vamic2_vaudio: + regulator_put(regulator_vaudio); +free_regulator_vdmic_vamic1_vamic2: + regulator_put(regulator_vamic2); +free_regulator_vdmic_vamic1: + regulator_put(regulator_vamic1); +free_regulator_vdmic: + regulator_put(regulator_vdmic); +free_audio_codec_cnxt: + kfree(ptr_audio_codec_cnxt); + return status; +} + +static struct transducer_context_t transducer_headset = { + .pwr_up_func = ste_audio_io_power_up_headset, + .pwr_down_func = ste_audio_io_power_down_headset, + .set_gain_func = ste_audio_io_set_headset_gain, + .get_gain_func = ste_audio_io_get_headset_gain, + .mute_func = ste_audio_io_mute_headset, + .unmute_func = ste_audio_io_unmute_headset, + .enable_fade_func = ste_audio_io_enable_fade_headset, + .disable_fade_func = ste_audio_io_disable_fade_headset, + .switch_to_burst_func = ste_audio_io_switch_to_burst_mode_headset, + .switch_to_normal_func = ste_audio_io_switch_to_normal_mode_headset +}; + +static struct transducer_context_t transducer_earpiece = { + .pwr_up_func = ste_audio_io_power_up_earpiece, + .pwr_down_func = ste_audio_io_power_down_earpiece, + .set_gain_func = ste_audio_io_set_earpiece_gain, + .get_gain_func = ste_audio_io_get_earpiece_gain, + .mute_func = ste_audio_io_mute_earpiece, + .unmute_func = ste_audio_io_unmute_earpiece, + .enable_fade_func = ste_audio_io_enable_fade_earpiece, + .disable_fade_func = ste_audio_io_disable_fade_earpiece +}; + +static struct transducer_context_t transducer_ihf = { + .pwr_up_func = ste_audio_io_power_up_ihf, + .pwr_down_func = ste_audio_io_power_down_ihf, + .set_gain_func = ste_audio_io_set_ihf_gain, + .get_gain_func = ste_audio_io_get_ihf_gain, + .mute_func = ste_audio_io_mute_ihf, + .unmute_func = ste_audio_io_unmute_ihf, + .enable_fade_func = ste_audio_io_enable_fade_ihf, + .disable_fade_func = ste_audio_io_disable_fade_ihf + +}; + +static struct transducer_context_t transducer_vibl = { + .pwr_up_func = ste_audio_io_power_up_vibl, + .pwr_down_func = ste_audio_io_power_down_vibl, + .set_gain_func = ste_audio_io_set_vibl_gain, + .get_gain_func = ste_audio_io_get_vibl_gain, + .mute_func = ste_audio_io_mute_vibl, + .unmute_func = ste_audio_io_unmute_vibl, + .enable_fade_func = ste_audio_io_enable_fade_vibl, + .disable_fade_func = ste_audio_io_disable_fade_vibl +}; + +static struct transducer_context_t transducer_vibr = { + .pwr_up_func = ste_audio_io_power_up_vibr, + .pwr_down_func = ste_audio_io_power_down_vibr, + .set_gain_func = ste_audio_io_set_vibr_gain, + .get_gain_func = ste_audio_io_get_vibr_gain, + .mute_func = ste_audio_io_mute_vibr, + .unmute_func = ste_audio_io_unmute_vibr, + .enable_fade_func = ste_audio_io_enable_fade_vibr, + .disable_fade_func = ste_audio_io_disable_fade_vibr +}; + +static struct transducer_context_t transducer_mic1a = { + .pwr_up_func = ste_audio_io_power_up_mic1a, + .pwr_down_func = ste_audio_io_power_down_mic1a, + .set_gain_func = ste_audio_io_set_mic1a_gain, + .get_gain_func = ste_audio_io_get_mic1a_gain, + .mute_func = ste_audio_io_mute_mic1a, + .unmute_func = ste_audio_io_unmute_mic1a, + .enable_fade_func = ste_audio_io_enable_fade_mic1a, + .disable_fade_func = ste_audio_io_disable_fade_mic1a +}; + +static struct transducer_context_t transducer_mic1b = { + .pwr_up_func = ste_audio_io_power_up_mic1b, + .pwr_down_func = ste_audio_io_power_down_mic1b, + .set_gain_func = ste_audio_io_set_mic1a_gain, + .get_gain_func = ste_audio_io_get_mic1a_gain, + .mute_func = ste_audio_io_mute_mic1a, + .unmute_func = ste_audio_io_unmute_mic1a, + .enable_fade_func = ste_audio_io_enable_fade_mic1a, + .disable_fade_func = ste_audio_io_disable_fade_mic1a, + .enable_loop = ste_audio_io_enable_loop_mic1b, + .disable_loop = ste_audio_io_disable_loop_mic1b +}; + +static struct transducer_context_t transducer_mic2 = { + .pwr_up_func = ste_audio_io_power_up_mic2, + .pwr_down_func = ste_audio_io_power_down_mic2, + .set_gain_func = ste_audio_io_set_mic2_gain, + .get_gain_func = ste_audio_io_get_mic2_gain, + .mute_func = ste_audio_io_mute_mic2, + .unmute_func = ste_audio_io_unmute_mic2, + .enable_fade_func = ste_audio_io_enable_fade_mic2, + .disable_fade_func = ste_audio_io_disable_fade_mic2 +}; + +static struct transducer_context_t transducer_lin = { + .pwr_up_func = ste_audio_io_power_up_lin, + .pwr_down_func = ste_audio_io_power_down_lin, + .set_gain_func = ste_audio_io_set_lin_gain, + .get_gain_func = ste_audio_io_get_lin_gain, + .mute_func = ste_audio_io_mute_lin, + .unmute_func = ste_audio_io_unmute_lin, + .enable_fade_func = ste_audio_io_enable_fade_lin, + .disable_fade_func = ste_audio_io_disable_fade_lin +}; + +static struct transducer_context_t transducer_dmic12 = { + .pwr_up_func = ste_audio_io_power_up_dmic12, + .pwr_down_func = ste_audio_io_power_down_dmic12, + .set_gain_func = ste_audio_io_set_dmic12_gain, + .get_gain_func = ste_audio_io_get_dmic12_gain, + .mute_func = ste_audio_io_mute_dmic12, + .unmute_func = ste_audio_io_unmute_dmic12, + .enable_fade_func = ste_audio_io_enable_fade_dmic12, + .disable_fade_func = ste_audio_io_disable_fade_dmic12, + .enable_loop = ste_audio_io_enable_loop_dmic12, + .disable_loop = ste_audio_io_disable_loop_dmic12 +}; + +static struct transducer_context_t transducer_dmic34 = { + .pwr_up_func = ste_audio_io_power_up_dmic34, + .pwr_down_func = ste_audio_io_power_down_dmic34, + .set_gain_func = ste_audio_io_set_dmic34_gain, + .get_gain_func = ste_audio_io_get_dmic34_gain, + .mute_func = ste_audio_io_mute_dmic34, + .unmute_func = ste_audio_io_unmute_dmic34, + .enable_fade_func = ste_audio_io_enable_fade_dmic34, + .disable_fade_func = ste_audio_io_disable_fade_dmic34 +}; + +static struct transducer_context_t transducer_dmic56 = { + .pwr_up_func = ste_audio_io_power_up_dmic56, + .pwr_down_func = ste_audio_io_power_down_dmic56, + .set_gain_func = ste_audio_io_set_dmic56_gain, + .get_gain_func = ste_audio_io_get_dmic56_gain, + .mute_func = ste_audio_io_mute_dmic56, + .unmute_func = ste_audio_io_unmute_dmic56, + .enable_fade_func = ste_audio_io_enable_fade_dmic56, + .disable_fade_func = ste_audio_io_disable_fade_dmic56, +}; + +static struct transducer_context_t transducer_fmrx = { + .pwr_up_func = ste_audio_io_power_up_fmrx, + .pwr_down_func = ste_audio_io_power_down_fmrx, +}; + +static struct transducer_context_t transducer_fmtx = { + .pwr_up_func = ste_audio_io_power_up_fmtx, + .pwr_down_func = ste_audio_io_power_down_fmtx, +}; + +static struct transducer_context_t transducer_bluetooth = { + .pwr_up_func = ste_audio_io_power_up_bluetooth, + .pwr_down_func = ste_audio_io_power_down_bluetooth, +}; + +static void ste_audio_io_init_transducer_cnxt(void) +{ + ptr_audio_codec_cnxt->transducer[HS_CH] = &transducer_headset; + ptr_audio_codec_cnxt->transducer[EAR_CH] = &transducer_earpiece; + ptr_audio_codec_cnxt->transducer[IHF_CH] = &transducer_ihf; + ptr_audio_codec_cnxt->transducer[VIBL_CH] = &transducer_vibl; + ptr_audio_codec_cnxt->transducer[VIBR_CH] = &transducer_vibr; + ptr_audio_codec_cnxt->transducer[MIC1A_CH] = &transducer_mic1a; + ptr_audio_codec_cnxt->transducer[MIC1B_CH] = &transducer_mic1b; + ptr_audio_codec_cnxt->transducer[MIC2_CH] = &transducer_mic2; + ptr_audio_codec_cnxt->transducer[LIN_CH] = &transducer_lin; + ptr_audio_codec_cnxt->transducer[DMIC12_CH] = &transducer_dmic12; + ptr_audio_codec_cnxt->transducer[DMIC34_CH] = &transducer_dmic34; + ptr_audio_codec_cnxt->transducer[DMIC56_CH] = &transducer_dmic56; + ptr_audio_codec_cnxt->transducer[FMRX_CH] = &transducer_fmrx; + ptr_audio_codec_cnxt->transducer[FMTX_CH] = &transducer_fmtx; + ptr_audio_codec_cnxt->transducer[BLUETOOTH_CH] = &transducer_bluetooth; +} + +void ste_audio_io_core_api_free_data(void) +{ + regulator_put(regulator_vdmic); + regulator_put(regulator_vamic1); + regulator_put(regulator_vamic2); + regulator_put(regulator_vaudio); + regulator_put(regulator_avsource); + kfree(ptr_audio_codec_cnxt); +} + +static int ste_audio_io_core_api_enable_regulators(int channel_type) +{ + int error = 0; + + switch (channel_type) { + case EAR_CH: + case HS_CH: + case IHF_CH: + case VIBL_CH: + case VIBR_CH: + case LIN_CH: + case FMRX_CH: + case FMTX_CH: + case BLUETOOTH_CH: + /* vaduio already enabled + no additional regualtor required */ + break; + + case MIC1A_CH: + case MIC1B_CH: + error = regulator_enable(regulator_vamic1); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "unable to enable regulator vamic1 error = %d", error); + break; + + case MIC2_CH: + error = regulator_enable(regulator_vamic2); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "unable to enable regulator vamic2 error = %d", error); + break; + + case DMIC12_CH: + case DMIC34_CH: + case DMIC56_CH: + case MULTI_MIC_CH: + error = regulator_enable(regulator_vdmic); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "unable to enable regulator vdmic error = %d", error); + } + return error; +} + +static int ste_audio_io_core_api_disable_regulators(int channel_type) +{ + int error = 0; + + switch (channel_type) { + case EAR_CH: + case HS_CH: + case IHF_CH: + case VIBL_CH: + case VIBR_CH: + case LIN_CH: + case FMRX_CH: + case FMTX_CH: + case BLUETOOTH_CH: + /* no need to disable separately*/ + break; + + case MIC1A_CH: + case MIC1B_CH: + error = regulator_disable(regulator_vamic1); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "unable to disable regulator vamic1 error = %d", error); + break; + + case MIC2_CH: + error = regulator_disable(regulator_vamic2); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "unable to disable regulator vamic2 error = %d", error); + break; + + case DMIC12_CH: + case DMIC34_CH: + case DMIC56_CH: + case MULTI_MIC_CH: + error = regulator_disable(regulator_vdmic); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "unable to disable regulator vdmic error = %d", error); + } + return error; +} + +int ste_audio_io_core_api_powerup_audiocodec(int power_client) +{ + int error = 0; + int acodec_device_id; + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + acodec_device_id = abx500_get_chip_id(&ste_audio_io_device->dev); + + /* + * If there is no power client registered, power up + * common audio blocks for audio and vibrator + */ + if (!ptr_audio_codec_cnxt->power_client) { + __u8 data, old_data; + + old_data = HW_REG_READ(AB8500_CTRL3_REG); + + /* Enable 32 Khz clock signal on Clk32KOut2 ball */ + data = (~CLK_32K_OUT2_DISABLE) & old_data; + error = HW_REG_WRITE(AB8500_CTRL3_REG, data); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "enabling 32KHz clock error = %d", error); + goto err_cleanup; + } + data = INACTIVE_RESET_AUDIO | old_data; + error = HW_REG_WRITE(AB8500_CTRL3_REG, data); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "deactivate audio codec reset error = %d", error); + goto err_cleanup; + } + old_data = HW_REG_READ(AB8500_SYSULPCLK_CTRL1_REG); + data = ENABLE_AUDIO_CLK_TO_AUDIO_BLK | old_data; + + error = HW_REG_WRITE(AB8500_SYSULPCLK_CTRL1_REG, data); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "enabling clock to audio block error = %d", error); + goto err_cleanup; + } + regulator_enable(regulator_vaudio); + + old_data = HW_REG_READ(AB8500_GPIO_DIR4_REG); + data = (GPIO27_DIR_OUTPUT | GPIO29_DIR_OUTPUT | + GPIO31_DIR_OUTPUT) | old_data; + error = HW_REG_WRITE(AB8500_GPIO_DIR4_REG, data); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "setting gpio dir4 error = %d", error); + goto err_cleanup; + } + error = HW_REG_WRITE(SOFTWARE_RESET_REG, SW_RESET); + if (error != 0) { + dev_err(ptr_audio_codec_cnxt->dev, + "Software reset error=%d", error); + goto err_cleanup; + } + + error = HW_ACODEC_MODIFY_WRITE(POWER_UP_CONTROL_REG, + (DEVICE_POWER_UP|ANALOG_PARTS_POWER_UP), 0); + if (error != 0) { + dev_err(ptr_audio_codec_cnxt->dev, + "Device Power Up, error=%d", error); + goto err_cleanup; + } + } + /* Save information that given client already powered up audio block */ + ptr_audio_codec_cnxt->power_client |= power_client; + + /* If audio block requested power up, turn on additional audio blocks */ + if (power_client == STE_AUDIOIO_POWER_AUDIO) { + if (!ptr_audio_codec_cnxt->audio_codec_powerup) { + clk_ptr_sysclk = + clk_get(ptr_audio_codec_cnxt->dev, "sysclk"); + if (!IS_ERR(clk_ptr_sysclk)) { + error = clk_enable(clk_ptr_sysclk); + if (error) + goto err_cleanup; + } else { + error = -EFAULT; + goto err_cleanup; + } + + clk_ptr_msp1 = clk_get_sys("msp1", NULL); + if (!IS_ERR(clk_ptr_msp1)) { + error = clk_enable(clk_ptr_msp1); + if (error) + goto err_cleanup; + } else { + error = -EFAULT; + goto err_cleanup; + } + + if (AB8500_REV_20 == acodec_device_id) { + clk_ptr_msp3 = clk_get_sys("msp3", NULL); + if (!IS_ERR(clk_ptr_msp3)) { + error = clk_enable(clk_ptr_msp3); + if (error) + goto err_cleanup; + } else { + error = -EFAULT; + goto err_cleanup; + } + } + + if (ptr_audio_codec_cnxt->gpio_altf_init) { + error = ptr_audio_codec_cnxt->gpio_altf_init(); + if (error) + goto err_cleanup; + } + + error = HW_ACODEC_MODIFY_WRITE(IF0_IF1_MASTER_CONF_REG, + EN_MASTGEN, 0); + if (error != 0) { + dev_err(ptr_audio_codec_cnxt->dev, + "Enable Master Generator, error=%d", error); + goto err_cleanup; + } + + error = HW_ACODEC_MODIFY_WRITE(TDM_IF_BYPASS_B_FIFO_REG, + IF0_MASTER, 0); + if (error != 0) { + dev_err(ptr_audio_codec_cnxt->dev, + "IF0: Master Mode, error=%d", error); + goto err_cleanup; + } + + /* Configuring IF0 */ + + error = HW_ACODEC_MODIFY_WRITE(IF0_IF1_MASTER_CONF_REG, + BITCLK_OSR_N_256, 0); + if (error != 0) { + dev_err(ptr_audio_codec_cnxt->dev, + "IF0: Enable FsBitClk & FSync error=%d", error); + goto err_cleanup; + } + + error = HW_REG_WRITE(IF0_CONF_REG, IF_DELAYED + | TDM_FORMAT | WORD_LENGTH_20); + if (error != 0) { + dev_err(ptr_audio_codec_cnxt->dev, + "IF0: TDM Format 16 Bits word length, error=%d", + error); + goto err_cleanup; + } + } + ptr_audio_codec_cnxt->audio_codec_powerup++; + } +err_cleanup: + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + return error; +} + +int ste_audio_io_core_api_powerdown_audiocodec(int power_client) +{ + int error = 0; + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + /* Update power client status */ + if (power_client == STE_AUDIOIO_POWER_AUDIO) { + ptr_audio_codec_cnxt->audio_codec_powerup--; + if (!ptr_audio_codec_cnxt->audio_codec_powerup) { + ptr_audio_codec_cnxt->power_client &= ~power_client; + clk_disable(clk_ptr_sysclk); + clk_put(clk_ptr_sysclk); + clk_disable(clk_ptr_msp1); + clk_put(clk_ptr_msp1); + if (AB8500_REV_20 == + abx500_get_chip_id(&ste_audio_io_device->dev)) { + clk_disable(clk_ptr_msp3); + clk_put(clk_ptr_msp3); + } + + if (ptr_audio_codec_cnxt->gpio_altf_exit) { + error = ptr_audio_codec_cnxt->gpio_altf_exit(); + if (error) + goto err_cleanup; + } + } + } else + ptr_audio_codec_cnxt->power_client &= ~power_client; + + /* If no power client registered, power down audio block */ + if (!ptr_audio_codec_cnxt->power_client) { + regulator_disable(regulator_vaudio); + if (error != 0) { + dev_err(ptr_audio_codec_cnxt->dev, + "Device Power Down and Analog Parts Power Down error = %d ", + error); + goto err_cleanup; + } + } + +err_cleanup: + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + return error; +} +/** + * @brief Read from AB8500 device + * @dev_data Pointer to the structure __audioio_data + * @return 0 + */ + +int ste_audio_io_core_api_access_read(struct audioio_data_t *dev_data) +{ + int reg; + if (NULL == dev_data) + return -EFAULT; + reg = (dev_data->block<<8)|(dev_data->addr&0xff); + dev_data->data = HW_REG_READ(reg); + return 0; +} +/** + * @brief Write on AB8500 device + * @dev_data Pointer to the structure __audioio_data + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_core_api_access_write(struct audioio_data_t *dev_data) +{ + int retval, reg; + if (NULL == dev_data) + return -EFAULT; + + reg = (dev_data->block<<8)|(dev_data->addr&0xff); + retval = HW_REG_WRITE(reg, dev_data->data); + + return retval; +} +/** + * @brief Store the power and mute status of transducer + * @channel_index Channel-index of transducer + * @ptr Array storing the status + * @value status being stored + * @return 0 on success otherwise negative error code + */ + +void ste_audio_io_core_api_store_data(enum AUDIOIO_CH_INDEX channel_index, + int *ptr, int value) +{ + if (channel_index & e_CHANNEL_1) + ptr[0] = value; + + if (channel_index & e_CHANNEL_2) + ptr[1] = value; + + if (channel_index & e_CHANNEL_3) + ptr[2] = value; + + if (channel_index & e_CHANNEL_4) + ptr[3] = value; +} +/** + * @brief Get power or mute status on a specific channel + * @channel_index Channel-index of the transducer + * @ptr Pointer to is_power_up array or is_muted array + * @return status of control switch + */ +enum AUDIOIO_COMMON_SWITCH ste_audio_io_core_api_get_status( + enum AUDIOIO_CH_INDEX channel_index, int *ptr) +{ + if (channel_index & e_CHANNEL_1) { + if (AUDIOIO_TRUE == ptr[0]) + return AUDIOIO_COMMON_ON; + else + return AUDIOIO_COMMON_OFF; + } + + if (channel_index & e_CHANNEL_2) { + if (AUDIOIO_TRUE == ptr[1]) + return AUDIOIO_COMMON_ON; + else + return AUDIOIO_COMMON_OFF; + } + + if (channel_index & e_CHANNEL_3) { + if (AUDIOIO_TRUE == ptr[2]) + return AUDIOIO_COMMON_ON; + else + return AUDIOIO_COMMON_OFF; + } + + if (channel_index & e_CHANNEL_4) { + if (AUDIOIO_TRUE == ptr[3]) + return AUDIOIO_COMMON_ON; + else + return AUDIOIO_COMMON_OFF; + } + return 0; +} + +int ste_audio_io_core_api_acodec_power_control(struct audioio_acodec_pwr_ctrl_t + *audio_acodec_pwr_ctrl) +{ + int error = 0; + if (audio_acodec_pwr_ctrl->ctrl_switch == AUDIOIO_COMMON_ON) + error = ste_audio_io_core_api_powerup_audiocodec( + STE_AUDIOIO_POWER_AUDIO); + else + error = ste_audio_io_core_api_powerdown_audiocodec( + STE_AUDIOIO_POWER_AUDIO); + + return error; +} +/** + * @brief Control for powering on/off HW components on a specific channel + * @pwr_ctrl Pointer to the structure __audioio_pwr_ctrl + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_core_api_power_control_transducer( + struct audioio_pwr_ctrl_t *pwr_ctrl) +{ + int error = 0; + struct transducer_context_t *ptr = NULL; + enum AUDIOIO_CH_INDEX channel_index; + + channel_index = pwr_ctrl->channel_index; + + if ((pwr_ctrl->channel_type < FIRST_CH) + || (pwr_ctrl->channel_type > LAST_CH)) + return -EINVAL; + + ptr = ptr_audio_codec_cnxt->transducer[pwr_ctrl->channel_type]; + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + if (AUDIOIO_COMMON_ON == pwr_ctrl->ctrl_switch) { + if (ptr->pwr_up_func) { + error = ste_audio_io_core_api_enable_regulators( + pwr_ctrl->channel_type); + if (error) + goto free_mutex; + + error = ptr->pwr_up_func(pwr_ctrl->channel_index, + ptr_audio_codec_cnxt->dev); + if (0 == error) { + ste_audio_io_core_api_store_data(channel_index, + ptr->is_power_up, AUDIOIO_TRUE); + } + } + } else { + if (ptr->pwr_down_func) { + error = ptr->pwr_down_func(pwr_ctrl->channel_index, + ptr_audio_codec_cnxt->dev); + if (0 == error) { + ste_audio_io_core_api_store_data(channel_index, + ptr->is_power_up, AUDIOIO_FALSE); + } + error = ste_audio_io_core_api_disable_regulators( + pwr_ctrl->channel_type); + } + } + +free_mutex: + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return error; +} +/** + * @brief Query power state of HW path on specified channel + * @pwr_ctrl Pointer to the structure __audioio_pwr_ctrl + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_core_api_power_status_transducer( + struct audioio_pwr_ctrl_t *pwr_ctrl) +{ + + struct transducer_context_t *ptr = NULL; + enum AUDIOIO_CH_INDEX channel_index; + + channel_index = pwr_ctrl->channel_index; + + if ((pwr_ctrl->channel_type < FIRST_CH) + || (pwr_ctrl->channel_type > LAST_CH)) + return -EINVAL; + + ptr = ptr_audio_codec_cnxt->transducer[pwr_ctrl->channel_type]; + + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + + pwr_ctrl->ctrl_switch = ste_audio_io_core_api_get_status(channel_index, + ptr->is_power_up); + + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return 0; + +} + +int ste_audio_io_core_api_loop_control(struct audioio_loop_ctrl_t *loop_ctrl) +{ + int error = 0; + struct transducer_context_t *ptr = NULL; + + if ((loop_ctrl->channel_type < FIRST_CH) + || (loop_ctrl->channel_type > LAST_CH)) + return -EINVAL; + + ptr = ptr_audio_codec_cnxt->transducer[loop_ctrl->channel_type]; + + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + if (AUDIOIO_COMMON_ON == loop_ctrl->ctrl_switch) { + if (ptr->enable_loop) { + error = ptr->enable_loop(loop_ctrl->channel_index, + loop_ctrl->hw_loop, + loop_ctrl->loop_gain, + ptr_audio_codec_cnxt->dev, + ptr_audio_codec_cnxt->transducer); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "Loop enable failed for hw loop = %d, error = %d ", + (int)loop_ctrl->hw_loop, error); + } else { + error = -EFAULT; + dev_err(ptr_audio_codec_cnxt->dev, + "Hw Loop enable does not exist for channel= %d, error = %d ", + (int)loop_ctrl->channel_type, error); + } + } else { + if (ptr->disable_loop) { + error = ptr->disable_loop(loop_ctrl->channel_index, + loop_ctrl->hw_loop, + ptr_audio_codec_cnxt->dev, + ptr_audio_codec_cnxt->transducer); + if (error) + dev_err(ptr_audio_codec_cnxt->dev, + "Loop disable failed for hw loop = %d, error = %d ", + (int)loop_ctrl->hw_loop, error); + } else { + error = -EFAULT; + dev_err(ptr_audio_codec_cnxt->dev, + "Hw Loop disable does not exist for channel= %d, error = %d ", + (int)loop_ctrl->channel_type, error); + } + } + + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return error; +} + +int ste_audio_io_core_api_loop_status(struct audioio_loop_ctrl_t *loop_ctrl) +{ + return 0; +} + +int ste_audio_io_core_api_get_transducer_gain_capability( + struct audioio_get_gain_t *get_gain) +{ + return 0; +} + +int ste_audio_io_core_api_gain_capabilities_loop( + struct audioio_gain_loop_t *gain_loop) +{ + if ((gain_loop->channel_type < FIRST_CH) + || (gain_loop->channel_type > LAST_CH)) + return -EINVAL; + + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + gain_loop->num_loop = + transducer_max_no_Of_supported_loops[gain_loop->channel_type]; + gain_loop->max_gains = max_no_of_loop_gains[gain_loop->channel_type]; + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + return 0; +} + +int ste_audio_io_core_api_supported_loops( + struct audioio_support_loop_t *support_loop) +{ + if ((support_loop->channel_type < FIRST_CH) + || (support_loop->channel_type > LAST_CH)) + return -EINVAL; + + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + support_loop->spprtd_loop_index = + transducer_no_Of_supported_loop_indexes[support_loop->channel_type]; + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + return 0; +} + +int ste_audio_io_core_api_gain_descriptor_transducer( + struct audioio_gain_desc_trnsdr_t *gdesc_trnsdr) +{ + return 0; +} +/** + * @brief Control for muting a specific channel in HW + * @mute_trnsdr Pointer to the structure __audioio_mute_trnsdr + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_core_api_mute_control_transducer( + struct audioio_mute_trnsdr_t *mute_trnsdr) +{ + int error = 0; + struct transducer_context_t *ptr = NULL; + enum AUDIOIO_CH_INDEX channel_index; + + channel_index = mute_trnsdr->channel_index; + + if ((mute_trnsdr->channel_type < FIRST_CH) + || (mute_trnsdr->channel_type > LAST_CH)) + return -EINVAL; + + ptr = ptr_audio_codec_cnxt->transducer[mute_trnsdr->channel_type]; + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + if (AUDIOIO_COMMON_ON == mute_trnsdr->ctrl_switch) { + if (ptr->mute_func) { + error = ptr->mute_func(mute_trnsdr->channel_index, + ptr_audio_codec_cnxt->dev); + if (0 == error) { + ste_audio_io_core_api_store_data(channel_index , + ptr->is_muted, AUDIOIO_TRUE); + } + } + } else { + if (ptr->unmute_func) { + if (0 == ptr->unmute_func(channel_index, ptr->gain, + ptr_audio_codec_cnxt->dev)) { + ste_audio_io_core_api_store_data(channel_index, + ptr->is_muted, AUDIOIO_FALSE); + } + } + } + + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return error; +} +/** + * @brief Query state of mute on specified channel + * @mute_trnsdr Pointer to the structure __audioio_mute_trnsdr + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_core_api_mute_status_transducer( + struct audioio_mute_trnsdr_t *mute_trnsdr) +{ + struct transducer_context_t *ptr = NULL; + enum AUDIOIO_CH_INDEX channel_index; + + channel_index = mute_trnsdr->channel_index; + + if ((mute_trnsdr->channel_type < FIRST_CH) + || (mute_trnsdr->channel_type > LAST_CH)) + return -EINVAL; + + ptr = ptr_audio_codec_cnxt->transducer[mute_trnsdr->channel_type]; + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + mute_trnsdr->ctrl_switch = ste_audio_io_core_api_get_status( + channel_index, ptr->is_muted); + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return 0; +} +/** + * @brief control the fading on the transducer called on. + * @fade_ctrl Pointer to the structure __audioio_fade_ctrl + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_core_api_fading_control(struct audioio_fade_ctrl_t *fade_ctrl) +{ + int error = 0; + struct transducer_context_t *ptr = NULL; + + if ((fade_ctrl->channel_type < FIRST_CH) + || (fade_ctrl->channel_type > LAST_CH)) + return -EINVAL; + ptr = ptr_audio_codec_cnxt->transducer[fade_ctrl->channel_type]; + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + if (AUDIOIO_COMMON_ON == fade_ctrl->ctrl_switch) + error = ptr->enable_fade_func(ptr_audio_codec_cnxt->dev); + + else + error = ptr->disable_fade_func(ptr_audio_codec_cnxt->dev); + + + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return error; +} +/** + * @brief control the low power mode of headset. + * @burst_ctrl Pointer to the structure __audioio_burst_ctrl + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_core_api_burstmode_control( + struct audioio_burst_ctrl_t *burst_ctrl) +{ + int error = 0; + struct transducer_context_t *ptr = NULL; + int burst_fifo_switch_frame; + + burst_fifo_switch_frame = burst_ctrl->burst_fifo_switch_frame; + + if ((burst_ctrl->channel_type < FIRST_CH) + || (burst_ctrl->channel_type > LAST_CH)) + return -EINVAL; + ptr = ptr_audio_codec_cnxt->transducer[burst_ctrl->channel_type]; + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + if (AUDIOIO_COMMON_ON == burst_ctrl->ctrl_switch) { + if (ptr->switch_to_burst_func) + error = ptr->switch_to_burst_func( + burst_fifo_switch_frame, + ptr_audio_codec_cnxt->dev); + } else + if (ptr->switch_to_normal_func) + error = ptr->switch_to_normal_func( + ptr_audio_codec_cnxt->dev); + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return error; +} +/** + * @brief Convert channel index to array index + * @channel_index Channel Index of transducer + * @return Array index corresponding to the specified channel index + */ + +int convert_channel_index_to_array_index(enum AUDIOIO_CH_INDEX channel_index) +{ + if (channel_index & e_CHANNEL_1) + return 0; + else if (channel_index & e_CHANNEL_2) + return 1; + else if (channel_index & e_CHANNEL_3) + return 2; + else + return 3; +} + +/** + * @brief Set individual gain along the HW path of a specified channel + * @gctrl_trnsdr Pointer to the structure __audioio_gain_ctrl_trnsdr + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_core_api_gain_control_transducer( + struct audioio_gain_ctrl_trnsdr_t *gctrl_trnsdr) +{ + struct transducer_context_t *ptr = NULL; + enum AUDIOIO_CH_INDEX channel_index; + int ch_array_index; + u16 gain_index; + int gain_value; + u32 linear; + int channel_type; + int error; + int min_gain, max_gain, gain; + + if ((gctrl_trnsdr->channel_type < FIRST_CH) + || (gctrl_trnsdr->channel_type > LAST_CH)) + return -EINVAL; + + if (gctrl_trnsdr->gain_index >= MAX_NO_GAINS) + return -EINVAL; + + ptr = ptr_audio_codec_cnxt->transducer[gctrl_trnsdr->channel_type]; + channel_index = gctrl_trnsdr->channel_index; + gain_index = gctrl_trnsdr->gain_index; + gain_value = gctrl_trnsdr->gain_value; + linear = gctrl_trnsdr->linear; + channel_type = gctrl_trnsdr->channel_type; + + ch_array_index = convert_channel_index_to_array_index(channel_index); + if (linear) { /* Gain is in the range 0 to 100 */ + min_gain = gain_descriptor[channel_type]\ + [ch_array_index][gain_index].min_gain; + max_gain = gain_descriptor[channel_type]\ + [ch_array_index][gain_index].max_gain; + + gain = ((gain_value * (max_gain - min_gain))/100) + min_gain; + } else + /* Convert to db */ + gain = gain_value/100; + + gain_value = gain; + +#if 1 + if (gain_index >= transducer_no_of_gains[channel_type]) + return -EINVAL; + + if (gain_value < gain_descriptor[channel_type]\ + [ch_array_index][gain_index].min_gain) + return -EINVAL; + + if (gain_value > gain_descriptor[channel_type]\ + [ch_array_index][gain_index].max_gain) + return -EINVAL; + +#endif + + /* aquire mutex */ + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + error = ptr->set_gain_func(channel_index, + gain_index, gain_value, linear, + ptr_audio_codec_cnxt->dev); + if (0 == error) + ste_audio_io_core_api_store_data(channel_index , + ptr->gain, gain_value); + + + /* release mutex */ + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + return error; +} +/** + * @brief Get individual gain along the HW path of a specified channel + * @gctrl_trnsdr Pointer to the structure __audioio_gain_ctrl_trnsdr + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_core_api_gain_query_transducer( + struct audioio_gain_ctrl_trnsdr_t *gctrl_trnsdr) +{ + struct transducer_context_t *ptr = NULL; + enum AUDIOIO_CH_INDEX channel_index; + u16 gain_index; + u32 linear; + int left_volume, right_volume; + int max_gain, min_gain; + int ch_array_index; + + if ((gctrl_trnsdr->channel_type < FIRST_CH) + || (gctrl_trnsdr->channel_type > LAST_CH)) + return -EINVAL; + + if (gctrl_trnsdr->gain_index >= MAX_NO_GAINS) + return -EINVAL; + + ptr = ptr_audio_codec_cnxt->transducer[gctrl_trnsdr->channel_type]; + + channel_index = gctrl_trnsdr->channel_index; + gain_index = gctrl_trnsdr->gain_index; + linear = gctrl_trnsdr->linear; + + ptr->get_gain_func(&left_volume, &right_volume, gain_index, + ptr_audio_codec_cnxt->dev); + + ch_array_index = convert_channel_index_to_array_index(channel_index); + max_gain = gain_descriptor[gctrl_trnsdr->channel_type]\ + [ch_array_index][gain_index].max_gain; + min_gain = gain_descriptor[gctrl_trnsdr->channel_type]\ + [ch_array_index][gain_index].min_gain; + + switch (channel_index) { + case e_CHANNEL_1: + gctrl_trnsdr->gain_value = linear ? \ + min_gain+left_volume*(max_gain-min_gain)/100 : left_volume; + break; + case e_CHANNEL_2: + gctrl_trnsdr->gain_value = linear ? \ + min_gain+right_volume*(max_gain-min_gain)/100 : right_volume; + break; + case e_CHANNEL_3: + break; + case e_CHANNEL_4: + break; + case e_CHANNEL_ALL: + if (left_volume == right_volume) { + if (linear) + gctrl_trnsdr->gain_value = + min_gain+right_volume*(max_gain-min_gain)/100; + else + gctrl_trnsdr->gain_value = right_volume; + } + } + + return 0; +} + + +int ste_audio_io_core_api_fsbitclk_control( + struct audioio_fsbitclk_ctrl_t *fsbitclk_ctrl) +{ + int error = 0; + + if (AUDIOIO_COMMON_ON == fsbitclk_ctrl->ctrl_switch) + error = HW_ACODEC_MODIFY_WRITE(IF0_IF1_MASTER_CONF_REG, + EN_FSYNC_BITCLK, 0); + else + error = HW_ACODEC_MODIFY_WRITE(IF0_IF1_MASTER_CONF_REG, 0, + EN_FSYNC_BITCLK); + + return error; +} +int ste_audio_io_core_api_pseudoburst_control( + struct audioio_pseudoburst_ctrl_t *pseudoburst_ctrl) +{ + int error = 0; + + return error; +} +int ste_audio_io_core_debug(int x) +{ + debug_audioio(x); + +return 0; +} + +/** + * ste_audioio_vibrator_alloc() + * @client: Client id which allocates vibrator + * @mask: Mask against which vibrator usage is checked + * + * This function allocates vibrator. + * Mask is added here as audioio driver controls left and right vibrator + * separately (can work independently). In case when audioio has allocated + * one of its channels (left or right) it should be still able to allocate + * the other channel. + * + * Returns: + * 0 - Success + * -EBUSY - other client already registered + **/ +int ste_audioio_vibrator_alloc(int client, int mask) +{ + int error = 0; + + /* Check if other client is already using vibrator */ + if (ptr_audio_codec_cnxt->vibra_client & ~mask) + error = -EBUSY; + else + ptr_audio_codec_cnxt->vibra_client |= client; + + return error; +} + +/** + * ste_audioio_vibrator_release() + * @client: Client id which releases vibrator + * + * This function releases vibrator + **/ +void ste_audioio_vibrator_release(int client) +{ + ptr_audio_codec_cnxt->vibra_client &= ~client; +} + +/** + * ste_audioio_vibrator_pwm_control() + * @client: Client id which will use vibrator + * @left_speed: Left vibrator speed + * @right_speed: Right vibrator speed + * + * This function controls vibrator using PWM source + * + * Returns: + * 0 - success + * -EBUSY - Vibrator already used + **/ +int ste_audioio_vibrator_pwm_control( + int client, + struct ste_vibra_speed left_speed, + struct ste_vibra_speed right_speed) +{ + int error = 0; + + mutex_lock(&ptr_audio_codec_cnxt->audio_io_mutex); + + /* Try to allocate vibrator for given client */ + error = ste_audioio_vibrator_alloc(client, client); + + mutex_unlock(&ptr_audio_codec_cnxt->audio_io_mutex); + + if (error) + return error; + + /* Duty cycle supported by vibrator's PWM is 0-100 */ + if (left_speed.positive > STE_AUDIOIO_VIBRATOR_MAX_SPEED) + left_speed.positive = STE_AUDIOIO_VIBRATOR_MAX_SPEED; + + if (right_speed.positive > STE_AUDIOIO_VIBRATOR_MAX_SPEED) + right_speed.positive = STE_AUDIOIO_VIBRATOR_MAX_SPEED; + + if (left_speed.negative > STE_AUDIOIO_VIBRATOR_MAX_SPEED) + left_speed.negative = STE_AUDIOIO_VIBRATOR_MAX_SPEED; + + if (right_speed.negative > STE_AUDIOIO_VIBRATOR_MAX_SPEED) + right_speed.negative = STE_AUDIOIO_VIBRATOR_MAX_SPEED; + + if (left_speed.negative || right_speed.negative || + left_speed.positive || right_speed.positive) { + /* Power up audio block for vibrator */ + error = ste_audio_io_core_api_powerup_audiocodec( + STE_AUDIOIO_POWER_VIBRA); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Audio power up failed %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + (EN_VIBL_MASK|EN_VIBR_MASK), 0); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Powerup Vibrator Class-D driver %d", + error); + return error; + } + + error = HW_REG_WRITE(VIB_DRIVER_CONF_REG, 0xff); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Enable Vibrator PWM generator %d", + error); + return error; + } + } + + error = HW_REG_WRITE(PWM_VIBNL_CONF_REG, left_speed.negative); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Write Left Vibrator negative PWM %d", error); + goto err_cleanup; + } + + error = HW_REG_WRITE(PWM_VIBPL_CONF_REG, left_speed.positive); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Write Left Vibrator positive PWM %d", error); + goto err_cleanup; + } + + error = HW_REG_WRITE(PWM_VIBNR_CONF_REG, right_speed.negative); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Write Right Vibrator negative PWM %d", error); + goto err_cleanup; + } + + error = HW_REG_WRITE(PWM_VIBPR_CONF_REG, right_speed.positive); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Write Right Vibrator positive PWM %d", error); + goto err_cleanup; + } + + if (!left_speed.negative && !right_speed.negative && + !left_speed.positive && !right_speed.positive) { + error = HW_REG_WRITE(VIB_DRIVER_CONF_REG, 0); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Disable PWM Vibrator generator %d", + error); + goto err_cleanup; + } + + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + 0, (EN_VIBL_MASK|EN_VIBR_MASK)); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Power down Vibrator Class-D driver %d", + error); + goto err_cleanup; + } + + /* Power down audio block */ + error = ste_audio_io_core_api_powerdown_audiocodec( + STE_AUDIOIO_POWER_VIBRA); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "Audio power down failed %d", error); + goto err_cleanup; + } + } + +err_cleanup: + /* Release client */ + if (!left_speed.negative && !right_speed.negative && + !left_speed.positive && !right_speed.positive) { + mutex_lock(&ptr_audio_codec_cnxt->audio_io_mutex); + ste_audioio_vibrator_release(client); + mutex_unlock(&ptr_audio_codec_cnxt->audio_io_mutex); + } + return error; +} +EXPORT_SYMBOL(ste_audioio_vibrator_pwm_control); + +/** + * @brief This function sets FIR coefficients + * @fir_coeffs: pointer to structure audioio_fir_coefficients_t + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_core_api_fir_coeffs_control(struct audioio_fir_coefficients_t + *fir_coeffs) +{ + unsigned char coefficient; + int i, error; + + if (fir_coeffs->start_addr >= STE_AUDIOIO_MAX_COEFFICIENTS) + return -EINVAL; + + mutex_lock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + + error = HW_REG_WRITE(SIDETONE_FIR_ADDR_REG, fir_coeffs->start_addr); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "FIR start address write failed %d", error); + goto err_cleanup; + } + + for (i = fir_coeffs->start_addr; + i < STE_AUDIOIO_MAX_COEFFICIENTS; i++) { + + coefficient = (fir_coeffs->coefficients[i]>>8) & 0xff; + error = HW_REG_WRITE(SIDETONE_FIR_COEFF_MSB_REG, coefficient); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "FIR coefficient [%d] msb write failed %d", i, error); + goto err_cleanup; + } + + coefficient = fir_coeffs->coefficients[i] & 0xff; + error = HW_REG_WRITE(SIDETONE_FIR_COEFF_LSB_REG, coefficient); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "FIR coefficient [%d] lsb write failed %d", i, error); + goto err_cleanup; + } + } + + error = HW_ACODEC_MODIFY_WRITE(SIDETONE_FIR_ADDR_REG, + APPLY_FIR_COEFFS_MASK, 0); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "FIR coefficients activation failed %d", error); + goto err_cleanup; + } + + error = HW_ACODEC_MODIFY_WRITE(FILTERS_CONTROL_REG, + FIR_FILTERCONTROL, 0); + if (error) { + dev_err(ptr_audio_codec_cnxt->dev, + "ST FIR Filters enable failed %d", error); + goto err_cleanup; + } + +err_cleanup: + mutex_unlock(&(ptr_audio_codec_cnxt->audio_io_mutex)); + return error; +} diff --git a/drivers/misc/audio_io_dev/ste_audio_io_core.h b/drivers/misc/audio_io_dev/ste_audio_io_core.h new file mode 100644 index 00000000000..7d109eb9e83 --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_core.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#ifndef _AUDIOIO_CORE_H_ +#define _AUDIOIO_CORE_H_ + +#include +#include "ste_audio_io_func.h" +#include "ste_audio_io_hwctrl_common.h" + +#define MAX_NO_CHANNELS 4 + +#define STE_AUDIOIO_POWER_AUDIO 1 +#define STE_AUDIOIO_POWER_VIBRA 2 + +struct transducer_context_t { + /* public variables */ + int gain[MAX_NO_CHANNELS]; + int is_muted[MAX_NO_CHANNELS]; + int is_power_up[MAX_NO_CHANNELS]; + /* public funcs */ + int (*pwr_up_func)(enum AUDIOIO_CH_INDEX, struct device *); + int (*pwr_down_func)(enum AUDIOIO_CH_INDEX, struct device *); + int (*pwr_state_func)(struct device *); + int (*set_gain_func)(enum AUDIOIO_CH_INDEX, u16, int, u32, + struct device *); + int (*get_gain_func)(int *, int *, u16, struct device *); + int (*mute_func)(enum AUDIOIO_CH_INDEX, struct device *); + int (*unmute_func)(enum AUDIOIO_CH_INDEX, int *, struct device *); + int (*mute_state_func)(struct device *); + int (*enable_fade_func)(struct device *); + int (*disable_fade_func)(struct device *); + int (*switch_to_burst_func)(int, struct device *); + int (*switch_to_normal_func)(struct device *); + int (*enable_loop)(enum AUDIOIO_CH_INDEX, enum AUDIOIO_HAL_HW_LOOPS, + int, struct device *, void *); + int (*disable_loop)(enum AUDIOIO_CH_INDEX, enum AUDIOIO_HAL_HW_LOOPS, + struct device *, void *); +}; + +struct audiocodec_context_t { + int audio_codec_powerup; + int power_client; + int vibra_client; + struct mutex audio_io_mutex; + struct mutex vibrator_mutex; + struct transducer_context_t *transducer[MAX_NO_TRANSDUCERS]; + struct device *dev; + int (*gpio_altf_init) (void); + int (*gpio_altf_exit) (void); +}; + + +int ste_audio_io_core_api_access_read(struct audioio_data_t *dev_data); + +int ste_audio_io_core_api_access_write(struct audioio_data_t *dev_data); + +int ste_audio_io_core_api_power_control_transducer( + struct audioio_pwr_ctrl_t *pwr_ctrl); + +int ste_audio_io_core_api_power_status_transducer( + struct audioio_pwr_ctrl_t *pwr_ctrl); + +int ste_audio_io_core_api_loop_control(struct audioio_loop_ctrl_t *loop_ctrl); + +int ste_audio_io_core_api_loop_status(struct audioio_loop_ctrl_t *loop_ctrl); + +int ste_audio_io_core_api_get_transducer_gain_capability( + struct audioio_get_gain_t *get_gain); + +int ste_audio_io_core_api_gain_capabilities_loop( + struct audioio_gain_loop_t *gain_loop); + +int ste_audio_io_core_api_supported_loops( + struct audioio_support_loop_t *support_loop); + +int ste_audio_io_core_api_gain_descriptor_transducer( + struct audioio_gain_desc_trnsdr_t *gdesc_trnsdr); + +int ste_audio_io_core_api_gain_control_transducer( + struct audioio_gain_ctrl_trnsdr_t *gctrl_trnsdr); + +int ste_audio_io_core_api_gain_query_transducer( + struct audioio_gain_ctrl_trnsdr_t *gctrl_trnsdr); + +int ste_audio_io_core_api_mute_control_transducer( + struct audioio_mute_trnsdr_t *mute_trnsdr); + +int ste_audio_io_core_api_mute_status_transducer( + struct audioio_mute_trnsdr_t *mute_trnsdr); + +int ste_audio_io_core_api_fading_control(struct audioio_fade_ctrl_t *fade_ctrl); + +int ste_audio_io_core_api_burstmode_control( + struct audioio_burst_ctrl_t *burst_ctrl); + +int ste_audio_io_core_api_powerup_audiocodec(int power_client); + +int ste_audio_io_core_api_powerdown_audiocodec(int power_client); + +int ste_audio_io_core_api_init_data(struct platform_device *pdev); + +bool ste_audio_io_core_is_ready_for_suspend(void); +void ste_audio_io_core_api_free_data(void); + +int ste_audio_io_core_api_fsbitclk_control( + struct audioio_fsbitclk_ctrl_t *fsbitclk_ctrl); +int ste_audio_io_core_api_pseudoburst_control( + struct audioio_pseudoburst_ctrl_t *pseudoburst_ctrl); + +void ste_audio_io_core_api_store_data(enum AUDIOIO_CH_INDEX channel_index, + int *ptr, int value); + +int ste_audioio_vibrator_alloc(int client, int mask); + +void ste_audioio_vibrator_release(int client); + +enum AUDIOIO_COMMON_SWITCH ste_audio_io_core_api_get_status( + enum AUDIOIO_CH_INDEX channel_index, int *ptr); + +int ste_audio_io_core_api_acodec_power_control(struct audioio_acodec_pwr_ctrl_t + *audio_acodec_pwr_ctrl); + +int ste_audio_io_core_api_fir_coeffs_control(struct audioio_fir_coefficients_t + *fir_coeffs); + +int ste_audio_io_core_debug(int x); + +#endif /* _AUDIOIO_CORE_H_ */ + diff --git a/drivers/misc/audio_io_dev/ste_audio_io_dev.c b/drivers/misc/audio_io_dev/ste_audio_io_dev.c new file mode 100644 index 00000000000..8b677909104 --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_dev.c @@ -0,0 +1,738 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ste_audio_io_dev.h" + +#define STR_DEBUG_ON "debug on" +#define AUDIOIO_DEVNAME "ab8500-codec" + +static int ste_audio_io_open(struct inode *inode, struct file *filp); +static int ste_audio_io_release(struct inode *inode, struct file *filp); +static long ste_audio_io_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +static int ste_audio_io_cmd_parser(unsigned int cmd, unsigned long arg); +static ssize_t ste_audio_io_write(struct file *filp, + const char __user *buf, size_t count, loff_t *f_pos); + + +/** + * @brief Check IOCTL type, command no and access direction + * @ inode value corresponding to the file descriptor + * @file value corresponding to the file descriptor + * @cmd IOCTL command code + * @arg Command argument + * @return 0 on success otherwise negative error code + */ +static long ste_audio_io_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + int err = 0; + + /* Check type and command number */ + if (_IOC_TYPE(cmd) != AUDIOIO_IOC_MAGIC) + return -ENOTTY; + + /* IOC_DIR is from the user perspective, while access_ok is + * from the kernel perspective; so they look reversed. + */ + if (_IOC_DIR(cmd) & _IOC_READ) + err = !access_ok(VERIFY_WRITE, (void __user *)arg, + _IOC_SIZE(cmd)); + if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, (void __user *)arg, + _IOC_SIZE(cmd)); + if (err) + return -EFAULT; + + retval = ste_audio_io_cmd_parser(cmd, arg); + + return retval; +} +/** + * @brief IOCTL call to read the value from AB8500 device + * @cmd IOCTL command code + * @arg Command argument + * @return 0 on success otherwise negative error code + */ + +static int process_read_register_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_data_t *audio_dev_data; + + audio_dev_data = (struct audioio_data_t *)&cmd_data; + + if (copy_from_user(audio_dev_data, (void __user *)arg, + sizeof(struct audioio_data_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_access_read(audio_dev_data); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_dev_data, + sizeof(struct audioio_data_t))) + return -EFAULT; + return 0; +} +/** + * @brief IOCTL call to write the given value to the AB8500 device + * @cmd IOCTL command code + * @arg Command argument + * @return 0 on success otherwise negative error code + */ + +static int process_write_register_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_data_t *audio_dev_data; + + audio_dev_data = (struct audioio_data_t *)&cmd_data; + + if (copy_from_user(audio_dev_data, (void __user *)arg, + sizeof(struct audioio_data_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_access_write(audio_dev_data); + + return retval; +} +/** + * @brief IOCTL call to control the power on/off of hardware components + * @cmd IOCTL command code + * @arg Command argument + * @return 0 on success otherwise negative error code + */ + +static int process_pwr_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_pwr_ctrl_t *audio_pwr_ctrl; + + audio_pwr_ctrl = (struct audioio_pwr_ctrl_t *)&cmd_data; + + if (copy_from_user(audio_pwr_ctrl, (void __user *)arg, + sizeof(struct audioio_pwr_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_power_control_transducer(audio_pwr_ctrl); + + return retval; +} + +static int process_pwr_sts_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_pwr_ctrl_t *audio_pwr_sts; + + audio_pwr_sts = (struct audioio_pwr_ctrl_t *)&cmd_data; + + if (copy_from_user(audio_pwr_sts, (void __user *)arg, + sizeof(struct audioio_pwr_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_power_status_transducer(audio_pwr_sts); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_pwr_sts, + sizeof(struct audioio_pwr_ctrl_t))) + return -EFAULT; + + return 0; +} + +static int process_lp_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_loop_ctrl_t *audio_lp_ctrl; + + audio_lp_ctrl = (struct audioio_loop_ctrl_t *)&cmd_data; + + if (copy_from_user(audio_lp_ctrl, (void __user *)arg, + sizeof(struct audioio_loop_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_loop_control(audio_lp_ctrl); + + return retval; +} + +static int process_lp_sts_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_loop_ctrl_t *audio_lp_sts; + + audio_lp_sts = (struct audioio_loop_ctrl_t *)&cmd_data; + + + if (copy_from_user(audio_lp_sts, (void __user *)arg, + sizeof(struct audioio_loop_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_loop_status(audio_lp_sts); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_lp_sts, + sizeof(struct audioio_loop_ctrl_t))) + return -EFAULT; + return 0; +} + +static int process_get_trnsdr_gain_capability_cmd(unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_get_gain_t *audio_trnsdr_gain; + + audio_trnsdr_gain = (struct audioio_get_gain_t *)&cmd_data; + + if (copy_from_user(audio_trnsdr_gain, (void __user *)arg, + sizeof(struct audioio_get_gain_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_get_transducer_gain_capability( + audio_trnsdr_gain); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_trnsdr_gain, + sizeof(struct audioio_get_gain_t))) + return -EFAULT; + return 0; +} + +static int process_gain_cap_loop_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_gain_loop_t *audio_gain_loop; + + audio_gain_loop = (struct audioio_gain_loop_t *)&cmd_data; + + if (copy_from_user(audio_gain_loop, (void __user *)arg, + sizeof(struct audioio_gain_loop_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_gain_capabilities_loop(audio_gain_loop); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_gain_loop, + sizeof(struct audioio_gain_loop_t))) + return -EFAULT; + return 0; +} + + +static int process_support_loop_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_support_loop_t *audio_spprt_loop; + + audio_spprt_loop = (struct audioio_support_loop_t *)&cmd_data; + + if (copy_from_user(audio_spprt_loop, (void __user *)arg, + sizeof(struct audioio_support_loop_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_supported_loops(audio_spprt_loop); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_spprt_loop, + sizeof(struct audioio_support_loop_t))) + return -EFAULT; + return 0; +} + + +static int process_gain_desc_trnsdr_cmd(unsigned int cmd, unsigned long arg) + +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_gain_desc_trnsdr_t *audio_gain_desc; + + audio_gain_desc = (struct audioio_gain_desc_trnsdr_t *)&cmd_data; + + if (copy_from_user(audio_gain_desc, (void __user *)arg, + sizeof(struct audioio_gain_desc_trnsdr_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_gain_descriptor_transducer( + audio_gain_desc); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_gain_desc, + sizeof(struct audioio_gain_desc_trnsdr_t))) + return -EFAULT; + return 0; +} + + +static int process_gain_ctrl_trnsdr_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_gain_ctrl_trnsdr_t *audio_gain_ctrl; + + audio_gain_ctrl = (struct audioio_gain_ctrl_trnsdr_t *)&cmd_data; + + if (copy_from_user(audio_gain_ctrl, (void __user *)arg, + sizeof(struct audioio_gain_ctrl_trnsdr_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_gain_control_transducer( + audio_gain_ctrl); + + return retval; +} + +static int process_gain_query_trnsdr_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_gain_ctrl_trnsdr_t *audio_gain_query; + + audio_gain_query = (struct audioio_gain_ctrl_trnsdr_t *)&cmd_data; + + if (copy_from_user(audio_gain_query, (void __user *)arg, + sizeof(struct audioio_gain_ctrl_trnsdr_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_gain_query_transducer(audio_gain_query); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_gain_query, + sizeof(struct audioio_gain_ctrl_trnsdr_t))) + return -EFAULT; + return 0; +} + +static int process_mute_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_mute_trnsdr_t *audio_mute_ctrl; + + audio_mute_ctrl = (struct audioio_mute_trnsdr_t *)&cmd_data; + if (copy_from_user(audio_mute_ctrl , (void __user *)arg, + sizeof(struct audioio_mute_trnsdr_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_mute_control_transducer( + audio_mute_ctrl); + + return retval; +} + +static int process_mute_sts_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_mute_trnsdr_t *audio_mute_sts; + + audio_mute_sts = (struct audioio_mute_trnsdr_t *)&cmd_data; + + if (copy_from_user(audio_mute_sts, (void __user *)arg, + sizeof(struct audioio_mute_trnsdr_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_mute_status_transducer(audio_mute_sts); + if (0 != retval) + return retval; + + if (copy_to_user((void __user *)arg, audio_mute_sts, + sizeof(struct audioio_mute_trnsdr_t))) + return -EFAULT; + return 0; +} + +static int process_fade_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_fade_ctrl_t *audio_fade; + audio_fade = (struct audioio_fade_ctrl_t *)&cmd_data; + + if (copy_from_user(audio_fade , (void __user *)arg, + sizeof(struct audioio_fade_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_fading_control(audio_fade); + + return retval; +} + +static int process_burst_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_burst_ctrl_t *audio_burst; + + audio_burst = (struct audioio_burst_ctrl_t *)&cmd_data; + if (copy_from_user(audio_burst , (void __user *)arg, + sizeof(struct audioio_burst_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_burstmode_control(audio_burst); + + return retval; + + return 0; +} + +static int process_fsbitclk_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_fsbitclk_ctrl_t *audio_fsbitclk; + + audio_fsbitclk = (struct audioio_fsbitclk_ctrl_t *)&cmd_data; + + if (copy_from_user(audio_fsbitclk , (void __user *)arg, + sizeof(struct audioio_fsbitclk_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_fsbitclk_control(audio_fsbitclk); + + return retval; + + return 0; + +} + +static int process_pseudoburst_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_pseudoburst_ctrl_t *audio_pseudoburst; + + audio_pseudoburst = (struct audioio_pseudoburst_ctrl_t *)&cmd_data; + + if (copy_from_user(audio_pseudoburst , (void __user *)arg, + sizeof(struct audioio_pseudoburst_ctrl_t))) + return -EFAULT; + + retval = ste_audio_io_core_api_pseudoburst_control(audio_pseudoburst); + + return retval; + + return 0; + +} +static int process_audiocodec_pwr_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + union audioio_cmd_data_t cmd_data; + struct audioio_acodec_pwr_ctrl_t *audio_acodec_pwr_ctrl; + audio_acodec_pwr_ctrl = (struct audioio_acodec_pwr_ctrl_t *)&cmd_data; + if (copy_from_user(audio_acodec_pwr_ctrl, (void __user *)arg, + sizeof(struct audioio_acodec_pwr_ctrl_t))) + return -EFAULT; + retval = ste_audio_io_core_api_acodec_power_control( + audio_acodec_pwr_ctrl); + return retval; +} + +static int process_fir_coeffs_ctrl_cmd(unsigned int cmd, unsigned long arg) +{ + int retval; + struct audioio_fir_coefficients_t *cmd_data; + cmd_data = kmalloc(sizeof(struct audioio_fir_coefficients_t), + GFP_KERNEL); + if (!cmd_data) + return -ENOMEM; + if (copy_from_user(cmd_data, (void __user *)arg, + sizeof(struct audioio_fir_coefficients_t))) { + kfree(cmd_data); + return -EFAULT; + } + retval = ste_audio_io_core_api_fir_coeffs_control(cmd_data); + kfree(cmd_data); + return retval; +} + +static int ste_audio_io_cmd_parser(unsigned int cmd, unsigned long arg) +{ + int retval = 0; + + switch (cmd) { + case AUDIOIO_READ_REGISTER: + retval = process_read_register_cmd(cmd, arg); + break; + + case AUDIOIO_WRITE_REGISTER: + retval = process_write_register_cmd(cmd, arg); + break; + + case AUDIOIO_PWR_CTRL_TRNSDR: + retval = process_pwr_ctrl_cmd(cmd, arg); + break; + + case AUDIOIO_PWR_STS_TRNSDR: + retval = process_pwr_sts_cmd(cmd, arg); + break; + + case AUDIOIO_LOOP_CTRL: + retval = process_lp_ctrl_cmd(cmd, arg); + break; + + case AUDIOIO_LOOP_STS: + retval = process_lp_sts_cmd(cmd, arg); + break; + + case AUDIOIO_GET_TRNSDR_GAIN_CAPABILITY: + retval = process_get_trnsdr_gain_capability_cmd(cmd, arg); + break; + + case AUDIOIO_GAIN_CAP_LOOP: + retval = process_gain_cap_loop_cmd(cmd, arg); + break; + + case AUDIOIO_SUPPORT_LOOP: + retval = process_support_loop_cmd(cmd, arg); + break; + + case AUDIOIO_GAIN_DESC_TRNSDR: + retval = process_gain_desc_trnsdr_cmd(cmd, arg); + break; + + case AUDIOIO_GAIN_CTRL_TRNSDR: + retval = process_gain_ctrl_trnsdr_cmd(cmd, arg); + break; + + case AUDIOIO_GAIN_QUERY_TRNSDR: + retval = process_gain_query_trnsdr_cmd(cmd, arg); + break; + + case AUDIOIO_MUTE_CTRL_TRNSDR: + retval = process_mute_ctrl_cmd(cmd, arg); + break; + + case AUDIOIO_MUTE_STS_TRNSDR: + retval = process_mute_sts_cmd(cmd, arg); + break; + + case AUDIOIO_FADE_CTRL: + retval = process_fade_cmd(cmd, arg); + break; + + case AUDIOIO_BURST_CTRL: + retval = process_burst_ctrl_cmd(cmd, arg); + break; + + case AUDIOIO_FSBITCLK_CTRL: + retval = process_fsbitclk_ctrl_cmd(cmd, arg); + break; + + case AUDIOIO_PSEUDOBURST_CTRL: + retval = process_pseudoburst_ctrl_cmd(cmd, arg); + break; + + case AUDIOIO_AUDIOCODEC_PWR_CTRL: + retval = process_audiocodec_pwr_ctrl_cmd(cmd, arg); + break; + + case AUDIOIO_FIR_COEFFS_CTRL: + retval = process_fir_coeffs_ctrl_cmd(cmd, arg); + break; + } + return retval; +} + +static int ste_audio_io_open(struct inode *inode, struct file *filp) +{ + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + return 0; +} + +static int ste_audio_io_release(struct inode *inode, struct file *filp) +{ + module_put(THIS_MODULE); + return 0; +} + +static ssize_t ste_audio_io_write(struct file *filp, + const char __user *buf, size_t count, loff_t *f_pos) +{ + char *x = kmalloc(count, GFP_KERNEL); + int debug_flag = 0; + + if (copy_from_user(x, buf, count)) + return -EFAULT; + + if (count >= strlen(STR_DEBUG_ON)) { + + if (!strncmp(STR_DEBUG_ON, x, strlen(STR_DEBUG_ON))) + debug_flag = 1; + else + debug_flag = 0; + } + + ste_audio_io_core_debug(debug_flag); + + kfree(x); + + return count; +} + +static const struct file_operations ste_audio_io_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ste_audio_io_ioctl, + .open = ste_audio_io_open, + .release = ste_audio_io_release, + .write = ste_audio_io_write, +}; + +/** + * audio_io_misc_dev - Misc device config for audio_io + */ +static struct miscdevice audio_io_misc_dev = { + MISC_DYNAMIC_MINOR, + "audioio", + &ste_audio_io_fops +}; + +/** + * ste_audio_io_probe() - probe the device + * @pdev: pointer to the platform device structure + * + * This funtion is called after the driver is registered to platform + * device framework. It does allocate the memory for the internal + * data structure and intialized core APIs. + */ +static int ste_audio_io_drv_probe(struct platform_device *pdev) +{ + int error; + + ste_audio_io_device = pdev; + + dev_dbg(&ste_audio_io_device->dev, "ste_audio_io device probe\n"); + + error = misc_register(&audio_io_misc_dev); + if (error) { + printk(KERN_WARNING "%s: registering misc device failed\n", + __func__); + return error; + } + + error = ste_audio_io_core_api_init_data(ste_audio_io_device); + if (error < 0) { + dev_err(&ste_audio_io_device->dev, + "ste_audioio_core_api_init_data failed err = %d", + error); + goto ste_audio_io_misc_deregister; + } + return 0; + +ste_audio_io_misc_deregister: + misc_deregister(&audio_io_misc_dev); + return error; +} + +/** + * ste_audio_io_remove() - Removes the device + * @pdev: pointer to the platform_device structure + * + * This function is called when this mnodule is removed using rmmod + */ +static int ste_audio_io_drv_remove(struct platform_device *pdev) +{ + ste_audio_io_core_api_free_data(); + misc_deregister(&audio_io_misc_dev); + return 0; +} + +/** + * ste_audio_io_drv_suspend - suspend audio_io + * @pdev: platform data + * @state: power down level + */ +static int ste_audio_io_drv_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (ste_audio_io_core_is_ready_for_suspend()) + return 0; + else + return -EINVAL; +} + +/** + * ste_audio_io_drv_resume - put back audio_io in the normal state + * @pdev: platform data + */ +static int ste_audio_io_drv_resume(struct platform_device *pdev) +{ + return 0; +} + +/** + * struct audio_io_driver: audio_io platform structure + * @probe: The probe funtion to be called + * @remove: The remove funtion to be called + * @resume: The resume function to be called + * @suspend: The suspend function to be called + * @driver: The driver data + */ +static struct platform_driver ste_audio_io_driver = { + .probe = ste_audio_io_drv_probe, + .remove = ste_audio_io_drv_remove, + .driver = { + .name = AUDIOIO_DEVNAME, + .owner = THIS_MODULE, + }, + .suspend = ste_audio_io_drv_suspend, + .resume = ste_audio_io_drv_resume, +}; + +/** Pointer to platform device needed to access abx500 core functions */ +struct platform_device *ste_audio_io_device; + +static int __init ste_audio_io_init(void) +{ + return platform_driver_register(&ste_audio_io_driver); +} +module_init(ste_audio_io_init); + +static void __exit ste_audio_io_exit(void) +{ + platform_driver_unregister(&ste_audio_io_driver); +} +module_exit(ste_audio_io_exit); + +MODULE_AUTHOR("Deepak KARDA "); +MODULE_DESCRIPTION("STE_AUDIO_IO"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/audio_io_dev/ste_audio_io_dev.h b/drivers/misc/audio_io_dev/ste_audio_io_dev.h new file mode 100644 index 00000000000..bcb9dce3ad2 --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_dev.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#ifndef _AUDIOIO_DEV_H_ +#define _AUDIOIO_DEV_H_ + +#include +#include "ste_audio_io_core.h" + +union audioio_cmd_data_t { + struct audioio_burst_ctrl_t audioio_burst_ctrl; + struct audioio_fade_ctrl_t audioio_fade_ctrl; + struct audioio_mute_trnsdr_t audioio_mute_trnsdr; + struct audioio_gain_ctrl_trnsdr_t audioio_gain_ctrl_trnsdr; + struct audioio_gain_desc_trnsdr_t audioio_gain_desc_trnsdr; + struct audioio_support_loop_t audioio_support_loop; + struct audioio_gain_loop_t audioio_gain_loop; + struct audioio_get_gain_t audioio_get_gain; + struct audioio_loop_ctrl_t audioio_loop_ctrl; + struct audioio_pwr_ctrl_t audioio_pwr_ctrl; + struct audioio_data_t audioio_data; + struct audioio_fsbitclk_ctrl_t audioio_fsbitclk_ctrl; + struct audioio_acodec_pwr_ctrl_t audioio_acodec_pwr_ctrl; + struct audioio_pseudoburst_ctrl_t audioio_pseudoburst_ctrl; +}; + + +#endif /* _AUDIOIO_DEV_H_ */ + diff --git a/drivers/misc/audio_io_dev/ste_audio_io_func.c b/drivers/misc/audio_io_dev/ste_audio_io_func.c new file mode 100644 index 00000000000..7238085938e --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_func.c @@ -0,0 +1,4371 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "ste_audio_io_func.h" +#include "ste_audio_io_core.h" +#include "ste_audio_io_ab8500_reg_defs.h" +#include "ste_audio_io_hwctrl_common.h" + +static struct clk *clk_ptr_msp0; +static int bluetooth_power_up_count; +static int acodec_reg_dump; + +#define NCP_TIMEOUT 200 /* 200 ms */ +/* + * TODO: Use proper register defines instead of home-made generic ones. + */ +#define SHIFT_QUARTET0 0 +#define SHIFT_QUARTET1 4 +#define MASK_QUARTET (0xFUL) +#define MASK_QUARTET1 (MASK_QUARTET << SHIFT_QUARTET1) +#define MASK_QUARTET0 (MASK_QUARTET << SHIFT_QUARTET0) + +/** + * @brief Modify the specified register + * @reg Register + * @mask_set Bit to be set + * @mask_clear Bit to be cleared + * @return 0 on success otherwise negative error code + */ + +unsigned int ab8500_acodec_modify_write(unsigned int reg, u8 mask_set, + u8 mask_clear) +{ + u8 value8, retval = 0; + value8 = HW_REG_READ(reg); + /* clear the specified bit */ + value8 &= ~mask_clear; + /* set the asked bit */ + value8 |= mask_set; + retval = HW_REG_WRITE(reg, value8); + return retval; +} + +/** + * @brief Power up headset on a specific channel + * @channel_index Channel-index of headset + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_up_headset(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + unsigned long end_time; + + /* Check if HS PowerUp request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "HS should have mono or stereo channels"); + return -EINVAL; + } + + ste_audio_io_mute_headset(channel_index, dev); + + error = HW_ACODEC_MODIFY_WRITE(NCP_ENABLE_HS_AUTOSTART_REG, + HS_AUTO_EN, 0); + if (0 != error) { + dev_err(dev, "NCP fully controlled with EnCpHs bit %d", error); + return error; + } + error = HW_ACODEC_MODIFY_WRITE(NCP_ENABLE_HS_AUTOSTART_REG, + (EN_NEG_CP|HS_AUTO_EN), 0); + if (0 != error) { + dev_err(dev, "Enable Negative Charge Pump %d", error); + return error; + } + + /* Wait for negative charge pump to start */ + end_time = jiffies + msecs_to_jiffies(NCP_TIMEOUT); + while (!(HW_REG_READ(IRQ_STATUS_MSB_REG) & NCP_READY_MASK) + && time_after_eq(end_time, jiffies)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } + + if (!(HW_REG_READ(IRQ_STATUS_MSB_REG) & NCP_READY_MASK)) { + error = -EFAULT; + dev_err(dev, "Negative Charge Pump start error % d", error); + return error; + } + + /* Enable DA1 for HSL */ + if (channel_index & e_CHANNEL_1) { + + /* Power Up HSL driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + EN_HSL_MASK, 0); + if (0 != error) { + dev_err(dev, "Power Up HSL Driver %d", error); + return error; + } + + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + if (EN_DA1 & initialVal_DA) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA1_REG, + SLOT08_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA1 from Slot 08 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, + DA1_TO_HSL, 0); + if (0 != error) { + dev_err(dev, + "DA_IN1 path mixed with sidetone FIR %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + EN_DA1, 0); + if (0 != error) { + dev_err(dev, "Power up HSL %d ", error); + return error; + } + + /* Power Up HSL DAC driver */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, + POWER_UP_HSL_DAC, 0); + if (0 != error) { + dev_err(dev, "Power Up HSL DAC driver %d", error); + return error; + } + + /* Power up HSL DAC and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, + EN_HSL_MASK, 0); + if (0 != error) { + dev_err(dev, + "Power up HSL DAC and digital path %d", + error); + return error; + } + + /* + * Disable short detection. Pull Down output to ground, + * Use local oscillator, Gain change without zero cross control + */ + error = HW_ACODEC_MODIFY_WRITE(SHORT_CIRCUIT_DISABLE_REG, + HS_SHORT_DIS|HS_PULL_DOWN_EN|HS_OSC_EN|HS_ZCD_DIS, 0); + if (0 != error) { + dev_err(dev, "Disable short detection." + "Pull Down output to ground,Use local oscillator,Gain" + "change without zero cross control %d", error); + return error; + } + } + + /* Enable DA2 for HSR */ + if (channel_index & e_CHANNEL_2) { + + /* Power Up HSR driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + EN_HSR_MASK, 0); + if (0 != error) { + dev_err(dev, "Power Up HSR Driver %d", error); + return error; + } + + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (EN_DA2 & initialVal_DA) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA2_REG, + SLOT09_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, + "Data sent to DA2 from Slot 09 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, DA2_TO_HSR, + 0); + if (0 != error) { + dev_err(dev, + "DA_IN2 path mixed with sidetone FIR %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + EN_DA2, 0); + if (0 != error) { + dev_err(dev, "Power up HSR %d ", error); + return error; + } + + /* Power Up HSR DAC driver */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, + POWER_UP_HSR_DAC, 0); + if (0 != error) { + dev_err(dev, "Power Up HSR DAC driver %d", error); + return error; + } + + /* Power up HSR DAC and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, + EN_HSR_MASK, 0); + if (0 != error) { + dev_err(dev, + "Power up HSR DAC and digital path %d", + error); + return error; + } + + /* + * TEST START .havent cleared the bits in power down.Disable short + * detection. Pull Down output to ground, Use local oscillator, + * Gain change without zero cross control + */ + + error = HW_ACODEC_MODIFY_WRITE(SHORT_CIRCUIT_DISABLE_REG, + HS_SHORT_DIS|HS_PULL_DOWN_EN|HS_OSC_EN|HS_ZCD_DIS, 0); + if (0 != error) { + dev_err(dev, "Disable short detection." + "Pull Down output to ground, Use local oscillator," + "Gain change without zero cross control %d", error); + return error; + } + /* TEST END */ + } + ste_audio_io_unmute_headset(channel_index, 0, dev); + dump_acodec_registers(__func__, dev); + return error; +} + +/** + * @brief Power down headset on a specific channel + * @channel_index Channel-index of headset + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_down_headset(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + unsigned long end_time; + + /* Check if HS Power Down request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "HS should have mono or stereo channels"); + return -EINVAL; + } + + /* Disable Negative Charge Pump */ + error = HW_ACODEC_MODIFY_WRITE(NCP_ENABLE_HS_AUTOSTART_REG, + (EN_NEG_CP|HS_AUTO_EN), 0); + if (0 != error) { + dev_err(dev, "NCP not fully controlled with EnCpHs bit %d", + error); + return error; + } + error = HW_ACODEC_MODIFY_WRITE(NCP_ENABLE_HS_AUTOSTART_REG, 0, + EN_NEG_CP); + if (0 != error) { + dev_err(dev, "Disable Negative Charge Pump %d", error); + return error; + } + + /* Wait for negative charge pump to stop */ + end_time = jiffies + msecs_to_jiffies(NCP_TIMEOUT); + while ((HW_REG_READ(IRQ_STATUS_MSB_REG) & NCP_READY_MASK) + && time_after_eq(end_time, jiffies)) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } + + if (HW_REG_READ(IRQ_STATUS_MSB_REG) & NCP_READY_MASK) { + error = -EFAULT; + dev_err(dev, "Negative Charge Pump stop error % d", error); + return error; + } + + if (channel_index & e_CHANNEL_1) { + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (!(initialVal_DA & EN_DA1)) + return 0; + + /* Power Down HSL driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, 0, + EN_HSL_MASK); + if (0 != error) { + dev_err(dev, "Power down HSL Driver %d", error); + return error; + } + + /* Power Down HSL DAC driver */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, + POWER_UP_HSL_DAC); + if (0 != error) { + dev_err(dev, "Power Up HSL DAC Driver %d", error); + return error; + } + + /* Power Down HSL DAC and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, 0, + EN_HSL_MASK); + if (0 != error) { + dev_err(dev, + "Power down HSL DAC and digital path %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + 0, EN_DA1); + if (0 != error) { + dev_err(dev, "Disable DA1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, + 0, DA1_TO_HSL); + if (0 != error) { + dev_err(dev, + "Clear DA_IN1 path mixed with sidetone FIR %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA1_REG, 0, + SLOT08_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Data sent to DA1 cleared from Slot 08 %d", + error); + return error; + } + + + } + /* Enable DA2 for HSR */ + + if (channel_index & e_CHANNEL_2) { + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (!(initialVal_DA & EN_DA2)) + return 0; + + /* Power Down HSR driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, 0, + EN_HSR_MASK); + if (0 != error) { + dev_err(dev, "Power down HSR Driver %d", error); + return error; + } + + /* Power Down HSR DAC driver */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, + POWER_UP_HSR_DAC); + if (0 != error) { + dev_err(dev, "Power down HSR DAC Driver %d", error); + return error; + } + + /* Power Down HSR DAC and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, 0, + EN_HSR_MASK); + if (0 != error) { + dev_err(dev, + "Power down HSR DAC and digital path %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + 0, EN_DA2); + if (0 != error) { + dev_err(dev, "Disable DA2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, 0, + DA2_TO_HSR); + if (0 != error) { + dev_err(dev, + "Clear DA_IN2 path mixed with sidetone FIR %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA2_REG, 0, + SLOT09_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Data sent to DA2 cleared from Slot 09 %d", + error); + return error; + } + + } + dump_acodec_registers(__func__, dev); + return error; +} + +/** + * @brief Mute headset on a specific channel + * @channel_index Headeset channel-index + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_mute_headset(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + /* Check if HS Mute request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "HS should have mono or stereo channels"); + return -EINVAL; + } + + if (channel_index & e_CHANNEL_1) { + /* Mute HSL */ + error = HW_ACODEC_MODIFY_WRITE(MUTE_HS_EAR_REG, + EN_HSL_MASK | EN_HSL_DAC_MASK, + 0); + if (0 != error) { + dev_err(dev, "Mute HSL %d", error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + /* Mute HSR */ + error = HW_ACODEC_MODIFY_WRITE(MUTE_HS_EAR_REG, + EN_HSR_MASK | EN_HSR_DAC_MASK, + 0); + if (0 != error) { + dev_err(dev, "Mute HSR %d", error); + return error; + } + } + + dump_acodec_registers(__func__, dev); + return error; +} + +/** + * @brief Unmute headset on a specific channel + * @channel_index Headeset channel-index + * @gain Gain index of headset + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_unmute_headset(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + + /* Check if HS UnMute request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "HS should have mono or stereo channels"); + return -EINVAL; + } + + if (channel_index & e_CHANNEL_1) { + /* UnMute HSL */ + error = HW_ACODEC_MODIFY_WRITE(MUTE_HS_EAR_REG, 0, + EN_HSL_MASK | EN_HSL_DAC_MASK); + if (0 != error) { + dev_err(dev, "UnMute HSL %d", error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + /* UnMute HSR */ + error = HW_ACODEC_MODIFY_WRITE(MUTE_HS_EAR_REG, 0, + EN_HSR_MASK | EN_HSR_DAC_MASK); + if (0 != error) { + dev_err(dev, "UnMute HSR %d", error); + return error; + } + } + dump_acodec_registers(__func__, dev); + return error; +} + +/** + * @brief Enables fading of headset on a specific channel + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_enable_fade_headset(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(SHORT_CIRCUIT_DISABLE_REG, + 0, DIS_HS_FAD); + if (0 != error) { + dev_err(dev, "Enable fading for HS %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DA1_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for HSL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(HSL_EAR_DIGITAL_GAIN_REG, 0, + DIS_DIG_GAIN_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for Digital Gain of HSL %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DA2_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for HSR %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(HSR_DIGITAL_GAIN_REG, 0, + DIS_DIG_GAIN_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for Digital Gain of HSR %d", + error); + return error; + } + + return error; +} +/** + * @brief Disables fading of headset on a specific channel + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_disable_fade_headset(struct device *dev) +{ + int error = 0; + error = HW_ACODEC_MODIFY_WRITE(SHORT_CIRCUIT_DISABLE_REG, + DIS_HS_FAD, 0); + if (0 != error) { + dev_err(dev, "Disable fading for HS %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DA1_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for HSL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(HSL_EAR_DIGITAL_GAIN_REG, + DIS_DIG_GAIN_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for Digital Gain of HSL %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DA2_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for HSR %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(HSR_DIGITAL_GAIN_REG, + DIS_DIG_GAIN_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for Digital Gain of HSR %d", + error); + return error; + } + return error; +} +/** + * @brief Power up earpiece + * @channel_index Channel-index + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_up_earpiece(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if Earpiece PowerUp request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "EARPIECE should have mono channel"); + return -EINVAL; + } + + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if Earpiece is already powered up or DA1 being used by HS */ + if (EN_DA1 & initialVal_DA) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, + DA1_TO_HSL, 0); + if (0 != error) { + dev_err(dev, + "DA_IN1 path mixed with sidetone FIR %d", error); + return error; + } + + /* Enable DA1 */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA1_REG, + SLOT08_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA1 from Slot 08 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + EN_DA1, 0); + if (0 != error) { + dev_err(dev, "Enable DA1 %d", error); + return error; + } + + /* Power Up EAR class-AB driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + EN_EAR_MASK, 0); + if (0 != error) { + dev_err(dev, "Power Up EAR class-AB driver %d", error); + return error; + } + + /* Power up EAR DAC and digital path */ + error = HW_ACODEC_MODIFY_WRITE( + DIGITAL_OUTPUT_ENABLE_REG, EN_EAR_MASK, 0); + if (0 != error) { + dev_err(dev, "Power up EAR DAC and digital path %d", error); + return error; + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Power down earpiece + * @channel_index Channel-index + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_down_earpiece(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if Earpiece PowerDown request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "EARPIECE should have mono channel"); + return -EINVAL; + } + + /* Check if Earpiece is already powered down or DA1 being used by HS */ + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (!(initialVal_DA & EN_DA1)) + return 0; + + /* Power Down EAR class-AB driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + 0, EN_EAR_MASK); + if (0 != error) { + dev_err(dev, "Power Down EAR class-AB driver %d", error); + return error; + } + + /* Power Down EAR DAC and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, + 0, EN_EAR_MASK); + if (0 != error) { + dev_err(dev, + "Power Down EAR DAC and digital path %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, 0, DA1_TO_HSL); + if (0 != error) { + dev_err(dev, + "Clear DA_IN1 path mixed with sidetone FIR %d", + error); + return error; + } + + /* Disable DA1 */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + 0, EN_DA1); + if (0 != error) { + dev_err(dev, "Disable DA1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA1_REG, 0, + SLOT08_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Data sent to DA1 cleared from Slot 08 %d", error); + return error; + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Mute earpiece + * @channel_index Channel-index + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_mute_earpiece(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + + /* Check if Earpiece Mute request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "EARPIECE should have mono channel"); + return -EINVAL; + } + + /* Mute Earpiece */ + error = HW_ACODEC_MODIFY_WRITE(MUTE_HS_EAR_REG, + EN_EAR_MASK | EN_EAR_DAC_MASK, 0); + if (0 != error) { + dev_err(dev, "Mute Earpiece %d", error); + return error; + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Unmute earpiece + * @channel_index Channel-index + * @gain Gain index of earpiece + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_unmute_earpiece(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + + /* Check if Earpiece UnMute request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "EARPIECE should have mono channel"); + return -EINVAL; + } + + /* UnMute Earpiece */ + error = HW_ACODEC_MODIFY_WRITE(MUTE_HS_EAR_REG, 0, + EN_EAR_MASK | EN_EAR_DAC_MASK); + if (0 != error) { + dev_err(dev, "UnMute Earpiece %d", error); + return error; + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Enables fading of earpiece + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_enable_fade_earpiece(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(DA1_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for Ear %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(HSL_EAR_DIGITAL_GAIN_REG, 0, + DIS_DIG_GAIN_FADING); + if (0 != error) { + dev_err(dev, + "Enable fading for Digital Gain of Ear %d", error); + return error; + } + + return error; +} +/** + * @brief Disables fading of earpiece + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_disable_fade_earpiece(struct device *dev) +{ + int error = 0; + error = HW_ACODEC_MODIFY_WRITE(DA1_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for Ear %d", error); + return error; + } + return error; +} +/** + * @brief Power up IHF on a specific channel + * @channel_index Channel-index + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_up_ihf(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if IHF PowerUp request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "IHF should have mono or stereo channels"); + return -EINVAL; + } + + if (channel_index & e_CHANNEL_1) { + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (EN_DA3 & initialVal_DA) + return 0; + + /* Enable DA3 for IHFL */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA3_REG, + SLOT10_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA3 from Slot 10 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + EN_DA3, 0); + if (0 != error) { + dev_err(dev, "Power up IHFL %d", error); + return error; + } + + /* Power Up HFL Class-D driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + EN_HFL_MASK, 0); + if (0 != error) { + dev_err(dev, "Power Up HFL Class-D Driver %d", error); + return error; + } + + /* Power up HFL Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, + EN_HFL_MASK, 0); + if (0 != error) { + dev_err(dev, + "Power up HFL Class D driver & digital path %d", + error); + return error; + } + } + + /* Enable DA4 for IHFR */ + if (channel_index & e_CHANNEL_2) { + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (EN_DA4 & initialVal_DA) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA4_REG, + SLOT11_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA4 from Slot 11 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + EN_DA4, 0); + if (0 != error) { + dev_err(dev, "Enable DA4 %d", error); + return error; + } + + /* Power Up HFR Class-D driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + EN_HFR_MASK, 0); + if (0 != error) { + dev_err(dev, "Power Up HFR Class-D Driver %d", error); + return error; + } + + /* Power up HFR Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, + EN_HFR_MASK, 0); + if (0 != error) { + dev_err(dev, + "Power up HFR Class D driver and digital path %d", + error); + return error; + } + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Power down IHF on a specific channel + * @channel_index Channel-index + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_down_ihf(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if IHF Power Down request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "IHF should have mono or stereo channels"); + return -EINVAL; + } + + if (channel_index & e_CHANNEL_1) { + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (!(initialVal_DA & EN_DA3)) + return 0; + + /* Power Down HFL Class-D driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, 0, + EN_HFL_MASK); + if (0 != error) { + dev_err(dev, "Power Down HFL Class-D Driver %d", + error); + return error; + } + + /* Power Down HFL Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, 0, + EN_HFL_MASK); + if (0 != error) { + dev_err(dev, + "Power Down HFL Class D driver & digital path %d", + error); + return error; + } + + /* Disable DA3 for IHFL */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + 0, EN_DA3); + if (0 != error) { + dev_err(dev, "Disable DA3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA3_REG, 0, + SLOT10_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Data sent to DA3 cleared from Slot 10 %d", + error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if IHF is already powered Down */ + if (!(initialVal_DA & EN_DA4)) + return 0; + + /* Power Down HFR Class-D Driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, 0, + EN_HFR_MASK); + if (0 != error) { + dev_err(dev, "Power Down HFR Class-D Driver %d", + error); + return error; + } + + /* Power Down HFR Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, 0, + EN_HFR_MASK); + if (0 != error) { + dev_err(dev, + "Power Down HFR Class D driver & digital path %d", + error); + return error; + } + + /* Disable DA4 for IHFR */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + 0, EN_DA4); + if (0 != error) { + dev_err(dev, "Disable DA4 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA4_REG, 0, + SLOT11_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Data sent to DA4 cleared from Slot 11 %d", + error); + return error; + } + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Mute IHF on a specific channel + * @channel_index Channel-index + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_mute_ihf(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + + if ((channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + error = ste_audio_io_set_ihf_gain(channel_index, 0, -63, + 0, dev); + if (0 != error) { + dev_err(dev, "Mute ihf %d", error); + return error; + } + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Unmute IHF on a specific channel + * @channel_index Channel-index + * @gain Gain index of IHF + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_unmute_ihf(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + + if (channel_index & e_CHANNEL_1) { + error = ste_audio_io_set_ihf_gain(channel_index, 0, gain[0], + 0, dev); + if (0 != error) { + dev_err(dev, "UnMute ihf %d", error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + error = ste_audio_io_set_ihf_gain(channel_index, 0, gain[1], + 0, dev); + if (0 != error) { + dev_err(dev, "UnMute ihf %d", error); + return error; + } + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Enable fading of IHF + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_enable_fade_ihf(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(DA3_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for HFL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DA4_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for HFR %d", error); + return error; + } + return error; +} +/** + * @brief Disable fading of IHF + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_disable_fade_ihf(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(DA3_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for HFL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DA4_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for HFR %d", error); + return error; + } + return error; +} +/** + * @brief Power up VIBL + * @channel_index Channel-index of VIBL + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_up_vibl(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if VibL PowerUp request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "VibL should have mono channel"); + return -EINVAL; + } + + /* Try to allocate vibrator for audio left channel */ + error = ste_audioio_vibrator_alloc(STE_AUDIOIO_CLIENT_AUDIO_L, + STE_AUDIOIO_CLIENT_AUDIO_R | STE_AUDIOIO_CLIENT_AUDIO_L); + if (error) { + dev_err(dev, " Couldn't allocate vibrator %d, client %d", + error, STE_AUDIOIO_CLIENT_AUDIO_L); + return error; + } + + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if VibL is already powered up */ + if (initialVal_DA & EN_DA5) + return 0; + + /* Enable DA5 for vibl */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA5_REG, + SLOT12_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA5 from Slot 12 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + EN_DA5, 0); + if (0 != error) { + dev_err(dev, "Enable DA5 for VibL %d", error); + return error; + } + + /* Power Up VibL Class-D driver */ + error = HW_ACODEC_MODIFY_WRITE( + ANALOG_OUTPUT_ENABLE_REG, EN_VIBL_MASK, 0); + if (0 != error) { + dev_err(dev, "Power Up VibL Class-D Driver %d", error); + return error; + } + + /* Power up VibL Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, + EN_VIBL_MASK, 0); + if (0 != error) { + dev_err(dev, + "Power up VibL Class D driver and digital path %d", + error); + return error; + } + return error; +} +/** + * @brief Power down VIBL + * @channel_index Channel-index of VIBL + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_down_vibl(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if VibL Power Down request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "VibL should have mono channel"); + return -EINVAL; + } + + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if VibL is already powered down */ + if (!(initialVal_DA & EN_DA5)) + return 0; + + + /* Power Down VibL Class-D driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, + 0, EN_VIBL_MASK); + if (0 != error) { + dev_err(dev, "Power Down VibL Class-D Driver %d", error); + return error; + } + + /* Power Down VibL Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, 0, + EN_VIBL_MASK); + if (0 != error) { + dev_err(dev, + "Power Down VibL Class D driver & digital path %d", + error); + return error; + } + + /* Disable DA5 for VibL */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + 0, EN_DA5); + if (0 != error) { + dev_err(dev, "Disable DA5 for VibL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA5_REG, 0, + SLOT12_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Data sent to DA5 cleared from Slot 12 %d", error); + return error; + } + + /* Release vibrator */ + ste_audioio_vibrator_release(STE_AUDIOIO_CLIENT_AUDIO_L); + + return error; +} +/** + * @brief Enable fading of VIBL + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_enable_fade_vibl(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(DA5_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for VibL %d", error); + return error; + } + return error; +} +/** + * @brief Disable fading of VIBL + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_disable_fade_vibl(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(DA5_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for VibL %d", error); + return error; + } + return error; +} +/** + * @brief Power up VIBR + * @channel_index Channel-index of VIBR + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_up_vibr(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if VibR PowerUp request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "VibR should have mono channel"); + return -EINVAL; + } + + /* Try to allocate vibrator for audio right channel */ + error = ste_audioio_vibrator_alloc(STE_AUDIOIO_CLIENT_AUDIO_R, + STE_AUDIOIO_CLIENT_AUDIO_R | STE_AUDIOIO_CLIENT_AUDIO_L); + if (error) { + dev_err(dev, " Couldn't allocate vibrator %d, client %d", + error, STE_AUDIOIO_CLIENT_AUDIO_R); + return error; + } + + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if VibR is already powered up */ + if (initialVal_DA & EN_DA6) + return 0; + + /* Enable DA6 for vibr */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA6_REG, + SLOT13_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA5 from Slot 13 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE( + DIGITAL_DA_CHANNELS_ENABLE_REG, EN_DA6, 0); + if (0 != error) { + dev_err(dev, "Enable DA6 for VibR %d", error); + return error; + } + + /* Power Up VibR Class-D driver */ + error = HW_ACODEC_MODIFY_WRITE( + ANALOG_OUTPUT_ENABLE_REG, EN_VIBR_MASK, 0); + if (0 != error) { + dev_err(dev, "Power Up VibR Class-D Driver %d", error); + return error; + } + + /* Power up VibR Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, + EN_VIBR_MASK, 0); + if (0 != error) { + dev_err(dev, + "Power up VibR Class D driver & digital path %d", + error); + return error; + } + return error; +} +/** + * @brief Power down VIBR + * @channel_index Channel-index of VIBR + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_down_vibr(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_DA = 0; + + /* Check if VibR PowerDown request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "VibR should have mono channel"); + return -EINVAL; + } + + initialVal_DA = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if VibR is already powered down */ + if (!(initialVal_DA & EN_DA6)) + return 0; + + + /* Power Down VibR Class-D driver */ + error = HW_ACODEC_MODIFY_WRITE(ANALOG_OUTPUT_ENABLE_REG, 0, + EN_VIBR_MASK); + if (0 != error) { + dev_err(dev, "Power Down VibR Class-D Driver %d", error); + return error; + } + + /* Power Down VibR Class D driver and digital path */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_OUTPUT_ENABLE_REG, 0, + EN_VIBR_MASK); + if (0 != error) { + dev_err(dev, + "Power Down VibR Class D driver & digital path %d", + error); + return error; + } + + /* Disable DA6 for VibR */ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_DA_CHANNELS_ENABLE_REG, + 0, EN_DA6); + if (0 != error) { + dev_err(dev, "Disable DA6 for VibR %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA6_REG, 0, + SLOT13_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, "Data sent to DA5 cleared from Slot 13 %d", + error); + return error; + } + + /* Release vibrator */ + ste_audioio_vibrator_release(STE_AUDIOIO_CLIENT_AUDIO_R); + + return error; +} +/** + * @brief Enable fading of VIBR + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_enable_fade_vibr(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(DA6_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for VibR %d", error); + return error; + } + return error; +} +/** + * @brief Disable fading of VIBR + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_disable_fade_vibr(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(DA6_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for VibR %d", error); + return error; + } + return error; +} +/** + * @brief Power up MIC1A + * @channel_index Channel-index of MIC1A + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_up_mic1a(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if Mic1 PowerUp request is mono channel */ + + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "MIC1 should have mono channel"); + return -EINVAL; + } + + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + /* Check if Mic1 is already powered up or used by Dmic3 */ + if (EN_AD3 & initialVal_AD) + return 0; + + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, DATA_FROM_AD_OUT3); + if (0 != error) { + dev_err(dev, "Slot 02 outputs data from AD_OUT3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + EN_AD3, 0); + if (0 != error) { + dev_err(dev, "Enable AD3 for Mic1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, 0, + SEL_DMIC3_FOR_AD_OUT3); + if (0 != error) { + dev_err(dev, "Select ADC1 for AD_OUT3 %d", error); + return error; + } + + /* Select MIC1A */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, + SEL_MIC1B_CLR_MIC1A); + if (0 != error) { + dev_err(dev, "Select MIC1A %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, EN_MIC1, 0); + if (0 != error) { + dev_err(dev, "Power up Mic1 %d", error); + return error; + } + + /* Power Up ADC1 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, POWER_UP_ADC1, 0); + if (0 != error) { + dev_err(dev, "Power Up ADC1 %d", error); + return error; + } + +return error; +} +/** + * @brief Power down MIC1A + * @channel_index Channel-index of MIC1A + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_down_mic1a(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if Mic1 PowerDown request is mono channel */ + + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic1 should have mono channel"); + return -EINVAL; + } + + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + /* Check if Mic1 is already powered down or used by Dmic3 */ + if (!(initialVal_AD & EN_AD3)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, EN_MIC1); + if (0 != error) { + dev_err(dev, "Power Down Mic1 %d", error); + return error; + } + + /* Power Down ADC1 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, POWER_UP_ADC1); + if (0 != error) { + dev_err(dev, "Power Down ADC1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, EN_AD3); + if (0 != error) { + dev_err(dev, "Disable AD3 for Mic1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT2_3_REG, 0, + DATA_FROM_AD_OUT3); + if (0 != error) { + dev_err(dev, "Slot 02 outputs data cleared from AD_OUT3 %d", + error); + return error; + } + return error; +} +/** + * @brief Mute MIC1A + * @channel_index Channel-index of MIC1A + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_mute_mic1a(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "MIC1 should have mono channel"); + return -EINVAL; + } + + /* Mute mic1 */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, MUT_MIC1, 0); + if (0 != error) { + dev_err(dev, "Mute Mic1 %d", error); + return error; + } + return error; +} +/** + * @brief Unmute MIC1A + * @channel_index Channel-index of MIC1A + * @gain Gain index of MIC1A + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_unmute_mic1a(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic1 should have mono channel"); + return -EINVAL; + } + /* UnMute mic1 */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, MUT_MIC1); + if (0 != error) { + dev_err(dev, "UnMute Mic1 %d", error); + return error; + } + return error; +} +/** + * @brief Enable fading of MIC1A + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_enable_fade_mic1a(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(AD3_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for Mic1 %d", error); + return error; + } + return error; +} +/** + * @brief Disable fading of MIC1A + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_disable_fade_mic1a(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(AD3_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for Mic1 %d", error); + return error; + } + return error; +} +/** + * @brief Power up MIC1B + * @channel_index Channel-index of MIC1B + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_up_mic1b(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error; + unsigned char initialVal_AD = 0; + + error = regulator_enable(regulator_avsource); + if (0 != error) { + dev_err(dev, "regulator avsource enable failed = %d", error); + return error; + } + /* GPIO35 settings to enable MIC 1B input instead of TVOUT */ + error = HW_ACODEC_MODIFY_WRITE(AB8500_GPIO_DIR5_REG, + GPIO35_DIR_OUTPUT, 0); + if (0 != error) { + dev_err(dev, "setting AB8500_GPIO_DIR5_REG reg %d", error); + return error; + } + error = HW_ACODEC_MODIFY_WRITE(AB8500_GPIO_OUT5_REG, + GPIO35_DIR_OUTPUT, 0); + if (0 != error) { + dev_err(dev, "setting AB8500_GPIO_OUT5_REG reg %d", error); + return error; + } + + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic1 should have mono channel"); + return -EINVAL; + } + + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + /* Check if Mic1 is already powered up or used by Dmic3 */ + if (EN_AD3 & initialVal_AD) + return 0; + + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, DATA_FROM_AD_OUT3); + if (0 != error) { + dev_err(dev, "Slot 02 outputs data from AD_OUT3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + EN_AD3, 0); + if (0 != error) { + dev_err(dev, "Enable AD3 for Mic1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, 0, + SEL_DMIC3_FOR_AD_OUT3); + if (0 != error) { + dev_err(dev, "Select ADC1 for AD_OUT3 %d", error); + return error; + } + + /* Select MIC1B */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, SEL_MIC1B_CLR_MIC1A, + 0); + if (0 != error) { + dev_err(dev, "Select MIC1B %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, EN_MIC1, 0); + if (0 != error) { + dev_err(dev, "Power up Mic1 %d", error); + return error; + } + + /* Power Up ADC1 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, POWER_UP_ADC1, 0); + if (0 != error) { + dev_err(dev, "Power Up ADC1 %d", error); + return error; + } + return error; +} +/** + * @brief Power down MIC1B + * @channel_index Channel-index of MIC1B + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_down_mic1b(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error; + unsigned char initialVal_AD = 0; + + /* Check if Mic1 PowerDown request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic1 should have mono channel"); + return -EINVAL; + } + + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if Mic1 is already powered down or used by Dmic3 */ + if (!(initialVal_AD & EN_AD3)) + return 0; + + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, EN_MIC1); + if (0 != error) { + dev_err(dev, "Power Down Mic1 %d", error); + return error; + } + + /* Power Down ADC1 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, POWER_UP_ADC1); + if (0 != error) { + dev_err(dev, "Power Down ADC1 %d", error); + return error; + } + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, 0, + EN_AD3); + if (0 != error) { + dev_err(dev, "Disable AD3 for Mic1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT2_3_REG, 0, + DATA_FROM_AD_OUT3); + if (0 != error) { + dev_err(dev, "Slot 02 outputs data cleared from AD_OUT3 %d", + error); + return error; + } + + /* undo GPIO35 settings */ + error = HW_ACODEC_MODIFY_WRITE(AB8500_GPIO_DIR5_REG, + 0, GPIO35_DIR_OUTPUT); + if (0 != error) { + dev_err(dev, "resetting AB8500_GPIO_DIR5_REG reg %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AB8500_GPIO_OUT5_REG, + 0, GPIO35_DIR_OUTPUT); + if (0 != error) { + dev_err(dev, "resetting AB8500_GPIO_OUT5_REG reg %d", error); + return error; + } + + error = regulator_disable(regulator_avsource); + if (0 != error) { + dev_err(dev, "regulator avsource disable failed = %d", error); + return error; + } + dump_acodec_registers(__func__, dev); + return error; +} + +/** + * @brief enable hardware loop of mic1b + * @chnl_index Channel-index of MIC1B + * @hw_loop type of hardware loop + * @loop_gain gain value to be used in hardware loop + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_enable_loop_mic1b(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS hw_loop, + int loop_gain, struct device *dev, + void *cookie) +{ + int error; + struct transducer_context_t *trnsdr; + trnsdr = (struct transducer_context_t *)cookie; + + switch (hw_loop) { + /* Check if HSL is active */ + case AUDIOIO_SIDETONE_LOOP: + if (!(trnsdr[HS_CH].is_power_up[e_CHANNEL_1]) + && !(trnsdr[EAR_CH].is_power_up[e_CHANNEL_1])) { + error = -EFAULT; + dev_err(dev, + "HS or Earpiece not powered up error = %d", + error); + return error; + } + + /* For ch1, Power On STFIR1, data comes from AD3*/ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG2, + FIR1_FROMAD3, 0); + if (error) + dev_err(dev, "FIR1 data comes from AD_OUT3 %d", + error); + error = HW_REG_WRITE(SIDETONE_FIR1_GAIN_REG, loop_gain); + if (error) { + dev_err(dev, + "Set FIR1 Gain index = %d", + error); + return error; + } + break; + default: + error = -EINVAL; + dev_err(dev, "loop not supported %d", error); + } + return error; +} + +/** + * @brief disable hardware loop of mic1b + * @chnl_index Channel-index of MIC1B + * @hw_loop type of hardware loop + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_disable_loop_mic1b(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS hw_loop, + struct device *dev, void *cookie) +{ + int error; + struct transducer_context_t *trnsdr; + trnsdr = (struct transducer_context_t *)cookie; + + switch (hw_loop) { + /* Check if HSL is active */ + case AUDIOIO_SIDETONE_LOOP: + if (!trnsdr[HS_CH].is_power_up[e_CHANNEL_1] + && !trnsdr[EAR_CH].is_power_up[e_CHANNEL_1]) { + error = -EFAULT; + dev_err(dev, "HS or Earpiece not powered up, err = %d", + error); + return error; + } + + /* For ch1, Power down STFIR1, data comes from AD3*/ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG2, + 0, FIR1_FROMAD3); + if (error) { + dev_err(dev, "FIR1 data comes from AD_OUT3, err = %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(FILTERS_CONTROL_REG, + 0, FIR_FILTERCONTROL); + if (error) { + dev_err(dev, + "ST FIR Filters disable failed %d", error); + return error; + } + break; + default: + error = -EINVAL; + dev_err(dev, "loop not supported %d", error); + } + return error; +} + +/** + * @brief Power up MIC2 + * @channel_index Channel-index of MIC2 + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_up_mic2(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if Mic2 PowerUp request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic2 should have mono channel"); + return -EINVAL; + } + + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if Mic2 is already powered up or used by LINR or Dmic2 */ + if (EN_AD2 & initialVal_AD) + return 0; + + + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, DATA_FROM_AD_OUT2); + if (0 != error) { + dev_err(dev, "Slot 01 outputs data from AD_OUT2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, EN_AD2, + 0); + if (0 != error) { + dev_err(dev, "Enable AD2 for Mic2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, 0, + SEL_DMIC2_FOR_AD_OUT2); + if (0 != error) { + dev_err(dev, "Select ADC2 for AD_OUT2 %d", error); + return error; + } + + /* Select mic2 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, + SEL_LINR_CLR_MIC2); + if (0 != error) { + dev_err(dev, "Select MIC2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, EN_MIC2, 0); + if (0 != error) { + dev_err(dev, "Power up Mic2 %d", error); + return error; + } + + /* Power Up ADC1 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, POWER_UP_ADC2, 0); + if (0 != error) { + dev_err(dev, "Power Up ADC2 %d", error); + return error; + } + return error; +} +/** + * @brief Power down MIC2 + * @channel_index Channel-index of MIC2 + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_power_down_mic2(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if Mic2 PowerDown request is mono channel */ + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic2 should have mono channel"); + return -EINVAL; + } + + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + + /* Check if Mic2 is already powered down or used by LINR or Dmic2 */ + if (!(initialVal_AD & EN_AD2)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, EN_MIC2); + if (0 != error) { + dev_err(dev, "Power Down Mic2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, EN_AD2); + if (0 != error) { + dev_err(dev, "Disable AD2 for Mic2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, 0, + (DATA_FROM_AD_OUT2<<4)); + if (0 != error) { + dev_err(dev, "Slot 01 outputs data cleared from AD_OUT2 %d", + error); + return error; + } + return error; +} +/** + * @brief Mute MIC2 + * @channel_index Channel-index of MIC2 + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_mute_mic2(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic2 should have mono channel"); + return -EINVAL; + } + + /* Mute mic2 */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, MUT_MIC2, 0); + if (0 != error) { + dev_err(dev, "Mute Mic2 %d", error); + return error; + } + return error; +} +/** + * @brief Unmute MIC2 + * @channel_index Channel-index of MIC2 + * @gain Gain index of MIC2 + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_unmute_mic2(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + if (!(channel_index & e_CHANNEL_1)) { + dev_err(dev, "Mic2 should have mono channel"); + return -EINVAL; + } + /* UnMute mic2 */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, MUT_MIC2); + if (0 != error) { + dev_err(dev, "UnMute Mic2 %d", error); + return error; + } + return error; +} +/** + * @brief Enable fading of MIC2 + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_enable_fade_mic2(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(AD2_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for Mic2 %d", error); + return error; + } + return error; +} +/** + * @brief Disable fading of MIC2 + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_disable_fade_mic2(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(AD2_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for Mic2 %d", error); + return error; + } + + return error; +} +/** + * @brief Power up LinIn + * @channel_index Channel-index of LinIn + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_up_lin(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if LinIn PowerUp request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "LinIn should have mono or stereo channels"); + return -EINVAL; + } + + /* Enable AD1 for LinInL */ + if (channel_index & e_CHANNEL_1) { + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (initialVal_AD & EN_AD1) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, + DATA_FROM_AD_OUT1, 0); + if (0 != error) { + dev_err(dev, "Slot 00 outputs data from AD_OUT1 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + EN_AD1, 0); + if (0 != error) { + dev_err(dev, "Enable AD1 for LinInL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, 0, + SEL_DMIC1_FOR_AD_OUT1); + if (0 != error) { + dev_err(dev, "Select ADC3 for AD_OUT1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE( + LINE_IN_MIC_CONF_REG, EN_LIN_IN_L, 0); + if (0 != error) { + dev_err(dev, "Power up LinInL %d", error); + return error; + } + + /* Power Up ADC3 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, + POWER_UP_ADC3, 0); + if (0 != error) { + dev_err(dev, "Power Up ADC3 %d", error); + return error; + } + } + /* Enable AD2 for LinInR */ + + if (channel_index & e_CHANNEL_2) { + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (EN_AD2 & initialVal_AD) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, + (DATA_FROM_AD_OUT2<<4), 0); + if (0 != error) { + dev_err(dev, "Slot 01 outputs data from AD_OUT2 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + EN_AD2, 0); + if (0 != error) { + dev_err(dev, "Enable AD2 LinInR %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, 0, + SEL_DMIC2_FOR_AD_OUT2); + if (0 != error) { + dev_err(dev, "Select ADC2 for AD_OUT2 %d", error); + return error; + } + + /* Select LinInR */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, + SEL_LINR_CLR_MIC2, 0); + if (0 != error) { + dev_err(dev, "Select LinInR %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, + EN_LIN_IN_R, 0); + if (0 != error) { + dev_err(dev, "Power up LinInR %d", error); + return error; + } + + /* Power Up ADC2 */ + error = HW_ACODEC_MODIFY_WRITE( + ADC_DAC_ENABLE_REG, POWER_UP_ADC2, 0); + if (0 != error) { + dev_err(dev, "Power Up ADC2 %d", error); + return error; + } + } + return error; +} +/** + * @brief Power down LinIn + * @channel_index Channel-index of LinIn + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_down_lin(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if LinIn PowerDown request is mono or Stereo channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "LinIn should have mono or stereo channels"); + return -EINVAL; + } + + /* Enable AD1 for LinInL */ + if (channel_index & e_CHANNEL_1) { + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (!(initialVal_AD & EN_AD1)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, + EN_LIN_IN_L); + if (0 != error) { + dev_err(dev, "Power Down LinInL %d", error); + return error; + } + + /* Power Down ADC3 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, + POWER_UP_ADC3); + if (0 != error) { + dev_err(dev, "Power Down ADC3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, EN_AD1); + if (0 != error) { + dev_err(dev, "Disable AD1 for LinInL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, 0, + DATA_FROM_AD_OUT1); + if (0 != error) { + dev_err(dev, + "Slot 00 outputs data cleared from AD_OUT1 %d", + error); + return error; + } + } + + /* Enable AD2 for LinInR */ + if (channel_index & e_CHANNEL_2) { + initialVal_AD = HW_REG_READ(DIGITAL_DA_CHANNELS_ENABLE_REG); + if (!(initialVal_AD & EN_AD2)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, + EN_LIN_IN_R); + if (0 != error) { + dev_err(dev, "Power Down LinInR %d", error); + return error; + } + + /* Power Down ADC2 */ + error = HW_ACODEC_MODIFY_WRITE(ADC_DAC_ENABLE_REG, 0, + POWER_UP_ADC2); + if (0 != error) { + dev_err(dev, "Power Down ADC2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, EN_AD2); + if (0 != error) { + dev_err(dev, "Disable AD2 LinInR %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, 0, + (DATA_FROM_AD_OUT2<<4)); + if (0 != error) { + dev_err(dev, + "Slot01 outputs data cleared from AD_OUT2 %d", + error); + return error; + } + } + return error; +} +/** + * @brief Mute LinIn + * @channel_index Channel-index of LinIn + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_mute_lin(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "LinIn should have mono or stereo channels"); + return -EINVAL; + } + + if (channel_index & e_CHANNEL_1) { + /* Mute LinInL */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, + MUT_LIN_IN_L, 0); + if (0 != error) { + dev_err(dev, "Mute LinInL %d", error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + /* Mute LinInR */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, + MUT_LIN_IN_R, + 0); + if (0 != error) { + dev_err(dev, "Mute LinInR %d", error); + return error; + } + } + return error; +} +/** + * @brief Unmute LinIn + * @channel_index Channel-index of LinIn + * @gain Gain index of LinIn + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_unmute_lin(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "LinIn should have mono or stereo channels"); + return -EINVAL; + } + + if (channel_index & e_CHANNEL_1) { + /* UnMute LinInL */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, + MUT_LIN_IN_L); + if (0 != error) { + dev_err(dev, "UnMute LinInL %d", error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + /* UnMute LinInR */ + error = HW_ACODEC_MODIFY_WRITE(LINE_IN_MIC_CONF_REG, 0, + MUT_LIN_IN_R); + if (0 != error) { + dev_err(dev, "UnMute LinInR %d", error); + return error; + } + } + return error; +} +/** + * @brief Enables fading of LinIn + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_enable_fade_lin(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(AD1_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for LinInL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD2_DIGITAL_GAIN_REG, 0, DIS_FADING); + if (0 != error) { + dev_err(dev, "Enable fading for LinInR %d", error); + return error; + } + return error; +} +/** + * @brief Disables fading of LinIn + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_disable_fade_lin(struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(AD1_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for LinInL %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD2_DIGITAL_GAIN_REG, DIS_FADING, 0); + if (0 != error) { + dev_err(dev, "Disable fading for LinInR %d", error); + return error; + } + return error; +} +/** + * @brief Power Up DMIC12 LinIn + * @channel_index Channel-index of DMIC12 + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_power_up_dmic12(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if DMic12 request is mono or Stereo */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "DMic12 does not support more than 2 channels"); + + return -EINVAL; + } + + /* Setting Direction for GPIO pins on AB8500 */ + error = HW_REG_WRITE(AB8500_GPIO_DIR4_REG, GPIO27_DIR_OUTPUT); + if (0 != error) { + dev_err(dev, "Setting Direction for GPIO pins on AB8500 %d", + error); + return error; + } + + /* Enable AD1 for Dmic1 */ + if (channel_index & e_CHANNEL_1) { + /* Check if DMIC1 is already powered up or used by LinInL */ + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + if (initialVal_AD & EN_AD1) + return 0; + + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, + DATA_FROM_AD_OUT1); + if (0 != error) { + dev_err(dev, "Slot 00 outputs data from AD_OUT1 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + EN_AD1, 0); + if (0 != error) { + dev_err(dev, "Enable AD1 for DMIC1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, + SEL_DMIC1_FOR_AD_OUT1, 0); + if (0 != error) { + dev_err(dev, "Select DMIC1 for AD_OUT1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, EN_DMIC1, 0); + if (0 != error) { + dev_err(dev, "Enable DMIC1 %d", error); + return error; + } + } + /* Enable AD2 for Dmic2 */ + + if (channel_index & e_CHANNEL_2) { + /* Check if DMIC2 is already powered up + or used by Mic2 or LinInR */ + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + if (initialVal_AD & EN_AD2) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, + (DATA_FROM_AD_OUT2<<4), 0); + if (0 != error) { + dev_err(dev, "Slot 01 outputs data from AD_OUT2 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + EN_AD2, 0); + if (0 != error) { + dev_err(dev, "Enable AD2 for DMIC2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, + SEL_DMIC2_FOR_AD_OUT2, 0); + if (0 != error) { + dev_err(dev, "Select DMIC2 for AD_OUT2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, EN_DMIC2, 0); + if (0 != error) { + dev_err(dev, "Enable DMIC2 %d", error); + return error; + } + } + + return error; +} +/** + * @brief Power down DMIC12 LinIn + * @channel_index Channel-index of DMIC12 + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_power_down_dmic12(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if DMic12 request is mono or Stereo or multi channel */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "DMic12 does not support more than 2 channels"); + + return -EINVAL; + } + + /* Setting Direction for GPIO pins on AB8500 */ + error = HW_ACODEC_MODIFY_WRITE(AB8500_GPIO_DIR4_REG, 0, + GPIO27_DIR_OUTPUT); + if (0 != error) { + dev_err(dev, "Clearing Direction for GPIO pins on AB8500 %d", + error); + return error; + } + /* Enable AD1 for Dmic1 */ + if (channel_index & e_CHANNEL_1) { + /* Check if DMIC1 is already powered Down or used by LinInL */ + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + if (!(initialVal_AD & EN_AD1)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, 0, EN_DMIC1); + if (0 != error) { + dev_err(dev, "Enable DMIC1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, EN_AD1); + if (0 != error) { + dev_err(dev, "Disable AD1 for DMIC1 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, 0, + DATA_FROM_AD_OUT1); + if (0 != error) { + dev_err(dev, + "Slot 00 outputs data cleared from AD_OUT1 %d", + error); + return error; + } + } + + /* Enable AD2 for Dmic2 */ + if (channel_index & e_CHANNEL_2) { + /* MIC2 is already powered Down or used by Mic2 or LinInR */ + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + if (!(initialVal_AD & EN_AD2)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, 0, EN_DMIC2); + if (0 != error) { + dev_err(dev, "Enable DMIC2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, EN_AD2); + if (0 != error) { + dev_err(dev, "Disable AD2 for DMIC2 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT0_1_REG, 0, + (DATA_FROM_AD_OUT2<<4)); + if (0 != error) { + dev_err(dev, + "Slot 01 outputs data cleared from AD_OUT2 %d", + error); + return error; + } + } + return error; +} +/** + * @brief Get headset gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_get_headset_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + int i = 0; + if (gain_index == 0) { + + *left_volume = 0 - HW_REG_READ(DA1_DIGITAL_GAIN_REG); + *right_volume = 0 - HW_REG_READ(DA2_DIGITAL_GAIN_REG); + + } + + if (gain_index == 1) { + *left_volume = 8 - HW_REG_READ(HSL_EAR_DIGITAL_GAIN_REG); + *right_volume = 8 - HW_REG_READ(HSR_DIGITAL_GAIN_REG); + } + + if (gain_index == 2) { + i = (HW_REG_READ(ANALOG_HS_GAIN_REG)>>4); + *left_volume = hs_analog_gain_table[i]; + i = (HW_REG_READ(ANALOG_HS_GAIN_REG) & MASK_QUARTET0); + *right_volume = hs_analog_gain_table[i]; + } + return 0; +} +/** + * @brief Get earpiece gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_get_earpiece_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + if (0 == gain_index) + *left_volume = 0 - HW_REG_READ(DA1_DIGITAL_GAIN_REG); + if (1 == gain_index) + *left_volume = 8 - HW_REG_READ(HSL_EAR_DIGITAL_GAIN_REG); + return 0; +} +/** + * @brief Get ihf gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_get_ihf_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + + *left_volume = 0 - HW_REG_READ(DA3_DIGITAL_GAIN_REG); + *right_volume = 0 - HW_REG_READ(DA4_DIGITAL_GAIN_REG); + return 0; +} +/** + * @brief Get vibl gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_get_vibl_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + + *left_volume = 0 - HW_REG_READ(DA5_DIGITAL_GAIN_REG); + + return 0; +} +/** + * @brief Get vibr gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_get_vibr_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + + *right_volume = 0 - HW_REG_READ(DA6_DIGITAL_GAIN_REG); + return 0; +} +/** + * @brief Get MIC1A & MIC2A gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_get_mic1a_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + if (gain_index == 0) + *left_volume = 31 - HW_REG_READ(AD3_DIGITAL_GAIN_REG); + if (gain_index == 1) + *left_volume = HW_REG_READ(ANALOG_MIC1_GAIN_REG); + + return 0; +} +/** + * @brief Get MIC2 gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_get_mic2_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + if (gain_index == 0) + *left_volume = 31 - HW_REG_READ(AD2_DIGITAL_GAIN_REG); + if (gain_index == 1) + *left_volume = HW_REG_READ(ANALOG_MIC2_GAIN_REG); + + return 0; +} +/** + * @brief Get Lin IN gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_get_lin_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + if (gain_index == 0) { + *left_volume = 31 - HW_REG_READ(AD1_DIGITAL_GAIN_REG); + *right_volume = 31 - HW_REG_READ(AD2_DIGITAL_GAIN_REG); + } + + if (gain_index == 0) { + *left_volume = 2 * ((HW_REG_READ(ANALOG_HS_GAIN_REG)>>4) - 5); + *right_volume = 2 * (HW_REG_READ(ANALOG_LINE_IN_GAIN_REG) - 5); + } + + return 0; +} +/** + * @brief Get DMIC12 gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_get_dmic12_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + + *left_volume = HW_REG_READ(AD1_DIGITAL_GAIN_REG); + + *right_volume = HW_REG_READ(AD2_DIGITAL_GAIN_REG); + + return 0; +} +/** + * @brief Get DMIC34 gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_get_dmic34_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + *left_volume = HW_REG_READ(AD3_DIGITAL_GAIN_REG); + *right_volume = HW_REG_READ(AD4_DIGITAL_GAIN_REG); + + return 0; +} +/** + * @brief Get DMIC56 gain + * @left_volume + * @right_volume + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_get_dmic56_gain(int *left_volume, int *right_volume, + u16 gain_index, struct device *dev) +{ + *left_volume = HW_REG_READ(AD5_DIGITAL_GAIN_REG); + + *right_volume = HW_REG_READ(AD6_DIGITAL_GAIN_REG); + return 0; +} +/** + * @brief Set gain of headset along a specified channel + * @channel_index Channel-index of headset + * @gain_index Gain index of headset + * @gain_value Gain value of headset + * @linear + * @return 0 on success otherwise negative error code + */ + + +int ste_audio_io_set_headset_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + int i = 0; + int acodec_device_id; + + acodec_device_id = abx500_get_chip_id(dev); + + if (channel_index & e_CHANNEL_1) { + if (gain_index == 0) { + int gain = 0; + gain = 0 - gain_value; + + initial_val = HW_REG_READ(DA1_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(DA1_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain HSL gainindex = %d %d", + gain_index, error); + return error; + } + } + + if (gain_index == 1) { + int gain = 0; + gain = 8 - gain_value; + + initial_val = HW_REG_READ(HSL_EAR_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(HSL_EAR_DIGITAL_GAIN_REG, + ((initial_val & (~HS_DIGITAL_GAIN_MASK)) | (gain & + HS_DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain HSL gain index = %d %d", + gain_index, error); + return error; + } + } + + if (gain_index == 2) { + /* Set Analog gain */ + int gain = -1; + + if (gain_value % 2) { + gain_value -= 1; + dev_err(dev, + "Odd Gain received.Fixing it to 2dB step gain_value = %d", + gain_value); + } + /* Fix for 4dB step gains. Select one lower value */ + if (gain_value == -22) + gain_value = -24; + + if (gain_value == -26) + gain_value = -28; + + if (gain_value == -30) + gain_value = -32; + + for (i = 0 ; i < 16; i++) { + if (hs_analog_gain_table[i] == gain_value) { + gain = i<<4; + break; + } + } + if (gain == -1) + return -1; + + if ((AB8500_REV_10 == acodec_device_id) || + (AB8500_REV_11 == acodec_device_id)) { + if (!gain) + gain = 0x10; + gain = 0xF0 - gain; + } + initial_val = HW_REG_READ(ANALOG_HS_GAIN_REG); + + /* Write gain */ + error = HW_REG_WRITE(ANALOG_HS_GAIN_REG, ((initial_val & + (~L_ANALOG_GAIN_MASK)) | (gain & L_ANALOG_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain HSL gain index = %d %d", + gain_index, error); + return error; + } + } + } + + /* for HSR */ + if (channel_index & e_CHANNEL_2) { + /* Set Gain HSR */ + if (gain_index == 0) { + int gain = 0; + gain = 0 - gain_value; + + initial_val = HW_REG_READ(DA2_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(DA2_DIGITAL_GAIN_REG, ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain HSR gain index = %d %d", + gain_index, error); + return error; + } + } + + if (gain_index == 1) { + int gain = 0; + gain = 8 - gain_value; + + initial_val = HW_REG_READ(HSR_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(HSR_DIGITAL_GAIN_REG, ((initial_val + & (~HS_DIGITAL_GAIN_MASK)) | (gain & + HS_DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain HSR gain index = %d %d", + gain_index, error); + return error; + } + + } + + if (gain_index == 2) { + /* Set Analog gain */ + int gain = -1; + + if (gain_value % 2) { + gain_value -= 1; + dev_err(dev, + "Odd Gain received.Fixing it to 2dB step gain_value = %d", + gain_value); + } + /* Fix for 4dB step gains. Select one lower value */ + if (gain_value == -22) + gain_value = -24; + + if (gain_value == -26) + gain_value = -28; + + if (gain_value == -30) + gain_value = -32; + + for (i = 0 ; i < 16 ; i++) { + if (hs_analog_gain_table[i] == gain_value) { + gain = i; + break; + } + } + if (gain == -1) + return -1; + + if ((AB8500_REV_10 == acodec_device_id) || + (AB8500_REV_11 == acodec_device_id)) { + if (!gain) + gain = 1; + gain = 0x0F - gain; + } + initial_val = HW_REG_READ(ANALOG_HS_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(ANALOG_HS_GAIN_REG, ((initial_val & + (~R_ANALOG_GAIN_MASK)) | (gain & R_ANALOG_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain HSR gainindex = %d %d", + gain_index, error); + return error; + } + } + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Set gain of earpiece + * @channel_index Channel-index of earpiece + * @gain_index Gain index of earpiece + * @gain_value Gain value of earpiece + * @linear + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_set_earpiece_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + if (channel_index & e_CHANNEL_1) { + if (0 == gain_index) { + int gain = 0; + gain = 0 - gain_value; + + initial_val = HW_REG_READ(DA1_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(DA1_DIGITAL_GAIN_REG, ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain Ear gainindex = %d %d", + gain_index, error); + return error; + } + } + + if (gain_index == 1) { + int gain = 0; + gain = 8 - gain_value; + + initial_val = HW_REG_READ(HSL_EAR_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(HSL_EAR_DIGITAL_GAIN_REG, + ((initial_val & (~HS_DIGITAL_GAIN_MASK)) | (gain & + HS_DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain Ear gainindex = %d %d", + gain_index, error); + return error; + } + } + } + dump_acodec_registers(__func__, dev); + return error; +} +/** + * @brief Set gain of vibl + * @channel_index Channel-index of vibl + * @gain_index Gain index of vibl + * @gain_value Gain value of vibl + * @linear + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_set_vibl_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + /* Set Gain vibl */ + if (gain_index == 0) { + int gain = 0; + gain = 0 - gain_value; + + initial_val = HW_REG_READ(DA5_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(DA5_DIGITAL_GAIN_REG, ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain VibL gain index = %d %d", + gain_index, error); + return error; + } + } + } + return error; +} +/** + * @brief Set gain of vibr + * @channel_index Channel-index of vibr + * @gain_index Gain index of vibr + * @gain_value Gain value of vibr + * @linear + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_set_vibr_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, + u32 linear, + struct device *dev) +{ + + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + /* Set Gain vibr */ + if (gain_index == 0) { + int gain = 0; + gain = 0 - gain_value; + + initial_val = HW_REG_READ(DA6_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(DA6_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain VibR gain index = %d %d", + gain_index, error); + return error; + } + } + } + return error; +} +/** + * @brief Set gain of ihf along a specified channel + * @channel_index Channel-index of ihf + * @gain_index Gain index of ihf + * @gain_value Gain value of ihf + * @linear + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_set_ihf_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + /* Set Gain IHFL */ + if (gain_index == 0) { + int gain = 0; + gain = 0 - gain_value; + + initial_val = HW_REG_READ(DA3_DIGITAL_GAIN_REG); + error = HW_REG_WRITE(DA3_DIGITAL_GAIN_REG, ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain IHFL gain index = %d %d", + gain_index, error); + return error; + } + + } + } + if (channel_index & e_CHANNEL_2) { + /* Set Gain IHFR */ + if (gain_index == 0) { + int gain = 0; + gain = 0 - gain_value; + + initial_val = HW_REG_READ(DA4_DIGITAL_GAIN_REG); + error = HW_REG_WRITE(DA4_DIGITAL_GAIN_REG, ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain IHFR gain index = %d %d", + gain_index, error); + return error; + } + } + } + + return error; +} +/** + * @brief Set gain of MIC1A & MIC1B + * @channel_index Channel-index of MIC1 + * @gain_index Gain index of MIC1 + * @gain_value Gain value of MIC1 + * @linear + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_set_mic1a_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + /* Set Gain mic1 */ + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD3_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(AD3_DIGITAL_GAIN_REG, ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain Mic1 gain index = %d %d", + gain_index, error); + return error; + } + + } + + if (gain_index == 1) { + /* Set Analog gain */ + initial_val = HW_REG_READ(ANALOG_MIC1_GAIN_REG); + + /* Write gain */ + error = HW_REG_WRITE(ANALOG_MIC1_GAIN_REG, ((initial_val + & (~MIC_ANALOG_GAIN_MASK)) | (gain_value & + MIC_ANALOG_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain Mic1 gain index = %d %d", + gain_index, error); + return error; + } + } + } + return error; +} +/** + * @brief Set gain of MIC2 + * @channel_index Channel-index of MIC2 + * @gain_index Gain index of MIC2 + * @gain_value Gain value of MIC2 + * @linear + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_set_mic2_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, + u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + /* Set Gain mic2 */ + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD2_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(AD2_DIGITAL_GAIN_REG, ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain Mic2 gain index = %d %d", + gain_index, error); + return error; + } + + } + + if (gain_index == 1) { + /* Set Analog gain */ + initial_val = HW_REG_READ(ANALOG_MIC2_GAIN_REG); + + /* Write gain */ + error = HW_REG_WRITE(ANALOG_MIC2_GAIN_REG, ((initial_val + & (~MIC_ANALOG_GAIN_MASK)) | (gain_value & + MIC_ANALOG_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain Mic2 gain index = %d %d", + gain_index, error); + return error; + } + } + } + return error; +} +/** + * @brief Set gain of Lin IN along a specified channel + * @channel_index Channel-index of Lin In + * @gain_index Gain index of Lin In + * @gain_value Gain value of Lin In + * @linear + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_set_lin_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD1_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(AD1_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain LinInL gain index = %d %d", + gain_index, error); + return error; + } + + } + + if (gain_index == 1) { + int gain = 0; + /* + * Converting -10 to 20 range into 0 - 15 + * & shifting it left by 4 bits + */ + gain = ((gain_value/2) + 5)<<4; + + initial_val = HW_REG_READ(ANALOG_LINE_IN_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(ANALOG_LINE_IN_GAIN_REG, + ((initial_val & (~L_ANALOG_GAIN_MASK)) | (gain & + L_ANALOG_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain LinInL gain index = %d %d", + gain_index, error); + return error; + } + } + } + + if (channel_index & e_CHANNEL_2) { + /* Set Gain LinInR */ + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD2_DIGITAL_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(AD2_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain LinInR gain index = %d%d", + gain_index, error); + return error; + } + } + if (gain_index == 1) { + int gain = 0; + /* Converting -10 to 20 range into 0 - 15 */ + gain = ((gain_value/2) + 5); + + initial_val = HW_REG_READ(ANALOG_LINE_IN_GAIN_REG); + /* Write gain */ + error = HW_REG_WRITE(ANALOG_LINE_IN_GAIN_REG, + ((initial_val & (~R_ANALOG_GAIN_MASK)) | (gain & + R_ANALOG_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain LinInR gain index = %d %d", + gain_index, error); + return error; + } + } + } + + return error; +} +/** + * @brief Set gain of DMIC12 along a specified channel + * @channel_index Channel-index of DMIC12 + * @gain_index Gain index of DMIC12 + * @gain_value Gain value of DMIC12 + * @linear + * @return 0 on success otherwise negative error code + */ + +int ste_audio_io_set_dmic12_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + /* Set Gain Dmic1 */ + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD1_DIGITAL_GAIN_REG); + error = HW_REG_WRITE(AD1_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain DMic1 gain index = %d %d", + gain_index, error); + return error; + } + } + } + if (channel_index & e_CHANNEL_2) { + /* Set Gain Dmic2 */ + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD2_DIGITAL_GAIN_REG); + error = HW_REG_WRITE(AD2_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain DMic2 gain index = %d %d", + gain_index, error); + return error; + } + } + } + return error; +} + +int ste_audio_io_switch_to_burst_mode_headset(int burst_fifo_switch_frame, + struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_INT_CONTROL_REG, + WAKEUP_SIGNAL_SAMPLE_COUNT, 0); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_LENGTH_REG, + BURST_FIFO_TRANSFER_LENGTH, 0); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_CONTROL_REG, + (BURST_FIFO_INF_RUNNING | BURST_FIFO_INF_IN_MASTER_MODE + |PRE_BIT_CLK0_COUNT), 0); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_WAKE_UP_DELAY_REG, + BURST_FIFO_WAKUP_DEALAY, 0); + if (0 != error) + return error; + + error = HW_REG_WRITE(BURST_FIFO_SWITCH_FRAME_REG, + burst_fifo_switch_frame); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(TDM_IF_BYPASS_B_FIFO_REG, + IF0_BFifoEn, 0); + if (0 != error) + return error; + + return error; +} +int ste_audio_io_switch_to_normal_mode_headset( + struct device *dev) +{ + int error = 0; + + error = HW_ACODEC_MODIFY_WRITE(TDM_IF_BYPASS_B_FIFO_REG, 0, + IF0_BFifoEn); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_INT_CONTROL_REG, + 0, WAKEUP_SIGNAL_SAMPLE_COUNT); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_LENGTH_REG, + 0, BURST_FIFO_TRANSFER_LENGTH); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_CONTROL_REG, 0, + (BURST_FIFO_INF_RUNNING | BURST_FIFO_INF_IN_MASTER_MODE + |PRE_BIT_CLK0_COUNT)); + if (0 != error) + return error; + + error = HW_ACODEC_MODIFY_WRITE(BURST_FIFO_WAKE_UP_DELAY_REG, + 0, BURST_FIFO_WAKUP_DEALAY); + if (0 != error) + return error; + + return error; +} + + +int ste_audio_io_mute_vibl(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + return 0; +} + +int ste_audio_io_unmute_vibl(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + return 0; +} + +int ste_audio_io_mute_vibr(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + return 0; +} +int ste_audio_io_unmute_vibr(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + return 0; +} + +int ste_audio_io_mute_dmic12(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + if ((channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + error = ste_audio_io_set_dmic12_gain(channel_index, 0, -32, + 0, dev); + if (0 != error) { + dev_err(dev, "Mute dmic12 %d", error); + return error; + } + } + + return error; + +} + +int ste_audio_io_unmute_dmic12(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + if ((channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + error = ste_audio_io_set_dmic12_gain(channel_index, + 0, gain[0], 0, dev); + if (0 != error) { + dev_err(dev, "UnMute dmic12 %d", error); + return error; + } + } + return error; +} +int ste_audio_io_enable_fade_dmic12(struct device *dev) +{ + return 0; +} + +int ste_audio_io_disable_fade_dmic12(struct device *dev) +{ + return 0; +} + +/** + * @brief enable hardware loop of dmic12 + * @chnl_index Channel-index of dmic12 + * @hw_loop type of hardware loop + * @loop_gain gain value to be used in hardware loop + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_enable_loop_dmic12(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS hw_loop, + int loop_gain, struct device *dev, + void *cookie) +{ + int error = 0; + struct transducer_context_t *trnsdr; + trnsdr = (struct transducer_context_t *)cookie; + + switch (hw_loop) { + /* Check if HSL is active */ + case AUDIOIO_SIDETONE_LOOP: + if (!trnsdr[HS_CH].is_power_up[e_CHANNEL_1] + && !trnsdr[EAR_CH].is_power_up[e_CHANNEL_1]) { + error = -EFAULT; + dev_err(dev, + "Sidetone enable needs HS or Earpiece powered up, err = %d", + error); + return error; + } + + if (chnl_index & e_CHANNEL_1) { + /* For ch1, Power On STFIR1, data comes from AD1*/ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG2, + 0, FIR1_FROMAD1); + if (error) { + dev_err(dev, "FIR1 data comes from AD_OUT1 %d", + error); + return error; + } + + error = HW_REG_WRITE(SIDETONE_FIR1_GAIN_REG, loop_gain); + if (error) { + dev_err(dev, + "Set FIR1 Gain index = %d", error); + return error; + } + } + + if (chnl_index & e_CHANNEL_2) { + /* For ch2, Power On STFIR1, data comes from AD2*/ + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG2, + 0, FIR1_FROMAD2); + if (error) { + dev_err(dev, "FIR1 data comes from AD_OUT2 %d", + error); + return error; + } + error = HW_REG_WRITE(SIDETONE_FIR2_GAIN_REG, loop_gain); + if (error) { + dev_err(dev, + "Set FIR2 Gain error = %d", error); + return error; + } + } + break; + default: + error = -EINVAL; + dev_err(dev, "loop not supported %d", error); + } + dump_acodec_registers(__func__, dev); + return error; +} + +/** + * @brief disable hardware loop of dmic12 + * @chnl_index Channel-index of dmic12 + * @hw_loop type of hardware loop + * @return 0 on success otherwise negative error code + */ +int ste_audio_io_disable_loop_dmic12(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS hw_loop, + struct device *dev, void *cookie) +{ + int error = -EINVAL; + struct transducer_context_t *trnsdr; + trnsdr = (struct transducer_context_t *)cookie; + + switch (hw_loop) { + /* Check if HSL is active */ + case AUDIOIO_SIDETONE_LOOP: + if (!trnsdr[HS_CH].is_power_up[e_CHANNEL_1] + && !trnsdr[EAR_CH].is_power_up[e_CHANNEL_1]) { + error = -EFAULT; + dev_err(dev, + "Sidetone disable needs HS or Earpiece powered up, err = %d", + error); + return error; + } + + if (chnl_index & e_CHANNEL_1) { + /* For ch1, Power On STFIR1, data comes from AD1*/ + error = + HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG2, + 0, FIR1_FROMAD1); + if (error) + dev_err(dev, "FIR1 data comes from AD_OUT1 %d", + error); + } + + if (chnl_index & e_CHANNEL_2) { + /* For ch2, Power On STFIR1, data comes from AD2*/ + error = + HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG2, + 0, FIR1_FROMAD2); + if (error) + dev_err(dev, "FIR1 data comes from AD_OUT2 %d", + error); + } + error = HW_ACODEC_MODIFY_WRITE(FILTERS_CONTROL_REG, + 0, FIR_FILTERCONTROL); + if (error) { + dev_err(dev, + "ST FIR Filters disable failed %d", error); + return error; + } + break; + default: + dev_err(dev, "loop not supported %d", error); + } + dump_acodec_registers(__func__, dev); + return error; +} + +int ste_audio_io_power_up_dmic34(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + /* Check if DMic34 request is mono or Stereo */ + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "DMic34 does not support more than 2 channels"); + return -EINVAL; + } + + /* Setting Direction for GPIO pins on AB8500 */ + error = HW_REG_WRITE(AB8500_GPIO_DIR4_REG, GPIO29_DIR_OUTPUT); + if (0 != error) { + dev_err(dev, "Setting Direction for GPIO pins on AB8500 %d", + error); + return error; + } + + if (channel_index & e_CHANNEL_1) { + /* Check if DMIC3 is already powered up or used by Mic1A + or Mic1B */ + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + + if (initialVal_AD & (EN_AD3)) + return 0; + + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT2_3_REG, + DATA_FROM_AD_OUT3, 0); + if (0 != error) { + dev_err(dev, "Slot 02 outputs data from AD_OUT3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, EN_AD3, + 0); + if (0 != error) { + dev_err(dev, "Enable AD3 for DMIC3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_MUXES_REG1, + SEL_DMIC3_FOR_AD_OUT3, + 0); + if (0 != error) { + dev_err(dev, "Select DMIC3 for AD_OUT3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, EN_DMIC3, 0); + if (0 != error) { + dev_err(dev, "Enable DMIC3 %d", error); + return error; + } +} + + /* Enable AD4 for Dmic4 */ + if (channel_index & e_CHANNEL_2) { + /* Check if DMIC4 is already powered up */ + if (initialVal_AD & (EN_AD4)) + return 0; + + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT2_3_REG, + (DATA_FROM_AD_OUT4<<4), 0); + if (0 != error) { + dev_err(dev, "Slot 03 outputs data from AD_OUT4 %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + EN_AD4, 0); + if (0 != error) { + dev_err(dev, "Enable AD4 for DMIC4 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, EN_DMIC4, 0); + if (0 != error) { + dev_err(dev, "Enable DMIC4 %d", error); + return error; + } + } + return error; +} + +int ste_audio_io_power_down_dmic34(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "DMic34 does not support more than 2 channels"); + return -EINVAL; + } + + /* Setting Direction for GPIO pins on AB8500 */ + error = HW_ACODEC_MODIFY_WRITE(AB8500_GPIO_DIR4_REG, 0, + GPIO29_DIR_OUTPUT); + if (0 != error) { + dev_err(dev, "Clearing Direction for GPIO pins on AB8500 %d", + error); + return error; + } + + /* Enable AD1 for Dmic1 */ + if (channel_index & e_CHANNEL_1) { + /* Check if DMIC3 is already powered Down or used by Mic1A + or Mic1B */ + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + if (!(initialVal_AD & EN_AD3)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, 0, EN_DMIC3); + if (0 != error) { + dev_err(dev, "Enable DMIC3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, + EN_AD3); + if (0 != error) { + dev_err(dev, "Disable AD3 for DMIC3 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT2_3_REG, 0, + DATA_FROM_AD_OUT3); + if (0 != error) { + dev_err(dev, + "Slot 02 outputs data cleared from AD_OUT3 %d", + error); + return error; + } + } + + /* Enable AD4 for Dmic4 */ + if (channel_index & e_CHANNEL_2) { + /* Check if DMIC4 is already powered down */ + initialVal_AD = HW_REG_READ(DIGITAL_AD_CHANNELS_ENABLE_REG); + if (!(initialVal_AD & EN_AD4)) + return 0; + + error = HW_ACODEC_MODIFY_WRITE(DMIC_ENABLE_REG, 0, EN_DMIC4); + if (0 != error) { + dev_err(dev, "Enable DMIC4 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(DIGITAL_AD_CHANNELS_ENABLE_REG, + 0, EN_AD4); + if (0 != error) { + dev_err(dev, "Disable AD4 for DMIC4 %d", error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT2_3_REG, 0, + (DATA_FROM_AD_OUT4<<4)); + if (0 != error) { + dev_err(dev, + "Slot 03 outputs data cleared from AD_OUT4 %d", + error); + return error; + } + } + return error; +} +int ste_audio_io_set_dmic34_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + int error = 0; + unsigned char initial_val = 0; + + if (channel_index & e_CHANNEL_1) { + /* Set Gain Dmic3 */ + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD3_DIGITAL_GAIN_REG); + error = HW_REG_WRITE(AD3_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + if (0 != error) { + dev_err(dev, + "Set Gain DMic3 gain index = %d %d", + gain_index, error); + return error; + } + } + } + + if (channel_index & e_CHANNEL_2) { + /* Set Gain Dmic4 */ + if (gain_index == 0) { + int gain = 0; + gain = 31 - gain_value; + + initial_val = HW_REG_READ(AD4_DIGITAL_GAIN_REG); + error = HW_REG_WRITE(AD4_DIGITAL_GAIN_REG, + ((initial_val + & (~DIGITAL_GAIN_MASK)) | (gain & DIGITAL_GAIN_MASK))); + + if (0 != error) { + dev_err(dev, + "Set Gain DMic4 gain index = %d %d", + gain_index, error); + return error; + } + } + } + + return error; +} +int ste_audio_io_mute_dmic34(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + if ((channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + error = ste_audio_io_set_dmic34_gain(channel_index, 0, -32, + 0, dev); + if (0 != error) { + dev_err(dev, "Mute dmic34 %d", error); + return error; + } + } + return error; +} +int ste_audio_io_unmute_dmic34(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + int error = 0; + if ((channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + error = ste_audio_io_set_dmic34_gain(channel_index, + 0, gain[0], 0, dev); + if (0 != error) { + dev_err(dev, "UnMute dmic34 %d", error); + return error; + } + } + return error; +} +int ste_audio_io_enable_fade_dmic34(struct device *dev) +{ + return 0; +} + +int ste_audio_io_disable_fade_dmic34(struct device *dev) +{ + return 0; +} + +int ste_audio_io_power_up_dmic56(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + return 0; +} +int ste_audio_io_power_down_dmic56(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + return 0; +} +int ste_audio_io_set_dmic56_gain(enum AUDIOIO_CH_INDEX channel_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev) +{ + return 0; +} +int ste_audio_io_mute_dmic56(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + return 0; +} +int ste_audio_io_unmute_dmic56(enum AUDIOIO_CH_INDEX channel_index, int *gain, + struct device *dev) +{ + return 0; +} +int ste_audio_io_enable_fade_dmic56(struct device *dev) +{ + return 0; +} + +int ste_audio_io_disable_fade_dmic56(struct device *dev) +{ + return 0; +} + +int ste_audio_io_configure_if1(struct device *dev) +{ + int error = 0; + + error = HW_REG_WRITE(IF1_CONF_REG, IF_DELAYED | + I2S_LEFT_ALIGNED_FORMAT | WORD_LENGTH_16); + if (error != 0) { + dev_err(dev, + "Configure IF1: I2S Format 16 Bits word length error = %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(TDM_IF_BYPASS_B_FIFO_REG, IF1_MASTER, 0); + if (error != 0) { + dev_err(dev, + "Configure IF1: IF1 master error = %d", + error); + return error; + } + + error = HW_ACODEC_MODIFY_WRITE(IF0_IF1_MASTER_CONF_REG, + EN_FSYNC_BITCLK1, 0); + if (error != 0) { + dev_err(dev, + "ConfigIF1 bitclk is 32x48KHz, enable Fsync1 and Bitclk1 error = %d", + error); + return error; + } + return error; +} + +int ste_audio_io_power_up_fmrx(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal = 0; + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "FMRX should have mono or stereo channels"); + return -EINVAL; + } + + ste_audio_io_configure_if1(dev); + + if (channel_index & e_CHANNEL_1) { + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA7_REG, + SLOT24_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA_IN7 from Slot 24 %d", + error); + return error; + } + /* DA_IN7 to AD_OUT8 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA5_REG, + SEL_AD_OUT8_FROM_DAIN7, 0); + if (0 != error) { + dev_err(dev, "Data sent to AD_OUT5 from DA_IN7 %d", + error); + return error; + } + + initialVal = HW_REG_READ(AD_ALLOCATION_TO_SLOT6_7_REG); + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT6_7_REG, + ((initialVal & MASK_QUARTET1)|SEL_IF6_FROM_AD_OUT5)); + if (0 != error) { + dev_err(dev, "Data sent to IF slot 6 from AD_OUT5 %d", + error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA8_REG, + SLOT25_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA_IN8 from Slot 25 %d", + error); + return error; + } + + /* DA_IN7 to AD_OUT8 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA6_REG, + SEL_AD_OUT6_FROM_DAIN8, 0); + if (0 != error) { + dev_err(dev, "Data sent to AD_OUT6 from DA_IN8 %d", + error); + return error; + } + + initialVal = HW_REG_READ(AD_ALLOCATION_TO_SLOT6_7_REG); + + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT6_7_REG, + (initialVal & MASK_QUARTET0)|SEL_IF7_FROM_AD_OUT6); + /* 5x is written */ + if (0 != error) { + dev_err(dev, "Data sent to IF7 from AD_OUT6 %d", + error); + return error; + } + } + return error; +} +int ste_audio_io_power_down_fmrx(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "FMRX should have mono or stereo channels"); + return -EINVAL; + } + if (channel_index & e_CHANNEL_1) { + /* data sent to DA7 input of DA filter form IF1 */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA7_REG, 0, + SLOT24_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, "Clearing Data sent to DA_IN7 from Slot 24 %d", + error); + return error; + } + /* DA_IN7 to AD_OUT8 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA5_REG, 0, + SEL_AD_OUT8_FROM_DAIN7); + if (0 != error) { + dev_err(dev, "Clearing Data sent to AD_OUT5 from DA_IN7 %d", + error); + return error; + } + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT6_7_REG, 0, + SEL_IF6_FROM_AD_OUT5); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to IF slot 6 from AD_OUT5 %d", + error); + return error; + } +} + + if (channel_index & e_CHANNEL_2) { + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA8_REG, 0, + SLOT25_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to DA_IN8 from Slot 25 %d", + error); + return error; + } + + /* DA_IN7 to AD_OUT8 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA6_REG, 0, + SEL_AD_OUT6_FROM_DAIN8); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to AD_OUT6 from DA_IN8 %d", + error); + return error; + } + error = HW_ACODEC_MODIFY_WRITE(AD_ALLOCATION_TO_SLOT6_7_REG, 0, + SEL_IF7_FROM_AD_OUT6); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to IF7 from AD_OUT6 %d", + error); + return error; + } + } + return error; +} + +int ste_audio_io_power_up_fmtx(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal = 0; + + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "FMTX should have mono or stereo channels"); + return -EINVAL; + } + + ste_audio_io_configure_if1(dev); + + if (channel_index & e_CHANNEL_1) { + /* data sent to DA7 input of DA filter form IF1 14 slot */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA7_REG, + SLOT14_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, + "Data sent to DA_IN7 from Slot 14 %d", error); + return error; + } + /* DA_IN7 to AD_OUT5 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA5_REG, + SEL_AD_OUT5_FROM_DAIN7, 0); + if (0 != error) { + dev_err(dev, "Data sent to AD_OUT5 from DA_IN7 %d", + error); + return error; + } + + initialVal = HW_REG_READ(AD_ALLOCATION_TO_SLOT16_17_REG); + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT16_17_REG, + (initialVal & MASK_QUARTET1)|SEL_IF6_FROM_AD_OUT5); + if (0 != error) { + dev_err(dev, "Data sent to IF16 from AD_OUT5 %d", + error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + /* data sent to DA8 input of DA filter */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA8_REG, + SLOT15_FOR_DA_PATH, 0); + if (0 != error) { + dev_err(dev, "Data sent to DA_IN8 from Slot 15 %d", + error); + return error; + } + + /* DA_IN8 to AD_OUT6 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA6_REG, + SEL_AD_OUT6_FROM_DAIN8, 0); + if (0 != error) { + dev_err(dev, "Data sent to AD_OUT6 from DA_IN8 %d", + error); + return error; + } + + initialVal = HW_REG_READ(AD_ALLOCATION_TO_SLOT16_17_REG); + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT16_17_REG, + (initialVal & MASK_QUARTET0)|SEL_IF17_FROM_AD_OUT6); + if (0 != error) { + dev_err(dev, "Data sent to IF17 from AD_OUT6 %d", + error); + return error; + } + } + return error; +} + +int ste_audio_io_power_down_fmtx(enum AUDIOIO_CH_INDEX channel_index, + struct device *dev) +{ + int error = 0; + unsigned char initialVal_AD = 0; + + if (!(channel_index & (e_CHANNEL_1 | e_CHANNEL_2))) { + dev_err(dev, "FMTX should have mono or stereo channels"); + return -EINVAL; + } + + if (channel_index & e_CHANNEL_1) { + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA7_REG, 0, + SLOT14_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to DA_IN7 from Slot 14 %d", + error); + return error; + } + /* DA_IN7 to AD_OUT8 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA5_REG, 0, + SEL_AD_OUT5_FROM_DAIN7); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to AD_OUT5 from DA_IN7 %d", + error); + return error; + } + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT16_17_REG, + SEL_IF6_FROM_AD_OUT5); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to IF16 from AD_OUT8 %d", + error); + return error; + } + } + + if (channel_index & e_CHANNEL_2) { + /* data sent to DA8 input of DA filter */ + initialVal_AD = HW_REG_READ(SLOT_SELECTION_TO_DA8_REG); + + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA8_REG, 0, + SLOT15_FOR_DA_PATH); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to DA_IN8 from Slot 15 %d", + error); + return error; + } + + /* DA_IN7 to AD_OUT8 path */ + error = HW_ACODEC_MODIFY_WRITE(SLOT_SELECTION_TO_DA6_REG, 0, + SEL_AD_OUT6_FROM_DAIN8); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to AD_OUT6 from DA_IN8 %d", + error); + return error; + } + error = HW_REG_WRITE(AD_ALLOCATION_TO_SLOT16_17_REG, + SEL_IF17_FROM_AD_OUT6); + if (0 != error) { + dev_err(dev, + "Clearing Data sent to IF17 from AD_OUT6 %d", + error); + return error; + } + } + return error; +} +int ste_audio_io_power_up_bluetooth(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev) +{ + int error = 0; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); + struct ab8500_platform_data *pdata = dev_get_platdata(ab8500->dev); + if (bluetooth_power_up_count++) + return error; + + if (pdata) { + if (pdata->audio) { + error = pdata->audio->ste_gpio_altf_init(); + if (error == 0) { + clk_ptr_msp0 = clk_get_sys("msp0", NULL); + if (!IS_ERR(clk_ptr_msp0)) { + error = clk_enable(clk_ptr_msp0); + return error; + } else + return -EFAULT; + } + } + } + return error; +} + +int ste_audio_io_power_down_bluetooth(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev) +{ + int error = 0; + struct ab8500 *ab8500 = dev_get_drvdata(dev->parent); + struct ab8500_platform_data *pdata = dev_get_platdata(ab8500->dev); + + if (--bluetooth_power_up_count) + return error; + + if (pdata) { + if (pdata->audio) { + error = pdata->audio->ste_gpio_altf_exit(); + if (error == 0) { + clk_disable(clk_ptr_msp0); + clk_put(clk_ptr_msp0); + } + } + } + return error; +} + +int dump_acodec_registers(const char *str, struct device *dev) +{ + int reg_count = REVISION_REG & 0xff; + if (1 == acodec_reg_dump) { + u8 i = 0; + dev_info(dev, "\n func : %s\n", str); + for (i = 0; i <= reg_count; i++) + dev_info(dev, + "block = 0x0D, adr = %x = %x\n", + i, HW_REG_READ((AB8500_AUDIO << 8) | i)); + } + str = str; /* keep compiler happy */ + return 0; +} + +int debug_audioio(int x) +{ + + if (1 == x) + acodec_reg_dump = 1; + else + acodec_reg_dump = 0; + return 0; +} + + + + + diff --git a/drivers/misc/audio_io_dev/ste_audio_io_func.h b/drivers/misc/audio_io_dev/ste_audio_io_func.h new file mode 100644 index 00000000000..282b25751d6 --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_func.h @@ -0,0 +1,359 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#ifndef _AUDIOIO_FUNC_H_ +#define _AUDIOIO_FUNC_H_ + +#include +#include +#include +#include +#include + +#define AB8500_REV_10 0x10 +#define AB8500_REV_11 0x11 +#define AB8500_REV_20 0x20 + +#define AB8500_CTRL3_REG 0x00000200 +#define AB8500_SYSULPCLK_CTRL1_REG 0x0000020B +#define AB8500_GPIO_DIR4_REG 0x00001013 +#define AB8500_GPIO_DIR5_REG 0x00001014 +#define AB8500_GPIO_OUT5_REG 0x00001024 + +extern struct platform_device *ste_audio_io_device; +extern struct regulator *regulator_avsource; + +int dump_acodec_registers(const char *, struct device *dev); +int debug_audioio(int x); + +#define AB8500_BLOCK_ADDR(address) ((address >> 8) & 0xff) +#define AB8500_OFFSET_ADDR(address) (address & 0xff) + +static inline unsigned char HW_REG_READ(unsigned short reg) +{ + unsigned char ret; + int err; + + err = abx500_get_register_interruptible(&ste_audio_io_device->dev, + AB8500_BLOCK_ADDR(reg), + AB8500_OFFSET_ADDR(reg), + &ret); + if (err < 0) + return err; + else + return ret; +} + +static inline int HW_REG_WRITE(unsigned short reg, unsigned char data) +{ + return abx500_set_register_interruptible(&ste_audio_io_device->dev, + AB8500_BLOCK_ADDR(reg), + AB8500_OFFSET_ADDR(reg), + data); +} + +unsigned int ab8500_acodec_modify_write(unsigned int reg, u8 mask_set, + u8 mask_clear); + +#define HW_ACODEC_MODIFY_WRITE(reg, mask_set, mask_clear)\ + ab8500_acodec_modify_write(reg, mask_set, mask_clear) + +unsigned int ab8500_modify_write(unsigned int reg, u8 mask_set, u8 mask_clear); + +int ste_audio_io_power_up_headset(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_headset(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_headset_query(struct device *dev); +int ste_audio_io_set_headset_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_headset_gain(int *, int *, u16, + struct device *dev); +int ste_audio_io_mute_headset(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_headset(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_headset_state(struct device *dev); +int ste_audio_io_enable_fade_headset(struct device *dev); +int ste_audio_io_disable_fade_headset(struct device *dev); +int ste_audio_io_switch_to_burst_mode_headset(int burst_fifo_switch_frame, + struct device *dev); +int ste_audio_io_switch_to_normal_mode_headset( + struct device *dev); + +int ste_audio_io_power_up_earpiece(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_earpiece(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_earpiece_query(struct device *dev); +int ste_audio_io_set_earpiece_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_earpiece_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_earpiece(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_earpiece(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_earpiece_state(struct device *dev); +int ste_audio_io_enable_fade_earpiece(struct device *dev); +int ste_audio_io_disable_fade_earpiece(struct device *dev); + +int ste_audio_io_power_up_ihf(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_ihf(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_ihf_query(struct device *dev); +int ste_audio_io_set_ihf_gain(enum AUDIOIO_CH_INDEX chnl_index, u16 gain_index, + int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_ihf_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_ihf(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_ihf(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_ihf_state(struct device *dev); +int ste_audio_io_enable_fade_ihf(struct device *dev); +int ste_audio_io_disable_fade_ihf(struct device *dev); + +int ste_audio_io_power_up_vibl(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_vibl(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_vibl_query(struct device *dev); +int ste_audio_io_set_vibl_gain(enum AUDIOIO_CH_INDEX chnl_index, u16 gain_index, + int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_vibl_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_vibl(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_vibl(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_vibl_state(struct device *dev); +int ste_audio_io_enable_fade_vibl(struct device *dev); +int ste_audio_io_disable_fade_vibl(struct device *dev); + +int ste_audio_io_power_up_vibr(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_vibr(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_vibr_query(struct device *dev); +int ste_audio_io_set_vibr_gain(enum AUDIOIO_CH_INDEX chnl_index, u16 gain_index, + int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_vibr_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_vibr(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_vibr(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_vibr_state(struct device *dev); +int ste_audio_io_enable_fade_vibr(struct device *dev); +int ste_audio_io_disable_fade_vibr(struct device *dev); + +int ste_audio_io_power_up_mic1a(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_mic1a(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_mic1a_query(struct device *dev); +int ste_audio_io_set_mic1a_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, struct device *dev); +int ste_audio_io_get_mic1a_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_mic1a(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_mic1a(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_mic1a_state(struct device *dev); +int ste_audio_io_enable_fade_mic1a(struct device *dev); +int ste_audio_io_disable_fade_mic1a(struct device *dev); + +/* + *** Mic1b *** + */ +int ste_audio_io_power_up_mic1b(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_mic1b(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_mic1b_query(struct device *dev); +int ste_audio_io_set_mic1b_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, struct device *dev); +int ste_audio_io_get_mic1b_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_mic1b(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_mic1b(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_mic1b_state(struct device *dev); +int ste_audio_io_enable_fade_mic1b(struct device *dev); +int ste_audio_io_disable_fade_mic1b(struct device *dev); +int ste_audio_io_enable_loop_mic1b(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS, + int loop_gain, struct device *dev, + void *cookie); +int ste_audio_io_disable_loop_mic1b(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS hw_loop, + struct device *dev, void *cookie); +/* + *** Mic2 *** + */ +int ste_audio_io_power_up_mic2(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_mic2(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_mic2_query(struct device *dev); +int ste_audio_io_set_mic2_gain(enum AUDIOIO_CH_INDEX chnl_index, u16 gain_index, + int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_mic2_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_mic2(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_mic2(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_mic2_state(struct device *dev); +int ste_audio_io_enable_fade_mic2(struct device *dev); +int ste_audio_io_disable_fade_mic2(struct device *dev); + +int ste_audio_io_power_up_lin(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_lin(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_lin_query(struct device *dev); +int ste_audio_io_set_lin_gain(enum AUDIOIO_CH_INDEX chnl_index, u16 gain_index, + int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_lin_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_lin(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_lin(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_lin_state(struct device *dev); +int ste_audio_io_enable_fade_lin(struct device *dev); +int ste_audio_io_disable_fade_lin(struct device *dev); + +int ste_audio_io_power_up_dmic12(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_dmic12(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_dmic12_query(struct device *dev); +int ste_audio_io_set_dmic12_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_dmic12_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_dmic12(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_dmic12(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_dmic12_state(struct device *dev); +int ste_audio_io_enable_fade_dmic12(struct device *dev); +int ste_audio_io_disable_fade_dmic12(struct device *dev); +int ste_audio_io_enable_loop_dmic12(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS, + int loop_gain, struct device *dev, + void *cookie); +int ste_audio_io_disable_loop_dmic12(enum AUDIOIO_CH_INDEX chnl_index, + enum AUDIOIO_HAL_HW_LOOPS hw_loop, + struct device *dev, void *cookie); + +int ste_audio_io_power_up_dmic34(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_dmic34(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_dmic34_query(struct device *dev); +int ste_audio_io_set_dmic34_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_dmic34_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_dmic34(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_dmic34(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_dmic34_state(struct device *dev); +int ste_audio_io_enable_fade_dmic34(struct device *dev); +int ste_audio_io_disable_fade_dmic34(struct device *dev); + +int ste_audio_io_power_up_dmic56(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_dmic56(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_dmic56_query(struct device *dev); +int ste_audio_io_set_dmic56_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_dmic56_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_dmic56(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_dmic56(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_dmic56_state(struct device *dev); +int ste_audio_io_enable_fade_dmic56(struct device *dev); +int ste_audio_io_disable_fade_dmic56(struct device *dev); + +int ste_audio_io_power_up_fmrx(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_fmrx(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_fmrx_query(struct device *dev); +int ste_audio_io_set_fmrx_gain(enum AUDIOIO_CH_INDEX chnl_index, u16 gain_index, + int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_fmrx_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_fmrx(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_fmrx(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_fmrx_state(struct device *dev); +int ste_audio_io_enable_fade_fmrx(struct device *dev); +int ste_audio_io_disable_fade_fmrx(struct device *dev); + +int ste_audio_io_power_up_fmtx(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_fmtx(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_fmtx_query(struct device *dev); +int ste_audio_io_set_fmtx_gain(enum AUDIOIO_CH_INDEX chnl_index, u16 gain_index, + int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_fmtx_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_fmtx(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_fmtx(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_fmtx_state(struct device *dev); +int ste_audio_io_enable_fade_fmtx(struct device *dev); +int ste_audio_io_disable_fade_fmtx(struct device *dev); + +int ste_audio_io_power_up_bluetooth(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_down_bluetooth(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_power_state_bluetooth_query(struct device *dev); +int ste_audio_io_set_bluetooth_gain(enum AUDIOIO_CH_INDEX chnl_index, + u16 gain_index, int gain_value, u32 linear, + struct device *dev); +int ste_audio_io_get_bluetooth_gain(int*, int*, u16, + struct device *dev); +int ste_audio_io_mute_bluetooth(enum AUDIOIO_CH_INDEX chnl_index, + struct device *dev); +int ste_audio_io_unmute_bluetooth(enum AUDIOIO_CH_INDEX chnl_index, int *gain, + struct device *dev); +int ste_audio_io_mute_bluetooth_state(struct device *dev); +int ste_audio_io_enable_fade_bluetooth(struct device *dev); +int ste_audio_io_disable_fade_bluetooth(struct device *dev); + + +#endif + diff --git a/drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.c b/drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.c new file mode 100644 index 00000000000..c2409f849ae --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + + +#include +#include "ste_audio_io_hwctrl_common.h" + +/* Number of channels for each transducer */ +const uint transducer_no_of_channels[MAX_NO_TRANSDUCERS] = { + 1, /* Earpiece */ + 2, /* HS */ + 2, /* IHF */ + 1, /* VibL */ + 1, /* VibR */ + 1, /* Mic1A */ + 1, /* Mic1B */ + 1, /* Mic2 */ + 2, /* LinIn */ + 2, /* DMIC12 */ + 2, /* DMIC34 */ + 2, /* /DMIC56 */ + 4 /* MultiMic */ + }; + +/* Maximum number of gains in each transducer path + (all channels of a specific transducer have same max no of gains) */ +const uint transducer_no_of_gains[MAX_NO_TRANSDUCERS] = { + 2, /* Ear g3 and g4 */ + 3, /* HS g3 and g4 and analog */ + 1, /* IHF g3 */ + 1, /* VibL g3 */ + 1, /* VibR g3 */ + 2, /* Mic1A g1 and analog */ + 2, /* Mic1A g1 and analog */ + 2, /* Mic2 g1 and analog */ + 2, /* LinIn g1 and analog */ + 1, /* DMIC12 g1 */ + 1, /* DMIC34 g1 */ + 1, /* DMIC56 g1 */ + 1 /* MultiMic g1 */ + }; + +const uint transducer_no_Of_supported_loop_indexes[MAX_NO_TRANSDUCERS] = { + 0x09,/* Ear0x01|0x08*/ + 0x38770,/*{0x01|0x10|0x20|0x40|0x100*/ + /*|0x200|0x400|0x8000|0x10000|0x20000}, HS*/ + 0x86,/*IHF*/ + 0x0,/*VibL*/ + 0x0,/*VibR*/ + 0x0,/*Mic1A*/ + 0x01,/*Mic1B Sidetone is controlled*/ + 0x0,/*Mic2*/ + 0x0,/*LinIn*/ + 0x0,/*DMIC12*/ + 0x0,/*DMIC34*/ + 0x0,/*DMIC56*/ + 0x01,/*MultiMic Sidetone is controlled*/ + 0x0,/*FMRx*/ + 0x0/*FMTx*/ + }; + +const uint transducer_max_no_Of_supported_loops[MAX_NO_TRANSDUCERS] = { + 0,/*Ear Sidetone*/ + 2,/*HS SideTone LININ_HS LININR_HSR LININ_HSL*/ + 1,/*IHF TO BE DEFINED*/ + 0,/*VibL TO BE DEFINED*/ + 0,/*VibR TO BE DEFINED*/ + 1,/*Mic1A TO BE DEFINED*/ + 1,/*Mic1B SIDETONE TO BE DEFINED*/ + 1,/*Mic2 TO BE DEFINED*/ + 0, /* LinIn */ + 1,/*DMIC12-ANC*/ + 0,/*DMIC34-ANC*/ + 0, /* DMIC56 */ + 1,/*MultiMic-SIDETONE ANC*/ + 0,/*FMRx*/ + 0/*FMTx*/ + }; + +const uint max_no_of_loop_gains[MAX_NO_TRANSDUCERS] = { + 0,/*Earpiece*/ + 2,/*HS*/ + 0, + 0, + 0, + 0, + 2,/*Mic1B-Sidetone 2 gains*/ + 0, + 0, + 2,/*DMIC12*/ + 0, + 0, + 2,/*Multimic, Sidetone max no gains = 2*/ + 0, + 0 + }; + + +struct gain_descriptor_t gain_descriptor[MAX_NO_TRANSDUCERS]\ + [MAX_NO_CHANNELS][MAX_NO_GAINS] = { + /* gainIndex=0 1 2 + EDestinationEar */ + {{{-63, 0, 1}, {-1, 8, 1}, {0, 0, 0} } ,/* channelIndex=0 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* EDestinationHS */ + {{{-63, 0, 1}, {-1, 8, 1}, {-32, 4, 2} } , /* channelIndex=0 */ + {{-63, 0, 1}, {-1, 8, 1}, {-32, 4, 2} } , /* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } , /* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } , /* channelIndex=3 */ + + /* EDestinationIHF */ + {{{-63, 0, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=0 */ + {{-63, 0, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* EDestinationVibL */ + {{{-63, 0, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=0 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* EDestinationVibR */ + {{{-63, 0, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=0 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceMic1A */ + {{{-32, 31, 1}, {0, 31, 1}, {0, 0, 0} } ,/* channelIndex=0 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceMic1B */ + {{{-32, 31, 1}, {0, 31, 1}, {0, 0, 0} } ,/* channelIndex=0 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceMic2 */ + {{{-32, 31, 1}, {0, 31, 1}, {0, 0, 0} } ,/* channelIndex=0 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceLin */ + {{{-32, 31, 1}, {-10, 20, 2}, {0, 0, 0} } ,/* channelIndex=0 */ + {{-32, 31, 1}, {-10, 20, 2}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceDMic12 */ + {{{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=0 */ + {{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceDMic34 */ + {{{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=0 */ + {{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceDMic56 */ + {{{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=0 */ + {{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=2 */ + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0} } } ,/* channelIndex=3 */ + + /* ESourceMultiMic */ + {{{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=0 */ + {{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } ,/* channelIndex=1 */ + {{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} },/* channelIndex=2 */ + {{-32, 31, 1}, {0, 0, 0}, {0, 0, 0} } } /* channelIndex=3 */ +}; + + +const int hs_analog_gain_table[16] = {4, 2, 0, -2, -4, -6, -8, -10, + -12, -14, -16, -18, -20, -24, -28, -32}; + + + diff --git a/drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.h b/drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.h new file mode 100644 index 00000000000..cc2bfe21d81 --- /dev/null +++ b/drivers/misc/audio_io_dev/ste_audio_io_hwctrl_common.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Deepak KARDA/ deepak.karda@stericsson.com for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2. + */ + +#ifndef __AUDIOIO_HWCTRL_COMMON_H__ +#define __AUDIOIO_HWCTRL_COMMON_H__ + +#include +#include +/* + * Defines + */ + +#define MAX_GAIN 100 +#define MIN_GAIN 0 +#define MAX_NO_CHANNELS 4 +#define MAX_NO_GAINS 3 +#define MAX_NO_LOOPS 1 +#define MAX_NO_LOOP_GAINS 1 + +struct gain_descriptor_t { + int min_gain; + int max_gain; + uint gain_step; +}; + + +/* Number of channels for each transducer */ +extern const uint transducer_no_of_channels[MAX_NO_TRANSDUCERS]; + +/* + * Maximum number of gains in each transducer path + * all channels of a specific transducer have same max no of gains + */ +extern const uint transducer_no_of_gains[MAX_NO_TRANSDUCERS]; + +/* Maximum number of supported loops for each transducer */ +extern const uint transducer_no_Of_supported_loop_indexes[MAX_NO_TRANSDUCERS]; +extern const uint transducer_max_no_Of_supported_loops[MAX_NO_TRANSDUCERS]; +extern const uint max_no_of_loop_gains[MAX_NO_TRANSDUCERS]; +extern const int hs_analog_gain_table[16] ; + +extern struct gain_descriptor_t gain_descriptor[MAX_NO_TRANSDUCERS]\ + [MAX_NO_CHANNELS][MAX_NO_GAINS]; + +#endif + +/* End of audio_io_hwctrl_common.h */ -- cgit v1.2.3 From 411c4b196d3ecff23cdd610c841f0d52f1c33258 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Wed, 16 Jun 2010 16:59:00 +0200 Subject: twd: add CLOCK_EVT_FEAT_C3STOP to timer features Signed-off-by: Mian Yousaf Kaukab Change-Id: I4f89143a84439415ee739b1268dd9683c82deb4d Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2254 Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/kernel/smp_twd.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 2c277d40cee..03f30152d1b 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -127,8 +127,13 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) twd_calibrate_rate(); clk->name = "local_timer"; +#if defined(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) && \ + defined(CONFIG_LOCAL_TIMERS) clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP; +#else + clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; +#endif clk->rating = 350; clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; -- cgit v1.2.3 From 9a06c36690708b6ae284de66a8bbe44a6f527127 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Mon, 14 Jun 2010 15:09:42 +0200 Subject: add prototype for cpu_idle_wait in system.h Signed-off-by: Mian Yousaf Kaukab Change-Id: I010d13db08b903c2199023152b989d2b17ca842f Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2245 Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/include/asm/system.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/include/asm/system.h b/arch/arm/include/asm/system.h index 832888d0c20..42d7d5c6594 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -110,6 +110,8 @@ extern void cpu_init(void); void arm_machine_restart(char mode, const char *cmd); extern void (*arm_pm_restart)(char str, const char *cmd); +void cpu_idle_wait(void); + #define UDBG_UNDEFINED (1 << 0) #define UDBG_SYSCALL (1 << 1) #define UDBG_BADABORT (1 << 2) -- cgit v1.2.3 From 3a94d8317218e0a8ad701f261ffed2b07791220d Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Tue, 22 Jun 2010 11:37:20 +0200 Subject: fix build errors with CPU_IDLE enabled Current PM for Montblanc enables both LOCAL_TIMERS and GENERIC_CLOCKEVENTS_BROADCAST. This causes build error with v2.6.34. This patch fixes this error. The PM scheme should be reviewed for v2.6.34. Signed-off-by: Mian Yousaf Kaukab Change-Id: Icdafd6f60cec858fc1311614be2e053bba168ef3 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2257 Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/kernel/smp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 8e3a000ee0a..2c8e49f2379 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -483,7 +483,7 @@ void show_local_irqs(struct seq_file *p, int prec) #endif #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST -static void smp_timer_broadcast(const struct cpumask *mask) +void smp_timer_broadcast(const struct cpumask *mask) { smp_cross_call(mask, IPI_TIMER); } -- cgit v1.2.3 From 3312319ff4ca23b7016ef20e77706bf067dc67ed Mon Sep 17 00:00:00 2001 From: Martin Persson Date: Tue, 21 Dec 2010 08:30:18 +0100 Subject: ARM: twd: Adjust localtimer frequency with cpufreq notifiers The clock to the ARM TWD local timer scales with the cpu frequency. To allow the cpu frequency to change while maintaining a constant TWD frequency, pick a lower target frequency for the TWD and use the prescaler to divide down to the closest lower frequency. This patch provides a new initialization function that takes a target TWD frequency and the ratio between the cpu clock and the TWD clock, specified as an integer divider >= 2 in the Cortex A9 MPCore TRM, and 2 in the ARM11 MPCore TRM. It also registers a cpufreq notifier that adjusts the prescaler when the cpu frequency changes. ST-Ericsson Linux next: Colin is driving this ST-Ericsson ID: 279802 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ieec17963da8d7d37d935782633406bc1a0484dc9 Signed-off-by: Colin Cross Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/11532 Tested-by: Martin PERSSON Reviewed-by: QATOOLS Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/include/asm/smp_twd.h | 11 +++++ arch/arm/kernel/smp_twd.c | 109 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/arch/arm/include/asm/smp_twd.h b/arch/arm/include/asm/smp_twd.h index fed9981fba0..859be20c38b 100644 --- a/arch/arm/include/asm/smp_twd.h +++ b/arch/arm/include/asm/smp_twd.h @@ -17,6 +17,7 @@ #define TWD_TIMER_CONTROL_ONESHOT (0 << 1) #define TWD_TIMER_CONTROL_PERIODIC (1 << 1) #define TWD_TIMER_CONTROL_IT_ENABLE (1 << 2) +#define TWD_TIMER_CONTROL_PRESCALE_MASK (0xFF << 8) struct clock_event_device; @@ -25,4 +26,14 @@ extern void __iomem *twd_base; int twd_timer_ack(void); void twd_timer_setup(struct clock_event_device *); +/* + * Use this setup function on systems that support cpufreq. + * periphclk_prescaler is the fixed divider value between the cpu + * clock and the PERIPHCLK clock that feeds the TWD. target_rate should be + * low enough that the prescaler can accurately reach the target rate from the + * lowest cpu frequency, but high enough to give a reasonable timer accuracy. + */ +void twd_timer_setup_scalable(struct clock_event_device *, + unsigned long target_rate, unsigned int periphclk_prescaler); + #endif diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 03f30152d1b..a727bc1de38 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -25,11 +26,17 @@ void __iomem *twd_base; static unsigned long twd_timer_rate; +static unsigned long twd_periphclk_prescaler; +static unsigned long twd_target_rate; static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) { unsigned long ctrl; + unsigned long prescale; + + prescale = __raw_readl(twd_base + TWD_TIMER_CONTROL) & + TWD_TIMER_CONTROL_PRESCALE_MASK; switch (mode) { case CLOCK_EVT_MODE_PERIODIC: @@ -48,6 +55,8 @@ static void twd_set_mode(enum clock_event_mode mode, ctrl = 0; } + ctrl |= prescale; + __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); } @@ -80,6 +89,52 @@ int twd_timer_ack(void) return 0; } +/* + * must be called with interrupts disabled and on the cpu that is being changed + */ +static void twd_update_cpu_frequency(unsigned long new_rate) +{ + u32 ctrl; + int prescaler; + + BUG_ON(twd_periphclk_prescaler == 0 || twd_target_rate == 0); + + twd_timer_rate = new_rate / twd_periphclk_prescaler; + + prescaler = DIV_ROUND_UP(twd_timer_rate, twd_target_rate); + prescaler = clamp(prescaler - 1, 0, 0xFF); + + ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL); + ctrl &= ~TWD_TIMER_CONTROL_PRESCALE_MASK; + ctrl |= prescaler << 8; + __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); +} + +static void twd_update_cpu_frequency_on_cpu(void *data) +{ + struct cpufreq_freqs *freq = data; + + twd_update_cpu_frequency(freq->new * 1000); +} + +static int twd_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_freqs *freq = data; + + if (event == CPUFREQ_RESUMECHANGE || + (event == CPUFREQ_PRECHANGE && freq->new > freq->old) || + (event == CPUFREQ_POSTCHANGE && freq->new < freq->old)) + smp_call_function_single(freq->cpu, + twd_update_cpu_frequency_on_cpu, freq, 1); + + return 0; +} + +static struct notifier_block twd_cpufreq_notifier_block = { + .notifier_call = twd_cpufreq_notifier, +}; + static void __cpuinit twd_calibrate_rate(void) { unsigned long count; @@ -122,10 +177,29 @@ static void __cpuinit twd_calibrate_rate(void) /* * Setup the local clock events for a CPU. */ -void __cpuinit twd_timer_setup(struct clock_event_device *clk) +static void __cpuinit __twd_timer_setup(struct clock_event_device *clk, + unsigned long target_rate, unsigned int periphclk_prescaler) { + unsigned long load; + unsigned long cpu_rate; + unsigned long twd_tick_rate; + twd_calibrate_rate(); + if (target_rate && periphclk_prescaler) { + cpu_rate = twd_timer_rate * periphclk_prescaler; + twd_target_rate = target_rate; + twd_periphclk_prescaler = periphclk_prescaler; + twd_update_cpu_frequency(cpu_rate); + twd_tick_rate = twd_target_rate; + } else { + twd_tick_rate = twd_timer_rate; + } + + load = twd_tick_rate / HZ; + + __raw_writel(load, twd_base + TWD_TIMER_LOAD); + clk->name = "local_timer"; #if defined(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) && \ defined(CONFIG_LOCAL_TIMERS) @@ -138,7 +212,7 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; clk->shift = 20; - clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift); + clk->mult = div_sc(twd_tick_rate, NSEC_PER_SEC, clk->shift); clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); clk->min_delta_ns = clockevent_delta2ns(0xf, clk); @@ -147,3 +221,34 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clockevents_register_device(clk); } + +void __cpuinit twd_timer_setup_scalable(struct clock_event_device *clk, + unsigned long target_rate, unsigned int periphclk_prescaler) +{ + __twd_timer_setup(clk, target_rate, periphclk_prescaler); +} + +void __cpuinit twd_timer_setup(struct clock_event_device *clk) +{ + __twd_timer_setup(clk, 0, 0); +} + +static int twd_timer_setup_cpufreq(void) +{ + if (twd_periphclk_prescaler) + cpufreq_register_notifier(&twd_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +arch_initcall(twd_timer_setup_cpufreq); + +#ifdef CONFIG_HOTPLUG_CPU +/* + * take a local timer down + */ +void twd_timer_stop(void) +{ + __raw_writel(0, twd_base + TWD_TIMER_CONTROL); +} +#endif -- cgit v1.2.3 From d7be54bc505dd726999ba69f09f8a8704794d3b2 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 24 Mar 2011 11:38:53 +0000 Subject: ARM: localtimers: Fix lost register settings Manually applied patch based on Jonas Aaberg's commit in aid of the 2.6.35 -> 2.6.38 GLK upgrade The localtimers loses its register settings when the ARM is powered off. This patch make sures that needed register settings are properly written at all times. Note: This patch collides with Russel Kings patch from around January 25 2011, acked by Nvidia. Russels patch does not work correctly. Since deepsleep is lacking in our mainline tree, I can't test and push these changes to mainline. ST-Ericsson Linux next: Not tested, ER322409 ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I2daa35fb4052e5ecd6634f0b5748020bbb5dc34c Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/14116 Reviewed-by: Rickard ANDERSSON Reviewed-by: Mattias WALLIN Signed-off-by: Lee Jones --- arch/arm/kernel/smp_twd.c | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index a727bc1de38..eec6c321fce 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -29,14 +29,17 @@ static unsigned long twd_timer_rate; static unsigned long twd_periphclk_prescaler; static unsigned long twd_target_rate; +static DEFINE_PER_CPU(unsigned long, twd_ctrl); +static DEFINE_PER_CPU(unsigned long, twd_load); + static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) { unsigned long ctrl; - unsigned long prescale; + int this_cpu = smp_processor_id(); - prescale = __raw_readl(twd_base + TWD_TIMER_CONTROL) & - TWD_TIMER_CONTROL_PRESCALE_MASK; + __raw_writel(per_cpu(twd_load, this_cpu), + twd_base + TWD_TIMER_LOAD); switch (mode) { case CLOCK_EVT_MODE_PERIODIC: @@ -55,20 +58,22 @@ static void twd_set_mode(enum clock_event_mode mode, ctrl = 0; } - ctrl |= prescale; + ctrl |= per_cpu(twd_ctrl, this_cpu) & TWD_TIMER_CONTROL_PRESCALE_MASK; __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); + per_cpu(twd_ctrl, this_cpu) = ctrl; } static int twd_set_next_event(unsigned long evt, struct clock_event_device *unused) { - unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL); + int this_cpu = smp_processor_id(); - ctrl |= TWD_TIMER_CONTROL_ENABLE; + per_cpu(twd_ctrl, this_cpu) |= TWD_TIMER_CONTROL_ENABLE; __raw_writel(evt, twd_base + TWD_TIMER_COUNTER); - __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); + __raw_writel(per_cpu(twd_ctrl, this_cpu), + twd_base + TWD_TIMER_CONTROL); return 0; } @@ -94,8 +99,8 @@ int twd_timer_ack(void) */ static void twd_update_cpu_frequency(unsigned long new_rate) { - u32 ctrl; int prescaler; + int this_cpu = smp_processor_id(); BUG_ON(twd_periphclk_prescaler == 0 || twd_target_rate == 0); @@ -104,10 +109,10 @@ static void twd_update_cpu_frequency(unsigned long new_rate) prescaler = DIV_ROUND_UP(twd_timer_rate, twd_target_rate); prescaler = clamp(prescaler - 1, 0, 0xFF); - ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL); - ctrl &= ~TWD_TIMER_CONTROL_PRESCALE_MASK; - ctrl |= prescaler << 8; - __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); + per_cpu(twd_ctrl, this_cpu) &= ~TWD_TIMER_CONTROL_PRESCALE_MASK; + per_cpu(twd_ctrl, this_cpu) |= prescaler << 8; + __raw_writel(per_cpu(twd_ctrl, this_cpu), + twd_base + TWD_TIMER_CONTROL); } static void twd_update_cpu_frequency_on_cpu(void *data) @@ -157,7 +162,11 @@ static void __cpuinit twd_calibrate_rate(void) waitjiffies += 5; /* enable, no interrupt or reload */ - __raw_writel(0x1, twd_base + TWD_TIMER_CONTROL); + __raw_writel(TWD_TIMER_CONTROL_ENABLE, + twd_base + TWD_TIMER_CONTROL); + + per_cpu(twd_ctrl, smp_processor_id()) = + TWD_TIMER_CONTROL_ENABLE; /* maximum value */ __raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER); @@ -180,7 +189,6 @@ static void __cpuinit twd_calibrate_rate(void) static void __cpuinit __twd_timer_setup(struct clock_event_device *clk, unsigned long target_rate, unsigned int periphclk_prescaler) { - unsigned long load; unsigned long cpu_rate; unsigned long twd_tick_rate; @@ -196,9 +204,7 @@ static void __cpuinit __twd_timer_setup(struct clock_event_device *clk, twd_tick_rate = twd_timer_rate; } - load = twd_tick_rate / HZ; - - __raw_writel(load, twd_base + TWD_TIMER_LOAD); + per_cpu(twd_load, smp_processor_id()) = twd_tick_rate / HZ; clk->name = "local_timer"; #if defined(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) && \ @@ -249,6 +255,11 @@ arch_initcall(twd_timer_setup_cpufreq); */ void twd_timer_stop(void) { - __raw_writel(0, twd_base + TWD_TIMER_CONTROL); + int this_cpu = smp_processor_id(); + per_cpu(twd_ctrl, this_cpu) &= ~(TWD_TIMER_CONTROL_ENABLE | + TWD_TIMER_CONTROL_IT_ENABLE); + __raw_writel(per_cpu(twd_ctrl, this_cpu), + twd_base + TWD_TIMER_CONTROL); + } #endif -- cgit v1.2.3 From e8808089009fc7c62ca30e35deded5b28e68c16f Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 24 Mar 2011 11:42:48 +0000 Subject: ARM: smp: don't recalculate loops_per_jiffy Manually applied patch based on Jonas Aaberg's commit in aid of the 2.6.35 -> 2.6.38 GLK upgrade loops_per_jiffy is calculated at boot, so no need to recalculate loops_per_jiffy each time a secondary cpu goes online. ST-Ericsson Linux next: Yes, 2011-01-12 ST-Ericsson ID: ER282335 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I359b3d2bcc03f1bdb0da641c6975478568778acd Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/11290 Reviewed-by: QATOOLS Reviewed-by: Mattias WALLIN Signed-off-by: Lee Jones --- arch/arm/kernel/smp.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 2c8e49f2379..1cfd07dd1b0 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -316,8 +316,6 @@ asmlinkage void __cpuinit secondary_start_kernel(void) */ percpu_timer_setup(); - calibrate_delay(); - smp_store_cpu_info(cpu); /* -- cgit v1.2.3 From e63bd16aa0cecd5fcc1829e63308c7ca44a83d33 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 16:53:46 +0100 Subject: misc/bh1780gli: add PM features misc/bh1780gli: change unsigned long to long Signed-off-by: Robert Marklund --- drivers/misc/bh1780gli.c | 69 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 82fe2d06782..c168fa22366 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -18,10 +18,12 @@ * this program. If not, see . */ #include +#include #include #include #include #include +#include #define BH1780_REG_CONTROL 0x80 #define BH1780_REG_PARTID 0x8A @@ -39,6 +41,7 @@ struct bh1780_data { struct i2c_client *client; + struct regulator *regulator; int power_state; /* lock for sysfs operations */ struct mutex lock; @@ -71,6 +74,9 @@ static ssize_t bh1780_show_lux(struct device *dev, struct bh1780_data *ddata = platform_get_drvdata(pdev); int lsb, msb; + if (ddata->power_state == BH1780_POFF) + return -EINVAL; + lsb = bh1780_read(ddata, BH1780_REG_DLOW, "DLOW"); if (lsb < 0) return lsb; @@ -88,13 +94,9 @@ static ssize_t bh1780_show_power_state(struct device *dev, { struct platform_device *pdev = to_platform_device(dev); struct bh1780_data *ddata = platform_get_drvdata(pdev); - int state; - - state = bh1780_read(ddata, BH1780_REG_CONTROL, "CONTROL"); - if (state < 0) - return state; - return sprintf(buf, "%d\n", state & BH1780_POWMASK); + /* we already maintain a sw state */ + return sprintf(buf, "%d\n", ddata->power_state); } static ssize_t bh1780_store_power_state(struct device *dev, @@ -103,7 +105,7 @@ static ssize_t bh1780_store_power_state(struct device *dev, { struct platform_device *pdev = to_platform_device(dev); struct bh1780_data *ddata = platform_get_drvdata(pdev); - unsigned long val; + long val; int error; error = strict_strtoul(buf, 0, &val); @@ -115,12 +117,22 @@ static ssize_t bh1780_store_power_state(struct device *dev, mutex_lock(&ddata->lock); + if (ddata->power_state == val) + return count; + + if (ddata->power_state == BH1780_POFF) + regulator_enable(ddata->regulator); + error = bh1780_write(ddata, BH1780_REG_CONTROL, val, "CONTROL"); if (error < 0) { mutex_unlock(&ddata->lock); + regulator_disable(ddata->regulator); return error; } + if (val == BH1780_POFF) + regulator_disable(ddata->regulator); + msleep(BH1780_PON_DELAY); ddata->power_state = val; mutex_unlock(&ddata->lock); @@ -130,7 +142,7 @@ static ssize_t bh1780_store_power_state(struct device *dev, static DEVICE_ATTR(lux, S_IRUGO, bh1780_show_lux, NULL); -static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(power_state, S_IWUGO | S_IRUGO, bh1780_show_power_state, bh1780_store_power_state); static struct attribute *bh1780_attributes[] = { @@ -152,21 +164,37 @@ static int __devinit bh1780_probe(struct i2c_client *client, if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) { ret = -EIO; - goto err_op_failed; + return ret; } ddata = kzalloc(sizeof(struct bh1780_data), GFP_KERNEL); if (ddata == NULL) { + dev_err(&client->dev, "failed to alloc ddata\n"); ret = -ENOMEM; - goto err_op_failed; + return ret; } ddata->client = client; i2c_set_clientdata(client, ddata); + dev_set_name(&client->dev, "bh1780"); + + ddata->regulator = regulator_get(&client->dev, "v-als"); + if (IS_ERR(ddata->regulator)) { + dev_err(&client->dev, "failed to get regulator\n"); + ret = PTR_ERR(ddata->regulator); + goto free_ddata; + } + + regulator_enable(ddata->regulator); ret = bh1780_read(ddata, BH1780_REG_PARTID, "PART ID"); - if (ret < 0) - goto err_op_failed; + if (ret < 0) { + dev_err(&client->dev, "failed to read part ID\n"); + goto put_regulator; + } + + regulator_disable(ddata->regulator); + ddata->power_state = BH1780_POFF; dev_info(&client->dev, "Ambient Light Sensor, Rev : %d\n", (ret & BH1780_REVMASK)); @@ -175,11 +203,14 @@ static int __devinit bh1780_probe(struct i2c_client *client, ret = sysfs_create_group(&client->dev.kobj, &bh1780_attr_group); if (ret) - goto err_op_failed; + goto put_regulator; return 0; -err_op_failed: +put_regulator: + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); +free_ddata: kfree(ddata); return ret; } @@ -215,6 +246,8 @@ static int bh1780_suspend(struct device *dev) if (ret < 0) return ret; + regulator_disable(ddata->regulator); + return 0; } @@ -229,11 +262,16 @@ static int bh1780_resume(struct device *dev) ret = bh1780_write(ddata, BH1780_REG_CONTROL, state, "CONTROL"); + regulator_enable(ddata->regulator); + + ret = bh1780_write(ddata, BH1780_REG_CONTROL, ddata->power_state, + "CONTROL"); if (ret < 0) return ret; return 0; } + static SIMPLE_DEV_PM_OPS(bh1780_pm, bh1780_suspend, bh1780_resume); #define BH1780_PMOPS (&bh1780_pm) #else @@ -251,8 +289,9 @@ static struct i2c_driver bh1780_driver = { .id_table = bh1780_id, .driver = { .name = "bh1780", +<<<<<<< HEAD .pm = BH1780_PMOPS, -}, + }, }; static int __init bh1780_init(void) -- cgit v1.2.3 From cf166717c610116ae17f862b14f6eccacf03662c Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 16:58:25 +0100 Subject: mpcore_wdt: Set correct watchdog rate mpcore_wdt: Use notifiers to modify WD rate mpcore_wdt: Stop watchdog of non-crashing cores mpcore_wdt: don't sleep in atomic context Signed-off-by: Robert Marklund --- drivers/watchdog/mpcore_wdt.c | 99 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/drivers/watchdog/mpcore_wdt.c b/drivers/watchdog/mpcore_wdt.c index 2b4af222b5f..818e453add9 100644 --- a/drivers/watchdog/mpcore_wdt.c +++ b/drivers/watchdog/mpcore_wdt.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include @@ -47,6 +49,8 @@ struct mpcore_wdt { static struct platform_device *mpcore_wdt_dev; static DEFINE_SPINLOCK(wdt_lock); +static DEFINE_PER_CPU(unsigned long, mpcore_wdt_rate); + #define TIMER_MARGIN 60 static int mpcore_margin = TIMER_MARGIN; module_param(mpcore_margin, int, 0); @@ -67,6 +71,8 @@ MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, " "set to 1 to ignore reboots, 0 to reboot (default=" __MODULE_STRING(ONLY_TESTING) ")"); +#define MPCORE_WDT_PERIPHCLK_PRESCALER 2 + /* * This is the interrupt handler. Note that we only use this * in testing mode, so don't actually do a reboot here. @@ -99,9 +105,8 @@ static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) spin_lock(&wdt_lock); /* Assume prescale is set to 256 */ - count = __raw_readl(wdt->base + TWD_WDOG_COUNTER); - count = (0xFFFFFFFFU - count) * (HZ / 5); - count = (count / 256) * mpcore_margin; + count = per_cpu(mpcore_wdt_rate, smp_processor_id()) / 256; + count = count*mpcore_margin; /* Reload the counter */ writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); @@ -109,6 +114,56 @@ static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) spin_unlock(&wdt_lock); } +static void mpcore_wdt_set_rate(unsigned long new_rate) +{ + unsigned long count; + unsigned long long rate_tmp; + unsigned long old_rate; + + spin_lock(&wdt_lock); + old_rate = per_cpu(mpcore_wdt_rate, smp_processor_id()); + per_cpu(mpcore_wdt_rate, smp_processor_id()) = new_rate; + + if (mpcore_wdt_dev) { + struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); + count = readl(wdt->base + TWD_WDOG_COUNTER); + /* The goal: count = count * (new_rate/old_rate); */ + rate_tmp = (unsigned long long)count * new_rate; + do_div(rate_tmp, old_rate); + count = rate_tmp; + writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); + wdt->perturb = wdt->perturb ? 0 : 1; + } + spin_unlock(&wdt_lock); +} + +static void mpcore_wdt_update_cpu_frequency_on_cpu(void *data) +{ + struct cpufreq_freqs *freq = data; + mpcore_wdt_set_rate((freq->new * 1000) / + MPCORE_WDT_PERIPHCLK_PRESCALER); +} + +static int mpcore_wdt_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_freqs *freq = data; + + if (event == CPUFREQ_RESUMECHANGE || + (event == CPUFREQ_PRECHANGE && freq->new > freq->old) || + (event == CPUFREQ_POSTCHANGE && freq->new < freq->old)) + smp_call_function_single(freq->cpu, + mpcore_wdt_update_cpu_frequency_on_cpu, + freq, 1); + + return 0; +} + +static struct notifier_block mpcore_wdt_cpufreq_notifier_block = { + .notifier_call = mpcore_wdt_cpufreq_notifier, +}; + + static void mpcore_wdt_stop(struct mpcore_wdt *wdt) { spin_lock(&wdt_lock); @@ -143,6 +198,20 @@ static int mpcore_wdt_set_heartbeat(int t) return 0; } +static int mpcore_wdt_stop_notifier(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); + printk(KERN_INFO "Stopping watchdog on non-crashing core %u\n", + smp_processor_id()); + mpcore_wdt_stop(wdt); + return NOTIFY_STOP; +} + +static struct notifier_block mpcore_wdt_stop_block = { + .notifier_call = mpcore_wdt_stop_notifier, +}; + /* * /dev/watchdog handling */ @@ -150,7 +219,7 @@ static int mpcore_wdt_open(struct inode *inode, struct file *file) { struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); - if (test_and_set_bit(0, &wdt->timer_alive)) + if (cpumask_test_and_set_cpu(smp_processor_id(), &wdt->timer_alive)) return -EBUSY; if (nowayout) @@ -158,6 +227,9 @@ static int mpcore_wdt_open(struct inode *inode, struct file *file) file->private_data = wdt; + atomic_notifier_chain_register(&crash_percpu_notifier_list, + &mpcore_wdt_stop_block); + /* * Activate timer */ @@ -181,7 +253,7 @@ static int mpcore_wdt_release(struct inode *inode, struct file *file) "unexpected close, not stopping watchdog!\n"); mpcore_wdt_keepalive(wdt); } - clear_bit(0, &wdt->timer_alive); + cpumask_clear_cpu(smp_processor_id(), &wdt->timer_alive); wdt->expect_close = 0; return 0; } @@ -425,16 +497,31 @@ static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. " static int __init mpcore_wdt_init(void) { + int i; + /* * Check that the margin value is within it's range; * if not reset to the default */ if (mpcore_wdt_set_heartbeat(mpcore_margin)) { mpcore_wdt_set_heartbeat(TIMER_MARGIN); - printk(KERN_INFO "mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n", + printk(KERN_INFO "mpcore_wdt: mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n", TIMER_MARGIN); } + cpufreq_register_notifier(&mpcore_wdt_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + for_each_online_cpu(i) + per_cpu(mpcore_wdt_rate, i) = + (cpufreq_get(i) * 1000) / MPCORE_WDT_PERIPHCLK_PRESCALER; + + for_each_online_cpu(i) + printk(KERN_INFO + "mpcore_wdt: rate for core %d is %lu.%02luMHz.\n", i, + per_cpu(mpcore_wdt_rate, i) / 1000000, + (per_cpu(mpcore_wdt_rate, i) / 10000) % 100); + printk(banner, mpcore_noboot, mpcore_margin, nowayout); return platform_driver_register(&mpcore_wdt_driver); -- cgit v1.2.3 From 5679e317885477923868881cdf4aa980af1308f3 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 16:59:40 +0100 Subject: cpufreq: update delay after dbs_cpu_check cpufreq: Set default sampling_down_factor to 10 Signed-off-by: Robert Marklund --- drivers/cpufreq/cpufreq_ondemand.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c index 891360edecd..a5bbbf60ae8 100644 --- a/drivers/cpufreq/cpufreq_ondemand.c +++ b/drivers/cpufreq/cpufreq_ondemand.c @@ -30,7 +30,7 @@ #define DEF_FREQUENCY_DOWN_DIFFERENTIAL (10) #define DEF_FREQUENCY_UP_THRESHOLD (80) -#define DEF_SAMPLING_DOWN_FACTOR (1) +#define DEF_SAMPLING_DOWN_FACTOR (10) #define MAX_SAMPLING_DOWN_FACTOR (100000) #define MICRO_FREQUENCY_DOWN_DIFFERENTIAL (3) #define MICRO_FREQUENCY_UP_THRESHOLD (95) -- cgit v1.2.3 From bd0fbf04b160069503c8df7b33fba3bededb7c48 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 17:00:41 +0100 Subject: smsc911x: use shifted register access At least one of the boards using this chip needs a shift in register access. This adds a "shift" field in the platform data to have the chip working in all cases without special ifdef. The approach is similaro to the one used in serial_8250 and other places. Performance-wise no difference is reported. Signed-off-by: Alessandro Rubini Signed-off-by: Robert Marklund --- drivers/net/smsc911x.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c index c6d47d10590..d9a16744e2f 100644 --- a/drivers/net/smsc911x.c +++ b/drivers/net/smsc911x.c @@ -139,12 +139,15 @@ struct smsc911x_data { static inline u32 __smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) { + void __iomem *addr = pdata->ioaddr; + int shift = pdata->config.shift; + if (pdata->config.flags & SMSC911X_USE_32BIT) - return readl(pdata->ioaddr + reg); + return readl(addr + (reg << shift)); if (pdata->config.flags & SMSC911X_USE_16BIT) - return ((readw(pdata->ioaddr + reg) & 0xFFFF) | - ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16)); + return ((readw(addr + (reg << shift)) & 0xFFFF) | + ((readw(addr + ((reg + 2) << shift)) & 0xFFFF) << 16)); BUG(); return 0; @@ -181,14 +184,17 @@ static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) static inline void __smsc911x_reg_write(struct smsc911x_data *pdata, u32 reg, u32 val) { + void __iomem *addr = pdata->ioaddr; + int shift = pdata->config.shift; + if (pdata->config.flags & SMSC911X_USE_32BIT) { - writel(val, pdata->ioaddr + reg); + writel(val, addr + (reg << shift)); return; } if (pdata->config.flags & SMSC911X_USE_16BIT) { - writew(val & 0xFFFF, pdata->ioaddr + reg); - writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2); + writew(val & 0xFFFF, addr + (reg << shift)); + writew((val >> 16) & 0xFFFF, addr + ((reg + 2) << shift)); return; } @@ -241,7 +247,8 @@ smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf, } if (pdata->config.flags & SMSC911X_USE_32BIT) { - writesl(pdata->ioaddr + TX_DATA_FIFO, buf, wordcount); + writesl(pdata->ioaddr + (TX_DATA_FIFO << pdata->config.shift), + buf, wordcount); goto out; } @@ -307,7 +314,8 @@ smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf, } if (pdata->config.flags & SMSC911X_USE_32BIT) { - readsl(pdata->ioaddr + RX_DATA_FIFO, buf, wordcount); + readsl(pdata->ioaddr + (RX_DATA_FIFO << pdata->config.shift), + buf, wordcount); goto out; } @@ -2148,7 +2156,8 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) dev->irq = irq_res->start; irq_flags = irq_res->flags & IRQF_TRIGGER_MASK; - pdata->ioaddr = ioremap_nocache(res->start, res_size); + pdata->ioaddr = ioremap_nocache(res->start, + (res_size << config->shift)); /* copy config parameters across to pdata */ memcpy(&pdata->config, config, sizeof(pdata->config)); -- cgit v1.2.3 From 00744e332c1d7cc0cb5bd71fc11b5c3c028e3601 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Tue, 22 Mar 2011 10:14:24 +0100 Subject: amba-pl022: Initialize burstsize from FIFO trigger level amba-pl022: support runtime PM Signed-off-by: Robert Marklund --- drivers/spi/amba-pl022.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/spi/amba-pl022.c b/drivers/spi/amba-pl022.c index d18ce9e946d..bb2cb43cbbf 100644 --- a/drivers/spi/amba-pl022.c +++ b/drivers/spi/amba-pl022.c @@ -42,6 +42,7 @@ #include #include #include +#include /* * This macro is used to define some register default values. @@ -391,6 +392,8 @@ struct pl022 { struct sg_table sgt_tx; char *dummypage; #endif + enum ssp_rx_level_trig rx_lev_trig; + enum ssp_tx_level_trig tx_lev_trig; }; /** @@ -517,6 +520,7 @@ static void giveback(struct pl022 *pl022) clk_disable(pl022->clk); amba_pclk_disable(pl022->adev); amba_vcore_disable(pl022->adev); + pm_runtime_put(&pl022->adev->dev); } /** @@ -924,6 +928,12 @@ static int configure_dma(struct pl022 *pl022) struct dma_async_tx_descriptor *rxdesc; struct dma_async_tx_descriptor *txdesc; + /* DMA burstsize should be same as the FIFO trigger level */ + rx_conf.src_maxburst = pl022->rx_lev_trig ? 1 << + (pl022->rx_lev_trig + 1) : pl022->rx_lev_trig; + tx_conf.dst_maxburst = pl022->tx_lev_trig ? 1 << + (pl022->tx_lev_trig + 1) : pl022->tx_lev_trig; + /* Check that the channels are available */ if (!rxchan || !txchan) return -ENODEV; @@ -1498,6 +1508,7 @@ static void pump_messages(struct work_struct *work) */ amba_vcore_enable(pl022->adev); amba_pclk_enable(pl022->adev); + pm_runtime_get_sync(&pl022->adev->dev); clk_enable(pl022->clk); restore_state(pl022); flush(pl022); @@ -1874,6 +1885,9 @@ static int pl022_setup(struct spi_device *spi) goto err_config_params; } + pl022->rx_lev_trig = chip_info->rx_lev_trig; + pl022->tx_lev_trig = chip_info->tx_lev_trig; + /* Now set controller state based on controller data */ chip->xfer_type = chip_info->com_mode; if (!chip_info->cs_control) { @@ -2095,6 +2109,9 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) printk(KERN_INFO "pl022: mapped registers from 0x%08x to %p\n", adev->res.start, pl022->virtbase); + pm_runtime_enable(dev); + pm_runtime_resume(dev); + pl022->clk = clk_get(&adev->dev, NULL); if (IS_ERR(pl022->clk)) { status = PTR_ERR(pl022->clk); @@ -2188,6 +2205,7 @@ pl022_remove(struct amba_device *adev) free_irq(adev->irq[0], pl022); clk_disable(pl022->clk); clk_put(pl022->clk); + pm_runtime_disable(&adev->dev); iounmap(pl022->virtbase); amba_release_regions(adev); tasklet_disable(&pl022->pump_transfers); @@ -2275,6 +2293,14 @@ static struct vendor_data vendor_db5500_pl023 = { .loopback = true, }; +static struct vendor_data vendor_db5500_pl023 = { + .fifodepth = 32, + .max_bpw = 32, + .unidir = false, + .extended_cr = true, + .pl023 = true, +}; + static struct amba_id pl022_ids[] = { { /* -- cgit v1.2.3 From 206eab067df048bfe4c03879375d5e3e63a0e3c5 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 10:23:23 +0100 Subject: hwmem: Add hardware memory driver to kernel This driver provides a way to allocate contiguous system memory which can be used by hardware. Signed-off-by: Robert Marklund --- drivers/misc/Kconfig | 8 + drivers/misc/Makefile | 1 + drivers/misc/hwmem/Makefile | 3 + drivers/misc/hwmem/cache_handler.c | 507 ++++++++++++++++++++++ drivers/misc/hwmem/cache_handler.h | 61 +++ drivers/misc/hwmem/hwmem-ioctl.c | 477 +++++++++++++++++++++ drivers/misc/hwmem/hwmem-main.c | 833 +++++++++++++++++++++++++++++++++++++ include/linux/hwmem.h | 519 +++++++++++++++++++++++ 8 files changed, 2409 insertions(+) create mode 100644 drivers/misc/hwmem/Makefile create mode 100644 drivers/misc/hwmem/cache_handler.c create mode 100644 drivers/misc/hwmem/cache_handler.h create mode 100644 drivers/misc/hwmem/hwmem-ioctl.c create mode 100644 drivers/misc/hwmem/hwmem-main.c create mode 100644 include/linux/hwmem.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4cc1da58e80..8719c535781 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -490,6 +490,14 @@ config PCH_PHUB To compile this driver as a module, choose M here: the module will be called pch_phub. +config HWMEM + bool "Hardware memory driver" + default n + help + This driver provides a way to allocate contiguous system memory which + can be used by hardware. It also enables accessing hwmem allocated + memory buffers through a secure id which can be shared across processes. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 1aa09309fc4..9ad03e8754e 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -47,3 +47,4 @@ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ obj-$(CONFIG_STE_AUDIO_IO_DEV) += audio_io_dev/ +obj-$(CONFIG_HWMEM) += hwmem/ diff --git a/drivers/misc/hwmem/Makefile b/drivers/misc/hwmem/Makefile new file mode 100644 index 00000000000..18da2ad7817 --- /dev/null +++ b/drivers/misc/hwmem/Makefile @@ -0,0 +1,3 @@ +hwmem-objs := hwmem-main.o hwmem-ioctl.o cache_handler.o + +obj-$(CONFIG_HWMEM) += hwmem.o diff --git a/drivers/misc/hwmem/cache_handler.c b/drivers/misc/hwmem/cache_handler.c new file mode 100644 index 00000000000..b313da36aa4 --- /dev/null +++ b/drivers/misc/hwmem/cache_handler.c @@ -0,0 +1,507 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Cache handler + * + * Author: Johan Mossberg + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include + +#include + +#include + +#include "cache_handler.h" + +#define U32_MAX (~(u32)0) + +enum hwmem_alloc_flags cachi_get_cache_settings( + enum hwmem_alloc_flags requested_cache_settings); +void cachi_set_pgprot_cache_options(enum hwmem_alloc_flags cache_settings, + pgprot_t *pgprot); + +static void sync_buf_pre_cpu(struct cach_buf *buf, enum hwmem_access access, + struct hwmem_region *region); +static void sync_buf_post_cpu(struct cach_buf *buf, + enum hwmem_access next_access, struct hwmem_region *next_region); + +static void invalidate_cpu_cache(struct cach_buf *buf, + struct cach_range *range_2b_used); +static void clean_cpu_cache(struct cach_buf *buf, + struct cach_range *range_2b_used); +static void flush_cpu_cache(struct cach_buf *buf, + struct cach_range *range_2b_used); + +static void null_range(struct cach_range *range); +static void expand_range(struct cach_range *range, + struct cach_range *range_2_add); +/* + * Expands range to one of enclosing_range's two edges. The function will + * choose which of enclosing_range's edges to expand range to in such a + * way that the size of range is minimized. range must be located inside + * enclosing_range. + */ +static void expand_range_2_edge(struct cach_range *range, + struct cach_range *enclosing_range); +static void shrink_range(struct cach_range *range, + struct cach_range *range_2_remove); +static bool is_non_empty_range(struct cach_range *range); +static void intersect_range(struct cach_range *range_1, + struct cach_range *range_2, struct cach_range *intersection); +/* Align_up restrictions apply here to */ +static void align_range_up(struct cach_range *range, u32 alignment); +static u32 range_length(struct cach_range *range); +static void region_2_range(struct hwmem_region *region, u32 buffer_size, + struct cach_range *range); + +static void *offset_2_vaddr(struct cach_buf *buf, u32 offset); +static u32 offset_2_paddr(struct cach_buf *buf, u32 offset); + +/* Saturates, might return unaligned values when that happens */ +static u32 align_up(u32 value, u32 alignment); +static u32 align_down(u32 value, u32 alignment); + +static bool is_wb(enum hwmem_alloc_flags cache_settings); +static bool is_inner_only(enum hwmem_alloc_flags cache_settings); + +/* + * Exported functions + */ + +void cach_init_buf(struct cach_buf *buf, enum hwmem_alloc_flags cache_settings, + u32 size) +{ + buf->vstart = NULL; + buf->pstart = 0; + buf->size = size; + + buf->cache_settings = cachi_get_cache_settings(cache_settings); +} + +void cach_set_buf_addrs(struct cach_buf *buf, void* vaddr, u32 paddr) +{ + bool tmp; + + buf->vstart = vaddr; + buf->pstart = paddr; + + if (buf->cache_settings & HWMEM_ALLOC_CACHED) { + /* + * Keep whatever is in the cache. This way we avoid an + * unnecessary synch if CPU is the first user. + */ + buf->range_in_cpu_cache.start = 0; + buf->range_in_cpu_cache.end = buf->size; + align_range_up(&buf->range_in_cpu_cache, + get_dcache_granularity()); + buf->range_dirty_in_cpu_cache.start = 0; + buf->range_dirty_in_cpu_cache.end = buf->size; + align_range_up(&buf->range_dirty_in_cpu_cache, + get_dcache_granularity()); + } else { + flush_cpu_dcache(buf->vstart, buf->pstart, buf->size, false, + &tmp); + drain_cpu_write_buf(); + + null_range(&buf->range_in_cpu_cache); + null_range(&buf->range_dirty_in_cpu_cache); + } + null_range(&buf->range_invalid_in_cpu_cache); +} + +void cach_set_pgprot_cache_options(struct cach_buf *buf, pgprot_t *pgprot) +{ + cachi_set_pgprot_cache_options(buf->cache_settings, pgprot); +} + +void cach_set_domain(struct cach_buf *buf, enum hwmem_access access, + enum hwmem_domain domain, struct hwmem_region *region) +{ + struct hwmem_region *__region; + struct hwmem_region full_region; + + if (region != NULL) + __region = region; + else { + full_region.offset = 0; + full_region.count = 1; + full_region.start = 0; + full_region.end = buf->size; + full_region.size = buf->size; + + __region = &full_region; + } + + switch (domain) { + case HWMEM_DOMAIN_SYNC: + sync_buf_post_cpu(buf, access, __region); + + break; + + case HWMEM_DOMAIN_CPU: + sync_buf_pre_cpu(buf, access, __region); + + break; + } +} + +/* + * Local functions + */ + +enum hwmem_alloc_flags __attribute__((weak)) cachi_get_cache_settings( + enum hwmem_alloc_flags requested_cache_settings) +{ + enum hwmem_alloc_flags cache_settings = + requested_cache_settings & ~HWMEM_ALLOC_CACHE_HINT_MASK; + + if ((cache_settings & HWMEM_ALLOC_CACHED) == HWMEM_ALLOC_CACHED) { + /* + * If the alloc is cached we'll use the default setting. We + * don't know what this setting is so we have to assume the + * worst case, ie write back inner and outer. + */ + cache_settings |= HWMEM_ALLOC_CACHE_HINT_WB; + } + + return cache_settings; +} + +void __attribute__((weak)) cachi_set_pgprot_cache_options( + enum hwmem_alloc_flags cache_settings, pgprot_t *pgprot) +{ + if ((cache_settings & HWMEM_ALLOC_CACHED) == HWMEM_ALLOC_CACHED) + *pgprot = *pgprot; /* To silence compiler and checkpatch */ + else if (cache_settings & HWMEM_ALLOC_BUFFERED) + *pgprot = pgprot_writecombine(*pgprot); + else + *pgprot = pgprot_noncached(*pgprot); +} + +bool __attribute__((weak)) speculative_data_prefetch(void) +{ + /* We don't know so we go with the safe alternative */ + return true; +} + +static void sync_buf_pre_cpu(struct cach_buf *buf, enum hwmem_access access, + struct hwmem_region *region) +{ + bool write = access & HWMEM_ACCESS_WRITE; + bool read = access & HWMEM_ACCESS_READ; + + if (!write && !read) + return; + + if ((buf->cache_settings & HWMEM_ALLOC_CACHED) == HWMEM_ALLOC_CACHED) { + struct cach_range region_range; + + region_2_range(region, buf->size, ®ion_range); + + if (read || (write && is_wb(buf->cache_settings))) + /* Perform defered invalidates */ + invalidate_cpu_cache(buf, ®ion_range); + if (read) + expand_range(&buf->range_in_cpu_cache, ®ion_range); + if (write && is_wb(buf->cache_settings)) { + expand_range(&buf->range_in_cpu_cache, ®ion_range); + expand_range(&buf->range_dirty_in_cpu_cache, + ®ion_range); + } + } + if (buf->cache_settings & HWMEM_ALLOC_BUFFERED) { + if (write) + buf->in_cpu_write_buf = true; + } +} + +static void sync_buf_post_cpu(struct cach_buf *buf, + enum hwmem_access next_access, struct hwmem_region *next_region) +{ + bool write = next_access & HWMEM_ACCESS_WRITE; + bool read = next_access & HWMEM_ACCESS_READ; + struct cach_range region_range; + + if (!write && !read) + return; + + region_2_range(next_region, buf->size, ®ion_range); + + if (write) { + if (speculative_data_prefetch()) { + /* Defer invalidate */ + struct cach_range intersection; + + intersect_range(&buf->range_in_cpu_cache, + ®ion_range, &intersection); + + expand_range(&buf->range_invalid_in_cpu_cache, + &intersection); + + clean_cpu_cache(buf, ®ion_range); + } else + flush_cpu_cache(buf, ®ion_range); + } + if (read) + clean_cpu_cache(buf, ®ion_range); + + if (buf->in_cpu_write_buf) { + drain_cpu_write_buf(); + + buf->in_cpu_write_buf = false; + } +} + +static void invalidate_cpu_cache(struct cach_buf *buf, struct cach_range *range) +{ + struct cach_range intersection; + + intersect_range(&buf->range_invalid_in_cpu_cache, range, + &intersection); + if (is_non_empty_range(&intersection)) { + bool flushed_everything; + + expand_range_2_edge(&intersection, + &buf->range_invalid_in_cpu_cache); + + /* + * Cache handler never uses invalidate to discard data in the + * cache so we can use flush instead which is considerably + * faster for large buffers. + */ + flush_cpu_dcache( + offset_2_vaddr(buf, intersection.start), + offset_2_paddr(buf, intersection.start), + range_length(&intersection), + is_inner_only(buf->cache_settings), + &flushed_everything); + + if (flushed_everything) { + null_range(&buf->range_invalid_in_cpu_cache); + null_range(&buf->range_dirty_in_cpu_cache); + } else + /* + * No need to shrink range_in_cpu_cache as invalidate + * is only used when we can't keep track of what's in + * the CPU cache. + */ + shrink_range(&buf->range_invalid_in_cpu_cache, + &intersection); + } +} + +static void clean_cpu_cache(struct cach_buf *buf, struct cach_range *range) +{ + struct cach_range intersection; + + intersect_range(&buf->range_dirty_in_cpu_cache, range, &intersection); + if (is_non_empty_range(&intersection)) { + bool cleaned_everything; + + expand_range_2_edge(&intersection, + &buf->range_dirty_in_cpu_cache); + + clean_cpu_dcache( + offset_2_vaddr(buf, intersection.start), + offset_2_paddr(buf, intersection.start), + range_length(&intersection), + is_inner_only(buf->cache_settings), + &cleaned_everything); + + if (cleaned_everything) + null_range(&buf->range_dirty_in_cpu_cache); + else + shrink_range(&buf->range_dirty_in_cpu_cache, + &intersection); + } +} + +static void flush_cpu_cache(struct cach_buf *buf, struct cach_range *range) +{ + struct cach_range intersection; + + intersect_range(&buf->range_in_cpu_cache, range, &intersection); + if (is_non_empty_range(&intersection)) { + bool flushed_everything; + + expand_range_2_edge(&intersection, &buf->range_in_cpu_cache); + + flush_cpu_dcache( + offset_2_vaddr(buf, intersection.start), + offset_2_paddr(buf, intersection.start), + range_length(&intersection), + is_inner_only(buf->cache_settings), + &flushed_everything); + + if (flushed_everything) { + if (!speculative_data_prefetch()) + null_range(&buf->range_in_cpu_cache); + null_range(&buf->range_dirty_in_cpu_cache); + null_range(&buf->range_invalid_in_cpu_cache); + } else { + if (!speculative_data_prefetch()) + shrink_range(&buf->range_in_cpu_cache, + &intersection); + shrink_range(&buf->range_dirty_in_cpu_cache, + &intersection); + shrink_range(&buf->range_invalid_in_cpu_cache, + &intersection); + } + } +} + +static void null_range(struct cach_range *range) +{ + range->start = U32_MAX; + range->end = 0; +} + +static void expand_range(struct cach_range *range, + struct cach_range *range_2_add) +{ + range->start = min(range->start, range_2_add->start); + range->end = max(range->end, range_2_add->end); +} + +/* + * Expands range to one of enclosing_range's two edges. The function will + * choose which of enclosing_range's edges to expand range to in such a + * way that the size of range is minimized. range must be located inside + * enclosing_range. + */ +static void expand_range_2_edge(struct cach_range *range, + struct cach_range *enclosing_range) +{ + u32 space_on_low_side = range->start - enclosing_range->start; + u32 space_on_high_side = enclosing_range->end - range->end; + + if (space_on_low_side < space_on_high_side) + range->start = enclosing_range->start; + else + range->end = enclosing_range->end; +} + +static void shrink_range(struct cach_range *range, + struct cach_range *range_2_remove) +{ + if (range_2_remove->start > range->start) + range->end = min(range->end, range_2_remove->start); + else + range->start = max(range->start, range_2_remove->end); + + if (range->start >= range->end) + null_range(range); +} + +static bool is_non_empty_range(struct cach_range *range) +{ + return range->end > range->start; +} + +static void intersect_range(struct cach_range *range_1, + struct cach_range *range_2, struct cach_range *intersection) +{ + intersection->start = max(range_1->start, range_2->start); + intersection->end = min(range_1->end, range_2->end); + + if (intersection->start >= intersection->end) + null_range(intersection); +} + +/* Align_up restrictions apply here to */ +static void align_range_up(struct cach_range *range, u32 alignment) +{ + if (!is_non_empty_range(range)) + return; + + range->start = align_down(range->start, alignment); + range->end = align_up(range->end, alignment); +} + +static u32 range_length(struct cach_range *range) +{ + if (is_non_empty_range(range)) + return range->end - range->start; + else + return 0; +} + +static void region_2_range(struct hwmem_region *region, u32 buffer_size, + struct cach_range *range) +{ + /* + * We don't care about invalid regions, instead we limit the region's + * range to the buffer's range. This should work good enough, worst + * case we synch the entire buffer when we get an invalid region which + * is acceptable. + */ + range->start = region->offset + region->start; + range->end = min(region->offset + (region->count * region->size) - + (region->size - region->end), buffer_size); + if (range->start >= range->end) { + null_range(range); + return; + } + + align_range_up(range, get_dcache_granularity()); +} + +static void *offset_2_vaddr(struct cach_buf *buf, u32 offset) +{ + return (void *)((u32)buf->vstart + offset); +} + +static u32 offset_2_paddr(struct cach_buf *buf, u32 offset) +{ + return buf->pstart + offset; +} + +/* Saturates, might return unaligned values when that happens */ +static u32 align_up(u32 value, u32 alignment) +{ + u32 remainder = value % alignment; + u32 value_2_add; + + if (remainder == 0) + return value; + + value_2_add = alignment - remainder; + + if (value_2_add > U32_MAX - value) /* Will overflow */ + return U32_MAX; + + return value + value_2_add; +} + +static u32 align_down(u32 value, u32 alignment) +{ + u32 remainder = value % alignment; + if (remainder == 0) + return value; + + return value - remainder; +} + +static bool is_wb(enum hwmem_alloc_flags cache_settings) +{ + u32 cache_hints = cache_settings & HWMEM_ALLOC_CACHE_HINT_MASK; + if (cache_hints == HWMEM_ALLOC_CACHE_HINT_WB || + cache_hints == HWMEM_ALLOC_CACHE_HINT_WB_INNER) + return true; + else + return false; +} + +static bool is_inner_only(enum hwmem_alloc_flags cache_settings) +{ + u32 cache_hints = cache_settings & HWMEM_ALLOC_CACHE_HINT_MASK; + if (cache_hints == HWMEM_ALLOC_CACHE_HINT_WT_INNER || + cache_hints == HWMEM_ALLOC_CACHE_HINT_WB_INNER) + return true; + else + return false; +} diff --git a/drivers/misc/hwmem/cache_handler.h b/drivers/misc/hwmem/cache_handler.h new file mode 100644 index 00000000000..792105196fa --- /dev/null +++ b/drivers/misc/hwmem/cache_handler.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Cache handler + * + * Author: Johan Mossberg + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +/* + * Cache handler can not handle simultaneous execution! The caller has to + * ensure such a situation does not occur. + */ + +#ifndef _CACHE_HANDLER_H_ +#define _CACHE_HANDLER_H_ + +#include +#include + +/* + * To not have to double all datatypes we've used hwmem datatypes. If someone + * want's to use cache handler but not hwmem then we'll have to define our own + * datatypes. + */ + +struct cach_range { + u32 start; /* Inclusive */ + u32 end; /* Exclusive */ +}; + +/* + * Internal, do not touch! + */ +struct cach_buf { + void *vstart; + u32 pstart; + u32 size; + + /* Remaining hints are active */ + enum hwmem_alloc_flags cache_settings; + + bool in_cpu_write_buf; + struct cach_range range_in_cpu_cache; + struct cach_range range_dirty_in_cpu_cache; + struct cach_range range_invalid_in_cpu_cache; +}; + +void cach_init_buf(struct cach_buf *buf, + enum hwmem_alloc_flags cache_settings, u32 size); + +void cach_set_buf_addrs(struct cach_buf *buf, void* vaddr, u32 paddr); + +void cach_set_pgprot_cache_options(struct cach_buf *buf, pgprot_t *pgprot); + +void cach_set_domain(struct cach_buf *buf, enum hwmem_access access, + enum hwmem_domain domain, struct hwmem_region *region); + +#endif /* _CACHE_HANDLER_H_ */ diff --git a/drivers/misc/hwmem/hwmem-ioctl.c b/drivers/misc/hwmem/hwmem-ioctl.c new file mode 100644 index 00000000000..8759c395147 --- /dev/null +++ b/drivers/misc/hwmem/hwmem-ioctl.c @@ -0,0 +1,477 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * Hardware memory driver, hwmem + * + * Author: Marcus Lorentzon + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * TODO: + * Count pin unpin at this level to ensure applications can't interfer + * with each other. + */ + +static int hwmem_open(struct inode *inode, struct file *file); +static int hwmem_ioctl_mmap(struct file *file, struct vm_area_struct *vma); +static int hwmem_release_fop(struct inode *inode, struct file *file); +static long hwmem_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +static unsigned long hwmem_get_unmapped_area(struct file *file, + unsigned long addr, unsigned long len, unsigned long pgoff, + unsigned long flags); + +static const struct file_operations hwmem_fops = { + .open = hwmem_open, + .mmap = hwmem_ioctl_mmap, + .unlocked_ioctl = hwmem_ioctl, + .release = hwmem_release_fop, + .get_unmapped_area = hwmem_get_unmapped_area, +}; + +static struct miscdevice hwmem_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "hwmem", + .fops = &hwmem_fops, +}; + +struct hwmem_file { + struct mutex lock; + struct idr idr; /* id -> struct hwmem_alloc*, ref counted */ + struct hwmem_alloc *fd_alloc; /* Ref counted */ +}; + +static int create_id(struct hwmem_file *hwfile, struct hwmem_alloc *alloc) +{ + int id, ret; + + while (true) { + if (idr_pre_get(&hwfile->idr, GFP_KERNEL) == 0) + return -ENOMEM; + + ret = idr_get_new_above(&hwfile->idr, alloc, 1, &id); + if (ret == 0) + break; + else if (ret != -EAGAIN) + return -ENOMEM; + } + + /* + * IDR always returns the lowest free id so the only way we can fail + * here is if hwfile has 2^19 - 1 (524287) allocations. + */ + if (id >= 1 << (31 - PAGE_SHIFT)) { + dev_err(hwmem_device.this_device, "Out of IDs!\n"); + idr_remove(&hwfile->idr, id); + return -ENOMSG; + } + + return id << PAGE_SHIFT; +} + +static void remove_id(struct hwmem_file *hwfile, int id) +{ + idr_remove(&hwfile->idr, id >> PAGE_SHIFT); +} + +static struct hwmem_alloc *resolve_id(struct hwmem_file *hwfile, int id) +{ + struct hwmem_alloc *alloc; + + alloc = id ? idr_find(&hwfile->idr, id >> PAGE_SHIFT) : + hwfile->fd_alloc; + if (alloc == NULL) + alloc = ERR_PTR(-EINVAL); + + return alloc; +} + +static int alloc(struct hwmem_file *hwfile, struct hwmem_alloc_request *req) +{ + int ret = 0; + struct hwmem_alloc *alloc; + + alloc = hwmem_alloc(req->size, req->flags, req->default_access, + req->mem_type); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + ret = create_id(hwfile, alloc); + if (ret < 0) + hwmem_release(alloc); + + return ret; +} + +static int alloc_fd(struct hwmem_file *hwfile, struct hwmem_alloc_request *req) +{ + struct hwmem_alloc *alloc; + + if (hwfile->fd_alloc) + return -EBUSY; + + alloc = hwmem_alloc(req->size, req->flags, req->default_access, + req->mem_type); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + hwfile->fd_alloc = alloc; + + return 0; +} + +static int release(struct hwmem_file *hwfile, s32 id) +{ + struct hwmem_alloc *alloc; + + alloc = resolve_id(hwfile, id); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + remove_id(hwfile, id); + hwmem_release(alloc); + + return 0; +} + +static int hwmem_ioctl_set_domain(struct hwmem_file *hwfile, + struct hwmem_set_domain_request *req) +{ + struct hwmem_alloc *alloc; + + alloc = resolve_id(hwfile, req->id); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + return hwmem_set_domain(alloc, req->access, req->domain, &req->region); +} + +static int pin(struct hwmem_file *hwfile, struct hwmem_pin_request *req) +{ + struct hwmem_alloc *alloc; + + alloc = resolve_id(hwfile, req->id); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + return hwmem_pin(alloc, &req->phys_addr, req->scattered_addrs); +} + +static int unpin(struct hwmem_file *hwfile, s32 id) +{ + struct hwmem_alloc *alloc; + + alloc = resolve_id(hwfile, id); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + hwmem_unpin(alloc); + + return 0; +} + +static int set_access(struct hwmem_file *hwfile, + struct hwmem_set_access_request *req) +{ + struct hwmem_alloc *alloc; + + alloc = resolve_id(hwfile, req->id); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + return hwmem_set_access(alloc, req->access, req->pid); +} + +static int get_info(struct hwmem_file *hwfile, + struct hwmem_get_info_request *req) +{ + struct hwmem_alloc *alloc; + + alloc = resolve_id(hwfile, req->id); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + hwmem_get_info(alloc, &req->size, &req->mem_type, &req->access); + + return 0; +} + +static int export(struct hwmem_file *hwfile, s32 id) +{ + int ret; + struct hwmem_alloc *alloc; + + uint32_t size; + enum hwmem_mem_type mem_type; + enum hwmem_access access; + + alloc = resolve_id(hwfile, id); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + /* + * The user could be about to send the buffer to a driver but + * there is a chance the current thread group don't have import rights + * if it gained access to the buffer via a inter-process fd transfer + * (fork, Android binder), if this is the case the driver will not be + * able to resolve the buffer name. To avoid this situation we give the + * current thread group import rights. This will not breach the + * security as the process already has access to the buffer (otherwise + * it would not be able to get here). + */ + hwmem_get_info(alloc, &size, &mem_type, &access); + + ret = hwmem_set_access(alloc, (access | HWMEM_ACCESS_IMPORT), + task_tgid_nr(current)); + if (ret < 0) + goto error; + + return hwmem_get_name(alloc); + +error: + return ret; +} + +static int import(struct hwmem_file *hwfile, s32 name) +{ + int ret = 0; + struct hwmem_alloc *alloc; + + uint32_t size; + enum hwmem_mem_type mem_type; + enum hwmem_access access; + + alloc = hwmem_resolve_by_name(name); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + /* Check access permissions for process */ + hwmem_get_info(alloc, &size, &mem_type, &access); + + if (!(access & HWMEM_ACCESS_IMPORT)) { + ret = -EPERM; + goto error; + } + + ret = create_id(hwfile, alloc); + if (ret < 0) + hwmem_release(alloc); + +error: + return ret; +} + +static int import_fd(struct hwmem_file *hwfile, s32 name) +{ + struct hwmem_alloc *alloc; + + if (hwfile->fd_alloc) + return -EBUSY; + + alloc = hwmem_resolve_by_name(name); + if (IS_ERR(alloc)) + return PTR_ERR(alloc); + + hwfile->fd_alloc = alloc; + + return 0; +} + +static int hwmem_open(struct inode *inode, struct file *file) +{ + struct hwmem_file *hwfile; + + hwfile = kzalloc(sizeof(struct hwmem_file), GFP_KERNEL); + if (hwfile == NULL) + return -ENOMEM; + + idr_init(&hwfile->idr); + mutex_init(&hwfile->lock); + file->private_data = hwfile; + + return 0; +} + +static int hwmem_ioctl_mmap(struct file *file, struct vm_area_struct *vma) +{ + int ret; + struct hwmem_file *hwfile = (struct hwmem_file *)file->private_data; + struct hwmem_alloc *alloc; + + mutex_lock(&hwfile->lock); + + alloc = resolve_id(hwfile, vma->vm_pgoff << PAGE_SHIFT); + if (IS_ERR(alloc)) { + ret = PTR_ERR(alloc); + goto out; + } + + ret = hwmem_mmap(alloc, vma); + +out: + mutex_unlock(&hwfile->lock); + + return ret; +} + +static int hwmem_release_idr_for_each_wrapper(int id, void *ptr, void *data) +{ + hwmem_release((struct hwmem_alloc *)ptr); + + return 0; +} + +static int hwmem_release_fop(struct inode *inode, struct file *file) +{ + struct hwmem_file *hwfile = (struct hwmem_file *)file->private_data; + + idr_for_each(&hwfile->idr, hwmem_release_idr_for_each_wrapper, NULL); + idr_remove_all(&hwfile->idr); + idr_destroy(&hwfile->idr); + + if (hwfile->fd_alloc) + hwmem_release(hwfile->fd_alloc); + + mutex_destroy(&hwfile->lock); + + kfree(hwfile); + + return 0; +} + +static long hwmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = -ENOSYS; + struct hwmem_file *hwfile = (struct hwmem_file *)file->private_data; + + mutex_lock(&hwfile->lock); + + switch (cmd) { + case HWMEM_ALLOC_IOC: + { + struct hwmem_alloc_request req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct hwmem_alloc_request))) + ret = -EFAULT; + else + ret = alloc(hwfile, &req); + } + break; + case HWMEM_ALLOC_FD_IOC: + { + struct hwmem_alloc_request req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct hwmem_alloc_request))) + ret = -EFAULT; + else + ret = alloc_fd(hwfile, &req); + } + break; + case HWMEM_RELEASE_IOC: + ret = release(hwfile, (s32)arg); + break; + case HWMEM_SET_DOMAIN_IOC: + { + struct hwmem_set_domain_request req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct hwmem_set_domain_request))) + ret = -EFAULT; + else + ret = hwmem_ioctl_set_domain(hwfile, &req); + } + break; + case HWMEM_PIN_IOC: + { + struct hwmem_pin_request req; + /* + * TODO: Validate and copy scattered_addrs. Not a + * problem right now as it's never used. + */ + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct hwmem_pin_request))) + ret = -EFAULT; + else + ret = pin(hwfile, &req); + if (ret == 0 && copy_to_user((void __user *)arg, &req, + sizeof(struct hwmem_pin_request))) + ret = -EFAULT; + } + break; + case HWMEM_UNPIN_IOC: + ret = unpin(hwfile, (s32)arg); + break; + case HWMEM_SET_ACCESS_IOC: + { + struct hwmem_set_access_request req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct hwmem_set_access_request))) + ret = -EFAULT; + else + ret = set_access(hwfile, &req); + } + break; + case HWMEM_GET_INFO_IOC: + { + struct hwmem_get_info_request req; + if (copy_from_user(&req, (void __user *)arg, + sizeof(struct hwmem_get_info_request))) + ret = -EFAULT; + else + ret = get_info(hwfile, &req); + if (ret == 0 && copy_to_user((void __user *)arg, &req, + sizeof(struct hwmem_get_info_request))) + ret = -EFAULT; + } + break; + case HWMEM_EXPORT_IOC: + ret = export(hwfile, (s32)arg); + break; + case HWMEM_IMPORT_IOC: + ret = import(hwfile, (s32)arg); + break; + case HWMEM_IMPORT_FD_IOC: + ret = import_fd(hwfile, (s32)arg); + break; + } + + mutex_unlock(&hwfile->lock); + + return ret; +} + +static unsigned long hwmem_get_unmapped_area(struct file *file, + unsigned long addr, unsigned long len, unsigned long pgoff, + unsigned long flags) +{ + /* + * pgoff will not be valid as it contains a buffer id (right shifted + * PAGE_SHIFT bits). To not confuse get_unmapped_area we'll not pass + * on file or pgoff. + */ + return current->mm->get_unmapped_area(NULL, addr, len, 0, flags); +} + +int __init hwmem_ioctl_init(void) +{ + return misc_register(&hwmem_device); +} + +void __exit hwmem_ioctl_exit(void) +{ + misc_deregister(&hwmem_device); +} diff --git a/drivers/misc/hwmem/hwmem-main.c b/drivers/misc/hwmem/hwmem-main.c new file mode 100644 index 00000000000..0010e45ff52 --- /dev/null +++ b/drivers/misc/hwmem/hwmem-main.c @@ -0,0 +1,833 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * Hardware memory driver, hwmem + * + * Author: Marcus Lorentzon + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cache_handler.h" + +struct hwmem_alloc_threadg_info { + struct list_head list; + + struct pid *threadg_pid; /* Ref counted */ + + enum hwmem_access access; +}; + +struct hwmem_alloc { + struct list_head list; + + atomic_t ref_cnt; + enum hwmem_alloc_flags flags; + u32 paddr; + void *kaddr; + u32 size; + u32 name; + + /* Access control */ + enum hwmem_access default_access; + struct list_head threadg_info_list; + + /* Cache handling */ + struct cach_buf cach_buf; +}; + +static struct platform_device *hwdev; + +static u32 hwmem_paddr; +static u32 hwmem_size; + +static LIST_HEAD(alloc_list); +static DEFINE_IDR(global_idr); +static DEFINE_MUTEX(lock); + +static void vm_open(struct vm_area_struct *vma); +static void vm_close(struct vm_area_struct *vma); +static struct vm_operations_struct vm_ops = { + .open = vm_open, + .close = vm_close, +}; + +#ifdef CONFIG_DEBUG_FS + +static int debugfs_allocs_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos); +static const struct file_operations debugfs_allocs_fops = { + .owner = THIS_MODULE, + .read = debugfs_allocs_read, +}; + +#endif /* #ifdef CONFIG_DEBUG_FS */ + +static void clean_alloc_list(void); +static void kunmap_alloc(struct hwmem_alloc *alloc); + +/* Helpers */ + +static u32 get_alloc_offset(struct hwmem_alloc *alloc) +{ + return alloc->paddr - hwmem_paddr; +} + +static void destroy_hwmem_alloc_threadg_info( + struct hwmem_alloc_threadg_info *info) +{ + if (info->threadg_pid) + put_pid(info->threadg_pid); + + kfree(info); +} + +static void clean_hwmem_alloc_threadg_info_list(struct hwmem_alloc *alloc) +{ + struct hwmem_alloc_threadg_info *info; + struct hwmem_alloc_threadg_info *tmp; + + list_for_each_entry_safe(info, tmp, &(alloc->threadg_info_list), list) { + list_del(&info->list); + destroy_hwmem_alloc_threadg_info(info); + } +} + +static enum hwmem_access get_access(struct hwmem_alloc *alloc) +{ + struct hwmem_alloc_threadg_info *info; + struct pid *my_pid; + bool found = false; + + my_pid = find_get_pid(task_tgid_nr(current)); + if (!my_pid) + return 0; + + list_for_each_entry(info, &(alloc->threadg_info_list), list) { + if (info->threadg_pid == my_pid) { + found = true; + break; + } + } + + put_pid(my_pid); + + if (found) + return info->access; + else + return alloc->default_access; +} + +static void clear_alloc_mem(struct hwmem_alloc *alloc) +{ + cach_set_domain(&alloc->cach_buf, HWMEM_ACCESS_WRITE, + HWMEM_DOMAIN_CPU, NULL); + + memset(alloc->kaddr, 0, alloc->size); +} + +static void clean_alloc(struct hwmem_alloc *alloc) +{ + if (alloc->name) { + idr_remove(&global_idr, alloc->name); + alloc->name = 0; + } + + alloc->flags = 0; + + clean_hwmem_alloc_threadg_info_list(alloc); + + kunmap_alloc(alloc); +} + +static void destroy_alloc(struct hwmem_alloc *alloc) +{ + clean_alloc(alloc); + + kfree(alloc); +} + +static void __hwmem_release(struct hwmem_alloc *alloc) +{ + struct hwmem_alloc *other; + + clean_alloc(alloc); + + other = list_entry(alloc->list.prev, struct hwmem_alloc, list); + if ((alloc->list.prev != &alloc_list) && + atomic_read(&other->ref_cnt) == 0) { + other->size += alloc->size; + list_del(&alloc->list); + destroy_alloc(alloc); + alloc = other; + } + other = list_entry(alloc->list.next, struct hwmem_alloc, list); + if ((alloc->list.next != &alloc_list) && + atomic_read(&other->ref_cnt) == 0) { + alloc->size += other->size; + list_del(&other->list); + destroy_alloc(other); + } +} + +static struct hwmem_alloc *find_free_alloc_bestfit(u32 size) +{ + u32 best_diff = ~0; + struct hwmem_alloc *alloc = NULL, *i; + + list_for_each_entry(i, &alloc_list, list) { + u32 diff = i->size - size; + if (atomic_read(&i->ref_cnt) > 0 || i->size < size) + continue; + if (diff < best_diff) { + alloc = i; + best_diff = diff; + } + } + + return alloc != NULL ? alloc : ERR_PTR(-ENOMEM); +} + +static struct hwmem_alloc *split_allocation(struct hwmem_alloc *alloc, + u32 new_alloc_size) +{ + struct hwmem_alloc *new_alloc; + + new_alloc = kzalloc(sizeof(struct hwmem_alloc), GFP_KERNEL); + if (new_alloc == NULL) + return ERR_PTR(-ENOMEM); + + atomic_inc(&new_alloc->ref_cnt); + INIT_LIST_HEAD(&new_alloc->threadg_info_list); + new_alloc->paddr = alloc->paddr; + new_alloc->size = new_alloc_size; + alloc->size -= new_alloc_size; + alloc->paddr += new_alloc_size; + + list_add_tail(&new_alloc->list, &alloc->list); + + return new_alloc; +} + +static int init_alloc_list(void) +{ + /* + * Hack to not get any allocs that cross a 64MiB boundary as B2R2 can't + * handle that. + */ + int ret; + u32 curr_pos = hwmem_paddr; + u32 hwmem_end = hwmem_paddr + hwmem_size; + u32 next_64mib_boundary = (curr_pos + SZ_64M) & ~(SZ_64M - 1); + struct hwmem_alloc *alloc; + + if (PAGE_SIZE >= SZ_64M) { + dev_err(&hwdev->dev, "PAGE_SIZE >= SZ_64M\n"); + return -ENOMSG; + } + + while (next_64mib_boundary < hwmem_end) { + if (next_64mib_boundary - curr_pos > PAGE_SIZE) { + alloc = kzalloc(sizeof(struct hwmem_alloc), GFP_KERNEL); + if (alloc == NULL) { + ret = -ENOMEM; + goto error; + } + alloc->paddr = curr_pos; + alloc->size = next_64mib_boundary - curr_pos - + PAGE_SIZE; + INIT_LIST_HEAD(&alloc->threadg_info_list); + list_add_tail(&alloc->list, &alloc_list); + curr_pos = alloc->paddr + alloc->size; + } + + alloc = kzalloc(sizeof(struct hwmem_alloc), GFP_KERNEL); + if (alloc == NULL) { + ret = -ENOMEM; + goto error; + } + alloc->paddr = curr_pos; + alloc->size = PAGE_SIZE; + atomic_inc(&alloc->ref_cnt); + INIT_LIST_HEAD(&alloc->threadg_info_list); + list_add_tail(&alloc->list, &alloc_list); + curr_pos = alloc->paddr + alloc->size; + + next_64mib_boundary += SZ_64M; + } + + alloc = kzalloc(sizeof(struct hwmem_alloc), GFP_KERNEL); + if (alloc == NULL) { + ret = -ENOMEM; + goto error; + } + alloc->paddr = curr_pos; + alloc->size = hwmem_end - curr_pos; + INIT_LIST_HEAD(&alloc->threadg_info_list); + list_add_tail(&alloc->list, &alloc_list); + + return 0; + +error: + clean_alloc_list(); + + return ret; +} + +static void clean_alloc_list(void) +{ + while (list_empty(&alloc_list) == 0) { + struct hwmem_alloc *i = list_first_entry(&alloc_list, + struct hwmem_alloc, list); + + list_del(&i->list); + + destroy_alloc(i); + } +} + +static int kmap_alloc(struct hwmem_alloc *alloc) +{ + int ret; + pgprot_t pgprot; + + struct vm_struct *area = get_vm_area(alloc->size, VM_IOREMAP); + if (area == NULL) { + dev_info(&hwdev->dev, "Failed to allocate %u bytes virtual" + " memory", alloc->size); + return -ENOMSG; + } + + pgprot = PAGE_KERNEL; + cach_set_pgprot_cache_options(&alloc->cach_buf, &pgprot); + + ret = ioremap_page_range((unsigned long)area->addr, + (unsigned long)area->addr + alloc->size, alloc->paddr, pgprot); + if (ret < 0) { + dev_info(&hwdev->dev, "Failed to map %#x - %#x", alloc->paddr, + alloc->paddr + alloc->size); + goto failed_to_map; + } + + alloc->kaddr = area->addr; + + return 0; + +failed_to_map: + area = remove_vm_area(area->addr); + if (area == NULL) + dev_err(&hwdev->dev, + "Failed to unmap alloc, resource leak!\n"); + + kfree(area); + + return ret; +} + +static void kunmap_alloc(struct hwmem_alloc *alloc) +{ + struct vm_struct *area; + + if (alloc->kaddr == NULL) + return; + + area = remove_vm_area(alloc->kaddr); + if (area == NULL) { + dev_err(&hwdev->dev, + "Failed to unmap alloc, resource leak!\n"); + return; + } + + kfree(area); + + alloc->kaddr = NULL; +} + +/* HWMEM API */ + +struct hwmem_alloc *hwmem_alloc(u32 size, enum hwmem_alloc_flags flags, + enum hwmem_access def_access, enum hwmem_mem_type mem_type) +{ + struct hwmem_alloc *alloc; + int ret; + + if (!hwdev) { + printk(KERN_ERR "hwmem: Badly configured\n"); + return ERR_PTR(-EINVAL); + } + + if (size == 0) + return ERR_PTR(-EINVAL); + + mutex_lock(&lock); + + size = PAGE_ALIGN(size); + + alloc = find_free_alloc_bestfit(size); + if (IS_ERR(alloc)) { + dev_info(&hwdev->dev, "Allocation failed, no free slot\n"); + goto no_slot; + } + + if (size < alloc->size) { + alloc = split_allocation(alloc, size); + if (IS_ERR(alloc)) + goto split_alloc_failed; + } else { + atomic_inc(&alloc->ref_cnt); + } + + alloc->flags = flags; + alloc->default_access = def_access; + cach_init_buf(&alloc->cach_buf, alloc->flags, alloc->size); + ret = kmap_alloc(alloc); + if (ret < 0) + goto kmap_alloc_failed; + cach_set_buf_addrs(&alloc->cach_buf, alloc->kaddr, alloc->paddr); + + clear_alloc_mem(alloc); + + goto out; + +kmap_alloc_failed: + __hwmem_release(alloc); + alloc = ERR_PTR(ret); +split_alloc_failed: +no_slot: + +out: + mutex_unlock(&lock); + + return alloc; +} +EXPORT_SYMBOL(hwmem_alloc); + +void hwmem_release(struct hwmem_alloc *alloc) +{ + mutex_lock(&lock); + + if (atomic_dec_and_test(&alloc->ref_cnt)) + __hwmem_release(alloc); + + mutex_unlock(&lock); +} +EXPORT_SYMBOL(hwmem_release); + +int hwmem_set_domain(struct hwmem_alloc *alloc, enum hwmem_access access, + enum hwmem_domain domain, struct hwmem_region *region) +{ + mutex_lock(&lock); + + cach_set_domain(&alloc->cach_buf, access, domain, region); + + mutex_unlock(&lock); + + return 0; +} +EXPORT_SYMBOL(hwmem_set_domain); + +int hwmem_pin(struct hwmem_alloc *alloc, uint32_t *phys_addr, + uint32_t *scattered_phys_addrs) +{ + mutex_lock(&lock); + + *phys_addr = alloc->paddr; + + mutex_unlock(&lock); + + return 0; +} +EXPORT_SYMBOL(hwmem_pin); + +void hwmem_unpin(struct hwmem_alloc *alloc) +{ +} +EXPORT_SYMBOL(hwmem_unpin); + +static void vm_open(struct vm_area_struct *vma) +{ + atomic_inc(&((struct hwmem_alloc *)vma->vm_private_data)->ref_cnt); +} + +static void vm_close(struct vm_area_struct *vma) +{ + hwmem_release((struct hwmem_alloc *)vma->vm_private_data); +} + +int hwmem_mmap(struct hwmem_alloc *alloc, struct vm_area_struct *vma) +{ + int ret = 0; + unsigned long vma_size = vma->vm_end - vma->vm_start; + enum hwmem_access access; + mutex_lock(&lock); + + access = get_access(alloc); + + /* Check permissions */ + if ((!(access & HWMEM_ACCESS_WRITE) && + (vma->vm_flags & VM_WRITE)) || + (!(access & HWMEM_ACCESS_READ) && + (vma->vm_flags & VM_READ))) { + ret = -EPERM; + goto illegal_access; + } + + if (vma_size > (unsigned long)alloc->size) { + ret = -EINVAL; + goto illegal_size; + } + + /* + * We don't want Linux to do anything (merging etc) with our VMAs as + * the offset is not necessarily valid + */ + vma->vm_flags |= VM_SPECIAL; + cach_set_pgprot_cache_options(&alloc->cach_buf, &vma->vm_page_prot); + vma->vm_private_data = (void *)alloc; + atomic_inc(&alloc->ref_cnt); + vma->vm_ops = &vm_ops; + + ret = remap_pfn_range(vma, vma->vm_start, alloc->paddr >> PAGE_SHIFT, + min(vma_size, (unsigned long)alloc->size), vma->vm_page_prot); + if (ret < 0) + goto map_failed; + + goto out; + +map_failed: + atomic_dec(&alloc->ref_cnt); +illegal_size: +illegal_access: + +out: + mutex_unlock(&lock); + + return ret; +} +EXPORT_SYMBOL(hwmem_mmap); + +void *hwmem_kmap(struct hwmem_alloc *alloc) +{ + void *ret; + + mutex_lock(&lock); + + ret = alloc->kaddr; + + mutex_unlock(&lock); + + return ret; +} +EXPORT_SYMBOL(hwmem_kmap); + +void hwmem_kunmap(struct hwmem_alloc *alloc) +{ +} +EXPORT_SYMBOL(hwmem_kunmap); + +int hwmem_set_access(struct hwmem_alloc *alloc, + enum hwmem_access access, pid_t pid_nr) +{ + int ret; + struct hwmem_alloc_threadg_info *info; + struct pid *pid; + bool found = false; + + pid = find_get_pid(pid_nr); + if (!pid) { + ret = -EINVAL; + goto error_get_pid; + } + + list_for_each_entry(info, &(alloc->threadg_info_list), list) { + if (info->threadg_pid == pid) { + found = true; + break; + } + } + + if (!found) { + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + ret = -ENOMEM; + goto error_alloc_info; + } + + info->threadg_pid = pid; + info->access = access; + + list_add_tail(&(info->list), &(alloc->threadg_info_list)); + } else { + info->access = access; + } + + return 0; + +error_alloc_info: + put_pid(pid); +error_get_pid: + return ret; +} +EXPORT_SYMBOL(hwmem_set_access); + +void hwmem_get_info(struct hwmem_alloc *alloc, uint32_t *size, + enum hwmem_mem_type *mem_type, enum hwmem_access *access) +{ + mutex_lock(&lock); + + *size = alloc->size; + *mem_type = HWMEM_MEM_CONTIGUOUS_SYS; + *access = get_access(alloc); + + mutex_unlock(&lock); +} +EXPORT_SYMBOL(hwmem_get_info); + +int hwmem_get_name(struct hwmem_alloc *alloc) +{ + int ret = 0, name; + + mutex_lock(&lock); + + if (alloc->name != 0) { + ret = alloc->name; + goto out; + } + + while (true) { + if (idr_pre_get(&global_idr, GFP_KERNEL) == 0) { + ret = -ENOMEM; + goto pre_get_id_failed; + } + + ret = idr_get_new_above(&global_idr, alloc, 1, &name); + if (ret == 0) + break; + else if (ret != -EAGAIN) + goto get_id_failed; + } + + alloc->name = name; + + ret = name; + goto out; + +get_id_failed: +pre_get_id_failed: + +out: + mutex_unlock(&lock); + + return ret; +} +EXPORT_SYMBOL(hwmem_get_name); + +struct hwmem_alloc *hwmem_resolve_by_name(s32 name) +{ + struct hwmem_alloc *alloc; + + mutex_lock(&lock); + + alloc = idr_find(&global_idr, name); + if (alloc == NULL) { + alloc = ERR_PTR(-EINVAL); + goto find_failed; + } + atomic_inc(&alloc->ref_cnt); + + goto out; + +find_failed: + +out: + mutex_unlock(&lock); + + return alloc; +} +EXPORT_SYMBOL(hwmem_resolve_by_name); + +/* Debug */ + +static int print_alloc(struct hwmem_alloc *alloc, char **buf, size_t buf_size) +{ + int ret; + + if (buf_size < 134) + return -EINVAL; + + ret = sprintf(*buf, "paddr: %#10x\tsize: %10u\tref cnt: %2i\t" + "name: %#10x\tflags: %#4x\t$ settings: %#4x\t" + "def acc: %#3x\n", alloc->paddr, alloc->size, + atomic_read(&alloc->ref_cnt), alloc->name, + alloc->flags, alloc->cach_buf.cache_settings, + alloc->default_access); + if (ret < 0) + return -ENOMSG; + + *buf += ret; + + return 0; +} + +#ifdef CONFIG_DEBUG_FS + +static int debugfs_allocs_read(struct file *file, char __user *buf, + size_t count, loff_t *f_pos) +{ + /* + * We assume the supplied buffer and PAGE_SIZE is large enough to hold + * information about at least one alloc, if not no data will be + * returned. + */ + + int ret; + struct hwmem_alloc *curr_alloc; + char *local_buf = kmalloc(PAGE_SIZE, GFP_KERNEL); + char *local_buf_pos = local_buf; + size_t available_space = min((size_t)PAGE_SIZE, count); + /* private_data is intialized to NULL in open which I assume is 0. */ + u32 *curr_pos = (u32 *)&file->private_data; + size_t bytes_read; + + if (local_buf == NULL) + return -ENOMEM; + + mutex_lock(&lock); + + list_for_each_entry(curr_alloc, &alloc_list, list) { + u32 alloc_offset = get_alloc_offset(curr_alloc); + + if (alloc_offset < *curr_pos) + continue; + + ret = print_alloc(curr_alloc, &local_buf_pos, available_space - + (size_t)(local_buf_pos - local_buf)); + if (ret == -EINVAL) /* No more room */ + break; + else if (ret < 0) + goto out; + + *curr_pos = alloc_offset + 1; + } + + bytes_read = (size_t)(local_buf_pos - local_buf); + + ret = copy_to_user(buf, local_buf, bytes_read); + if (ret < 0) + goto out; + + ret = bytes_read; + +out: + kfree(local_buf); + + mutex_unlock(&lock); + + return ret; +} + +static void init_debugfs(void) +{ + /* Hwmem is never unloaded so dropping the dentrys is ok. */ + struct dentry *debugfs_root_dir = debugfs_create_dir("hwmem", NULL); + (void)debugfs_create_file("allocs", 0444, debugfs_root_dir, 0, + &debugfs_allocs_fops); +} + +#endif /* #ifdef CONFIG_DEBUG_FS */ + +/* Module */ + +extern int hwmem_ioctl_init(void); +extern void hwmem_ioctl_exit(void); + +static int __devinit hwmem_probe(struct platform_device *pdev) +{ + int ret = 0; + struct hwmem_platform_data *platform_data = pdev->dev.platform_data; + + if (hwdev || platform_data->size == 0 || + platform_data->start != PAGE_ALIGN(platform_data->start) || + platform_data->size != PAGE_ALIGN(platform_data->size)) { + dev_err(&pdev->dev, "hwdev || platform_data->size == 0 ||" + "platform_data->start !=" + " PAGE_ALIGN(platform_data->start) ||" + "platform_data->size !=" + " PAGE_ALIGN(platform_data->size)\n"); + return -EINVAL; + } + + hwdev = pdev; + hwmem_paddr = platform_data->start; + hwmem_size = platform_data->size; + + /* + * No need to flush the caches here. If we can keep track of the cache + * content then none of our memory will be in the caches, if we can't + * keep track of the cache content we always assume all our memory is + * in the caches. + */ + + ret = init_alloc_list(); + if (ret < 0) + goto init_alloc_list_failed; + + ret = hwmem_ioctl_init(); + if (ret) + goto ioctl_init_failed; + +#ifdef CONFIG_DEBUG_FS + init_debugfs(); +#endif + + dev_info(&pdev->dev, "Hwmem probed, device contains %#x bytes\n", + hwmem_size); + + goto out; + +ioctl_init_failed: + clean_alloc_list(); +init_alloc_list_failed: + hwdev = NULL; + +out: + return ret; +} + +static struct platform_driver hwmem_driver = { + .probe = hwmem_probe, + .driver = { + .name = "hwmem", + }, +}; + +static int __init hwmem_init(void) +{ + return platform_driver_register(&hwmem_driver); +} +subsys_initcall(hwmem_init); + +MODULE_AUTHOR("Marcus Lorentzon "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Hardware memory driver"); + diff --git a/include/linux/hwmem.h b/include/linux/hwmem.h new file mode 100644 index 00000000000..bc0a26a30a6 --- /dev/null +++ b/include/linux/hwmem.h @@ -0,0 +1,519 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * ST-Ericsson HW memory driver + * + * Author: Marcus Lorentzon + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef _HWMEM_H_ +#define _HWMEM_H_ + +#if !defined(__KERNEL__) && !defined(_KERNEL) +#include +#include +#else +#include +#include +#endif + +#define HWMEM_DEFAULT_DEVICE_NAME "hwmem" + +/** + * @brief Flags defining behavior of allocation + */ +enum hwmem_alloc_flags { + /** + * @brief Buffer will not be cached and not buffered + */ + HWMEM_ALLOC_UNCACHED = (0 << 0), + /** + * @brief Buffer will be buffered, but not cached + */ + HWMEM_ALLOC_BUFFERED = (1 << 0), + /** + * @brief Buffer will be cached and buffered, use cache hints to be + * more specific + */ + HWMEM_ALLOC_CACHED = (3 << 0), + /** + * @brief Buffer should be cached write-back in both level 1 and 2 cache + */ + HWMEM_ALLOC_CACHE_HINT_WB = (1 << 2), + /** + * @brief Buffer should be cached write-through in both level 1 and + * 2 cache + */ + HWMEM_ALLOC_CACHE_HINT_WT = (2 << 2), + /** + * @brief Buffer should be cached write-back in level 1 cache + */ + HWMEM_ALLOC_CACHE_HINT_WB_INNER = (3 << 2), + /** + * @brief Buffer should be cached write-through in level 1 cache + */ + HWMEM_ALLOC_CACHE_HINT_WT_INNER = (4 << 2), + HWMEM_ALLOC_CACHE_HINT_MASK = 0x1C, +}; + +/** + * @brief Flags defining buffer access mode. + */ +enum hwmem_access { + /** + * @brief Buffer will be read from. + */ + HWMEM_ACCESS_READ = (1 << 0), + /** + * @brief Buffer will be written to. + */ + HWMEM_ACCESS_WRITE = (1 << 1), + /** + * @brief Buffer will be imported. + */ + HWMEM_ACCESS_IMPORT = (1 << 2), +}; + +/** + * @brief Flags defining memory type. + */ +enum hwmem_mem_type { + /** + * @brief Scattered system memory. Currently not supported! + */ + HWMEM_MEM_SCATTERED_SYS = (1 << 0), + /** + * @brief Contiguous system memory. + */ + HWMEM_MEM_CONTIGUOUS_SYS = (1 << 1), +}; + +/** + * @brief Values defining memory domain. + */ +enum hwmem_domain { + /** + * @brief This value specifies the neutral memory domain. Setting this + * domain will syncronize all supported memory domains (currently CPU). + */ + HWMEM_DOMAIN_SYNC = 0, + /** + * @brief This value specifies the CPU memory domain. + */ + HWMEM_DOMAIN_CPU = 1, +}; + +/** + * @brief Structure defining a region of a memory buffer. + * + * A buffer is defined to contain a number of equally sized blocks. Each block + * has a part of it included in the region [-). That is + * - bytes. Each block is bytes long. Total number of bytes + * in the region is ( - ) * . First byte of the region is + * + bytes into the buffer. + * + * Here's an example of a region in a graphics buffer (X = buffer, R = region): + * + * XXXXXXXXXXXXXXXXXXXX \ + * XXXXXXXXXXXXXXXXXXXX |-- offset = 60 + * XXXXXXXXXXXXXXXXXXXX / + * XXRRRRRRRRXXXXXXXXXX \ + * XXRRRRRRRRXXXXXXXXXX |-- count = 4 + * XXRRRRRRRRXXXXXXXXXX | + * XXRRRRRRRRXXXXXXXXXX / + * XXXXXXXXXXXXXXXXXXXX + * --| start = 2 + * ----------| end = 10 + * --------------------| size = 20 + */ +struct hwmem_region { + /** + * @brief The first block's offset from beginning of buffer. + */ + uint32_t offset; + /** + * @brief The number of blocks included in this region. + */ + uint32_t count; + /** + * @brief The index of the first byte included in this block. + */ + uint32_t start; + /** + * @brief The index of the last byte included in this block plus one. + */ + uint32_t end; + /** + * @brief The size in bytes of each block. + */ + uint32_t size; +}; + +/* User space API */ + +/** + * @brief Alloc request data. + */ +struct hwmem_alloc_request { + /** + * @brief [in] Size of requested allocation in bytes. Size will be + * aligned to PAGE_SIZE bytes. + */ + uint32_t size; + /** + * @brief [in] Flags describing requested allocation options. + */ + uint32_t flags; /* enum hwmem_alloc_flags */ + /** + * @brief [in] Default access rights for buffer. + */ + uint32_t default_access; /* enum hwmem_access */ + /** + * @brief [in] Memory type of the buffer. + */ + uint32_t mem_type; /* enum hwmem_mem_type */ +}; + +/** + * @brief Set domain request data. + */ +struct hwmem_set_domain_request { + /** + * @brief [in] Identifier of buffer to be prepared. If 0 is specified + * the buffer associated with the current file instance will be used. + */ + int32_t id; + /** + * @brief [in] Value specifying the new memory domain. + */ + uint32_t domain; /* enum hwmem_domain */ + /** + * @brief [in] Flags specifying access mode of the operation. + * + * One of HWMEM_ACCESS_READ and HWMEM_ACCESS_WRITE is required. + * For details, @see enum hwmem_access. + */ + uint32_t access; /* enum hwmem_access */ + /** + * @brief [in] The region of bytes to be prepared. + * + * For details, @see struct hwmem_region. + */ + struct hwmem_region region; +}; + +/** + * @brief Pin request data. + */ +struct hwmem_pin_request { + /** + * @brief [in] Identifier of buffer to be pinned. If 0 is specified, + * the buffer associated with the current file instance will be used. + */ + int32_t id; + /** + * @brief [out] Physical address of first word in buffer. + */ + uint32_t phys_addr; + /** + * @brief [in] Pointer to buffer for physical addresses of pinned + * scattered buffer. Buffer must be (buffer_size / page_size) * + * sizeof(uint32_t) bytes. + * This field can be NULL for physically contiguos buffers. + */ + uint32_t *scattered_addrs; +}; + +/** + * @brief Set access rights request data. + */ +struct hwmem_set_access_request { + /** + * @brief [in] Identifier of buffer to be pinned. If 0 is specified, + * the buffer associated with the current file instance will be used. + */ + int32_t id; + /** + * @param access Access value indicating what is allowed. + */ + uint32_t access; /* enum hwmem_access */ + /** + * @param pid Process ID to set rights for. + */ + pid_t pid; +}; + +/** + * @brief Get info request data. + */ +struct hwmem_get_info_request { + /** + * @brief [in] Identifier of buffer to get info about. If 0 is specified, + * the buffer associated with the current file instance will be used. + */ + int32_t id; + /** + * @brief [out] Size in bytes of buffer. + */ + uint32_t size; + /** + * @brief [out] Memory type of buffer. + */ + uint32_t mem_type; /* enum hwmem_mem_type */ + /** + * @brief [out] Access rights for buffer. + */ + uint32_t access; /* enum hwmem_access */ +}; + +/** + * @brief Allocates number of bytes and returns a buffer identifier. + * + * Input is a pointer to a hwmem_alloc_request struct. + * + * @return A buffer identifier on success, or a negative error code. + */ +#define HWMEM_ALLOC_IOC _IOW('W', 1, struct hwmem_alloc_request) + +/** + * @brief Allocates number of bytes and associates the created buffer + * with the current file instance. + * + * If the current file instance is already associated with a buffer the call + * will fail. Buffers referenced through files instances shall not be released + * with HWMEM_RELEASE_IOC, instead the file instance shall be closed. + * + * Input is a pointer to a hwmem_alloc_request struct. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_ALLOC_FD_IOC _IOW('W', 2, struct hwmem_alloc_request) + +/** + * @brief Releases buffer. + * + * Buffers are reference counted and will not be destroyed until the last + * reference is released. Bufferes allocated with ALLOC_FD_IOC not allowed. + * + * Input is the buffer identifier. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_RELEASE_IOC _IO('W', 3) + +/** + * @brief Set the buffer's memory domain and prepares it for access. + * + * Input is a pointer to a hwmem_set_domain_request struct. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_SET_DOMAIN_IOC _IOR('W', 4, struct hwmem_set_domain_request) + +/** + * @brief Pins the buffer and returns the physical address of the buffer. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_PIN_IOC _IOWR('W', 5, struct hwmem_pin_request) + +/** + * @brief Unpins the buffer. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_UNPIN_IOC _IO('W', 6) + +/** + * @brief Set access rights for buffer. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_SET_ACCESS_IOC _IOW('W', 7, struct hwmem_set_access_request) + +/** + * @brief Get buffer information. + * + * Input is the buffer identifier. If 0 is specified the buffer associated + * with the current file instance will be used. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_GET_INFO_IOC _IOWR('W', 8, struct hwmem_get_info_request) + +/** + * @brief Export the buffer identifier for use in another process. + * + * The global name will not increase the buffers reference count and will + * therefore not keep the buffer alive. + * + * Input is the buffer identifier. If 0 is specified the buffer associated with + * the current file instance will be exported. + * + * @return A global buffer name on success, or a negative error code. + */ +#define HWMEM_EXPORT_IOC _IO('W', 9) + +/** + * @brief Import a buffer to allow local access to the buffer. + * + * Input is the buffer's global name. + * + * @return The imported buffer's identifier on success, or a negative error code. + */ +#define HWMEM_IMPORT_IOC _IO('W', 10) + +/** + * @brief Import a buffer to allow local access to the buffer using fd. + * + * Input is the buffer's global name. + * + * @return Zero on success, or a negative error code. + */ +#define HWMEM_IMPORT_FD_IOC _IO('W', 11) + +#ifdef __KERNEL__ + +/* Kernel API */ + +struct hwmem_alloc; + +/** + * @brief Allocates number of bytes. + * + * @param size Number of bytes to allocate. All allocations are page aligned. + * @param flags Allocation options. + * @param def_access Default buffer access rights. + * @param mem_type Memory type. + * + * @return Pointer to allocation, or a negative error code. + */ +struct hwmem_alloc *hwmem_alloc(u32 size, enum hwmem_alloc_flags flags, + enum hwmem_access def_access, enum hwmem_mem_type mem_type); + +/** + * @brief Release a previously allocated buffer. + * When last reference is released, the buffer will be freed. + * + * @param alloc Buffer to be released. + */ +void hwmem_release(struct hwmem_alloc *alloc); + +/** + * @brief Set the buffer domain and prepare it for access. + * + * @param alloc Buffer to be prepared. + * @param access Flags defining memory access mode of the call. + * @param domain Value specifying the memory domain. + * @param region Structure defining the minimum area of the buffer to be + * prepared. + * + * @return Zero on success, or a negative error code. + */ +int hwmem_set_domain(struct hwmem_alloc *alloc, enum hwmem_access access, + enum hwmem_domain domain, struct hwmem_region *region); + +/** + * @brief Pins the buffer. + * + * @param alloc Buffer to be pinned. + * @param phys_addr Reference to variable to receive physical address. + * @param scattered_phys_addrs Pointer to buffer to receive physical addresses + * of all pages in the scattered buffer. Can be NULL if buffer is contigous. + * Buffer size must be (buffer_size / page_size) * sizeof(uint32_t) bytes. + */ +int hwmem_pin(struct hwmem_alloc *alloc, uint32_t *phys_addr, + uint32_t *scattered_phys_addrs); + +/** + * @brief Unpins the buffer. + * + * @param alloc Buffer to be unpinned. + */ +void hwmem_unpin(struct hwmem_alloc *alloc); + +/** + * @brief Map the buffer to user space. + * + * @param alloc Buffer to be unpinned. + */ +int hwmem_mmap(struct hwmem_alloc *alloc, struct vm_area_struct *vma); + +/** + * @brief Map the buffer for use in the kernel. + * + * This function implicitly pins the buffer. + * + * @param alloc Buffer to be mapped. + * + * @return Pointer to buffer, or a negative error code. + */ +void *hwmem_kmap(struct hwmem_alloc *alloc); + +/** + * @brief Un-map a buffer previously mapped with hwmem_kmap. + * + * This function implicitly unpins the buffer. + * + * @param alloc Buffer to be un-mapped. + */ +void hwmem_kunmap(struct hwmem_alloc *alloc); + +/** + * @brief Set access rights for buffer. + * + * @param alloc Buffer to set rights for. + * @param access Access value indicating what is allowed. + * @param pid Process ID to set rights for. + */ +int hwmem_set_access(struct hwmem_alloc *alloc, enum hwmem_access access, + pid_t pid); + +/** + * @brief Get buffer information. + * + * @param alloc Buffer to get information about. + * @param size Pointer to size output variable. + * @param size Pointer to memory type output variable. + * @param size Pointer to access rights output variable. + */ +void hwmem_get_info(struct hwmem_alloc *alloc, uint32_t *size, + enum hwmem_mem_type *mem_type, enum hwmem_access *access); + +/** + * @brief Allocate a global buffer name. + * Generated buffer name is valid in all processes. Consecutive calls will get + * the same name for the same buffer. + * + * @param alloc Buffer to be made public. + * + * @return Positive global name on success, or a negative error code. + */ +int hwmem_get_name(struct hwmem_alloc *alloc); + +/** + * @brief Import the global buffer name to allow local access to the buffer. + * This call will add a buffer reference. Resulting buffer should be + * released with a call to hwmem_release. + * + * @param name A valid global buffer name. + * + * @return Pointer to allocation, or a negative error code. + */ +struct hwmem_alloc *hwmem_resolve_by_name(s32 name); + +/* Internal */ + +struct hwmem_platform_data { + /* Starting physical address of memory region */ + unsigned long start; + /* Size of memory region */ + unsigned long size; +}; + +#endif + +#endif /* _HWMEM_H_ */ -- cgit v1.2.3 From bd459f375470269a792908f5e50411a5f9b66fbc Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 18 Mar 2011 15:47:12 +0100 Subject: misc: Add i2s driver Signed-off-by: Robert Marklund --- Documentation/DocBook/Makefile | 3 +- Documentation/DocBook/i2s.tmpl | 97 ++ drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 3 +- drivers/misc/i2s/Kconfig | 29 + drivers/misc/i2s/Makefile | 8 + drivers/misc/i2s/i2s.c | 597 ++++++++ drivers/misc/i2s/i2s_test_protocol_driver.c | 305 ++++ drivers/misc/i2s/msp_i2s.c | 2045 +++++++++++++++++++++++++++ drivers/misc/i2s/msp_i2s.h | 362 +++++ include/linux/i2s/i2s.h | 225 +++ include/linux/i2s/i2s_test_prot.h | 44 + 12 files changed, 3717 insertions(+), 2 deletions(-) create mode 100644 Documentation/DocBook/i2s.tmpl create mode 100644 drivers/misc/i2s/Kconfig create mode 100644 drivers/misc/i2s/Makefile create mode 100644 drivers/misc/i2s/i2s.c create mode 100644 drivers/misc/i2s/i2s_test_protocol_driver.c create mode 100644 drivers/misc/i2s/msp_i2s.c create mode 100644 drivers/misc/i2s/msp_i2s.h create mode 100644 include/linux/i2s/i2s.h create mode 100644 include/linux/i2s/i2s_test_prot.h diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index 3cebfa0d161..fdbc16c6e33 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -14,7 +14,8 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ genericirq.xml s390-drivers.xml uio-howto.xml scsi.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ - tracepoint.xml media.xml drm.xml + tracepoint.xml media.xml drm.xml \ + i2s.xml ### # The build process is as follows (targets): diff --git a/Documentation/DocBook/i2s.tmpl b/Documentation/DocBook/i2s.tmpl new file mode 100644 index 00000000000..6b6c50572e2 --- /dev/null +++ b/Documentation/DocBook/i2s.tmpl @@ -0,0 +1,97 @@ + + + + + + I2S + + + + Sandeep + Kaushik + +
+ sandeep.kaushik@st.com +
+
+
+
+ + + 2008-2009 + STMicroelectronics Pvt Ltd + + + + + Linux standard functions + + + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + +
+ + + + Introduction + + This Documentation describes the APIs provided by the I2S Bus Driver. I2S bus supports different + protocols like I2S, PCM, SPI etc. + + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Public Functions Provided + + This Section lists the functions exported by the I2S bus driver. These functions cater to all the protocols + supported namely: I2S, PCM, SPI. + +!Edrivers/misc/i2s/i2s.c + +
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 8719c535781..8dce7fc1e20 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -505,6 +505,7 @@ source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" +source "drivers/misc/i2s/Kconfig" source "drivers/misc/audio_io_dev/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 9ad03e8754e..4682fccff1f 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -46,5 +46,6 @@ obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ +obj-$(CONFIG_STM_I2S) += i2s/ obj-$(CONFIG_STE_AUDIO_IO_DEV) += audio_io_dev/ -obj-$(CONFIG_HWMEM) += hwmem/ +obj-$(CONFIG_HWMEM) += hwmem/ diff --git a/drivers/misc/i2s/Kconfig b/drivers/misc/i2s/Kconfig new file mode 100644 index 00000000000..a2652e9eab3 --- /dev/null +++ b/drivers/misc/i2s/Kconfig @@ -0,0 +1,29 @@ +# +# U8500 I2S HW kernel configuration +# +config STM_I2S + bool "U8500 I2S hardware driver" + depends on ARCH_U8500 && STE_DMA40 + default y + ---help--- + If you say Y here, you will enable the U8500 I2S hardware driver. + + If unsure, say N. +config STM_MSP_I2S + tristate "U8500 MSP_I2S hardware driver" + depends on ARCH_U8500 && STE_DMA40 && STM_I2S + default y + ---help--- + If you say Y here, you will enable the U8500 MSP_I2S hardware driver. + + If unsure, say N. + +config STM_I2S_TEST_PROTOCOL_DRIVER + tristate "U8500 I2S test protocol driver" + depends on STM_I2S && STE_DMA40 && STM_MSP_I2S + default n + ---help--- + If you say Y here, you will enable the test protocol driver used for testing I2S Rx and Tx controllers + + If unsure, say N. + diff --git a/drivers/misc/i2s/Makefile b/drivers/misc/i2s/Makefile new file mode 100644 index 00000000000..22cfdc07551 --- /dev/null +++ b/drivers/misc/i2s/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for I2S drivers +# + +nmdk_i2s-objs := i2s.o +obj-$(CONFIG_STM_I2S) += nmdk_i2s.o +obj-$(CONFIG_STM_MSP_I2S) += msp_i2s.o +obj-$(CONFIG_STM_I2S_TEST_PROTOCOL_DRIVER) += i2s_test_protocol_driver.o diff --git a/drivers/misc/i2s/i2s.c b/drivers/misc/i2s/i2s.c new file mode 100644 index 00000000000..a77711e3dd4 --- /dev/null +++ b/drivers/misc/i2s/i2s.c @@ -0,0 +1,597 @@ +/*----------------------------------------------------------------------------*/ +/* copyright STMicroelectronics, 2007. */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the Free */ +/* Software Foundation; either version 2.1 of the License, or (at your option)*/ +/* any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY */ +/* or FITNES */ +/* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more */ +/* details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/*----------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*******************************************************************************/ +static DEFINE_MUTEX(core_lock); + +static void i2sdev_release(struct device *dev) +{ + struct i2s_device *i2s = to_i2s_device(dev); + + if (i2s->controller) + put_device(&(i2s->controller->dev)); + kfree(dev); +} +static ssize_t +modalias_show(struct device *dev, struct device_attribute *a, char *buf) +{ + const struct i2s_device *i2s = to_i2s_device(dev); + return sprintf(buf, "%s\n", i2s->modalias); +} + +static struct device_attribute i2s_dev_attrs[] = { + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +/* modalias support makes "modprobe $MODALIAS" new-style hotplug work, + * and the sysfs version makes coldplug work too. + */ +static const struct i2s_device_id *i2s_match_id(const struct i2s_device_id *id, + const struct i2s_device *device) +{ + while (id->name[0]) { + if (strcmp(device->modalias, id->name) == 0) + return id; + id++; + } + return NULL; +} + +static int i2s_match_device(struct device *dev, struct device_driver *drv) +{ + const struct i2s_device *device = to_i2s_device(dev); + struct i2s_driver *driver = to_i2s_driver(drv); + if (driver->id_table) + return i2s_match_id(driver->id_table, device) != NULL; + return 0; +} + +static int i2s_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + const struct i2s_device *i2s = to_i2s_device(dev); + + add_uevent_var(env, "MODALIAS=%s", i2s->modalias); + return 0; +} + +#ifdef CONFIG_PM +static int i2s_suspend(struct device *dev, pm_message_t message) +{ + int value = 0; + struct i2s_driver *drv = to_i2s_driver(dev->driver); + + /* suspend will stop irqs and dma; no more i/o */ + if (drv) { + if (drv->suspend) + value = drv->suspend(to_i2s_device(dev), message); + else + dev_dbg(dev, "... can't suspend\n"); + } + return value; +} + +static int i2s_resume(struct device *dev) +{ + int value = 0; + struct i2s_driver *drv = to_i2s_driver(dev->driver); + + /* resume may restart the i/o queue */ + if (drv) { + if (drv->resume) + value = drv->resume(to_i2s_device(dev)); + else + dev_dbg(dev, "... can't resume\n"); + } + return value; +} + +#else +#define i2s_suspend NULL +#define i2s_resume NULL +#endif + +/*This bus is designed to handle various protocols supported by the MSP- ARM Primecell IP + * such as + * I2s, PCM, AC97, TDM .... (refer to the data sheet for the complete list. + * Current MSP driver has the above ones coded. + * */ +struct bus_type i2s_bus_type = { + .name = "i2s", + .dev_attrs = i2s_dev_attrs, + .match = i2s_match_device, + .uevent = i2s_uevent, + .suspend = i2s_suspend, + .resume = i2s_resume, +}; + +EXPORT_SYMBOL_GPL(i2s_bus_type); + +static int i2s_drv_probe(struct device *dev) +{ + const struct i2s_driver *sdrv = to_i2s_driver(dev->driver); + + return sdrv->probe(to_i2s_device(dev)); +} + +static int i2s_drv_remove(struct device *dev) +{ + const struct i2s_driver *sdrv = to_i2s_driver(dev->driver); + + return sdrv->remove(to_i2s_device(dev)); +} + +static void i2s_drv_shutdown(struct device *dev) +{ + const struct i2s_driver *sdrv = to_i2s_driver(dev->driver); + + sdrv->shutdown(to_i2s_device(dev)); +} + +/** + * i2s_register_driver - register a I2S driver + * @sdrv: the driver to register + * Context: can sleep + */ +int i2s_register_driver(struct i2s_driver *sdrv) +{ + sdrv->driver.bus = &i2s_bus_type; + if (sdrv->probe) + sdrv->driver.probe = i2s_drv_probe; + if (sdrv->remove) + sdrv->driver.remove = i2s_drv_remove; + if (sdrv->shutdown) + sdrv->driver.shutdown = i2s_drv_shutdown; + return driver_register(&sdrv->driver); +} + +EXPORT_SYMBOL_GPL(i2s_register_driver); + +/******************************************************************************/ +struct boardinfo { + struct list_head list; + unsigned n_board_info; + struct i2s_board_info board_info[0]; +}; + +static LIST_HEAD(board_list); +static DEFINE_MUTEX(board_lock); + +/* I2S devices should normally not be created by I2S device drivers; that + * would make them board-specific. Similarly with I2S master drivers. + * Device registration normally goes into like arch/.../mach.../board-YYY.c + * with other readonly (flashable) information about mainboard devices. + */ +struct i2s_device *i2s_alloc_device(struct device *device) +{ + struct i2s_device *i2s; + struct device *dev = device->parent; + + get_device(device); + i2s = kzalloc(sizeof *i2s, GFP_KERNEL); + if (!i2s) { + dev_err(dev, "cannot alloc i2s_device\n"); + return NULL; + } + + i2s->dev.parent = dev; + i2s->dev.bus = &i2s_bus_type; + i2s->dev.release = i2sdev_release; + device_initialize(&i2s->dev); + return i2s; +} + +EXPORT_SYMBOL_GPL(i2s_alloc_device); + +/** + * i2s_add_device - Add i2s_device allocated with i2s_alloc_device + * @i2s: i2s_device to register + * + * Companion function to i2s_alloc_device. Devices allocated with + * i2s_alloc_device can be added onto the i2s bus with this function. + * + * Returns 0 on success; negative errno on failure + */ +int i2s_add_device(struct i2s_device *i2s) +{ + static DEFINE_MUTEX(i2s_add_lock); + struct device *dev = i2s->dev.parent; + int status; + + dev_set_name(&i2s->dev, "%s.%u", "i2s", i2s->chip_select); + + mutex_lock(&i2s_add_lock); + + if (bus_find_device_by_name(&i2s_bus_type, NULL, dev_name(&i2s->dev)) + != NULL) { + dev_err(dev, "chipselect %d already in use\n", + i2s->chip_select); + status = -EBUSY; + goto done; + } + + /* Device may be bound to an active driver when this returns */ + status = device_add(&i2s->dev); + if (status < 0) + dev_err(dev, "can't %s %s, status %d\n", + "add", dev_name(&i2s->dev), status); + else + dev_dbg(dev, "registered child %s\n", dev_name(&i2s->dev)); + + done: + mutex_unlock(&i2s_add_lock); + return status; +} + +EXPORT_SYMBOL_GPL(i2s_add_device); + +/** + * i2s_new_device - instantiate one new I2S device + * @i2s_cont: Controller to which device is connected + * @chip: Describes the I2S device + * Context: can sleep + * + * On typical mainboards, this is purely internal; and it's not needed + * after board init creates the hard-wired devices. Some development + * platforms may not be able to use i2s_register_board_info though, and + * this is exported so that driver could add devices (which it would + * learn about out-of-band). + * + * Returns the new device, or NULL. + */ +struct i2s_device *i2s_new_device(struct i2s_controller *i2s_cont, + struct i2s_board_info *chip) +{ + struct i2s_device *proxy; + int status; + + /* NOTE: caller did any chip->bus_num checks necessary. + * + * Also, unless we change the return value convention to use + * error-or-pointer (not NULL-or-pointer), troubleshootability + * suggests syslogged diagnostics are best here (ugh). + */ + + proxy = i2s_alloc_device(&i2s_cont->dev); + if (!proxy) + return NULL; + + WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias)); + + proxy->chip_select = chip->chip_select; + strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias)); + proxy->dev.platform_data = (void *)chip->platform_data; + proxy->controller = i2s_cont; + + status = i2s_add_device(proxy); + if (status < 0) { + kfree(proxy); + return NULL; + } + + return proxy; +} + +EXPORT_SYMBOL_GPL(i2s_new_device); + +/** + * i2s_register_board_info - register I2S devices for a given board + * @info: array of chip descriptors + * @n: how many descriptors are provided + * Context: can sleep + * + * Board-specific early init code calls this (probably during arch_initcall) + * with segments of the I2S device table. Any device nodes are created later, + * after the relevant parent I2S controller (id) is defined. We keep + * this table of devices forever, so that reloading a controller driver will + * not make Linux forget about these hard-wired devices. + * + */ +int __init +i2s_register_board_info(struct i2s_board_info const *info, unsigned n) +{ + struct boardinfo *bi; + + bi = kmalloc(sizeof(*bi) + n * sizeof *info, GFP_KERNEL); + if (!bi) + return -ENOMEM; + bi->n_board_info = n; + memcpy(bi->board_info, info, n * sizeof *info); + + mutex_lock(&board_lock); + list_add_tail(&bi->list, &board_list); + mutex_unlock(&board_lock); + return 0; +} + +/** + * scan_boardinfo - Scan, creates and registered new i2s device structure. + * @i2s_cont: i2s controller structure + * Context: process + * + * It will scan the device list that may be registered statically using + * register_board_info func in arch specific directory and call + * i2s_new_device to create and registered i2s device over i2s bus. It is + * called by i2s_add_controller function. + * + * Returns void. + */ +static void scan_boardinfo(struct i2s_controller *i2s_cont) +{ + struct boardinfo *bi; + + mutex_lock(&board_lock); + list_for_each_entry(bi, &board_list, list) { + struct i2s_board_info *chip = bi->board_info; + unsigned n; + + for (n = bi->n_board_info; n > 0; n--, chip++) { + if (chip->id != i2s_cont->id) + continue; + /* NOTE: this relies on i2s_new_device to + * issue diagnostics when given bogus inputs + */ + (void)i2s_new_device(i2s_cont, chip); + } + } + mutex_unlock(&board_lock); +} + +/******************************************************************************/ +/**I2S Controller inittialization*/ +static void i2s_controller_dev_release(struct device *dev) +{ + struct i2s_controller *i2s_cont; + i2s_cont = container_of(dev, struct i2s_controller, dev); + kfree(i2s_cont); +} + +static ssize_t +show_controller_name(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2s_controller *cont = to_i2s_controller(dev); + return sprintf(buf, "%s\n", cont->name); +} + +static struct device_attribute i2s_controller_attrs[] = { + __ATTR(name, S_IRUGO, show_controller_name, NULL), + {}, +}; + +static struct class i2s_controller_class = { + .owner = THIS_MODULE, + .name = "i2s-controller", + .dev_attrs = i2s_controller_attrs, +}; + +static int i2s_register_controller(struct i2s_controller *cont) +{ + int res = 0; + mutex_init(&cont->bus_lock); + + mutex_lock(&core_lock); + + /* Add the controller to the driver core. + * If the parent pointer is not set up, + * we add this controller to the host bus. + */ + if (cont->dev.parent == NULL) { + cont->dev.parent = &platform_bus; + pr_debug("I2S controller driver [%s] forgot to specify " + "physical device\n", cont->name); + } + dev_set_name(&cont->dev, "I2Scrlr-%d", cont->id); + cont->dev.release = &i2s_controller_dev_release; + cont->dev.class = &i2s_controller_class; + res = device_register(&cont->dev); + if (res) + goto out_unlock; + + dev_dbg(&cont->dev, "controller [%s] registered\n", cont->name); + scan_boardinfo(cont); + out_unlock: + mutex_unlock(&core_lock); + return res; +} + +/** + * i2s_add_controller - declare i2s controller, use dynamic bus number + * @controller: the controller to add + * Context: can sleep + * + */ +int i2s_add_controller(struct i2s_controller *controller) +{ + return i2s_register_controller(controller); +} + +EXPORT_SYMBOL(i2s_add_controller); + +static int __unregister(struct device *dev, void *controller_dev) +{ + /* note: before about 2.6.14-rc1 this would corrupt memory: */ + if (dev != controller_dev) + i2s_unregister_device(to_i2s_device(dev)); + return 0; +} + +/** + * i2s_del_controller - unregister I2S controller + * @cont: the controller being unregistered + * Context: can sleep + * + * This unregisters an I2S controller which was previously registered + * by @i2s_add_controller. + */ +int i2s_del_controller(struct i2s_controller *cont) +{ + int res = 0; + int dummy; + mutex_lock(&core_lock); + + dummy = device_for_each_child(cont->dev.parent, &cont->dev, + __unregister); + device_unregister(&cont->dev); + mutex_unlock(&core_lock); + return res; +} + +EXPORT_SYMBOL(i2s_del_controller); + +/******************************************************************************/ +/*I2S interface apis*/ + +/** + * i2s_transfer - Main i2s transfer function. + * @i2s_cont: i2s controller structure passed by client driver. + * @message: i2s message structure contains transceive info. + * Context: process or interrupt. + * + * This API is called by client i2s driver as i2s_xfer funtion. It will handle + * main i2s transfer over i2s bus. The controller should registered its own + * functions using i2s algorithm structure. + * + * Returns error(-1) in case of failure or success(0). + */ +int i2s_transfer(struct i2s_controller *i2s_cont, struct i2s_message *message) +{ + return i2s_cont->algo->cont_transfer(i2s_cont, message); + +} + +EXPORT_SYMBOL(i2s_transfer); + +/** + * i2s_cleanup - Close the current i2s connection btw controller and client. + * @i2s_cont: i2s controller structure + * @flag: It indicates the functionality that needs to be disabled. + * Context: process + * + * This API will disable and reset the controller's configuration. Reset the + * controller so that i2s client driver can reconfigure with new configuration. + * Controller should release all the necessary resources which was acquired + * during setup. + * + * Returns error(-1) in case of failure or success(0). + */ +int i2s_cleanup(struct i2s_controller *i2s_cont, i2s_flag flag) +{ + int status = 0; + status = i2s_cont->algo->cont_cleanup(i2s_cont, flag); + if (status) + return -1; + else + return 0; +} + +EXPORT_SYMBOL(i2s_cleanup); + +/** + * i2s_setup - configures and enables the I2S controller. + * @i2s_cont: i2s controller sent by i2s device. + * @config: specifies the configuration parameters. + * + * This function configures the I2S controller with the client configuration. + * Controller was already registered on I2S bus by some master controller + * driver. + * + * Returns error(-1) in case of failure else success(0) + */ +int i2s_setup(struct i2s_controller *i2s_cont, void *config) +{ + return i2s_cont->algo->cont_setup(i2s_cont, config); +} + +EXPORT_SYMBOL(i2s_setup); + +/** + * i2s_hw_status - Get the current hw status for the i2s controller. + * @i2s_cont: i2s controller structure passed by client driver. + * Context: process or interrupt. + * + * This API is called by client i2s driver to find out current hw status. + * The controller should registered its own functions using i2s algorithm structure. + * + * Returns current hw status register. + */ +int i2s_hw_status(struct i2s_controller *i2s_cont) +{ + return i2s_cont->algo->cont_hw_status(i2s_cont); +} + +/** + * i2s_get_pointer - Get the current dma_addr_t for the i2s controller. + * @i2s_cont: i2s controller structure passed by client driver. + * @i2s_direction: Specifies TX or RX direction. + * Context: process or interrupt. + * + * This API is called by client i2s driver to return a dma_addr_t corresponding + * to the position of the DMA-controller. + * The controller should registered its own functions using i2s algorithm structure. + * + * Returns current hw status register. + */ +dma_addr_t i2s_get_pointer(struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction) +{ + return i2s_cont->algo->cont_get_pointer(i2s_cont, i2s_direction); +} + +/******************************************************************************/ + +static int __init i2s_init(void) +{ + int status; + + status = bus_register(&i2s_bus_type); + if (status < 0) + goto err0; + + status = class_register(&i2s_controller_class); + if (status < 0) + goto err1; + return 0; + + err1: + bus_unregister(&i2s_bus_type); + err0: + return status; +} + +static void __exit i2s_exit(void) +{ + class_unregister(&i2s_controller_class); + bus_unregister(&i2s_bus_type); +} + +subsys_initcall(i2s_init); +module_exit(i2s_exit); + +MODULE_AUTHOR("Sandeep Kaushik, "); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/i2s/i2s_test_protocol_driver.c b/drivers/misc/i2s/i2s_test_protocol_driver.c new file mode 100644 index 00000000000..639a28454f5 --- /dev/null +++ b/drivers/misc/i2s/i2s_test_protocol_driver.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) ST-Ericsson SA 2009 + * Author: Sandeep Kaushik, + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int i2s_drv_offset = 1; + +module_param(i2s_drv_offset, int, 1); +MODULE_PARM_DESC(i2s_drv_offset, "i2s driver to be opened)=(0/1/2/3/4/5)"); + +#define MAX_I2S_CLIENTS 6 + +struct i2sdrv_data { + spinlock_t i2s_lock; + struct i2s_device *i2s; + /* flag to show the device is closed or not */ + bool device_closed; + u32 tx_status; + u32 rx_status; +}; + +static struct i2sdrv_data *i2sdrv[MAX_I2S_CLIENTS]; + +/*API Interface */ +int i2s_testprot_drv_open(int i2s_device_num) +{ + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + spin_lock_irq(&i2sdrv[i2s_device_num]->i2s_lock); + if (!i2sdrv[i2s_device_num]->device_closed) { + spin_unlock_irq(&i2sdrv[i2s_device_num]->i2s_lock); + return -EBUSY; + } + i2sdrv[i2s_device_num]->device_closed = false; + spin_unlock_irq(&i2sdrv[i2s_device_num]->i2s_lock); + + return 0; +} +EXPORT_SYMBOL(i2s_testprot_drv_open); + +int i2s_config_default_protocol(int i2s_device_num, + struct test_prot_config *config) +{ + return __i2s_testprot_drv_configure(i2s_device_num, config, true); +} +EXPORT_SYMBOL(i2s_config_default_protocol); + +int i2s_testprot_drv_configure(int i2s_device_num, + struct test_prot_config *config) +{ + return __i2s_testprot_drv_configure(i2s_device_num, config, false); +} +EXPORT_SYMBOL(i2s_testprot_drv_configure); + +int __i2s_testprot_drv_configure(int i2s_device_num, + struct test_prot_config *config, bool use_default) +{ + int error_status = 0; + struct i2s_device *i2s_dev; + struct msp_config msp_config = { + .tx_clock_sel = TX_CLK_SEL_SRG, + .rx_clock_sel = 0x0, + .tx_frame_sync_sel = TX_SYNC_SRG_AUTO, + .rx_frame_sync_sel = 0x0, + .input_clock_freq = MSP_INPUT_FREQ_48MHZ, + .srg_clock_sel = SRG_CLK_SEL_APB, + .rx_frame_sync_pol = RX_FIFO_SYNC_HI, + .tx_frame_sync_pol = TX_FIFO_SYNC_HI, + .rx_fifo_config = RX_FIFO_ENABLE, + .tx_fifo_config = TX_FIFO_ENABLE, + .spi_clk_mode = SPI_CLK_MODE_NORMAL, + .tx_data_enable = 0x8000, + .spi_burst_mode = 0, + .loopback_enable = 0x80, + }; + + msp_config.default_protocol_desc = use_default; + + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + i2s_dev = i2sdrv[i2s_device_num]->i2s; + + if (i2sdrv[i2s_device_num]->device_closed) + return -EINVAL; + + if (!config) + return -EINVAL; + + msp_config.handler = config->handler; + msp_config.tx_callback_data = config->tx_callback_data; + msp_config.rx_callback_data = config->rx_callback_data; + msp_config.frame_freq = config->frame_freq; + msp_config.frame_size = config->frame_size; + msp_config.data_size = config->data_size; + msp_config.direction = config->direction; + msp_config.protocol = config->protocol; + msp_config.work_mode = config->work_mode; + + msp_config.def_elem_len = use_default; + + msp_config.multichannel_configured = 0; + msp_config.protocol_desc = config->protocol_desc; + + msp_config.multichannel_configured = config->multichannel_configured; + msp_config.multichannel_config.tx_multichannel_enable = + config->multichannel_config.tx_multichannel_enable; + /* Channel 1 to 3 */ + msp_config.multichannel_config.tx_channel_0_enable = + config->multichannel_config.tx_channel_0_enable; + /* Channel 33 to 64 */ + msp_config.multichannel_config.tx_channel_1_enable = + config->multichannel_config.tx_channel_1_enable; + /* Channel 65 to 96 */ + msp_config.multichannel_config.tx_channel_2_enable = + config->multichannel_config.tx_channel_2_enable; + /* Channel 97 to 128 */ + msp_config.multichannel_config.tx_channel_3_enable = + config->multichannel_config.tx_channel_3_enable; + msp_config.multichannel_config.rx_multichannel_enable = + config->multichannel_config.rx_multichannel_enable; + /* Channel 1 to 32 */ + msp_config.multichannel_config.rx_channel_0_enable = + config->multichannel_config.rx_channel_0_enable; + /* Channel 33 to 64 */ + msp_config.multichannel_config.rx_channel_1_enable = + config->multichannel_config.rx_channel_1_enable; + /* Channel 65 to 96 */ + msp_config.multichannel_config.rx_channel_2_enable = + config->multichannel_config.rx_channel_2_enable; + /* Channel 97 to 128 */ + msp_config.multichannel_config.rx_channel_3_enable = + config->multichannel_config.rx_channel_3_enable; + msp_config.multichannel_config.rx_comparison_enable_mode = + config->multichannel_config.rx_comparison_enable_mode; + msp_config.multichannel_config.comparison_value = + config->multichannel_config.comparison_value; + msp_config.multichannel_config.comparison_mask = + config->multichannel_config.comparison_mask; + + error_status = + i2s_setup(i2s_dev->controller, &msp_config); + if (error_status < 0) + dev_err(&i2s_dev->dev, "error in msp enable, error_status is %d\n", + error_status); + + return error_status; + +} + +int i2s_testprot_drv_transfer(int i2s_device_num, + void *txdata, size_t txbytes, void *rxdata, size_t rxbytes, + enum i2s_transfer_mode_t transfer_mode) +{ + int bytes_transreceive; + struct i2s_device *i2s_dev; + struct i2s_message message = {}; + + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + i2s_dev = i2sdrv[i2s_device_num]->i2s; + + if (i2sdrv[i2s_device_num]->device_closed) { + dev_info(&i2s_dev->dev, "msp device not opened yet\n"); + return -EINVAL; + } + + message.i2s_transfer_mode = transfer_mode; + message.i2s_direction = I2S_DIRECTION_BOTH; + message.txbytes = txbytes; + message.txdata = txdata; + message.rxbytes = rxbytes; + message.rxdata = rxdata; + message.dma_flag = 1; + bytes_transreceive = i2s_transfer(i2s_dev->controller, &message); + dev_dbg(&i2s_dev->dev, "bytes transreceived %d\n", bytes_transreceive); + + return bytes_transreceive; +} +EXPORT_SYMBOL(i2s_testprot_drv_transfer); + +int i2s_testprot_drv_close(int i2s_device_num) +{ + int status; + struct i2s_device *i2s_dev; + + if (!i2sdrv[i2s_device_num]) + return -EINVAL; + + i2s_dev = i2sdrv[i2s_device_num]->i2s; + + if (i2sdrv[i2s_device_num]->device_closed) + return -EINVAL; + + status = i2s_cleanup(i2s_dev->controller, DISABLE_ALL); + if (status) + return status; + + /* Mark the device as closed */ + i2sdrv[i2s_device_num]->device_closed = true; + + return 0; +} +EXPORT_SYMBOL(i2s_testprot_drv_close); + +static int i2sdrv_probe(struct i2s_device *i2s) +{ + int status = 0; + + /* Allocate driver data */ + if (!try_module_get(i2s->controller->dev.parent->driver->owner)) + return -ENOENT; + + i2sdrv[i2s->chip_select] = kzalloc(sizeof(*i2sdrv[i2s->chip_select]), + GFP_KERNEL); + + if (!i2sdrv[i2s->chip_select]) + return -ENOMEM; + + /* Initialize the driver data */ + i2sdrv[i2s->chip_select]->i2s = i2s; + i2sdrv[i2s->chip_select]->device_closed = true; + i2sdrv[i2s->chip_select]->tx_status = 0; + i2sdrv[i2s->chip_select]->rx_status = 0; + spin_lock_init(&i2sdrv[i2s->chip_select]->i2s_lock); + + i2s_set_drvdata(i2s, (void *)i2sdrv[i2s->chip_select]); + return status; +} + +static int i2sdrv_remove(struct i2s_device *i2s) +{ + spin_lock_irq(&i2sdrv[i2s->chip_select]->i2s_lock); + i2sdrv[i2s->chip_select]->i2s = NULL; + i2s_set_drvdata(i2s, NULL); + module_put(i2s->controller->dev.parent->driver->owner); + spin_unlock_irq(&i2sdrv[i2s->chip_select]->i2s_lock); + + kfree(i2sdrv[i2s->chip_select]); + + return 0; +} + +static const struct i2s_device_id i2s_test_prot_id_table[] = { + { "i2s_device.0", 0, 0 }, + { "i2s_device.1", 0, 0 }, + { "i2s_device.2", 0, 0 }, + { "i2s_device.3", 0, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2s, i2s_test_prot_id_table); + +static struct i2s_driver i2sdrv_i2s = { + .driver = { + .name = "i2s_test_protocol_driver", + .owner = THIS_MODULE, + }, + .probe = i2sdrv_probe, + .remove = __devexit_p(i2sdrv_remove), + .id_table = i2s_test_prot_id_table, + + /* + * NOTE: suspend/resume methods are not necessary here. + */ +}; + +static int __init i2sdrv_init(void) +{ + int status; + + status = i2s_register_driver(&i2sdrv_i2s); + if (status < 0) + printk(KERN_ERR "Unable to register i2s driver\n"); + + return status; +} +module_init(i2sdrv_init); + +static void __exit i2sdrv_exit(void) +{ + i2s_unregister_driver(&i2sdrv_i2s); +} +module_exit(i2sdrv_exit); + +MODULE_AUTHOR("Sandeep Kaushik, "); +MODULE_DESCRIPTION("Test Driver module I2S device interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/i2s/msp_i2s.c b/drivers/misc/i2s/msp_i2s.c new file mode 100644 index 00000000000..e8455e651b7 --- /dev/null +++ b/drivers/misc/i2s/msp_i2s.c @@ -0,0 +1,2045 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct regulator *msp_vape_supply; + +#define STM_MSP_NAME "STM_MSP" +#define MSP_NAME "msp" +#define DRIVER_DEBUG_PFX "MSP" +#define DRIVER_DEBUG CONFIG_STM_MSP_DEBUG +#define DRIVER_DBG "MSP" +#define NMDK_DBG /* message level */ + +extern struct driver_debug_st DBG_ST; + /* Protocol desciptors */ +static const struct msp_protocol_desc protocol_desc_tab[] = { + I2S_PROTOCOL_DESC, + PCM_PROTOCOL_DESC, + PCM_COMPAND_PROTOCOL_DESC, + AC97_PROTOCOL_DESC, + SPI_MASTER_PROTOCOL_DESC, + SPI_SLAVE_PROTOCOL_DESC, +}; + +/* Local static functions */ +static int msp_dma_xfer(struct msp *msp, struct i2s_message *msg); +static int msp_polling_xfer(struct msp *msp, struct i2s_message *msg); +static int msp_interrupt_xfer(struct msp *msp, struct i2s_message *msg); +static int msp_start_dma(struct msp *msp, int transmit, dma_addr_t data, + size_t bytes); +static int configure_protocol(struct msp *msp, + struct msp_config *config); +static int configure_clock(struct msp *msp, + struct msp_config *config); +static int configure_multichannel(struct msp *msp, + struct msp_config *config); +static int stm_msp_configure_enable(struct i2s_controller *i2s_cont, + void *configuration); +static int stm_msp_transceive_data(struct i2s_controller *i2s_cont, + struct i2s_message *message); + +static int stm_msp_disable(struct msp *msp, int direction, + i2s_flag flag); +static int stm_msp_close(struct i2s_controller *i2s_cont, i2s_flag flag); +static int stm_msp_hw_status(struct i2s_controller *i2s_cont); +static dma_addr_t stm_msp_get_pointer(struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction); + +#define I2S_DEVICE "i2s_device" +static struct i2s_algorithm i2s_algo = { + .cont_setup = stm_msp_configure_enable, + .cont_transfer = stm_msp_transceive_data, + .cont_cleanup = stm_msp_close, + .cont_hw_status = stm_msp_hw_status, + .cont_get_pointer = stm_msp_get_pointer, +}; + +/** + * stm_msp_write - writel a value to specified register + * @value: value + * @reg: pointer to register' address + * Context: atomic(can be both process and interrupt) + * Returns void. + */ +static inline void stm_msp_write(u32 value, void __iomem *reg) +{ + writel(value, reg); +} + +/** + * stm_msp_read - readl a value to specified register + * @reg: pointer to register' address + * Context: atomic(can be both process and interrupt) + * Returns u32 register's value. + */ +static inline u32 stm_msp_read(void __iomem *reg) +{ + return readl(reg); +} + +static void u8_msp_read(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->rx_offset < message->rxbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + RX_FIFO_EMPTY)) { + message->rx_offset += 1; + *(u8 *) message->rxdata = + (u8) stm_msp_read(xfer_data->msp->registers + MSP_DR); + message->rxdata += 1; + } +} + +static void u16_msp_read(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->rx_offset < message->rxbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + RX_FIFO_EMPTY)) { + message->rx_offset += 2; + *(u16 *) message->rxdata = + (u16) stm_msp_read(xfer_data->msp->registers + MSP_DR); + message->rxdata += 2; + } +} + +/** + * u32_msp_read - Msp 32bit read function. + * @xfer_data: transfer data structure. + * + * It reads 32bit data from msp receive fifo until it gets empty. + * + * Returns void. + */ +static void u32_msp_read(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->rx_offset < message->rxbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + RX_FIFO_EMPTY)) { + *(u32 *) message->rxdata = + (u32) stm_msp_read(xfer_data->msp->registers + MSP_DR); + message->rx_offset += 4; + message->rxdata += 4; + } +} +static void u8_msp_write(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->tx_offset < message->txbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + TX_FIFO_FULL)) { + message->tx_offset += 1; + stm_msp_write(*(u8 *) message->txdata, + xfer_data->msp->registers + MSP_DR); + message->txdata += 1; + } +} + +static void u16_msp_write(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->tx_offset < message->txbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + TX_FIFO_FULL)) { + message->tx_offset += 2; + stm_msp_write(*(u16 *) message->txdata, + xfer_data->msp->registers + MSP_DR); + message->txdata += 2; + } +} + +/** + * u32_msp_write - Msp 32bit write function. + * @xfer_data: transfer data structure. + * + * It writes 32bit data to msp transmit fifo until it gets full. + * + * Returns void. + */ +static void u32_msp_write(struct trans_data *xfer_data) +{ + struct i2s_message *message = &xfer_data->message; + while ((message->tx_offset < message->txbytes) && + !((stm_msp_read(xfer_data->msp->registers + MSP_FLR)) & + TX_FIFO_FULL)) { + message->tx_offset += 4; + stm_msp_write(*(u32 *) message->txdata, + xfer_data->msp->registers + MSP_DR); + message->txdata += 4; + } +} + +/** + * set_transmit_protocol_descriptor - Set the Transmit Configuration register. + * @msp: main msp controller structure. + * @protocol_desc: pointer to protocol descriptor structure. + * @data_size: Run time configurable element length. + * + * It will setup transmit configuration register of msp. + * Various values related to a particular protocol can be set like, elemnet + * length, frame length, endianess etc. + * + * Returns void. + */ +static void set_transmit_protocol_descriptor(struct msp *msp, + struct msp_protocol_desc + *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->tx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->tx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->tx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->tx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= + MSP_P1_ELEM_LEN_BITS(protocol_desc->tx_element_length_1); + temp_reg |= + MSP_P2_ELEM_LEN_BITS(protocol_desc->tx_element_length_2); + if (protocol_desc->tx_element_length_1 == + protocol_desc->tx_element_length_2) { + msp->actual_data_size = + protocol_desc->tx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->tx_data_delay); + temp_reg |= + MSP_SET_ENDIANNES_BIT(protocol_desc->tx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->tx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->tx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->compression_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + stm_msp_write(temp_reg, msp->registers + MSP_TCF); +} + +/** + * set_receive_protocol_descriptor - Set the Receive Configuration register. + * @msp: main msp controller structure. + * @protocol_desc: pointer to protocol descriptor structure. + * @data_size: Run time configurable element length. + * + * It will setup receive configuration register of msp. + * Various values related to a particular protocol can be set like, elemnet + * length, frame length, endianess etc. + * + * Returns void. + */ +static void set_receive_protocol_descriptor(struct msp *msp, + struct msp_protocol_desc + *protocol_desc, + enum msp_data_size + data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->rx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->rx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->rx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->rx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= + MSP_P1_ELEM_LEN_BITS(protocol_desc->rx_element_length_1); + temp_reg |= + MSP_P2_ELEM_LEN_BITS(protocol_desc->rx_element_length_2); + if (protocol_desc->rx_element_length_1 == + protocol_desc->rx_element_length_2) { + msp->actual_data_size = + protocol_desc->rx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->rx_data_delay); + temp_reg |= + MSP_SET_ENDIANNES_BIT(protocol_desc->rx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->rx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->rx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->expansion_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + stm_msp_write(temp_reg, msp->registers + MSP_RCF); + +} + +/** + * configure_protocol - Configures transmit and receive protocol. + * @msp: main msp controller structure. + * @config: configuration structure passed by client driver + * + * This will configure transmit and receive protocol decriptors. + * + * Returns error(-1) on failure else success(0). + */ +static int configure_protocol(struct msp *msp, + struct msp_config *config) +{ + int direction; + struct msp_protocol_desc *protocol_desc; + enum msp_data_size data_size; + u32 temp_reg = 0; + + data_size = config->data_size; + msp->def_elem_len = config->def_elem_len; + direction = config->direction; + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + printk(KERN_ERR + "invalid protocol in configure_protocol()\n"); + return -EINVAL; + } + protocol_desc = + (struct msp_protocol_desc *)&protocol_desc_tab[config-> + protocol]; + } else { + protocol_desc = + (struct msp_protocol_desc *)&config->protocol_desc; + } + + if (data_size < MSP_DATA_BITS_DEFAULT + || data_size > MSP_DATA_BITS_32) { + printk(KERN_ERR + "invalid data size requested in configure_protocol()\n"); + return -EINVAL; + } + + switch (direction) { + case MSP_TRANSMIT_MODE: + set_transmit_protocol_descriptor(msp, protocol_desc, data_size); + break; + case MSP_RECEIVE_MODE: + set_receive_protocol_descriptor(msp, protocol_desc, data_size); + break; + case MSP_BOTH_T_R_MODE: + set_transmit_protocol_descriptor(msp, protocol_desc, data_size); + set_receive_protocol_descriptor(msp, protocol_desc, data_size); + break; + default: + printk(KERN_ERR "Invalid direction given\n"); + return -EINVAL; + } + /* The below code is needed for both Rx and Tx path can't separate + * them. + */ + temp_reg = stm_msp_read(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; + temp_reg |= MSP_TX_CLKPOL_BIT(protocol_desc->tx_clock_pol); + stm_msp_write(temp_reg, msp->registers + MSP_GCR); + temp_reg = stm_msp_read(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; + temp_reg |= MSP_RX_CLKPOL_BIT(protocol_desc->rx_clock_pol); + stm_msp_write(temp_reg, msp->registers + MSP_GCR); + + return 0; +} + +/** + * configure_clock - Set clock in sample rate generator. + * @msp: main msp controller structure. + * @config: configuration structure passed by client driver + * + * This will set the frame width and period. Also enable sample rate generator + * + * Returns error(-1) on failure else success(0). + */ +static int configure_clock(struct msp *msp, + struct msp_config *config) +{ + + u32 dummy; + u32 frame_per = 0; + u32 sck_div = 0; + u32 frame_width = 0; + u32 temp_reg = 0; + u32 bit_clock = 0; + struct msp_protocol_desc *protocol_desc = NULL; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~(SRG_ENABLE))), msp->registers + MSP_GCR); + + if (config->default_protocol_desc) { + protocol_desc = + (struct msp_protocol_desc *)&protocol_desc_tab[config-> + protocol]; + } else { + protocol_desc = + (struct msp_protocol_desc *)&config->protocol_desc; + } + + switch (config->protocol) { + case MSP_PCM_PROTOCOL: + case MSP_PCM_COMPAND_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = + config->input_clock_freq / (config->frame_freq * + (protocol_desc-> + total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_I2S_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = + config->input_clock_freq / (config->frame_freq * + (protocol_desc-> + total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + + break; + case MSP_AC97_PROTOCOL: + /* Not supported */ + printk(KERN_WARNING "AC97 protocol not supported\n"); + return -ENOSYS; + default: + printk(KERN_ERR "Invalid mode attempted for setting clocks\n"); + return -EINVAL; + } + + temp_reg = (sck_div - 1) & SCK_DIV_MASK; + temp_reg |= FRAME_WIDTH_BITS(frame_width); + temp_reg |= FRAME_PERIOD_BITS(frame_per); + stm_msp_write(temp_reg, msp->registers + MSP_SRG); + + /* Input clock frequency value configured is in MHz/1000 */ + bit_clock = (config->input_clock_freq * 1000)/(sck_div + 1); + + /* If the bit clock is higher than 19.2MHz, Vape should be run in 100% OPP */ + if (bit_clock > 19200000) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 100); + msp->vape_opp_constraint = 1; + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 50); + msp->vape_opp_constraint = 0; + } + + /* Wait a bit */ + dummy = ((stm_msp_read(msp->registers + MSP_SRG)) >> FRWID_SHIFT) & 0x0000003F; + + /* Enable clock */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | ((SRG_ENABLE))), + msp->registers + MSP_GCR); + + /* Another wait */ + dummy = + ((stm_msp_read(msp->registers + MSP_SRG)) >> FRWID_SHIFT) & + 0x0000003F; + return 0; +} + +/** + * configure_multichannel - Enable multichannel support for transmit & receive. + * @msp: main msp controller structure. + * @config: configuration structure passed by client driver + * + * This will enable multichannel support for transmit and receive. + * It will set Receive comparator also if configured. + * + * Returns error(-1) on failure else success(0). + */ +static int configure_multichannel(struct msp *msp, + struct msp_config *config) +{ + struct msp_protocol_desc *protocol_desc; + struct msp_multichannel_config *mult_config; + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + printk(KERN_ERR + "invalid protocol in configure_protocol()\n"); + return -EINVAL; + } + protocol_desc = + (struct msp_protocol_desc *)&protocol_desc_tab[config-> + protocol]; + } else { + protocol_desc = + (struct msp_protocol_desc *)&config->protocol_desc; + } + mult_config = &config->multichannel_config; + if (true == mult_config->tx_multichannel_enable) { + if (MSP_SINGLE_PHASE == protocol_desc->tx_phase_mode) { + stm_msp_write((stm_msp_read(msp->registers + MSP_MCR) | + ((mult_config-> + tx_multichannel_enable << TMCEN_BIT) & + (0x0000020))), + msp->registers + MSP_MCR); + stm_msp_write(mult_config->tx_channel_0_enable, + msp->registers + MSP_TCE0); + stm_msp_write(mult_config->tx_channel_1_enable, + msp->registers + MSP_TCE1); + stm_msp_write(mult_config->tx_channel_2_enable, + msp->registers + MSP_TCE2); + stm_msp_write(mult_config->tx_channel_3_enable, + msp->registers + MSP_TCE3); + } else { + printk(KERN_ERR "Not in authorised mode\n"); + return -1; + } + } + if (true == mult_config->rx_multichannel_enable) { + if (MSP_SINGLE_PHASE == protocol_desc->rx_phase_mode) { + stm_msp_write((stm_msp_read(msp->registers + MSP_MCR) | + ((mult_config-> + rx_multichannel_enable << RMCEN_BIT) & + (0x0000001))), + msp->registers + MSP_MCR); + stm_msp_write(mult_config->rx_channel_0_enable, + msp->registers + MSP_RCE0); + stm_msp_write(mult_config->rx_channel_1_enable, + msp->registers + MSP_RCE1); + stm_msp_write(mult_config->rx_channel_2_enable, + msp->registers + MSP_RCE2); + stm_msp_write(mult_config->rx_channel_3_enable, + msp->registers + MSP_RCE3); + } else { + printk(KERN_ERR "Not in authorised mode\n"); + return -1; + } + if (mult_config->rx_comparison_enable_mode) { + stm_msp_write((stm_msp_read(msp->registers + MSP_MCR) | + ((mult_config-> + rx_comparison_enable_mode << RCMPM_BIT) + & (0x0000018))), + msp->registers + MSP_MCR); + + stm_msp_write(mult_config->comparison_mask, + msp->registers + MSP_RCM); + stm_msp_write(mult_config->comparison_value, + msp->registers + MSP_RCV); + + } + } + return 0; + +} + +/** + * configure_dma - configure dma channel for transmit or receive. + * @msp: msp structure + * @config: configuration structure. + * Context: process + * + * It will configure dma channels and request them in Logical mode for both + * transmit and recevie modes.It also register the respective callback handlers + * for DMA. + * + * Returns void. + */ +void configure_dma(struct msp *msp, struct msp_config *config) +{ + struct stedma40_chan_cfg *rx_dma_info = msp->dma_cfg_rx; + struct stedma40_chan_cfg *tx_dma_info = msp->dma_cfg_tx; + dma_cap_mask_t mask; + + if (config->direction == MSP_TRANSMIT_MODE + || config->direction == MSP_BOTH_T_R_MODE) { + + if (msp->tx_pipeid != NULL) { + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + + if (config->data_size == MSP_DATA_BITS_32) + tx_dma_info->src_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + tx_dma_info->src_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + tx_dma_info->src_info.data_width + = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + if (config->data_size == MSP_DATA_BITS_32) + tx_dma_info->dst_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + tx_dma_info->dst_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + tx_dma_info->dst_info.data_width + = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + msp->tx_pipeid = dma_request_channel(mask, stedma40_filter, + tx_dma_info); + } + if (config->direction == MSP_RECEIVE_MODE + || config->direction == MSP_BOTH_T_R_MODE) { + + if (msp->rx_pipeid != NULL) { + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + + if (config->data_size == MSP_DATA_BITS_32) + rx_dma_info->src_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + rx_dma_info->src_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + rx_dma_info->src_info.data_width = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + if (config->data_size == MSP_DATA_BITS_32) + rx_dma_info->dst_info.data_width = STEDMA40_WORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_16) + rx_dma_info->dst_info.data_width + = STEDMA40_HALFWORD_WIDTH; + else if (config->data_size == MSP_DATA_BITS_8) + rx_dma_info->dst_info.data_width = STEDMA40_BYTE_WIDTH; + else + printk(KERN_ERR "Wrong data size\n"); + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + msp->rx_pipeid = dma_request_channel(mask, stedma40_filter, + rx_dma_info); + } + +} + +/** + * msp_enable - Setup the msp configuration. + * @msp: msp data contains main msp structure. + * @config: configuration structure sent by i2s client driver. + * Context: process + * + * Main msp configuring functions to configure msp in accordance with msp + * protocol descriptor, configuring msp clock,setup transfer mode selected by + * user like DMA, interrupt or polling and in the end enable RX and Tx path. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_enable(struct msp *msp, struct msp_config *config) +{ + int status = 0; + int state; + + /* Check msp state whether in RUN or CONFIGURED Mode */ + state = msp->msp_state; + if (state == MSP_STATE_IDLE) { + if (msp->plat_init) { + status = msp->plat_init(); + if (status) { + printk(KERN_ERR "Error in msp_i2s_init," + " status is %d\n", status); + return status; + } + } + } + + /* Configure msp with protocol dependent settings */ + configure_protocol(msp, config); + configure_clock(msp, config); + if (config->multichannel_configured == 1) { + status = configure_multichannel(msp, config); + if (status) + printk(KERN_ERR "multichannel can't be configured\n"); + } + msp->work_mode = config->work_mode; + + if (msp->work_mode == MSP_DMA_MODE && !msp->dma_cfg_rx) { + switch (config->direction) { + case MSP_RECEIVE_MODE: + case MSP_BOTH_T_R_MODE: + dev_err(&msp->i2s_cont->dev, "RX DMA not available"); + return -EINVAL; + } + } + + if (msp->work_mode == MSP_DMA_MODE && !msp->dma_cfg_tx) { + switch (config->direction) { + case MSP_TRANSMIT_MODE: + case MSP_BOTH_T_R_MODE: + dev_err(&msp->i2s_cont->dev, "TX DMA not available"); + return -EINVAL; + } + } + + switch (config->direction) { + case MSP_TRANSMIT_MODE: + /*Currently they are ignored + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) | + TRANSMIT_UNDERRUN_ERR_INT | + TRANSMIT_FRAME_SYNC_ERR_INT), + msp->registers + MSP_IMSC); */ + if (config->work_mode == MSP_DMA_MODE) { + stm_msp_write(stm_msp_read(msp->registers + MSP_DMACR) | + TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + configure_dma(msp, config); + } + if (config->work_mode == MSP_POLLING_MODE) { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), msp->registers + MSP_GCR); + } + if (msp->work_mode != MSP_DMA_MODE) { + switch (msp->actual_data_size) { + case MSP_DATA_BITS_8: + msp->write = u8_msp_write; + break; + case MSP_DATA_BITS_10: + case MSP_DATA_BITS_12: + case MSP_DATA_BITS_14: + case MSP_DATA_BITS_16: + msp->write = u16_msp_write; + break; + case MSP_DATA_BITS_20: + case MSP_DATA_BITS_24: + case MSP_DATA_BITS_32: + default: + msp->write = u32_msp_write; + break; + } + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + msp->xfer_data.msp = msp; + } + break; + case MSP_RECEIVE_MODE: + /*Currently they are ignored + stm_msp_write(stm_msp_read(msp->registers + MSP_IMSC) | + RECEIVE_OVERRUN_ERROR_INT | RECEIVE_FRAME_SYNC_ERR_INT, + msp->registers + MSP_IMSC); */ + if (config->work_mode == MSP_DMA_MODE) { + stm_msp_write(stm_msp_read(msp->registers + MSP_DMACR) | + RX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + + configure_dma(msp, config); + } + if (config->work_mode == MSP_POLLING_MODE) { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), msp->registers + MSP_GCR); + } + if (msp->work_mode != MSP_DMA_MODE) { + switch (msp->actual_data_size) { + case MSP_DATA_BITS_8: + msp->read = u8_msp_read; + break; + case MSP_DATA_BITS_10: + case MSP_DATA_BITS_12: + case MSP_DATA_BITS_14: + case MSP_DATA_BITS_16: + msp->read = u16_msp_read; + break; + case MSP_DATA_BITS_20: + case MSP_DATA_BITS_24: + case MSP_DATA_BITS_32: + default: + msp->read = u32_msp_read; + break; + } + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + msp->xfer_data.msp = msp; + } + + break; + case MSP_BOTH_T_R_MODE: + /*Currently they are ignored + stm_msp_write(stm_msp_read(msp->registers + MSP_IMSC) | + RECEIVE_OVERRUN_ERROR_INT | RECEIVE_FRAME_SYNC_ERR_INT | + TRANSMIT_UNDERRUN_ERR_INT | TRANSMIT_FRAME_SYNC_ERR_INT , + msp->registers + MSP_IMSC); */ + if (config->work_mode == MSP_DMA_MODE) { + stm_msp_write(stm_msp_read(msp->registers + MSP_DMACR) | + RX_DMA_ENABLE | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + + configure_dma(msp, config); + } + if (config->work_mode == MSP_POLLING_MODE) { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), msp->registers + MSP_GCR); + } + if (msp->work_mode != MSP_DMA_MODE) { + switch (msp->actual_data_size) { + case MSP_DATA_BITS_8: + msp->read = u8_msp_read; + msp->write = u8_msp_write; + break; + case MSP_DATA_BITS_10: + case MSP_DATA_BITS_12: + case MSP_DATA_BITS_14: + case MSP_DATA_BITS_16: + msp->read = u16_msp_read; + msp->write = u16_msp_write; + break; + case MSP_DATA_BITS_20: + case MSP_DATA_BITS_24: + case MSP_DATA_BITS_32: + default: + msp->read = u32_msp_read; + msp->write = u32_msp_write; + break; + } + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = + config->tx_callback_data; + msp->xfer_data.rx_callback_data = + config->rx_callback_data; + msp->xfer_data.msp = msp; + } + + break; + default: + printk(KERN_ERR "Invalid direction parameter\n"); + if (msp->plat_exit) + msp->plat_exit(); + status = -EINVAL; + return status; + } + + switch (config->work_mode) { + case MSP_DMA_MODE: + msp->transfer = msp_dma_xfer; + break; + case MSP_POLLING_MODE: + msp->transfer = msp_polling_xfer; + break; + case MSP_INTERRUPT_MODE: + msp->transfer = msp_interrupt_xfer; + break; + default: + msp->transfer = NULL; + } + + stm_msp_write(config->iodelay, msp->registers + MSP_IODLY); + + /* enable frame generation logic */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (FRAME_GEN_ENABLE)), msp->registers + MSP_GCR); + + return status; +} + +/** + * flush_rx_fifo - Flush Rx fifo MSP controller. + * @msp: msp structure. + * + * This function flush the rx fifo of msp controller. + * + * Returns error(-1) in case of failure else success(0) + */ +static void flush_rx_fifo(struct msp *msp) +{ + u32 dummy = 0; + u32 limit = 32; + u32 cur = stm_msp_read(msp->registers + MSP_GCR); + stm_msp_write(cur | RX_ENABLE, msp->registers + MSP_GCR); + while (!(stm_msp_read(msp->registers + MSP_FLR) & RX_FIFO_EMPTY) + && limit--) { + dummy = stm_msp_read(msp->registers + MSP_DR); + } + stm_msp_write(cur, msp->registers + MSP_GCR); +} + +/** + * flush_tx_fifo - Flush Tx fifo MSP controller. + * @msp: msp structure. + * + * This function flush the tx fifo using test intergration register to read data + * from tx fifo directly. + * + * Returns error(-1) in case of failure else success(0) + */ +static void flush_tx_fifo(struct msp *msp) +{ + u32 dummy = 0; + u32 limit = 32; + u32 cur = stm_msp_read(msp->registers + MSP_GCR); + stm_msp_write(cur | TX_ENABLE, msp->registers + MSP_GCR); + stm_msp_write(0x3, msp->registers + MSP_ITCR); + while (!(stm_msp_read(msp->registers + MSP_FLR) & TX_FIFO_EMPTY) + && limit--) { + dummy = stm_msp_read(msp->registers + MSP_TSTDR); + } + stm_msp_write(0x0, msp->registers + MSP_ITCR); + stm_msp_write(cur, msp->registers + MSP_GCR); +} + +/** + * stm_msp_configure_enable - configures and enables the MSP controller. + * @i2s_cont: i2s controller sent by i2s device. + * @configuration: specifies the configuration parameters. + * + * This function configures the msp controller with the client configuration. + * + * Returns error(-1) in case of failure else success(0) + */ +static int stm_msp_configure_enable(struct i2s_controller *i2s_cont, + void *configuration) +{ + u32 old_reg; + u32 new_reg; + u32 mask; + int res; + struct msp_config *config = + (struct msp_config *)configuration; + struct msp *msp = (struct msp *)i2s_cont->data; + + if (in_interrupt()) { + printk(KERN_ERR + "can't call configure_enable in interrupt context\n"); + return -1; + } + + /* Two simultanous configuring msp is avoidable */ + down(&msp->lock); + switch (msp->users) { + case 0: + res = regulator_enable(msp_vape_supply); + if (res != 0) { + dev_err(&msp->i2s_cont->dev, + "Failed to enable regulator\n"); + up(&msp->lock); + return res; + } + + clk_enable(msp->clk); + msp->direction = config->direction; + break; + case 1: + if (msp->direction == MSP_BOTH_T_R_MODE || + config->direction == msp->direction || + config->direction == MSP_BOTH_T_R_MODE) { + dev_notice(&i2s_cont->dev, "%s: MSP in use in the " + "desired direction.\n", __func__); + up(&msp->lock); + return -EBUSY; + } + msp->direction = MSP_BOTH_T_R_MODE; + break; + default: + dev_notice(&i2s_cont->dev, "%s: MSP in use in both " + "directions.\n", __func__); + up(&msp->lock); + return -EBUSY; + } + msp->users++; + + /* First do the global config register */ + mask = + RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FRAME_SYNC_MASK | + TX_FRAME_SYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | + LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + + new_reg = + (config->tx_clock_sel | config->rx_clock_sel | config-> + rx_frame_sync_pol | config->tx_frame_sync_pol | config-> + rx_frame_sync_sel | config->tx_frame_sync_sel | config-> + rx_fifo_config | config->tx_fifo_config | config-> + srg_clock_sel | config->loopback_enable | config->tx_data_enable); + + old_reg = stm_msp_read(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + stm_msp_write(new_reg, msp->registers + MSP_GCR); + + if (msp_enable(msp, config) != 0) { + printk(KERN_ERR "error enabling MSP\n"); + return -EBUSY; + } + if (config->loopback_enable & 0x80) + msp->loopback_enable = 1; + /*Sometimes FIFO doesn't gets empty hence limit is provided */ + flush_tx_fifo(msp); + /*This has been added in order to fix fifo flush problem + When last xfer occurs some data remains in fifo. In order to + flush that data delay is needed */ + msleep(10); + /* wait for fifo to flush */ + flush_rx_fifo(msp); + + /* RX_BUSY take a while to clear */ + msleep(10); + + msp->msp_state = MSP_STATE_CONFIGURED; + up(&msp->lock); + return 0; +} + +static int msp_start_dma(struct msp *msp, int transmit, dma_addr_t data, + size_t bytes) +{ + struct dma_async_tx_descriptor *desc; + struct scatterlist sg; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(data)), bytes, + offset_in_page(data)); + sg_dma_address(&sg) = data; + sg_dma_len(&sg) = bytes; + + if (transmit) { + if (!msp->tx_pipeid) + return -EINVAL; + + desc = msp->tx_pipeid->device-> + device_prep_slave_sg(msp->tx_pipeid, + &sg, 1, DMA_TO_DEVICE, + DMA_PREP_INTERRUPT + | DMA_CTRL_ACK); + if (!desc) + return -ENOMEM; + + desc->callback = msp->xfer_data.tx_handler; + desc->callback_param = msp->xfer_data.tx_callback_data; + desc->tx_submit(desc); + dma_async_issue_pending(msp->tx_pipeid); + } else { + if (!msp->rx_pipeid) + return -EINVAL; + + desc = msp->rx_pipeid->device-> + device_prep_slave_sg(msp->rx_pipeid, + &sg, 1, DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT + | DMA_CTRL_ACK); + if (!desc) + return -EBUSY; + + desc->callback = msp->xfer_data.rx_handler; + desc->callback_param = msp->xfer_data.rx_callback_data; + desc->tx_submit(desc); + dma_async_issue_pending(msp->rx_pipeid); + } + + return 0; +} + +static int msp_single_dma_tx(struct msp *msp, dma_addr_t data, size_t bytes) +{ + int status; + status = msp_start_dma(msp, 1, data, bytes); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | (TX_ENABLE)), + msp->registers + MSP_GCR); + return status; +} + +static int msp_single_dma_rx(struct msp *msp, dma_addr_t data, size_t bytes) +{ + int status; + status = msp_start_dma(msp, 0, data, bytes); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | (RX_ENABLE)), + msp->registers + MSP_GCR); + return status; +} + +static void msp_cyclic_dma_start(struct msp *msp, + struct scatterlist *sg, + int sg_len, + enum dma_data_direction direction) +{ + struct stedma40_cyclic_desc *cdesc; + int ret; + struct dma_chan *pipeid = (direction == DMA_TO_DEVICE) ? + msp->tx_pipeid : + msp->rx_pipeid; + + cdesc = stedma40_cyclic_prep_sg(pipeid, + sg, + sg_len, + direction, + DMA_PREP_INTERRUPT); + if (IS_ERR(cdesc)) { + pr_err("%s: Error: stedma40_cyclic_prep_sg failed (%ld)!\n", + __func__, + PTR_ERR(cdesc)); + return; + } + + cdesc->period_callback = (direction == DMA_TO_DEVICE) ? + msp->xfer_data.tx_handler : + msp->xfer_data.rx_handler; + cdesc->period_callback_param = (direction == DMA_TO_DEVICE) ? + msp->xfer_data.tx_callback_data : + msp->xfer_data.rx_callback_data; + + ret = stedma40_cyclic_start(pipeid); + if (ret) { + pr_err("%s: stedma40_cyclic_start failed (%d)!\n", __func__, ret); + goto free; + } + + msp->infinite = true; + + return; + +free: + stedma40_cyclic_free(pipeid); +} + +/* Legacy function. Used by HATS driver. */ +static void msp_loopback_inf_start_dma(struct msp *msp, + dma_addr_t data, + size_t bytes) +{ + struct stedma40_cyclic_desc *rxcdesc; + struct stedma40_cyclic_desc *txcdesc; + struct scatterlist rxsg[2]; + struct scatterlist txsg[2]; + size_t len = bytes >> 1; + int ret; + + sg_init_table(rxsg, ARRAY_SIZE(rxsg)); + sg_init_table(txsg, ARRAY_SIZE(txsg)); + + sg_dma_len(&rxsg[0]) = len; + sg_dma_len(&rxsg[1]) = len; + sg_dma_len(&txsg[0]) = len; + sg_dma_len(&txsg[1]) = len; + + sg_dma_address(&rxsg[0]) = data; + sg_dma_address(&rxsg[1]) = data + len; + + sg_dma_address(&txsg[0]) = data + len; + sg_dma_address(&txsg[1]) = data; + + rxcdesc = stedma40_cyclic_prep_sg(msp->rx_pipeid, + rxsg, ARRAY_SIZE(rxsg), + DMA_FROM_DEVICE, 0); + if (IS_ERR(rxcdesc)) + return; + + txcdesc = stedma40_cyclic_prep_sg(msp->tx_pipeid, + txsg, ARRAY_SIZE(txsg), + DMA_TO_DEVICE, 0); + if (IS_ERR(txcdesc)) + goto free_rx; + + ret = stedma40_cyclic_start(msp->rx_pipeid); + if (ret) + goto free_tx; + + ret = stedma40_cyclic_start(msp->tx_pipeid); + if (ret) + goto stop_rx; + + msp->infinite = true; + + return; + +stop_rx: + stedma40_cyclic_stop(msp->rx_pipeid); +free_tx: + stedma40_cyclic_free(msp->tx_pipeid); +free_rx: + stedma40_cyclic_free(msp->rx_pipeid); +} + +/** + * msp_dma_xfer - Handles DMA transfers over i2s bus. + * @msp: main msp structure. + * @msg: i2s_message contains info about transmit and receive data. + * Context: process + * + * This will first check whether data buffer is dmaable or not. + * Call dma_map_single apis etc to make it dmaable dma. Starts the dma transfer + * for TX and RX parallely and wait for it to get completed. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_dma_xfer(struct msp *msp, struct i2s_message *msg) +{ + int status = 0; + + switch (msg->i2s_transfer_mode) { + default: + case I2S_TRANSFER_MODE_SINGLE_DMA: + if (msg->i2s_direction == I2S_DIRECTION_TX || + msg->i2s_direction == I2S_DIRECTION_BOTH) + if (msg->txdata && (msg->txbytes > 0)) { + if (!msg->dma_flag) + msg->txdata = + (void *)dma_map_single(NULL, + msg->txdata, + msg->txbytes, + DMA_TO_DEVICE); + status = msp_single_dma_tx(msp, + (dma_addr_t)msg->txdata, + msg->txbytes); + } + if (msg->i2s_direction == I2S_DIRECTION_RX || + msg->i2s_direction == I2S_DIRECTION_BOTH) + if (msg->rxdata && (msg->rxbytes > 0)) { + if (!msg->dma_flag) + msg->rxdata = + (void *)dma_map_single(NULL, + msg->rxdata, + msg->rxbytes, + DMA_FROM_DEVICE + ); + status = msp_single_dma_rx(msp, + (dma_addr_t)msg->rxdata, + msg->rxbytes); + } + break; + + case I2S_TRANSFER_MODE_CYCLIC_DMA: + if (msg->i2s_direction == I2S_DIRECTION_TX) { + msp_cyclic_dma_start(msp, + msg->sg, + msg->sg_len, + DMA_TO_DEVICE); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), + msp->registers + MSP_GCR); + } else { + msp_cyclic_dma_start(msp, + msg->sg, + msg->sg_len, + DMA_FROM_DEVICE); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), + msp->registers + MSP_GCR); + } + break; + + case I2S_TRANSFER_MODE_INF_LOOPBACK: + msp_loopback_inf_start_dma(msp, + (dma_addr_t)msg->rxdata, + msg->rxbytes); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), + msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), + msp->registers + MSP_GCR); + break; + } + + return status; +} + +#if 0 +/** + * msp_handle_irq - Interrupt handler routine. + * @irq: irq no. + * @dev_id: device structure registered in request irq. + * + * Returns error(-1) on failure else success(0). + */ +static irqreturn_t msp_handle_irq(int irq, void *dev_id) +{ + u32 irq_status; + struct msp *msp = (struct msp *)dev_id; + struct i2s_message *message = &msp->xfer_data.message; + u32 irq_mask = 0; + irq_status = stm_msp_read(msp->registers + MSP_MIS); + irq_mask = stm_msp_read(msp->registers + MSP_IMSC); +/* Disable the interrupt to prevent immediate recurrence */ + stm_msp_write(stm_msp_read(msp->registers + MSP_IMSC) & ~irq_status, + msp->registers + MSP_IMSC); + +/* Clear the interrupt */ + stm_msp_write(irq_status, msp->registers + MSP_ICR); +/* Check for an error condition */ + msp->msp_io_error = irq_status & (RECEIVE_OVERRUN_ERROR_INT | + RECEIVE_FRAME_SYNC_ERR_INT | + TRANSMIT_UNDERRUN_ERR_INT | + TRANSMIT_FRAME_SYNC_ERR_INT); + + /*Currently they are ignored */ + if (irq_status & RECEIVE_OVERRUN_ERROR_INT) + ; + if (irq_status & TRANSMIT_UNDERRUN_ERR_INT) + ; + + /* This code has been added basically to support loopback mode + * Basically Transmit interrupt is not disabled even after its + * completion so that receive fifo gets an additional interrupt + */ + if (irq_mask & (RECEIVE_SERVICE_INT) + && (irq_mask & (TRANSMIT_SERVICE_INT)) && (msp->loopback_enable)) { + if (msp->read) + msp->read(&msp->xfer_data); + if (msp->write) + msp->write(&msp->xfer_data); + if (message->rx_offset >= message->rxbytes) { + if (msp->xfer_data.rx_handler) + msp->xfer_data.rx_handler(msp-> + xfer_data. + rx_callback_data, + message->rx_offset); + msp->xfer_data.rx_handler = NULL; + return IRQ_HANDLED; + } + + if (message->tx_offset >= message->txbytes) { + if (msp->xfer_data.tx_handler) + msp->xfer_data.tx_handler(msp->xfer_data. + tx_callback_data, + message->tx_offset); + msp->xfer_data.tx_handler = NULL; + } + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + return IRQ_HANDLED; + } + + if (irq_status & RECEIVE_SERVICE_INT) { + if (msp->read) + msp->read(&msp->xfer_data); + if (message->rx_offset >= message->rxbytes) { + irq_mask &= ~RECEIVE_SERVICE_INT; + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + if (msp->xfer_data.rx_handler) + msp->xfer_data.rx_handler(msp-> + xfer_data. + rx_callback_data, + message->rx_offset); + if (!(irq_status & TRANSMIT_SERVICE_INT)) + return IRQ_HANDLED; + } + } + if (irq_status & TRANSMIT_SERVICE_INT) { + if (msp->write) + msp->write(&msp->xfer_data); + if (message->tx_offset >= message->txbytes) { + irq_mask &= ~TRANSMIT_SERVICE_INT; + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + if (msp->xfer_data.tx_handler) + msp->xfer_data.tx_handler(msp->xfer_data. + tx_callback_data, + message->tx_offset); + return IRQ_HANDLED; + } + } + stm_msp_write(irq_mask, msp->registers + MSP_IMSC); + return IRQ_HANDLED; + +} +#endif + +/** + * msp_interrupt_xfer - Handles Interrupt transfers over i2s bus. + * @msp: main msp structure. + * @msg: i2s_message contains info about transmit and receive data. + * Context: Process or interrupt. + * + * This implements transfer and receive functions used in interrupt mode. + * This can be used in interrupt context if a callback handler is registered + * by client driver. This has been to improve performance in interrupt mode. + * Hence can't use sleep in this function. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_interrupt_xfer(struct msp *msp, struct i2s_message *msg) +{ + struct i2s_message *message; + u32 irq_mask = 0; + + if (msg->i2s_transfer_mode != I2S_TRANSFER_MODE_NON_DMA) + return -EINVAL; + + if (msg->txbytes) { + msp->xfer_data.message.txbytes = msg->txbytes; + msp->xfer_data.message.txdata = msg->txdata; + msp->xfer_data.message.tx_offset = 0; + } + if (msg->rxbytes) { + msp->xfer_data.message.rxbytes = msg->rxbytes; + msp->xfer_data.message.rxdata = msg->rxdata; + msp->xfer_data.message.rx_offset = 0; + } + message = &msp->xfer_data.message; + if ((message->txdata == NULL || message->txbytes == 0) + && (message->rxdata == NULL || message->rxbytes == 0)) { + printk(KERN_ERR + "transmit_receive_data is NULL with bytes > 0\n"); + return -EINVAL; + } + + msp->msp_io_error = 0; + + if (message->tx_offset < message->txbytes) { + irq_mask |= TRANSMIT_SERVICE_INT; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (TX_ENABLE)), msp->registers + MSP_GCR); + } + if (message->rx_offset < message->rxbytes) { + irq_mask |= RECEIVE_SERVICE_INT; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (RX_ENABLE)), msp->registers + MSP_GCR); + } + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) | + irq_mask), msp->registers + MSP_IMSC); + return 0; +} + +/** + * func_notify_timer - Handles Polling hang issue over i2s bus. + * @data: main msp data address + * Context: Interrupt. + * + * This is used to handle error condition in transfer and receive function used + * in polling mode. + * Sometimes due to passing wrong protocol desc , polling transfer may hang. + * To prevent this, timer is added. + * + * Returns void. + */ +static void func_notify_timer(unsigned long data) +{ + struct msp *msp = (struct msp *)data; + if (msp->polling_flag) { + msp->msp_io_error = 1; + printk(KERN_ERR + "Polling is taking two much time, may be it got hang\n"); + del_timer(&msp->notify_timer); + } +} + +/** + * msp_polling_xfer - Handles Polling transfers over i2s bus. + * @msp: main msp structure. + * @msg: i2s_message contains info about transmit and receive data. + * Context: Process. + * + * This implements transfer and receive functions used in polling mode. This is + * blocking fucntion. + * It is recommended to use interrupt or dma mode for better performance rather + * than the polling mode. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_polling_xfer(struct msp *msp, struct i2s_message *msg) +{ + struct i2s_message *message; + u32 time_expire = 0; + u32 tr_ex = 0, rr_ex = 0; + u32 msec_jiffies = 0; + + if (msg->i2s_transfer_mode != I2S_TRANSFER_MODE_NON_DMA) + return -EINVAL; + + if (msg->txbytes) { + msp->xfer_data.message.txbytes = msg->txbytes; + msp->xfer_data.message.txdata = msg->txdata; + msp->xfer_data.message.tx_offset = 0; + tr_ex = msg->txbytes; + } + if (msg->rxbytes) { + msp->xfer_data.message.rxbytes = msg->rxbytes; + msp->xfer_data.message.rxdata = msg->rxdata; + msp->xfer_data.message.rx_offset = 0; + rr_ex = msg->rxbytes; + } + message = &msp->xfer_data.message; + time_expire = (tr_ex + rr_ex) / 1024; + if (!time_expire) + msec_jiffies = 500; + else + msec_jiffies = time_expire * 500; + msp->notify_timer.expires = jiffies + msecs_to_jiffies(msec_jiffies); + down(&msp->lock); + if (message->txdata == NULL && message->txbytes > 0) { + printk(KERN_ERR + "transmit_receive_data is NULL with bytes > 0\n"); + return -EINVAL; + } + + if (message->rxdata == NULL && message->rxbytes > 0) { + printk(KERN_ERR + "transmit_receive_data is NULL with bytes > 0\n"); + return -EINVAL; + } + msp->msp_io_error = 0; + msp->polling_flag = 1; + add_timer(&msp->notify_timer); + while (message->tx_offset < message->txbytes + || message->rx_offset < message->rxbytes) { + if (msp->msp_io_error) + break; + if (msp->read) + msp->read(&msp->xfer_data); + if (msp->write) + msp->write(&msp->xfer_data); + } + msp->polling_flag = 0; + del_timer(&msp->notify_timer); + up(&msp->lock); + return message->txbytes + message->rxbytes; +} + +/** + * stm_msp_transceive_data - Main i2s transfer function. + * @i2s_cont: i2s controller structure passed by client driver. + * @message: i2s message structure contains transceive info. + * Context: process or interrupt. + * + * This function is registered over i2s_xfer funtions. It will handle main i2s + * transfer over i2s bus in various modes.It call msp transfer function on which + * suitable transfer function is already registered i.e dma ,interrupt or + * polling function. + * + * Returns error(-1) in case of failure or success(0). + */ +static int stm_msp_transceive_data(struct i2s_controller *i2s_cont, + struct i2s_message *message) +{ + int status = 0; + struct msp *msp = (struct msp *)i2s_cont->data; + + if (!message || (msp->msp_state == MSP_STATE_IDLE)) { + printk(KERN_ERR "Message is NULL\n"); + return -EPERM; + } + + msp->msp_state = MSP_STATE_RUN; + if (msp->transfer) + status = msp->transfer(msp, message); + + if (msp->msp_state == MSP_STATE_RUN) + msp->msp_state = MSP_STATE_CONFIGURED; + + return status; +} + +/** + * msp_disable_receive - Disable receive functionality. + * @msp: main msp structure. + * Context: process. + * + * This function will disable msp controller's receive functionality like dma, + * interrupt receive data buffer all are disabled. + * + * Returns void. + */ +static void msp_disable_receive(struct msp *msp) +{ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~RX_ENABLE)), msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_DMACR) & + (~RX_DMA_ENABLE)), msp->registers + MSP_DMACR); + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) & + (~ + (RECEIVE_SERVICE_INT | + RECEIVE_OVERRUN_ERROR_INT))), + msp->registers + MSP_IMSC); + msp->xfer_data.message.rxbytes = 0; + msp->xfer_data.message.rx_offset = 0; + msp->xfer_data.message.rxdata = NULL; + msp->read = NULL; + +} + +/** + * msp_disable_transmit - Disable transmit functionality. + * @msp: main msp structure. + * Context: process. + * + * This function will disable msp controller's transmit functionality like dma, + * interrupt transmit data buffer all are disabled. + * + * Returns void. + */ +static void msp_disable_transmit(struct msp *msp) +{ + + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + stm_msp_write((stm_msp_read(msp->registers + MSP_DMACR) & + (~TX_DMA_ENABLE)), msp->registers + MSP_DMACR); + stm_msp_write((stm_msp_read(msp->registers + MSP_IMSC) & + (~ + (TRANSMIT_SERVICE_INT | + TRANSMIT_UNDERRUN_ERR_INT))), + msp->registers + MSP_IMSC); + msp->xfer_data.message.txbytes = 0; + msp->xfer_data.message.tx_offset = 0; + msp->xfer_data.message.txdata = NULL; + msp->write = NULL; + +} + +/** + * stm_msp_disable - disable the given msp controller + * @msp: specifies the msp contoller data + * @direction: specifies the transmit/receive direction + * @flag: It indicates the functionality that needs to be disabled. + * + * Returns error(-1) in case of failure else success(0) + */ +static int stm_msp_disable(struct msp *msp, int direction, i2s_flag flag) +{ + int limit = 32; + u32 dummy = 0; + int status = 0; + if (! + (stm_msp_read(msp->registers + MSP_GCR) & + ((TX_ENABLE | RX_ENABLE)))) { + return 0; + } + if (msp->work_mode == MSP_DMA_MODE) { + if (flag == DISABLE_ALL || flag == DISABLE_TRANSMIT) { + if (msp->tx_pipeid != NULL) { + if (msp->infinite) { + stedma40_cyclic_stop(msp->tx_pipeid); + stedma40_cyclic_free(msp->tx_pipeid); + } + msp->tx_pipeid->device-> + device_control(msp->tx_pipeid, + DMA_TERMINATE_ALL, 0); + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + } + if ((flag == DISABLE_ALL || flag == DISABLE_RECEIVE)) { + if (msp->rx_pipeid != NULL) { + if (msp->infinite) { + stedma40_cyclic_stop(msp->rx_pipeid); + stedma40_cyclic_free(msp->rx_pipeid); + } + + msp->rx_pipeid->device-> + device_control(msp->rx_pipeid, + DMA_TERMINATE_ALL, 0); + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + } + + msp->infinite = false; + } + if (flag == DISABLE_TRANSMIT) + msp_disable_transmit(msp); + else if (flag == DISABLE_RECEIVE) + msp_disable_receive(msp); + else { + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) | + (LOOPBACK_MASK)), msp->registers + MSP_GCR); + /* Flush Tx fifo */ + while ((! + (stm_msp_read(msp->registers + MSP_FLR) & + TX_FIFO_EMPTY)) && limit--) + dummy = stm_msp_read(msp->registers + MSP_DR); + + /* Disable Transmit channel */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + limit = 32; + /* Flush Rx Fifo */ + while ((! + (stm_msp_read(msp->registers + MSP_FLR) & + RX_FIFO_EMPTY)) && limit--) + dummy = stm_msp_read(msp->registers + MSP_DR); + /* Disable Loopback and Receive channel */ + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~(RX_ENABLE | LOOPBACK_MASK))), + msp->registers + MSP_GCR); + /*This has been added in order to fix fifo flush problem + When last xfer occurs some data remains in fifo. In order to + flush that data delay is needed */ + msleep(10); + msp_disable_transmit(msp); + msp_disable_receive(msp); + + } + + /* disable sample rate and frame generators */ + if (flag == DISABLE_ALL) { + msp->msp_state = MSP_STATE_IDLE; + stm_msp_write((stm_msp_read(msp->registers + MSP_GCR) & + (~(FRAME_GEN_ENABLE | SRG_ENABLE))), + msp->registers + MSP_GCR); + memset(&msp->xfer_data, 0, sizeof(struct trans_data)); + if (msp->plat_exit) + status = msp->plat_exit(); + if (status) + printk(KERN_ERR "Error in msp_i2s_exit\n"); + if (msp->work_mode == MSP_POLLING_MODE + && msp->msp_state == MSP_STATE_RUN) { + up(&msp->lock); + } + msp->transfer = NULL; + stm_msp_write(0, msp->registers + MSP_GCR); + stm_msp_write(0, msp->registers + MSP_TCF); + stm_msp_write(0, msp->registers + MSP_RCF); + stm_msp_write(0, msp->registers + MSP_DMACR); + stm_msp_write(0, msp->registers + MSP_SRG); + stm_msp_write(0, msp->registers + MSP_MCR); + stm_msp_write(0, msp->registers + MSP_RCM); + stm_msp_write(0, msp->registers + MSP_RCV); + stm_msp_write(0, msp->registers + MSP_TCE0); + stm_msp_write(0, msp->registers + MSP_TCE1); + stm_msp_write(0, msp->registers + MSP_TCE2); + stm_msp_write(0, msp->registers + MSP_TCE3); + stm_msp_write(0, msp->registers + MSP_RCE0); + stm_msp_write(0, msp->registers + MSP_RCE1); + stm_msp_write(0, msp->registers + MSP_RCE2); + stm_msp_write(0, msp->registers + MSP_RCE3); + } + return status; +} + +/** + * stm_msp_close - Close the current i2s connection btw controller and client. + * @i2s_cont: i2s controller structure + * @flag: It indicates the functionality that needs to be disabled. + * Context: process + * + * It will call msp_disable and reset the msp configuration. Disables Rx and Tx + * channels, free gpio irqs and interrupt pins. + * Called by i2s client driver to indicate the completion of use of i2s bus. + * It is registered on i2s_close function. + * + * Returns error(-1) in case of failure or success(0). + */ +static int stm_msp_close(struct i2s_controller *i2s_cont, i2s_flag flag) +{ + int status = 0; + struct msp *msp = (struct msp *)i2s_cont->data; + down(&msp->lock); + if (msp->users == 0) { + pr_err("MSP already closed!\n"); + status = -EINVAL; + goto end; + } + dev_dbg(&i2s_cont->dev, "%s: users = %d, flag = %d.\n", + __func__, msp->users, flag); + /* We need to call it twice for DISABLE_ALL*/ + msp->users = flag == DISABLE_ALL ? 0 : msp->users - 1; + if (msp->users) + status = stm_msp_disable(msp, MSP_BOTH_T_R_MODE, flag); + else { + status = stm_msp_disable(msp, MSP_BOTH_T_R_MODE, DISABLE_ALL); + clk_disable(msp->clk); + status = regulator_disable(msp_vape_supply); + if (status != 0) { + dev_err(&msp->i2s_cont->dev, + "Failed to disable regulator\n"); + clk_enable(msp->clk); + goto end; + } + } + if (status) + goto end; + if (msp->users) + msp->direction = flag == DISABLE_TRANSMIT ? + MSP_RECEIVE_MODE : MSP_TRANSMIT_MODE; + + if (msp->vape_opp_constraint == 1) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 50); + msp->vape_opp_constraint = 0; + } +end: + up(&msp->lock); + return status; + +} + +static int stm_msp_hw_status(struct i2s_controller *i2s_cont) +{ + struct msp *msp = (struct msp *)i2s_cont->data; + + int status = stm_msp_read(msp->registers + MSP_RIS) & 0xee; + if (status) + stm_msp_write(status, msp->registers + MSP_ICR); + + return status; +} + +static dma_addr_t stm_msp_get_pointer(struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction) +{ + struct msp *msp = (struct msp *)i2s_cont->data; + return (i2s_direction == I2S_DIRECTION_TX) ? + stedma40_get_src_addr(msp->tx_pipeid) : + stedma40_get_dst_addr(msp->rx_pipeid); +} + + /*Platform driver's functions */ +/** + * msp_probe - Probe function + * @pdev: platform device structure. + * Context: process + * + * Probe function of msp platform driver.Handles allocation of memory and irq + * resource. It creates i2s_controller and one i2s_device per msp controller. + * + * Returns error(-1) in case of failure or success(0). + */ +int msp_probe(struct platform_device *pdev) +{ + int status = 0; + struct device *dev; + s16 platform_num = 0; + struct resource *res = NULL; + int irq; + struct i2s_controller *i2s_cont; + struct msp_i2s_platform_data *platform_data; + struct msp *msp; + + if (!pdev) + return -EPERM; + msp = kzalloc(sizeof(*msp), GFP_KERNEL); + + platform_data = (struct msp_i2s_platform_data *)pdev->dev.platform_data; + + msp->id = platform_data->id; + msp->plat_init = platform_data->msp_i2s_init; + msp->plat_exit = platform_data->msp_i2s_exit; + + msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx; + msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx; + + dev = &pdev->dev; + platform_num = msp->id - 1; + + sema_init(&msp->lock, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "probe - MEM resources not defined\n"); + status = -EINVAL; + goto free_msp; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + status = -EINVAL; + goto free_msp; + } + msp->irq = irq; + + msp->registers = ioremap(res->start, (res->end - res->start + 1)); + if (msp->registers == NULL) { + status = -EINVAL; + goto free_msp; + } + + msp_vape_supply = regulator_get(NULL, "v-ape"); + if (IS_ERR(msp_vape_supply)) { + status = PTR_ERR(msp_vape_supply); + printk(KERN_WARNING "msp i2s : failed to get v-ape supply\n"); + goto free_irq; + } + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, "msp_i2s", 50); + msp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(msp->clk)) { + status = PTR_ERR(msp->clk); + goto free_irq; + } + + init_timer(&msp->notify_timer); + msp->notify_timer.expires = jiffies + msecs_to_jiffies(1000); + msp->notify_timer.function = func_notify_timer; + msp->notify_timer.data = (unsigned long)msp; + + msp->rx_pipeid = NULL; + msp->tx_pipeid = NULL; + msp->read = NULL; + msp->write = NULL; + msp->transfer = NULL; + msp->msp_state = MSP_STATE_IDLE; + msp->loopback_enable = 0; + + dev_set_drvdata(&pdev->dev, msp); + /* I2S Controller is allocated and added in I2S controller class. */ + i2s_cont = + (struct i2s_controller *)kzalloc(sizeof(*i2s_cont), GFP_KERNEL); + if (!i2s_cont) { + dev_err(&pdev->dev, "i2s controller alloc failed \n"); + status = -EINVAL; + goto del_timer; + } + i2s_cont->dev.parent = dev; + i2s_cont->algo = &i2s_algo; + i2s_cont->data = (void *)msp; + i2s_cont->id = platform_num; + snprintf(i2s_cont->name, sizeof(i2s_cont->name), + "MSP_I2S.%04x", platform_num); + + status = i2s_add_controller(i2s_cont); + if (status) { + dev_err(&pdev->dev, "i2s add controller failed (%d)\n", status); + goto free_cont; + } + msp->i2s_cont = i2s_cont; + return status; +free_cont: + kfree(msp->i2s_cont); +del_timer: + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); +free_irq: + iounmap(msp->registers); +free_msp: + kfree(msp); + return status; +} + +/** + * msp_remove - remove function + * @pdev: platform device structure. + * Context: process + * + * remove function of msp platform driver.Handles dellocation of memory and irq + * resource. It deletes i2s_controller and one i2s_device per msp controller + * created in msp_probe. + * + * Returns error(-1) in case of failure or success(0). + */ +static int msp_remove(struct platform_device *pdev) +{ + struct msp *msp = + (struct msp *)dev_get_drvdata(&pdev->dev); + int status = 0; + i2s_del_controller(msp->i2s_cont); + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); + iounmap(msp->registers); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "msp_i2s"); + regulator_put(msp_vape_supply); + kfree(msp); + return status; +} +#ifdef CONFIG_PM +/** + * msp_suspend - MSP suspend function registered with PM framework. + * @pdev: Reference to platform device structure of the device + * @state: power mgmt state. + * + * This function is invoked when the system is going into sleep, called + * by the power management framework of the linux kernel. + * Nothing is required as controller is configured with every transfer. + * It is assumed that no active tranfer is in progress at this time. + * Client driver should make sure of this. + * + */ + +int msp_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct msp *msp = + (struct msp *)dev_get_drvdata(&pdev->dev); + + down(&msp->lock); + if (msp->users > 0) { + up(&msp->lock); + return -EBUSY; + } + up(&msp->lock); + + return 0; +} +/** + * msp_resume - MSP Resume function registered with PM framework. + * @pdev: Reference to platform device structure of the device + * + * This function is invoked when the system is coming out of sleep, called + * by the power management framework of the linux kernel. + * Nothing is required. + * + */ + +int msp_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define msp_suspend NULL +#define msp_resume NULL +#endif + +static struct platform_driver msp_i2s_driver = { + .probe = msp_probe, + .remove = msp_remove, + .suspend = msp_suspend, + .resume = msp_resume, + .driver = { + .owner = THIS_MODULE, + .name = "MSP_I2S", + }, +}; + +static int __init stm_msp_mod_init(void) +{ + return platform_driver_register(&msp_i2s_driver); +} + +static void __exit stm_msp_exit(void) +{ + platform_driver_unregister(&msp_i2s_driver); + return; +} + +module_init(stm_msp_mod_init); +module_exit(stm_msp_exit); + +MODULE_AUTHOR("Sandeep Kaushik"); +MODULE_DESCRIPTION("STM MSP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/i2s/msp_i2s.h b/drivers/misc/i2s/msp_i2s.h new file mode 100644 index 00000000000..2ac86e7700e --- /dev/null +++ b/drivers/misc/i2s/msp_i2s.h @@ -0,0 +1,362 @@ +/*----------------------------------------------------------------------------------*/ +/* copyright STMicroelectronics, 2007. */ +/* */ +/* This program is free software; you can redistribute it and/or modify it under */ +/* the terms of the GNU General Public License as published by the Free */ +/* Software Foundation; either version 2.1 of the License, or (at your option) */ +/* any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS */ +/* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/*----------------------------------------------------------------------------------*/ + + +#ifndef STM_MSP_HEADER +#define STM_MSP_HEADER + +#define MSP_DR 0x00 +#define MSP_GCR 0x04 +#define MSP_TCF 0x08 +#define MSP_RCF 0x0c +#define MSP_SRG 0x10 +#define MSP_FLR 0x14 +#define MSP_DMACR 0x18 + +#define MSP_IMSC 0x20 +#define MSP_RI 0x24 +#define MSP_MIS 0x28 +#define MSP_ICR 0x2c +#define MSP_MCR 0x30 +#define MSP_RCV 0x34 +#define MSP_RCM 0x38 + +#define MSP_TCE0 0x40 +#define MSP_TCE1 0x44 +#define MSP_TCE2 0x48 +#define MSP_TCE3 0x4c + +#define MSP_RCE0 0x60 +#define MSP_RCE1 0x64 +#define MSP_RCE2 0x68 +#define MSP_RCE3 0x6c + +#define MSP_ITCR 0x80 +#define MSP_ITIP 0x84 +#define MSP_ITOP 0x88 +#define MSP_TSTDR 0x8c + +#define MSP_PID0 0xfe0 +#define MSP_PID1 0xfe4 +#define MSP_PID2 0xfe8 +#define MSP_PID3 0xfec + +#define MSP_CID0 0xff0 +#define MSP_CID1 0xff4 +#define MSP_CID2 0xff8 +#define MSP_CID3 0xffc + + +/* Single or dual phase mode */ +enum +{ + MSP_SINGLE_PHASE, + MSP_DUAL_PHASE +}; + + +/* Transmit/Receive shifter status +-----------------------------------*/ +enum +{ + MSP_SxHIFTER_IDLE = 0, + MSP_SHIFTER_WORKING = 1 +}; + + +/* Transmit/Receive FIFO status +---------------------------------*/ +enum +{ + MSP_FIFO_FULL, + MSP_FIFO_PART_FILLED, + MSP_FIFO_EMPTY +}; + + +/* Frame length +------------------*/ +enum +{ + MSP_FRAME_LENGTH_1 = 0, + MSP_FRAME_LENGTH_2 = 1, + MSP_FRAME_LENGTH_4 = 3, + MSP_FRAME_LENGTH_8 = 7, + MSP_FRAME_LENGTH_12 = 11, + MSP_FRAME_LENGTH_16 = 15, + MSP_FRAME_LENGTH_20 = 19, + MSP_FRAME_LENGTH_32 = 31, + MSP_FRAME_LENGTH_48 = 47, + MSP_FRAME_LENGTH_64 = 63 +}; + +/* Element length */ +enum +{ + MSP_ELEM_LENGTH_8 = 0, + MSP_ELEM_LENGTH_10 = 1, + MSP_ELEM_LENGTH_12 = 2, + MSP_ELEM_LENGTH_14 = 3, + MSP_ELEM_LENGTH_16 = 4, + MSP_ELEM_LENGTH_20 = 5, + MSP_ELEM_LENGTH_24 = 6, + MSP_ELEM_LENGTH_32 = 7 +}; + + +/* Data delay (in bit clock cycles) +---------------------------------------*/ +enum +{ + MSP_DELAY_0 = 0, + MSP_DELAY_1 = 1, + MSP_DELAY_2 = 2, + MSP_DELAY_3 = 3 +}; + + +/* Configurations of clocks (transmit, receive or sample rate generator) +-------------------------------------------------------------------------*/ +enum +{ + MSP_RISING_EDGE = 0, + MSP_FALLING_EDGE = 1 +}; + +/* Protocol dependant parameters list */ +struct msp_protocol_desc +{ + u32 phase_mode; + u32 frame_len_1; + u32 frame_len_2; + u32 element_len_1; + u32 element_len_2; + u32 data_delay; + u32 tx_clock_edge; + u32 rx_clock_edge; +}; +#define RX_ENABLE_MASK 0x00000001 +#define RX_FIFO_ENABLE_MASK 0x00000002 +#define RX_FRAME_SYNC_MASK 0x00000004 +#define DIRECT_COMPANDING_MASK 0x00000008 +#define RX_SYNC_SEL_MASK 0x00000010 +#define RX_CLK_POL_MASK 0x00000020 +#define RX_CLK_SEL_MASK 0x00000040 +#define LOOPBACK_MASK 0x00000080 +#define TX_ENABLE_MASK 0x00000100 +#define TX_FIFO_ENABLE_MASK 0x00000200 +#define TX_FRAME_SYNC_MASK 0x00000400 +#define TX_MSP_TDR_TSR 0x00000800 +#define TX_SYNC_SEL_MASK 0x00001800 +#define TX_CLK_POL_MASK 0x00002000 +#define TX_CLK_SEL_MASK 0x00004000 +#define TX_EXTRA_DELAY_MASK 0x00008000 +#define SRG_ENABLE_MASK 0x00010000 +#define SRG_CLK_POL_MASK 0x00020000 +#define SRG_CLK_SEL_MASK 0x000C0000 +#define FRAME_GEN_EN_MASK 0x00100000 +#define SPI_CLK_MODE_MASK 0x00600000 +#define SPI_BURST_MODE_MASK 0x00800000 + +#define RXEN_BIT 0 +#define RFFEN_BIT 1 +#define RFSPOL_BIT 2 +#define DCM_BIT 3 +#define RFSSEL_BIT 4 +#define RCKPOL_BIT 5 +#define RCKSEL_BIT 6 +#define LBM_BIT 7 +#define TXEN_BIT 8 +#define TFFEN_BIT 9 +#define TFSPOL_BIT 10 +#define TFSSEL_BIT 11 +#define TCKPOL_BIT 13 +#define TCKSEL_BIT 14 +#define TXDDL_BIT 15 +#define SGEN_BIT 16 +#define SCKPOL_BIT 17 +#define SCKSEL_BIT 18 +#define FGEN_BIT 20 +#define SPICKM_BIT 21 + +#define msp_rx_clkpol_bit(n) ((n & 1) << RCKPOL_BIT) +#define msp_tx_clkpol_bit(n) ((n & 1) << TCKPOL_BIT) +#define msp_spi_clk_mode_bits(n) ((n & 3) << SPICKM_BIT) + + +/* Use this to clear the clock mode bits to non-spi */ +#define MSP_NON_SPI_CLK_MASK 0x00600000 + +#define P1ELEN_BIT 0 +#define P1FLEN_BIT 3 +#define DTYP_BIT 10 +#define ENDN_BIT 12 +#define DDLY_BIT 13 +#define FSIG_BIT 15 +#define P2ELEN_BIT 16 +#define P2FLEN_BIT 19 +#define P2SM_BIT 26 +#define P2EN_BIT 27 + +#define msp_p1_elem_len_bits(n) (n & 0x00000007) +#define msp_p2_elem_len_bits(n) (((n) << P2ELEN_BIT) & 0x00070000) +#define msp_p1_frame_len_bits(n) (((n) << P1FLEN_BIT) & 0x00000378) +#define msp_p2_frame_len_bits(n) (((n) << P2FLEN_BIT) & 0x03780000) +#define msp_data_delay_bits(n) (((n) << DDLY_BIT) & 0x00003000) +#define msp_data_type_bits(n) (((n) << DTYP_BIT) & 0x00000600) +#define msp_p2_start_mode_bit(n) (n << P2SM_BIT) +#define msp_p2_enable_bit(n) (n << P2EN_BIT) + +/* Flag register +--------------------*/ +#define RX_BUSY 0x00000001 +#define RX_FIFO_EMPTY 0x00000002 +#define RX_FIFO_FULL 0x00000004 +#define TX_BUSY 0x00000008 +#define TX_FIFO_EMPTY 0x00000010 +#define TX_FIFO_FULL 0x00000020 + +#define RBUSY_BIT 0 +#define RFE_BIT 1 +#define RFU_BIT 2 +#define TBUSY_BIT 3 +#define TFE_BIT 4 +#define TFU_BIT 5 + +/* Multichannel control register +---------------------------------*/ +#define RMCEN_BIT 0 +#define RMCSF_BIT 1 +#define RCMPM_BIT 3 +#define TMCEN_BIT 5 +#define TNCSF_BIT 6 + +/* Sample rate generator register +------------------------------------*/ +#define SCKDIV_BIT 0 +#define FRWID_BIT 10 +#define FRPER_BIT 16 + +#define SCK_DIV_MASK 0x0000003FF +#define frame_width_bits(n) (((n) << FRWID_BIT) &0x0000FC00) +#define frame_period_bits(n) (((n) << FRPER_BIT) &0x1FFF0000) + + +/* DMA controller register +---------------------------*/ +#define RX_DMA_ENABLE 0x00000001 +#define TX_DMA_ENABLE 0x00000002 + +#define RDMAE_BIT 0 +#define TDMAE_BIT 1 + +/*Interrupt Register +-----------------------------------------*/ +#define RECEIVE_SERVICE_INT 0x00000001 +#define RECEIVE_OVERRUN_ERROR_INT 0x00000002 +#define RECEIVE_FRAME_SYNC_ERR_INT 0x00000004 +#define RECEIVE_FRAME_SYNC_INT 0x00000008 +#define TRANSMIT_SERVICE_INT 0x00000010 +#define TRANSMIT_UNDERRUN_ERR_INT 0x00000020 +#define TRANSMIT_FRAME_SYNC_ERR_INT 0x00000040 +#define TRANSMIT_FRAME_SYNC_INT 0x00000080 +#define ALL_INT 0x000000ff + +/* Protocol configuration values +* I2S: Single phase, 16 bits, 2 words per frame +-----------------------------------------------*/ +#define I2S_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_32, \ + MSP_ELEM_LENGTH_32, \ + MSP_DELAY_1, \ + MSP_FALLING_EDGE, \ + MSP_FALLING_EDGE \ +} + +#define PCM_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_16, \ + MSP_DATA_DELAY, \ + MSP_TX_CLOCK_EDGE, \ + MSP_RX_CLOCK_EDGE \ +} + +/* Companded PCM: Single phase, 8 bits, 1 word per frame +--------------------------------------------------------*/ +#define PCM_COMPAND_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_0, \ + MSP_RISING_EDGE, \ + MSP_FALLING_EDGE \ +} + +/* AC97: Double phase, 1 element of 16 bits during first phase, +* 12 elements of 20 bits in second phase. +--------------------------------------------------------------*/ +#define AC97_PROTOCOL_DESC \ +{ \ + MSP_DUAL_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_12, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_20, \ + MSP_DELAY_1, \ + MSP_RISING_EDGE, \ + MSP_FALLING_EDGE \ +} + +#define SPI_MASTER_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_1, \ + MSP_FALLING_EDGE, \ + MSP_RISING_EDGE \ +} +#define SPI_SLAVE_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_1, \ + MSP_FALLING_EDGE, \ + MSP_RISING_EDGE \ +} + +#define MSP_FRAME_PERIOD_IN_MONO_MODE 256 +#define MSP_FRAME_PERIOD_IN_STEREO_MODE 32 +#define MSP_FRAME_WIDTH_IN_STEREO_MODE 16 + +#endif + diff --git a/include/linux/i2s/i2s.h b/include/linux/i2s/i2s.h new file mode 100644 index 00000000000..b67fdb2d80d --- /dev/null +++ b/include/linux/i2s/i2s.h @@ -0,0 +1,225 @@ +/*----------------------------------------------------------------------------*/ +/* copyright STMicroelectronics, 2007. */ +/* */ +/* This program is free software; you can redistribute it and/or modify it */ +/* under the terms of the GNU General Public License as published by the Free */ +/* Software Foundation; either version 2.1 of the License, or (at your option)*/ +/* any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, but */ +/* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY */ +/* or FITNES */ +/* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more */ +/* details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ +/*----------------------------------------------------------------------------*/ + +#ifndef __LINUX_I2S_H +#define __LINUX_I2S_H + +/* + * INTERFACES between I2S controller-side drivers and I2S infrastructure. + */ +extern struct bus_type i2s_bus_type; +#define I2S_NAME_SIZE 48 +/** + * struct i2s_device - Controller side proxy for an I2S slave device + * @dev: Driver model representation of the device. + * @controller: I2S controller used with the device. + * @chip_select: Chipselect, distinguishing chips handled by @controller. + * @modalias: Name of the driver to use with this device, or an alias + * for that name. This appears in the sysfs "modalias" attribute + * for driver coldplugging, and in uevents used for hotplugging + * + * A @i2s_device is used to interchange data between an I2S slave + * + * In @dev, the platform_data is used to hold information about this + * device that's meaningful to the device's protocol driver, but not + * to its controller. + */ +struct i2s_device { + struct device dev; + struct i2s_controller *controller; + u8 chip_select; + char modalias[32]; +}; +struct i2s_board_info { + /* the device name and module name are coupled, like platform_bus; + * "modalias" is normally the driver name. + * + * platform_data goes to i2s_device.dev.platform_data, + */ + char modalias[32]; + const void *platform_data; + u16 id; + u16 chip_select; +}; + +#ifdef CONFIG_STM_I2S +extern int +i2s_register_board_info(struct i2s_board_info const *info, unsigned n); +#else +/* board init code may ignore whether I2S is configured or not */ +static inline int +i2s_register_board_info(struct i2s_board_info const *info, unsigned n) +{ + return 0; +} +#endif + +static inline struct i2s_device *to_i2s_device(struct device *dev) +{ + return dev ? container_of(dev, struct i2s_device, dev) : NULL; +} + +static inline struct i2s_device *i2s_dev_get(struct i2s_device *i2s) +{ + return (i2s && get_device(&i2s->dev)) ? i2s : NULL; +} + +static inline void i2s_dev_put(struct i2s_device *i2s) +{ + if (i2s) + put_device(&i2s->dev); +} + +static inline void i2s_set_drvdata(struct i2s_device *i2s, void *data) +{ + dev_set_drvdata(&i2s->dev, data); +} + +static inline void *i2s_get_drvdata(struct i2s_device *i2s) +{ + return dev_get_drvdata(&i2s->dev); +} + +struct i2s_device_id { + char name[I2S_NAME_SIZE]; + /*currently not used may be used in future */ + u32 device_id; + u32 vendor_id; +}; + +/** + * struct i2s_driver - Host side "protocol" driver + */ +struct i2s_driver { + int (*probe) (struct i2s_device *i2s); + int (*remove) (struct i2s_device *i2s); + void (*shutdown) (struct i2s_device *i2s); + int (*suspend) (struct i2s_device *i2s, pm_message_t mesg); + int (*resume) (struct i2s_device *i2s); + struct device_driver driver; + const struct i2s_device_id *id_table; + +}; + +static inline struct i2s_driver *to_i2s_driver(struct device_driver *drv) +{ + return drv ? container_of(drv, struct i2s_driver, driver) : NULL; +} + +extern int i2s_register_driver(struct i2s_driver *sdrv); + +/** + * i2s_unregister_driver - reverse effect of i2s_register_driver + * @sdrv: the driver to unregister + * Context: can sleep + */ +static inline void i2s_unregister_driver(struct i2s_driver *sdrv) +{ + if (sdrv) + driver_unregister(&sdrv->driver); +} + +/**I2S controller parameters*/ + +enum i2s_direction_t { + I2S_DIRECTION_TX = 0, + I2S_DIRECTION_RX = 1, + I2S_DIRECTION_BOTH = 2 +}; + +enum i2s_transfer_mode_t { + I2S_TRANSFER_MODE_SINGLE_DMA = 0, + I2S_TRANSFER_MODE_CYCLIC_DMA = 1, + I2S_TRANSFER_MODE_INF_LOOPBACK = 2, + I2S_TRANSFER_MODE_NON_DMA = 4, +}; + +struct i2s_message { + enum i2s_transfer_mode_t i2s_transfer_mode; + enum i2s_direction_t i2s_direction; + void *txdata; + void *rxdata; + size_t txbytes; + size_t rxbytes; + int dma_flag; + int tx_offset; + int rx_offset; + bool cyclic_dma; + struct scatterlist *sg; + int sg_len; +}; + +typedef enum { + DISABLE_ALL = 0, + DISABLE_TRANSMIT = 1, + DISABLE_RECEIVE = 2, +} i2s_flag; + +struct i2s_algorithm { + int (*cont_setup) (struct i2s_controller *i2s_cont, void *config); + int (*cont_transfer) (struct i2s_controller *i2s_cont, + struct i2s_message *message); + int (*cont_cleanup) (struct i2s_controller *i2s_cont, i2s_flag flag); + int (*cont_hw_status) (struct i2s_controller *i2s_cont); + dma_addr_t (*cont_get_pointer) (struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction); +}; + +struct i2s_controller { + struct module *owner; + unsigned int id; + unsigned int class; + const struct i2s_algorithm *algo; /* the algorithm to access the bus */ + void *data; + struct mutex bus_lock; + struct device dev; /* the controller device */ + char name[48]; +}; +#define to_i2s_controller(d) container_of(d, struct i2s_controller, dev) + +static inline void *i2s_get_contdata(struct i2s_controller *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void i2s_set_contdata(struct i2s_controller *dev, void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +extern int i2s_add_controller(struct i2s_controller *controller); +extern int i2s_del_controller(struct i2s_controller *controller); +extern int i2s_setup(struct i2s_controller *i2s_cont, void *config); +extern int i2s_transfer(struct i2s_controller *i2s_cont, + struct i2s_message *message); +extern int i2s_cleanup(struct i2s_controller *i2s_cont, i2s_flag flag); +extern int i2s_hw_status(struct i2s_controller *i2s_cont); +extern dma_addr_t i2s_get_pointer(struct i2s_controller *i2s_cont, + enum i2s_direction_t i2s_direction); + +extern struct i2s_device *i2s_alloc_device(struct device *dev); + +extern int i2s_add_device(struct i2s_device *i2s); + +static inline void i2s_unregister_device(struct i2s_device *i2s) +{ + if (i2s) + device_unregister(&i2s->dev); +} + +#endif /* __LINUX_I2S_H */ diff --git a/include/linux/i2s/i2s_test_prot.h b/include/linux/i2s/i2s_test_prot.h new file mode 100644 index 00000000000..003d0e6e3ab --- /dev/null +++ b/include/linux/i2s/i2s_test_prot.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef __I2S_TEST_PROT_DRIVER_IF_H__ +#define __I2S_TEST_PROT_DRIVER_IF_H__ +#include +struct test_prot_config { + u32 tx_config_desc; + u32 rx_config_desc; + u32 frame_freq; + u32 frame_size; + u32 data_size; + u32 direction; + u32 protocol; + u32 work_mode; + struct msp_protocol_desc protocol_desc; + void (*handler) (void *data); + void *tx_callback_data; + void *rx_callback_data; + int multichannel_configured; + struct msp_multichannel_config multichannel_config; +}; + +int i2s_testprot_drv_open(int i2s_device_num); +int i2s_testprot_drv_configure(int i2s_device_num, + struct test_prot_config *config); +int i2s_config_default_protocol(int i2s_device_num, + struct test_prot_config *config); +int __i2s_testprot_drv_configure(int i2s_device_num, + struct test_prot_config *config, + bool use_default); +int i2s_testprot_drv_transfer(int i2s_device_num, void *txdata, size_t txbytes, + void *rxdata, size_t rxbytes, + enum i2s_transfer_mode_t transfer_mode); +int i2s_testprot_drv_close(int i2s_device_num); + +#endif -- cgit v1.2.3 From 504bcff6cc4706969f968d1d42f03cfb89781078 Mon Sep 17 00:00:00 2001 From: Per Fransson Date: Fri, 9 Jul 2010 12:14:56 +0200 Subject: arm: allow passing an ELF64 header to elf_check_arch() Signed-off-by: Per Fransson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2732 Tested-by: Per FRANSSON Reviewed-by: Jonas ABERG Change-Id: I616a21ea86221d179fff1e87174aafa0eeee7f98 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/3181 Tested-by: Mian Yousaf KAUKAB Reviewed-by: Srinidhi KASAGAR Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Lee Jones --- arch/arm/include/asm/elf.h | 4 ++-- arch/arm/kernel/elf.c | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/arm/include/asm/elf.h b/arch/arm/include/asm/elf.h index 0e9ce8d9686..85cf3655914 100644 --- a/arch/arm/include/asm/elf.h +++ b/arch/arm/include/asm/elf.h @@ -96,8 +96,8 @@ struct elf32_hdr; /* * This is used to ensure we don't load something for the wrong architecture. */ -extern int elf_check_arch(const struct elf32_hdr *); -#define elf_check_arch elf_check_arch +extern int arm_elf_check_arch(const struct elf32_hdr *); +#define elf_check_arch(x) arm_elf_check_arch((const struct elf32_hdr *)(x)) #define vmcore_elf64_check_arch(x) (0) diff --git a/arch/arm/kernel/elf.c b/arch/arm/kernel/elf.c index 9b05c6a0dce..ddd49b23365 100644 --- a/arch/arm/kernel/elf.c +++ b/arch/arm/kernel/elf.c @@ -4,11 +4,13 @@ #include #include -int elf_check_arch(const struct elf32_hdr *x) +int arm_elf_check_arch(const struct elf32_hdr *x) { unsigned int eflags; /* Make sure it's an ARM executable */ + if (x->e_ident[EI_CLASS] != ELF_CLASS) + return 0; if (x->e_machine != EM_ARM) return 0; @@ -35,7 +37,7 @@ int elf_check_arch(const struct elf32_hdr *x) } return 1; } -EXPORT_SYMBOL(elf_check_arch); +EXPORT_SYMBOL(arm_elf_check_arch); void elf_set_personality(const struct elf32_hdr *x) { -- cgit v1.2.3 From 12238efdb8e5bc0edfd2c91f5418d9ff04ecedc7 Mon Sep 17 00:00:00 2001 From: Per Fransson Date: Thu, 27 Jan 2011 14:08:16 +0100 Subject: mpcore_wdt: Stop watchdog of non-crashing cores When kexec'ing to u-boot only the crashing core is used (and will have to be kicked by u-boot). The watchdog of other core has to be stopped before leaving linux. ST-Ericsson ID: - Change-Id: I329630fd8040f8af3e54fc51aa7187217938f0b8 Signed-off-by: Per Fransson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13678 Reviewed-by: QATOOLS Reviewed-by: Srinidhi KASAGAR Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/kernel/machine_kexec.c | 1 + include/linux/kexec.h | 1 + kernel/kexec.c | 2 ++ 3 files changed, 4 insertions(+) diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c index e59bbd496c3..ae1d73a15d3 100644 --- a/arch/arm/kernel/machine_kexec.c +++ b/arch/arm/kernel/machine_kexec.c @@ -47,6 +47,7 @@ void machine_crash_nonpanic_core(void *unused) printk(KERN_DEBUG "CPU %u will stop doing anything useful since another CPU has crashed\n", smp_processor_id()); crash_save_cpu(®s, smp_processor_id()); + atomic_notifier_call_chain(&crash_percpu_notifier_list, 0, NULL); flush_cache_all(); atomic_dec(&waiting_for_crash_ipi); diff --git a/include/linux/kexec.h b/include/linux/kexec.h index c2478a342cd..76b4db78ec1 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -158,6 +158,7 @@ unsigned long paddr_vmcoreinfo_note(void); extern struct kimage *kexec_image; extern struct kimage *kexec_crash_image; +extern struct atomic_notifier_head crash_percpu_notifier_list; #ifndef kexec_flush_icache_page #define kexec_flush_icache_page(page) diff --git a/kernel/kexec.c b/kernel/kexec.c index 8d814cbc810..cf5f9ad2b91 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -50,6 +50,8 @@ u32 vmcoreinfo_note[VMCOREINFO_NOTE_SIZE/4]; size_t vmcoreinfo_size; size_t vmcoreinfo_max_size = sizeof(vmcoreinfo_data); +ATOMIC_NOTIFIER_HEAD(crash_percpu_notifier_list); + /* Location of the reserved area for the crash kernel */ struct resource crashk_res = { .name = "Crash kernel", -- cgit v1.2.3 From eb301ce3af4639a86541c7f9c03aa054340666f6 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 18 Mar 2011 14:57:19 +0100 Subject: spi: add stm msp driver Signed-off-by: Robert Marklund --- Documentation/DocBook/Makefile | 2 +- Documentation/DocBook/msp.tmpl | 104 ++ arch/arm/mach-ux500/include/mach/msp.h | 970 ++++++++++++++++ drivers/spi/Kconfig | 14 + drivers/spi/Makefile | 1 + drivers/spi/stm_msp.c | 1929 ++++++++++++++++++++++++++++++++ include/linux/spi/stm_msp.h | 126 +++ 7 files changed, 3145 insertions(+), 1 deletion(-) create mode 100644 Documentation/DocBook/msp.tmpl create mode 100644 arch/arm/mach-ux500/include/mach/msp.h create mode 100644 drivers/spi/stm_msp.c create mode 100644 include/linux/spi/stm_msp.h diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index fdbc16c6e33..27c7eca9f40 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,7 +15,7 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml media.xml drm.xml \ - i2s.xml + i2s.xml msp.xml ### # The build process is as follows (targets): diff --git a/Documentation/DocBook/msp.tmpl b/Documentation/DocBook/msp.tmpl new file mode 100644 index 00000000000..55cec352a76 --- /dev/null +++ b/Documentation/DocBook/msp.tmpl @@ -0,0 +1,104 @@ + + + + + + MSP + + + + Sandeep + Kaushik + +
+ sandeep.kaushik@st.com +
+
+
+
+ + + 2008-2009 + STMicroelectronics Pvt Ltd + + + + + Linux standard functions + + + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + +
+ + + + Introduction + + This Documentation describes the API's provided by the MSP controller Driver. + MSP controller supports different protocols like I2S, PCM, SPI etc. + + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Public Functions Provided + + Not Applicable. + + + + + Private Functions + + This Section lists the functions used by the MSP controller driver. + These functions cater to all the protocols supported namely: I2S, PCM, SPI. + +!Idrivers/misc/i2s/msp_i2s.c + +
diff --git a/arch/arm/mach-ux500/include/mach/msp.h b/arch/arm/mach-ux500/include/mach/msp.h new file mode 100644 index 00000000000..aecca3a59a3 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/msp.h @@ -0,0 +1,970 @@ +/* + * Copyright (c) 2009 STMicroelectronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#ifndef _STM_MSP_HEADER +#define _STM_MSP_HEADER +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Generic config struct. Use the actual values defined below for global + * control register + */ + +enum msp_state { + MSP_STATE_IDLE = 0, + MSP_STATE_CONFIGURED = 1, + MSP_STATE_RUN = 2, +}; + +enum msp_rx_comparison_enable_mode { + MSP_COMPARISON_DISABLED = 0, + MSP_COMPARISON_NONEQUAL_ENABLED = 2, + MSP_COMPARISON_EQUAL_ENABLED = 3 +}; + +#define RMCEN_BIT 0 +#define RMCSF_BIT 1 +#define RCMPM_BIT 3 +#define TMCEN_BIT 5 +#define TNCSF_BIT 6 + +struct msp_multichannel_config { + bool rx_multichannel_enable; + bool tx_multichannel_enable; + enum msp_rx_comparison_enable_mode rx_comparison_enable_mode; + u8 padding; + u32 comparison_value; + u32 comparison_mask; + u32 rx_channel_0_enable; + u32 rx_channel_1_enable; + u32 rx_channel_2_enable; + u32 rx_channel_3_enable; + u32 tx_channel_0_enable; + u32 tx_channel_1_enable; + u32 tx_channel_2_enable; + u32 tx_channel_3_enable; +}; + +/** + * struct msp_protocol_desc- MSP Protocol desc structure per MSP. + * @rx_phase_mode: rx_phase_mode whether single or dual. + * @tx_phase_mode: tx_phase_mode whether single or dual. + * @rx_phase2_start_mode: rx_phase2_start_mode whether imediate or after + * some delay. + * @tx_phase2_start_mode: tx_phase2_start_mode whether imediate or after + * some delay. + * @rx_bit_transfer_format: MSP or LSB. + * @tx_bit_transfer_format: MSP or LSB. + * @rx_frame_length_1: Frame1 length 1,2,3.. + * @rx_frame_length_2: Frame2 length 1,2,3.. + * @tx_frame_length_1: Frame1 length 1,2,3.. + * @tx_frame_length_2: Frame2 length 1,2,3.. + * @rx_element_length_1: Element1 length 1,2,... + * @rx_element_length_2: Element2 length 1,2,... + * @tx_element_length_1: Element1 length 1,2,... + * @tx_element_length_2: Element2 length 1,2,... + * @rx_data_delay: Delay in clk cycle after frame sync + * @tx_data_delay: Delay in clk cycle after frame sync + * @rx_clock_pol: Rxpol whether rising or falling.It indicates pol of bit clock. + * @tx_clock_pol: Txpol whether rising or falling.It indicates pol of bit clock. + * @rx_frame_sync_pol: Frame sync pol whether rising or Falling. + * @tx_frame_sync_pol: Frame sync pol whether rising or Falling. + * @rx_half_word_swap: Word swap half word, full word. + * @tx_half_word_swap: Word swap half word, full word. + * @compression_mode: Compression mode whether Alaw or Ulaw or disabled. + * @expansion_mode: Compression mode whether Alaw or Ulaw or disabled. + * @spi_clk_mode: Spi clock mode to be enabled or not. + * @spi_burst_mode: Spi burst mode to be enabled or not. + * @frame_sync_ignore: Frame sync to be ignored or not. Ignore in case of Audio + * codec acting as Master. + * @frame_period: Frame period (clk cycles) after which new frame sync occurs. + * @frame_width: Frame width (clk cycles) after which frame sycn changes state. + * @total_clocks_for_one_frame: No. of clk cycles per frame. + * + * Main Msp protocol descriptor data structure to be used to store various info + * in transmit or recevie configuration registers of an MSP. + */ + +struct msp_protocol_desc { + u32 rx_phase_mode; + u32 tx_phase_mode; + u32 rx_phase2_start_mode; + u32 tx_phase2_start_mode; + u32 rx_bit_transfer_format; + u32 tx_bit_transfer_format; + u32 rx_frame_length_1; + u32 rx_frame_length_2; + u32 tx_frame_length_1; + u32 tx_frame_length_2; + u32 rx_element_length_1; + u32 rx_element_length_2; + u32 tx_element_length_1; + u32 tx_element_length_2; + u32 rx_data_delay; + u32 tx_data_delay; + u32 rx_clock_pol; + u32 tx_clock_pol; + u32 rx_frame_sync_pol; + u32 tx_frame_sync_pol; + u32 rx_half_word_swap; + u32 tx_half_word_swap; + u32 compression_mode; + u32 expansion_mode; + u32 spi_clk_mode; + u32 spi_burst_mode; + u32 frame_sync_ignore; + u32 frame_period; + u32 frame_width; + u32 total_clocks_for_one_frame; +}; + +/** + * struct trans_data - MSP transfer data structure used during xfer. + * @message: i2s message. + * @msp: msp structure. + * @tx_handler: callback handler for transmit path. + * @rx_handler: callback handler for receive path. + * @tx_callback_data: callback data for transmit. + * @rx_callback_data: callback data for receive. + * + */ +struct trans_data { + struct i2s_message message; + struct msp *msp; + void (*tx_handler) (void *data); + void (*rx_handler) (void *data); + void *tx_callback_data; + void *rx_callback_data; +}; + +/** + * struct msp_config- MSP configuration structure used by i2s client. + * @input_clock_freq: Input clock frequency default is 48MHz. + * @rx_clock_sel: Receive clock selection (Provided by Sample Gen or external + * source). + * @tx_clock_sel: Transmit clock selection (Provided by Sample Gen or external. + * source). + * @srg_clock_sel: APB clock or clock dervied from Slave (Audio codec). + * @rx_frame_sync_pol: Receive frame sync polarity. + * @tx_frame_sync_pol: Transmit frame sync polarity. + * @rx_frame_sync_sel: Rx frame sync signal is provided by which source. + * External source or by frame generator logic. + * @tx_frame_sync_sel: Tx frame sync signal is provided by which source. + * External source or by frame generator logic. + * @rx_fifo_config: Receive fifo enable or not. + * @tx_fifo_config: Transmit fifo enable or not. + * @spi_clk_mode: In case of SPI protocol spi modes: Normal, Zero delay or + * half cycle delay. + * @spi_burst_mode: Spi burst mode is enabled or not. + * @loopback_enable: Loopback mode. + * @tx_data_enable: Transmit extra delay enable. + * @default_protocol_desc: Flag to indicate client defined protocol desc or + * statically defined in msp.h. + * @protocol_desc: Protocol desc structure filled by i2s client driver. + * In case client defined default_prtocol_desc as 0. + * @multichannel_configured: multichannel configuration structure. + * @multichannel_config: multichannel is enabled or not. + * @direction: Transmit, Receive or Both. + * @work_mode: Dma, Polling or Interrupt. + * @protocol: I2S, PCM, etc. + * @frame_freq: Sampling freq at which data is sampled. + * @frame_size: size of element. + * @data_size: data size which defines the format in which data is written on + * transmit or receive fifo. Only three modes 8,16,32 are supported. + * @def_elem_len: Flag to indicate whether default element length is to be used + * or should be changed acc to data size defined by user at run time. + * @iodelay: value for the MSP_IODLY register + * @handler: callback handler in case of interrupt or dma. + * @tx_callback_data: Callback data for transmit. + * @rx_callback_data: Callback data for receive. + * + * Main Msp configuration data structure used by i2s client driver to fill + * various info like data size, frequency etc. + */ +struct msp_config { + unsigned int input_clock_freq; + unsigned int rx_clock_sel; + unsigned int tx_clock_sel; + unsigned int srg_clock_sel; + unsigned int rx_frame_sync_pol; + unsigned int tx_frame_sync_pol; + unsigned int rx_frame_sync_sel; + unsigned int tx_frame_sync_sel; + unsigned int rx_fifo_config; + unsigned int tx_fifo_config; + unsigned int spi_clk_mode; + unsigned int spi_burst_mode; + unsigned int loopback_enable; + unsigned int tx_data_enable; + unsigned int default_protocol_desc; + struct msp_protocol_desc protocol_desc; + int multichannel_configured; + struct msp_multichannel_config multichannel_config; + unsigned int direction; + unsigned int work_mode; + unsigned int protocol; + unsigned int frame_freq; + unsigned int frame_size; + enum msp_data_size data_size; + unsigned int def_elem_len; + unsigned int iodelay; + void (*handler) (void *data); + void *tx_callback_data; + void *rx_callback_data; + +}; + +/*** Protocols ***/ +enum msp_protocol { + MSP_I2S_PROTOCOL, + MSP_PCM_PROTOCOL, + MSP_PCM_COMPAND_PROTOCOL, + MSP_AC97_PROTOCOL, + MSP_MASTER_SPI_PROTOCOL, + MSP_SLAVE_SPI_PROTOCOL, + MSP_INVALID_PROTOCOL +}; + +/*** Sample Frequencies ***/ +/* These are no longer required, frequencies in Hz can be used directly */ +enum msp_sample_freq { + MSP_SAMPLE_FREQ_NOT_SUPPORTED = -1, + MSP_SAMPLE_FREQ_8KHZ = 8000, + MSP_SAMPLE_FREQ_12KHZ = 12000, + MSP_SAMPLE_FREQ_16KHZ = 16000, + MSP_SAMPLE_FREQ_24KHZ = 24000, + MSP_SAMPLE_FREQ_32KHZ = 32000, + MSP_SAMPLE_FREQ_44KHZ = 44000, + MSP_SAMPLE_FREQ_48KHZ = 48000, + MSP_SAMPLE_FREQ_64KHZ = 64000, + MSP_SAMPLE_FREQ_88KHZ = 88000, + MSP_SAMPLE_FREQ_96KHZ = 96000, + MSP_SAMPLE_FREQ_22KHZ = 22000, + MSP_SAMPLE_FREQ_11KHZ = 11000 +}; + +/*** Input Frequencies ***/ +/* These are no longer required, frequencies in Hz can be used directly */ +enum msp_in_clock_freq { + MSP_INPUT_FREQ_1MHZ = 1000, + MSP_INPUT_FREQ_2MHZ = 2000, + MSP_INPUT_FREQ_3MHZ = 3000, + MSP_INPUT_FREQ_4MHZ = 4000, + MSP_INPUT_FREQ_5MHZ = 5000, + MSP_INPUT_FREQ_6MHZ = 6000, + MSP_INPUT_FREQ_8MHZ = 8000, + MSP_INPUT_FREQ_11MHZ = 11000, + MSP_INPUT_FREQ_12MHZ = 12000, + MSP_INPUT_FREQ_16MHZ = 16000, + MSP_INPUT_FREQ_22MHZ = 22000, + MSP_INPUT_FREQ_24MHZ = 24000, + MSP_INPUT_FREQ_48MHZ = 48000 +}; + +#define MSP_INPUT_FREQ_APB 48000000 + +/*** Stereo mode. Used for APB data accesses as 16 bits accesses (mono), + * 32 bits accesses (stereo). + ***/ +enum msp_stereo_mode { + MSP_MONO, + MSP_STEREO +}; + +/* Direction (Transmit/Receive mode) */ +enum msp_direction { + MSP_TRANSMIT_MODE, + MSP_RECEIVE_MODE, + MSP_BOTH_T_R_MODE +}; + +/* Dma mode should be used for large transfers, + * polling mode should be used for transfers of a few bytes + */ +enum msp_xfer_mode { + MSP_DMA_MODE, + MSP_POLLING_MODE, + MSP_INTERRUPT_MODE +}; + +/* User client for the MSP */ +enum msp_user { + MSP_NO_USER = 0, + MSP_USER_SPI, + MSP_USER_ALSA, + MSP_USER_SAA, +}; + +/*Flag structure for MSPx*/ +struct msp_flag { + struct semaphore lock; + enum msp_user user; +}; + +/* User client for the MSP */ +enum msp_mode { + MSP_NO_MODE = 0, + MSP_MODE_SPI, + MSP_MODE_NON_SPI, +}; + +/* Transmit and receive configuration register */ +#define MSP_BIG_ENDIAN 0x00000000 +#define MSP_LITTLE_ENDIAN 0x00001000 +#define MSP_UNEXPECTED_FS_ABORT 0x00000000 +#define MSP_UNEXPECTED_FS_IGNORE 0x00008000 +#define MSP_NON_MODE_BIT_MASK 0x00009000 + +/* Global configuration register */ +#define RX_ENABLE 0x00000001 +#define RX_FIFO_ENABLE 0x00000002 +#define RX_SYNC_SRG 0x00000010 +#define RX_CLK_POL_RISING 0x00000020 +#define RX_CLK_SEL_SRG 0x00000040 +#define TX_ENABLE 0x00000100 +#define TX_FIFO_ENABLE 0x00000200 +#define TX_SYNC_SRG_PROG 0x00001800 +#define TX_SYNC_SRG_AUTO 0x00001000 +#define TX_CLK_POL_RISING 0x00002000 +#define TX_CLK_SEL_SRG 0x00004000 +#define TX_EXTRA_DELAY_ENABLE 0x00008000 +#define SRG_ENABLE 0x00010000 +#define FRAME_GEN_ENABLE 0x00100000 +#define SRG_CLK_SEL_APB 0x00000000 +#define RX_FIFO_SYNC_HI 0x00000000 +#define TX_FIFO_SYNC_HI 0x00000000 +#define SPI_CLK_MODE_NORMAL 0x00000000 + +/* SPI Clock Modes enumertion + * SPI clock modes of MSP provides compatibility with + * the SPI protocol.MSP supports 2 SPI transfer formats. + * MSP_ZERO_DELAY_SPI_MODE:MSP transmits data over Tx/Rx + * Lines immediately after MSPTCK/MSPRCK rising/falling edge. + * MSP_HALF_CYCLE_DELY_SPI_MODE:MSP transmits data one-half cycle + * ahead of the rising/falling edge of the MSPTCK + */ + +#define MSP_FRAME_SIZE_AUTO -1 + + +#define MSP_DR 0x00 +#define MSP_GCR 0x04 +#define MSP_TCF 0x08 +#define MSP_RCF 0x0c +#define MSP_SRG 0x10 +#define MSP_FLR 0x14 +#define MSP_DMACR 0x18 + +#define MSP_IMSC 0x20 +#define MSP_RIS 0x24 +#define MSP_MIS 0x28 +#define MSP_ICR 0x2c +#define MSP_MCR 0x30 +#define MSP_RCV 0x34 +#define MSP_RCM 0x38 + +#define MSP_TCE0 0x40 +#define MSP_TCE1 0x44 +#define MSP_TCE2 0x48 +#define MSP_TCE3 0x4c + +#define MSP_RCE0 0x60 +#define MSP_RCE1 0x64 +#define MSP_RCE2 0x68 +#define MSP_RCE3 0x6c +#define MSP_IODLY 0x70 + +#define MSP_ITCR 0x80 +#define MSP_ITIP 0x84 +#define MSP_ITOP 0x88 +#define MSP_TSTDR 0x8c + +#define MSP_PID0 0xfe0 +#define MSP_PID1 0xfe4 +#define MSP_PID2 0xfe8 +#define MSP_PID3 0xfec + +#define MSP_CID0 0xff0 +#define MSP_CID1 0xff4 +#define MSP_CID2 0xff8 +#define MSP_CID3 0xffc + +/* Single or dual phase mode */ +enum msp_phase_mode { + MSP_SINGLE_PHASE, + MSP_DUAL_PHASE +}; + +/* Frame length */ +enum msp_frame_length { + MSP_FRAME_LENGTH_1 = 0, + MSP_FRAME_LENGTH_2 = 1, + MSP_FRAME_LENGTH_4 = 3, + MSP_FRAME_LENGTH_8 = 7, + MSP_FRAME_LENGTH_12 = 11, + MSP_FRAME_LENGTH_16 = 15, + MSP_FRAME_LENGTH_20 = 19, + MSP_FRAME_LENGTH_32 = 31, + MSP_FRAME_LENGTH_48 = 47, + MSP_FRAME_LENGTH_64 = 63 +}; + +/* Element length */ +enum msp_elem_length { + MSP_ELEM_LENGTH_8 = 0, + MSP_ELEM_LENGTH_10 = 1, + MSP_ELEM_LENGTH_12 = 2, + MSP_ELEM_LENGTH_14 = 3, + MSP_ELEM_LENGTH_16 = 4, + MSP_ELEM_LENGTH_20 = 5, + MSP_ELEM_LENGTH_24 = 6, + MSP_ELEM_LENGTH_32 = 7 +}; + +enum msp_data_xfer_width { + MSP_DATA_TRANSFER_WIDTH_BYTE, + MSP_DATA_TRANSFER_WIDTH_HALFWORD, + MSP_DATA_TRANSFER_WIDTH_WORD +}; + +enum msp_frame_sync { + MSP_FRAME_SYNC_UNIGNORE = 0, + MSP_FRAME_SYNC_IGNORE = 1, + +}; + +enum msp_phase2_start_mode { + MSP_PHASE2_START_MODE_IMEDIATE, + MSP_PHASE2_START_MODE_FRAME_SYNC +}; + +enum msp_btf { + MSP_BTF_MS_BIT_FIRST = 0, + MSP_BTF_LS_BIT_FIRST = 1 +}; + +enum msp_frame_sync_pol { + MSP_FRAME_SYNC_POL_ACTIVE_HIGH = 0, + MSP_FRAME_SYNC_POL_ACTIVE_LOW = 1 +}; + +/* Data delay (in bit clock cycles) */ +enum msp_delay { + MSP_DELAY_0 = 0, + MSP_DELAY_1 = 1, + MSP_DELAY_2 = 2, + MSP_DELAY_3 = 3 +}; + +/* Configurations of clocks (transmit, receive or sample rate generator) */ +enum msp_edge { + MSP_FALLING_EDGE = 0, + MSP_RISING_EDGE = 1, +}; + +enum msp_hws { + MSP_HWS_NO_SWAP = 0, + MSP_HWS_BYTE_SWAP_IN_WORD = 1, + MSP_HWS_BYTE_SWAP_IN_EACH_HALF_WORD = 2, + MSP_HWS_HALF_WORD_SWAP_IN_WORD = 3 +}; + +enum msp_compress_mode { + MSP_COMPRESS_MODE_LINEAR = 0, + MSP_COMPRESS_MODE_MU_LAW = 2, + MSP_COMPRESS_MODE_A_LAW = 3 +}; + +enum msp_spi_clock_mode { + MSP_SPI_CLOCK_MODE_NON_SPI = 0, + MSP_SPI_CLOCK_MODE_ZERO_DELAY = 2, + MSP_SPI_CLOCK_MODE_HALF_CYCLE_DELAY = 3 +}; + +enum msp_spi_burst_mode { + MSP_SPI_BURST_MODE_DISABLE = 0, + MSP_SPI_BURST_MODE_ENABLE = 1 +}; + +enum msp_expand_mode { + MSP_EXPAND_MODE_LINEAR = 0, + MSP_EXPAND_MODE_LINEAR_SIGNED = 1, + MSP_EXPAND_MODE_MU_LAW = 2, + MSP_EXPAND_MODE_A_LAW = 3 +}; + +/* Protocol dependant parameters list */ +#define RX_ENABLE_MASK BIT(0) +#define RX_FIFO_ENABLE_MASK BIT(1) +#define RX_FRAME_SYNC_MASK BIT(2) +#define DIRECT_COMPANDING_MASK BIT(3) +#define RX_SYNC_SEL_MASK BIT(4) +#define RX_CLK_POL_MASK BIT(5) +#define RX_CLK_SEL_MASK BIT(6) +#define LOOPBACK_MASK BIT(7) +#define TX_ENABLE_MASK BIT(8) +#define TX_FIFO_ENABLE_MASK BIT(9) +#define TX_FRAME_SYNC_MASK BIT(10) +#define TX_MSP_TDR_TSR BIT(11) +#define TX_SYNC_SEL_MASK (BIT(12) | BIT(11)) +#define TX_CLK_POL_MASK BIT(13) +#define TX_CLK_SEL_MASK BIT(14) +#define TX_EXTRA_DELAY_MASK BIT(15) +#define SRG_ENABLE_MASK BIT(16) +#define SRG_CLK_POL_MASK BIT(17) +#define SRG_CLK_SEL_MASK (BIT(19) | BIT(18)) +#define FRAME_GEN_EN_MASK BIT(20) +#define SPI_CLK_MODE_MASK (BIT(22) | BIT(21)) +#define SPI_BURST_MODE_MASK BIT(23) + +#define RXEN_SHIFT 0 +#define RFFEN_SHIFT 1 +#define RFSPOL_SHIFT 2 +#define DCM_SHIFT 3 +#define RFSSEL_SHIFT 4 +#define RCKPOL_SHIFT 5 +#define RCKSEL_SHIFT 6 +#define LBM_SHIFT 7 +#define TXEN_SHIFT 8 +#define TFFEN_SHIFT 9 +#define TFSPOL_SHIFT 10 +#define TFSSEL_SHIFT 11 +#define TCKPOL_SHIFT 13 +#define TCKSEL_SHIFT 14 +#define TXDDL_SHIFT 15 +#define SGEN_SHIFT 16 +#define SCKPOL_SHIFT 17 +#define SCKSEL_SHIFT 18 +#define FGEN_SHIFT 20 +#define SPICKM_SHIFT 21 +#define TBSWAP_SHIFT 28 + +#define RCKPOL_MASK BIT(0) +#define TCKPOL_MASK BIT(0) +#define SPICKM_MASK (BIT(1) | BIT(0)) +#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT) +#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT) +#define MSP_SPI_CLK_MODE_BITS(n) ((n & SPICKM_MASK) << SPICKM_SHIFT) + + + +/* Use this to clear the clock mode bits to non-spi */ +#define MSP_NON_SPI_CLK_MASK (BIT(22) | BIT(21)) + +#define P1ELEN_SHIFT 0 +#define P1FLEN_SHIFT 3 +#define DTYP_SHIFT 10 +#define ENDN_SHIFT 12 +#define DDLY_SHIFT 13 +#define FSIG_SHIFT 15 +#define P2ELEN_SHIFT 16 +#define P2FLEN_SHIFT 19 +#define P2SM_SHIFT 26 +#define P2EN_SHIFT 27 +#define FRAME_SYNC_SHIFT 15 + + +#define P1ELEN_MASK 0x00000007 +#define P2ELEN_MASK 0x00070000 +#define P1FLEN_MASK 0x00000378 +#define P2FLEN_MASK 0x03780000 +#define DDLY_MASK 0x00003000 +#define DTYP_MASK 0x00000600 +#define P2SM_MASK 0x04000000 +#define P2EN_MASK 0x08000000 +#define ENDN_MASK 0x00001000 +#define TFSPOL_MASK 0x00000400 +#define TBSWAP_MASK 0x30000000 +#define COMPANDING_MODE_MASK 0x00000c00 +#define FRAME_SYNC_MASK 0x00008000 + +#define MSP_P1_ELEM_LEN_BITS(n) (n & P1ELEN_MASK) +#define MSP_P2_ELEM_LEN_BITS(n) (((n) << P2ELEN_SHIFT) & P2ELEN_MASK) +#define MSP_P1_FRAME_LEN_BITS(n) (((n) << P1FLEN_SHIFT) & P1FLEN_MASK) +#define MSP_P2_FRAME_LEN_BITS(n) (((n) << P2FLEN_SHIFT) & P2FLEN_MASK) +#define MSP_DATA_DELAY_BITS(n) (((n) << DDLY_SHIFT) & DDLY_MASK) +#define MSP_DATA_TYPE_BITS(n) (((n) << DTYP_SHIFT) & DTYP_MASK) +#define MSP_P2_START_MODE_BIT(n) ((n << P2SM_SHIFT) & P2SM_MASK) +#define MSP_P2_ENABLE_BIT(n) ((n << P2EN_SHIFT) & P2EN_MASK) +#define MSP_SET_ENDIANNES_BIT(n) ((n << ENDN_SHIFT) & ENDN_MASK) +#define MSP_FRAME_SYNC_POL(n) ((n << TFSPOL_SHIFT) & TFSPOL_MASK) +#define MSP_DATA_WORD_SWAP(n) ((n << TBSWAP_SHIFT) & TBSWAP_MASK) +#define MSP_SET_COMPANDING_MODE(n) ((n << DTYP_SHIFT) & COMPANDING_MODE_MASK) +#define MSP_SET_FRAME_SYNC_IGNORE(n) ((n << FRAME_SYNC_SHIFT) & \ + FRAME_SYNC_MASK) + +/* Flag register */ +#define RX_BUSY BIT(0) +#define RX_FIFO_EMPTY BIT(1) +#define RX_FIFO_FULL BIT(2) +#define TX_BUSY BIT(3) +#define TX_FIFO_EMPTY BIT(4) +#define TX_FIFO_FULL BIT(5) + +#define RBUSY_SHIFT 0 +#define RFE_SHIFT 1 +#define RFU_SHIFT 2 +#define TBUSY_SHIFT 3 +#define TFE_SHIFT 4 +#define TFU_SHIFT 5 + +/* Multichannel control register */ +#define RMCEN_SHIFT 0 +#define RMCSF_SHIFT 1 +#define RCMPM_SHIFT 3 +#define TMCEN_SHIFT 5 +#define TNCSF_SHIFT 6 + +/* Sample rate generator register */ +#define SCKDIV_SHIFT 0 +#define FRWID_SHIFT 10 +#define FRPER_SHIFT 16 + +#define SCK_DIV_MASK 0x0000003FF +#define FRAME_WIDTH_BITS(n) (((n) << FRWID_SHIFT) & 0x0000FC00) +#define FRAME_PERIOD_BITS(n) (((n) << FRPER_SHIFT) & 0x1FFF0000) + +/* DMA controller register */ +#define RX_DMA_ENABLE BIT(0) +#define TX_DMA_ENABLE BIT(1) + +#define RDMAE_SHIFT 0 +#define TDMAE_SHIFT 1 + +/* Interrupt Register */ +#define RECEIVE_SERVICE_INT BIT(0) +#define RECEIVE_OVERRUN_ERROR_INT BIT(1) +#define RECEIVE_FRAME_SYNC_ERR_INT BIT(2) +#define RECEIVE_FRAME_SYNC_INT BIT(3) +#define TRANSMIT_SERVICE_INT BIT(4) +#define TRANSMIT_UNDERRUN_ERR_INT BIT(5) +#define TRANSMIT_FRAME_SYNC_ERR_INT BIT(6) +#define TRANSMIT_FRAME_SYNC_INT BIT(7) +#define ALL_INT 0x000000ff + +/* + * Protocol configuration values I2S: + * Single phase, 16 bits, 2 words per frame + */ +#define I2S_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_SINGLE_PHASE, \ + MSP_PHASE2_START_MODE_IMEDIATE, \ + MSP_PHASE2_START_MODE_IMEDIATE, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_32, \ + MSP_ELEM_LENGTH_32, \ + MSP_ELEM_LENGTH_32, \ + MSP_ELEM_LENGTH_32, \ + MSP_DELAY_1, \ + MSP_DELAY_1, \ + MSP_RISING_EDGE, \ + MSP_RISING_EDGE, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_HWS_NO_SWAP, \ + MSP_HWS_NO_SWAP, \ + MSP_COMPRESS_MODE_LINEAR, \ + MSP_EXPAND_MODE_LINEAR, \ + MSP_SPI_CLOCK_MODE_NON_SPI, \ + MSP_SPI_BURST_MODE_DISABLE, \ + MSP_FRAME_SYNC_IGNORE, \ + 31, \ + 15, \ + 32, \ +} + +#define PCM_PROTOCOL_DESC \ +{ \ + MSP_DUAL_PHASE, \ + MSP_DUAL_PHASE, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_16, \ + MSP_DELAY_0, \ + MSP_DELAY_0, \ + MSP_FALLING_EDGE, \ + MSP_FALLING_EDGE, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_HWS_NO_SWAP, \ + MSP_HWS_NO_SWAP, \ + MSP_COMPRESS_MODE_LINEAR, \ + MSP_EXPAND_MODE_LINEAR, \ + MSP_SPI_CLOCK_MODE_NON_SPI, \ + MSP_SPI_BURST_MODE_DISABLE, \ + MSP_FRAME_SYNC_IGNORE, \ + 255, \ + 0, \ + 256, \ +} + +/* Companded PCM: Single phase, 8 bits, 1 word per frame */ +#define PCM_COMPAND_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_SINGLE_PHASE, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_0, \ + MSP_DELAY_0, \ + MSP_FALLING_EDGE, \ + MSP_RISING_EDGE, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_HWS_NO_SWAP, \ + MSP_HWS_NO_SWAP, \ + MSP_COMPRESS_MODE_LINEAR, \ + MSP_EXPAND_MODE_LINEAR, \ + MSP_SPI_CLOCK_MODE_NON_SPI, \ + MSP_SPI_BURST_MODE_DISABLE, \ + MSP_FRAME_SYNC_IGNORE, \ + 255, \ + 0, \ + 256, \ +} + +/* + * AC97: Double phase, 1 element of 16 bits during first phase, + * 12 elements of 20 bits in second phase. + */ +#define AC97_PROTOCOL_DESC \ +{ \ + MSP_DUAL_PHASE, \ + MSP_DUAL_PHASE, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_12, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_12, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_20, \ + MSP_ELEM_LENGTH_16, \ + MSP_ELEM_LENGTH_20, \ + MSP_DELAY_1, \ + MSP_DELAY_1, \ + MSP_FALLING_EDGE, \ + MSP_RISING_EDGE, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_HWS_NO_SWAP, \ + MSP_HWS_NO_SWAP, \ + MSP_COMPRESS_MODE_LINEAR, \ + MSP_EXPAND_MODE_LINEAR, \ + MSP_SPI_CLOCK_MODE_NON_SPI, \ + MSP_SPI_BURST_MODE_DISABLE, \ + MSP_FRAME_SYNC_IGNORE, \ + 255, \ + 0, \ + 256, \ +} + +#define SPI_MASTER_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_SINGLE_PHASE, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_1, \ + MSP_DELAY_1, \ + MSP_RISING_EDGE, \ + MSP_FALLING_EDGE, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_HWS_NO_SWAP, \ + MSP_HWS_NO_SWAP, \ + MSP_COMPRESS_MODE_LINEAR, \ + MSP_EXPAND_MODE_LINEAR, \ + MSP_SPI_CLOCK_MODE_NON_SPI, \ + MSP_SPI_BURST_MODE_DISABLE, \ + MSP_FRAME_SYNC_IGNORE, \ + 255, \ + 0, \ + 256, \ +} + +#define SPI_SLAVE_PROTOCOL_DESC \ +{ \ + MSP_SINGLE_PHASE, \ + MSP_SINGLE_PHASE, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_PHASE2_START_MODE_FRAME_SYNC, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_BTF_MS_BIT_FIRST, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_FRAME_LENGTH_1, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_ELEM_LENGTH_8, \ + MSP_DELAY_1, \ + MSP_DELAY_1, \ + MSP_RISING_EDGE, \ + MSP_FALLING_EDGE, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, \ + MSP_HWS_NO_SWAP, \ + MSP_HWS_NO_SWAP, \ + MSP_COMPRESS_MODE_LINEAR, \ + MSP_EXPAND_MODE_LINEAR, \ + MSP_SPI_CLOCK_MODE_NON_SPI, \ + MSP_SPI_BURST_MODE_DISABLE, \ + MSP_FRAME_SYNC_IGNORE, \ + 255, \ + 0, \ + 256, \ +} + +#define MSP_FRAME_PERIOD_IN_MONO_MODE 256 +#define MSP_FRAME_PERIOD_IN_STEREO_MODE 32 +#define MSP_FRAME_WIDTH_IN_STEREO_MODE 16 + +/* + * No of registers to backup during + * suspend resume + */ +#define MAX_MSP_BACKUP_REGS 36 + +enum enum_i2s_controller { + MSP_0_I2S_CONTROLLER = 1, + MSP_1_I2S_CONTROLLER, + MSP_2_I2S_CONTROLLER, + MSP_3_I2S_CONTROLLER, +}; + +/** + * struct msp - Main msp controller data structure per MSP. + * @work_mode: Mode i.e dma, polling or interrupt. + * @id: Controller id like MSP1 or MSP2 etc. + * @msp_io_error: To indicate error while transferring. + * @registers: MSP's register base address. + * @actual_data_size: Data size in which data needs to send or receive. + * @irq: MSP's irq number. + * @i2s_cont: MSP's Controller's structure pointer created per MSP. + * @lock: semaphore lock acquired while configuring msp. + * @dma_cfg_tx: TX DMA configuration + * @dma_cfg_rx: RX DMA configuration + * @tx_pipeid: TX DMA channel + * @rx_pipeid: RX DMA channel + * @msp_state: Current state of msp. + * @read: Function pointer for read, u8_msp_read,u16_msp_read,u32_msp_read. + * @write: Function pointer for write, u8_msp_write,u16_msp_write,u32_msp_write. + * @transfer: Function pointer for type of transfer i.e dma,polling or interrupt + * @xfer_data: MSP's transfer data structure. Contains info about current xfer. + * @plat_init: MSP's initialization function. + * @plat_exit: MSP's Exit function. + * @notify_timer: Timer used in Polling mode to prevent hang. + * @polling_flag: Flag used in error handling while polling. + * @def_elem_len: Flag indicates whether default elem len to be used in + * protocol_desc or not. + * @vape_opp_constraint: 1 if constraint is applied to have vape at 100OPP; 0 otherwise + * @infinite: true if an infinite transfer has been configured + * + * Main Msp private data structure to be used to store various info of a + * particular MSP.Longer description + */ +struct msp { + int work_mode; + enum enum_i2s_controller id; + int msp_io_error; + void __iomem *registers; + enum msp_data_size actual_data_size; + int irq; + struct i2s_controller *i2s_cont; + struct semaphore lock; + struct stedma40_chan_cfg *dma_cfg_rx; + struct stedma40_chan_cfg *dma_cfg_tx; + struct dma_chan *tx_pipeid; + struct dma_chan *rx_pipeid; + enum msp_state msp_state; + void (*read) (struct trans_data *xfer_data); + void (*write) (struct trans_data *xfer_data); + int (*transfer) (struct msp *msp, struct i2s_message *message); + struct trans_data xfer_data; + int (*plat_init) (void); + int (*plat_exit) (void); + struct timer_list notify_timer; + int polling_flag; + int def_elem_len; + struct clk *clk; + unsigned int direction; + int users; + int loopback_enable; + u32 backup_regs[MAX_MSP_BACKUP_REGS]; + int vape_opp_constraint; + bool infinite; +}; + +/** + * struct msp_i2s_platform_data - Main msp controller platform data structure. + * @id: Controller id like MSP1 or MSP2 etc. + * @msp_i2s_dma_rx: RX DMA channel config + * @msp_i2s_dma_tx: RX DMA channel config + * @msp_i2s_init: MSP's initialization function. + * @msp_i2s_exit: MSP's Exit function. + * @backup_regs: used for backup registers during suspend resume. + * + * Platform data structure passed by devices.c file. + */ +struct msp_i2s_platform_data { + enum enum_i2s_controller id; + struct stedma40_chan_cfg *msp_i2s_dma_rx; + struct stedma40_chan_cfg *msp_i2s_dma_tx; + int (*msp_i2s_init) (void); + int (*msp_i2s_exit) (void); +}; + +#endif diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index de35c3ad8a6..4edbcff78fb 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -113,6 +113,20 @@ config SPI_BITBANG need it. You only need to select this explicitly to support driver modules that aren't part of this kernel tree. +config STM_MSP_SPI + tristate "STM MSP CONTROLLER (SPI master)" + default y + help + This enables using the STM MSP controller in master + mode. + +config SPI_WORKQUEUE + bool "SPI_WORKQUEUE" + depends on STM_MSP_SPI + default n + help + This feature allow SPI works to be deferred in MSP driver. + config SPI_BUTTERFLY tristate "Parallel port adapter for AVR Butterfly (DEVELOPMENT)" depends on PARPORT diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 0f8c69b6b19..d50739d10d5 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_msiof.o obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o obj-$(CONFIG_SPI_NUC900) += spi_nuc900.o +obj-$(CONFIG_STM_MSP_SPI) += stm_msp.o # special build for s3c24xx spi driver with fiq support spi_s3c24xx_hw-y := spi_s3c24xx.o diff --git a/drivers/spi/stm_msp.c b/drivers/spi/stm_msp.c new file mode 100644 index 00000000000..6b9d7bb75e5 --- /dev/null +++ b/drivers/spi/stm_msp.c @@ -0,0 +1,1929 @@ +/* + * drivers/spi/stm_msp.c + * + * Copyright (C) 2010 STMicroelectronics Pvt. Ltd. + * + * Author: Sachin Verma + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * MSP Controller Register Offsets + */ +#define MSP_DR(r) (r + 0x000) +#define MSP_GCR(r) (r + 0x004) +#define MSP_TCF(r) (r + 0x008) +#define MSP_RCF(r) (r + 0x00C) +#define MSP_SRG(r) (r + 0x010) +#define MSP_FLR(r) (r + 0x014) +#define MSP_DMACR(r) (r + 0x018) +#define MSP_IMSC(r) (r + 0x020) +#define MSP_RIS(r) (r + 0x024) +#define MSP_MIS(r) (r + 0x028) +#define MSP_ICR(r) (r + 0x02C) +#define MSP_MCR(r) (r + 0x030) +#define MSP_RCV(r) (r + 0x034) +#define MSP_RCM(r) (r + 0x038) +#define MSP_TCE0(r) (r + 0x040) +#define MSP_TCE1(r) (r + 0x044) +#define MSP_TCE2(r) (r + 0x048) +#define MSP_TCE3(r) (r + 0x04C) +#define MSP_RCE0(r) (r + 0x060) +#define MSP_RCE1(r) (r + 0x064) +#define MSP_RCE2(r) (r + 0x068) +#define MSP_RCE3(r) (r + 0x06C) +#define MSP_PID0(r) (r + 0xFE0) +#define MSP_PID1(r) (r + 0xFE4) +#define MSP_PID2(r) (r + 0xFE8) +#define MSP_PID3(r) (r + 0xFEC) + +/** + * MSP Global Configuration Register - MSP_GCR + */ +#define MSP_GCR_MASK_RXEN ((u32)(0x1UL << 0)) +#define MSP_GCR_MASK_RFFEN ((u32)(0x1UL << 1)) +#define MSP_GCR_MASK_RFSPOL ((u32)(0x1UL << 2)) +#define MSP_GCR_MASK_DCM ((u32)(0x1UL << 3)) +#define MSP_GCR_MASK_RFSSEL ((u32)(0x1UL << 4)) +#define MSP_GCR_MASK_RCKPOL ((u32)(0x1UL << 5)) +#define MSP_GCR_MASK_RCKSEL ((u32)(0x1UL << 6)) +#define MSP_GCR_MASK_LBM ((u32)(0x1UL << 7)) +#define MSP_GCR_MASK_TXEN ((u32)(0x1UL << 8)) +#define MSP_GCR_MASK_TFFEN ((u32)(0x1UL << 9)) +#define MSP_GCR_MASK_TFSPOL ((u32)(0x1UL << 10)) +#define MSP_GCR_MASK_TFSSEL ((u32)(0x3UL << 11)) +#define MSP_GCR_MASK_TCKPOL ((u32)(0x1UL << 13)) +#define MSP_GCR_MASK_TCKSEL ((u32)(0x1UL << 14)) +#define MSP_GCR_MASK_TXDDL ((u32)(0x1UL << 15)) +#define MSP_GCR_MASK_SGEN ((u32)(0x1UL << 16)) +#define MSP_GCR_MASK_SCKPOL ((u32)(0x1UL << 17)) +#define MSP_GCR_MASK_SCKSEL ((u32)(0x3UL << 18)) +#define MSP_GCR_MASK_FGEN ((u32)(0x1UL << 20)) +#define MSP_GCR_MASK_SPICKM ((u32)(0x3UL << 21)) +#define MSP_GCR_MASK_SPIBME ((u32)(0x1UL << 23)) + +/** + * MSP Transmit Configuration Register - MSP_TCF + */ +#define MSP_TCF_MASK_TP1ELEN ((u32)(0x7UL << 0)) +#define MSP_TCF_MASK_TP1FLEN ((u32)(0x7FUL << 3)) +#define MSP_TCF_MASK_TDTYP ((u32)(0x3UL << 10)) +#define MSP_TCF_MASK_TENDN ((u32)(0x1UL << 12)) +#define MSP_TCF_MASK_TDDLY ((u32)(0x3UL << 13)) +#define MSP_TCF_MASK_TFSIG ((u32)(0x1UL << 15)) +#define MSP_TCF_MASK_TP2ELEN ((u32)(0x7UL << 16)) +#define MSP_TCF_MASK_TP2FLEN ((u32)(0x7FUL << 19)) +#define MSP_TCF_MASK_TP2SM ((u32)(0x1UL << 26)) +#define MSP_TCF_MASK_TP2EN ((u32)(0x1UL << 27)) +#define MSP_TCF_MASK_TBSWAP ((u32)(0x3UL << 28)) + +/** + * MSP Receive Configuration Register - MSP_RCF + */ +#define MSP_RCF_MASK_RP1ELEN ((u32)(0x7UL << 0)) +#define MSP_RCF_MASK_RP1FLEN ((u32)(0x7FUL << 3)) +#define MSP_RCF_MASK_RDTYP ((u32)(0x3UL << 10)) +#define MSP_RCF_MASK_RENDN ((u32)(0x1UL << 12)) +#define MSP_RCF_MASK_RDDLY ((u32)(0x3UL << 13)) +#define MSP_RCF_MASK_RFSIG ((u32)(0x1UL << 15)) +#define MSP_RCF_MASK_RP2ELEN ((u32)(0x7UL << 16)) +#define MSP_RCF_MASK_RP2FLEN ((u32)(0x7FUL << 19)) +#define MSP_RCF_MASK_RP2SM ((u32)(0x1UL << 26)) +#define MSP_RCF_MASK_RP2EN ((u32)(0x1UL << 27)) +#define MSP_RCF_MASK_RBSWAP ((u32)(0x3UL << 28)) + +/** + * MSP Sample Rate Generator Register - MSP_SRG + */ +#define MSP_SRG_MASK_SCKDIV ((u32)(0x3FFUL << 0)) +#define MSP_SRG_MASK_FRWID ((u32)(0x3FUL << 10)) +#define MSP_SRG_MASK_FRPER ((u32)(0x1FFFUL << 16)) + +/** + * MSP Flag Register - MSP_FLR + */ +#define MSP_FLR_MASK_RBUSY ((u32)(0x1UL << 0)) +#define MSP_FLR_MASK_RFE ((u32)(0x1UL << 1)) +#define MSP_FLR_MASK_RFU ((u32)(0x1UL << 2)) +#define MSP_FLR_MASK_TBUSY ((u32)(0x1UL << 3)) +#define MSP_FLR_MASK_TFE ((u32)(0x1UL << 4)) +#define MSP_FLR_MASK_TFU ((u32)(0x1UL << 5)) + +/** + * MSP DMA Control Register - MSP_DMACR + */ +#define MSP_DMACR_MASK_RDMAE ((u32)(0x1UL << 0)) +#define MSP_DMACR_MASK_TDMAE ((u32)(0x1UL << 1)) + +/** + * MSP Interrupt Mask Set/Clear Register - MSP_IMSC + */ +#define MSP_IMSC_MASK_RXIM ((u32)(0x1UL << 0)) +#define MSP_IMSC_MASK_ROEIM ((u32)(0x1UL << 1)) +#define MSP_IMSC_MASK_RSEIM ((u32)(0x1UL << 2)) +#define MSP_IMSC_MASK_RFSIM ((u32)(0x1UL << 3)) +#define MSP_IMSC_MASK_TXIM ((u32)(0x1UL << 4)) +#define MSP_IMSC_MASK_TUEIM ((u32)(0x1UL << 5)) +#define MSP_IMSC_MASK_TSEIM ((u32)(0x1UL << 6)) +#define MSP_IMSC_MASK_TFSIM ((u32)(0x1UL << 7)) +#define MSP_IMSC_MASK_RFOIM ((u32)(0x1UL << 8)) +#define MSP_IMSC_MASK_TFOIM ((u32)(0x1UL << 9)) + +/** + * MSP Raw Interrupt status Register - MSP_RIS + */ +#define MSP_RIS_MASK_RXRIS ((u32)(0x1UL << 0)) +#define MSP_RIS_MASK_ROERIS ((u32)(0x1UL << 1)) +#define MSP_RIS_MASK_RSERIS ((u32)(0x1UL << 2)) +#define MSP_RIS_MASK_RFSRIS ((u32)(0x1UL << 3)) +#define MSP_RIS_MASK_TXRIS ((u32)(0x1UL << 4)) +#define MSP_RIS_MASK_TUERIS ((u32)(0x1UL << 5)) +#define MSP_RIS_MASK_TSERIS ((u32)(0x1UL << 6)) +#define MSP_RIS_MASK_TFSRIS ((u32)(0x1UL << 7)) +#define MSP_RIS_MASK_RFORIS ((u32)(0x1UL << 8)) +#define MSP_RIS_MASK_TFORIS ((u32)(0x1UL << 9)) + +/** + * MSP Masked Interrupt status Register - MSP_MIS + */ +#define MSP_MIS_MASK_RXMIS ((u32)(0x1UL << 0)) +#define MSP_MIS_MASK_ROEMIS ((u32)(0x1UL << 1)) +#define MSP_MIS_MASK_RSEMIS ((u32)(0x1UL << 2)) +#define MSP_MIS_MASK_RFSMIS ((u32)(0x1UL << 3)) +#define MSP_MIS_MASK_TXMIS ((u32)(0x1UL << 4)) +#define MSP_MIS_MASK_TUEMIS ((u32)(0x1UL << 5)) +#define MSP_MIS_MASK_TSEMIS ((u32)(0x1UL << 6)) +#define MSP_MIS_MASK_TFSMIS ((u32)(0x1UL << 7)) +#define MSP_MIS_MASK_RFOMIS ((u32)(0x1UL << 8)) +#define MSP_MIS_MASK_TFOMIS ((u32)(0x1UL << 9)) + +/** + * MSP Interrupt Clear Register - MSP_ICR + */ +#define MSP_ICR_MASK_ROEIC ((u32)(0x1UL << 1)) +#define MSP_ICR_MASK_RSEIC ((u32)(0x1UL << 2)) +#define MSP_ICR_MASK_RFSIC ((u32)(0x1UL << 3)) +#define MSP_ICR_MASK_TUEIC ((u32)(0x1UL << 5)) +#define MSP_ICR_MASK_TSEIC ((u32)(0x1UL << 6)) +#define MSP_ICR_MASK_TFSIC ((u32)(0x1UL << 7)) + +#define GEN_MASK_BITS(val, mask, sb) ((u32)((((u32)val) << (sb)) & (mask))) +#define MSP_WBITS(reg, val, mask, sb) ((reg) = (((reg) & ~(mask)) |\ + (((val) << (sb)) & (mask)))) +#define DEFAULT_MSP_REG_DMACR 0x00000000 +#define DEFAULT_MSP_REG_SRG 0x1FFF0000 + +#define DEFAULT_MSP_REG_GCR ( \ + GEN_MASK_BITS(MSP_RECEIVER_DISABLED, MSP_GCR_MASK_RXEN, 0) |\ + GEN_MASK_BITS(MSP_RX_FIFO_ENABLED, MSP_GCR_MASK_RFFEN, 1) |\ + GEN_MASK_BITS(MSP_LOOPBACK_DISABLED, MSP_GCR_MASK_LBM, 7) |\ + GEN_MASK_BITS(MSP_TRANSMITTER_DISABLED, MSP_GCR_MASK_TXEN, 8) |\ + GEN_MASK_BITS(MSP_TX_FIFO_ENABLED, MSP_GCR_MASK_TFFEN, 9) |\ + GEN_MASK_BITS(MSP_TX_FRAME_SYNC_POL_LOW, MSP_GCR_MASK_TFSPOL, 10)|\ + GEN_MASK_BITS(MSP_TX_FRAME_SYNC_INT, MSP_GCR_MASK_TFSSEL, 11) |\ + GEN_MASK_BITS(MSP_TX_CLOCK_POL_HIGH, MSP_GCR_MASK_TCKPOL, 13) |\ + GEN_MASK_BITS(MSP_IS_SPI_MASTER, MSP_GCR_MASK_TCKSEL, 14) |\ + GEN_MASK_BITS(MSP_TRANSMIT_DATA_WITHOUT_DELAY, MSP_GCR_MASK_TXDDL, 15)|\ + GEN_MASK_BITS(MSP_SAMPLE_RATE_GEN_ENABLE, MSP_GCR_MASK_SGEN, 16)|\ + GEN_MASK_BITS(MSP_CLOCK_INTERNAL, MSP_GCR_MASK_SCKSEL, 18) |\ + GEN_MASK_BITS(MSP_FRAME_GEN_ENABLE, MSP_GCR_MASK_FGEN, 20) |\ + GEN_MASK_BITS(MSP_SPI_PHASE_ZERO_CYCLE_DELAY, MSP_GCR_MASK_SPICKM, 21)|\ + GEN_MASK_BITS(SPI_BURST_MODE_DISABLE, MSP_GCR_MASK_SPIBME, 23)\ + ) +#define DEFAULT_MSP_REG_RCF ( \ + GEN_MASK_BITS(MSP_DATA_BITS_32, MSP_RCF_MASK_RP1ELEN, 0) | \ + GEN_MASK_BITS(MSP_IGNORE_RX_FRAME_SYNC_PULSE, MSP_RCF_MASK_RFSIG, 15) |\ + GEN_MASK_BITS(MSP_RX_1BIT_DATA_DELAY, MSP_RCF_MASK_RDDLY, 13) | \ + GEN_MASK_BITS(MSP_RX_ENDIANESS_LSB, MSP_RCF_MASK_RENDN, 12) \ + ) + +#define DEFAULT_MSP_REG_TCF ( \ + GEN_MASK_BITS(MSP_DATA_BITS_32, MSP_TCF_MASK_TP1ELEN, 0) | \ + GEN_MASK_BITS(MSP_IGNORE_TX_FRAME_SYNC_PULSE, MSP_TCF_MASK_TFSIG, 15) |\ + GEN_MASK_BITS(MSP_TX_1BIT_DATA_DELAY, MSP_TCF_MASK_TDDLY, 13) | \ + GEN_MASK_BITS(MSP_TX_ENDIANESS_LSB, MSP_TCF_MASK_TENDN, 12) \ + ) + +/** + * MSP Receiver/Transmitter states (enabled or disabled) + */ +#define MSP_RECEIVER_DISABLED 0 +#define MSP_RECEIVER_ENABLED 1 +#define MSP_TRANSMITTER_DISABLED 0 +#define MSP_TRANSMITTER_ENABLED 1 + +/** + * MSP Receiver/Transmitter FIFO constants + */ +#define MSP_LOOPBACK_DISABLED 0 +#define MSP_LOOPBACK_ENABLED 1 + +#define MSP_TX_FIFO_DISABLED 0 +#define MSP_TX_FIFO_ENABLED 1 +#define MSP_TX_ENDIANESS_MSB 0 +#define MSP_TX_ENDIANESS_LSB 1 + +#define MSP_RX_FIFO_DISABLED 0 +#define MSP_RX_FIFO_ENABLED 1 +#define MSP_RX_ENDIANESS_MSB 0 +#define MSP_RX_ENDIANESS_LSB 1 + +#define MSP_TX_FRAME_SYNC_EXT 0x0 +#define MSP_TX_FRAME_SYNC_INT 0x2 +#define MSP_TX_FRAME_SYNC_INT_CFG 0x3 + +#define MSP_TX_FRAME_SYNC_POL_HIGH 0 +#define MSP_TX_FRAME_SYNC_POL_LOW 1 + +#define MSP_HANDLE_RX_FRAME_SYNC_PULSE 0 +#define MSP_IGNORE_RX_FRAME_SYNC_PULSE 1 + +#define MSP_RX_NO_DATA_DELAY 0x0 +#define MSP_RX_1BIT_DATA_DELAY 0x1 +#define MSP_RX_2BIT_DATA_DELAY 0x2 +#define MSP_RX_3BIT_DATA_DELAY 0x3 + +#define MSP_HANDLE_TX_FRAME_SYNC_PULSE 0 +#define MSP_IGNORE_TX_FRAME_SYNC_PULSE 1 + +#define MSP_TX_NO_DATA_DELAY 0x0 +#define MSP_TX_1BIT_DATA_DELAY 0x1 +#define MSP_TX_2BIT_DATA_DELAY 0x2 +#define MSP_TX_3BIT_DATA_DELAY 0x3 + +#define MSP_TX_CLOCK_POL_LOW 0 +#define MSP_TX_CLOCK_POL_HIGH 1 + +#define MSP_SPI_PHASE_ZERO_CYCLE_DELAY 0x2 +#define MSP_SPI_PHASE_HALF_CYCLE_DELAY 0x3 + +#define MSP_IS_SPI_SLAVE 0 +#define MSP_IS_SPI_MASTER 1 + +#define MSP_FRAME_GEN_DISABLE 0 +#define MSP_FRAME_GEN_ENABLE 1 + +#define MSP_SAMPLE_RATE_GEN_DISABLE 0 +#define MSP_SAMPLE_RATE_GEN_ENABLE 1 + +#define SPI_BURST_MODE_DISABLE 0 +#define SPI_BURST_MODE_ENABLE 1 + +#define MSP_TRANSMIT_DATA_WITHOUT_DELAY 0 +#define MSP_TRANSMIT_DATA_WITH_DELAY 1 + +#define MSP_CLOCK_INTERNAL 0x0 /* 48 MHz */ + +/* SRG is derived from MSPSCK pin but is resynchronized on MSPRFS + * (Receive Frame Sync signal) */ +#define MSP_CLOCK_EXTERNAL 0x2 +#define MSP_CLOCK_EXTERNAL_RESYNC 0x3 + +#define DISABLE_ALL_MSP_INTERRUPTS (0x0) +#define ENABLE_ALL_MSP_INTERRUPTS (0x333) +#define CLEAR_ALL_MSP_INTERRUPTS (0xEE) +#define DEFAULT_MSP_CLK (48000000) +#define MAX_SCKDIV (1023) + +#define MSP_FIFO_DEPTH 8 + +/** + * Queue State + */ +#define QUEUE_RUNNING (0) +#define QUEUE_STOPPED (1) + +#define START_STATE ((void *)0) +#define RUNNING_STATE ((void *)1) +#define DONE_STATE ((void *)2) +#define ERROR_STATE ((void *)-1) + +/* Default values */ +#define SPI_DEFAULT_MAX_SPEED_HZ 48000 +#define SPI_TRANSFER_TIMEOUT_MS 5000 + +/* CONTROLLER COMMANDS */ +enum cntlr_commands { + DISABLE_CONTROLLER = 0, + ENABLE_CONTROLLER , + DISABLE_ALL_INTERRUPT , + ENABLE_ALL_INTERRUPT , + FLUSH_FIFO , + RESTORE_STATE , + LOAD_DEFAULT_CONFIG , + CLEAR_ALL_INTERRUPT, +}; + +struct stm_msp { + struct amba_device *adev; + struct spi_master *master; + struct stm_msp_controller *master_info; + void __iomem *regs; + struct clk *clk; +#ifdef CONFIG_SPI_WORKQUEUE + struct workqueue_struct *workqueue; +#endif + struct work_struct spi_work; + spinlock_t lock; + struct list_head queue; + int busy; + int run; + struct tasklet_struct pump_transfers; + struct timer_list spi_notify_timer; + int spi_io_error; + struct spi_message *cur_msg; + struct spi_transfer *cur_transfer; + struct chip_data *cur_chip; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + void (*write)(struct stm_msp *stm_msp); + void (*read)(struct stm_msp *stm_msp); + void (*delay)(struct stm_msp *stm_msp); +}; + +/** + * struct chip_data - To maintain runtime state of SPICntlr for each client chip + * @ctr_regs: void pointer which is assigned a struct having regs of the cntlr. + * @chip_id: Chip Id assigned to this client to identify it. + * @n_bytes: how many bytes(power of 2) reqd for a given data width of client + * @write: function to be used to write when doing xfer for this chip + * @null_write: function to be used for dummy write for receiving data. + * @read: function to be used to read when doing xfer for this chip + * @null_read: function to be used to for dummy read while writting data. + * @cs_control: chip select callback provided by chip + * @xfer_type: polling/interrupt + * + * Runtime state of the SPI controller, maintained per chip, + * This would be set according to the current message that would be served + */ +struct chip_data { + void *ctr_regs; + u32 chip_id; + u8 n_bytes; + void (*write) (struct stm_msp *stm_msp); + void (*null_write) (struct stm_msp *stm_msp); + void (*read) (struct stm_msp *stm_msp); + void (*null_read) (struct stm_msp *stm_msp); + void (*delay) (struct stm_msp *stm_msp); + void (*cs_control) (u32 command); + int xfer_type; +}; + +/** + * struct msp_regs - Used to store MSP controller registry values + * used by the driver. + * @gcr: global configuration register + * @tcf: transmit configuration register + * @rcf: receive configuration register + * @srg: sample rate generator register + * @dmacr: DMA configuration register + */ +struct msp_regs { + u32 gcr; + u32 tcf; + u32 rcf; + u32 srg; + u32 dmacr; +}; + +/** + * stm_msp_controller_cmd - To execute controller commands for MSP + * @stm_msp: SPI driver private data structure + * @cmd: Command which is to be executed on the controller + */ +static int stm_msp_controller_cmd(struct stm_msp *stm_msp, int cmd) +{ + int retval = 0; + struct msp_regs *msp_regs = NULL; + + switch (cmd) { + case DISABLE_CONTROLLER: { + dev_dbg(&stm_msp->adev->dev, + "Disabling MSP controller...\n"); + writel((readl(MSP_GCR(stm_msp->regs)) & + (~(MSP_GCR_MASK_TXEN | MSP_GCR_MASK_RXEN))), + MSP_GCR(stm_msp->regs)); + break; + } + case ENABLE_CONTROLLER: { + dev_dbg(&stm_msp->adev->dev, + "Enabling MSP controller...\n"); + writel((readl(MSP_GCR(stm_msp->regs)) | + (MSP_GCR_MASK_TXEN | MSP_GCR_MASK_RXEN)), + MSP_GCR(stm_msp->regs)); + break; + } + case DISABLE_ALL_INTERRUPT: { + dev_dbg(&stm_msp->adev->dev, + "Disabling all MSP interrupts...\n"); + writel(DISABLE_ALL_MSP_INTERRUPTS, + MSP_IMSC(stm_msp->regs)); + break; + } + case ENABLE_ALL_INTERRUPT: { + dev_dbg(&stm_msp->adev->dev, + "Enabling all MSP interrupts...\n"); + writel(ENABLE_ALL_MSP_INTERRUPTS, + MSP_IMSC(stm_msp->regs)); + break; + } + case CLEAR_ALL_INTERRUPT: { + dev_dbg(&stm_msp->adev->dev, + "Clearing all MSP interrupts...\n"); + writel(CLEAR_ALL_MSP_INTERRUPTS, + MSP_ICR(stm_msp->regs)); + break; + } + case FLUSH_FIFO: { + unsigned long limit = loops_per_jiffy << 1; + + dev_dbg(&stm_msp->adev->dev, "MSP FIFO flushed\n"); + + do { + while (!(readl(MSP_FLR(stm_msp->regs)) & + MSP_FLR_MASK_RFE)) { + readl(MSP_DR(stm_msp->regs)); + } + } while ((readl(MSP_FLR(stm_msp->regs)) & + (MSP_FLR_MASK_TBUSY | MSP_FLR_MASK_RBUSY)) && + limit--); + + retval = limit; + break; + } + case RESTORE_STATE: { + msp_regs = + (struct msp_regs *)stm_msp->cur_chip->ctr_regs; + + dev_dbg(&stm_msp->adev->dev, + "Restoring MSP state...\n"); + + writel(msp_regs->gcr, MSP_GCR(stm_msp->regs)); + writel(msp_regs->tcf, MSP_TCF(stm_msp->regs)); + writel(msp_regs->rcf, MSP_RCF(stm_msp->regs)); + writel(msp_regs->srg, MSP_SRG(stm_msp->regs)); + writel(msp_regs->dmacr, MSP_DMACR(stm_msp->regs)); + writel(DISABLE_ALL_MSP_INTERRUPTS, + MSP_IMSC(stm_msp->regs)); + writel(CLEAR_ALL_MSP_INTERRUPTS, + MSP_ICR(stm_msp->regs)); + break; + } + case LOAD_DEFAULT_CONFIG: { + dev_dbg(&stm_msp->adev->dev, + "Loading default MSP config...\n"); + + writel(DEFAULT_MSP_REG_GCR, MSP_GCR(stm_msp->regs)); + writel(DEFAULT_MSP_REG_TCF, MSP_TCF(stm_msp->regs)); + writel(DEFAULT_MSP_REG_RCF, MSP_RCF(stm_msp->regs)); + writel(DEFAULT_MSP_REG_SRG, MSP_SRG(stm_msp->regs)); + writel(DEFAULT_MSP_REG_DMACR, MSP_DMACR(stm_msp->regs)); + writel(DISABLE_ALL_MSP_INTERRUPTS, + MSP_IMSC(stm_msp->regs)); + writel(CLEAR_ALL_MSP_INTERRUPTS, + MSP_ICR(stm_msp->regs)); + break; + } + default: + dev_dbg(&stm_msp->adev->dev, "Unknown command\n"); + retval = -1; + break; + } + + return retval; +} + +/** + * giveback - current spi_message is over, schedule next spi_message + * @message: current SPI message + * @stm_msp: spi driver private data structure + * + * current spi_message is over, schedule next spi_message and call + * callback of this msg. + */ +static void giveback(struct spi_message *message, struct stm_msp *stm_msp) +{ + struct spi_transfer *last_transfer; + unsigned long flags; + struct spi_message *msg; + void (*curr_cs_control)(u32 command); + + spin_lock_irqsave(&stm_msp->lock, flags); + msg = stm_msp->cur_msg; + + curr_cs_control = stm_msp->cur_chip->cs_control; + + stm_msp->cur_msg = NULL; + stm_msp->cur_transfer = NULL; + stm_msp->cur_chip = NULL; +#ifdef CONFIG_SPI_WORKQUEUE + queue_work(stm_msp->workqueue, &stm_msp->spi_work); +#else + schedule_work(&stm_msp->spi_work); +#endif + spin_unlock_irqrestore(&stm_msp->lock, flags); + + last_transfer = list_entry(msg->transfers.prev, + struct spi_transfer, transfer_list); + + if (!last_transfer->cs_change) + curr_cs_control(SPI_CHIP_DESELECT); + + msg->state = NULL; + + if (msg->complete) + msg->complete(msg->context); + + stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER); + clk_disable(stm_msp->clk); +} + +/** + * spi_notify - Handles Polling hang issue over spi bus. + * @data: main driver data + * Context: Process. + * + * This is used to handle error condition in transfer and receive function used + * in polling mode. + * Sometimes due to passing wrong protocol desc , polling transfer may hang. + * To prevent this, timer is added. + * + * Returns void. + */ +static void spi_notify(unsigned long data) +{ + struct stm_msp *stm_msp = (struct stm_msp *)data; + stm_msp->spi_io_error = 1; + + dev_err(&stm_msp->adev->dev, + "Polling is taking time, maybe device not responding\n"); + + del_timer(&stm_msp->spi_notify_timer); +} + +/** + * stm_msp_transfer - transfer function registered to SPI master framework + * @spi: spi device which is requesting transfer + * @msg: spi message which is to handled is queued to driver queue + * + * This function is registered to the SPI framework for this SPI master + * controller. It will queue the spi_message in the queue of driver if + * the queue is not stopped and return. + */ +static int stm_msp_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct stm_msp *stm_msp = spi_master_get_devdata(spi->master); + unsigned long flags; + + spin_lock_irqsave(&stm_msp->lock, flags); + + if (stm_msp->run == QUEUE_STOPPED) { + spin_unlock_irqrestore(&stm_msp->lock, flags); + return -ESHUTDOWN; + } + dev_err(&spi->dev, "Regular request (No infinite DMA ongoing)\n"); + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + msg->state = START_STATE; + + list_add_tail(&msg->queue, &stm_msp->queue); + + if ((stm_msp->run == QUEUE_RUNNING) && (!stm_msp->busy)) +#ifdef CONFIG_SPI_WORKQUEUE + queue_work(stm_msp->workqueue, &stm_msp->spi_work); +#else + schedule_work(&stm_msp->spi_work); +#endif + spin_unlock_irqrestore(&stm_msp->lock, flags); + return 0; +} + +/** + * next_transfer - Move to the Next transfer in the current spi message + * @stm_msp: spi driver private data structure + * + * This function moves though the linked list of spi transfers in the + * current spi message and returns with the state of current spi + * message i.e whether its last transfer is done(DONE_STATE) or + * Next transfer is ready(RUNNING_STATE) + */ +static void *next_transfer(struct stm_msp *stm_msp) +{ + struct spi_message *msg = stm_msp->cur_msg; + struct spi_transfer *trans = stm_msp->cur_transfer; + + /* Move to next transfer */ + if (trans->transfer_list.next != &msg->transfers) { + stm_msp->cur_transfer = list_entry(trans->transfer_list.next, + struct spi_transfer, + transfer_list); + return RUNNING_STATE; + } + return DONE_STATE; +} + +static void do_interrupt_transfer(void *data) +{ + struct stm_msp *stm_msp = (struct stm_msp *)data; + + stm_msp->tx = (void *)stm_msp->cur_transfer->tx_buf; + stm_msp->tx_end = stm_msp->tx + stm_msp->cur_transfer->len; + + stm_msp->rx = (void *)stm_msp->cur_transfer->rx_buf; + stm_msp->rx_end = stm_msp->rx + stm_msp->cur_transfer->len; + + stm_msp->write = stm_msp->tx ? + stm_msp->cur_chip->write : stm_msp->cur_chip->null_write; + stm_msp->read = stm_msp->rx ? + stm_msp->cur_chip->read : stm_msp->cur_chip->null_read; + + stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT); + + stm_msp_controller_cmd(stm_msp, ENABLE_ALL_INTERRUPT); + stm_msp_controller_cmd(stm_msp, ENABLE_CONTROLLER); +} + +static void do_polling_transfer(void *data) +{ + struct stm_msp *stm_msp = (struct stm_msp *)data; + struct spi_message *message = NULL; + struct spi_transfer *transfer = NULL; + struct spi_transfer *previous = NULL; + struct chip_data *chip; + unsigned long limit = 0; + u32 timer_expire = 0; + + chip = stm_msp->cur_chip; + message = stm_msp->cur_msg; + + while (message->state != DONE_STATE) { + /* Handle for abort */ + if (message->state == ERROR_STATE) + break; + + transfer = stm_msp->cur_transfer; + + /* Delay if requested at end of transfer */ + if (message->state == RUNNING_STATE) { + previous = list_entry(transfer->transfer_list.prev, + struct spi_transfer, + transfer_list); + + if (previous->delay_usecs) + udelay(previous->delay_usecs); + + if (previous->cs_change) + stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT); + } else { + /* START_STATE */ + message->state = RUNNING_STATE; + stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT); + } + + /* Configuration Changing Per Transfer */ + stm_msp->tx = (void *)transfer->tx_buf; + stm_msp->tx_end = stm_msp->tx + stm_msp->cur_transfer->len; + stm_msp->rx = (void *)transfer->rx_buf; + stm_msp->rx_end = stm_msp->rx + stm_msp->cur_transfer->len; + + stm_msp->write = stm_msp->tx ? + stm_msp->cur_chip->write : + stm_msp->cur_chip->null_write; + stm_msp->read = stm_msp->rx ? + stm_msp->cur_chip->read : + stm_msp->cur_chip->null_read; + stm_msp->delay = stm_msp->cur_chip->delay; + + stm_msp_controller_cmd(stm_msp, FLUSH_FIFO); + stm_msp_controller_cmd(stm_msp, ENABLE_CONTROLLER); + + timer_expire = stm_msp->cur_transfer->len / 1024; + + if (!timer_expire) + timer_expire = SPI_TRANSFER_TIMEOUT_MS; + else + timer_expire = + (stm_msp->cur_transfer->len / 1024) * + SPI_TRANSFER_TIMEOUT_MS; + + stm_msp->spi_notify_timer.expires = + jiffies + msecs_to_jiffies(timer_expire); + + add_timer(&stm_msp->spi_notify_timer); + + dev_dbg(&stm_msp->adev->dev, "Polling transfer ongoing...\n"); + + while (stm_msp->tx < stm_msp->tx_end) { + + stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER); + stm_msp->read(stm_msp); + stm_msp->write(stm_msp); + + stm_msp_controller_cmd(stm_msp, ENABLE_CONTROLLER); + + if (stm_msp->delay) + stm_msp->delay(stm_msp); + + if (stm_msp->spi_io_error == 1) + break; + } + + del_timer(&stm_msp->spi_notify_timer); + + if (stm_msp->spi_io_error == 1) + goto out; + + limit = loops_per_jiffy << 1; + + while ((stm_msp->rx < stm_msp->rx_end) && (limit--)) + stm_msp->read(stm_msp); + + /* Update total byte transfered */ + message->actual_length += stm_msp->cur_transfer->len; + + if (stm_msp->cur_transfer->cs_change) + stm_msp->cur_chip->cs_control(SPI_CHIP_DESELECT); + + stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER); + + /* Move to next transfer */ + message->state = next_transfer(stm_msp); + } +out: + /* Handle end of message */ + if (message->state == DONE_STATE) + message->status = 0; + else + message->status = -EIO; + + giveback(message, stm_msp); + + stm_msp->spi_io_error = 0; /* Reset state for further transfers */ + + return; +} + +/** + * pump_messages - Workqueue function which processes spi message queue + * @work: pointer to work + * + * This function checks if there is any spi message in the queue that + * needs processing and delegate control to appropriate function + * do_polling_transfer()/do_interrupt_transfer()/do_dma_transfer() + * based on the kind of the transfer + * + */ +static void pump_messages(struct work_struct *work) +{ + struct stm_msp *stm_msp = container_of(work, struct stm_msp, spi_work); + unsigned long flags; + + /* Lock queue and check for queue work */ + spin_lock_irqsave(&stm_msp->lock, flags); + + if (list_empty(&stm_msp->queue) || stm_msp->run == QUEUE_STOPPED) { + dev_dbg(&stm_msp->adev->dev, "work_queue: Queue Empty\n"); + stm_msp->busy = 0; + spin_unlock_irqrestore(&stm_msp->lock, flags); + return; + } + /* Make sure we are not already running a message */ + if (stm_msp->cur_msg) { + spin_unlock_irqrestore(&stm_msp->lock, flags); + return; + } + + clk_enable(stm_msp->clk); + + /* Extract head of queue */ + stm_msp->cur_msg = list_entry(stm_msp->queue.next, + struct spi_message, + queue); + + list_del_init(&stm_msp->cur_msg->queue); + stm_msp->busy = 1; + spin_unlock_irqrestore(&stm_msp->lock, flags); + + /* Initial message state */ + stm_msp->cur_msg->state = START_STATE; + stm_msp->cur_transfer = list_entry(stm_msp->cur_msg->transfers.next, + struct spi_transfer, + transfer_list); + + /* Setup the SPI using the per chip configuration */ + stm_msp->cur_chip = spi_get_ctldata(stm_msp->cur_msg->spi); + stm_msp_controller_cmd(stm_msp, RESTORE_STATE); + stm_msp_controller_cmd(stm_msp, FLUSH_FIFO); + + if (stm_msp->cur_chip->xfer_type == SPI_POLLING_TRANSFER) + do_polling_transfer(stm_msp); + else if (stm_msp->cur_chip->xfer_type == SPI_INTERRUPT_TRANSFER) + do_interrupt_transfer(stm_msp); +} + +/** + * pump_transfers - Tasklet function which schedules next interrupt xfer + * @data: spi driver private data structure + */ +static void pump_transfers(unsigned long data) +{ + struct stm_msp *stm_msp = (struct stm_msp *)data; + struct spi_message *message = NULL; + struct spi_transfer *transfer = NULL; + struct spi_transfer *previous = NULL; + + message = stm_msp->cur_msg; + + /* Handle for abort */ + if (message->state == ERROR_STATE) { + message->status = -EIO; + giveback(message, stm_msp); + return; + } + + /* Handle end of message */ + if (message->state == DONE_STATE) { + message->status = 0; + giveback(message, stm_msp); + return; + } + transfer = stm_msp->cur_transfer; + + /* Delay if requested at end of transfer */ + if (message->state == RUNNING_STATE) { + previous = list_entry(transfer->transfer_list.prev, + struct spi_transfer, transfer_list); + if (previous->delay_usecs) + udelay(previous->delay_usecs); + if (previous->cs_change) + stm_msp->cur_chip->cs_control(SPI_CHIP_SELECT); + } else { + /* START_STATE */ + message->state = RUNNING_STATE; + } + stm_msp->tx = (void *)transfer->tx_buf; + stm_msp->tx_end = stm_msp->tx + stm_msp->cur_transfer->len; + stm_msp->rx = (void *)transfer->rx_buf; + stm_msp->rx_end = stm_msp->rx + stm_msp->cur_transfer->len; + + stm_msp->write = stm_msp->tx ? + stm_msp->cur_chip->write : stm_msp->cur_chip->null_write; + stm_msp->read = stm_msp->rx ? + stm_msp->cur_chip->read : stm_msp->cur_chip->null_read; + + stm_msp_controller_cmd(stm_msp, FLUSH_FIFO); + stm_msp_controller_cmd(stm_msp, ENABLE_ALL_INTERRUPT); +} + +static int init_queue(struct stm_msp *stm_msp) +{ + INIT_LIST_HEAD(&stm_msp->queue); + spin_lock_init(&stm_msp->lock); + + stm_msp->run = QUEUE_STOPPED; + stm_msp->busy = 0; + + tasklet_init(&stm_msp->pump_transfers, pump_transfers, + (unsigned long)stm_msp); + INIT_WORK(&stm_msp->spi_work, pump_messages); + +#ifdef CONFIG_SPI_WORKQUEUE + stm_msp->workqueue = create_singlethread_workqueue( + dev_name(&stm_msp->master->dev)); + + if (stm_msp->workqueue == NULL) + return -EBUSY; +#endif /* CONFIG_SPI_WORKQUEUE */ + + init_timer(&stm_msp->spi_notify_timer); + + stm_msp->spi_notify_timer.expires = jiffies + msecs_to_jiffies(1000); + stm_msp->spi_notify_timer.function = spi_notify; + stm_msp->spi_notify_timer.data = (unsigned long)stm_msp; + + return 0; +} + +static int start_queue(struct stm_msp *stm_msp) +{ + unsigned long flags; + + spin_lock_irqsave(&stm_msp->lock, flags); + + if (stm_msp->run == QUEUE_RUNNING || stm_msp->busy) { + spin_unlock_irqrestore(&stm_msp->lock, flags); + return -EBUSY; + } + + stm_msp->run = QUEUE_RUNNING; + stm_msp->cur_msg = NULL; + stm_msp->cur_transfer = NULL; + stm_msp->cur_chip = NULL; + spin_unlock_irqrestore(&stm_msp->lock, flags); + return 0; +} + +static int stop_queue(struct stm_msp *stm_msp) +{ + unsigned long flags; + unsigned limit = 500; + int status = 0; + + spin_lock_irqsave(&stm_msp->lock, flags); + + /* This is a bit lame, but is optimized for the common execution path. + * A wait_queue on the stm_msp->busy could be used, but then the common + * execution path (pump_messages) would be required to call wake_up or + * friends on every SPI message. Do this instead */ + + stm_msp->run = QUEUE_STOPPED; + + while (!list_empty(&stm_msp->queue) && stm_msp->busy && limit--) { + spin_unlock_irqrestore(&stm_msp->lock, flags); + msleep(10); + spin_lock_irqsave(&stm_msp->lock, flags); + } + + if (!list_empty(&stm_msp->queue) || stm_msp->busy) + status = -EBUSY; + + spin_unlock_irqrestore(&stm_msp->lock, flags); + + return status; +} + +static int destroy_queue(struct stm_msp *stm_msp) +{ + int status; + + status = stop_queue(stm_msp); + + if (status != 0) + return status; +#ifdef CONFIG_SPI_WORKQUEUE + destroy_workqueue(stm_msp->workqueue); +#endif + del_timer_sync(&stm_msp->spi_notify_timer); + + return 0; +} + +/** + * stm_msp_null_writer - To Write Dummy Data in Data register + * @stm_msp: spi driver private data structure + * + * This function is set as a write function for transfer which have + * Tx transfer buffer as NULL. It simply writes '0' in the Data + * register + */ +static void stm_msp_null_writer(struct stm_msp *stm_msp) +{ + u32 cur_write = 0; + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_TFU) || + (stm_msp->tx >= stm_msp->tx_end)) + return; + + writel(0x0, MSP_DR(stm_msp->regs)); + stm_msp->tx += (stm_msp->cur_chip->n_bytes); + cur_write++; + + if (cur_write == 8) + return; + } +} + +/** + * stm_msp_null_reader - To read data from Data register and discard it + * @stm_msp: spi driver private data structure + * + * This function is set as a reader function for transfer which have + * Rx Transfer buffer as null. Read Data is rejected + */ +static void stm_msp_null_reader(struct stm_msp *stm_msp) +{ + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_RFE) || + (stm_msp->rx >= stm_msp->rx_end)) + return; + + readl(MSP_DR(stm_msp->regs)); + stm_msp->rx += (stm_msp->cur_chip->n_bytes); + } +} + +/** + * stm_msp_u8_writer - Write FIFO data in Data register as a 8 Bit Data + * @stm_msp: spi driver private data structure + * + * This function writes data in Tx FIFO till it is not full + * which is indicated by the status register or our transfer is complete. + * It also updates the temporary write ptr tx in stm_msp which maintains + * current write position in transfer buffer. we do not write data more than + * FIFO depth + */ +void stm_msp_u8_writer(struct stm_msp *stm_msp) +{ + u32 cur_write = 0; + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_TFU) || + (stm_msp->tx >= stm_msp->tx_end)) + return; + + writel((u32)(*(u8 *)(stm_msp->tx)), MSP_DR(stm_msp->regs)); + stm_msp->tx += (stm_msp->cur_chip->n_bytes); + cur_write++; + + if (cur_write == MSP_FIFO_DEPTH) + return; + } +} + +/** + * stm_msp_u8_reader - Read FIFO data in Data register as a 8 Bit Data + * @stm_msp: spi driver private data structure + * + * This function reads data in Rx FIFO till it is not empty + * which is indicated by the status register or our transfer is complete. + * It also updates the temporary Read ptr rx in stm_msp which maintains + * current read position in transfer buffer + */ +void stm_msp_u8_reader(struct stm_msp *stm_msp) +{ + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_RFE) || + (stm_msp->rx >= stm_msp->rx_end)) + return; + + *(u8 *)(stm_msp->rx) = (u8)readl(MSP_DR(stm_msp->regs)); + stm_msp->rx += (stm_msp->cur_chip->n_bytes); + } +} + +/** + * stm_msp_u16_writer - Write FIFO data in Data register as a 16 Bit Data + * @stm_msp: spi driver private data structure + * + * This function writes data in Tx FIFO till it is not full + * which is indicated by the status register or our transfer is complete. + * It also updates the temporary write ptr tx in stm_msp which maintains + * current write position in transfer buffer. we do not write data more than + * FIFO depth + */ +void stm_msp_u16_writer(struct stm_msp *stm_msp) +{ + u32 cur_write = 0; + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_TFU) || + (stm_msp->tx >= stm_msp->tx_end)) + return; + + writel((u32)(*(u16 *)(stm_msp->tx)), MSP_DR(stm_msp->regs)); + stm_msp->tx += (stm_msp->cur_chip->n_bytes); + cur_write++; + + if (cur_write == MSP_FIFO_DEPTH) + return; + } +} + +/** + * stm_msp_u16_reader - Read FIFO data in Data register as a 16 Bit Data + * @stm_msp: spi driver private data structure + * + * This function reads data in Rx FIFO till it is not empty + * which is indicated by the status register or our transfer is complete. + * It also updates the temporary Read ptr rx in stm_msp which maintains + * current read position in transfer buffer + */ +void stm_msp_u16_reader(struct stm_msp *stm_msp) +{ + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_RFE) || + (stm_msp->rx >= stm_msp->rx_end)) + return; + + *(u16 *)(stm_msp->rx) = (u16)readl(MSP_DR(stm_msp->regs)); + stm_msp->rx += (stm_msp->cur_chip->n_bytes); + } +} + +/** + * stm_msp_u32_writer - Write FIFO data in Data register as a 32 Bit Data + * @stm_msp: spi driver private data structure + * + * This function writes data in Tx FIFO till it is not full + * which is indicated by the status register or our transfer is complete. + * It also updates the temporary write ptr tx in stm_msp which maintains + * current write position in transfer buffer. we do not write data more than + * FIFO depth + */ +void stm_msp_u32_writer(struct stm_msp *stm_msp) +{ + u32 cur_write = 0; + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_TFU) || + (stm_msp->tx >= stm_msp->tx_end)) + return; + + /* Write Data to Data Register */ + writel(*(u32 *)(stm_msp->tx), MSP_DR(stm_msp->regs)); + stm_msp->tx += (stm_msp->cur_chip->n_bytes); + cur_write++; + + if (cur_write == MSP_FIFO_DEPTH) + return; + } +} + +/** + * stm_msp_u32_reader - Read FIFO data in Data register as a 32 Bit Data + * @stm_msp: spi driver private data structure + * + * This function reads data in Rx FIFO till it is not empty + * which is indicated by the status register or our transfer is complete. + * It also updates the temporary Read ptr rx in stm_msp which maintains + * current read position in transfer buffer + */ +void stm_msp_u32_reader(struct stm_msp *stm_msp) +{ + u32 status; + + while (1) { + status = readl(MSP_FLR(stm_msp->regs)); + + if ((status & MSP_FLR_MASK_RFE) || + (stm_msp->rx >= stm_msp->rx_end)) + return; + + *(u32 *)(stm_msp->rx) = readl(MSP_DR(stm_msp->regs)); + stm_msp->rx += (stm_msp->cur_chip->n_bytes); + } +} + +/** + * stm_msp_interrupt_handler - Interrupt hanlder function + */ +static irqreturn_t stm_msp_interrupt_handler(int irq, void *dev_id) +{ + struct stm_msp *stm_msp = (struct stm_msp *)dev_id; + struct spi_message *msg = stm_msp->cur_msg; + u32 irq_status = 0; + u32 flag = 0; + + if (!msg) { + dev_err(&stm_msp->adev->dev, + "Bad message state in interrupt handler"); + /* Never fail */ + return IRQ_HANDLED; + } + + /* Read the Interrupt Status Register */ + irq_status = readl(MSP_MIS(stm_msp->regs)); + + if (irq_status) { + if (irq_status & MSP_MIS_MASK_ROEMIS) { /* Overrun interrupt */ + /* Bail out our Data has been corrupted */ + dev_dbg(&stm_msp->adev->dev, + "Received ROR interrupt\n"); + + stm_msp_controller_cmd(stm_msp, DISABLE_ALL_INTERRUPT); + stm_msp_controller_cmd(stm_msp, CLEAR_ALL_INTERRUPT); + stm_msp_controller_cmd(stm_msp, DISABLE_CONTROLLER); + msg->state = ERROR_STATE; + tasklet_schedule(&stm_msp->pump_transfers); + return IRQ_HANDLED; + } + + stm_msp->read(stm_msp); + stm_msp->write(stm_msp); + + if ((stm_msp->tx == stm_msp->tx_end) && (flag == 0)) { + flag = 1; + /* Disable Transmit interrupt */ + writel(readl(MSP_IMSC(stm_msp->regs)) & + (~MSP_IMSC_MASK_TXIM) & (~MSP_IMSC_MASK_TFOIM), + (stm_msp->regs + 0x14)); + } + + /* Clearing any Xmit underrun error. Overrun already handled */ + stm_msp_controller_cmd(stm_msp, CLEAR_ALL_INTERRUPT); + + if (stm_msp->rx == stm_msp->rx_end) { + stm_msp_controller_cmd(stm_msp, DISABLE_ALL_INTERRUPT); + stm_msp_controller_cmd(stm_msp, CLEAR_ALL_INTERRUPT); + + dev_dbg(&stm_msp->adev->dev, + "Interrupt transfer completed.\n"); + + /* Update total bytes transfered */ + msg->actual_length += stm_msp->cur_transfer->len; + + if (stm_msp->cur_transfer->cs_change) + stm_msp->cur_chip->cs_control( + SPI_CHIP_DESELECT); + + /* Move to next transfer */ + msg->state = next_transfer(stm_msp); + tasklet_schedule(&stm_msp->pump_transfers); + return IRQ_HANDLED; + } + } + return IRQ_HANDLED; +} + +/** + * stm_msp_cleanup - cleanup function registered to SPI master framework + * @spi: spi device which is requesting cleanup + * + * This function is registered to the SPI framework for this SPI master + * controller. It will free the runtime state of chip. + */ +static void stm_msp_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata((struct spi_device *)spi); + struct stm_msp *stm_msp = spi_master_get_devdata(spi->master); + struct spi_master *master; + master = stm_msp->master; + + if (chip) { + kfree(chip->ctr_regs); + kfree(chip); + spi_set_ctldata(spi, NULL); + } +} + +/** + * null_cs_control - Dummy chip select function + * @command: select/delect the chip + * + * If no chip select function is provided by client this is used as dummy + * chip select + */ +static void null_cs_control(u32 command) +{ + /* Nothing to do */ + (void)command; +} + +static int verify_msp_controller_parameters(struct stm_msp_config_chip + *chip_info) +{ + + /* FIXME: check clock params */ + if ((chip_info->lbm != SPI_LOOPBACK_ENABLED) && + (chip_info->lbm != SPI_LOOPBACK_DISABLED)) { + dev_dbg(chip_info->dev, + "Loopback Mode is configured incorrectly\n"); + return -1; + } + if ((chip_info->hierarchy != SPI_MASTER) && + (chip_info->hierarchy != SPI_SLAVE)) { + dev_dbg(chip_info->dev, + "hierarchy is configured incorrectly\n"); + return -1; + } + if ((chip_info->endian_rx != SPI_FIFO_MSB) && + (chip_info->endian_rx != SPI_FIFO_LSB)) { + dev_dbg(chip_info->dev, + "Rx FIFO endianess is configured incorrectly\n"); + return -1; + } + if ((chip_info->endian_tx != SPI_FIFO_MSB) && + (chip_info->endian_tx != SPI_FIFO_LSB)) { + dev_dbg(chip_info->dev, + "Tx FIFO endianess is configured incorrectly\n"); + return -1; + } + if ((chip_info->data_size < MSP_DATA_BITS_8) || + (chip_info->data_size > MSP_DATA_BITS_32)) { + dev_dbg(chip_info->dev, + "MSP DATA Size is configured incorrectly\n"); + return -1; + } + if ((chip_info->com_mode != SPI_INTERRUPT_TRANSFER) && + (chip_info->com_mode != SPI_POLLING_TRANSFER)) { + dev_dbg(chip_info->dev, + "Communication mode is configured incorrectly\n"); + return -1; + } + if (((chip_info->proto_params).clk_phase != + SPI_CLK_ZERO_CYCLE_DELAY) && + ((chip_info->proto_params).clk_phase != + SPI_CLK_HALF_CYCLE_DELAY)) { + dev_dbg(chip_info->dev, + "Clock Phase is configured incorrectly\n"); + return -1; + } + if (((chip_info->proto_params).clk_pol != + SPI_CLK_POL_IDLE_LOW) && + ((chip_info->proto_params).clk_pol != + SPI_CLK_POL_IDLE_HIGH)) { + dev_dbg(chip_info->dev, + "Clk Polarity configured incorrectly\n"); + return -1; + } + if (chip_info->cs_control == NULL) { + dev_dbg(chip_info->dev, + "Chip Select Function is NULL for this chip\n"); + chip_info->cs_control = null_cs_control; + } + return 0; +} + +static struct stm_msp_config_chip *allocate_default_msp_chip_cfg( + struct spi_device *spi) +{ + struct stm_msp_config_chip *chip_info; + + chip_info = kzalloc(sizeof(struct stm_msp_config_chip), GFP_KERNEL); + + if (!chip_info) { + dev_err(&spi->dev, "setup - cannot allocate controller data"); + return NULL; + } + dev_dbg(&spi->dev, "Allocated Memory for controller data\n"); + + chip_info->lbm = SPI_LOOPBACK_DISABLED; + chip_info->com_mode = SPI_POLLING_TRANSFER; + chip_info->hierarchy = SPI_MASTER; + chip_info->endian_tx = SPI_FIFO_LSB; + chip_info->endian_rx = SPI_FIFO_LSB; + chip_info->data_size = MSP_DATA_BITS_32; + + if (spi->max_speed_hz != 0) + chip_info->freq = spi->max_speed_hz; + else + chip_info->freq = SPI_DEFAULT_MAX_SPEED_HZ; + + chip_info->proto_params.clk_phase = SPI_CLK_HALF_CYCLE_DELAY; + chip_info->proto_params.clk_pol = SPI_CLK_POL_IDLE_LOW; + chip_info->cs_control = null_cs_control; + + return chip_info; +} + +static void stm_msp_delay(struct stm_msp *stm_msp) +{ + udelay(15); + + while (readl(MSP_FLR(stm_msp->regs)) & + (MSP_FLR_MASK_RBUSY | MSP_FLR_MASK_TBUSY)) + udelay(1); +} + +/** + * stm_msp_setup - setup function registered to SPI master framework + * @spi: spi device which is requesting setup + * + * This function is registered to the SPI framework for this SPI master + * controller. If it is the first time when setup is called by this device, + * this function will initialize the runtime state for this chip and save + * the same in the device structure. Else it will update the runtime info + * with the updated chip info. + */ +static int stm_msp_setup(struct spi_device *spi) +{ + struct stm_msp_config_chip *chip_info; + struct chip_data *curr_cfg; + struct spi_master *master; + int status = 0; + u16 sckdiv = 0; + s16 bus_num = 0; + struct stm_msp *stm_msp = spi_master_get_devdata(spi->master); + struct msp_regs *msp_regs; + master = stm_msp->master; + bus_num = master->bus_num - 1; + + /* Get controller data */ + chip_info = spi->controller_data; + /* Get controller_state */ + curr_cfg = spi_get_ctldata(spi); + + if (curr_cfg == NULL) { + curr_cfg = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + + if (!curr_cfg) { + dev_err(&stm_msp->adev->dev, + "setup - cannot allocate controller state"); + return -ENOMEM; + } + + curr_cfg->chip_id = spi->chip_select; + curr_cfg->ctr_regs = kzalloc(sizeof(struct msp_regs), + GFP_KERNEL); + + if (curr_cfg->ctr_regs == NULL) { + dev_err(&stm_msp->adev->dev, + "setup - cannot allocate mem for regs"); + goto err_first_setup; + } + + dev_err(&stm_msp->adev->dev, + "chip Id = %d\n", curr_cfg->chip_id); + + if (chip_info == NULL) { + chip_info = allocate_default_msp_chip_cfg(spi); + + if (!chip_info) { + dev_err(&stm_msp->adev->dev, + "setup - cannot allocate cntlr data"); + status = -ENOMEM; + goto err_first_setup; + } + + spi->controller_data = chip_info; + } + } + + /* Pointer back to the SPI device */ + chip_info->dev = &spi->dev; + + if (chip_info->freq == 0) { + /* Calculate Specific Freq. */ + if ((MSP_INTERNAL_CLK == chip_info->clk_freq.clk_src) || + (MSP_EXTERNAL_CLK == chip_info->clk_freq.clk_src)) { + sckdiv = chip_info->clk_freq.sckdiv; + } else { + status = -1; + dev_err(&stm_msp->adev->dev, + "setup - controller clock data is incorrect"); + goto err_config_params; + } + } else { + /* Calculate Effective Freq. */ + sckdiv = (DEFAULT_MSP_CLK / (chip_info->freq)) - 1; + + if (sckdiv > MAX_SCKDIV) { + dev_dbg(&stm_msp->adev->dev, + "SPI: Cannot set frequency less than 48Khz," + "setting lowest(48 Khz)\n"); + sckdiv = MAX_SCKDIV; + } + } + + status = verify_msp_controller_parameters(chip_info); + + if (status) { + dev_err(&stm_msp->adev->dev, + "setup - controller data is incorrect"); + goto err_config_params; + } + + /* Now set controller state based on controller data */ + curr_cfg->xfer_type = chip_info->com_mode; + curr_cfg->cs_control = chip_info->cs_control; + curr_cfg->delay = stm_msp_delay; + + curr_cfg->null_write = stm_msp_null_writer; + curr_cfg->null_read = stm_msp_null_reader; + + if (chip_info->data_size <= MSP_DATA_BITS_8) { + dev_dbg(&stm_msp->adev->dev, "Less than 8 bits per word...\n"); + + curr_cfg->n_bytes = 1; + curr_cfg->read = stm_msp_u8_reader; + curr_cfg->write = stm_msp_u8_writer; + } else if (chip_info->data_size <= MSP_DATA_BITS_16) { + dev_dbg(&stm_msp->adev->dev, "Less than 16 bits per word...\n"); + + curr_cfg->n_bytes = 2; + curr_cfg->read = stm_msp_u16_reader; + curr_cfg->write = stm_msp_u16_writer; + } else { + dev_dbg(&stm_msp->adev->dev, "Less than 32 bits per word...\n"); + + curr_cfg->n_bytes = 4; + curr_cfg->read = stm_msp_u32_reader; + curr_cfg->write = stm_msp_u32_writer; + } + + /* Now initialize all register settings reqd. for this chip */ + + msp_regs = (struct msp_regs *)(curr_cfg->ctr_regs); + msp_regs->gcr = 0x0; + msp_regs->tcf = 0x0; + msp_regs->rcf = 0x0; + msp_regs->srg = 0x0; + msp_regs->dmacr = 0x0; + + MSP_WBITS(msp_regs->dmacr, 0x0, MSP_DMACR_MASK_RDMAE, 0); + MSP_WBITS(msp_regs->dmacr, 0x0, MSP_DMACR_MASK_TDMAE, 1); + + /* GCR Reg Config */ + + MSP_WBITS(msp_regs->gcr, + MSP_RECEIVER_DISABLED, MSP_GCR_MASK_RXEN, 0); + MSP_WBITS(msp_regs->gcr, + MSP_RX_FIFO_ENABLED, MSP_GCR_MASK_RFFEN, 1); + MSP_WBITS(msp_regs->gcr, + MSP_TRANSMITTER_DISABLED, MSP_GCR_MASK_TXEN, 8); + MSP_WBITS(msp_regs->gcr, + MSP_TX_FIFO_ENABLED, MSP_GCR_MASK_TFFEN, 9); + MSP_WBITS(msp_regs->gcr, + MSP_TX_FRAME_SYNC_POL_LOW, MSP_GCR_MASK_TFSPOL, 10); + MSP_WBITS(msp_regs->gcr, + MSP_TX_FRAME_SYNC_INT, MSP_GCR_MASK_TFSSEL, 11); + MSP_WBITS(msp_regs->gcr, + MSP_TRANSMIT_DATA_WITH_DELAY, MSP_GCR_MASK_TXDDL, 15); + MSP_WBITS(msp_regs->gcr, + MSP_SAMPLE_RATE_GEN_ENABLE, MSP_GCR_MASK_SGEN, 16); + MSP_WBITS(msp_regs->gcr, + MSP_CLOCK_INTERNAL, MSP_GCR_MASK_SCKSEL, 18); + MSP_WBITS(msp_regs->gcr, + MSP_FRAME_GEN_ENABLE, MSP_GCR_MASK_FGEN, 20); + MSP_WBITS(msp_regs->gcr, + SPI_BURST_MODE_DISABLE, MSP_GCR_MASK_SPIBME, 23); + + if (chip_info->lbm == SPI_LOOPBACK_ENABLED) + MSP_WBITS(msp_regs->gcr, + MSP_LOOPBACK_ENABLED, MSP_GCR_MASK_LBM, 7); + else + MSP_WBITS(msp_regs->gcr, + MSP_LOOPBACK_DISABLED, MSP_GCR_MASK_LBM, 7); + + if (chip_info->hierarchy == SPI_MASTER) + MSP_WBITS(msp_regs->gcr, + MSP_IS_SPI_MASTER, MSP_GCR_MASK_TCKSEL, 14); + else + MSP_WBITS(msp_regs->gcr, + MSP_IS_SPI_SLAVE, MSP_GCR_MASK_TCKSEL, 14); + + if (chip_info->proto_params.clk_phase == SPI_CLK_ZERO_CYCLE_DELAY) + MSP_WBITS(msp_regs->gcr, + MSP_SPI_PHASE_ZERO_CYCLE_DELAY, + MSP_GCR_MASK_SPICKM, 21); + else + MSP_WBITS(msp_regs->gcr, + MSP_SPI_PHASE_HALF_CYCLE_DELAY, + MSP_GCR_MASK_SPICKM, 21); + + if (chip_info->proto_params.clk_pol == SPI_CLK_POL_IDLE_HIGH) + MSP_WBITS(msp_regs->gcr, + MSP_TX_CLOCK_POL_HIGH, MSP_GCR_MASK_TCKPOL, 13); + else + MSP_WBITS(msp_regs->gcr, + MSP_TX_CLOCK_POL_LOW, MSP_GCR_MASK_TCKPOL, 13); + + /* RCF Reg Config */ + MSP_WBITS(msp_regs->rcf, + MSP_IGNORE_RX_FRAME_SYNC_PULSE, MSP_RCF_MASK_RFSIG, 15); + MSP_WBITS(msp_regs->rcf, + MSP_RX_1BIT_DATA_DELAY, MSP_RCF_MASK_RDDLY, 13); + + if (chip_info->endian_rx == SPI_FIFO_LSB) + MSP_WBITS(msp_regs->rcf, + MSP_RX_ENDIANESS_LSB, MSP_RCF_MASK_RENDN, 12); + else + MSP_WBITS(msp_regs->rcf, + MSP_RX_ENDIANESS_MSB, MSP_RCF_MASK_RENDN, 12); + + MSP_WBITS(msp_regs->rcf, chip_info->data_size, MSP_RCF_MASK_RP1ELEN, 0); + + /* TCF Reg Config */ + + MSP_WBITS(msp_regs->tcf, + MSP_IGNORE_TX_FRAME_SYNC_PULSE, MSP_TCF_MASK_TFSIG, 15); + MSP_WBITS(msp_regs->tcf, + MSP_TX_1BIT_DATA_DELAY, MSP_TCF_MASK_TDDLY, 13); + + if (chip_info->endian_rx == SPI_FIFO_LSB) + MSP_WBITS(msp_regs->tcf, + MSP_TX_ENDIANESS_LSB, MSP_TCF_MASK_TENDN, 12); + else + MSP_WBITS(msp_regs->tcf, + MSP_TX_ENDIANESS_MSB, MSP_TCF_MASK_TENDN, 12); + MSP_WBITS(msp_regs->tcf, chip_info->data_size, MSP_TCF_MASK_TP1ELEN, 0); + + /* SRG Reg Config */ + + MSP_WBITS(msp_regs->srg, sckdiv, MSP_SRG_MASK_SCKDIV, 0); + + /* Save controller_state */ + spi_set_ctldata(spi, curr_cfg); + + return status; + +err_config_params: +err_first_setup: + + kfree(curr_cfg); + return status; +} + +static int __init stm_msp_probe(struct amba_device *adev, struct amba_id *id) +{ + struct device *dev = &adev->dev; + struct stm_msp_controller *platform_info = adev->dev.platform_data; + struct spi_master *master; + struct stm_msp *stm_msp = NULL; /* Data for this driver */ + int irq, status = 0; + + dev_info(dev, "STM MSP driver, device ID: 0x%08x\n", adev->periphid); + + if (platform_info == NULL) { + dev_err(dev, "probe - no platform data supplied\n"); + status = -ENODEV; + goto err_no_pdata; + } + + /* Allocate master with space for data */ + master = spi_alloc_master(dev, sizeof(struct stm_msp)); + + if (master == NULL) { + dev_err(dev, "probe - cannot alloc spi_master\n"); + status = -ENOMEM; + goto err_no_mem; + } + + stm_msp = spi_master_get_devdata(master); + stm_msp->master = master; + stm_msp->master_info = platform_info; + stm_msp->adev = adev; + + stm_msp->clk = clk_get(&adev->dev, NULL); + + if (IS_ERR(stm_msp->clk)) { + dev_err(dev, "probe - cannot find clock\n"); + status = PTR_ERR(stm_msp->clk); + goto free_master; + } + + /* Fetch the Resources, using platform data */ + status = amba_request_regions(adev, NULL); + + if (status) { + status = -ENODEV; + goto disable_clk; + } + + /* Get Hold of Device Register Area... */ + stm_msp->regs = ioremap(adev->res.start, resource_size(&adev->res)); + + if (stm_msp->regs == NULL) { + status = -ENODEV; + goto disable_clk; + } + + irq = adev->irq[0]; + + if (irq <= 0) { + status = -ENODEV; + goto err_no_iores; + } + + stm_msp_controller_cmd(stm_msp, LOAD_DEFAULT_CONFIG); + + /* Required Info for an SPI controller */ + /* Bus Number Which Assigned to this SPI controller on this board */ + master->bus_num = (u16) platform_info->id; + master->num_chipselect = platform_info->num_chipselect; + master->setup = stm_msp_setup; + master->cleanup = (void *)stm_msp_cleanup; + master->transfer = stm_msp_transfer; + + dev_dbg(dev, "BUSNO: %d\n", master->bus_num); + + /* Initialize and start queue */ + status = init_queue(stm_msp); + + if (status != 0) { + dev_err(dev, "probe - problem initializing queue\n"); + goto err_init_queue; + } + + status = start_queue(stm_msp); + + if (status != 0) { + dev_err(dev, "probe - problem starting queue\n"); + goto err_start_queue; + } + + amba_set_drvdata(adev, stm_msp); + + dev_dbg(dev, "probe succeded\n"); + dev_dbg(dev, "Bus No = %d, IRQ Line = %d, Virtual Addr: %x\n", + master->bus_num, irq, (u32)(stm_msp->regs)); + + status = request_irq(stm_msp->adev->irq[0], + stm_msp_interrupt_handler, + 0, stm_msp->master_info->device_name, + stm_msp); + + if (status < 0) { + dev_err(dev, "probe - cannot get IRQ (%d)\n", status); + goto err_irq; + } + + /* Register with the SPI framework */ + status = spi_register_master(master); + + if (status != 0) { + dev_err(dev, "probe - problem registering spi master\n"); + goto err_spi_register; + } + + return 0; + +err_spi_register: + free_irq(stm_msp->adev->irq[0], stm_msp); +err_irq: +err_init_queue: +err_start_queue: + destroy_queue(stm_msp); +err_no_iores: + iounmap(stm_msp->regs); +disable_clk: + clk_put(stm_msp->clk); +free_master: + spi_master_put(master); +err_no_mem: +err_no_pdata: + return status; +} + +static int __exit stm_msp_remove(struct amba_device *adev) +{ + struct stm_msp *stm_msp = amba_get_drvdata(adev); + int status = 0; + + if (!stm_msp) + return 0; + + /* Remove the queue */ + status = destroy_queue(stm_msp); + + if (status != 0) { + dev_err(&adev->dev, "queue remove failed (%d)\n", status); + return status; + } + + stm_msp_controller_cmd(stm_msp, LOAD_DEFAULT_CONFIG); + + /* Release map resources */ + iounmap(stm_msp->regs); + amba_release_regions(adev); + tasklet_disable(&stm_msp->pump_transfers); + free_irq(stm_msp->adev->irq[0], stm_msp); + + /* Disconnect from the SPI framework */ + spi_unregister_master(stm_msp->master); + + clk_put(stm_msp->clk); + + /* Prevent double remove */ + amba_set_drvdata(adev, NULL); + dev_dbg(&adev->dev, "remove succeded\n"); + return status; +} + +#ifdef CONFIG_PM + +/** + * stm_msp_suspend - MSP suspend function registered with PM framework. + * @dev: Reference to amba device structure of the device + * @state: power mgmt state. + * + * This function is invoked when the system is going into sleep, called + * by the power management framework of the linux kernel. + */ +static int stm_msp_suspend(struct amba_device *adev, pm_message_t state) +{ + struct stm_msp *stm_msp = amba_get_drvdata(adev); + int status = 0; + + status = stop_queue(stm_msp); + + if (status != 0) { + dev_warn(&adev->dev, "suspend cannot stop queue\n"); + return status; + } + + dev_dbg(&adev->dev, "suspended\n"); + return 0; +} + +/** + * stm_msp_resume - MSP Resume function registered with PM framework. + * @dev: Reference to amba device structure of the device + * + * This function is invoked when the system is coming out of sleep, called + * by the power management framework of the linux kernel. + */ +static int stm_msp_resume(struct amba_device *adev) +{ + struct stm_msp *stm_msp = amba_get_drvdata(adev); + int status = 0; + + /* Start the queue running */ + status = start_queue(stm_msp); + + if (status) + dev_err(&adev->dev, "problem starting queue (%d)\n", status); + else + dev_dbg(&adev->dev, "resumed\n"); + + return status; +} + +#else +#define stm_msp_suspend NULL +#define stm_msp_resume NULL +#endif /* CONFIG_PM */ + +static struct amba_id stm_msp_ids[] = { + { + .id = MSP_PER_ID, + .mask = MSP_PER_MASK, + }, + { + 0, + 0, + }, +}; + +static struct amba_driver stm_msp_driver = { + .drv = { + .name = "MSP", + }, + .id_table = stm_msp_ids, + .probe = stm_msp_probe, + .remove = __exit_p(stm_msp_remove), + .resume = stm_msp_resume, + .suspend = stm_msp_suspend, +}; + +static int __init stm_msp_init(void) +{ + return amba_driver_register(&stm_msp_driver); +} + +static void __exit stm_msp_exit(void) +{ + amba_driver_unregister(&stm_msp_driver); +} + +module_init(stm_msp_init); +module_exit(stm_msp_exit); + +MODULE_AUTHOR("Sachin Verma "); +MODULE_DESCRIPTION("STM MSP (SPI protocol) Driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/spi/stm_msp.h b/include/linux/spi/stm_msp.h new file mode 100644 index 00000000000..501023105cb --- /dev/null +++ b/include/linux/spi/stm_msp.h @@ -0,0 +1,126 @@ +/* + * include/linux/spi/stm_msp.h + * + * Copyright (C) 2010 STMicroelectronics Pvt. Ltd. + * + * Author: Sachin Verma + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _STM_MSP_H +#define _STM_MSP_H + +#include + +/* CHIP select/deselect commands */ +enum spi_chip_select { + SPI_CHIP_SELECT, + SPI_CHIP_DESELECT +}; + +/* Common configuration for different SPI controllers */ +enum spi_loopback { + SPI_LOOPBACK_DISABLED, + SPI_LOOPBACK_ENABLED +}; + +enum spi_hierarchy { + SPI_MASTER, + SPI_SLAVE +}; + +/* Endianess of FIFO Data */ +enum spi_fifo_endian { + SPI_FIFO_MSB, + SPI_FIFO_LSB +}; + +/* SPI mode of operation (Communication modes) */ +enum spi_mode { + SPI_INTERRUPT_TRANSFER, + SPI_POLLING_TRANSFER, +}; + +enum msp_data_size { + MSP_DATA_BITS_DEFAULT = -1, + MSP_DATA_BITS_8 = 0x00, + MSP_DATA_BITS_10, + MSP_DATA_BITS_12, + MSP_DATA_BITS_14, + MSP_DATA_BITS_16, + MSP_DATA_BITS_20, + MSP_DATA_BITS_24, + MSP_DATA_BITS_32, +}; + +enum msp_clk_src { + MSP_INTERNAL_CLK = 0x0, + MSP_EXTERNAL_CLK, +}; + +struct msp_clock_params { + enum msp_clk_src clk_src; + /* value from 0 to 1023 */ + u16 sckdiv; + /* Used only when MSPSCK clocks the sample rate + * generator (SCKSEL = 1Xb): + * 0b: The rising edge of MSPSCK clocks the sample rate generator + * 1b: The falling edge of MSPSCK clocks the sample rate generator */ + int sckpol; +}; + +/* Motorola SPI protocol specific definitions */ +enum spi_clk_phase { + SPI_CLK_ZERO_CYCLE_DELAY = 0x0, /* Receive data on rising edge. */ + SPI_CLK_HALF_CYCLE_DELAY /* Receive data on falling edge. */ +}; + +/* SPI Clock Polarity */ +enum spi_clk_pol { + SPI_CLK_POL_IDLE_LOW, /* Low inactive level */ + SPI_CLK_POL_IDLE_HIGH /* High inactive level */ +}; + +struct motorola_spi_proto_params { + enum spi_clk_phase clk_phase; + enum spi_clk_pol clk_pol; +}; + +struct stm_msp_config_chip { + struct device *dev; + enum spi_loopback lbm; + enum spi_hierarchy hierarchy; + enum spi_fifo_endian endian_rx; + enum spi_fifo_endian endian_tx; + enum spi_mode com_mode; + enum msp_data_size data_size; + struct msp_clock_params clk_freq; + int spi_burst_mode_enable; + struct motorola_spi_proto_params proto_params; + u32 freq; + void (*cs_control)(u32 control); +}; + +/** + * struct stm_msp_controller - device.platform_data for SPI controller devices. + * + * @num_chipselect: chipselects are used to distinguish individual + * SPI slaves, and are numbered from zero to num_chipselects - 1. + * each slave has a chipselect signal, but it's common that not + * every chipselect is connected to a slave. + */ +struct stm_msp_controller { + u8 num_chipselect; + u32 id; + u32 base_addr; + char *device_name; +}; +#endif /* _STM_MSP_H */ -- cgit v1.2.3 From 15c9c962bf4a2050949ca9b09b3bfac51f847150 Mon Sep 17 00:00:00 2001 From: Sakethram Bommisetti Date: Tue, 25 Jan 2011 16:10:30 +0530 Subject: USB: gadget: OTG supplement revision 2.0 updates Introduce otg_version field in usb_gadget struct.UDC can advertise OTG spec version compatibility by setting otg_version field appropriately. Gadget drivers fill the bcdOTG field in OTG descriptor based on UDC's OTG version. Add sysfs file for host_request and UDC returns the same when HNP polling request arrives from the host. Signed-off-by: Pavankumar Kondeti Change-Id: Ic5ea40369159c10e524a13a2d9b1722fb3ee6921 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13421 Reviewed-by: Praveena NADAHALLY Tested-by: Praveena NADAHALLY --- .../ABI/testing/sysfs-devices-platform-_UDC_-gadget | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-devices-platform-_UDC_-gadget b/Documentation/ABI/testing/sysfs-devices-platform-_UDC_-gadget index d548eaac230..1424c746fda 100644 --- a/Documentation/ABI/testing/sysfs-devices-platform-_UDC_-gadget +++ b/Documentation/ABI/testing/sysfs-devices-platform-_UDC_-gadget @@ -7,15 +7,15 @@ Description: 0 -> resumed (_UDC_ is the name of the USB Device Controller driver) - -What: /sys/devices/platform/_UDC_/gadget/gadget-lunX/nofua -Date: July 2010 -Contact: Andy Shevchenko +What: /sys/devices/platform/_UDC_/gadget/host_request +Date: December 2010 +Contact: Pavan Kondeti Description: - Show or set the reaction on the FUA (Force Unit Access) bit in - the SCSI WRITE(10,12) commands when a gadget in USB Mass - Storage mode. + OTG 2.0 compliant host keeps polling OTG2.0 peripheral + for host role. Set host_request flag, which tells host + to give up the host role to peripheral. + + 1 -> host role is requested + 0 -> no effect (automatically cleared upon reset/disconnect) - Possible values are: - 1 -> ignore the FUA flag - 0 -> obey the FUA flag + (_UDC_ is the name of the USB Device Controller driver) \ No newline at end of file -- cgit v1.2.3 From f3ce73cd910eef8f82763d1c9d20fe7e59a27ef6 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Tue, 22 Mar 2011 07:30:01 +0100 Subject: drivers: shrm: Add shared memory (shrm) driver Signed-off-by: Robert Marklund --- Documentation/DocBook/Makefile | 2 +- Documentation/DocBook/shrm.tmpl | 139 +++ arch/arm/mach-ux500/include/mach/shrm.h | 23 + arch/arm/mach-ux500/include/mach/shrm_config.h | 111 ++ arch/arm/mach-ux500/include/mach/shrm_driver.h | 201 ++++ arch/arm/mach-ux500/include/mach/shrm_net.h | 44 + arch/arm/mach-ux500/include/mach/shrm_private.h | 180 +++ drivers/char/Makefile | 4 + drivers/char/shrm_char.c | 858 ++++++++++++++ drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/shrm/Kconfig | 49 + drivers/misc/shrm/Makefile | 11 + drivers/misc/shrm/modem_shrm_driver.c | 667 +++++++++++ drivers/misc/shrm/shrm_driver.c | 1439 +++++++++++++++++++++++ drivers/misc/shrm/shrm_fifo.c | 829 +++++++++++++ drivers/misc/shrm/shrm_protocol.c | 1177 ++++++++++++++++++ drivers/net/Makefile | 4 + drivers/net/u8500_shrm.c | 336 ++++++ 19 files changed, 6075 insertions(+), 1 deletion(-) create mode 100644 Documentation/DocBook/shrm.tmpl create mode 100644 arch/arm/mach-ux500/include/mach/shrm.h create mode 100644 arch/arm/mach-ux500/include/mach/shrm_config.h create mode 100644 arch/arm/mach-ux500/include/mach/shrm_driver.h create mode 100644 arch/arm/mach-ux500/include/mach/shrm_net.h create mode 100644 arch/arm/mach-ux500/include/mach/shrm_private.h create mode 100644 drivers/char/shrm_char.c create mode 100644 drivers/misc/shrm/Kconfig create mode 100644 drivers/misc/shrm/Makefile create mode 100644 drivers/misc/shrm/modem_shrm_driver.c create mode 100644 drivers/misc/shrm/shrm_driver.c create mode 100644 drivers/misc/shrm/shrm_fifo.c create mode 100644 drivers/misc/shrm/shrm_protocol.c create mode 100644 drivers/net/u8500_shrm.c diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index 27c7eca9f40..a466fc92ada 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,7 +15,7 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml media.xml drm.xml \ - i2s.xml msp.xml + i2s.xml msp.xml shrm.xml ### # The build process is as follows (targets): diff --git a/Documentation/DocBook/shrm.tmpl b/Documentation/DocBook/shrm.tmpl new file mode 100644 index 00000000000..b35e1bc4e0b --- /dev/null +++ b/Documentation/DocBook/shrm.tmpl @@ -0,0 +1,139 @@ + + + + + + Shared Memory + + + Biju + Das + +
+ biju.das@stericsson.com +
+
+
+ + Kumar + Sanghvi + +
+ kumar.sanghvi@stericsson.com +
+
+
+ + Arun + Murthy + +
+ arun.murthy@stericsson.com +
+
+
+
+ + + 2009-2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + + Licence terms: GNU General Public Licence (GPL) version 2. + + +
+ + + + Introduction + + This Documentation describes the ST-Ericsson's adaptation on protocol used for CMT/APE communication when SHaRedMemory is used as IPC link. + + + + + Design + + The APE consists Cortex A9 dual core SMP, a multimedia DSP and PRCMU. Modem consists of 2 Cortex R4 ARM processor. + The exchange of messages between CMT(Cellular Mobile Terminal) and APE includes copying the data to a shared area DDR. This region is accessible by both CMT and APE. The design includes 2 channels common and audio. Common channel is used for exchanging ISI, RPC and SECURITY messages. Audio channel is used for exchanging AUDIO messages. Each channel consists of 2 FIFO. One FIFO for sending message from CMT to APE and other from APE to CMT. Each of these FIFO have write and read pointer shared between APE and CMT. Writer pointer is updated on copying the message to FIFO and reader will read the messages from the read pointer upto the writer pointer. Writer and reader notifications are used to notify the completion of read/write operation(seperate for APE and CMT). Driver includes 4 queues. Once the messages are sent from CMT to APE it resides in the FIFO and then copied to one of the 4 queues based on the message type(ISI, RPC, AUDIO, SECURITY) and then the net/char device interface fetches this message from the queue and copies to the user space buffer. + + + + + Concepts + + The user space application sends ISI/RPC/AUDIO/SECURITY messages. ISI is sent through the phonet to shrm driver. For achieving this there are 2 interfaces to the shrm driver. Net interface used for exchanging the ISI message and char interface for RPC, AUDIO and SECURITY messages. On receiving any of these messages from the user space application, it is copied to a memory in kernel space. From here it is then copied to respective FIFO from where the CMT reads the message. + CMT(Cellular Mobile Terminal) writes messages to the respective FIFO and thereafter to respective queue. The net/char device copies this message from the queue to the user space buffer. + + + + + Known Bugs And Assumptions + + + + None + + + Assumptions + 1. ApeShmFifo#0 is of 128kB in size. As this is used for transmission except CS audio call data. Expected message size is 1.5kB with a max of 16kB. + 2. ApeShmFifo#1 is of 4kB in size. This is used for transmission of CS audio call data. Expected message size is 24kb. + 3. CmtShmFifo#0 is of 128kB in size. As this is used for transmission except CS audio call data. Expected message size is 1.5kB with a max of 16kB. + 4. CmtShmFifo#1 is of 4kB in size. This is used for transmission of CS audio call data. Expected message size is 24kb. + The total size of the FIFO is 264 kB. + + + + + + + + + Public Functions Provided + + This Section lists the API's provided by the SHRM driver to phonet drivers. + +!Edrivers/net/u8500_shrm.c + + This Section lists the API's provided by the SHRM driver used in transmission of RPC, AUDIO and SECURITY messages. + +!Edrivers/char/shrm_char.c + + + + Private Functions + + This Section lists the functions used internally by the SHRM driver to implement FIFO management. It physically reads/writes data to/from memory. + +!Idrivers/misc/shrm/shrm_fifo.c + + This Section lists the functions used internally by the SHRM driver to implement the SHM protocol and handle all interrupt callback. + +!Idrivers/misc/shrm/shrm_protocol.c + + This Section lists the functions used internally by the SHRM driver to implement Modem-Host communication L1 interface specifications. + +!Idrivers/misc/shrm/modem_shrm_driver.c + + + + Other Data Structures + + This Section lists some of the Data structure used by the SHRM driver. + +!Iarch/arm/mach-ux500/include/mach/shrm.h +!Iarch/arm/mach-ux500/include/mach/shrm_driver.h +!Iarch/arm/mach-ux500/include/mach/shrm_private.h + +
diff --git a/arch/arm/mach-ux500/include/mach/shrm.h b/arch/arm/mach-ux500/include/mach/shrm.h new file mode 100644 index 00000000000..6deeeb16ba8 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/shrm.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghavi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __SHM_DRIVER_IF_H__ +#define __SHM_DRIVER_IF_H__ + +#include + +/* forward declaration */ +struct shrm_dev; + +typedef void (*rx_cb)(void *data, unsigned int length); +typedef void (*received_msg_handler)(unsigned char l2_header, + void *msg_ptr, unsigned int length, + struct shrm_dev *shrm); + +#endif diff --git a/arch/arm/mach-ux500/include/mach/shrm_config.h b/arch/arm/mach-ux500/include/mach/shrm_config.h new file mode 100644 index 00000000000..a82b35ef77b --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/shrm_config.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghavi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __SHRM_CONFIG_H +#define __SHRM_CONFIG_H + + +/* +Note: modem need to define IPC as a non-cacheable area. +In Cortex R4 MPU requires that base address of NC area is aligned on a +region-sized boundary.On modem side, only 1 NC area can be defined, hence +the whole IPC area must be defined as NC (at least). + +*/ + +/* cache line size = 32bytes*/ +#define SHM_CACHE_LINE 32 +#define SHM_PTR_SIZE 4 + +/* FIFO 0 address configuration */ +/* ---------------------------- */ +/* 128KB */ +#define SHM_FIFO_0_SIZE (128*1024) + + +/* == APE addresses == */ +#ifdef CONFIG_SHRM_V1_UPDATES_VERSION +#define SHM_IPC_BASE_AMCU 0x06F80000 +#else +#define SHM_IPC_BASE_AMCU 0x06000000 +#endif + +/* offset pointers */ +#define SHM_ACFIFO_0_WRITE_AMCU SHM_IPC_BASE_AMCU +#define SHM_ACFIFO_0_READ_AMCU (SHM_ACFIFO_0_WRITE_AMCU + SHM_PTR_SIZE) +#define SHM_CAFIFO_0_WRITE_AMCU (SHM_ACFIFO_0_WRITE_AMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_0_READ_AMCU (SHM_CAFIFO_0_WRITE_AMCU + SHM_PTR_SIZE) +/* FIFO start */ +#define SHM_ACFIFO_0_START_AMCU (SHM_CAFIFO_0_WRITE_AMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_0_START_AMCU (SHM_ACFIFO_0_START_AMCU + SHM_FIFO_0_SIZE) + + +/* == CMT addresses ==*/ +#define SHM_IPC_BASE_CMCU (SHM_IPC_BASE_AMCU+0x08000000) +/* offset pointers */ +#define SHM_ACFIFO_0_WRITE_CMCU SHM_IPC_BASE_CMCU +#define SHM_ACFIFO_0_READ_CMCU (SHM_ACFIFO_0_WRITE_CMCU + SHM_PTR_SIZE) +#define SHM_CAFIFO_0_WRITE_CMCU (SHM_ACFIFO_0_WRITE_CMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_0_READ_CMCU (SHM_CAFIFO_0_WRITE_CMCU + SHM_PTR_SIZE) +/* FIFO*/ +#define SHM_ACFIFO_0_START_CMCU (SHM_CAFIFO_0_WRITE_CMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_0_START_CMCU (SHM_ACFIFO_0_START_CMCU + SHM_FIFO_0_SIZE) + + +/* ADSP addresses*/ +#define SHM_ACFIFO_0_START_ADSP 0x0 +#define SHM_CAFIFO_0_START_ADSP 0x0 +#define SHM_ACFIFO_0_WRITE_ADSP 0x0 +#define SHM_ACFIFO_0_READ_ADSP 0x0 +#define SHM_CAFIFO_0_WRITE_ADSP 0x0 +#define SHM_CAFIFO_0_READ_ADSP 0x0 + +/* FIFO 1 address configuration */ +/* ---------------------------- */ + + +/* FIFO 1 - 4K */ +#define SHM_FIFO_1_SIZE (4*1024) + + +/* == APE addresses == */ +#define SHM_ACFIFO_1_WRITE_AMCU (SHM_CAFIFO_0_START_AMCU + SHM_FIFO_0_SIZE) +#define SHM_ACFIFO_1_READ_AMCU (SHM_ACFIFO_1_WRITE_AMCU + SHM_PTR_SIZE) +#define SHM_CAFIFO_1_WRITE_AMCU (SHM_ACFIFO_1_WRITE_AMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_1_READ_AMCU (SHM_CAFIFO_1_WRITE_AMCU + SHM_PTR_SIZE) +/* FIFO*/ +#define SHM_ACFIFO_1_START_AMCU (SHM_CAFIFO_1_WRITE_AMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_1_START_AMCU (SHM_ACFIFO_1_START_AMCU + SHM_FIFO_1_SIZE) + + +/* == CMT addresses ==*/ +#define SHM_ACFIFO_1_WRITE_CMCU (SHM_CAFIFO_0_START_CMCU + SHM_FIFO_0_SIZE) +#define SHM_ACFIFO_1_READ_CMCU (SHM_ACFIFO_1_WRITE_CMCU + SHM_PTR_SIZE) +#define SHM_CAFIFO_1_WRITE_CMCU (SHM_ACFIFO_1_WRITE_CMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_1_READ_CMCU (SHM_CAFIFO_1_WRITE_CMCU + SHM_PTR_SIZE) +/* FIFO1 start */ +#define SHM_ACFIFO_1_START_CMCU (SHM_CAFIFO_1_WRITE_CMCU + SHM_CACHE_LINE) +#define SHM_CAFIFO_1_START_CMCU (SHM_ACFIFO_1_START_CMCU + SHM_FIFO_1_SIZE) + + +/* ADSP addresses*/ +#define SHM_ACFIFO_1_START_ADSP 0x0 +#define SHM_CAFIFO_1_START_ADSP 0x0 +#define SHM_ACFIFO_1_WRITE_ADSP 0x0 +#define SHM_ACFIFO_1_READ_ADSP 0x0 +#define SHM_CAFIFO_1_WRITE_ADSP 0x0 +#define SHM_CAFIFO_1_READ_ADSP 0x0 + + +#define U8500_SHM_FIFO_APE_COMMON_BASE (SHM_ACFIFO_0_START_AMCU) +#define U8500_SHM_FIFO_CMT_COMMON_BASE (SHM_CAFIFO_0_START_AMCU) +#define U8500_SHM_FIFO_APE_AUDIO_BASE (SHM_ACFIFO_1_START_AMCU) +#define U8500_SHM_FIFO_CMT_AUDIO_BASE (SHM_CAFIFO_1_START_AMCU) + +#endif /* __SHRM_CONFIG_H */ diff --git a/arch/arm/mach-ux500/include/mach/shrm_driver.h b/arch/arm/mach-ux500/include/mach/shrm_driver.h new file mode 100644 index 00000000000..41f518238b3 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/shrm_driver.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghavi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __SHRM_DRIVER_H__ +#define __SHRM_DRIVER_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define ISA_DEVICES 6 + +#define BOOT_INIT (0) +#define BOOT_INFO_SYNC (1) +#define BOOT_DONE (2) +#define BOOT_UNKNOWN (3) + +/** + * struct shrm_dev - shrm device information + * @ca_wake_irq: CMT wake interrupt number + * @ac_read_notif_0_irq: ape-cmt common channel read notify interrupt + * @ac_read_notif_1_irq: ape-cmt audio channel read notify interrupt + * @ca_msg_pending_notif_0_irq: cmt-ape common channel msg pending interrupt + * @ca_msg_pending_notif_1_irq: cmt-ape audio channel msg pending interrupt + * @intr_base: interrupt base register address + * @ape_common_fifo_base: ape side common channel fifo base addr + * @ape_audio_fifo_base: ape side audio channel fifo base addr + * @cmt_common_fifo_base: cmt side common channel fifo base addr + * @cmt_audio_fifo_base: cmt side audio channel fifo base addr + * @ape_common_fifo_base_phy: physical addr of ape common fifo + * @ape_audio_fifo_base_phy: physical addr of ape audio fifo + * @cmt_common_fifo_base_phy: physical addr of cmt common fifo + * @cmt_audio_fifo_base_phy: physical addr of cmt audio fifo + * @ape_common_fifo_size: ape side common channel fifo size + * @ape_audio_fifo_size: ape side audio channel fifo size + * @cmt_common_fifo_size: cmt side common channel fifo size + * @cmt_audio_fifo_size: cmt side audio channel fifo size + * @netdev_flag_up: flag to indicate up/down of netwok device + * @msr_flag: flag to check on-going MSR sequence + * @ac_common_shared_wptr: ape-cmt common channel write pointer + * @ac_common_shared_rptr: ape-cmt common channel read pointer + * @ca_common_shared_wptr: cmt-ape common channel write pointer + * @ca_common_shared_rptr: cmt-ape common channel read pointer + * @ac_audio_shared_wptr: ape-cmt audio channel write pointer + * @ac_audio_shared_rptr: ape-cmt audio channel read pointer + * @ca_audio_shared_wptr: cmt-ape audio channel write pointer + * @ca_audio_shared_rptr: cmt-ape audio channel read pointer + * @dev: pointer to the driver device + * @ndev: pointer to the network device structure + * @isa_context: pointer to t_isa_driver_sontext dtructure + * @shm_common_ch_wr_wq: work queue for writing to common channel + * @shm_audio_ch_wr_wq: workqueue for writing to audio channel + * @shm_ac_wake_wq: workqueue for receiving ape-cmt wake requests + * @shm_ca_wake_wq: workqueue for receiving cmt-ape wake requests + * @shm_ac_sleep_wq: workqueue for recieving ape-cmt sleep requests + * @send_ac_msg_pend_notify_0: work for handling pending message on common + * channel + * @send_ac_msg_pend_notify_1: work for handling pending message on audio + * channel + * @shm_ac_wake_req: work to send ape-cmt wake request + * @shm_ca_wake_req: work to send cmt-ape wake request + * @shm_ca_sleep_req: work to send cmt-ape sleep request + * @shm_ac_sleep_req: work to send ape-cmt sleep request + */ +struct shrm_dev { + u8 ca_wake_irq; + u8 ac_read_notif_0_irq; + u8 ac_read_notif_1_irq; + u8 ca_msg_pending_notif_0_irq; + u8 ca_msg_pending_notif_1_irq; + void __iomem *intr_base; + void __iomem *ape_common_fifo_base; + void __iomem *ape_audio_fifo_base; + void __iomem *cmt_common_fifo_base; + void __iomem *cmt_audio_fifo_base; + + u32 *ape_common_fifo_base_phy; + u32 *ape_audio_fifo_base_phy; + u32 *cmt_common_fifo_base_phy; + u32 *cmt_audio_fifo_base_phy; + + int ape_common_fifo_size; + int ape_audio_fifo_size; + int cmt_common_fifo_size; + int cmt_audio_fifo_size; + int netdev_flag_up; + int msr_flag; + + void __iomem *ac_common_shared_wptr; + void __iomem *ac_common_shared_rptr; + void __iomem *ca_common_shared_wptr; + void __iomem *ca_common_shared_rptr; + + void __iomem *ac_audio_shared_wptr; + void __iomem *ac_audio_shared_rptr; + void __iomem *ca_audio_shared_wptr; + void __iomem *ca_audio_shared_rptr; + + struct device *dev; + struct net_device *ndev; + struct isa_driver_context *isa_context; + struct workqueue_struct *shm_common_ch_wr_wq; + struct workqueue_struct *shm_audio_ch_wr_wq; + struct workqueue_struct *shm_ac_wake_wq; + struct workqueue_struct *shm_ca_wake_wq; + struct workqueue_struct *shm_ac_sleep_wq; + struct work_struct send_ac_msg_pend_notify_0; + struct work_struct send_ac_msg_pend_notify_1; + struct work_struct shm_ac_wake_req; + struct work_struct shm_ca_wake_req; + struct work_struct shm_ca_sleep_req; + struct work_struct shm_ac_sleep_req; +}; + +/** + * struct queue_element - information to add an element to queue + * @entry: list entry + * @offset: message offset + * @size: message size + * @no: total number of messages + */ +struct queue_element { + struct list_head entry; + u32 offset; + u32 size; + u32 no; +}; + +/** + * struct message_queue - ISI, RPC, AUDIO, SECURITY message queue information + * @fifo_base: pointer to the respective fifo base + * @size: size of the data to be read + * @readptr: fifo read pointer + * @writeptr: fifo write pointer + * @no: total number of messages + * @update_lock: spinlock for protecting the queue read operation + * @q_rp: queue write pointer + * @wq_readable: wait queue head + * @msg_list: message list + * @shrm: pointer to shrm device information structure + */ +struct message_queue { + u8 *fifo_base; + u32 size; + u32 readptr; + u32 writeptr; + u32 no; + spinlock_t update_lock; + atomic_t q_rp; + wait_queue_head_t wq_readable; + struct list_head msg_list; + struct shrm_dev *shrm; +}; + +/** + * struct isadev_context - shrm char interface context + * @dl_queue: structre to store the queue related info + * @device_id: message id(ISI, RPC, AUDIO, SECURITY) + */ +struct isadev_context { + struct message_queue dl_queue; + u8 device_id; + void *addr; +}; + +/** + * struct isa_driver_context - shrm char interface device information + * @is_open: flag to check the usage of queue + * @isadev: pointer to struct t_isadev_context + * @common_tx: spinlock for protecting common channel + * @tx_audio_mutex: mutex for protecting audio channel + * @cdev: character device structre + * @shm_class: pointer to the class structure + */ +struct isa_driver_context { + atomic_t is_open[ISA_DEVICES]; + struct isadev_context *isadev; + spinlock_t common_tx; + struct mutex tx_audio_mutex; + struct cdev cdev; + struct class *shm_class; +}; + +#endif diff --git a/arch/arm/mach-ux500/include/mach/shrm_net.h b/arch/arm/mach-ux500/include/mach/shrm_net.h new file mode 100644 index 00000000000..4e675a98f3d --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/shrm_net.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) ST-Ericsson SA 2009 + * + * Author: Kumar Sanghvi for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __SHRM_NET_H +#define __SHRM_NET_H + +#define SHRM_HLEN 1 +#define PHONET_ALEN 1 + +#define PN_PIPE 0xD9 +#define PN_DEV_HOST 0x00 +#define PN_LINK_ADDR 0x26 +#define PN_TX_QUEUE_LEN 3 + +#define RESOURCE_ID_INDEX 3 +#define SRC_OBJ_INDEX 7 +#define MSG_ID_INDEX 9 +#define PIPE_HDL_INDEX 10 +#define NETLINK_SHRM 20 + +/** + * struct shrm_net_iface_priv - shrm net interface device information + * @shrm_device: pointer to the shrm device information structure + * @iface_num: flag used to indicate the up/down of netdev + */ +struct shrm_net_iface_priv { + struct shrm_dev *shrm_device; + unsigned int iface_num; +}; + +int shrm_register_netdev(struct shrm_dev *shrm_dev_data); +int shrm_net_receive(struct net_device *dev, unsigned char *data); +int shrm_suspend_netdev(struct net_device *dev); +int shrm_resume_netdev(struct net_device *dev); +int shrm_stop_netdev(struct net_device *dev); +int shrm_restart_netdev(struct net_device *dev); +int shrm_start_netdev(struct net_device *dev); +void shrm_unregister_netdev(struct shrm_dev *shrm_dev_data); + +#endif /* __SHRM_NET_H */ diff --git a/arch/arm/mach-ux500/include/mach/shrm_private.h b/arch/arm/mach-ux500/include/mach/shrm_private.h new file mode 100644 index 00000000000..33a0e18234b --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/shrm_private.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghavi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __SHRM_PRIVATE_INCLUDED +#define __SHRM_PRIVATE_INCLUDED + +#include +#include +#include +#include + +#define GOP_OUTPUT_REGISTER_BASE (0x0) +#define GOP_SET_REGISTER_BASE (0x4) +#define GOP_CLEAR_REGISTER_BASE (0x8) +#define GOP_TOGGLE_REGISTER_BASE (0xc) + + +#define GOP_AUDIO_AC_READ_NOTIFICATION_BIT (0) +#define GOP_AUDIO_CA_MSG_PENDING_NOTIFICATION_BIT (1) +#define GOP_COMMON_AC_READ_NOTIFICATION_BIT (2) +#define GOP_COMMON_CA_MSG_PENDING_NOTIFICATION_BIT (3) +#define GOP_CA_WAKE_REQ_BIT (7) +#define GOP_AUDIO_CA_READ_NOTIFICATION_BIT (23) +#define GOP_AUDIO_AC_MSG_PENDING_NOTIFICATION_BIT (24) +#define GOP_COMMON_CA_READ_NOTIFICATION_BIT (25) +#define GOP_COMMON_AC_MSG_PENDING_NOTIFICATION_BIT (26) +#define GOP_CA_WAKE_ACK_BIT (27) + +#define L2_MSG_MAPID_OFFSET (24) +#define L1_MSG_MAPID_OFFSET (28) + +#define SHRM_SLEEP_STATE (0) +#define SHRM_PTR_FREE (1) +#define SHRM_PTR_BUSY (2) +#define SHRM_IDLE (3) + +#define ISI_MESSAGING (0) +#define RPC_MESSAGING (1) +#define AUDIO_MESSAGING (2) +#define SECURITY_MESSAGING (3) +#define COMMON_LOOPBACK_MESSAGING (0xC0) +#define AUDIO_LOOPBACK_MESSAGING (0x80) + +#define COMMON_CHANNEL 0 +#define AUDIO_CHANNEL 1 + +typedef void (*MSG_PENDING_NOTIF)(const u32 Wptr); + +/** + * struct fifo_write_params - parameters used for FIFO write operation. + * @writer_local_rptr: pointer to local read buffer + * @writer_local_wptr: pointer to local write buffer + * @shared_wptr: write pointer shared by cmt and ape + * @shared_rptr: read pointer shared by cmt and ape + * @availablesize: available memory in fifo + * @end_addr_fifo: fifo end addr + * @fifo_virtual_addr: fifo virtual addr + * + * On writting a message to FIFO the same has to be read by the modem before + * writing the next message to the FIFO. In oder to over come this a local + * write and read pointer is used for internal purpose. + */ +struct fifo_write_params { + u32 writer_local_rptr; + u32 writer_local_wptr; + u32 shared_wptr; + u32 shared_rptr; + u32 availablesize; + u32 end_addr_fifo; + u32 *fifo_virtual_addr; + spinlock_t fifo_update_lock; +} ; + +/** + * struct fifo_read_params - parameters used for FIFO read operation + * @reader_local_rptr: pointer to local read buffer + * @reader_local_wptr: pointer to local write buffer + * @shared_wptr: write pointer shared by cmt and ape + * @shared_rptr: read pointer shared by cmt and ape + * @availablesize: available memory in fifo + * @end_addr_fifo: fifo end add + * @fifo_virtual_addr: fifo virtual addr + */ +struct fifo_read_params{ + u32 reader_local_rptr; + u32 reader_local_wptr; + u32 shared_wptr; + u32 shared_rptr; + u32 availablesize; + u32 end_addr_fifo; + u32 *fifo_virtual_addr; + +} ; + +int shrm_protocol_init(struct shrm_dev *shrm, + received_msg_handler common_rx_handler, + received_msg_handler audio_rx_handler); +void shrm_protocol_deinit(struct shrm_dev *shrm); +void shm_fifo_init(struct shrm_dev *shrm); +int shm_write_msg_to_fifo(struct shrm_dev *shrm, u8 channel, + u8 l2header, void *addr, u32 length); +int shm_write_msg(struct shrm_dev *shrm, + u8 l2_header, void *addr, u32 length); + +u8 is_the_only_one_unread_message(struct shrm_dev *shrm, + u8 channel, u32 length); +u8 read_remaining_messages_common(void); +u8 read_remaining_messages_audio(void); +u8 read_one_l2msg_audio(struct shrm_dev *shrm, + u8 *p_l2_msg, u32 *p_len); +u8 read_one_l2msg_common(struct shrm_dev *shrm, + u8 *p_l2_msg, u32 *p_len); +void receive_messages_common(struct shrm_dev *shrm); +void receive_messages_audio(struct shrm_dev *shrm); + +void update_ac_common_local_rptr(struct shrm_dev *shrm); +void update_ac_audio_local_rptr(struct shrm_dev *shrm); +void update_ca_common_local_wptr(struct shrm_dev *shrm); +void update_ca_audio_local_wptr(struct shrm_dev *shrm); +void update_ac_common_shared_wptr(struct shrm_dev *shrm); +void update_ac_audio_shared_wptr(struct shrm_dev *shrm); +void update_ca_common_shared_rptr(struct shrm_dev *shrm); +void update_ca_audio_shared_rptr(struct shrm_dev *shrm); + + +void get_writer_pointers(u8 msg_type, u32 *WriterLocalRptr, \ + u32 *WriterLocalWptr, u32 *SharedWptr); +void get_reader_pointers(u8 msg_type, u32 *ReaderLocalRptr, \ + u32 *ReaderLocalWptr, u32 *SharedRptr); +u8 read_boot_info_req(struct shrm_dev *shrm, + u32 *pConfig, + u32 *pVersion); +void write_boot_info_resp(struct shrm_dev *shrm, u32 Config, + u32 Version); + +void send_ac_msg_pending_notification_0(struct shrm_dev *shrm); +void send_ac_msg_pending_notification_1(struct shrm_dev *shrm); +void ca_msg_read_notification_0(struct shrm_dev *shrm); +void ca_msg_read_notification_1(struct shrm_dev *shrm); + +void set_ca_msg_0_read_notif_send(u8 val); +u8 get_ca_msg_0_read_notif_send(void); +void set_ca_msg_1_read_notif_send(u8 val); +u8 get_ca_msg_1_read_notif_send(void); + +irqreturn_t ca_wake_irq_handler(int irq, void *ctrlr); +irqreturn_t ac_read_notif_0_irq_handler(int irq, void *ctrlr); +irqreturn_t ac_read_notif_1_irq_handler(int irq, void *ctrlr); +irqreturn_t ca_msg_pending_notif_0_irq_handler(int irq, void *ctrlr); +irqreturn_t ca_msg_pending_notif_1_irq_handler(int irq, void *ctrlr); + +void shm_ca_msgpending_0_tasklet(unsigned long); +void shm_ca_msgpending_1_tasklet(unsigned long); +void shm_ac_read_notif_0_tasklet(unsigned long); +void shm_ac_read_notif_1_tasklet(unsigned long); +void shm_ca_wake_req_tasklet(unsigned long); + +u8 get_boot_state(void); + +int get_ca_wake_req_state(void); + +/* shrm character interface */ +int isa_init(struct shrm_dev *shrm); +void isa_exit(struct shrm_dev *shrm); +int add_msg_to_queue(struct message_queue *q, u32 size); +ssize_t isa_read(struct file *filp, char __user *buf, size_t len, + loff_t *ppos); +int get_size_of_new_msg(struct message_queue *q); +int remove_msg_from_queue(struct message_queue *q); +void shrm_char_reset_queues(struct shrm_dev *shrm); +int shrm_get_cdev_index(u8 l2_header); +int shrm_get_cdev_l2header(u8 idx); + +#endif diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 7a00672bd85..6e0655a71f1 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -50,6 +50,10 @@ obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_GPIO_TB0219) += tb0219.o obj-$(CONFIG_TELCLOCK) += tlclk.o +ifdef CONFIG_PHONET +obj-$(CONFIG_U8500_SHRM) += shrm_char.o +endif + obj-$(CONFIG_MWAVE) += mwave/ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_PCMCIA) += pcmcia/ diff --git a/drivers/char/shrm_char.c b/drivers/char/shrm_char.c new file mode 100644 index 00000000000..256add7cdb8 --- /dev/null +++ b/drivers/char/shrm_char.c @@ -0,0 +1,858 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghavi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#define NAME "IPC_ISA" +/* L2 header for common loopback device is 0xc0 and hence 0xc0+1 = 193*/ +#define MAX_L2_HEADERS 193 + +#define SIZE_OF_FIFO (512*1024) + +static u8 message_fifo[ISA_DEVICES][SIZE_OF_FIFO]; + +static u8 wr_rpc_msg[10*1024]; +static u8 wr_sec_msg[10*1024]; +static u8 wr_audio_msg[10*1024]; + +struct map_device { + u8 l2_header; + u8 idx; + char *name; +}; + +static struct map_device map_dev[] = { + {ISI_MESSAGING, 0, "isi"}, + {RPC_MESSAGING, 1, "rpc"}, + {AUDIO_MESSAGING, 2, "modemaudio"}, + {SECURITY_MESSAGING, 3, "sec"}, + {COMMON_LOOPBACK_MESSAGING, 4, "common_loopback"}, + {AUDIO_LOOPBACK_MESSAGING, 5, "audio_loopback"}, +}; + +/* + * int major:This variable is exported to user as module_param to specify + * major number at load time + */ +static int major; +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); +/* global fops mutex */ +static DEFINE_MUTEX(isa_lock); + +/** + * shrm_get_cdev_index() - return the index mapped to l2 header + * @l2_header: L2 header + * + * struct map_device maps the index(count) with the device L2 header. + * This function returns the index for the provided L2 header in case + * of success else -ve value. + */ +int shrm_get_cdev_index(u8 l2_header) +{ + u8 cnt; + for (cnt = 0; cnt < ISA_DEVICES; cnt++) { + if (map_dev[cnt].l2_header == l2_header) + return map_dev[cnt].idx; + } + return -EINVAL; +} + +/** + * shrm_get_cdev_l2header() - return l2_header mapped to the index + * @idx: index + * + * struct map_device maps the index(count) with the device L2 header. + * This function returns the L2 header for the given index in case + * of success else -ve value. + */ +int shrm_get_cdev_l2header(u8 idx) +{ + u8 cnt; + for (cnt = 0; cnt < ISA_DEVICES; cnt++) { + if (map_dev[cnt].idx == idx) + return map_dev[cnt].l2_header; + } + return -EINVAL; +} + +void shrm_char_reset_queues(struct shrm_dev *shrm) +{ + struct isadev_context *isadev; + struct isa_driver_context *isa_context; + struct queue_element *cur_msg = NULL; + struct list_head *cur_msg_ptr = NULL; + struct list_head *msg_ptr; + struct message_queue *q; + int no_dev; + + dev_info(shrm->dev, "%s: Resetting char device queues\n", __func__); + isa_context = shrm->isa_context; + for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + q = &isadev->dl_queue; + + /* empty out the msg queue */ + list_for_each_safe(cur_msg_ptr, msg_ptr, &q->msg_list) { + cur_msg = list_entry(cur_msg_ptr, + struct queue_element, entry); + list_del(cur_msg_ptr); + kfree(cur_msg); + } + + /* reset the msg queue pointers */ + q->size = SIZE_OF_FIFO; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + + /* wake up the blocking read/select */ + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + } +} + +/** + * create_queue() - To create FIFO for Tx and Rx message buffering. + * @q: message queue. + * @devicetype: device type 0-isi,1-rpc,2-audio,3-security, + * 4-common_loopback, 5-audio_loopback. + * @shrm: pointer to the shrm device information structure + * + * This function creates a FIFO buffer of n_bytes size using + * dma_alloc_coherent(). It also initializes all queue handling + * locks, queue management pointers. It also initializes message list + * which occupies this queue. + */ +static int create_queue(struct message_queue *q, u32 devicetype, + struct shrm_dev *shrm) +{ + q->fifo_base = (u8 *)&message_fifo[devicetype]; + q->size = SIZE_OF_FIFO; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + q->shrm = shrm; + spin_lock_init(&q->update_lock); + INIT_LIST_HEAD(&q->msg_list); + init_waitqueue_head(&q->wq_readable); + atomic_set(&q->q_rp, 0); + + return 0; +} + +static void delete_queue(struct message_queue *q) +{ + q->size = 0; + q->readptr = 0; + q->writeptr = 0; +} + +/** + * add_msg_to_queue() - Add a message inside queue + * @q: message queue + * @size: size in bytes + * + * This function tries to allocate n_bytes of size in FIFO q. + * It returns negative number when no memory can be allocated + * currently. + */ +int add_msg_to_queue(struct message_queue *q, u32 size) +{ + struct queue_element *new_msg = NULL; + struct shrm_dev *shrm = q->shrm; + + dev_dbg(shrm->dev, "%s IN q->writeptr=%d\n", __func__, q->writeptr); + new_msg = kmalloc(sizeof(struct queue_element), GFP_ATOMIC); + if (new_msg == NULL) { + dev_err(shrm->dev, "unable to allocate memory\n"); + return -ENOMEM; + } + new_msg->offset = q->writeptr; + new_msg->size = size; + new_msg->no = q->no++; + + /* check for overflow condition */ + if (q->readptr <= q->writeptr) { + if (((q->writeptr-q->readptr) + size) >= q->size) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON(((q->writeptr-q->readptr) + size) >= q->size); + } + } else { + if ((q->writeptr + size) >= q->readptr) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON((q->writeptr + size) >= q->readptr); + } + } + q->writeptr = (q->writeptr + size) % q->size; + if (list_empty(&q->msg_list)) { + list_add_tail(&new_msg->entry, &q->msg_list); + /* There can be 2 blocking calls read and another select */ + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + } else + list_add_tail(&new_msg->entry, &q->msg_list); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * remove_msg_from_queue() - To remove a message from the msg queue. + * @q: message queue + * + * This function delets a message from the message list associated with message + * queue q and also updates read ptr. + * If the message list is empty, then, event is set to block the select and + * read calls of the paricular queue. + * + * The message list is FIFO style and message is always added to tail and + * removed from head. + */ +int remove_msg_from_queue(struct message_queue *q) +{ + struct queue_element *old_msg = NULL; + struct shrm_dev *shrm = q->shrm; + struct list_head *msg; + + dev_dbg(shrm->dev, "%s IN q->readptr %d\n", __func__, q->readptr); + + list_for_each(msg, &q->msg_list) { + old_msg = list_entry(msg, struct queue_element, entry); + if (old_msg == NULL) { + dev_err(shrm->dev, "no message found\n"); + return -EFAULT; + } + break; + } + list_del(msg); + q->readptr = (q->readptr + old_msg->size)%q->size; + if (list_empty(&q->msg_list)) { + dev_dbg(shrm->dev, "List is empty setting RP= 0\n"); + atomic_set(&q->q_rp, 0); + } + kfree(old_msg); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * get_size_of_new_msg() - retrieve new message from message list + * @q: message queue + * + * This function will retrieve most recent message from the corresponding + * queue list. New message is always retrieved from head side. + * It returns new message no, offset if FIFO and size. + */ +int get_size_of_new_msg(struct message_queue *q) +{ + struct queue_element *new_msg = NULL; + struct list_head *msg_list; + struct shrm_dev *shrm = q->shrm; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + spin_lock_bh(&q->update_lock); + list_for_each(msg_list, &q->msg_list) { + new_msg = list_entry(msg_list, struct queue_element, entry); + if (new_msg == NULL) { + spin_unlock_bh(&q->update_lock); + dev_err(shrm->dev, "no message found\n"); + return -EFAULT; + } + break; + } + spin_unlock_bh(&q->update_lock); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return new_msg->size; +} + +/** + * isa_select() - shrm char interface driver select interface + * @filp: file descriptor pointer + * @wait: poll_table_struct pointer + * + * This function is used to perform non-blocking read operations. It allows + * a process to determine whether it can read from one or more open files + * without blocking. These calls can also block a process until any of a + * given set of file descriptors becomes available for reading. + * If a file is ready to read, POLLIN | POLLRDNORM bitmask is returned. + * The driver method is called whenever the user-space program performs a select + * system call involving a file descriptor associated with the driver. + */ +static u32 isa_select(struct file *filp, + struct poll_table_struct *wait) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + u32 mask = 0; + u32 m = iminor(filp->f_path.dentry->d_inode); + u8 idx = shrm_get_cdev_index(m); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (shrm->msr_flag) + return -ENODEV; + + if (isadev->device_id != idx) + return -1; + + q = &isadev->dl_queue; + poll_wait(filp, &q->wq_readable, wait); + if (atomic_read(&q->q_rp) == 1) + mask = POLLIN | POLLRDNORM; + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return mask; +} + +/** + * isa_read() - Read from device + * @filp: file descriptor + * @buf: user buffer pointer + * @len: size of requested data transfer + * @ppos: not used + * + * It reads a oldest message from queue and copies it into user buffer and + * returns its size. + * If there is no message present in queue, then it blocks until new data is + * available. + */ +ssize_t isa_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) +{ + u32 size = 0; + int ret; + char *psrc; + struct isadev_context *isadev = (struct isadev_context *) + filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + u32 msgsize; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (len <= 0) + return -EFAULT; + + q = &isadev->dl_queue; + + if (shrm->msr_flag) { + atomic_set(&q->q_rp, 0); + return -ENODEV; + } + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "Waiting for Data\n"); + if (wait_event_interruptible(q->wq_readable, + atomic_read(&q->q_rp) == 1)) + return -ERESTARTSYS; + } else + spin_unlock_bh(&q->update_lock); + + if (shrm->msr_flag) { + atomic_set(&q->q_rp, 0); + return -ENODEV; + } + + msgsize = get_size_of_new_msg(q); + + if (len < msgsize) + return -EINVAL; + + if ((q->readptr+msgsize) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (char *)buf; + size = (q->size-q->readptr); + /* Copy First Part of msg */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base+q->readptr), + size)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base), + (msgsize-size))) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } else { + if (copy_to_user(buf, + (u8 *)(q->fifo_base + q->readptr), + msgsize)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } + spin_lock_bh(&q->update_lock); + ret = remove_msg_from_queue(q); + if (ret < 0) { + dev_err(shrm->dev, + "Remove msg from message queue failed\n"); + msgsize = ret; + } + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return msgsize; +} + +/** + * isa_write() - Write to shrm char device + * @filp: file descriptor + * @buf: user buffer pointer + * @len: size of requested data transfer + * @ppos: not used + * + * It checks if there is space available in queue, and copies the message + * inside queue. If there is no space, it blocks until space becomes available. + * It also schedules transfer thread to transmit the newly added message. + */ +ssize_t isa_write(struct file *filp, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + void *addr = 0; + int err, l2_header; + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (len <= 0 || buf == NULL) + return -EFAULT; + q = &isadev->dl_queue; + l2_header = shrm_get_cdev_l2header(isadev->device_id); + if (l2_header < 0) { + dev_err(shrm->dev, "failed to get L2 header\n"); + return l2_header; + } + + switch (l2_header) { + case RPC_MESSAGING: + dev_dbg(shrm->dev, "RPC\n"); + addr = (void *)wr_rpc_msg; + break; + case AUDIO_MESSAGING: + dev_dbg(shrm->dev, "Audio\n"); + addr = (void *)wr_audio_msg; + break; + case SECURITY_MESSAGING: + dev_dbg(shrm->dev, "Security\n"); + addr = (void *)wr_sec_msg; + break; + case COMMON_LOOPBACK_MESSAGING: + dev_dbg(shrm->dev, "Common loopback\n"); + addr = isadev->addr; + break; + case AUDIO_LOOPBACK_MESSAGING: + dev_dbg(shrm->dev, "Audio loopback\n"); + addr = isadev->addr; + break; + default: + dev_dbg(shrm->dev, "Wrong device\n"); + return -EFAULT; + } + + if (copy_from_user(addr, buf, len)) { + dev_err(shrm->dev, "copy_from_user failed\n"); + return -EFAULT; + } + /* Write msg to Fifo */ + if ((l2_header == AUDIO_MESSAGING) || + (l2_header == AUDIO_LOOPBACK_MESSAGING)) { + mutex_lock(&shrm->isa_context->tx_audio_mutex); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + mutex_unlock(&shrm->isa_context->tx_audio_mutex); + } else { + spin_lock_bh(&shrm->isa_context->common_tx); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + spin_unlock_bh(&shrm->isa_context->common_tx); + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * isa_ioctl() - To handle different ioctl commands supported by driver. + * @inode: structure is used by the kernel internally to represent files + * @filp: file descriptor pointer + * @cmd: ioctl command + * @arg: input param + * + * Following ioctls are supported by this driver. + * DLP_IOCTL_ALLOCATE_BUFFER - To allocate buffer for new uplink message. + * This ioctl is called with required message size. It returns offset for + * the allocates space in the queue. DLP_IOCTL_PUT_MESSAGE - To indicate + * new uplink message available in queuq for transmission. Message is copied + * from offset location returned by previous ioctl before calling this ioctl. + * DLP_IOCTL_GET_MESSAGE - To check if any downlink message is available in + * queue. It returns offset for new message inside queue. + * DLP_IOCTL_DEALLOCATE_BUFFER - To deallocate any buffer allocate for + * downlink message once the message is copied. Message is copied from offset + * location returned by previous ioctl before calling this ioctl. + */ +static int isa_ioctl(struct inode *inode, struct file *filp, + unsigned cmd, unsigned long arg) +{ + int err = 0; + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + u32 m = iminor(inode); + + isadev = (struct isadev_context *)filp->private_data; + + if (isadev->device_id != m) + return -EINVAL; + + switch (cmd) { + case DLP_IOC_ALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_ALLOCATE_BUFFER\n"); + break; + case DLP_IOC_PUT_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_PUT_MESSAGE\n"); + break; + case DLP_IOC_GET_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_GET_MESSAGE\n"); + break; + case DLP_IOC_DEALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_DEALLOCATE_BUFFER\n"); + break; + default: + dev_dbg(shrm->dev, "Unknown IOCTL\n"); + err = -EFAULT; + break; + } + return err; +} +/** + * isa_mmap() - Maps kernel queue memory to user space. + * @filp: file descriptor pointer + * @vma: virtual area memory structure. + * + * This function maps kernel FIFO into user space. This function + * shall be called twice to map both uplink and downlink buffers. + */ +static int isa_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + + u32 m = iminor(filp->f_path.dentry->d_inode); + dev_dbg(shrm->dev, "%s %d\n", __func__, m); + + return 0; +} + +/** + * isa_close() - Close device file + * @inode: structure is used by the kernel internally to represent files + * @filp: device file descriptor + * + * This function deletes structues associated with this file, deletes + * queues, flushes and destroys workqueus and closes this file. + * It also unregisters itself from l2mux driver. + */ +static int isa_close(struct inode *inode, struct file *filp) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct isa_driver_context *isa_context = shrm->isa_context; + u8 m; + int idx; + + mutex_lock(&isa_lock); + m = iminor(filp->f_path.dentry->d_inode); + idx = shrm_get_cdev_index(m); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + return idx; + } + dev_dbg(shrm->dev, "isa_close %d", m); + + if (atomic_dec_and_test(&isa_context->is_open[idx])) { + atomic_inc(&isa_context->is_open[idx]); + dev_err(shrm->dev, "Device not opened yet\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + atomic_set(&isa_context->is_open[idx], 1); + + switch (m) { + case RPC_MESSAGING: + dev_info(shrm->dev, "Close RPC_MESSAGING Device\n"); + break; + case AUDIO_MESSAGING: + dev_info(shrm->dev, "Close AUDIO_MESSAGING Device\n"); + break; + case SECURITY_MESSAGING: + dev_info(shrm->dev, "CLose SECURITY_MESSAGING Device\n"); + break; + case COMMON_LOOPBACK_MESSAGING: + kfree(isadev->addr); + dev_info(shrm->dev, "Close COMMON_LOOPBACK_MESSAGING Device\n"); + break; + case AUDIO_LOOPBACK_MESSAGING: + kfree(isadev->addr); + dev_info(shrm->dev, "Close AUDIO_LOOPBACK_MESSAGING Device\n"); + break; + default: + dev_info(shrm->dev, "No such device present\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + }; + mutex_unlock(&isa_lock); + return 0; +} +/** + * isa_open() - Open device file + * @inode: structure is used by the kernel internally to represent files + * @filp: device file descriptor + * + * This function performs initialization tasks needed to open SHM channel. + * Following tasks are performed. + * -return if device is already opened + * -create uplink FIFO + * -create downlink FIFO + * -init delayed workqueue thread + * -register to l2mux driver + */ +static int isa_open(struct inode *inode, struct file *filp) +{ + int err = 0; + u8 m; + int idx; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = container_of( + inode->i_cdev, + struct isa_driver_context, + cdev); + struct shrm_dev *shrm = isa_context->isadev->dl_queue.shrm; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_boot_state() != BOOT_DONE) { + dev_err(shrm->dev, "Boot is not done\n"); + return -EBUSY; + } + mutex_lock(&isa_lock); + m = iminor(inode); + + if ((m != RPC_MESSAGING) && + (m != AUDIO_LOOPBACK_MESSAGING) && + (m != COMMON_LOOPBACK_MESSAGING) && + (m != AUDIO_MESSAGING) && + (m != SECURITY_MESSAGING)) { + dev_err(shrm->dev, "No such device present\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + idx = shrm_get_cdev_index(m); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + return idx; + } + if (!atomic_dec_and_test(&isa_context->is_open[idx])) { + atomic_inc(&isa_context->is_open[idx]); + dev_err(shrm->dev, "Device already opened\n"); + mutex_unlock(&isa_lock); + return -EBUSY; + } + isadev = &isa_context->isadev[idx]; + if (filp != NULL) + filp->private_data = isadev; + + switch (m) { + case RPC_MESSAGING: + dev_info(shrm->dev, "Open RPC_MESSAGING Device\n"); + break; + case AUDIO_MESSAGING: + dev_info(shrm->dev, "Open AUDIO_MESSAGING Device\n"); + break; + case SECURITY_MESSAGING: + dev_info(shrm->dev, "Open SECURITY_MESSAGING Device\n"); + break; + case COMMON_LOOPBACK_MESSAGING: + isadev->addr = kzalloc(10 * 1024, GFP_KERNEL); + if (!isadev->addr) { + mutex_unlock(&isa_lock); + return -ENOMEM; + } + dev_info(shrm->dev, "Open COMMON_LOOPBACK_MESSAGING Device\n"); + break; + case AUDIO_LOOPBACK_MESSAGING: + isadev->addr = kzalloc(10 * 1024, GFP_KERNEL); + if (!isadev->addr) { + mutex_unlock(&isa_lock); + return -ENOMEM; + } + dev_info(shrm->dev, "Open AUDIO_LOOPBACK_MESSAGING Device\n"); + break; + }; + + mutex_unlock(&isa_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return err; +} + +const struct file_operations isa_fops = { + .owner = THIS_MODULE, + .open = isa_open, + .release = isa_close, + .ioctl = isa_ioctl, + .mmap = isa_mmap, + .read = isa_read, + .write = isa_write, + .poll = isa_select, +}; + +/** + * isa_init() - module insertion function + * @shrm: pointer to the shrm device information structure + * + * This function registers module as a character driver using + * register_chrdev_region() or alloc_chrdev_region. It adds this + * driver to system using cdev_add() call. Major number is dynamically + * allocated using alloc_chrdev_region() by default or left to user to specify + * it during load time. For this variable major is used as module_param + * Nodes to be created using + * mknod /dev/isi c $major 0 + * mknod /dev/rpc c $major 1 + * mknod /dev/audio c $major 2 + * mknod /dev/sec c $major 3 + */ +int isa_init(struct shrm_dev *shrm) +{ + dev_t dev_id; + int retval, no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context; + + isa_context = kzalloc(sizeof(struct isa_driver_context), + GFP_KERNEL); + if (isa_context == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + shrm->isa_context = isa_context; + if (major) { + dev_id = MKDEV(major, MAX_L2_HEADERS); + retval = register_chrdev_region(dev_id, ISA_DEVICES, NAME); + } else { + /* + * L2 header of loopback device is 192(0xc0). As per the shrm + * protocol the minor id of the deivce is mapped to the + * L2 header. + */ + retval = alloc_chrdev_region(&dev_id, 0, MAX_L2_HEADERS, NAME); + major = MAJOR(dev_id); + } + dev_dbg(shrm->dev, " major %d\n", major); + + cdev_init(&isa_context->cdev, &isa_fops); + isa_context->cdev.owner = THIS_MODULE; + retval = cdev_add(&isa_context->cdev, dev_id, MAX_L2_HEADERS); + if (retval) { + dev_err(shrm->dev, "Failed to add char device\n"); + return retval; + } + /* create class and device */ + isa_context->shm_class = class_create(THIS_MODULE, NAME); + if (IS_ERR(isa_context->shm_class)) { + dev_err(shrm->dev, "Error creating shrm class\n"); + cdev_del(&isa_context->cdev); + retval = PTR_ERR(isa_context->shm_class); + kfree(isa_context); + return retval; + } + + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) { + atomic_set(&isa_context->is_open[no_dev], 1); + device_create(isa_context->shm_class, NULL, + MKDEV(MAJOR(dev_id), + map_dev[no_dev].l2_header), NULL, + map_dev[no_dev].name); + } + + isa_context->isadev = kzalloc(sizeof + (struct isadev_context)*ISA_DEVICES, + GFP_KERNEL); + if (isa_context->isadev == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + isadev->device_id = no_dev; + retval = create_queue(&isadev->dl_queue, + isadev->device_id, shrm); + + if (retval < 0) { + dev_err(shrm->dev, "create dl_queue failed\n"); + delete_queue(&isadev->dl_queue); + kfree(isadev); + return retval; + } + } + mutex_init(&isa_context->tx_audio_mutex); + spin_lock_init(&isa_context->common_tx); + dev_dbg(shrm->dev, " SHM Char Driver added\n"); + return retval; +} + +void isa_exit(struct shrm_dev *shrm) +{ + int no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = shrm->isa_context; + dev_t dev_id = MKDEV(major, 0); + + for (no_dev = 0 ; no_dev < ISA_DEVICES ; no_dev++) { + device_destroy(isa_context->shm_class, + MKDEV(MAJOR(dev_id), + map_dev[no_dev].l2_header)); + isadev = &isa_context->isadev[no_dev]; + delete_queue(&isadev->dl_queue); + kfree(isadev); + } + class_destroy(isa_context->shm_class); + cdev_del(&isa_context->cdev); + unregister_chrdev_region(dev_id, ISA_DEVICES); + kfree(isa_context); + dev_dbg(shrm->dev, " SHM Char Driver removed\n"); +} diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 8dce7fc1e20..f55f38d9df6 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -505,6 +505,7 @@ source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" +source "drivers/misc/shrm/Kconfig" source "drivers/misc/i2s/Kconfig" source "drivers/misc/audio_io_dev/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 4682fccff1f..8416e0ea5fe 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -46,6 +46,7 @@ obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ +obj-$(CONFIG_U8500_SHRM) += shrm/ obj-$(CONFIG_STM_I2S) += i2s/ obj-$(CONFIG_STE_AUDIO_IO_DEV) += audio_io_dev/ obj-$(CONFIG_HWMEM) += hwmem/ diff --git a/drivers/misc/shrm/Kconfig b/drivers/misc/shrm/Kconfig new file mode 100644 index 00000000000..fffee1c703e --- /dev/null +++ b/drivers/misc/shrm/Kconfig @@ -0,0 +1,49 @@ +# +# SHM HW kernel configuration +# +config U8500_SHRM + tristate "U8500 SHRM hardware driver" + depends on ARCH_U8500 && PHONET + default Y + ---help--- + If you say Y here, you will enable the STN8500 SHM hardware driver. + + If unsure, say N. +choice + prompt "Modem Image Version" + depends on U8500_SHRM + default SHRM_V1_UPDATES_VERSION + + config SHRM_ED_V1_VERSION + depends on U8500_SHRM + bool "SHRM ED / V1 " + help + Modem Images with ED/V1 updates + + config SHRM_V1_UPDATES_VERSION + depends on U8500_SHRM + bool "SHRM V1 UPDATES" + help + Modem Images with V1 Updates + +endchoice + +config U8500_SHRM_LOOP_BACK + tristate "U8500 SHRM loopback" + depends on U8500_SHRM + default n + ---help--- + If you say Y here, you will enable the shm loopback + + If unsure, say N. + +config U8500_SHRM_MODEM_SILENT_RESET + bool "U8500 SHRM Modem Silent Reset" + depends on U8500_SHRM + default n + ---help--- + If you say Y here, you will enable the modem silent reset feature + + If unsure, say N. + + diff --git a/drivers/misc/shrm/Makefile b/drivers/misc/shrm/Makefile new file mode 100644 index 00000000000..8115c24920b --- /dev/null +++ b/drivers/misc/shrm/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for SHRM drivers +# + +ifdef CONFIG_PHONET +u8500_shrm-objs := modem_shrm_driver.o shrm_fifo.o shrm_protocol.o +else +u8500_shrm-objs := shrm_driver.o shrm_fifo.o shrm_protocol.o +endif + +obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o diff --git a/drivers/misc/shrm/modem_shrm_driver.c b/drivers/misc/shrm/modem_shrm_driver.c new file mode 100644 index 00000000000..d94c007098b --- /dev/null +++ b/drivers/misc/shrm/modem_shrm_driver.c @@ -0,0 +1,667 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghvi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#ifdef CONFIG_HIGH_RES_TIMERS +#include +static struct hrtimer timer; +#endif +#include +#include +#include + +/* debug functionality */ +#define ISA_DEBUG 0 + +#define PHONET_TASKLET +#define MAX_RCV_LEN 2048 +static u8 ph_recv_buf[MAX_RCV_LEN]; + +void do_phonet_rcv_tasklet(unsigned long unused); +struct tasklet_struct phonet_rcv_tasklet; + +/** + * audio_receive() - Receive audio channel completion callback + * @shrm: pointer to shrm device information structure + * @data: message pointer + * @n_bytes: message size + * @l2_header: L2 header/device ID 2->audio, 5->audio_loopback + * + * This fucntion is called from the audio receive handler. Copies the audio + * message from the FIFO to the AUDIO queue. The message is later copied from + * this queue to the user buffer through the char or net interface read + * operation. + */ +static int audio_receive(struct shrm_dev *shrm, void *data, + u32 n_bytes, u8 l2_header) +{ + u32 size = 0; + int ret = 0; + int idx; + u8 *psrc; + struct message_queue *q; + struct isadev_context *audiodev; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + idx = shrm_get_cdev_index(l2_header); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + return idx; + } + audiodev = &shrm->isa_context->isadev[idx]; + q = &audiodev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + spin_unlock(&q->update_lock); + if (ret < 0) + dev_err(shrm->dev, "Adding a msg to message queue failed"); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * common_receive() - Receive common channel completion callback + * @shrm: pointer to the shrm device information structure + * @data: message pointer + * @n_bytes: message size + * @l2_header: L2 header / device ID + * + * This function is called from the receive handler to copy the respective + * ISI, RPC, SECURITY message to its respective queue. The message is then + * copied from queue to the user buffer on char net interface read operation. + */ +static int common_receive(struct shrm_dev *shrm, void *data, + u32 n_bytes, u8 l2_header) +{ + u32 size = 0; + int ret = 0; + int idx; + u8 *psrc; + struct message_queue *q; + struct isadev_context *isa_dev; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + idx = shrm_get_cdev_index(l2_header); + if (idx < 0) { + dev_err(shrm->dev, "failed to get index\n"); + return idx; + } + isa_dev = &shrm->isa_context->isadev[idx]; + q = &isa_dev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + spin_unlock(&q->update_lock); + if (ret < 0) { + dev_err(shrm->dev, "Adding a msg to message queue failed"); + return ret; + } + + + if (l2_header == ISI_MESSAGING) { + if (shrm->netdev_flag_up) { + dev_dbg(shrm->dev, + "scheduling the phonet tasklet from %s!\n", + __func__); + tasklet_schedule(&phonet_rcv_tasklet); + } + dev_dbg(shrm->dev, + "Out of phonet tasklet %s!!!\n", __func__); + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * rx_common_l2msg_handler() - common channel receive handler + * @l2_header: L2 header + * @msg: pointer to the receive buffer + * @length: length of the msg to read + * @shrm: pointer to shrm device information structure + * + * This function is called to receive the message from CaMsgPendingNotification + * interrupt handler. + */ +static void rx_common_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; + dev_dbg(shrm->dev, "%s IN\n", __func__); + + ret = common_receive(shrm, msg, length, l2_header); + if (ret < 0) + dev_err(shrm->dev, + "common receive with l2 header %d failed\n", l2_header); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +/** + * rx_audio_l2msg_handler() - audio channel receive handler + * @l2_header: L2 header + * @msg: pointer to the receive buffer + * @length: length of the msg to read + * @shrm: pointer to shrm device information structure + * + * This function is called to receive the message from CaMsgPendingNotification + * interrupt handler. + */ +static void rx_audio_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + ret = audio_receive(shrm, msg, length, l2_header); + if (ret < 0) + dev_err(shrm->dev, "audio receive failed\n"); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static int __init shm_initialise_irq(struct shrm_dev *shrm) +{ + int err = 0; + + err = shrm_protocol_init(shrm, + rx_common_l2msg_handler, rx_audio_l2msg_handler); + if (err < 0) { + dev_err(shrm->dev, "SHM Protocol Init Failure\n"); + return err; + } + + err = request_irq(shrm->ca_wake_irq, + ca_wake_irq_handler, IRQF_TRIGGER_RISING, + "ca_wake-up", shrm); + if (err < 0) { + dev_err(shrm->dev, + "Unable to allocate shm tx interrupt line\n"); + free_irq(shrm->ca_wake_irq, shrm); + return err; + } + + err = request_irq(shrm->ac_read_notif_0_irq, + ac_read_notif_0_irq_handler, 0, + "ac_read_notif_0", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_0_irq interrupt line\n"); + goto irq_err1; + } + + err = request_irq(shrm->ac_read_notif_1_irq, + ac_read_notif_1_irq_handler, 0, + "ac_read_notif_1", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_1_irq interrupt line\n"); + goto irq_err2; + } + + err = request_irq(shrm->ca_msg_pending_notif_0_irq, + ca_msg_pending_notif_0_irq_handler, 0, + "ca_msg_pending_notif_0", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_0_irq line\n"); + goto irq_err3; + } + + err = request_irq(shrm->ca_msg_pending_notif_1_irq, + ca_msg_pending_notif_1_irq_handler, 0, + "ca_msg_pending_notif_1", shrm); + + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_1_irq interrupt line\n"); + goto irq_err4; + } + return err; +irq_err4: + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); +irq_err3: + free_irq(shrm->ac_read_notif_1_irq, shrm); +irq_err2: + free_irq(shrm->ac_read_notif_0_irq, shrm); +irq_err1: + free_irq(shrm->ca_wake_irq, shrm); + return err; +} + +static void free_shm_irq(struct shrm_dev *shrm) +{ + free_irq(shrm->ca_wake_irq, shrm); + free_irq(shrm->ac_read_notif_0_irq, shrm); + free_irq(shrm->ac_read_notif_1_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_1_irq, shrm); +} + + + +#ifdef CONFIG_HIGH_RES_TIMERS +static enum hrtimer_restart callback(struct hrtimer *timer) +{ + return HRTIMER_NORESTART; +} +#endif + +void do_phonet_rcv_tasklet(unsigned long unused) +{ + ssize_t ret; + struct shrm_dev *shrm = (struct shrm_dev *)unused; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + for (;;) { + ret = shrm_net_receive(shrm->ndev, ph_recv_buf); + if (ret == 0) { + dev_dbg(shrm->dev, "len is zero, queue empty\n"); + break; + } + if (ret < 0) { + dev_err(shrm->dev, "len < 0 !!! error!!!\n"); + break; + } + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static int shrm_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct shrm_dev *shrm = NULL; + + shrm = kzalloc(sizeof(struct shrm_dev), GFP_KERNEL); + if (shrm == NULL) { + dev_err(&pdev->dev, + "Could not allocate memory for struct shm_dev\n"); + return -ENOMEM; + } + + shrm->dev = &pdev->dev; + /* initialise the SHM */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(shrm->dev, + "Unable to map Ca Wake up interrupt\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_wake_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_common IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_0_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 2); + + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_1_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 3); + + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_common IRQbase\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_0_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 4); + + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_1_irq = res->start; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res) { + dev_err(shrm->dev, + "Could not get SHM IO memory information\n"); + err = -ENODEV; + goto rollback_intr; + } + shrm->intr_base = (void __iomem *)ioremap_nocache(res->start, + res->end - res->start + 1); + if (!(shrm->intr_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ape_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_COMMON_BASE; + shrm->ape_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_APE_COMMON_BASE, + SHM_FIFO_0_SIZE); + shrm->ape_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->ape_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_common_fifo_base; + } + shrm->cmt_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_COMMON_BASE; + shrm->cmt_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_CMT_COMMON_BASE, SHM_FIFO_0_SIZE); + shrm->cmt_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->cmt_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_common_fifo_base; + } + shrm->ape_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_AUDIO_BASE; + shrm->ape_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_APE_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->ape_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->ape_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_audio_fifo_base; + } + shrm->cmt_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_AUDIO_BASE; + shrm->cmt_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_CMT_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->cmt_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->cmt_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_audio_fifo_base; + } + shrm->ac_common_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ac_common_shared_wptr; + } + shrm->ac_common_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_common_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_common_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ac_audio_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ac_audio_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_audio_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + shrm->ca_audio_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + if (isa_init(shrm) != 0) { + dev_err(shrm->dev, "Driver Initialization Error\n"); + err = -EBUSY; + } + /* install handlers and tasklets */ + if (shm_initialise_irq(shrm)) { + dev_err(shrm->dev, + "shm error in interrupt registration\n"); + goto rollback_irq; + } +#ifdef CONFIG_HIGH_RES_TIMERS + hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer.function = callback; + hrtimer_start(&timer, ktime_set(0, 2*NSEC_PER_MSEC), HRTIMER_MODE_REL); +#endif + err = shrm_register_netdev(shrm); + if (err < 0) + goto rollback_irq; + + tasklet_init(&phonet_rcv_tasklet, do_phonet_rcv_tasklet, 0); + phonet_rcv_tasklet.data = (unsigned long)shrm; + + platform_set_drvdata(pdev, shrm); + + return err; +rollback_irq: + free_shm_irq(shrm); +rollback_map: + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); +rollback_ac_common_shared_wptr: + iounmap(shrm->cmt_audio_fifo_base); +rollback_cmt_audio_fifo_base: + iounmap(shrm->ape_audio_fifo_base); +rollback_ape_audio_fifo_base: + iounmap(shrm->cmt_common_fifo_base); +rollback_cmt_common_fifo_base: + iounmap(shrm->ape_common_fifo_base); +rollback_ape_common_fifo_base: + iounmap(shrm->intr_base); +rollback_intr: + kfree(shrm); + return err; +} + +static int __exit shrm_remove(struct platform_device *pdev) +{ + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + free_shm_irq(shrm); + iounmap(shrm->intr_base); + iounmap(shrm->ape_common_fifo_base); + iounmap(shrm->cmt_common_fifo_base); + iounmap(shrm->ape_audio_fifo_base); + iounmap(shrm->cmt_audio_fifo_base); + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); + shrm_unregister_netdev(shrm); + isa_exit(shrm); + kfree(shrm); + + return 0; +} + +#ifdef CONFIG_PM +/** + * u8500_shrm_suspend() - This routine puts the SHRM in to sustend state. + * @dev: pointer to device structure. + * + * This routine checks the current ongoing communication with Modem by + * examining the ca_wake state and prevents suspend if modem communication + * is on-going. + * If ca_wake = 1 (high), modem comm. is on-going; don't suspend + * If ca_wake = 0 (low), no comm. with modem on-going.Allow suspend + */ +int u8500_shrm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + int err; + + dev_dbg(&pdev->dev, "%s called...\n", __func__); + dev_dbg(&pdev->dev, "ca_wake_req_state = %x\n", + get_ca_wake_req_state()); + + /* if ca_wake_req is high, prevent system suspend */ + if (!get_ca_wake_req_state()) { + err = shrm_suspend_netdev(shrm->ndev); + return err; + } else + return -EBUSY; +} + +/** + * u8500_shrm_resume() - This routine resumes the SHRM from suspend state. + * @dev: pointer to device structure + * + * This routine restore back the current state of the SHRM + */ +int u8500_shrm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + int err; + + dev_dbg(&pdev->dev, "%s called...\n", __func__); + err = shrm_resume_netdev(shrm->ndev); + + return err; +} + +static const struct dev_pm_ops shrm_dev_pm_ops = { + .suspend_noirq = u8500_shrm_suspend, + .resume_noirq = u8500_shrm_resume, +}; +#endif + +static struct platform_driver shrm_driver = { + .remove = __exit_p(shrm_remove), + .driver = { + .name = "u8500_shrm", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &shrm_dev_pm_ops, +#endif + }, +}; + +static int __init shrm_driver_init(void) +{ + return platform_driver_probe(&shrm_driver, shrm_probe); +} + +static void __exit shrm_driver_exit(void) +{ + platform_driver_unregister(&shrm_driver); +} + +module_init(shrm_driver_init); +module_exit(shrm_driver_exit); + +MODULE_AUTHOR("Biju Das, Kumar Sanghvi, Arun Murthy"); +MODULE_DESCRIPTION("Shared Memory Modem Driver Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/shrm/shrm_driver.c b/drivers/misc/shrm/shrm_driver.c new file mode 100644 index 00000000000..6277794608a --- /dev/null +++ b/drivers/misc/shrm/shrm_driver.c @@ -0,0 +1,1439 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghvi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#ifdef CONFIG_HIGH_RES_TIMERS +#include +static struct hrtimer timer; +#endif + + +#define NAME "IPC_ISA" +#define ISA_DEVICES 4 +/**debug functionality*/ +#define ISA_DEBUG 0 + +#define ISI_MESSAGING (0) +#define RPC_MESSAGING (1) +#define AUDIO_MESSAGING (2) +#define SECURITY_MESSAGING (3) + +#define SIZE_OF_FIFO (512*1024) + +static u8 message_fifo[4][SIZE_OF_FIFO]; + +static u8 wr_isi_msg[10*1024]; +static u8 wr_rpc_msg[10*1024]; +static u8 wr_sec_msg[10*1024]; +static u8 wr_audio_msg[10*1024]; + +/* global data */ +/* + * int major:This variable is exported to user as module_param to specify + * major number at load time + */ +static int major; +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); +/* global fops mutex */ +static DEFINE_MUTEX(isa_lock); +rx_cb common_rx; +rx_cb audio_rx; + + +static int isi_receive(struct shrm_dev *shrm, void *data, u32 n_bytes); +static int rpc_receive(struct shrm_dev *shrm, void *data, u32 n_bytes); +static int audio_receive(struct shrm_dev *shrm, void *data, u32 n_bytes); +static int security_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes); + +static void rx_common_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + u8 *pdata; +#endif + dev_dbg(shrm->dev, "%s IN\n", __func__); + + switch (l2_header) { + case ISI_MESSAGING: + ret = isi_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "isi receive failed\n"); + break; + case RPC_MESSAGING: + ret = rpc_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "rpc receive failed\n"); + break; + case SECURITY_MESSAGING: + ret = security_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, + "security receive failed\n"); + break; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + case COMMMON_LOOPBACK_MESSAGING: + pdata = (u8 *)msg; + if ((*pdata == 0x50) || (*pdata == 0xAF)) { + ret = isi_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "isi receive failed\n"); + } else if ((*pdata == 0x0A) || (*pdata == 0xF5)) { + ret = rpc_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "rpc receive failed\n"); + } else if ((*pdata == 0xFF) || (*pdata == 0x00)) { + ret = security_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, + "security receive failed\n"); + } + break; +#endif + default: + break; + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static void rx_audio_l2msg_handler(u8 l2_header, + void *msg, u32 length, + struct shrm_dev *shrm) +{ + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + audio_receive(shrm, msg, length); + if (ret < 0) + dev_err(shrm->dev, "audio receive failed\n"); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static int __init shm_initialise_irq(struct shrm_dev *shrm) +{ + int err = 0; + + shrm_protocol_init(shrm, + rx_common_l2msg_handler, rx_audio_l2msg_handler); + + err = request_irq(shrm->ca_wake_irq, + ca_wake_irq_handler, IRQF_TRIGGER_RISING, + "ca_wake-up", shrm); + if (err < 0) { + dev_err(shrm->dev, + "Unable to allocate shm tx interrupt line\n"); + return err; + } + + err = request_irq(shrm->ac_read_notif_0_irq, + ac_read_notif_0_irq_handler, 0, + "ac_read_notif_0", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_0_irq interrupt line\n"); + goto irq_err1; + } + + err = request_irq(shrm->ac_read_notif_1_irq, + ac_read_notif_1_irq_handler, 0, + "ac_read_notif_1", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ac_read_notif_1_irq interrupt line\n"); + goto irq_err2; + } + + err = request_irq(shrm->ca_msg_pending_notif_0_irq, + ca_msg_pending_notif_0_irq_handler, 0, + "ca_msg_pending_notif_0", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_0_irq line\n"); + goto irq_err3; + } + + err = request_irq(shrm->ca_msg_pending_notif_1_irq, + ca_msg_pending_notif_1_irq_handler, 0, + "ca_msg_pending_notif_1", shrm); + if (err < 0) { + dev_err(shrm->dev, + "error ca_msg_pending_notif_1_irq interrupt line\n"); + goto irq_err4; + } + + return err; + +irq_err4: + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); +irq_err3: + free_irq(shrm->ac_read_notif_1_irq, shrm); +irq_err2: + free_irq(shrm->ac_read_notif_0_irq, shrm); +irq_err1: + free_irq(shrm->ca_wake_irq, shrm); + return err; +} + +static void free_shm_irq(struct shrm_dev *shrm) +{ + free_irq(shrm->ca_wake_irq, shrm); + free_irq(shrm->ac_read_notif_0_irq, shrm); + free_irq(shrm->ac_read_notif_1_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_0_irq, shrm); + free_irq(shrm->ca_msg_pending_notif_1_irq, shrm); +} + +/** + * create_queue() - To create FIFO for Tx and Rx message buffering. + * @q: message queue. + * @devicetype: device type 0-isi,1-rpc,2-audio,3-security. + * + * This function creates a FIFO buffer of n_bytes size using + * dma_alloc_coherent(). It also initializes all queue handling + * locks, queue management pointers. It also initializes message list + * which occupies this queue. + * + * It return -ENOMEM in case of no memory. + */ +static int create_queue(struct message_queue *q, u32 devicetype, + struct shrm_dev *shrm) +{ + q->fifo_base = (u8 *)&message_fifo[devicetype]; + q->size = SIZE_OF_FIFO; + q->readptr = 0; + q->writeptr = 0; + q->no = 0; + q->shrm = shrm; + spin_lock_init(&q->update_lock); + INIT_LIST_HEAD(&q->msg_list); + init_waitqueue_head(&q->wq_readable); + atomic_set(&q->q_rp, 0); + + return 0; +} +/** + * delete_queue() - To delete FIFO and assiciated memory. + * @q: message queue + * + * This function deletes FIFO created using create_queue() function. + * It resets queue management pointers. + */ +static void delete_queue(struct message_queue *q) +{ + q->size = 0; + q->readptr = 0; + q->writeptr = 0; +} + +/** + * add_msg_to_queue() - Add a message inside inside queue + * + * @q: message queue + * @size: size in bytes + * + * This function tries to allocate n_bytes of size in FIFO q. + * It returns negative number when no memory can be allocated + * currently. + */ +int add_msg_to_queue(struct message_queue *q, u32 size) +{ + struct queue_element *new_msg = NULL; + struct shrm_dev *shrm = q->shrm; + + dev_dbg(shrm->dev, "%s IN q->writeptr=%d\n", + __func__, q->writeptr); + new_msg = kmalloc(sizeof(struct queue_element), + GFP_KERNEL|GFP_ATOMIC); + + if (new_msg == NULL) { + dev_err(shrm->dev, "memory overflow inside while(1)\n"); + return -ENOMEM; + } + new_msg->offset = q->writeptr; + new_msg->size = size; + new_msg->no = q->no++; + + /* check for overflow condition */ + if (q->readptr <= q->writeptr) { + if (((q->writeptr-q->readptr) + size) >= q->size) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON(((q->writeptr-q->readptr) + size) >= q->size); + } + } else { + if ((q->writeptr + size) >= q->readptr) { + dev_err(shrm->dev, "Buffer overflow !!\n"); + BUG_ON((q->writeptr + size) >= q->readptr); + } + } + q->writeptr = (q->writeptr + size) % q->size; + if (list_empty(&q->msg_list)) { + list_add_tail(&new_msg->entry, &q->msg_list); + /* There can be 2 blocking calls read and another select */ + + atomic_set(&q->q_rp, 1); + wake_up_interruptible(&q->wq_readable); + } else + list_add_tail(&new_msg->entry, &q->msg_list); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * remove_msg_from_queue() - To remove a message from the msg queue. + * + * @q: message queue + * + * This function delets a message from the message list associated with message + * queue q and also updates read ptr. + * If the message list is empty, then, event is set to block the select and + * read calls of the paricular queue. + * + * The message list is FIFO style and message is always added to tail and + * removed from head. + */ + +int remove_msg_from_queue(struct message_queue *q) +{ + struct queue_element *old_msg = NULL; + struct shrm_dev *shrm = q->shrm; + struct list_head *msg; + + dev_dbg(shrm->dev, "%s IN q->readptr %d\n", + __func__, q->readptr); + + list_for_each(msg, &q->msg_list) { + old_msg = list_entry(msg, struct queue_element, entry); + if (old_msg == NULL) { + dev_err(shrm->dev, ":no message found\n"); + return -EFAULT; + } + break; + } + list_del(msg); + q->readptr = (q->readptr + old_msg->size) % q->size; + if (list_empty(&q->msg_list)) { + dev_dbg(shrm->dev, "List is empty setting RP= 0\n"); + atomic_set(&q->q_rp, 0); + } + kfree(old_msg); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; +} + +/** + * get_size_of_new_msg() - retrieve new message from message list + * + * @q: message queue + * + * This function will retrieve most recent message from the corresponding + * queue list. New message is always retrieved from head side. + * It returns new message no, offset if FIFO and size. + */ +int get_size_of_new_msg(struct message_queue *q) +{ + struct queue_element *new_msg = NULL; + struct list_head *msg_list; + struct shrm_dev *shrm = q->shrm; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + spin_lock_bh(&q->update_lock); + list_for_each(msg_list, &q->msg_list) { + new_msg = list_entry(msg_list, struct queue_element, entry); + if (new_msg == NULL) { + spin_unlock_bh(&q->update_lock); + dev_err(shrm->dev, "no message found\n"); + return -1; + } + break; + } + spin_unlock_bh(&q->update_lock); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return new_msg->size; +} + +/** + * isi_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes:message size + * + * This function is a callback to indicate ISI message reception is complete. + * It updates Writeptr of the Fifo + */ +static int isi_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *isidev = &shrm->isa_context->isadev[0]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &isidev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * rpc_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes:message size + * + * This function is a callback to indicate RPC message reception is complete. + * It updates Writeptr of the Fifo + */ +static int rpc_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *rpcdev = &shrm->isa_context->isadev[1]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &rpcdev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * audio_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes:message size + * + * This function is a callback to indicate audio message reception is complete. + * It updates Writeptr of the Fifo + */ +static int audio_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *audiodev = &shrm->isa_context->isadev[2]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &audiodev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * security_receive() - Rx Completion callback + * + * @data:message pointer + * @n_bytes: message size + * + * This function is a callback to indicate security message reception + * is complete.It updates Writeptr of the Fifo + */ +static int security_receive(struct shrm_dev *shrm, + void *data, u32 n_bytes) +{ + u32 size = 0; + int ret = 0; + u8 *psrc; + struct message_queue *q; + struct isadev_context *secdev = &shrm->isa_context->isadev[3]; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + q = &secdev->dl_queue; + spin_lock(&q->update_lock); + /* Memcopy RX data first */ + if ((q->writeptr+n_bytes) >= q->size) { + psrc = (u8 *)data; + size = (q->size-q->writeptr); + /* Copy First Part of msg */ + memcpy((q->fifo_base+q->writeptr), psrc, size); + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + memcpy(q->fifo_base, psrc, (n_bytes-size)); + } else { + memcpy((q->fifo_base+q->writeptr), data, n_bytes); + } + ret = add_msg_to_queue(q, n_bytes); + if (ret < 0) + dev_err(shrm->dev, "Adding msg to message queue failed\n"); + spin_unlock(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + + +/** + * isa_select() - Select Interface + * + * @filp:file descriptor pointer + * @wait:poll_table_struct pointer + * + * This function is used to perform non-blocking read operations. It allows + * a process to determine whether it can read from one or more open files + * without blocking. These calls can also block a process until any of a + * given set of file descriptors becomes available for reading. + * If a file is ready to read, POLLIN | POLLRDNORM bitmask is returned. + * The driver method is called whenever the user-space program performs a select + * system call involving a file descriptor associated with the driver. + */ +static u32 isa_select(struct file *filp, + struct poll_table_struct *wait) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + u32 mask = 0; + u32 m = iminor(filp->f_path.dentry->d_inode); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (isadev->device_id != m) + return -1; + q = &isadev->dl_queue; + poll_wait(filp, &q->wq_readable, wait); + if (atomic_read(&q->q_rp) == 1) + mask = POLLIN | POLLRDNORM; + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return mask; +} + +/** + * isa_read() - Read from device + * + * @filp:file descriptor + * @buf:user buffer pointer + * @len:size of requested data transfer + * @ppos:not used + * + * This function is called whenever user calls read() system call. + * It reads a oldest message from queue and copies it into user buffer and + * returns its size. + * If there is no message present in queue, then it blocks until new data is + * available. + */ +ssize_t isa_read(struct file *filp, char __user *buf, + size_t len, loff_t *ppos) +{ + struct isadev_context *isadev = (struct isadev_context *) + filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + char *psrc; + u32 msgsize; + u32 size = 0; + int ret = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (len <= 0) + return -EFAULT; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + if (wait_event_interruptible(q->wq_readable, + atomic_read(&q->q_rp) == 1)) { + return -ERESTARTSYS; + } + } else + spin_unlock_bh(&q->update_lock); + + msgsize = get_size_of_new_msg(q); + if ((q->readptr+msgsize) >= q->size) { + dev_dbg(shrm->dev, "Inside Loop Back\n"); + psrc = (char *)buf; + size = (q->size-q->readptr); + /* Copy First Part of msg */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base+q->readptr), + size)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + psrc += size; + /* Copy Second Part of msg at the top of fifo */ + if (copy_to_user(psrc, + (u8 *)(q->fifo_base), + (msgsize-size))) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } else { + if (copy_to_user(buf, + (u8 *)(q->fifo_base+q->readptr), + msgsize)) { + dev_err(shrm->dev, "copy_to_user failed\n"); + return -EFAULT; + } + } + + spin_lock_bh(&q->update_lock); + ret = remove_msg_from_queue(q); + if (ret < 0) { + dev_err(shrm->dev, + "Removing msg from message queue failed\n"); + msgsize = ret; + } + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return msgsize; +} +/** + * isa_write() - Write to device + * + * @filp:file descriptor + * @buf:user buffer pointer + * @len:size of requested data transfer + * @ppos:not used + * + * This function is called whenever user calls write() system call. + * It checks if there is space available in queue, and copies the message + * inside queue. If there is no space, it blocks until space becomes available. + * It also schedules transfer thread to transmit the newly added message. + */ +static ssize_t isa_write(struct file *filp, const char __user *buf, + size_t len, loff_t *ppos) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct message_queue *q; + int err, ret; + void *addr = 0; + u8 l2_header = 0; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + if (len <= 0) + return -EFAULT; + q = &isadev->dl_queue; + + switch (isadev->device_id) { + case ISI_MESSAGING: + dev_dbg(shrm->dev, "ISI\n"); + addr = (void *)wr_isi_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + dev_dbg(shrm->dev, "Loopback\n"); + l2_header = COMMON_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + break; + case RPC_MESSAGING: + dev_dbg(shrm->dev, "RPC\n"); + addr = (void *)wr_rpc_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + l2_header = COMMON_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + break; + case AUDIO_MESSAGING: + dev_dbg(shrm->dev, "Audio\n"); + addr = (void *)wr_audio_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + l2_header = AUDIO_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + + break; + case SECURITY_MESSAGING: + dev_dbg(shrm->dev, "Security\n"); + addr = (void *)wr_sec_msg; +#ifdef CONFIG_U8500_SHRM_LOOP_BACK + l2_header = COMMON_LOOPBACK_MESSAGING; +#else + l2_header = isadev->device_id; +#endif + break; + default: + dev_dbg(shrm->dev, "Wrong device\n"); + return -EFAULT; + } + + if (copy_from_user(addr, buf, len)) { + dev_err(shrm->dev, "copy_from_user failed\n"); + return -EFAULT; + } + + /* Write msg to Fifo */ + if (isadev->device_id == 2) { + mutex_lock(&shrm->isa_context->tx_audio_mutex); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + mutex_unlock(&shrm->isa_context->tx_audio_mutex); + } else { + spin_lock_bh(&shrm->isa_context->common_tx); + err = shm_write_msg(shrm, l2_header, addr, len); + if (!err) + ret = len; + else + ret = err; + spin_unlock_bh(&shrm->isa_context->common_tx); + } + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return ret; +} + +/** + * isa_ioctl() - To handle different ioctl commands supported by driver. + * + * @inode: structure is used by the kernel internally to represent files + * @filp:file descriptor pointer + * @cmd:ioctl command + * @arg:input param + * + * Following ioctls are supported by this driver. + * DLP_IOCTL_ALLOCATE_BUFFER - To allocate buffer for new uplink message. + * This ioctl is called with required message size. It returns offset for + * the allocates space in the queue. DLP_IOCTL_PUT_MESSAGE - To indicate + * new uplink message available in queuq for transmission. Message is copied + * from offset location returned by previous ioctl before calling this ioctl. + * DLP_IOCTL_GET_MESSAGE - To check if any downlink message is available in + * queue. It returns offset for new message inside queue. + * DLP_IOCTL_DEALLOCATE_BUFFER - To deallocate any buffer allocate for + * downlink message once the message is copied. Message is copied from offset + * location returned by previous ioctl before calling this ioctl. + */ +static int isa_ioctl(struct inode *inode, struct file *filp, + unsigned cmd, unsigned long arg) +{ + int err = 0; + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + u32 m = iminor(inode); + + if (isadev->device_id != m) + return -1; + + switch (cmd) { + case DLP_IOC_ALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_ALLOCATE_BUFFER\n"); + break; + case DLP_IOC_PUT_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_PUT_MESSAGE\n"); + break; + case DLP_IOC_GET_MESSAGE: + dev_dbg(shrm->dev, "DLP_IOC_GET_MESSAGE\n"); + break; + case DLP_IOC_DEALLOCATE_BUFFER: + dev_dbg(shrm->dev, "DLP_IOC_DEALLOCATE_BUFFER\n"); + break; + default: + dev_dbg(shrm->dev, "Unknown IOCTL\n"); + err = -1; + break; + } + return err; +} +/** + * isa_mmap() - Maps kernel queue memory to user space. + * + * @filp:file descriptor pointer + * @vma:virtual area memory structure. + * + * This function maps kernel FIFO into user space. This function + * shall be called twice to map both uplink and downlink buffers. + */ +static int isa_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + + u32 m = iminor(filp->f_path.dentry->d_inode); + dev_dbg(shrm->dev, "%s %dIN\n", __func__, m); + + isadev = (struct isadev_context *)filp->private_data; + return 0; +} + +/** + * isa_close() - Close device file + * + * @inode:structure is used by the kernel internally to represent files + * @filp:device file descriptor + * + * This function deletes structues associated with this file, deletes + * queues, flushes and destroys workqueus and closes this file. + * It also unregisters itself from l2mux driver. + */ +static int isa_close(struct inode *inode, struct file *filp) +{ + struct isadev_context *isadev = filp->private_data; + struct shrm_dev *shrm = isadev->dl_queue.shrm; + struct isa_driver_context *isa_context = shrm->isa_context; + u8 m; + + mutex_lock(&isa_lock); + m = iminor(filp->f_path.dentry->d_inode); + dev_dbg(shrm->dev, "%s IN %d", __func__, m); + + if (atomic_dec_and_test(&isa_context->is_open[m])) { + atomic_inc(&isa_context->is_open[m]); + dev_err(shrm->dev, "Device not opened yet\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + atomic_set(&isa_context->is_open[m], 1); + + dev_dbg(shrm->dev, "isadev->device_id %d", isadev->device_id); + dev_dbg(shrm->dev, "Closed %d device\n", m); + + if (m == ISI_MESSAGING) + dev_dbg(shrm->dev, "Closed ISI_MESSAGING Device\n"); + else if (m == RPC_MESSAGING) + dev_dbg(shrm->dev, "Closed RPC_MESSAGING Device\n"); + else if (m == AUDIO_MESSAGING) + dev_dbg(shrm->dev, "Closed AUDIO_MESSAGING Device\n"); + else if (m == SECURITY_MESSAGING) + dev_dbg(shrm->dev, "Closed SECURITY_MESSAGING Device\n"); + else + dev_dbg(shrm->dev, NAME ":No such device present\n"); + + mutex_unlock(&isa_lock); + return 0; +} +/** + * isa_open() - Open device file + * + * @inode: structure is used by the kernel internally to represent files + * @filp: device file descriptor + * + * This function performs initialization tasks needed to open SHM channel. + * Following tasks are performed. + * -return if device is already opened + * -create uplink FIFO + * -create downlink FIFO + * -init delayed workqueue thread + * -register to l2mux driver + */ +static int isa_open(struct inode *inode, struct file *filp) +{ + int err = 0; + u8 m; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = container_of( + inode->i_cdev, + struct isa_driver_context, + cdev); + struct shrm_dev *shrm = isa_context->isadev->dl_queue.shrm; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_boot_state() != BOOT_DONE) { + dev_err(shrm->dev, "Boot is not done\n"); + return -EBUSY; + } + mutex_lock(&isa_lock); + m = iminor(inode); + + if ((m != ISI_MESSAGING) && (m != RPC_MESSAGING) && + (m != AUDIO_MESSAGING) && (m != SECURITY_MESSAGING)) { + dev_err(shrm->dev, "No such device present\n"); + mutex_unlock(&isa_lock); + return -ENODEV; + } + if (!atomic_dec_and_test(&isa_context->is_open[m])) { + atomic_inc(&isa_context->is_open[m]); + dev_err(shrm->dev, "Device already opened\n"); + mutex_unlock(&isa_lock); + return -EBUSY; + } + + if (m == ISI_MESSAGING) + dev_dbg(shrm->dev, "Open ISI_MESSAGING Device\n"); + else if (m == RPC_MESSAGING) + dev_dbg(shrm->dev, "Open RPC_MESSAGING Device\n"); + else if (m == AUDIO_MESSAGING) + dev_dbg(shrm->dev, "Open AUDIO_MESSAGING Device\n"); + else if (m == SECURITY_MESSAGING) + dev_dbg(shrm->dev, "Open SECURITY_MESSAGING Device\n"); + else + dev_dbg(shrm->dev, ":No such device present\n"); + + isadev = &isa_context->isadev[m]; + if (filp != NULL) + filp->private_data = isadev; + + mutex_unlock(&isa_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return err; +} + +const struct file_operations isa_fops = { + .owner = THIS_MODULE, + .open = isa_open, + .release = isa_close, + .ioctl = isa_ioctl, + .mmap = isa_mmap, + .read = isa_read, + .write = isa_write, + .poll = isa_select, +}; + +/** + * isa_init() - module insertion function + * + * This function registers module as a character driver using + * register_chrdev_region() or alloc_chrdev_region. It adds this + * driver to system using cdev_add() call. Major number is dynamically + * allocated using alloc_chrdev_region() by default or left to user to specify + * it during load time. For this variable major is used as module_param + * Nodes to be created using + * mknod /dev/isi c $major 0 + * mknod /dev/rpc c $major 1 + * mknod /dev/audio c $major 2 + * mknod /dev/sec c $major 3 + */ +int isa_init(struct shrm_dev *shrm) +{ + dev_t dev_id; + int retval, no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context; + + isa_context = kzalloc(sizeof(struct isa_driver_context), + GFP_KERNEL); + shrm->isa_context = isa_context; + if (isa_context == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + + if (major) { + dev_id = MKDEV(major, 0); + retval = register_chrdev_region(dev_id, ISA_DEVICES, NAME); + } else { + retval = alloc_chrdev_region(&dev_id, 0, ISA_DEVICES, NAME); + major = MAJOR(dev_id); + } + + dev_dbg(shrm->dev, "major %d\n", major); + + cdev_init(&isa_context->cdev, &isa_fops); + isa_context->cdev.owner = THIS_MODULE; + retval = cdev_add(&isa_context->cdev, dev_id, ISA_DEVICES); + if (retval) { + dev_err(shrm->dev, "Failed to add char device\n"); + return retval; + } + + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) + atomic_set(&isa_context->is_open[no_dev], 1); + + isa_context->isadev = kzalloc(sizeof + (struct isadev_context)*ISA_DEVICES, + GFP_KERNEL); + if (isa_context->isadev == NULL) { + dev_err(shrm->dev, "Failed to alloc memory\n"); + return -ENOMEM; + } + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + isadev->device_id = no_dev; + retval = create_queue(&isadev->dl_queue, + isadev->device_id, shrm); + if (retval < 0) { + dev_err(shrm->dev, "create dl_queue failed\n"); + delete_queue(&isadev->dl_queue); + kfree(isadev); + return retval; + } + } + mutex_init(&isa_context->tx_audio_mutex); + spin_lock_init(&isa_context->common_tx); + + dev_err(shrm->dev, "SHRM char driver added\n"); + + return retval; +} + +void isa_exit(struct shrm_dev *shrm) +{ + int no_dev; + struct isadev_context *isadev; + struct isa_driver_context *isa_context = shrm->isa_context; + dev_t dev_id = MKDEV(major, 0); + + for (no_dev = 0; no_dev < ISA_DEVICES; no_dev++) { + isadev = &isa_context->isadev[no_dev]; + delete_queue(&isadev->dl_queue); + kfree(isadev); + } + + cdev_del(&isa_context->cdev); + unregister_chrdev_region(dev_id, ISA_DEVICES); + kfree(isa_context); + + dev_err(shrm->dev, "SHRM char driver removed\n"); +} + +#ifdef CONFIG_HIGH_RES_TIMERS +static enum hrtimer_restart callback(struct hrtimer *timer) +{ + return HRTIMER_NORESTART; +} +#endif + + +static int __init shrm_probe(struct platform_device *pdev) +{ + int err = 0; + struct resource *res; + struct shrm_dev *shrm = NULL; + + if (pdev == NULL) { + dev_err(shrm->dev, + "No device/platform_data found on shm device\n"); + return -ENODEV; + } + + + shrm = kzalloc(sizeof(struct shrm_dev), GFP_KERNEL); + if (shrm == NULL) { + dev_err(shrm->dev, + "Could not allocate memory for struct shm_dev\n"); + return -ENOMEM; + } + shrm->dev = &pdev->dev; + + /* initialise the SHM */ + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(shrm->dev, "Unable to map Ca Wake up interrupt\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_wake_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 1); + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_common IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_0_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 2); + if (!res) { + dev_err(shrm->dev, + "Unable to map APE_Read_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ac_read_notif_1_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 3); + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_common IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_0_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 4); + if (!res) { + dev_err(shrm->dev, + "Unable to map Cmt_msg_pending_notif_audio IRQ base\n"); + err = -EBUSY; + goto rollback_intr; + } + shrm->ca_msg_pending_notif_1_irq = res->start; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(shrm->dev, + "Could not get SHM IO memory information\n"); + err = -ENODEV; + goto rollback_intr; + } + + shrm->intr_base = (void __iomem *)ioremap_nocache(res->start, + res->end - res->start + 1); + + if (!(shrm->intr_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_intr; + } + + shrm->ape_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_COMMON_BASE; + shrm->ape_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_APE_COMMON_BASE, + SHM_FIFO_0_SIZE); + shrm->ape_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->ape_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_common_fifo_base; + } + + shrm->cmt_common_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_COMMON_BASE; + + shrm->cmt_common_fifo_base = + (void __iomem *)ioremap_nocache( + U8500_SHM_FIFO_CMT_COMMON_BASE, SHM_FIFO_0_SIZE); + shrm->cmt_common_fifo_size = (SHM_FIFO_0_SIZE)/4; + + if (!(shrm->cmt_common_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_common_fifo_base; + } + + shrm->ape_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_APE_AUDIO_BASE; + shrm->ape_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_APE_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->ape_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->ape_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ape_audio_fifo_base; + } + + shrm->cmt_audio_fifo_base_phy = + (u32 *)U8500_SHM_FIFO_CMT_AUDIO_BASE; + shrm->cmt_audio_fifo_base = + (void __iomem *)ioremap_nocache(U8500_SHM_FIFO_CMT_AUDIO_BASE, + SHM_FIFO_1_SIZE); + shrm->cmt_audio_fifo_size = (SHM_FIFO_1_SIZE)/4; + + if (!(shrm->cmt_audio_fifo_base)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_cmt_audio_fifo_base; + } + + shrm->ac_common_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_ac_common_shared_wptr; + } + + shrm->ac_common_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ca_common_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + shrm->ca_common_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_0_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_common_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ac_audio_shared_wptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ac_audio_shared_rptr = + (void __iomem *)ioremap(SHM_ACFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ac_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ca_audio_shared_wptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_WRITE_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_wptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + shrm->ca_audio_shared_rptr = + (void __iomem *)ioremap(SHM_CAFIFO_1_READ_AMCU, SHM_PTR_SIZE); + + if (!(shrm->ca_audio_shared_rptr)) { + dev_err(shrm->dev, "Unable to map register base\n"); + err = -EBUSY; + goto rollback_map; + } + + + if (isa_init(shrm) != 0) { + dev_err(shrm->dev, "Driver Initialization Error\n"); + err = -EBUSY; + } + /* install handlers and tasklets */ + if (shm_initialise_irq(shrm)) { + dev_err(shrm->dev, "shm error in interrupt registration\n"); + goto rollback_irq; + } + +#ifdef CONFIG_HIGH_RES_TIMERS + hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer.function = callback; + + hrtimer_start(&timer, ktime_set(0, 2*NSEC_PER_MSEC), HRTIMER_MODE_REL); +#endif + + return err; + +rollback_irq: + free_shm_irq(shrm); +rollback_map: + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); +rollback_ac_common_shared_wptr: + iounmap(shrm->cmt_audio_fifo_base); +rollback_cmt_audio_fifo_base: + iounmap(shrm->ape_audio_fifo_base); +rollback_ape_audio_fifo_base: + iounmap(shrm->cmt_common_fifo_base); +rollback_cmt_common_fifo_base: + iounmap(shrm->ape_common_fifo_base); +rollback_ape_common_fifo_base: + iounmap(shrm->intr_base); +rollback_intr: + kfree(shrm); + return err; +} + +static int __exit shrm_remove(struct platform_device *pdev) +{ + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + free_shm_irq(shrm); + iounmap(shrm->intr_base); + iounmap(shrm->ape_common_fifo_base); + iounmap(shrm->cmt_common_fifo_base); + iounmap(shrm->ape_audio_fifo_base); + iounmap(shrm->cmt_audio_fifo_base); + iounmap(shrm->ac_common_shared_wptr); + iounmap(shrm->ac_common_shared_rptr); + iounmap(shrm->ca_common_shared_wptr); + iounmap(shrm->ca_common_shared_rptr); + iounmap(shrm->ac_audio_shared_wptr); + iounmap(shrm->ac_audio_shared_rptr); + iounmap(shrm->ca_audio_shared_wptr); + iounmap(shrm->ca_audio_shared_rptr); + kfree(shrm); + isa_exit(shrm); + + return 0; +} +#ifdef CONFIG_PM + +/** + * u8500_shrm_suspend() - This routine puts the SHRM in to sustend state. + * @pdev: platform device. + * + * This routine checks the current ongoing communication with Modem by + * examining the ca_wake state and prevents suspend if modem communication + * is on-going. + * If ca_wake = 1 (high), modem comm. is on-going; don't suspend + * If ca_wake = 0 (low), no comm. with modem on-going.Allow suspend + */ +int u8500_shrm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + dev_dbg(shrm->dev, "%s called...\n", __func__); + dev_dbg(shrm->dev, "\n ca_wake_req_state = %x\n", + get_ca_wake_req_state()); + /* if ca_wake_req is high, prevent system suspend */ + if (get_ca_wake_req_state()) + return -EBUSY; + else + return 0; +} + +/** + * u8500_shrm_resume() - This routine resumes the SHRM from sustend state. + * @pdev: platform device. + * + * This routine restore back the current state of the SHRM + */ +int u8500_shrm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct shrm_dev *shrm = platform_get_drvdata(pdev); + + dev_dbg(shrm->dev, "%s called...\n", __func__); + /* TODO: + * As of now, no state save takes place in suspend. + * So, nothing to restore in resume. + * Simply return as of now. + * State saved in suspend should be restored here. + */ + + return 0; +} + +static const struct dev_pm_ops shrm_dev_pm_ops = { + .suspend = u8500_shrm_suspend, + .resume = u8500_shrm_resume, +}; +#endif + +static struct platform_driver shrm_driver = { + .remove = __exit_p(shrm_remove), + .driver = { + .name = "u8500_shrm", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &shrm_dev_pm_ops, +#endif + }, +}; + +static int __init shrm_driver_init(void) +{ + return platform_driver_probe(&shrm_driver, shrm_probe); +} + +static void __exit shrm_driver_exit(void) +{ + platform_driver_unregister(&shrm_driver); +} + +module_init(shrm_driver_init); +module_exit(shrm_driver_exit); + +MODULE_AUTHOR("Biju Das"); +MODULE_DESCRIPTION("Shared Memory Modem Driver Interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/shrm/shrm_fifo.c b/drivers/misc/shrm/shrm_fifo.c new file mode 100644 index 00000000000..ecf4b7e4466 --- /dev/null +++ b/drivers/misc/shrm/shrm_fifo.c @@ -0,0 +1,829 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghavi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include + +#define L1_BOOT_INFO_REQ 1 +#define L1_BOOT_INFO_RESP 2 +#define L1_NORMAL_MSG 3 +#define L1_HEADER_MASK 28 +#define L1_MAPID_MASK 0xF0000000 +#define CONFIG_OFFSET 8 +#define COUNTER_OFFSET 20 +#define L2_HEADER_SIZE 4 +#define L2_HEADER_OFFSET 24 +#define MASK_0_15_BIT 0xFF +#define MASK_16_31_BIT 0xFF00 +#define MASK_16_27_BIT 0xFFF0000 +#define MASK_0_39_BIT 0xFFFFF +#define MASK_40_55_BIT 0xFF00000 +#define MASK_8_16_BIT 0x0000FF00 +#define MSG_LEN_OFFSET 16 +#define SHRM_VER 2 +#define ca_ist_inactivity_timer 100 /*100ms */ +#define ca_csc_inactivity_timer 100 /*100ms */ + +static u8 msg_audio_counter; +static u8 msg_common_counter; + +struct fifo_write_params ape_shm_fifo_0; +struct fifo_write_params ape_shm_fifo_1; +struct fifo_read_params cmt_shm_fifo_0; +struct fifo_read_params cmt_shm_fifo_1; + + +static u8 cmt_read_notif_0_send; +static u8 cmt_read_notif_1_send; + +void shm_fifo_init(struct shrm_dev *shrm) +{ + ape_shm_fifo_0.writer_local_wptr = 0; + ape_shm_fifo_0.writer_local_rptr = 0; + *((u32 *)shrm->ac_common_shared_wptr) = 0; + *((u32 *)shrm->ac_common_shared_rptr) = 0; + ape_shm_fifo_0.shared_wptr = 0; + ape_shm_fifo_0.shared_rptr = 0; + ape_shm_fifo_0.availablesize = shrm->ape_common_fifo_size; + ape_shm_fifo_0.end_addr_fifo = shrm->ape_common_fifo_size; + ape_shm_fifo_0.fifo_virtual_addr = shrm->ape_common_fifo_base; + spin_lock_init(&ape_shm_fifo_0.fifo_update_lock); + + + cmt_shm_fifo_0.reader_local_rptr = 0; + cmt_shm_fifo_0.reader_local_wptr = 0; + cmt_shm_fifo_0.shared_wptr = + *((u32 *)shrm->ca_common_shared_wptr); + cmt_shm_fifo_0.shared_rptr = + *((u32 *)shrm->ca_common_shared_rptr); + cmt_shm_fifo_0.availablesize = shrm->cmt_common_fifo_size; + cmt_shm_fifo_0.end_addr_fifo = shrm->cmt_common_fifo_size; + cmt_shm_fifo_0.fifo_virtual_addr = shrm->cmt_common_fifo_base; + + ape_shm_fifo_1.writer_local_wptr = 0; + ape_shm_fifo_1.writer_local_rptr = 0; + ape_shm_fifo_1.shared_wptr = 0; + ape_shm_fifo_1.shared_rptr = 0; + *((u32 *)shrm->ac_audio_shared_wptr) = 0; + *((u32 *)shrm->ac_audio_shared_rptr) = 0; + ape_shm_fifo_1.availablesize = shrm->ape_audio_fifo_size; + ape_shm_fifo_1.end_addr_fifo = shrm->ape_audio_fifo_size; + ape_shm_fifo_1.fifo_virtual_addr = shrm->ape_audio_fifo_base; + spin_lock_init(&ape_shm_fifo_1.fifo_update_lock); + + cmt_shm_fifo_1.reader_local_rptr = 0; + cmt_shm_fifo_1.reader_local_wptr = 0; + cmt_shm_fifo_1.shared_wptr = + *((u32 *)shrm->ca_audio_shared_wptr); + cmt_shm_fifo_1.shared_rptr = + *((u32 *)shrm->ca_audio_shared_rptr); + cmt_shm_fifo_1.availablesize = shrm->cmt_audio_fifo_size; + cmt_shm_fifo_1.end_addr_fifo = shrm->cmt_audio_fifo_size; + cmt_shm_fifo_1.fifo_virtual_addr = shrm->cmt_audio_fifo_base; + msg_audio_counter = 0; + msg_common_counter = 0; +} + +u8 read_boot_info_req(struct shrm_dev *shrm, + u32 *config, + u32 *version) +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + u32 *msg; + u32 header = 0; + u8 msgtype; + + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->reader_local_rptr + fifo->fifo_virtual_addr); + header = *msg; + msgtype = (header & L1_MAPID_MASK) >> L1_MSG_MAPID_OFFSET; + if (msgtype != L1_BOOT_INFO_REQ) { + dev_err(shrm->dev, "Read_Boot_Info_Req Fatal ERROR\n"); + BUG(); + } + *config = (header >> CONFIG_OFFSET) & MASK_0_15_BIT; + *version = header & MASK_0_15_BIT; + fifo->reader_local_rptr += 1; + + return 1; +} + +void write_boot_info_resp(struct shrm_dev *shrm, u32 config, + u32 version) +{ + struct fifo_write_params *fifo = &ape_shm_fifo_0; + u32 *msg; + u8 msg_length; + version = SHRM_VER; + + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->writer_local_wptr+fifo->fifo_virtual_addr); + if (version < 1) { + *msg = ((L1_BOOT_INFO_RESP << L1_MSG_MAPID_OFFSET) | + ((config << CONFIG_OFFSET) & MASK_16_31_BIT) + | (version & MASK_0_15_BIT)); + msg_length = 1; + } else { + *msg = ((L1_BOOT_INFO_RESP << L1_MSG_MAPID_OFFSET) | + ((0x8 << MSG_LEN_OFFSET) & MASK_16_27_BIT) | + ((config << CONFIG_OFFSET) & MASK_8_16_BIT)| + version); + msg++; + *msg = ca_ist_inactivity_timer; + msg++; + *msg = ca_csc_inactivity_timer; + msg_length = L1_NORMAL_MSG; + } + spin_lock(&fifo->fifo_update_lock); + fifo->writer_local_wptr += msg_length; + fifo->availablesize -= msg_length; + spin_unlock(&fifo->fifo_update_lock); +} + +/** + * shm_write_msg_to_fifo() - write message to FIFO + * @shrm: pointer to shrm device information structure + * @channel: audio or common channel + * @l2header: L2 header or device ID + * @addr: pointer to write buffer address + * @length: length of mst to write + * + * Function Which Writes the data into Fifo in IPC zone + * It is called from shm_write_msg. This function will copy the msg + * from the kernel buffer to FIFO. There are 4 kernel buffers from where + * the data is to copied to FIFO one for each of the messages ISI, RPC, + * AUDIO and SECURITY. ISI, RPC and SECURITY messages are pushed to FIFO + * in commmon channel and AUDIO message is pushed onto audio channel FIFO. + */ +int shm_write_msg_to_fifo(struct shrm_dev *shrm, u8 channel, + u8 l2header, void *addr, u32 length) +{ + struct fifo_write_params *fifo = NULL; + u32 l1_header = 0, l2_header = 0; + u32 requiredsize; + u32 size = 0; + u32 *msg; + u8 *src; + + if (channel == COMMON_CHANNEL) + fifo = &ape_shm_fifo_0; + else if (channel == AUDIO_CHANNEL) + fifo = &ape_shm_fifo_1; + else { + dev_err(shrm->dev, "invalid channel\n"); + return -EINVAL; + } + + /* L2 size in 32b */ + requiredsize = ((length + 3) / 4); + /* Add size of L1 & L2 header */ + requiredsize += 2; + + /* if availablesize = or < requiredsize then error */ + if (fifo->availablesize <= requiredsize) { + /* Fatal ERROR - should never happens */ + dev_dbg(shrm->dev, "wr_wptr= %x\n", + fifo->writer_local_wptr); + dev_dbg(shrm->dev, "wr_rptr= %x\n", + fifo->writer_local_rptr); + dev_dbg(shrm->dev, "shared_wptr= %x\n", + fifo->shared_wptr); + dev_dbg(shrm->dev, "shared_rptr= %x\n", + fifo->shared_rptr); + dev_dbg(shrm->dev, "availsize= %x\n", + fifo->availablesize); + dev_dbg(shrm->dev, "end__fifo= %x\n", + fifo->end_addr_fifo); + dev_warn(shrm->dev, "Modem is busy, please wait." + " c_cnt = %d; a_cnt = %d\n", msg_common_counter, + msg_audio_counter); + if (channel == COMMON_CHANNEL) { + dev_warn(shrm->dev, + "Modem is lagging behind in reading." + "Stopping n/w dev queue\n"); + shrm_stop_netdev(shrm->ndev); + } + + return -EAGAIN; + } + + if (channel == COMMON_CHANNEL) { + /* build L1 header */ + l1_header = ((L1_NORMAL_MSG << L1_MSG_MAPID_OFFSET) | + (((msg_common_counter++) << COUNTER_OFFSET) + & MASK_40_55_BIT) | + ((length + L2_HEADER_SIZE) & MASK_0_39_BIT)); + } else if (channel == AUDIO_CHANNEL) { + /* build L1 header */ + l1_header = ((L1_NORMAL_MSG << L1_MSG_MAPID_OFFSET) | + (((msg_audio_counter++) << COUNTER_OFFSET) + & MASK_40_55_BIT) | + ((length + L2_HEADER_SIZE) & MASK_0_39_BIT)); + } + + /* + * Need to take care race condition for fifo->availablesize + * & fifo->writer_local_rptr with Ac_Read_notification interrupt. + * One option could be use stack variable for LocalRptr and recompute + * fifo->availablesize,based on flag enabled in the + * Ac_read_notification + */ + l2_header = ((l2header << L2_HEADER_OFFSET) | + ((length) & MASK_0_39_BIT)); + /* Check Local Rptr is less than or equal to Local WPtr */ + if (fifo->writer_local_rptr <= fifo->writer_local_wptr) { + msg = (u32 *) + (fifo->fifo_virtual_addr+fifo->writer_local_wptr); + + /* check enough place bewteen writer_local_wptr & end of FIFO */ + if ((fifo->end_addr_fifo-fifo->writer_local_wptr) >= + requiredsize) { + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + + /* copy the l2 message in 1 memcpy */ + memcpy((void *)msg, addr, length); + /* UpdateWptr */ + spin_lock_bh(&fifo->fifo_update_lock); + fifo->writer_local_wptr += requiredsize; + fifo->availablesize -= requiredsize; + fifo->writer_local_wptr %= fifo->end_addr_fifo; + spin_unlock_bh(&fifo->fifo_update_lock); + } else { + /* + * message is split between and of FIFO and beg of FIFO + * copy first part from writer_local_wptr to end of FIFO + */ + size = fifo->end_addr_fifo-fifo->writer_local_wptr; + + if (size == 1) { + /* Add L1 header */ + *msg = l1_header; + msg++; + spin_lock_bh(&fifo->fifo_update_lock); + /* UpdateWptr */ + fifo->writer_local_wptr = 0; + fifo->availablesize -= size; + /* + * copy second part from beg of FIFO + * with remaining part of msg + */ + msg = (u32 *) + fifo->fifo_virtual_addr; + *msg = l2_header; + msg++; + + /* copy the l3 message in 1 memcpy */ + memcpy((void *)msg, addr, length); + /* UpdateWptr */ + fifo->writer_local_wptr += + requiredsize-size; + fifo->availablesize -= + (requiredsize-size); + spin_unlock_bh(&fifo->fifo_update_lock); + } else if (size == 2) { + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + + spin_lock_bh(&fifo->fifo_update_lock); + /* UpdateWptr */ + fifo->writer_local_wptr = 0; + fifo->availablesize -= size; + + /* + * copy second part from beg of FIFO + * with remaining part of msg + */ + msg = (u32 *) + fifo->fifo_virtual_addr; + /* copy the l3 message in 1 memcpy */ + memcpy((void *)msg, addr, length); + + /* UpdateWptr */ + fifo->writer_local_wptr += + requiredsize-size; + fifo->availablesize -= + (requiredsize-size); + spin_unlock_bh(&fifo->fifo_update_lock); + } else { + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + + /* copy the l2 message in 1 memcpy */ + memcpy((void *)msg, addr, (size-2)*4); + + spin_lock_bh(&fifo->fifo_update_lock); + + /* UpdateWptr */ + fifo->writer_local_wptr = 0; + fifo->availablesize -= size; + + /* + * copy second part from beg of FIFO + * with remaining part of msg + */ + msg = (u32 *)fifo->fifo_virtual_addr; + src = (u8 *)addr+((size - 2) * 4); + memcpy((void *)msg, src, + (length-((size - 2) * 4))); + + /* UpdateWptr */ + fifo->writer_local_wptr += + requiredsize-size; + fifo->availablesize -= + (requiredsize-size); + spin_unlock_bh(&fifo->fifo_update_lock); + } + + } + } else { + /* writer_local_rptr > writer_local_wptr */ + msg = (u32 *) + (fifo->fifo_virtual_addr+fifo->writer_local_wptr); + /* Add L1 header and L2 header */ + *msg = l1_header; + msg++; + *msg = l2_header; + msg++; + /* + * copy message possbile between writer_local_wptr up + * to writer_local_rptr copy the l3 message in 1 memcpy + */ + memcpy((void *)msg, addr, length); + + spin_lock_bh(&fifo->fifo_update_lock); + /* UpdateWptr */ + fifo->writer_local_wptr += requiredsize; + fifo->availablesize -= requiredsize; + spin_unlock_bh(&fifo->fifo_update_lock); + + } + return length; +} + +/** + * read_one_l2msg_common() - read message from common channel + * @shrm: pointer to shrm device information structure + * @l2_msg: pointer to the read L2 message buffer + * @len: message length + * + * This function read one message from the FIFO and returns l2 header type + */ +u8 read_one_l2msg_common(struct shrm_dev *shrm, + u8 *l2_msg, u32 *len) +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + + u32 *msg; + u32 l1_header = 0; + u32 l2_header = 0; + u32 length; + u8 msgtype; + u32 msg_size; + u32 size = 0; + + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->reader_local_rptr+fifo->fifo_virtual_addr); + l1_header = *msg++; + msgtype = (l1_header & 0xF0000000) >> L1_HEADER_MASK; + + if (msgtype != L1_NORMAL_MSG) { + /* Fatal ERROR - should never happens */ + dev_dbg(shrm->dev, "wr_wptr= %x\n", + fifo->reader_local_wptr); + dev_dbg(shrm->dev, "wr_rptr= %x\n", + fifo->reader_local_rptr); + dev_dbg(shrm->dev, "shared_wptr= %x\n", + fifo->shared_wptr); + dev_dbg(shrm->dev, "shared_rptr= %x\n", + fifo->shared_rptr); + dev_dbg(shrm->dev, "availsize= %x\n", + fifo->availablesize); + dev_dbg(shrm->dev, "end_fifo= %x\n", + fifo->end_addr_fifo); + /* Fatal ERROR - should never happens */ + dev_crit(shrm->dev, "Fatal ERROR - should never happen\n"); + BUG(); + } + if (fifo->reader_local_rptr == (fifo->end_addr_fifo-1)) { + l2_header = (*((u32 *)fifo->fifo_virtual_addr)); + length = l2_header & MASK_0_39_BIT; + } else { + /* Read L2 header,Msg size & content of reader_local_rptr */ + l2_header = *msg; + length = l2_header & MASK_0_39_BIT; + } + + *len = length; + msg_size = ((length + 3) / 4); + msg_size += 2; + + if (fifo->reader_local_rptr + msg_size <= + fifo->end_addr_fifo) { + /* Skip L2 header */ + msg++; + + /* read msg between reader_local_rptr and end of FIFO */ + memcpy((void *)l2_msg, (void *)msg, length); + /* UpdateLocalRptr */ + fifo->reader_local_rptr += msg_size; + fifo->reader_local_rptr %= fifo->end_addr_fifo; + } else { + /* + * msg split between end of FIFO and beg copy first + * part of msg read msg between reader_local_rptr + * and end of FIFO + */ + size = fifo->end_addr_fifo-fifo->reader_local_rptr; + if (size == 1) { + msg = (u32 *)(fifo->fifo_virtual_addr); + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)(msg), length); + } else if (size == 2) { + /* Skip L2 header */ + msg++; + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, + (void *)(msg), length); + } else { + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)msg, ((size - 2) * 4)); + /* copy second part of msg */ + l2_msg += ((size - 2) * 4); + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, (void *)(msg), + (length-((size - 2) * 4))); + } + fifo->reader_local_rptr = + (fifo->reader_local_rptr+msg_size) % + fifo->end_addr_fifo; + } + return (l2_header>>L2_HEADER_OFFSET) & MASK_0_15_BIT; + } + +u8 read_remaining_messages_common() +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + /* + * There won't be any Race condition reader_local_rptr & + * fifo->reader_local_wptr with CaMsgpending Notification Interrupt + */ + return ((fifo->reader_local_rptr != fifo->reader_local_wptr) ? 1 : 0); +} + +u8 read_one_l2msg_audio(struct shrm_dev *shrm, + u8 *l2_msg, u32 *len) +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_1; + + u32 *msg; + u32 l1_header = 0; + u32 l2_header = 0; + u32 length; + u8 msgtype; + u32 msg_size; + u32 size = 0; + + /* Read L1 header read content of reader_local_rptr */ + msg = (u32 *) + (fifo->reader_local_rptr+fifo->fifo_virtual_addr); + l1_header = *msg++; + msgtype = (l1_header & 0xF0000000) >> L1_HEADER_MASK; + + if (msgtype != L1_NORMAL_MSG) { + /* Fatal ERROR - should never happens */ + dev_dbg(shrm->dev, "wr_local_wptr= %x\n", + fifo->reader_local_wptr); + dev_dbg(shrm->dev, "wr_local_rptr= %x\n", + fifo->reader_local_rptr); + dev_dbg(shrm->dev, "shared_wptr= %x\n", + fifo->shared_wptr); + dev_dbg(shrm->dev, "shared_rptr= %x\n", + fifo->shared_rptr); + dev_dbg(shrm->dev, "availsize=%x\n", + fifo->availablesize); + dev_dbg(shrm->dev, "end_fifo= %x\n", + fifo->end_addr_fifo); + /* Fatal ERROR - should never happens */ + dev_crit(shrm->dev, "Fatal ERROR - should never happen\n"); + BUG(); + } + if (fifo->reader_local_rptr == (fifo->end_addr_fifo-1)) { + l2_header = (*((u32 *)fifo->fifo_virtual_addr)); + length = l2_header & MASK_0_39_BIT; + } else { + /* Read L2 header,Msg size & content of reader_local_rptr */ + l2_header = *msg; + length = l2_header & MASK_0_39_BIT; + } + + *len = length; + msg_size = ((length + 3) / 4); + msg_size += 2; + + if (fifo->reader_local_rptr + msg_size <= + fifo->end_addr_fifo) { + /* Skip L2 header */ + msg++; + /* read msg between reader_local_rptr and end of FIFO */ + memcpy((void *)l2_msg, (void *)msg, length); + /* UpdateLocalRptr */ + fifo->reader_local_rptr += msg_size; + fifo->reader_local_rptr %= fifo->end_addr_fifo; + } else { + + /* + * msg split between end of FIFO and beg + * copy first part of msg + * read msg between reader_local_rptr and end of FIFO + */ + size = fifo->end_addr_fifo-fifo->reader_local_rptr; + if (size == 1) { + msg = (u32 *)(fifo->fifo_virtual_addr); + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)(msg), length); + } else if (size == 2) { + /* Skip L2 header */ + msg++; + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, (void *)(msg), length); + } else { + /* Skip L2 header */ + msg++; + memcpy((void *)l2_msg, (void *)msg, ((size - 2) * 4)); + /* copy second part of msg */ + l2_msg += ((size - 2) * 4); + msg = (u32 *)(fifo->fifo_virtual_addr); + memcpy((void *)l2_msg, (void *)(msg), + (length-((size - 2) * 4))); + } + fifo->reader_local_rptr = + (fifo->reader_local_rptr+msg_size) % + fifo->end_addr_fifo; + + } + return (l2_header>>L2_HEADER_OFFSET) & MASK_0_15_BIT; + } + +u8 read_remaining_messages_audio() +{ + struct fifo_read_params *fifo = &cmt_shm_fifo_1; + + return ((fifo->reader_local_rptr != fifo->reader_local_wptr) ? + 1 : 0); +} + +u8 is_the_only_one_unread_message(struct shrm_dev *shrm, + u8 channel, u32 length) +{ + struct fifo_write_params *fifo = NULL; + u32 messagesize = 0; + u8 is_only_one_unread_msg = 0; + + if (channel == COMMON_CHANNEL) + fifo = &ape_shm_fifo_0; + else /* channel = AUDIO_CHANNEL */ + fifo = &ape_shm_fifo_1; + + /* L3 size in 32b */ + messagesize = ((length + 3) / 4); + /* Add size of L1 & L2 header */ + messagesize += 2; + /* + * possibility of race condition with Ac Read notification interrupt. + * need to check ? + */ + if (fifo->writer_local_wptr > fifo->writer_local_rptr) + is_only_one_unread_msg = + ((fifo->writer_local_rptr + messagesize) == + fifo->writer_local_wptr) ? 1 : 0; + else + /* Msg split between end of fifo and starting of Fifo */ + is_only_one_unread_msg = + (((fifo->writer_local_rptr + messagesize) % + fifo->end_addr_fifo) == fifo->writer_local_wptr) ? + 1 : 0; + + return is_only_one_unread_msg; +} + +void update_ca_common_local_wptr(struct shrm_dev *shrm) +{ + /* + * update CA common reader local write pointer with the + * shared write pointer + */ + struct fifo_read_params *fifo = &cmt_shm_fifo_0; + + fifo->shared_wptr = + (*((u32 *)shrm->ca_common_shared_wptr)); + fifo->reader_local_wptr = fifo->shared_wptr; +} + +void update_ca_audio_local_wptr(struct shrm_dev *shrm) +{ + /* + * update CA audio reader local write pointer with the + * shared write pointer + */ + struct fifo_read_params *fifo = &cmt_shm_fifo_1; + + fifo->shared_wptr = + (*((u32 *)shrm->ca_audio_shared_wptr)); + fifo->reader_local_wptr = fifo->shared_wptr; +} + +void update_ac_common_local_rptr(struct shrm_dev *shrm) +{ + /* + * update AC common writer local read pointer with the + * shared read pointer + */ + struct fifo_write_params *fifo; + u32 free_space = 0; + + fifo = &ape_shm_fifo_0; + + fifo->shared_rptr = + (*((u32 *)shrm->ac_common_shared_rptr)); + + if (fifo->shared_rptr >= fifo->writer_local_rptr) + free_space = + (fifo->shared_rptr-fifo->writer_local_rptr); + else { + free_space = + (fifo->end_addr_fifo-fifo->writer_local_rptr); + free_space += fifo->shared_rptr; + } + + /* Chance of race condition of below variables with write_msg */ + spin_lock(&fifo->fifo_update_lock); + fifo->availablesize += free_space; + fifo->writer_local_rptr = fifo->shared_rptr; + spin_unlock(&fifo->fifo_update_lock); +} + +void update_ac_audio_local_rptr(struct shrm_dev *shrm) +{ + /* + * update AC audio writer local read pointer with the + * shared read pointer + */ + struct fifo_write_params *fifo; + u32 free_space = 0; + + fifo = &ape_shm_fifo_1; + fifo->shared_rptr = + (*((u32 *)shrm->ac_audio_shared_rptr)); + + if (fifo->shared_rptr >= fifo->writer_local_rptr) + free_space = + (fifo->shared_rptr-fifo->writer_local_rptr); + else { + free_space = + (fifo->end_addr_fifo-fifo->writer_local_rptr); + free_space += fifo->shared_rptr; + } + + /* Chance of race condition of below variables with write_msg */ + spin_lock(&fifo->fifo_update_lock); + fifo->availablesize += free_space; + fifo->writer_local_rptr = fifo->shared_rptr; + spin_unlock(&fifo->fifo_update_lock); +} + +void update_ac_common_shared_wptr(struct shrm_dev *shrm) +{ + /* + * update AC common shared write pointer with the + * local write pointer + */ + struct fifo_write_params *fifo; + + fifo = &ape_shm_fifo_0; + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ac_common_shared_wptr)) = + fifo->writer_local_wptr; + + fifo->shared_wptr = fifo->writer_local_wptr; +} + +void update_ac_audio_shared_wptr(struct shrm_dev *shrm) +{ + /* + * update AC audio shared write pointer with the + * local write pointer + */ + struct fifo_write_params *fifo; + + fifo = &ape_shm_fifo_1; + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ac_audio_shared_wptr)) = + fifo->writer_local_wptr; + fifo->shared_wptr = fifo->writer_local_wptr; +} + +void update_ca_common_shared_rptr(struct shrm_dev *shrm) +{ + /* + * update CA common shared read pointer with the + * local read pointer + */ + struct fifo_read_params *fifo; + + fifo = &cmt_shm_fifo_0; + + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ca_common_shared_rptr)) = + fifo->reader_local_rptr; + fifo->shared_rptr = fifo->reader_local_rptr; +} + +void update_ca_audio_shared_rptr(struct shrm_dev *shrm) +{ + /* + * update CA audio shared read pointer with the + * local read pointer + */ + struct fifo_read_params *fifo; + + fifo = &cmt_shm_fifo_1; + + /* Update shared pointer fifo offset of the IPC zone */ + (*((u32 *)shrm->ca_audio_shared_rptr)) = + fifo->reader_local_rptr; + fifo->shared_rptr = fifo->reader_local_rptr; +} + +void get_reader_pointers(u8 channel_type, u32 *reader_local_rptr, + u32 *reader_local_wptr, u32 *shared_rptr) +{ + struct fifo_read_params *fifo = NULL; + + if (channel_type == COMMON_CHANNEL) + fifo = &cmt_shm_fifo_0; + else /* channel_type = AUDIO_CHANNEL */ + fifo = &cmt_shm_fifo_1; + + *reader_local_rptr = fifo->reader_local_rptr; + *reader_local_wptr = fifo->reader_local_wptr; + *shared_rptr = fifo->shared_rptr; +} + +void get_writer_pointers(u8 channel_type, u32 *writer_local_rptr, + u32 *writer_local_wptr, u32 *shared_wptr) +{ + struct fifo_write_params *fifo = NULL; + + if (channel_type == COMMON_CHANNEL) + fifo = &ape_shm_fifo_0; + else /* channel_type = AUDIO_CHANNEL */ + fifo = &ape_shm_fifo_1; + + *writer_local_rptr = fifo->writer_local_rptr; + *writer_local_wptr = fifo->writer_local_wptr; + *shared_wptr = fifo->shared_wptr; +} + +void set_ca_msg_0_read_notif_send(u8 val) +{ + cmt_read_notif_0_send = val; +} + +u8 get_ca_msg_0_read_notif_send(void) +{ + return cmt_read_notif_0_send; +} + +void set_ca_msg_1_read_notif_send(u8 val) +{ + cmt_read_notif_1_send = val; +} + +u8 get_ca_msg_1_read_notif_send(void) +{ + return cmt_read_notif_1_send; +} diff --git a/drivers/misc/shrm/shrm_protocol.c b/drivers/misc/shrm/shrm_protocol.c new file mode 100644 index 00000000000..dd52f9e20ea --- /dev/null +++ b/drivers/misc/shrm/shrm_protocol.c @@ -0,0 +1,1177 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghvi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define L2_HEADER_ISI 0x0 +#define L2_HEADER_RPC 0x1 +#define L2_HEADER_AUDIO 0x2 +#define L2_HEADER_SECURITY 0x3 +#define L2_HEADER_COMMON_SIMPLE_LOOPBACK 0xC0 +#define L2_HEADER_COMMON_ADVANCED_LOOPBACK 0xC1 +#define L2_HEADER_AUDIO_SIMPLE_LOOPBACK 0x80 +#define L2_HEADER_AUDIO_ADVANCED_LOOPBACK 0x81 +#define MAX_PAYLOAD 1024 + +static u8 boot_state = BOOT_INIT; +static u8 recieve_common_msg[8*1024]; +static u8 recieve_audio_msg[8*1024]; +static received_msg_handler rx_common_handler; +static received_msg_handler rx_audio_handler; +static struct hrtimer timer; +static char is_earlydrop; +struct sock *shrm_nl_sk; + +static char shrm_common_tx_state = SHRM_SLEEP_STATE; +static char shrm_common_rx_state = SHRM_SLEEP_STATE; +static char shrm_audio_tx_state = SHRM_SLEEP_STATE; +static char shrm_audio_rx_state = SHRM_SLEEP_STATE; + +static atomic_t ac_sleep_disable_count = ATOMIC_INIT(0); +static struct shrm_dev *shm_dev; + +/* Spin lock and tasklet declaration */ +DECLARE_TASKLET(shm_ca_0_tasklet, shm_ca_msgpending_0_tasklet, 0); +DECLARE_TASKLET(shm_ca_1_tasklet, shm_ca_msgpending_1_tasklet, 0); +DECLARE_TASKLET(shm_ac_read_0_tasklet, shm_ac_read_notif_0_tasklet, 0); +DECLARE_TASKLET(shm_ac_read_1_tasklet, shm_ac_read_notif_1_tasklet, 0); + +static DEFINE_MUTEX(ac_state_mutex); + +static DEFINE_SPINLOCK(ca_common_lock); +static DEFINE_SPINLOCK(ca_audio_lock); +static DEFINE_SPINLOCK(ca_wake_req_lock); +static DEFINE_SPINLOCK(boot_lock); + +enum shrm_nl { + SHRM_NL_MOD_RESET = 1, + SHRM_NL_MOD_QUERY_STATE, + SHRM_NL_USER_MOD_RESET, + SHRM_NL_STATUS_MOD_ONLINE, + SHRM_NL_STATUS_MOD_OFFLINE, +}; + +static void shm_ac_sleep_req_work(struct work_struct *work) +{ + mutex_lock(&ac_state_mutex); + if (atomic_read(&ac_sleep_disable_count) == 0) + prcmu_ac_sleep_req(); + mutex_unlock(&ac_state_mutex); +} + +static u32 get_host_accessport_val(void) +{ + u32 prcm_hostaccess; + + prcm_hostaccess = readl(PRCM_HOSTACCESS_REQ); + wmb(); + prcm_hostaccess = prcm_hostaccess & 0x01; + + return prcm_hostaccess; +} +static enum hrtimer_restart callback(struct hrtimer *timer) +{ + unsigned long flags; + + spin_lock_irqsave(&ca_wake_req_lock, flags); + if (((shrm_common_rx_state == SHRM_IDLE) || + (shrm_common_rx_state == SHRM_SLEEP_STATE)) + && ((shrm_common_tx_state == SHRM_IDLE) || + (shrm_common_tx_state == SHRM_SLEEP_STATE)) + && ((shrm_audio_rx_state == SHRM_IDLE) || + (shrm_audio_rx_state == SHRM_SLEEP_STATE)) + && ((shrm_audio_tx_state == SHRM_IDLE) || + (shrm_audio_tx_state == SHRM_SLEEP_STATE))) { + + shrm_common_rx_state = SHRM_SLEEP_STATE; + shrm_audio_rx_state = SHRM_SLEEP_STATE; + shrm_common_tx_state = SHRM_SLEEP_STATE; + shrm_audio_tx_state = SHRM_SLEEP_STATE; + + queue_work(shm_dev->shm_ac_sleep_wq, + &shm_dev->shm_ac_sleep_req); + + } + spin_unlock_irqrestore(&ca_wake_req_lock, flags); + + return HRTIMER_NORESTART; +} + +int nl_send_multicast_message(int msg, gfp_t gfp_mask) +{ + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + int err; + + /* prepare netlink message */ + skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), gfp_mask); + if (!skb) { + dev_err(shm_dev->dev, "%s:alloc_skb failed\n", __func__); + err = -ENOMEM; + goto out; + } + + nlh = (struct nlmsghdr *)skb->data; + nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); + dev_dbg(shm_dev->dev, "nlh->nlmsg_len = %d\n", nlh->nlmsg_len); + + nlh->nlmsg_pid = 0; /* from kernel */ + nlh->nlmsg_flags = 0; + *(int *)NLMSG_DATA(nlh) = msg; + skb_put(skb, MAX_PAYLOAD); + /* sender is in group 1<<0 */ + NETLINK_CB(skb).pid = 0; /* from kernel */ + /* to mcast group 1<<0 */ + NETLINK_CB(skb).dst_group = 1; + + /*multicast the message to all listening processes*/ + err = netlink_broadcast(shrm_nl_sk, skb, 0, 1, gfp_mask); + dev_dbg(shm_dev->dev, "ret val from nl-multicast = %d\n", err); + +out: + return err; +} + +static void nl_send_unicast_message(int dst_pid) +{ + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh = NULL; + int err; + int bt_state; + unsigned long flags; + + dev_info(shm_dev->dev, "Sending unicast message\n"); + + /* prepare the NL message for unicast */ + skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_KERNEL); + if (!skb) { + dev_err(shm_dev->dev, "%s:alloc_skb failed\n", __func__); + return; + } + + nlh = (struct nlmsghdr *)skb->data; + nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); + dev_dbg(shm_dev->dev, "nlh->nlmsg_len = %d\n", nlh->nlmsg_len); + + nlh->nlmsg_pid = 0; /* from kernel */ + nlh->nlmsg_flags = 0; + + spin_lock_irqsave(&boot_lock, flags); + bt_state = boot_state; + spin_unlock_irqrestore(&boot_lock, flags); + + if (bt_state == BOOT_DONE) + *(int *)NLMSG_DATA(nlh) = SHRM_NL_STATUS_MOD_ONLINE; + else + *(int *)NLMSG_DATA(nlh) = SHRM_NL_STATUS_MOD_OFFLINE; + + skb_put(skb, MAX_PAYLOAD); + /* sender is in group 1<<0 */ + NETLINK_CB(skb).pid = 0; /* from kernel */ + NETLINK_CB(skb).dst_group = 0; + + /*unicast the message to the querying processes*/ + err = netlink_unicast(shrm_nl_sk, skb, dst_pid, MSG_DONTWAIT); + dev_dbg(shm_dev->dev, "ret val from nl-unicast = %d\n", err); +} + + +static int check_modem_in_reset(void) +{ + u8 bt_state; + unsigned long flags; + + spin_lock_irqsave(&boot_lock, flags); + bt_state = boot_state; + spin_unlock_irqrestore(&boot_lock, flags); + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET + if (bt_state != BOOT_UNKNOWN) + return 0; + else + return -ENODEV; +#else + /* + * this check won't be applicable and won't work correctly + * if modem-silent-feature is not enabled + * so, simply return 0 + */ + return 0; +#endif +} + +void shm_ca_msgpending_0_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 reader_local_rptr; + u32 reader_local_wptr; + u32 shared_rptr; + u32 config = 0, version = 0; + unsigned long flags; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + /* Interprocess locking */ + spin_lock(&ca_common_lock); + + /* Update_reader_local_wptr with shared_wptr */ + update_ca_common_local_wptr(shrm); + get_reader_pointers(COMMON_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + + set_ca_msg_0_read_notif_send(0); + + if (boot_state == BOOT_DONE) { + shrm_common_rx_state = SHRM_PTR_FREE; + + if (reader_local_rptr != shared_rptr) + ca_msg_read_notification_0(shrm); + if (reader_local_rptr != reader_local_wptr) + receive_messages_common(shrm); + get_reader_pointers(COMMON_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + if (reader_local_rptr == reader_local_wptr) + shrm_common_rx_state = SHRM_IDLE; + } else { + /* BOOT phase.only a BOOT_RESP should be in FIFO */ + if (boot_state != BOOT_INFO_SYNC) { + if (!read_boot_info_req(shrm, &config, &version)) { + dev_err(shrm->dev, + "Unable to read boot state\n"); + BUG(); + } + /* SendReadNotification */ + ca_msg_read_notification_0(shrm); + /* + * Check the version number before + * sending Boot info response + */ + + /* send MsgPending notification */ + write_boot_info_resp(shrm, config, version); + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_INFO_SYNC; + spin_unlock_irqrestore(&boot_lock, flags); + dev_info(shrm->dev, "BOOT_INFO_SYNC\n"); + queue_work(shrm->shm_common_ch_wr_wq, + &shrm->send_ac_msg_pend_notify_0); + } else { + ca_msg_read_notification_0(shrm); + dev_info(shrm->dev, + "BOOT_INFO_SYNC\n"); + } + } + /* Interprocess locking */ + spin_unlock(&ca_common_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ca_msgpending_1_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 reader_local_rptr; + u32 reader_local_wptr; + u32 shared_rptr; + + /* + * This function is called when CaMsgPendingNotification Trigerred + * by CMU. It means that CMU has wrote a message into Ca Audio FIFO + */ + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + /* Interprocess locking */ + spin_lock(&ca_audio_lock); + + /* Update_reader_local_wptr(with shared_wptr) */ + update_ca_audio_local_wptr(shrm); + get_reader_pointers(AUDIO_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + + set_ca_msg_1_read_notif_send(0); + + if (boot_state != BOOT_DONE) { + dev_err(shrm->dev, "Boot Error\n"); + return; + } + shrm_audio_rx_state = SHRM_PTR_FREE; + /* Check we already read the message */ + if (reader_local_rptr != shared_rptr) + ca_msg_read_notification_1(shrm); + if (reader_local_rptr != reader_local_wptr) + receive_messages_audio(shrm); + + get_reader_pointers(AUDIO_CHANNEL, &reader_local_rptr, + &reader_local_wptr, &shared_rptr); + if (reader_local_rptr == reader_local_wptr) + shrm_audio_rx_state = SHRM_IDLE; + + /* Interprocess locking */ + spin_unlock(&ca_audio_lock); + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ac_read_notif_0_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 writer_local_rptr; + u32 writer_local_wptr; + u32 shared_wptr; + unsigned long flags; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + /* Update writer_local_rptrwith shared_rptr */ + update_ac_common_local_rptr(shrm); + get_writer_pointers(COMMON_CHANNEL, &writer_local_rptr, + &writer_local_wptr, &shared_wptr); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + if (boot_state == BOOT_INFO_SYNC) { + /* BOOT_RESP sent by APE has been received by CMT */ + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_DONE; + spin_unlock_irqrestore(&boot_lock, flags); + dev_info(shrm->dev, "IPC_ISA BOOT_DONE\n"); + + if (shrm->msr_flag) { + shrm_start_netdev(shrm->ndev); + shrm->msr_flag = 0; + + /* multicast that modem is online */ + nl_send_multicast_message(SHRM_NL_STATUS_MOD_ONLINE, GFP_ATOMIC); + } + + } else if (boot_state == BOOT_DONE) { + if (writer_local_rptr != writer_local_wptr) { + shrm_common_tx_state = SHRM_PTR_FREE; + queue_work(shrm->shm_common_ch_wr_wq, + &shrm->send_ac_msg_pend_notify_0); + } else { + shrm_common_tx_state = SHRM_IDLE; + shrm_restart_netdev(shrm->ndev); + } + } else { + dev_err(shrm->dev, "Invalid boot state\n"); + } + /* start timer here */ + hrtimer_start(&timer, ktime_set(0, 10*NSEC_PER_MSEC), + HRTIMER_MODE_REL); + atomic_dec(&ac_sleep_disable_count); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ac_read_notif_1_tasklet(unsigned long tasklet_data) +{ + struct shrm_dev *shrm = (struct shrm_dev *)tasklet_data; + u32 writer_local_rptr; + u32 writer_local_wptr; + u32 shared_wptr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown\n", + __func__); + return; + } + + /* Update writer_local_rptr(with shared_rptr) */ + update_ac_audio_local_rptr(shrm); + get_writer_pointers(AUDIO_CHANNEL, &writer_local_rptr, + &writer_local_wptr, &shared_wptr); + if (boot_state != BOOT_DONE) { + dev_err(shrm->dev, "Error Case in boot state\n"); + return; + } + if (writer_local_rptr != writer_local_wptr) { + shrm_audio_tx_state = SHRM_PTR_FREE; + queue_work(shrm->shm_audio_ch_wr_wq, + &shrm->send_ac_msg_pend_notify_1); + } else { + shrm_audio_tx_state = SHRM_IDLE; + } + /* start timer here */ + hrtimer_start(&timer, ktime_set(0, 10*NSEC_PER_MSEC), + HRTIMER_MODE_REL); + atomic_dec(&ac_sleep_disable_count); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_ca_sleep_req_work(struct work_struct *work) +{ + dev_dbg(shm_dev->dev, "%s:IRQ_PRCMU_CA_SLEEP\n", __func__); + + shrm_common_rx_state = SHRM_IDLE; + shrm_audio_rx_state = SHRM_IDLE; + + writel((1<intr_base + GOP_SET_REGISTER_BASE); + + hrtimer_start(&timer, ktime_set(0, 10*NSEC_PER_MSEC), + HRTIMER_MODE_REL); +#ifdef CONFIG_UX500_SUSPEND + suspend_unblock_sleep(); +#endif + atomic_dec(&ac_sleep_disable_count); +} + +void shm_ca_wake_req_work(struct work_struct *work) +{ + struct shrm_dev *shrm = container_of(work, + struct shrm_dev, shm_ca_wake_req); + + /* initialize the FIFO Variables */ + if (boot_state == BOOT_INIT) + shm_fifo_init(shrm); + + mutex_lock(&ac_state_mutex); + prcmu_ac_wake_req(); + mutex_unlock(&ac_state_mutex); + + /* send ca_wake_ack_interrupt to CMU */ + if (!get_host_accessport_val()) + BUG(); + writel((1<intr_base + GOP_SET_REGISTER_BASE); +} + +static int shrm_modem_reset_sequence(void) +{ + int err; + unsigned long flags; + + /* + * disable irqs + * very much needed for user-space initiated + * modem-reset + */ + disable_irq_nosync(shm_dev->ac_read_notif_0_irq); + disable_irq_nosync(shm_dev->ac_read_notif_1_irq); + disable_irq_nosync(shm_dev->ca_msg_pending_notif_0_irq); + disable_irq_nosync(shm_dev->ca_msg_pending_notif_1_irq); + disable_irq_nosync(IRQ_PRCMU_CA_WAKE); + disable_irq_nosync(IRQ_PRCMU_CA_SLEEP); + + + /* update the boot_state */ + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_UNKNOWN; + + /* + * put a barrier over here to make sure boot_state is updated + * else, it is seen that some of already executing modem + * irqs or tasklets fail the protocol checks and will ultimately + * try to acces the modem causing system to hang. + * This is particularly seen with user-space initiated modem reset + */ + wmb(); + spin_unlock_irqrestore(&boot_lock, flags); + + hrtimer_cancel(&timer); + + /* reset the state for ac-wake LOW logic */ + atomic_set(&ac_sleep_disable_count, 0); + + /* stop network queue */ + shrm_stop_netdev(shm_dev->ndev); + + /* reset char device queues */ + shrm_char_reset_queues(shm_dev); + + /* reset protocol states */ + shrm_common_tx_state = SHRM_SLEEP_STATE; + shrm_common_rx_state = SHRM_SLEEP_STATE; + shrm_audio_tx_state = SHRM_SLEEP_STATE; + shrm_audio_rx_state = SHRM_SLEEP_STATE; + + /* set the msr flag */ + shm_dev->msr_flag = 1; + + /* multicast that modem is going to reset */ + err = nl_send_multicast_message(SHRM_NL_MOD_RESET, GFP_ATOMIC); + + /* reset the boot state */ + spin_lock_irqsave(&boot_lock, flags); + boot_state = BOOT_INIT; + spin_unlock_irqrestore(&boot_lock, flags); + + /* re-enable irqs */ + enable_irq(shm_dev->ac_read_notif_0_irq); + enable_irq(shm_dev->ac_read_notif_1_irq); + enable_irq(shm_dev->ca_msg_pending_notif_0_irq); + enable_irq(shm_dev->ca_msg_pending_notif_1_irq); + enable_irq(IRQ_PRCMU_CA_WAKE); + enable_irq(IRQ_PRCMU_CA_SLEEP); + + /* reset counter for ac-wake/ac-sleep logic */ + atomic_set(&ac_sleep_disable_count, 0); + + return err; +} + +static void shrm_modem_reset_callback(unsigned long irq) +{ + dev_err(shm_dev->dev, "Received mod_reset_req interrupt\n"); + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET + { + int err; + dev_info(shm_dev->dev, "Initiating Modem silent reset\n"); + + err = shrm_modem_reset_sequence(); + if (err) + dev_err(shm_dev->dev, + "Failed multicast of modem reset\n"); + } +#else + dev_info(shm_dev->dev, "Modem in reset loop, doing System reset\n"); + /* Call the PRCMU reset API */ + prcmu_system_reset(); +#endif +} + +DECLARE_TASKLET(shrm_sw_reset_callback, shrm_modem_reset_callback, + IRQ_PRCMU_MODEM_SW_RESET_REQ); + +static irqreturn_t shrm_prcmu_irq_handler(int irq, void *data) +{ + struct shrm_dev *shrm = data; + + switch (irq) { + case IRQ_PRCMU_CA_WAKE: +#ifdef CONFIG_UX500_SUSPEND + suspend_block_sleep(); +#endif + atomic_inc(&ac_sleep_disable_count); + queue_work(shrm->shm_ca_wake_wq, &shrm->shm_ca_wake_req); + break; + case IRQ_PRCMU_CA_SLEEP: + queue_work(shrm->shm_ca_wake_wq, &shrm->shm_ca_sleep_req); + break; + case IRQ_PRCMU_MODEM_SW_RESET_REQ: + tasklet_schedule(&shrm_sw_reset_callback); + break; + default: + dev_err(shrm->dev, "%s: => IRQ %d\n", __func__, irq); + return IRQ_NONE; + } + return IRQ_HANDLED; +} + +static void send_ac_msg_pend_notify_0_work(struct work_struct *work) +{ + struct shrm_dev *shrm = container_of(work, struct shrm_dev, + send_ac_msg_pend_notify_0); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + update_ac_common_shared_wptr(shrm); + + mutex_lock(&ac_state_mutex); + atomic_inc(&ac_sleep_disable_count); + prcmu_ac_wake_req(); + mutex_unlock(&ac_state_mutex); + + if (!get_host_accessport_val()) + BUG(); + + /* Trigger AcMsgPendingNotification to CMU */ + writel((1<intr_base + GOP_SET_REGISTER_BASE); + + if (shrm_common_tx_state == SHRM_PTR_FREE) + shrm_common_tx_state = SHRM_PTR_BUSY; + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +static void send_ac_msg_pend_notify_1_work(struct work_struct *work) +{ + struct shrm_dev *shrm = container_of(work, struct shrm_dev, + send_ac_msg_pend_notify_1); + + dev_dbg(shrm->dev, "%s IN\n", __func__); + /* Update shared_wptr with writer_local_wptr) */ + update_ac_audio_shared_wptr(shrm); + + mutex_lock(&ac_state_mutex); + atomic_inc(&ac_sleep_disable_count); + prcmu_ac_wake_req(); + mutex_unlock(&ac_state_mutex); + + if (!get_host_accessport_val()) + BUG(); + + /* Trigger AcMsgPendingNotification to CMU */ + writel((1<intr_base + GOP_SET_REGISTER_BASE); + + if (shrm_audio_tx_state == SHRM_PTR_FREE) + shrm_audio_tx_state = SHRM_PTR_BUSY; + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void shm_nl_receive(struct sk_buff *skb) +{ + struct nlmsghdr *nlh = NULL; + int msg; + + dev_dbg(shm_dev->dev, "Received NL msg from user-space\n"); + + nlh = (struct nlmsghdr *)skb->data; + msg = *((int *)(NLMSG_DATA(nlh))); + switch (msg) { + case SHRM_NL_MOD_QUERY_STATE: + dev_info(shm_dev->dev, "mod-query-state from user-space\n"); + nl_send_unicast_message(nlh->nlmsg_pid); + break; + + case SHRM_NL_USER_MOD_RESET: + dev_info(shm_dev->dev, "user-space inited mod-reset-req\n"); + dev_info(shm_dev->dev, "PCRMU resets modem\n"); + prcmu_modem_reset(); + break; + + default: + dev_err(shm_dev->dev, "Invalid NL msg from user-space\n"); + break; + }; +} + +int shrm_protocol_init(struct shrm_dev *shrm, + received_msg_handler common_rx_handler, + received_msg_handler audio_rx_handler) +{ + int err; + + shm_dev = shrm; + boot_state = BOOT_INIT; + dev_info(shrm->dev, "IPC_ISA BOOT_INIT\n"); + rx_common_handler = common_rx_handler; + rx_audio_handler = audio_rx_handler; + atomic_set(&ac_sleep_disable_count, 0); + + is_earlydrop = cpu_is_u8500ed(); + if (is_earlydrop != 0x01) { + hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + timer.function = callback; + } + + shrm->shm_common_ch_wr_wq = create_singlethread_workqueue + ("shm_common_channel_irq"); + if (!shrm->shm_common_ch_wr_wq) { + dev_err(shrm->dev, "failed to create work queue\n"); + return -ENOMEM; + } + shrm->shm_audio_ch_wr_wq = create_singlethread_workqueue + ("shm_audio_channel_irq"); + if (!shrm->shm_audio_ch_wr_wq) { + dev_err(shrm->dev, "failed to create work queue\n"); + err = -ENOMEM; + goto free_wq1; + } + shrm->shm_ac_wake_wq = create_singlethread_workqueue("shm_ac_wake_req"); + if (!shrm->shm_ac_wake_wq) { + dev_err(shrm->dev, "failed to create work queue\n"); + err = -ENOMEM; + goto free_wq2; + } + shrm->shm_ca_wake_wq = create_singlethread_workqueue("shm_ca_wake_req"); + if (!shrm->shm_ac_wake_wq) { + dev_err(shrm->dev, "failed to create work queue\n"); + err = -ENOMEM; + goto free_wq3; + } + shrm->shm_ac_sleep_wq = create_singlethread_workqueue + ("shm_ac_sleep_req"); + if (!shrm->shm_ac_sleep_wq) { + dev_err(shrm->dev, "failed to create work queue\n"); + err = -ENOMEM; + goto free_wq4; + } + INIT_WORK(&shrm->send_ac_msg_pend_notify_0, + send_ac_msg_pend_notify_0_work); + INIT_WORK(&shrm->send_ac_msg_pend_notify_1, + send_ac_msg_pend_notify_1_work); + INIT_WORK(&shrm->shm_ca_wake_req, shm_ca_wake_req_work); + INIT_WORK(&shrm->shm_ca_sleep_req, shm_ca_sleep_req_work); + INIT_WORK(&shrm->shm_ac_sleep_req, shm_ac_sleep_req_work); + + /* set tasklet data */ + shm_ca_0_tasklet.data = (unsigned long)shrm; + shm_ca_1_tasklet.data = (unsigned long)shrm; + + err = request_irq(IRQ_PRCMU_CA_SLEEP, shrm_prcmu_irq_handler, + IRQF_NO_SUSPEND, "ca-sleep", shrm); + if (err < 0) { + dev_err(shm_dev->dev, "Failed alloc IRQ_PRCMU_CA_SLEEP.\n"); + goto free_wq5; + } + + err = request_irq(IRQ_PRCMU_CA_WAKE, shrm_prcmu_irq_handler, + IRQF_NO_SUSPEND, "ca-wake", shrm); + if (err < 0) { + dev_err(shm_dev->dev, "Failed alloc IRQ_PRCMU_CA_WAKE.\n"); + goto drop2; + } + + err = request_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, shrm_prcmu_irq_handler, + IRQF_NO_SUSPEND, "modem-sw-reset-req", shrm); + if (err < 0) { + dev_err(shm_dev->dev, + "Failed alloc IRQ_PRCMU_MODEM_SW_RESET_REQ.\n"); + goto drop1; + } + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET + /* init netlink socket for user-space communication */ + shrm_nl_sk = netlink_kernel_create(NULL, NETLINK_SHRM, 1, + shm_nl_receive, NULL, THIS_MODULE); + + if (!shrm_nl_sk) { + dev_err(shm_dev->dev, "netlink socket creation failed\n"); + goto drop; + } +#endif + + return 0; + +#ifdef CONFIG_U8500_SHRM_MODEM_SILENT_RESET +drop: + free_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, NULL); +#endif +drop1: + free_irq(IRQ_PRCMU_CA_WAKE, NULL); +drop2: + free_irq(IRQ_PRCMU_CA_SLEEP, NULL); +free_wq5: + destroy_workqueue(shrm->shm_ac_sleep_wq); +free_wq4: + destroy_workqueue(shrm->shm_ca_wake_wq); +free_wq3: + destroy_workqueue(shrm->shm_ac_wake_wq); +free_wq2: + destroy_workqueue(shrm->shm_audio_ch_wr_wq); +free_wq1: + destroy_workqueue(shrm->shm_common_ch_wr_wq); + return err; +} + +void shrm_protocol_deinit(struct shrm_dev *shrm) +{ + free_irq(IRQ_PRCMU_CA_SLEEP, NULL); + free_irq(IRQ_PRCMU_CA_WAKE, NULL); + free_irq(IRQ_PRCMU_MODEM_SW_RESET_REQ, NULL); + flush_scheduled_work(); + destroy_workqueue(shrm->shm_common_ch_wr_wq); + destroy_workqueue(shrm->shm_audio_ch_wr_wq); + destroy_workqueue(shrm->shm_ac_wake_wq); + destroy_workqueue(shrm->shm_ca_wake_wq); + destroy_workqueue(shrm->shm_ac_sleep_wq); +} + +int get_ca_wake_req_state(void) +{ + return ((atomic_read(&ac_sleep_disable_count) > 0) || + prcmu_is_ac_wake_requested()); +} + +irqreturn_t ca_wake_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + /* initialize the FIFO Variables */ + if (boot_state == BOOT_INIT) + shm_fifo_init(shrm); + + dev_dbg(shrm->dev, "Inside ca_wake_irq_handler\n"); + + /* Clear the interrupt */ + writel((1 << GOP_CA_WAKE_REQ_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + /* send ca_wake_ack_interrupt to CMU */ + writel((1 << GOP_CA_WAKE_ACK_BIT), + shrm->intr_base + GOP_SET_REGISTER_BASE); + + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + + +irqreturn_t ac_read_notif_0_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + shm_ac_read_0_tasklet.data = (unsigned long)shrm; + tasklet_schedule(&shm_ac_read_0_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1 << GOP_COMMON_AC_READ_NOTIFICATION_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + +irqreturn_t ac_read_notif_1_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN+\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + shm_ac_read_1_tasklet.data = (unsigned long)shrm; + tasklet_schedule(&shm_ac_read_1_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1 << GOP_AUDIO_AC_READ_NOTIFICATION_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + +irqreturn_t ca_msg_pending_notif_0_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + tasklet_schedule(&shm_ca_0_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1 << GOP_COMMON_CA_MSG_PENDING_NOTIFICATION_BIT), + shrm->intr_base + GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; +} + +irqreturn_t ca_msg_pending_notif_1_irq_handler(int irq, void *ctrlr) +{ + struct shrm_dev *shrm = ctrlr; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + tasklet_schedule(&shm_ca_1_tasklet); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return IRQ_HANDLED; + } + + /* Clear the interrupt */ + writel((1<intr_base+GOP_CLEAR_REGISTER_BASE); + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return IRQ_HANDLED; + +} + +/** + * shm_write_msg() - write message to shared memory + * @shrm: pointer to the shrm device information structure + * @l2_header: L2 header + * @addr: pointer to the message + * @length: length of the message to be written + * + * This function is called from net or char interface driver write operation. + * Prior to calling this function the message is copied from the user space + * buffer to the kernel buffer. This function based on the l2 header routes + * the message to the respective channel and FIFO. Then makes a call to the + * fifo write function where the message is written to the physical device. + */ +int shm_write_msg(struct shrm_dev *shrm, u8 l2_header, + void *addr, u32 length) +{ + u8 channel = 0; + int ret; + + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (boot_state != BOOT_DONE) { + dev_err(shrm->dev, + "error after boot done call this fn\n"); + ret = -ENODEV; + goto out; + } + + if ((l2_header == L2_HEADER_ISI) || + (l2_header == L2_HEADER_RPC) || + (l2_header == L2_HEADER_SECURITY) || + (l2_header == L2_HEADER_COMMON_SIMPLE_LOOPBACK) || + (l2_header == L2_HEADER_COMMON_ADVANCED_LOOPBACK)) { + channel = 0; + if (shrm_common_tx_state == SHRM_SLEEP_STATE) + shrm_common_tx_state = SHRM_PTR_FREE; + else if (shrm_common_tx_state == SHRM_IDLE) + shrm_common_tx_state = SHRM_PTR_FREE; + + } else if ((l2_header == L2_HEADER_AUDIO) || + (l2_header == L2_HEADER_AUDIO_SIMPLE_LOOPBACK) || + (l2_header == L2_HEADER_AUDIO_ADVANCED_LOOPBACK)) { + if (shrm_audio_tx_state == SHRM_SLEEP_STATE) + shrm_audio_tx_state = SHRM_PTR_FREE; + else if (shrm_audio_tx_state == SHRM_IDLE) + shrm_audio_tx_state = SHRM_PTR_FREE; + + channel = 1; + } else { + ret = -ENODEV; + goto out; + } + ret = shm_write_msg_to_fifo(shrm, channel, l2_header, addr, length); + if (ret < 0) { + dev_err(shrm->dev, "write message to fifo failed\n"); + return ret; + } + /* + * notify only if new msg copied is the only unread one + * otherwise it means that reading process is ongoing + */ + if (is_the_only_one_unread_message(shrm, channel, length)) { + + /* Send Message Pending Noitication to CMT */ + if (channel == 0) + queue_work(shrm->shm_common_ch_wr_wq, + &shrm->send_ac_msg_pend_notify_0); + else + queue_work(shrm->shm_audio_ch_wr_wq, + &shrm->send_ac_msg_pend_notify_1); + + } + + dev_dbg(shrm->dev, "%s OUT\n", __func__); + return 0; + +out: + return ret; +} + +void ca_msg_read_notification_0(struct shrm_dev *shrm) +{ + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_ca_msg_0_read_notif_send() == 0) { + update_ca_common_shared_rptr(shrm); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + /* Trigger CaMsgReadNotification to CMU */ + writel((1 << GOP_COMMON_CA_READ_NOTIFICATION_BIT), + shrm->intr_base + GOP_SET_REGISTER_BASE); + set_ca_msg_0_read_notif_send(1); + shrm_common_rx_state = SHRM_PTR_BUSY; + } + + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +void ca_msg_read_notification_1(struct shrm_dev *shrm) +{ + dev_dbg(shrm->dev, "%s IN\n", __func__); + + if (get_ca_msg_1_read_notif_send() == 0) { + update_ca_audio_shared_rptr(shrm); + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + /* Trigger CaMsgReadNotification to CMU */ + writel((1<intr_base+GOP_SET_REGISTER_BASE); + set_ca_msg_1_read_notif_send(1); + shrm_audio_rx_state = SHRM_PTR_BUSY; + } + dev_dbg(shrm->dev, "%s OUT\n", __func__); +} + +/** + * receive_messages_common - receive common channnel msg from + * CMT(Cellular Mobile Terminal) + * @shrm: pointer to shrm device information structure + * + * The messages sent from CMT to APE are written to the respective FIFO + * and an interrupt is triggered by the CMT. This ca message pending + * interrupt calls this function. This function sends a read notification + * acknowledgement to the CMT and calls the common channel receive handler + * where the messsage is copied to the respective(ISI, RPC, SECURIT) queue + * based on the message l2 header. + */ +void receive_messages_common(struct shrm_dev *shrm) +{ + u8 l2_header; + u32 len; + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_common(shrm, recieve_common_msg, &len); + /* Send Recieve_Call_back to Upper Layer */ + if (!rx_common_handler) { + dev_err(shrm->dev, "common_rx_handler is Null\n"); + BUG(); + } + (*rx_common_handler)(l2_header, &recieve_common_msg, len, + shrm); + /* SendReadNotification */ + ca_msg_read_notification_0(shrm); + + while (read_remaining_messages_common()) { + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_common(shrm, recieve_common_msg, + &len); + /* Send Recieve_Call_back to Upper Layer */ + (*rx_common_handler)(l2_header, + &recieve_common_msg, len, + shrm); + } +} + +/** + * receive_messages_audio() - receive audio message from CMT + * @shrm: pointer to shrm device information structure + * + * The messages sent from CMT to APE are written to the respective FIFO + * and an interrupt is triggered by the CMT. This ca message pending + * interrupt calls this function. This function sends a read notification + * acknowledgement to the CMT and calls the common channel receive handler + * where the messsage is copied to the audio queue. + */ +void receive_messages_audio(struct shrm_dev *shrm) +{ + u8 l2_header; + u32 len; + + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_audio(shrm, recieve_audio_msg, &len); + /* Send Recieve_Call_back to Upper Layer */ + + if (!rx_audio_handler) { + dev_crit(shrm->dev, "audio_rx_handler is Null\n"); + BUG(); + } + (*rx_audio_handler)(l2_header, &recieve_audio_msg, + len, shrm); + + /* SendReadNotification */ + ca_msg_read_notification_1(shrm); + while (read_remaining_messages_audio()) { + if (check_modem_in_reset()) { + dev_err(shrm->dev, "%s:Modem state reset or unknown.\n", + __func__); + return; + } + + l2_header = read_one_l2msg_audio(shrm, + recieve_audio_msg, &len); + /* Send Recieve_Call_back to Upper Layer */ + (*rx_audio_handler)(l2_header, + &recieve_audio_msg, len, + shrm); + } +} + +u8 get_boot_state() +{ + return boot_state; +} diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 776a478e629..00e142268cb 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -306,3 +306,7 @@ obj-$(CONFIG_CAIF) += caif/ obj-$(CONFIG_OCTEON_MGMT_ETHERNET) += octeon/ obj-$(CONFIG_PCH_GBE) += pch_gbe/ obj-$(CONFIG_TILE_NET) += tile/ + +ifdef CONFIG_PHONET +obj-$(CONFIG_U8500_SHRM) += u8500_shrm.o +endif diff --git a/drivers/net/u8500_shrm.c b/drivers/net/u8500_shrm.c new file mode 100644 index 00000000000..55983a59761 --- /dev/null +++ b/drivers/net/u8500_shrm.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) ST-Ericsson SA 2009 + * + * Author: Biju Das for ST-Ericsson + * Author: Kumar Sanghvi for ST-Ericsson + * Author: Arun Murthy for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static u8 wr_isi_msg[10*1024]; + +/** + * shrm_net_receive() - receive data and copy to user space buffer + * @dev: pointer to the network device structure + * @data: pointer to the receive buffer + * + * Copy data from ISI queue to the user space buffer. + */ +int shrm_net_receive(struct net_device *dev, u8 *data) +{ + struct sk_buff *skb; + struct isadev_context *isadev; + struct message_queue *q; + u32 msgsize; + u32 size = 0; + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + if (data == NULL) + goto out; + + isadev = &shrm->isa_context->isadev[ISI_MESSAGING]; + q = &isadev->dl_queue; + + spin_lock_bh(&q->update_lock); + if (list_empty(&q->msg_list)) { + spin_unlock_bh(&q->update_lock); + dev_dbg(shrm->dev, "Empty Shrm queue\n"); + return 0; + } + spin_unlock_bh(&q->update_lock); + + msgsize = get_size_of_new_msg(q); + if (msgsize <= 0) + return msgsize; + + if ((q->readptr+msgsize) >= q->size) { + size = (q->size-q->readptr); + /*Copy First Part of msg*/ + memcpy(data, + (u8 *)(q->fifo_base + q->readptr), size); + /*Copy Second Part of msg at the top of fifo*/ + memcpy(data+size, + (u8 *)(q->fifo_base), (msgsize - size)); + } else { + memcpy(data, + (u8 *)(q->fifo_base+q->readptr), msgsize); + } + + spin_lock_bh(&q->update_lock); + remove_msg_from_queue(q); + spin_unlock_bh(&q->update_lock); + + dev_dbg(shrm->dev, "Data len at shrm_net_receive: %d\n", msgsize); + + /* + * The packet has been retrieved from the transmission + * medium. Build an skb around it, so upper layers can handle it + */ + + skb = dev_alloc_skb(msgsize); + if (!skb) { + if (printk_ratelimit()) + dev_notice(shrm->dev, + "isa rx: low on mem - packet dropped\n"); + dev->stats.rx_dropped++; + goto out; + } + skb_copy_to_linear_data(skb, data, msgsize); + skb_put(skb, msgsize); + skb_reset_mac_header(skb); + __skb_pull(skb, dev->hard_header_len); + /*Write metadata, and then pass to the receive level*/ + skb->dev = dev;/*kmalloc(sizeof(struct net_device), GFP_ATOMIC);*/ + skb->protocol = htons(ETH_P_PHONET); + skb->priority = 0; + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + if (likely(netif_rx_ni(skb) == NET_RX_SUCCESS)) { + dev->stats.rx_packets++; + dev->stats.rx_bytes += msgsize; + } else + dev->stats.rx_dropped++; + + return msgsize; +out: + return -ENOMEM; +} + +static int netdev_isa_open(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + shrm->netdev_flag_up = 1; + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_wake_queue(dev); + return 0; +} + +static int netdev_isa_close(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + shrm->netdev_flag_up = 0; + netif_stop_queue(dev); + netif_carrier_off(dev); + return 0; +} + +static int netdev_isa_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct if_phonet_req *req = (struct if_phonet_req *)ifr; + + switch (cmd) { + case SIOCPNGAUTOCONF: + req->ifr_phonet_autoconf.device = PN_DEV_HOST; + return 0; + } + return -ENOIOCTLCMD; +} + +static struct net_device_stats *netdev_isa_stats(struct net_device *dev) +{ + return &dev->stats; +} + +/** + * netdev_isa_write() - write through the net interface + * @skb: pointer to the socket buffer + * @dev: pointer to the network device structure + * + * Copies data(ISI message) from the user buffer to the kernel buffer and + * schedule transfer thread to transmit the message to the modem via FIFO. + */ +static netdev_tx_t netdev_isa_write(struct sk_buff *skb, struct net_device *dev) +{ + int err; + int retval = 0; + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + /* + * FIXME: + * U8500 modem requires that Pipe created/enabled Indication should + * be sent from the port corresponding to GPRS socket. + * Also, the U8500 modem does not implement Pipe controller + * which takes care of port manipulations for GPRS traffic. + * + * Now, APE has GPRS socket and the socket for sending + * Indication msgs bound to different ports. + * Phonet stack does not allow an indication msg to be sent + * from GPRS socket, since Phonet stack assumes the presence + * of Pipe controller in modem. + * + * So, due to lack of Pipe controller implementation in the + * U8500 modem, carry out the port manipulation related to + * GPRS traffic here. + * Ideally, it should be done either by Pipe controller in + * modem OR some implementation of Pipe controller on APE side + */ + if (skb->data[RESOURCE_ID_INDEX] == PN_PIPE) { + if ((skb->data[MSG_ID_INDEX] == PNS_PIPE_CREATED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_ENABLED_IND) || + (skb->data[MSG_ID_INDEX] == PNS_PIPE_DISABLED_IND)) + skb->data[SRC_OBJ_INDEX] = skb->data[PIPE_HDL_INDEX]; + } + + if ((void *)wr_isi_msg != + memcpy((void *)wr_isi_msg, skb->data, skb->len)) { + dev_err(shrm->dev, "memcpy failed\n"); + dev_kfree_skb(skb); + return -EFAULT; + } + + spin_lock_bh(&shrm->isa_context->common_tx); + err = shm_write_msg(shrm, ISI_MESSAGING, (void *)wr_isi_msg, + skb->len); + if (!err) { + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + retval = NETDEV_TX_OK; + dev_kfree_skb(skb); + } else { + dev->stats.tx_dropped++; + retval = NETDEV_TX_BUSY; + } + spin_unlock_bh(&shrm->isa_context->common_tx); + + return retval; +} + +static const struct net_device_ops shrm_netdev_ops = { + .ndo_open = netdev_isa_open, + .ndo_stop = netdev_isa_close, + .ndo_do_ioctl = netdev_isa_ioctl, + .ndo_start_xmit = netdev_isa_write, + .ndo_get_stats = netdev_isa_stats, +}; + +static void shm_net_init(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv; + + dev->netdev_ops = &shrm_netdev_ops; + dev->header_ops = &phonet_header_ops; + dev->type = ARPHRD_PHONET; + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + dev->mtu = PHONET_MAX_MTU; + dev->hard_header_len = SHRM_HLEN; + dev->addr_len = PHONET_ALEN; + dev->tx_queue_len = PN_TX_QUEUE_LEN; + dev->destructor = free_netdev; + dev->dev_addr[0] = PN_LINK_ADDR; + net_iface_priv = netdev_priv(dev); + memset(net_iface_priv, 0 , sizeof(struct shrm_net_iface_priv)); +} + +int shrm_register_netdev(struct shrm_dev *shrm) +{ + struct net_device *nw_device; + struct shrm_net_iface_priv *net_iface_priv; + char *devname = "shrm%d"; + int err; + + /* allocate the net device */ + nw_device = shrm->ndev = alloc_netdev( + sizeof(struct shrm_net_iface_priv), + devname, shm_net_init); + if (nw_device == NULL) { + dev_err(shrm->dev, "Failed to allocate SHRM Netdev\n"); + return -ENOMEM; + } + err = register_netdev(shrm->ndev); + if (err) { + dev_err(shrm->dev, "Err %i in reg shrm-netdev\n", err); + free_netdev(shrm->ndev); + return -ENODEV; + } + dev_info(shrm->dev, "Registered shrm netdev\n"); + + net_iface_priv = (struct shrm_net_iface_priv *)netdev_priv(nw_device); + net_iface_priv->shrm_device = shrm; + net_iface_priv->iface_num = 0; + + return err; +} + +int shrm_stop_netdev(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + + netif_stop_queue(dev); + return 0; +} + +int shrm_restart_netdev(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + return 0; +} + +int shrm_start_netdev(struct net_device *dev) +{ + struct shrm_net_iface_priv *net_iface_priv = + (struct shrm_net_iface_priv *)netdev_priv(dev); + struct shrm_dev *shrm = net_iface_priv->shrm_device; + + if (!netif_carrier_ok(dev)) + netif_carrier_on(dev); + netif_start_queue(dev); + shrm->netdev_flag_up = 1; + return 0; +} + +int shrm_suspend_netdev(struct net_device *dev) +{ + if (netif_running(dev)) { + netif_stop_queue(dev); + netif_carrier_off(dev); + } + netif_device_detach(dev); + + return 0; +} + +int shrm_resume_netdev(struct net_device *dev) +{ + netif_device_attach(dev); + if (netif_running(dev)) { + netif_carrier_on(dev); + netif_wake_queue(dev); + } + + return 0; +} + +void shrm_unregister_netdev(struct shrm_dev *shrm) +{ + unregister_netdev(shrm->ndev); +} -- cgit v1.2.3 From cdba8348c5e3100a34fc76d8a8e1eb124c288a43 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 11:56:11 +0100 Subject: mfd: stmpe: Recheck the status to avoid failure stmpe: add stmpe documentation to DocBook Signed-off-by: Robert Marklund --- Documentation/DocBook/Makefile | 2 +- Documentation/DocBook/stmpe.tmpl | 119 +++++++++++++++++++++++++++++++++++++++ drivers/mfd/stmpe.c | 18 +++++- 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 Documentation/DocBook/stmpe.tmpl diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index a466fc92ada..9733f6bcde7 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,7 +15,7 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml media.xml drm.xml \ - i2s.xml msp.xml shrm.xml + i2s.xml msp.xml shrm.xml stmpe.xml ### # The build process is as follows (targets): diff --git a/Documentation/DocBook/stmpe.tmpl b/Documentation/DocBook/stmpe.tmpl new file mode 100644 index 00000000000..6759149d6ce --- /dev/null +++ b/Documentation/DocBook/stmpe.tmpl @@ -0,0 +1,119 @@ + + + + + + STMPE IO-Port Expander guide + + + + Rabin + Vincent + +
+ rabin.vincent@stericsson.com +
+
+
+
+ + + 2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License version 2 as published by the Free Software Foundation. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + + +
+ + + + + Introduction + + This documentation describes the driver for STMicroelectronics + STMPExxxx port expander devices. + + + + + Known Bugs And Assumptions + + + + None. + + + None. + + + + + + + + + Public Functions Provided + + List of public interfaces in stmpe driver + +!Edrivers/mfd/stmpe.c + + + + Private Functions + + STMPE Keypad driver + STMPE GPIO driver + +
+ stmpe-keypad.c +!Idrivers/input/keyboard/stmpe-keypad.c +
+
+ stmpe-gpio.c +!Idrivers/gpio/stmpe-gpio.c +
+
+ + + Other Data Structures + + This Section lists some of the Data structure used by the stmpe driver and client drivers. + +!Iinclude/linux/mfd/stmpe.h +!Idrivers/mfd/stmpe.h + +
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 7ab7746631d..a791d415e8d 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -674,7 +674,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]; @@ -696,6 +696,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; } -- cgit v1.2.3 From 825ebf12b43c091a22e989532b4a714c12a7b646 Mon Sep 17 00:00:00 2001 From: Shujuan Chen Date: Tue, 15 Jun 2010 17:27:11 +0200 Subject: add trusted execution environment (tee) driver TEE working now for Android, tested ok with COPS! * Updated according to review comments - Added ST-Ericsson copyright headers to all tee files. - Fixed problem with not using readl/writel when using ioremap. - Fixed problem with forgetting to do iounmap on special case. - Fixed incorrect usage when doing copy_to_user when writing to the device. - Added architecture dependent file for the tee service that calls the secure world. - Added support for more several inputs (sharedmemory buffers) for tee. - Added dummy macro to map MT_MEMORY device. - Fixed memory leak in secure world due to not closing a TEE session correctly from the kernel. - Now we only copies input buffer from user space for tee. - Documented structures in tee.h. - Moved SVP implementation into arch/arm/mach-ux500 folder. - Added new config flags for ux500 and SVP regarding TEE driver. - Update mach-ux500/Kconfig: - Enable TEE_UX500 by default when using target hardware. - Enabel TEE_SVP by default when building simulator. - Fix the cache sync problem: not request ROM code to clean cache - ioremap for ICN_BASE, remove static mapping in cpu-db8500.c. - Fix ioremap of ICN_BASE and do iounmap after use. ST-Ericsson ID: WP269815 Change-Id: Ie861a90ec790e95fb3992e560512661693548a43 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/4168 Reviewed-by: Shujuan CHEN Tested-by: Shujuan CHEN Signed-off-by: Lee Jones --- arch/arm/mach-ux500/Kconfig | 14 + arch/arm/mach-ux500/Makefile | 3 + arch/arm/mach-ux500/include/mach/db5500-regs.h | 3 +- .../mach-ux500/include/mach/tee_ta_start_modem.h | 48 ++ arch/arm/mach-ux500/tee_service_svp.c | 66 +++ arch/arm/mach-ux500/tee_ta_start_modem_svp.c | 56 +++ arch/arm/mach-ux500/tee_ux500.c | 79 ++++ drivers/Kconfig | 3 + drivers/Makefile | 1 + drivers/tee/Kconfig | 13 + drivers/tee/Makefile | 8 + drivers/tee/tee_driver.c | 484 +++++++++++++++++++++ drivers/tee/tee_service.c | 17 + include/linux/tee.h | 143 ++++++ 14 files changed, 936 insertions(+), 2 deletions(-) create mode 100644 arch/arm/mach-ux500/include/mach/tee_ta_start_modem.h create mode 100644 arch/arm/mach-ux500/tee_service_svp.c create mode 100644 arch/arm/mach-ux500/tee_ta_start_modem_svp.c create mode 100644 arch/arm/mach-ux500/tee_ux500.c create mode 100644 drivers/tee/Kconfig create mode 100644 drivers/tee/Makefile create mode 100644 drivers/tee/tee_driver.c create mode 100644 drivers/tee/tee_service.c create mode 100644 include/linux/tee.h diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index b6d3c981217..22b0c4903b4 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -69,5 +69,19 @@ config U5500_MBOX help Add support for U5500 mailbox communication with modem side +config TEE_UX500 + bool "Trusted Execution Environment (TEE) ux500 hardware support" + depends on (TEE_SUPPORT && MACH_U8500_MOP) + default y + help + Adds TEE hardware support for ux500 platforms. + +config TEE_SVP + bool "Trusted Execution Environment (TEE) ux500 SVP support" + depends on (TEE_SUPPORT && (MACH_U8500_SIMULATOR || MACH_U5500_SIMULATOR)) + default y + help + Adds TEE support for SVP in ux500 platforms. + source "arch/arm/mach-ux500/pm/Kconfig" endif diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 52c45e86b15..4790d43874f 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -20,3 +20,6 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o obj-$(CONFIG_U5500_MODEM_IRQ) += modem-irq-db5500.o obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o +obj-$(CONFIG_TEE_UX500) += tee_ux500.o +obj-$(CONFIG_TEE_SVP) += tee_service_svp.o +obj-$(CONFIG_TEE_SVP) += tee_ta_start_modem_svp.o diff --git a/arch/arm/mach-ux500/include/mach/db5500-regs.h b/arch/arm/mach-ux500/include/mach/db5500-regs.h index 6ad98329410..667b6dc7cd5 100644 --- a/arch/arm/mach-ux500/include/mach/db5500-regs.h +++ b/arch/arm/mach-ux500/include/mach/db5500-regs.h @@ -119,12 +119,11 @@ #define U5500_MBOX2_LOCAL_START (U5500_MBOX_BASE + 0x20) #define U5500_MBOX2_LOCAL_END (U5500_MBOX_BASE + 0x3F) +#define U5500_ESRAM_BASE 0x40000000 #define U5500_ACCCON_BASE_SEC (0xBFFF0000) #define U5500_ACCCON_BASE (0xBFFF1000) #define U5500_ACCCON_CPUVEC_RESET_ADDR_OFFSET (0x00000020) #define U5500_ACCCON_ACC_CPU_CTRL_OFFSET (0x000000BC) - -#define U5500_ESRAM_BASE 0x40000000 #define U5500_ESRAM_DMA_LCPA_OFFSET 0x10000 #define U5500_DMA_LCPA_BASE (U5500_ESRAM_BASE + U5500_ESRAM_DMA_LCPA_OFFSET) diff --git a/arch/arm/mach-ux500/include/mach/tee_ta_start_modem.h b/arch/arm/mach-ux500/include/mach/tee_ta_start_modem.h new file mode 100644 index 00000000000..6978b7314c5 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/tee_ta_start_modem.h @@ -0,0 +1,48 @@ +/* + * Data types and interface for TEE application for starting the modem. + * + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef TEE_TA_START_MODEM_H +#define TEE_TA_START_MODEM_H + +#define COMMAND_ID_START_MODEM 0x00000001 + +#define UUID_TEE_TA_START_MODEM_LOW 0x8AD94107 +#define UUID_TEE_TA_START_MODEM_MID 0x6E50 +#define UUID_TEE_TA_START_MODEM_HIGH 0x418E +#define UUID_TEE_TA_START_MODEM_CLOCKSEQ \ + {0xB1, 0x14, 0x75, 0x7D, 0x60, 0x21, 0xBD, 0x36} + +struct mcore_segment_descr { + void *segment; + void *hash; + u32 size; +}; + +struct access_image_descr { + void *elf_hdr; + void *pgm_hdr_tbl; + void *signature; + unsigned long nbr_segment; + struct mcore_segment_descr *descr; +}; + +/* TODO: To be redefined with only info needed by Secure world. */ +struct tee_ta_start_modem { + void *access_mem_start; + u32 shared_mem_size; + u32 access_private_mem_size; + struct access_image_descr access_image_descr; +}; + +/** + * This is the function to handle the modem release. + */ +int tee_ta_start_modem(struct tee_ta_start_modem *data); + +#endif + diff --git a/arch/arm/mach-ux500/tee_service_svp.c b/arch/arm/mach-ux500/tee_service_svp.c new file mode 100644 index 00000000000..aa65dd961a0 --- /dev/null +++ b/arch/arm/mach-ux500/tee_service_svp.c @@ -0,0 +1,66 @@ +/* + * TEE service to handle the calls to trusted applications in SVP. + * + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include "mach/tee_ta_start_modem.h" + +static int cmp_uuid_start_modem(struct tee_uuid *uuid) +{ + int ret = -EINVAL; + + if (uuid == NULL) + return -EINVAL; + + /* This handles the calls to TA for start the modem */ + if ((uuid->timeLow == UUID_TEE_TA_START_MODEM_LOW) && + (uuid->timeMid == UUID_TEE_TA_START_MODEM_MID) && + (uuid->timeHiAndVersion == UUID_TEE_TA_START_MODEM_HIGH)) { + + u8 clockSeqAndNode[TEE_UUID_CLOCK_SIZE] = + UUID_TEE_TA_START_MODEM_CLOCKSEQ; + + ret = memcmp(uuid->clockSeqAndNode, clockSeqAndNode, + TEE_UUID_CLOCK_SIZE); + } + + return ret; +} + +int call_sec_world(struct tee_session *ts, int sec_cmd) +{ + int ret = 0; + + if (ts == NULL) + return -EINVAL; + + if (cmp_uuid_start_modem(ts->uuid)) + return -EINVAL; + + switch (ts->cmd) { + case COMMAND_ID_START_MODEM: + ret = tee_ta_start_modem((struct tee_ta_start_modem *) + ts->op); + if (ret) { + ts->err = TEED_ERROR_GENERIC; + ts->origin = TEED_ORIGIN_TEE_APPLICATION; + pr_err("tee_ta_start_modem() failed!\n"); + return ret; + } + break; + + default: + break; + } + + /* TODO: to handle more trusted applications. */ + + return ret; +} diff --git a/arch/arm/mach-ux500/tee_ta_start_modem_svp.c b/arch/arm/mach-ux500/tee_ta_start_modem_svp.c new file mode 100644 index 00000000000..12337b93154 --- /dev/null +++ b/arch/arm/mach-ux500/tee_ta_start_modem_svp.c @@ -0,0 +1,56 @@ +/* + * Trusted application for starting the modem. + * + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include + +#include "mach/tee_ta_start_modem.h" + +static int reset_modem(unsigned long modem_start_addr) +{ + void __iomem *base = ioremap(U5500_ACCCON_BASE_SEC, 0x2FF); + if (!base) + return -ENOMEM; + + pr_info("[%s] Setting modem start address!\n", __func__); + writel(base + (U5500_ACCCON_CPUVEC_RESET_ADDR_OFFSET/sizeof(uint32_t)), + modem_start_addr); + + pr_info("[%s] resetting the modem!\n", __func__); + writel(base + (U5500_ACCCON_ACC_CPU_CTRL_OFFSET/sizeof(uint32_t)), 1); + + iounmap(base); + + return 0; +} + +int tee_ta_start_modem(struct tee_ta_start_modem *data) +{ + int ret = 0; + struct elfhdr *elfhdr; + void __iomem *vaddr; + + vaddr = ioremap((unsigned long)data->access_image_descr.elf_hdr, + sizeof(struct elfhdr)); + if (!vaddr) + return -ENOMEM; + + elfhdr = (struct elfhdr *)readl(vaddr); + pr_info("Reading in kernel:elfhdr 0x%x:elfhdr->entry=0x%x\n", + (uint32_t)elfhdr, (uint32_t)elfhdr->e_entry); + + pr_info("[%s] reset modem()...\n", __func__); + ret = reset_modem(elfhdr->e_entry); + + iounmap(vaddr); + + return ret; +} diff --git a/arch/arm/mach-ux500/tee_ux500.c b/arch/arm/mach-ux500/tee_ux500.c new file mode 100644 index 00000000000..ab3782a323c --- /dev/null +++ b/arch/arm/mach-ux500/tee_ux500.c @@ -0,0 +1,79 @@ +/* + * TEE service to handle the calls to trusted applications. + * + * Copyright (C) ST-Ericsson SA 2010 + * Author: Joakim Bech + * License terms: GNU General Public License (GPL) version 2 + */ +#include +#include +#include + +#include + +#define BOOT_BRIDGE_FUNC (U8500_BOOT_ROM_BASE + 0x18300) + +#define ISSWAPI_EXECUTE_TA 0x11000001 +#define ISSWAPI_CLOSE_TA 0x11000002 + +#define SEC_ROM_NO_FLAG_MASK 0x0000 + +static u32 call_sec_rom_bridge(u32 service_id, u32 cfg, ...) +{ + typedef u32 (*bridge_func)(u32, u32, va_list); + static bridge_func hw_sec_rom_pub_bridge; + va_list ap; + u32 ret; + + hw_sec_rom_pub_bridge = + (bridge_func)((u32)IO_ADDRESS(BOOT_BRIDGE_FUNC)); + + va_start(ap, cfg); + ret = hw_sec_rom_pub_bridge(service_id, cfg, ap); + va_end(ap); + + return ret; +} + +int call_sec_world(struct tee_session *ts, int sec_cmd) +{ + /* + * ts->ta and ts->uuid is set to NULL when opening the device, + * hence it should be safe to just do the call here. + */ + + switch (sec_cmd) { + case TEED_INVOKE: + if (!ts->uuid) { + call_sec_rom_bridge(ISSWAPI_EXECUTE_TA, + SEC_ROM_NO_FLAG_MASK, + virt_to_phys(&ts->id), + NULL, + virt_to_phys(ts->ta), + ts->cmd, + virt_to_phys((void *)(ts->op)), + virt_to_phys((void *)(&ts->origin))); + } else { + call_sec_rom_bridge(ISSWAPI_EXECUTE_TA, + SEC_ROM_NO_FLAG_MASK, + virt_to_phys(&ts->id), + virt_to_phys(ts->uuid), + virt_to_phys(ts->ta), + ts->cmd, + virt_to_phys((void *)(ts->op)), + virt_to_phys((void *)(&ts->origin))); + } + break; + + case TEED_CLOSE_SESSION: + call_sec_rom_bridge(ISSWAPI_CLOSE_TA, + SEC_ROM_NO_FLAG_MASK, + ts->id, + NULL, + virt_to_phys(ts->ta), + virt_to_phys((void *)(&ts->origin))); + break; + } + + return 0; +} diff --git a/drivers/Kconfig b/drivers/Kconfig index 3bb154d8c8c..1787eed0f36 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -122,8 +122,11 @@ source "drivers/platform/Kconfig" source "drivers/clk/Kconfig" +source "drivers/tee/Kconfig" + source "drivers/hwspinlock/Kconfig" source "drivers/clocksource/Kconfig" endmenu + diff --git a/drivers/Makefile b/drivers/Makefile index 09f3232bcdc..99f9f18d00a 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -120,5 +120,6 @@ obj-y += platform/ obj-y += ieee802154/ #common clk code obj-y += clk/ +obj-$(CONFIG_TEE_SUPPORT) += tee/ obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig new file mode 100644 index 00000000000..a452e888d77 --- /dev/null +++ b/drivers/tee/Kconfig @@ -0,0 +1,13 @@ +# +# Copyright (C) ST-Ericsson SA 2010 +# Author: Martin Hovang (martin.xm.hovang@stericsson.com) +# License terms: GNU General Public License (GPL) version 2 +# + +# Trursted Execution Environment Configuration +config TEE_SUPPORT + bool "Trusted Execution Environment Support" + default y + ---help--- + This implements the Trusted Execution Environment (TEE) Client + API Specification from GlobalPlatform Device Technology. diff --git a/drivers/tee/Makefile b/drivers/tee/Makefile new file mode 100644 index 00000000000..b937eb19d72 --- /dev/null +++ b/drivers/tee/Makefile @@ -0,0 +1,8 @@ +# +# Copyright (C) ST-Ericsson SA 2010 +# Author: Martin Hovang (martin.xm.hovang@stericsson.com) +# License terms: GNU General Public License (GPL) version 2 +# + +obj-$(CONFIG_TEE_SUPPORT) += tee_service.o +obj-$(CONFIG_TEE_SUPPORT) += tee_driver.o diff --git a/drivers/tee/tee_driver.c b/drivers/tee/tee_driver.c new file mode 100644 index 00000000000..551c92cc054 --- /dev/null +++ b/drivers/tee/tee_driver.c @@ -0,0 +1,484 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Hovang + * Author: Joakim Bech + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEED_NAME "tee" + +#define TEED_STATE_OPEN_DEV 0 +#define TEED_STATE_OPEN_SESSION 1 + +#define TEEC_MEM_INPUT 0x00000001 +#define TEEC_MEM_OUTPUT 0x00000002 + +static int tee_open(struct inode *inode, struct file *file); +static int tee_release(struct inode *inode, struct file *file); +static int tee_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset); +static int tee_write(struct file *filp, const char __user *buffer, + size_t length, loff_t *offset); + +static inline void set_emsg(struct tee_session *ts, u32 msg) +{ + ts->err = msg; + ts->origin = TEED_ORIGIN_DRIVER; +} + +static void reset_session(struct tee_session *ts) +{ + ts->state = TEED_STATE_OPEN_DEV; + ts->err = TEED_SUCCESS; + ts->origin = TEED_ORIGIN_DRIVER; + ts->id = 0; + ts->ta = NULL; + ts->uuid = NULL; + ts->cmd = 0; + ts->driver_cmd = TEED_OPEN_SESSION; + ts->ta_size = 0; + ts->op = NULL; +} + +static int copy_ta(struct tee_session *ts, + struct tee_session *ku_buffer) +{ + ts->ta = kmalloc(ku_buffer->ta_size, GFP_KERNEL); + if (ts->ta == NULL) { + pr_err("[%s] error, out of memory (ta)\n", + __func__); + set_emsg(ts, TEED_ERROR_OUT_OF_MEMORY); + return -ENOMEM; + } + + ts->ta_size = ku_buffer->ta_size; + + memcpy(ts->ta, ku_buffer->ta, ku_buffer->ta_size); + return 0; +} + +static int copy_uuid(struct tee_session *ts, + struct tee_session *ku_buffer) +{ + ts->uuid = kmalloc(sizeof(struct tee_uuid), GFP_KERNEL); + + if (ts->uuid == NULL) { + pr_err("[%s] error, out of memory (uuid)\n", + __func__); + set_emsg(ts, TEED_ERROR_OUT_OF_MEMORY); + return -ENOMEM; + } + + memcpy(ts->uuid, ku_buffer->uuid, sizeof(struct tee_uuid)); + + return 0; +} + +static inline void free_operation(struct tee_session *ts) +{ + int i; + + for (i = 0; i < 4; ++i) { + kfree(ts->op->shm[i].buffer); + ts->op->shm[i].buffer = NULL; + } + + kfree(ts->op); + ts->op = NULL; +} + +static inline void memrefs_phys_to_virt(struct tee_session *ts) +{ + int i; + + for (i = 0; i < 4; ++i) { + if (ts->op->flags & (1 << i)) { + ts->op->shm[i].buffer = + phys_to_virt((unsigned long) + ts->op->shm[i].buffer); + } + } +} + +static int copy_memref_to_user(struct tee_operation *op, + struct tee_operation *ubuf_op, + int memref) +{ + unsigned long bytes_left; + + bytes_left = copy_to_user(ubuf_op->shm[memref].buffer, + op->shm[memref].buffer, + op->shm[memref].size); + + if (bytes_left != 0) { + pr_err("[%s] Failed to copy result to user space (%lu " + "bytes left of buffer).\n", __func__, bytes_left); + return bytes_left; + } + + bytes_left = put_user(op->shm[memref].size, &ubuf_op->shm[memref].size); + + if (bytes_left != 0) { + pr_err("[%s] Failed to copy result to user space (%lu " + "bytes left of size).\n", __func__, bytes_left); + return -EINVAL; + } + + bytes_left = put_user(op->shm[memref].flags, + &ubuf_op->shm[memref].flags); + if (bytes_left != 0) { + pr_err("[%s] Failed to copy result to user space (%lu " + "bytes left of flags).\n", __func__, bytes_left); + return -EINVAL; + } + + return 0; +} + +static int copy_memref_to_kernel(struct tee_operation *op, + struct tee_operation *kbuf_op, + int memref) +{ + /* Buffer freed in invoke_command if this function fails */ + op->shm[memref].buffer = kmalloc(kbuf_op->shm[memref].size, GFP_KERNEL); + + if (!op->shm[memref].buffer) { + pr_err("[%s] out of memory\n", __func__); + return -ENOMEM; + } + + /* + * Copy shared memory operations to a local kernel + * buffer if they are of type input. + */ + if (kbuf_op->shm[memref].flags & TEEC_MEM_INPUT) { + memcpy(op->shm[memref].buffer, + kbuf_op->shm[memref].buffer, + kbuf_op->shm[memref].size); + } + + op->shm[memref].size = kbuf_op->shm[memref].size; + op->shm[memref].flags = kbuf_op->shm[memref].flags; + + /* Secure world expects physical addresses. */ + op->shm[memref].buffer = (void *)virt_to_phys(op->shm[memref].buffer); + + return 0; +} + +static int open_tee_device(struct tee_session *ts, + struct tee_session *ku_buffer) +{ + int ret; + + if (ku_buffer->driver_cmd != TEED_OPEN_SESSION) { + set_emsg(ts, TEED_ERROR_BAD_STATE); + return -EINVAL; + } + + if (ku_buffer->ta) { + ret = copy_ta(ts, ku_buffer); + } else if (ku_buffer->uuid) { + ret = copy_uuid(ts, ku_buffer); + } else { + set_emsg(ts, TEED_ERROR_COMMUNICATION); + return -EINVAL; + } + + ts->id = 0; + ts->state = TEED_STATE_OPEN_SESSION; + return ret; +} + +static int invoke_command(struct tee_session *ts, + struct tee_session *ku_buffer, + struct tee_session __user *u_buffer) +{ + int i; + int ret = 0; + struct tee_operation *kbuf_op = + (struct tee_operation *)ku_buffer->op; + + ts->op = kmalloc(sizeof(struct tee_operation), GFP_KERNEL); + + if (!ts->op) { + if (ts->op == NULL) { + pr_err("[%s] error, out of memory " + "(op)\n", __func__); + set_emsg(ts, TEED_ERROR_OUT_OF_MEMORY); + ret = -ENOMEM; + goto err; + } + } + + /* Copy memrefs to kernel space. */ + ts->op->flags = kbuf_op->flags; + ts->cmd = ku_buffer->cmd; + + for (i = 0; i < 4; ++i) { + /* We only want to copy memrefs in use. */ + if (kbuf_op->flags & (1 << i)) { + ret = copy_memref_to_kernel(ts->op, kbuf_op, i); + + if (ret) + goto err; + } else { + ts->op->shm[i].buffer = NULL; + ts->op->shm[i].size = 0; + ts->op->shm[i].flags = 0; + } + } + + /* To call secure world */ + if (call_sec_world(ts, TEED_INVOKE)) { + ret = -EINVAL; + goto err; + } + + /* + * Convert physical addresses back to virtual address so the + * kernel can free the buffers when closing the session. + */ + memrefs_phys_to_virt(ts); + + for (i = 0; i < 4; ++i) { + if ((kbuf_op->flags & (1 << i)) && + (kbuf_op->shm[i].flags & TEEC_MEM_OUTPUT)) { + struct tee_operation *ubuf_op = + (struct tee_operation *)u_buffer->op; + + ret = copy_memref_to_user(ts->op, ubuf_op, i); + } + } +err: + free_operation(ts); + + return ret; +} + +static int tee_open(struct inode *inode, struct file *filp) +{ + struct tee_session *ts; + + filp->private_data = kmalloc(sizeof(struct tee_session), + GFP_KERNEL); + + if (filp->private_data == NULL) + return -ENOMEM; + + ts = (struct tee_session *) (filp->private_data); + + reset_session(ts); + + ts->sync = kmalloc(sizeof(struct mutex), GFP_KERNEL); + + if (!ts->sync) + return -ENOMEM; + + mutex_init(ts->sync); + + return 0; +} + +static int tee_release(struct inode *inode, struct file *filp) +{ + struct tee_session *ts; + int i; + + ts = (struct tee_session *) (filp->private_data); + + if (ts == NULL) + goto no_ts; + + if (ts->op) { + for (i = 0; i < 4; ++i) { + kfree(ts->op->shm[i].buffer); + ts->op->shm[i].buffer = NULL; + } + } + + kfree(ts->op); + ts->op = NULL; + + kfree(ts->sync); + ts->sync = NULL; + + kfree(ts->ta); + ts->ta = NULL; + +no_ts: + kfree(filp->private_data); + filp->private_data = NULL; + + return 0; +} + +/* + * Called when a process, which already opened the dev file, attempts + * to read from it. This function gets the current status of the session. + */ +static int tee_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset) +{ + struct tee_read buf; + struct tee_session *ts; + + if (length != sizeof(struct tee_read)) { + pr_err("[%s] error, incorrect input length\n", + __func__); + return -EINVAL; + } + + ts = (struct tee_session *) (filp->private_data); + + if (ts == NULL || ts->sync == NULL) { + pr_err("[%s] error, private_data not " + "initialized\n", __func__); + return -EINVAL; + } + + mutex_lock(ts->sync); + + buf.err = ts->err; + buf.origin = ts->origin; + + mutex_unlock(ts->sync); + + if (copy_to_user(buffer, &buf, length)) { + pr_err("[%s] error, copy_to_user failed!\n", + __func__); + return -EINVAL; + } + + return length; +} + +/* + * Called when a process writes to a dev file + */ +static int tee_write(struct file *filp, const char __user *buffer, + size_t length, loff_t *offset) +{ + struct tee_session ku_buffer; + struct tee_session *ts; + int ret = length; + + if (length != sizeof(struct tee_session)) { + pr_err("[%s] error, incorrect input length\n", + __func__); + return -EINVAL; + } + + if (copy_from_user(&ku_buffer, buffer, length)) { + pr_err("[%s] error, tee_session " + "copy_from_user failed\n", __func__); + return -EINVAL; + } + + ts = (struct tee_session *) (filp->private_data); + + if (ts == NULL || ts->sync == NULL) { + pr_err("[%s] error, private_data not " + "initialized\n", __func__); + return -EINVAL; + } + + mutex_lock(ts->sync); + + switch (ts->state) { + case TEED_STATE_OPEN_DEV: + ret = open_tee_device(ts, &ku_buffer); + break; + + case TEED_STATE_OPEN_SESSION: + switch (ku_buffer.driver_cmd) { + case TEED_INVOKE: + ret = invoke_command(ts, &ku_buffer, + (struct tee_session *)buffer); + break; + + case TEED_CLOSE_SESSION: + /* no caching implemented yet... */ + if (call_sec_world(ts, TEED_CLOSE_SESSION)) + ret = -EINVAL; + + kfree(ts->ta); + ts->ta = NULL; + + reset_session(ts); + break; + + default: + set_emsg(ts, TEED_ERROR_BAD_PARAMETERS); + ret = -EINVAL; + } + break; + default: + pr_err("[%s] unknown state\n", __func__); + set_emsg(ts, TEED_ERROR_BAD_STATE); + ret = -EINVAL; + } + + /* + * We expect that ret has value zero when reaching the end here. + * If it has any other value some error must have occured. + */ + if (!ret) + ret = length; + else + ret = -EINVAL; + + mutex_unlock(ts->sync); + + return ret; +} + +static const struct file_operations tee_fops = { + .owner = THIS_MODULE, + .read = tee_read, + .write = tee_write, + .open = tee_open, + .release = tee_release, +}; + +static struct miscdevice tee_dev = { + MISC_DYNAMIC_MINOR, + TEED_NAME, + &tee_fops +}; + +static int __init tee_init(void) +{ + int err = 0; + + err = misc_register(&tee_dev); + + if (err) { + pr_err("[%s] error %d adding character device " + "TEE\n", __func__, err); + } + + return err; +} + +static void __exit tee_exit(void) +{ + misc_deregister(&tee_dev); +} + +module_init(tee_init); +module_exit(tee_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Trusted Execution Enviroment driver"); diff --git a/drivers/tee/tee_service.c b/drivers/tee/tee_service.c new file mode 100644 index 00000000000..b01e9d0ac39 --- /dev/null +++ b/drivers/tee/tee_service.c @@ -0,0 +1,17 @@ +/* + * TEE service to handle the calls to trusted applications. + * + * Copyright (C) ST-Ericsson SA 2010 + * Author: Joakim Bech + * License terms: GNU General Public License (GPL) version 2 + */ +#include +#include +#include + +int __weak call_sec_world(struct tee_session *ts, int sec_cmd) +{ + pr_info("[%s] Generic call_sec_world called!\n", __func__); + + return 0; +} diff --git a/include/linux/tee.h b/include/linux/tee.h new file mode 100644 index 00000000000..0cdec2d254a --- /dev/null +++ b/include/linux/tee.h @@ -0,0 +1,143 @@ +/* + * Trusted Execution Environment (TEE) interface for TrustZone enabled ARM CPUs. + * + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen + * Author: Martin Hovang + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef TEE_H +#define TEE_H + +/* tee_cmd id values */ +#define TEED_OPEN_SESSION 0x00000000U +#define TEED_CLOSE_SESSION 0x00000001U +#define TEED_INVOKE 0x00000002U + +/* tee_retval id values */ +#define TEED_SUCCESS 0x00000000U +#define TEED_ERROR_GENERIC 0xFFFF0000U +#define TEED_ERROR_ACCESS_DENIED 0xFFFF0001U +#define TEED_ERROR_CANCEL 0xFFFF0002U +#define TEED_ERROR_ACCESS_CONFLICT 0xFFFF0003U +#define TEED_ERROR_EXCESS_DATA 0xFFFF0004U +#define TEED_ERROR_BAD_FORMAT 0xFFFF0005U +#define TEED_ERROR_BAD_PARAMETERS 0xFFFF0006U +#define TEED_ERROR_BAD_STATE 0xFFFF0007U +#define TEED_ERROR_ITEM_NOT_FOUND 0xFFFF0008U +#define TEED_ERROR_NOT_IMPLEMENTED 0xFFFF0009U +#define TEED_ERROR_NOT_SUPPORTED 0xFFFF000AU +#define TEED_ERROR_NO_DATA 0xFFFF000BU +#define TEED_ERROR_OUT_OF_MEMORY 0xFFFF000CU +#define TEED_ERROR_BUSY 0xFFFF000DU +#define TEED_ERROR_COMMUNICATION 0xFFFF000EU +#define TEED_ERROR_SECURITY 0xFFFF000FU +#define TEED_ERROR_SHORT_BUFFER 0xFFFF0010U + +/* TEE origin codes */ +#define TEED_ORIGIN_DRIVER 0x00000002U +#define TEED_ORIGIN_TEE 0x00000003U +#define TEED_ORIGIN_TEE_APPLICATION 0x00000004U + +#define TEE_UUID_CLOCK_SIZE 8 + +#define TEEC_CONFIG_PAYLOAD_REF_COUNT 4 + +/** + * struct tee_uuid - Structure that represent an uuid. + * @timeLow: The low field of the time stamp. + * @timeMid: The middle field of the time stamp. + * @timeHiAndVersion: The high field of the timestamp multiplexed + * with the version number. + * @clockSeqAndNode: The clock sequence and the node. + * + * This structure have different naming (camel case) to comply with Global + * Platforms TEE Client API spec. This type is defined in RFC4122. + */ +struct tee_uuid { + uint32_t timeLow; + uint16_t timeMid; + uint16_t timeHiAndVersion; + uint8_t clockSeqAndNode[TEE_UUID_CLOCK_SIZE]; +}; + +/** + * struct tee_sharedmemory - Shared memory block for TEE. + * @buffer: The in/out data to TEE. + * @size: The size of the data. + * @flags: Variable telling whether it is a in, out or in/out parameter. + */ +struct tee_sharedmemory { + void *buffer; + size_t size; + uint32_t flags; +}; + +/** + * struct tee_operation - Payload for sessions or invoke operation. + * @shm: Array containing the shared memory buffers. + * @flags: Tells which if memory buffers that are in use. + */ +struct tee_operation { + struct tee_sharedmemory shm[TEEC_CONFIG_PAYLOAD_REF_COUNT]; + uint32_t flags; +}; + +/** + * struct tee_session - The session of an open tee device. + * @state: The current state in the linux kernel. + * @err: Error code (as in Global Platform TEE Client API spec) + * @origin: Origin for the error code (also from spec). + * @id: Implementation defined type, 0 if not used. + * @ta: The trusted application. + * @uuid: The uuid for the trusted application. + * @cmd: The command to be executed in the trusted application. + * @driver_cmd: The command type in the driver. This is used from a client (user + * space to tell the Linux kernel whether it's a open-, + * close-session or if it is an invoke command. + * @ta_size: The size of the trusted application. + * @op: The payload for the trusted application. + * @sync: Mutex to handle multiple use of clients. + * + * This structure is mainly used in the Linux kernel as a session context for + * ongoing operations. Other than that it is also used in the communication with + * the user space. + */ +struct tee_session { + uint32_t state; + uint32_t err; + uint32_t origin; + uint32_t id; + void *ta; + struct tee_uuid *uuid; + unsigned int cmd; + unsigned int driver_cmd; + unsigned int ta_size; + struct tee_operation *op; + struct mutex *sync; +}; + +/** + * struct tee_read - Contains the error message and the origin. + * @err: Error code (as in Global Platform TEE Client API spec) + * @origin: Origin for the error code (also from spec). + * + * This is used by user space when a user space application wants to get more + * information about an error. + */ +struct tee_read { + unsigned int err; /* return value */ + unsigned int origin; /* error origin */ +}; + +/** + * Function that handles the function calls to trusted applications. + * @param ts: The session of a operation to be executed. + * @param sec_cmd: The type of command to be executed, open-, close-session, + * invoke command. + */ +int call_sec_world(struct tee_session *ts, int sec_cmd); + +#endif -- cgit v1.2.3 From 8dc90c992e246112da2d9d6a9684299e708605a8 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 18 Mar 2011 15:10:48 +0100 Subject: input: update bu21013 touchscreen controller staging: update Synaptics RMI4 touchpad driver Signed-off-by: Robert Marklund --- Documentation/DocBook/Makefile | 2 +- Documentation/DocBook/touchp.tmpl | 104 ++++ drivers/input/touchscreen/bu21013_ts.c | 489 +++++++++++++------ drivers/input/touchscreen/synaptics_i2c_rmi.c | 675 ++++++++++++++++++++++++++ drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c | 309 +++++++++--- include/linux/input/bu21013.h | 26 +- 6 files changed, 1373 insertions(+), 232 deletions(-) create mode 100644 Documentation/DocBook/touchp.tmpl create mode 100644 drivers/input/touchscreen/synaptics_i2c_rmi.c diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index 9733f6bcde7..f9b3b24cb8e 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,7 +15,7 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml media.xml drm.xml \ - i2s.xml msp.xml shrm.xml stmpe.xml + i2s.xml msp.xml shrm.xml stmpe.xml touchp.xml ### # The build process is as follows (targets): diff --git a/Documentation/DocBook/touchp.tmpl b/Documentation/DocBook/touchp.tmpl new file mode 100644 index 00000000000..4301b23bfc0 --- /dev/null +++ b/Documentation/DocBook/touchp.tmpl @@ -0,0 +1,104 @@ + + + + + + Touch screen ROHM BU21013MWV + + + + Naveen Kumar + Gaddipati + +
+ naveen.gaddipati@stericsson.com +
+
+
+
+ + + 2009 + ST-Ericsson + + + + + Linux standard functions + + + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + +
+ + + + + Introduction + + This documentation describes the functions provided by the driver of touch panel for BU21013 controller + + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Public Functions Provided + + Not Applicable. + + + + + Internal Functions Provided + + This chapter contains the autogenerated documentation of the internal functions of the Tocuh panel driver. + +!Idrivers/input/touchscreen/bu21013_ts.c + + +
diff --git a/drivers/input/touchscreen/bu21013_ts.c b/drivers/input/touchscreen/bu21013_ts.c index 1507ce108d5..109fe386e93 100644 --- a/drivers/input/touchscreen/bu21013_ts.c +++ b/drivers/input/touchscreen/bu21013_ts.c @@ -1,5 +1,5 @@ /* - * Copyright (C) ST-Ericsson SA 2010 + * Copyright (C) ST-Ericsson SA 2009 * Author: Naveen Kumar G for ST-Ericsson * License terms:GNU General Public License (GPL) version 2 */ @@ -12,12 +12,13 @@ #include #include #include +#include #include #define PEN_DOWN_INTR 0 -#define MAX_FINGERS 2 #define RESET_DELAY 30 -#define PENUP_TIMEOUT (10) +#define PENUP_TIMEOUT 2 /* 2msecs */ +#define SCALE_FACTOR 1000 #define DELTA_MIN 16 #define MASK_BITS 0x03 #define SHIFT_8 8 @@ -130,7 +131,7 @@ #define BU21013_NUMBER_OF_X_SENSORS (6) #define BU21013_NUMBER_OF_Y_SENSORS (11) -#define DRIVER_TP "bu21013_tp" +#define DRIVER_TP "bu21013_ts" /** * struct bu21013_ts_data - touch panel data structure @@ -141,6 +142,12 @@ * @in_dev: pointer to the input device structure * @intr_pin: interrupt pin value * @regulator: pointer to the Regulator used for touch screen + * @enable: variable to indicate the enable/disable of touch screen + * @ext_clk_enable: true if running on ext clk + * @ext_clk_state: Saved state for suspend/resume of ext clk + * @factor_x: x scale factor + * @factor_y: y scale factor + * @tpclk: pointer to clock structure * * Touch panel device data structure */ @@ -148,12 +155,216 @@ struct bu21013_ts_data { struct i2c_client *client; wait_queue_head_t wait; bool touch_stopped; - const struct bu21013_platform_device *chip; + struct bu21013_platform_device *chip; struct input_dev *in_dev; unsigned int intr_pin; struct regulator *regulator; + bool enable; + bool ext_clk_enable; + bool ext_clk_state; + unsigned int factor_x; + unsigned int factor_y; + struct clk *tpclk; }; +static int bu21013_init_chip(struct bu21013_ts_data *data, bool on_ext_clk); + +/** + * bu21013_ext_clk() - enable/disable the external clock + * @pdata: touch screen data + * @enable: enable external clock + * @reconfig: reconfigure chip upon external clock off. + * + * This function used to enable or disable the external clock and possible + * reconfigure hw. + */ +static int bu21013_ext_clk(struct bu21013_ts_data *pdata, bool enable, + bool reconfig) +{ + int retval = 0; + + if (!pdata->tpclk || pdata->ext_clk_enable == enable) + return retval; + + if (enable) { + pdata->ext_clk_enable = true; + clk_enable(pdata->tpclk); + retval = bu21013_init_chip(pdata, true); + } else { + pdata->ext_clk_enable = false; + if (reconfig) + retval = bu21013_init_chip(pdata, false); + clk_disable(pdata->tpclk); + } + return retval; +} + +/** + * bu21013_enable() - enable the touch driver event + * @pdata: touch screen data + * + * This function used to enable the driver and returns integer + */ +static int bu21013_enable(struct bu21013_ts_data *pdata) +{ + int retval; + + if (pdata->regulator) + regulator_enable(pdata->regulator); + + if (pdata->ext_clk_state) + retval = bu21013_ext_clk(pdata, true, true); + else + retval = bu21013_init_chip(pdata, false); + + if (retval < 0) { + dev_err(&pdata->client->dev, "enable hw failed\n"); + return retval; + } + pdata->touch_stopped = false; + enable_irq(pdata->chip->irq); + + return 0; +} + +/** + * bu21013_disable() - disable the touch driver event + * @pdata: touch screen data + * + * This function used to disable the driver and returns integer + */ +static void bu21013_disable(struct bu21013_ts_data *pdata) +{ + pdata->touch_stopped = true; + + pdata->ext_clk_state = pdata->ext_clk_enable; + (void) bu21013_ext_clk(pdata, false, false); + + disable_irq(pdata->chip->irq); + if (pdata->regulator) + regulator_disable(pdata->regulator); +} + +/** + * bu21013_show_attr_enable() - show the touch screen controller status + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * + * This funtion is used to show whether the touch screen is enabled or + * disabled + */ +static ssize_t bu21013_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", pdata->enable); +} + +/** + * bu21013_store_attr_enable() - Enable/Disable the touchscreen. + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * @count: number of parameters + * + * This funtion is used to enable or disable the touch screen controller. + */ +static ssize_t bu21013_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + unsigned long val; + + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (pdata->enable != val) { + pdata->enable = val ? true : false; + if (pdata->enable) { + ret = bu21013_enable(pdata); + if (ret < 0) + return ret; + } else + bu21013_disable(pdata); + } + return count; +} + +/** + * bu21013_show_attr_extclk() - shows the external clock status + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * + * This funtion is used to show whether the external clock for the touch + * screen is enabled or disabled. + */ +static ssize_t bu21013_show_attr_extclk(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", pdata->ext_clk_enable); +} + +/** + * bu21013_store_attr_extclk() - Enable/Disable the external clock + * for the tocuh screen controller. + * @dev: pointer to device structure + * @attr: pointer to device attribute + * @buf: parameter buffer + * @count: number of parameters + * + * This funtion is used enabled or disable the external clock for the touch + * screen controller. + */ +static ssize_t bu21013_store_attr_extclk(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int retval = 0; + struct bu21013_ts_data *pdata = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (pdata->chip->has_ext_clk) { + if (pdata->enable) + retval = bu21013_ext_clk(pdata, val, true); + else + pdata->ext_clk_state = val; + if (retval < 0) + return retval; + } + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + bu21013_show_attr_enable, bu21013_store_attr_enable); + +static DEVICE_ATTR(ext_clk, S_IWUSR | S_IRUGO, + bu21013_show_attr_extclk, bu21013_store_attr_extclk); + + +static struct attribute *bu21013_attribute[] = { + &dev_attr_enable.attr, + &dev_attr_ext_clk.attr, + NULL, +}; + +static struct attribute_group bu21013_attr_group = { + .attrs = bu21013_attribute, +}; + + /** * bu21013_read_block_data(): read the touch co-ordinates * @data: bu21013_ts_data structure pointer @@ -203,12 +414,14 @@ static int bu21013_do_touch_report(struct bu21013_ts_data *data) if (!has_x_sensors || !has_y_sensors) return 0; - for (i = 0; i < MAX_FINGERS; i++) { + for (i = 0; i < 2; i++) { const u8 *p = &buf[4 * i + 3]; unsigned int x = p[0] << SHIFT_2 | (p[1] & MASK_BITS); unsigned int y = p[2] << SHIFT_2 | (p[3] & MASK_BITS); if (x == 0 || y == 0) continue; + x = x * data->factor_x / SCALE_FACTOR; + y = y * data->factor_y / SCALE_FACTOR; pos_x[finger_down_count] = x; pos_y[finger_down_count] = y; finger_down_count++; @@ -216,21 +429,21 @@ static int bu21013_do_touch_report(struct bu21013_ts_data *data) if (finger_down_count) { if (finger_down_count == 2 && - (abs(pos_x[0] - pos_x[1]) < DELTA_MIN || - abs(pos_y[0] - pos_y[1]) < DELTA_MIN)) { + (abs(pos_x[0] - pos_x[1]) < DELTA_MIN || + abs(pos_y[0] - pos_y[1]) < DELTA_MIN)) return 0; - } for (i = 0; i < finger_down_count; i++) { - if (data->chip->x_flip) - pos_x[i] = data->chip->touch_x_max - pos_x[i]; - if (data->chip->y_flip) - pos_y[i] = data->chip->touch_y_max - pos_y[i]; - - input_report_abs(data->in_dev, - ABS_MT_POSITION_X, pos_x[i]); - input_report_abs(data->in_dev, - ABS_MT_POSITION_Y, pos_y[i]); + if (data->chip->portrait && data->chip->x_flip) + pos_x[i] = data->chip->x_max_res - pos_x[i]; + if (data->chip->portrait && data->chip->y_flip) + pos_y[i] = data->chip->y_max_res - pos_y[i]; + input_report_abs(data->in_dev, ABS_MT_TOUCH_MAJOR, + max(pos_x[i], pos_y[i])); + input_report_abs(data->in_dev, ABS_MT_POSITION_X, + pos_x[i]); + input_report_abs(data->in_dev, ABS_MT_POSITION_Y, + pos_y[i]); input_mt_sync(data->in_dev); } } else @@ -260,24 +473,23 @@ static irqreturn_t bu21013_gpio_irq(int irq, void *device_data) dev_err(&i2c->dev, "bu21013_do_touch_report failed\n"); return IRQ_NONE; } - data->intr_pin = data->chip->irq_read_val(); if (data->intr_pin == PEN_DOWN_INTR) wait_event_timeout(data->wait, data->touch_stopped, - msecs_to_jiffies(2)); + msecs_to_jiffies(PENUP_TIMEOUT)); } while (!data->intr_pin && !data->touch_stopped); - return IRQ_HANDLED; } /** * bu21013_init_chip() - power on sequence for the bu21013 controller * @data: device structure pointer + * @on_ext_clk: Run on external clock * * This function is used to power on * the bu21013 controller and returns integer. */ -static int bu21013_init_chip(struct bu21013_ts_data *data) +static int bu21013_init_chip(struct bu21013_ts_data *data, bool on_ext_clk) { int retval; struct i2c_client *i2c = data->client; @@ -296,28 +508,24 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) dev_err(&i2c->dev, "BU21013_SENSOR_0_7 reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_8_15_REG, BU21013_SENSORS_EN_8_15); if (retval < 0) { dev_err(&i2c->dev, "BU21013_SENSOR_8_15 reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_SENSOR_16_23_REG, BU21013_SENSORS_EN_16_23); if (retval < 0) { dev_err(&i2c->dev, "BU21013_SENSOR_16_23 reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_POS_MODE1_REG, (BU21013_POS_MODE1_0 | BU21013_POS_MODE1_1)); if (retval < 0) { dev_err(&i2c->dev, "BU21013_POS_MODE1 reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_POS_MODE2_REG, (BU21013_POS_MODE2_ZERO | BU21013_POS_MODE2_AVG1 | BU21013_POS_MODE2_AVG2 | BU21013_POS_MODE2_EN_RAW | @@ -326,8 +534,7 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) dev_err(&i2c->dev, "BU21013_POS_MODE2 reg write failed\n"); return retval; } - - if (data->chip->ext_clk) + if (on_ext_clk) retval = i2c_smbus_write_byte_data(i2c, BU21013_CLK_MODE_REG, (BU21013_CLK_MODE_EXT | BU21013_CLK_MODE_CALIB)); else @@ -337,21 +544,18 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) dev_err(&i2c->dev, "BU21013_CLK_MODE reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_IDLE_REG, (BU21013_IDLET_0 | BU21013_IDLE_INTERMIT_EN)); if (retval < 0) { dev_err(&i2c->dev, "BU21013_IDLE reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_INT_MODE_REG, BU21013_INT_MODE_LEVEL); if (retval < 0) { dev_err(&i2c->dev, "BU21013_INT_MODE reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_FILTER_REG, (BU21013_DELTA_0_6 | BU21013_FILTER_EN)); @@ -366,14 +570,12 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) dev_err(&i2c->dev, "BU21013_TH_ON reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_TH_OFF_REG, BU21013_TH_OFF_4 | BU21013_TH_OFF_3); if (retval < 0) { dev_err(&i2c->dev, "BU21013_TH_OFF reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_GAIN_REG, (BU21013_GAIN_0 | BU21013_GAIN_1)); if (retval < 0) { @@ -387,7 +589,6 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) dev_err(&i2c->dev, "BU21013_OFFSET_MODE reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_XY_EDGE_REG, (BU21013_X_EDGE_0 | BU21013_X_EDGE_2 | BU21013_Y_EDGE_1 | BU21013_Y_EDGE_3)); @@ -395,7 +596,6 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) dev_err(&i2c->dev, "BU21013_XY_EDGE reg write failed\n"); return retval; } - retval = i2c_smbus_write_byte_data(i2c, BU21013_DONE_REG, BU21013_DONE); if (retval < 0) { @@ -403,25 +603,15 @@ static int bu21013_init_chip(struct bu21013_ts_data *data) return retval; } - return 0; -} - -/** - * bu21013_free_irq() - frees IRQ registered for touchscreen - * @bu21013_data: device structure pointer - * - * This function signals interrupt thread to stop processing and - * frees interrupt. - */ -static void bu21013_free_irq(struct bu21013_ts_data *bu21013_data) -{ - bu21013_data->touch_stopped = true; - wake_up(&bu21013_data->wait); - free_irq(bu21013_data->chip->irq, bu21013_data); + data->factor_x = (data->chip->x_max_res * SCALE_FACTOR / + data->chip->touch_x_max); + data->factor_y = (data->chip->y_max_res * SCALE_FACTOR / + data->chip->touch_y_max); + return retval; } /** - * bu21013_probe() - initializes the i2c-client touchscreen driver + * bu21013_probe() - initialzes the i2c-client touchscreen driver * @client: i2c client structure pointer * @id: i2c device id pointer * @@ -431,11 +621,11 @@ static void bu21013_free_irq(struct bu21013_ts_data *bu21013_data) static int __devinit bu21013_probe(struct i2c_client *client, const struct i2c_device_id *id) { + int retval; struct bu21013_ts_data *bu21013_data; struct input_dev *in_dev; - const struct bu21013_platform_device *pdata = + struct bu21013_platform_device *pdata = client->dev.platform_data; - int error; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { @@ -445,53 +635,72 @@ static int __devinit bu21013_probe(struct i2c_client *client, if (!pdata) { dev_err(&client->dev, "platform data not defined\n"); - return -EINVAL; + retval = -EINVAL; + return retval; } bu21013_data = kzalloc(sizeof(struct bu21013_ts_data), GFP_KERNEL); - in_dev = input_allocate_device(); - if (!bu21013_data || !in_dev) { + if (!bu21013_data) { dev_err(&client->dev, "device memory alloc failed\n"); - error = -ENOMEM; - goto err_free_mem; + retval = -ENOMEM; + return retval; + } + /* allocate input device */ + in_dev = input_allocate_device(); + if (!in_dev) { + dev_err(&client->dev, "input device memory alloc failed\n"); + retval = -ENOMEM; + goto err_alloc; } bu21013_data->in_dev = in_dev; bu21013_data->chip = pdata; bu21013_data->client = client; - bu21013_data->regulator = regulator_get(&client->dev, "V-TOUCH"); + bu21013_data->regulator = regulator_get(&client->dev, "avdd"); if (IS_ERR(bu21013_data->regulator)) { - dev_err(&client->dev, "regulator_get failed\n"); - error = PTR_ERR(bu21013_data->regulator); - goto err_free_mem; + dev_warn(&client->dev, "regulator_get failed\n"); + bu21013_data->regulator = NULL; } - - error = regulator_enable(bu21013_data->regulator); - if (error < 0) { - dev_err(&client->dev, "regulator enable failed\n"); - goto err_put_regulator; - } - - bu21013_data->touch_stopped = false; - init_waitqueue_head(&bu21013_data->wait); + if (bu21013_data->regulator) + regulator_enable(bu21013_data->regulator); /* configure the gpio pins */ if (pdata->cs_en) { - error = pdata->cs_en(pdata->cs_pin); - if (error < 0) { + retval = pdata->cs_en(pdata->cs_pin); + if (retval < 0) { dev_err(&client->dev, "chip init failed\n"); - goto err_disable_regulator; + goto err_init_cs; + } + } + + if (pdata->has_ext_clk) { + bu21013_data->tpclk = clk_get(&client->dev, NULL); + if (IS_ERR(bu21013_data->tpclk)) { + dev_warn(&client->dev, "get extern clock failed\n"); + bu21013_data->tpclk = NULL; + } + } + + if (pdata->enable_ext_clk && bu21013_data->tpclk) { + retval = clk_enable(bu21013_data->tpclk); + if (retval < 0) { + dev_err(&client->dev, "clock enable failed\n"); + goto err_ext_clk; } + bu21013_data->ext_clk_enable = true; } /* configure the touch panel controller */ - error = bu21013_init_chip(bu21013_data); - if (error) { + retval = bu21013_init_chip(bu21013_data, bu21013_data->ext_clk_enable); + if (retval < 0) { dev_err(&client->dev, "error in bu21013 config\n"); - goto err_cs_disable; + goto err_init_config; } + init_waitqueue_head(&bu21013_data->wait); + bu21013_data->touch_stopped = false; + /* register the device to input subsystem */ in_dev->name = DRIVER_TP; in_dev->id.bustype = BUS_I2C; @@ -502,44 +711,63 @@ static int __devinit bu21013_probe(struct i2c_client *client, __set_bit(EV_ABS, in_dev->evbit); input_set_abs_params(in_dev, ABS_MT_POSITION_X, 0, - pdata->touch_x_max, 0, 0); + pdata->x_max_res, 0, 0); input_set_abs_params(in_dev, ABS_MT_POSITION_Y, 0, - pdata->touch_y_max, 0, 0); + pdata->y_max_res, 0, 0); + input_set_abs_params(in_dev, ABS_MT_TOUCH_MAJOR, 0, + max(pdata->x_max_res , pdata->y_max_res), 0, 0); input_set_drvdata(in_dev, bu21013_data); - - error = request_threaded_irq(pdata->irq, NULL, bu21013_gpio_irq, - IRQF_TRIGGER_FALLING | IRQF_SHARED, - DRIVER_TP, bu21013_data); - if (error) { + retval = input_register_device(in_dev); + if (retval) + goto err_input_register; + + retval = request_threaded_irq(pdata->irq, NULL, bu21013_gpio_irq, + (IRQF_TRIGGER_FALLING | IRQF_SHARED), + DRIVER_TP, bu21013_data); + if (retval) { dev_err(&client->dev, "request irq %d failed\n", pdata->irq); - goto err_cs_disable; + goto err_init_irq; } + bu21013_data->enable = true; + i2c_set_clientdata(client, bu21013_data); - error = input_register_device(in_dev); - if (error) { - dev_err(&client->dev, "failed to register input device\n"); - goto err_free_irq; + /* sysfs implementation for dynamic enable/disable the input event */ + retval = sysfs_create_group(&client->dev.kobj, &bu21013_attr_group); + if (retval) { + dev_err(&client->dev, "failed to create sysfs entries\n"); + goto err_sysfs_create; } - device_init_wakeup(&client->dev, pdata->wakeup); - i2c_set_clientdata(client, bu21013_data); - - return 0; + return retval; -err_free_irq: - bu21013_free_irq(bu21013_data); -err_cs_disable: - pdata->cs_dis(pdata->cs_pin); -err_disable_regulator: - regulator_disable(bu21013_data->regulator); -err_put_regulator: - regulator_put(bu21013_data->regulator); -err_free_mem: - input_free_device(in_dev); +err_sysfs_create: + free_irq(pdata->irq, bu21013_data); + i2c_set_clientdata(client, NULL); +err_init_irq: + input_unregister_device(bu21013_data->in_dev); +err_input_register: + wake_up(&bu21013_data->wait); +err_init_config: + if (bu21013_data->tpclk) { + if (bu21013_data->ext_clk_enable) + clk_disable(bu21013_data->tpclk); + clk_put(bu21013_data->tpclk); + } +err_ext_clk: + if (pdata->cs_dis) + pdata->cs_dis(pdata->cs_pin); +err_init_cs: + if (bu21013_data->regulator) { + regulator_disable(bu21013_data->regulator); + regulator_put(bu21013_data->regulator); + } + input_free_device(bu21013_data->in_dev); +err_alloc: kfree(bu21013_data); - return error; + return retval; } + /** * bu21013_remove() - removes the i2c-client touchscreen driver * @client: i2c client structure pointer @@ -551,19 +779,24 @@ static int __devexit bu21013_remove(struct i2c_client *client) { struct bu21013_ts_data *bu21013_data = i2c_get_clientdata(client); - bu21013_free_irq(bu21013_data); - + bu21013_data->touch_stopped = true; + sysfs_remove_group(&client->dev.kobj, &bu21013_attr_group); + wake_up(&bu21013_data->wait); + free_irq(bu21013_data->chip->irq, bu21013_data); bu21013_data->chip->cs_dis(bu21013_data->chip->cs_pin); - input_unregister_device(bu21013_data->in_dev); - regulator_disable(bu21013_data->regulator); - regulator_put(bu21013_data->regulator); - + if (bu21013_data->tpclk) { + if (bu21013_data->ext_clk_enable) + clk_disable(bu21013_data->tpclk); + clk_put(bu21013_data->tpclk); + } + if (bu21013_data->regulator) { + regulator_disable(bu21013_data->regulator); + regulator_put(bu21013_data->regulator); + } kfree(bu21013_data); - device_init_wakeup(&client->dev, false); - return 0; } @@ -578,15 +811,8 @@ static int __devexit bu21013_remove(struct i2c_client *client) static int bu21013_suspend(struct device *dev) { struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); - struct i2c_client *client = bu21013_data->client; - bu21013_data->touch_stopped = true; - if (device_may_wakeup(&client->dev)) - enable_irq_wake(bu21013_data->chip->irq); - else - disable_irq(bu21013_data->chip->irq); - - regulator_disable(bu21013_data->regulator); + bu21013_disable(bu21013_data); return 0; } @@ -601,29 +827,8 @@ static int bu21013_suspend(struct device *dev) static int bu21013_resume(struct device *dev) { struct bu21013_ts_data *bu21013_data = dev_get_drvdata(dev); - struct i2c_client *client = bu21013_data->client; - int retval; - retval = regulator_enable(bu21013_data->regulator); - if (retval < 0) { - dev_err(&client->dev, "bu21013 regulator enable failed\n"); - return retval; - } - - retval = bu21013_init_chip(bu21013_data); - if (retval < 0) { - dev_err(&client->dev, "bu21013 controller config failed\n"); - return retval; - } - - bu21013_data->touch_stopped = false; - - if (device_may_wakeup(&client->dev)) - disable_irq_wake(bu21013_data->chip->irq); - else - enable_irq(bu21013_data->chip->irq); - - return 0; + return bu21013_enable(bu21013_data); } static const struct dev_pm_ops bu21013_dev_pm_ops = { diff --git a/drivers/input/touchscreen/synaptics_i2c_rmi.c b/drivers/input/touchscreen/synaptics_i2c_rmi.c new file mode 100644 index 00000000000..5729602cbb6 --- /dev/null +++ b/drivers/input/touchscreen/synaptics_i2c_rmi.c @@ -0,0 +1,675 @@ +/* drivers/input/keyboard/synaptics_i2c_rmi.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct workqueue_struct *synaptics_wq; + +struct synaptics_ts_data { + uint16_t addr; + struct i2c_client *client; + struct input_dev *input_dev; + int use_irq; + bool has_relative_report; + struct hrtimer timer; + struct work_struct work; + uint16_t max[2]; + int snap_state[2][2]; + int snap_down_on[2]; + int snap_down_off[2]; + int snap_up_on[2]; + int snap_up_off[2]; + int snap_down[2]; + int snap_up[2]; + uint32_t flags; + int reported_finger_count; + int8_t sensitivity_adjust; + int (*power)(int on); + struct early_suspend early_suspend; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void synaptics_ts_early_suspend(struct early_suspend *h); +static void synaptics_ts_late_resume(struct early_suspend *h); +#endif + +static int synaptics_init_panel(struct synaptics_ts_data *ts) +{ + int ret; + + ret = i2c_smbus_write_byte_data(ts->client, 0xff, 0x10); /* page select = 0x10 */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed for page select\n"); + goto err_page_select_failed; + } + ret = i2c_smbus_write_byte_data(ts->client, 0x41, 0x04); /* Set "No Clip Z" */ + if (ret < 0) + printk(KERN_ERR "i2c_smbus_write_byte_data failed for No Clip Z\n"); + + ret = i2c_smbus_write_byte_data(ts->client, 0x44, + ts->sensitivity_adjust); + if (ret < 0) + pr_err("synaptics_ts: failed to set Sensitivity Adjust\n"); + +err_page_select_failed: + ret = i2c_smbus_write_byte_data(ts->client, 0xff, 0x04); /* page select = 0x04 */ + if (ret < 0) + printk(KERN_ERR "i2c_smbus_write_byte_data failed for page select\n"); + ret = i2c_smbus_write_byte_data(ts->client, 0xf0, 0x81); /* normal operation, 80 reports per second */ + if (ret < 0) + printk(KERN_ERR "synaptics_ts_resume: i2c_smbus_write_byte_data failed\n"); + return ret; +} + +static void synaptics_ts_work_func(struct work_struct *work) +{ + int i; + int ret; + int bad_data = 0; + struct i2c_msg msg[2]; + uint8_t start_reg; + uint8_t buf[15]; + struct synaptics_ts_data *ts = container_of(work, struct synaptics_ts_data, work); + int buf_len = ts->has_relative_report ? 15 : 13; + + msg[0].addr = ts->client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = &start_reg; + start_reg = 0x00; + msg[1].addr = ts->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = buf_len; + msg[1].buf = buf; + + /* printk("synaptics_ts_work_func\n"); */ + for (i = 0; i < ((ts->use_irq && !bad_data) ? 1 : 10); i++) { + ret = i2c_transfer(ts->client->adapter, msg, 2); + if (ret < 0) { + printk(KERN_ERR "synaptics_ts_work_func: i2c_transfer failed\n"); + bad_data = 1; + } else { + /* printk("synaptics_ts_work_func: %x %x %x %x %x %x" */ + /* " %x %x %x %x %x %x %x %x %x, ret %d\n", */ + /* buf[0], buf[1], buf[2], buf[3], */ + /* buf[4], buf[5], buf[6], buf[7], */ + /* buf[8], buf[9], buf[10], buf[11], */ + /* buf[12], buf[13], buf[14], ret); */ + if ((buf[buf_len - 1] & 0xc0) != 0x40) { + printk(KERN_WARNING "synaptics_ts_work_func:" + " bad read %x %x %x %x %x %x %x %x %x" + " %x %x %x %x %x %x, ret %d\n", + buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], + buf[8], buf[9], buf[10], buf[11], + buf[12], buf[13], buf[14], ret); + if (bad_data) + synaptics_init_panel(ts); + bad_data = 1; + continue; + } + bad_data = 0; + if ((buf[buf_len - 1] & 1) == 0) { + /* printk("read %d coordinates\n", i); */ + break; + } else { + int pos[2][2]; + int f, a; + int base; + /* int x = buf[3] | (uint16_t)(buf[2] & 0x1f) << 8; */ + /* int y = buf[5] | (uint16_t)(buf[4] & 0x1f) << 8; */ + int z = buf[1]; + int w = buf[0] >> 4; + int finger = buf[0] & 7; + + /* int x2 = buf[3+6] | (uint16_t)(buf[2+6] & 0x1f) << 8; */ + /* int y2 = buf[5+6] | (uint16_t)(buf[4+6] & 0x1f) << 8; */ + /* int z2 = buf[1+6]; */ + /* int w2 = buf[0+6] >> 4; */ + /* int finger2 = buf[0+6] & 7; */ + + /* int dx = (int8_t)buf[12]; */ + /* int dy = (int8_t)buf[13]; */ + int finger2_pressed; + + /* printk("x %4d, y %4d, z %3d, w %2d, F %d, 2nd: x %4d, y %4d, z %3d, w %2d, F %d, dx %4d, dy %4d\n", */ + /* x, y, z, w, finger, */ + /* x2, y2, z2, w2, finger2, */ + /* dx, dy); */ + + base = 2; + for (f = 0; f < 2; f++) { + uint32_t flip_flag = SYNAPTICS_FLIP_X; + for (a = 0; a < 2; a++) { + int p = buf[base + 1]; + p |= (uint16_t)(buf[base] & 0x1f) << 8; + if (ts->flags & flip_flag) + p = ts->max[a] - p; + if (ts->flags & SYNAPTICS_SNAP_TO_INACTIVE_EDGE) { + if (ts->snap_state[f][a]) { + if (p <= ts->snap_down_off[a]) + p = ts->snap_down[a]; + else if (p >= ts->snap_up_off[a]) + p = ts->snap_up[a]; + else + ts->snap_state[f][a] = 0; + } else { + if (p <= ts->snap_down_on[a]) { + p = ts->snap_down[a]; + ts->snap_state[f][a] = 1; + } else if (p >= ts->snap_up_on[a]) { + p = ts->snap_up[a]; + ts->snap_state[f][a] = 1; + } + } + } + pos[f][a] = p; + base += 2; + flip_flag <<= 1; + } + base += 2; + if (ts->flags & SYNAPTICS_SWAP_XY) + swap(pos[f][0], pos[f][1]); + } + if (z) { + input_report_abs(ts->input_dev, ABS_X, pos[0][0]); + input_report_abs(ts->input_dev, ABS_Y, pos[0][1]); + } + input_report_abs(ts->input_dev, ABS_PRESSURE, z); + input_report_abs(ts->input_dev, ABS_TOOL_WIDTH, w); + input_report_key(ts->input_dev, BTN_TOUCH, finger); + finger2_pressed = finger > 1 && finger != 7; + input_report_key(ts->input_dev, BTN_2, finger2_pressed); + if (finger2_pressed) { + input_report_abs(ts->input_dev, ABS_HAT0X, pos[1][0]); + input_report_abs(ts->input_dev, ABS_HAT0Y, pos[1][1]); + } + + if (!finger) + z = 0; + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, z); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, pos[0][0]); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, pos[0][1]); + input_mt_sync(ts->input_dev); + if (finger2_pressed) { + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, z); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, pos[1][0]); + input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, pos[1][1]); + input_mt_sync(ts->input_dev); + } else if (ts->reported_finger_count > 1) { + input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0); + input_mt_sync(ts->input_dev); + } + ts->reported_finger_count = finger; + input_sync(ts->input_dev); + } + } + } + if (ts->use_irq) + enable_irq(ts->client->irq); +} + +static enum hrtimer_restart synaptics_ts_timer_func(struct hrtimer *timer) +{ + struct synaptics_ts_data *ts = container_of(timer, struct synaptics_ts_data, timer); + /* printk("synaptics_ts_timer_func\n"); */ + + queue_work(synaptics_wq, &ts->work); + + hrtimer_start(&ts->timer, ktime_set(0, 12500000), HRTIMER_MODE_REL); + return HRTIMER_NORESTART; +} + +static irqreturn_t synaptics_ts_irq_handler(int irq, void *dev_id) +{ + struct synaptics_ts_data *ts = dev_id; + + /* printk("synaptics_ts_irq_handler\n"); */ + disable_irq_nosync(ts->client->irq); + queue_work(synaptics_wq, &ts->work); + return IRQ_HANDLED; +} + +static int synaptics_ts_probe( + struct i2c_client *client, const struct i2c_device_id *id) +{ + struct synaptics_ts_data *ts; + uint8_t buf0[4]; + uint8_t buf1[8]; + struct i2c_msg msg[2]; + int ret = 0; + uint16_t max_x, max_y; + int fuzz_x, fuzz_y, fuzz_p, fuzz_w; + struct synaptics_i2c_rmi_platform_data *pdata; + unsigned long irqflags; + int inactive_area_left; + int inactive_area_right; + int inactive_area_top; + int inactive_area_bottom; + int snap_left_on; + int snap_left_off; + int snap_right_on; + int snap_right_off; + int snap_top_on; + int snap_top_off; + int snap_bottom_on; + int snap_bottom_off; + uint32_t panel_version; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + printk(KERN_ERR "synaptics_ts_probe: need I2C_FUNC_I2C\n"); + ret = -ENODEV; + goto err_check_functionality_failed; + } + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (ts == NULL) { + ret = -ENOMEM; + goto err_alloc_data_failed; + } + INIT_WORK(&ts->work, synaptics_ts_work_func); + ts->client = client; + i2c_set_clientdata(client, ts); + pdata = client->dev.platform_data; + if (pdata) + ts->power = pdata->power; + if (ts->power) { + ret = ts->power(1); + if (ret < 0) { + printk(KERN_ERR "synaptics_ts_probe power on failed\n"); + goto err_power_failed; + } + } + + ret = i2c_smbus_write_byte_data(ts->client, 0xf4, 0x01); /* device command = reset */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed\n"); + /* fail? */ + } + { + int retry = 10; + while (retry-- > 0) { + ret = i2c_smbus_read_byte_data(ts->client, 0xe4); + if (ret >= 0) + break; + msleep(100); + } + } + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: Product Major Version %x\n", ret); + panel_version = ret << 8; + ret = i2c_smbus_read_byte_data(ts->client, 0xe5); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: Product Minor Version %x\n", ret); + panel_version |= ret; + + ret = i2c_smbus_read_byte_data(ts->client, 0xe3); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: product property %x\n", ret); + + if (pdata) { + while (pdata->version > panel_version) + pdata++; + ts->flags = pdata->flags; + ts->sensitivity_adjust = pdata->sensitivity_adjust; + irqflags = pdata->irqflags; + inactive_area_left = pdata->inactive_left; + inactive_area_right = pdata->inactive_right; + inactive_area_top = pdata->inactive_top; + inactive_area_bottom = pdata->inactive_bottom; + snap_left_on = pdata->snap_left_on; + snap_left_off = pdata->snap_left_off; + snap_right_on = pdata->snap_right_on; + snap_right_off = pdata->snap_right_off; + snap_top_on = pdata->snap_top_on; + snap_top_off = pdata->snap_top_off; + snap_bottom_on = pdata->snap_bottom_on; + snap_bottom_off = pdata->snap_bottom_off; + fuzz_x = pdata->fuzz_x; + fuzz_y = pdata->fuzz_y; + fuzz_p = pdata->fuzz_p; + fuzz_w = pdata->fuzz_w; + } else { + irqflags = 0; + inactive_area_left = 0; + inactive_area_right = 0; + inactive_area_top = 0; + inactive_area_bottom = 0; + snap_left_on = 0; + snap_left_off = 0; + snap_right_on = 0; + snap_right_off = 0; + snap_top_on = 0; + snap_top_off = 0; + snap_bottom_on = 0; + snap_bottom_off = 0; + fuzz_x = 0; + fuzz_y = 0; + fuzz_p = 0; + fuzz_w = 0; + } + + ret = i2c_smbus_read_byte_data(ts->client, 0xf0); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: device control %x\n", ret); + + ret = i2c_smbus_read_byte_data(ts->client, 0xf1); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_byte_data failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: interrupt enable %x\n", ret); + + ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0); /* disable interrupt */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed\n"); + goto err_detect_failed; + } + + msg[0].addr = ts->client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = buf0; + buf0[0] = 0xe0; + msg[1].addr = ts->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 8; + msg[1].buf = buf1; + ret = i2c_transfer(ts->client->adapter, msg, 2); + if (ret < 0) { + printk(KERN_ERR "i2c_transfer failed\n"); + goto err_detect_failed; + } + printk(KERN_INFO "synaptics_ts_probe: 0xe0: %x %x %x %x %x %x %x %x\n", + buf1[0], buf1[1], buf1[2], buf1[3], + buf1[4], buf1[5], buf1[6], buf1[7]); + + ret = i2c_smbus_write_byte_data(ts->client, 0xff, 0x10); /* page select = 0x10 */ + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_write_byte_data failed for page select\n"); + goto err_detect_failed; + } + ret = i2c_smbus_read_word_data(ts->client, 0x02); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_word_data failed\n"); + goto err_detect_failed; + } + ts->has_relative_report = !(ret & 0x100); + printk(KERN_INFO "synaptics_ts_probe: Sensor properties %x\n", ret); + ret = i2c_smbus_read_word_data(ts->client, 0x04); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_word_data failed\n"); + goto err_detect_failed; + } + ts->max[0] = max_x = (ret >> 8 & 0xff) | ((ret & 0x1f) << 8); + ret = i2c_smbus_read_word_data(ts->client, 0x06); + if (ret < 0) { + printk(KERN_ERR "i2c_smbus_read_word_data failed\n"); + goto err_detect_failed; + } + ts->max[1] = max_y = (ret >> 8 & 0xff) | ((ret & 0x1f) << 8); + if (ts->flags & SYNAPTICS_SWAP_XY) + swap(max_x, max_y); + + ret = synaptics_init_panel(ts); /* will also switch back to page 0x04 */ + if (ret < 0) { + printk(KERN_ERR "synaptics_init_panel failed\n"); + goto err_detect_failed; + } + + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + ret = -ENOMEM; + printk(KERN_ERR "synaptics_ts_probe: Failed to allocate input device\n"); + goto err_input_dev_alloc_failed; + } + ts->input_dev->name = "synaptics-rmi-touchscreen"; + set_bit(EV_SYN, ts->input_dev->evbit); + set_bit(EV_KEY, ts->input_dev->evbit); + set_bit(BTN_TOUCH, ts->input_dev->keybit); + set_bit(BTN_2, ts->input_dev->keybit); + set_bit(EV_ABS, ts->input_dev->evbit); + inactive_area_left = inactive_area_left * max_x / 0x10000; + inactive_area_right = inactive_area_right * max_x / 0x10000; + inactive_area_top = inactive_area_top * max_y / 0x10000; + inactive_area_bottom = inactive_area_bottom * max_y / 0x10000; + snap_left_on = snap_left_on * max_x / 0x10000; + snap_left_off = snap_left_off * max_x / 0x10000; + snap_right_on = snap_right_on * max_x / 0x10000; + snap_right_off = snap_right_off * max_x / 0x10000; + snap_top_on = snap_top_on * max_y / 0x10000; + snap_top_off = snap_top_off * max_y / 0x10000; + snap_bottom_on = snap_bottom_on * max_y / 0x10000; + snap_bottom_off = snap_bottom_off * max_y / 0x10000; + fuzz_x = fuzz_x * max_x / 0x10000; + fuzz_y = fuzz_y * max_y / 0x10000; + ts->snap_down[!!(ts->flags & SYNAPTICS_SWAP_XY)] = -inactive_area_left; + ts->snap_up[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x + inactive_area_right; + ts->snap_down[!(ts->flags & SYNAPTICS_SWAP_XY)] = -inactive_area_top; + ts->snap_up[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y + inactive_area_bottom; + ts->snap_down_on[!!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_left_on; + ts->snap_down_off[!!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_left_off; + ts->snap_up_on[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x - snap_right_on; + ts->snap_up_off[!!(ts->flags & SYNAPTICS_SWAP_XY)] = max_x - snap_right_off; + ts->snap_down_on[!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_top_on; + ts->snap_down_off[!(ts->flags & SYNAPTICS_SWAP_XY)] = snap_top_off; + ts->snap_up_on[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y - snap_bottom_on; + ts->snap_up_off[!(ts->flags & SYNAPTICS_SWAP_XY)] = max_y - snap_bottom_off; + printk(KERN_INFO "synaptics_ts_probe: max_x %d, max_y %d\n", max_x, max_y); + printk(KERN_INFO "synaptics_ts_probe: inactive_x %d %d, inactive_y %d %d\n", + inactive_area_left, inactive_area_right, + inactive_area_top, inactive_area_bottom); + printk(KERN_INFO "synaptics_ts_probe: snap_x %d-%d %d-%d, snap_y %d-%d %d-%d\n", + snap_left_on, snap_left_off, snap_right_on, snap_right_off, + snap_top_on, snap_top_off, snap_bottom_on, snap_bottom_off); + input_set_abs_params(ts->input_dev, ABS_X, -inactive_area_left, max_x + inactive_area_right, fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_Y, -inactive_area_top, max_y + inactive_area_bottom, fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, fuzz_p, 0); + input_set_abs_params(ts->input_dev, ABS_TOOL_WIDTH, 0, 15, fuzz_w, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0X, -inactive_area_left, max_x + inactive_area_right, fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0Y, -inactive_area_top, max_y + inactive_area_bottom, fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, -inactive_area_left, max_x + inactive_area_right, fuzz_x, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, -inactive_area_top, max_y + inactive_area_bottom, fuzz_y, 0); + input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, fuzz_p, 0); + input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, fuzz_w, 0); + /* ts->input_dev->name = ts->keypad_info->name; */ + ret = input_register_device(ts->input_dev); + if (ret) { + printk(KERN_ERR "synaptics_ts_probe: Unable to register %s input device\n", ts->input_dev->name); + goto err_input_register_device_failed; + } + if (client->irq) { + ret = request_irq(client->irq, synaptics_ts_irq_handler, irqflags, client->name, ts); + if (ret == 0) { + ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0x01); /* enable abs int */ + if (ret) + free_irq(client->irq, ts); + } + if (ret == 0) + ts->use_irq = 1; + else + dev_err(&client->dev, "request_irq failed\n"); + } + if (!ts->use_irq) { + hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ts->timer.function = synaptics_ts_timer_func; + hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); + } +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ts->early_suspend.suspend = synaptics_ts_early_suspend; + ts->early_suspend.resume = synaptics_ts_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + + printk(KERN_INFO "synaptics_ts_probe: Start touchscreen %s in %s mode\n", ts->input_dev->name, ts->use_irq ? "interrupt" : "polling"); + + return 0; + +err_input_register_device_failed: + input_free_device(ts->input_dev); + +err_input_dev_alloc_failed: +err_detect_failed: +err_power_failed: + kfree(ts); +err_alloc_data_failed: +err_check_functionality_failed: + return ret; +} + +static int synaptics_ts_remove(struct i2c_client *client) +{ + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + unregister_early_suspend(&ts->early_suspend); + if (ts->use_irq) + free_irq(client->irq, ts); + else + hrtimer_cancel(&ts->timer); + input_unregister_device(ts->input_dev); + kfree(ts); + return 0; +} + +static int synaptics_ts_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int ret; + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + + if (ts->use_irq) + disable_irq(client->irq); + else + hrtimer_cancel(&ts->timer); + ret = cancel_work_sync(&ts->work); + if (ret && ts->use_irq) /* if work was pending disable-count is now 2 */ + enable_irq(client->irq); + ret = i2c_smbus_write_byte_data(ts->client, 0xf1, 0); /* disable interrupt */ + if (ret < 0) + printk(KERN_ERR "synaptics_ts_suspend: i2c_smbus_write_byte_data failed\n"); + + ret = i2c_smbus_write_byte_data(client, 0xf0, 0x86); /* deep sleep */ + if (ret < 0) + printk(KERN_ERR "synaptics_ts_suspend: i2c_smbus_write_byte_data failed\n"); + if (ts->power) { + ret = ts->power(0); + if (ret < 0) + printk(KERN_ERR "synaptics_ts_resume power off failed\n"); + } + return 0; +} + +static int synaptics_ts_resume(struct i2c_client *client) +{ + int ret; + struct synaptics_ts_data *ts = i2c_get_clientdata(client); + + if (ts->power) { + ret = ts->power(1); + if (ret < 0) + printk(KERN_ERR "synaptics_ts_resume power on failed\n"); + } + + synaptics_init_panel(ts); + + if (ts->use_irq) + enable_irq(client->irq); + + if (!ts->use_irq) + hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); + else + i2c_smbus_write_byte_data(ts->client, 0xf1, 0x01); /* enable abs int */ + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void synaptics_ts_early_suspend(struct early_suspend *h) +{ + struct synaptics_ts_data *ts; + ts = container_of(h, struct synaptics_ts_data, early_suspend); + synaptics_ts_suspend(ts->client, PMSG_SUSPEND); +} + +static void synaptics_ts_late_resume(struct early_suspend *h) +{ + struct synaptics_ts_data *ts; + ts = container_of(h, struct synaptics_ts_data, early_suspend); + synaptics_ts_resume(ts->client); +} +#endif + +static const struct i2c_device_id synaptics_ts_id[] = { + { SYNAPTICS_I2C_RMI_NAME, 0 }, + { } +}; + +static struct i2c_driver synaptics_ts_driver = { + .probe = synaptics_ts_probe, + .remove = synaptics_ts_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = synaptics_ts_suspend, + .resume = synaptics_ts_resume, +#endif + .id_table = synaptics_ts_id, + .driver = { + .name = SYNAPTICS_I2C_RMI_NAME, + }, +}; + +static int __devinit synaptics_ts_init(void) +{ + synaptics_wq = create_singlethread_workqueue("synaptics_wq"); + if (!synaptics_wq) + return -ENOMEM; + return i2c_add_driver(&synaptics_ts_driver); +} + +static void __exit synaptics_ts_exit(void) +{ + i2c_del_driver(&synaptics_ts_driver); + if (synaptics_wq) + destroy_workqueue(synaptics_wq); +} + +module_init(synaptics_ts_init); +module_exit(synaptics_ts_exit); + +MODULE_DESCRIPTION("Synaptics Touchscreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c index 36f4cb77567..211a1cd0c3a 100644 --- a/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c +++ b/drivers/staging/ste_rmi4/synaptics_i2c_rmi4.c @@ -5,7 +5,7 @@ * * Author: Js HA for ST-Ericsson * Author: Naveen Kumar G for ST-Ericsson - * Copyright 2010 (c) ST-Ericsson AB + * Copyright 2010 (c) ST-Ericsson SA */ /* * This file is licensed under the GPL2 license. @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -35,8 +36,10 @@ /* TODO: for multiple device support will need a per-device mutex */ #define DRIVER_NAME "synaptics_rmi4_i2c" +#define DELTA 8 #define MAX_ERROR_REPORT 6 -#define MAX_TOUCH_MAJOR 15 +#define TIMEOUT_PERIOD 1 +#define MAX_WIDTH_MAJOR 255 #define MAX_RETRY_COUNT 5 #define STD_QUERY_LEN 21 #define PAGE_LEN 2 @@ -44,6 +47,7 @@ #define BUF_LEN 37 #define QUERY_LEN 9 #define DATA_LEN 12 +#define RESUME_DELAY 100 /* msecs */ #define HAS_TAP 0x01 #define HAS_PALMDETECT 0x01 #define HAS_ROTATE 0x02 @@ -163,6 +167,8 @@ struct synaptics_rmi4_device_info { * @regulator: pointer to the regulator structure * @wait: wait queue structure variable * @touch_stopped: flag to stop the thread function + * @enable: flag to enable/disable the driver event. + * @resume_wq_handler: work queue for resume the device * * This structure gives the device data information. */ @@ -183,6 +189,8 @@ struct synaptics_rmi4_data { struct regulator *regulator; wait_queue_head_t wait; bool touch_stopped; + bool enable; + struct work_struct resume_wq_handler; }; /** @@ -289,6 +297,133 @@ exit: return retval; } +/** + * synaptics_rmi4_enable() - enable the touchpad driver event + * @pdata: pointer to synaptics_rmi4_data structure + * + * This function is to enable the touchpad driver event and returns integer. + */ +static int synaptics_rmi4_enable(struct synaptics_rmi4_data *pdata) +{ + int retval; + unsigned char intr_status; + + if (pdata->board->regulator_en) + regulator_enable(pdata->regulator); + enable_irq(pdata->board->irq_number); + pdata->touch_stopped = false; + + msleep(RESUME_DELAY); + retval = synaptics_rmi4_i2c_block_read(pdata, + pdata->fn01_data_base_addr + 1, + &intr_status, + pdata->number_of_interrupt_register); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_i2c_byte_write(pdata, + pdata->fn01_ctrl_base_addr + 1, + (intr_status | TOUCHPAD_CTRL_INTR)); + if (retval < 0) + return retval; + + return 0; +} + +/** + * synaptics_rmi4_disable() - disable the touchpad driver event + * @pdata: pointer to synaptics_rmi4_data structure + * + * This function is to disable the driver event and returns integer. + */ + +static int synaptics_rmi4_disable(struct synaptics_rmi4_data *pdata) +{ + int retval; + unsigned char intr_status; + + pdata->touch_stopped = true; + disable_irq(pdata->board->irq_number); + + retval = synaptics_rmi4_i2c_block_read(pdata, + pdata->fn01_data_base_addr + 1, + &intr_status, + pdata->number_of_interrupt_register); + if (retval < 0) + return retval; + + retval = synaptics_rmi4_i2c_byte_write(pdata, + pdata->fn01_ctrl_base_addr + 1, + (intr_status & ~TOUCHPAD_CTRL_INTR)); + if (retval < 0) + return retval; + if (pdata->board->regulator_en) + regulator_disable(pdata->regulator); + + return 0; +} + +/** + * synaptics_rmi4_show_attr_enable() - show the touchpad enable value + * @dev: pointer to device data structure + * @attr: pointer to attribute structure + * @buf: pointer to character buffer + * + * This function is to show the touchpad enable value and returns ssize_t. + */ +static ssize_t synaptics_rmi4_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct synaptics_rmi4_data *pdata = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", pdata->enable); +} + +/** + * synaptics_rmi4_store_attr_enable() - store the touchpad enable value + * @dev: pointer to device data structure + * @attr: pointer to attribute structure + * @buf: pointer to character buffer + * @count: number fo arguments + * + * This function is to store the touchpad enable value and returns ssize_t. + */ +static ssize_t synaptics_rmi4_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct synaptics_rmi4_data *pdata = dev_get_drvdata(dev); + unsigned long val; + int retval = 0; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (pdata->enable != val) { + pdata->enable = val ? true : false; + if (pdata->enable) + retval = synaptics_rmi4_enable(pdata); + else + retval = synaptics_rmi4_disable(pdata); + + } + return ((retval < 0) ? retval : count); +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + synaptics_rmi4_show_attr_enable, synaptics_rmi4_store_attr_enable); + +static struct attribute *synaptics_rmi4_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group synaptics_rmi4_attr_group = { + .attrs = synaptics_rmi4_attrs, +}; + /** * synpatics_rmi4_touchpad_report() - reports for the rmi4 touchpad device * @pdata: pointer to synaptics_rmi4_data structure @@ -315,8 +450,9 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, unsigned char data[DATA_LEN]; int x[RMI4_NUMBER_OF_MAX_FINGERS]; int y[RMI4_NUMBER_OF_MAX_FINGERS]; - int wx[RMI4_NUMBER_OF_MAX_FINGERS]; - int wy[RMI4_NUMBER_OF_MAX_FINGERS]; + int w[RMI4_NUMBER_OF_MAX_FINGERS]; + static int prv_x[RMI4_NUMBER_OF_MAX_FINGERS]; + static int prv_y[RMI4_NUMBER_OF_MAX_FINGERS]; struct i2c_client *client = pdata->i2c_client; /* get 2D sensor finger data */ @@ -375,11 +511,7 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, y[touch_count] = (data[1] << 4) | ((data[2] >> 4) & MASK_4BIT); - wy[touch_count] = - (data[3] >> 4) & MASK_4BIT; - wx[touch_count] = - (data[3] & MASK_4BIT); - + w[touch_count] = data[3]; if (pdata->board->x_flip) x[touch_count] = pdata->sensor_max_x - @@ -388,6 +520,25 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, y[touch_count] = pdata->sensor_max_y - y[touch_count]; + if (x[touch_count] < 0) + x[touch_count] = 0; + else if (x[touch_count] >= pdata->sensor_max_x) + x[touch_count] = + pdata->sensor_max_x - 1; + + if (y[touch_count] < 0) + y[touch_count] = 0; + else if (y[touch_count] >= pdata->sensor_max_y) + y[touch_count] = + pdata->sensor_max_y - 1; + } + if ((abs(x[finger] - prv_x[finger]) < DELTA) && + (abs(y[finger] - prv_y[finger]) < DELTA)) { + x[finger] = prv_x[finger]; + y[finger] = prv_y[finger]; + } else { + prv_x[finger] = x[finger]; + prv_y[finger] = y[finger]; } /* number of active touch points */ touch_count++; @@ -398,7 +549,9 @@ static int synpatics_rmi4_touchpad_report(struct synaptics_rmi4_data *pdata, if (touch_count) { for (finger = 0; finger < touch_count; finger++) { input_report_abs(pdata->input_dev, ABS_MT_TOUCH_MAJOR, - max(wx[finger] , wy[finger])); + max(x[finger] , y[finger])); + input_report_abs(pdata->input_dev, ABS_MT_WIDTH_MAJOR, + w[finger]); input_report_abs(pdata->input_dev, ABS_MT_POSITION_X, x[finger]); input_report_abs(pdata->input_dev, ABS_MT_POSITION_Y, @@ -501,7 +654,7 @@ static irqreturn_t synaptics_rmi4_irq(int irq, void *data) touch_count = synaptics_rmi4_sensor_report(pdata); if (touch_count) wait_event_timeout(pdata->wait, pdata->touch_stopped, - msecs_to_jiffies(1)); + msecs_to_jiffies(TIMEOUT_PERIOD)); else break; } while (!pdata->touch_stopped); @@ -879,6 +1032,24 @@ static int synaptics_rmi4_i2c_query_device(struct synaptics_rmi4_data *pdata) return 0; } +/** + * synaptics_rmi4_resume_handler() - work queue for resume handler + * @work:work_struct structure pointer + * + * This work queue handler used to resume the device and returns none + */ +static void synaptics_rmi4_resume_handler(struct work_struct *work) +{ + struct synaptics_rmi4_data *prmi4_data = container_of(work, + struct synaptics_rmi4_data, resume_wq_handler); + struct i2c_client *client = prmi4_data->i2c_client; + int retval; + + retval = synaptics_rmi4_enable(prmi4_data); + if (retval < 0) + dev_err(&client->dev, "%s: resume failed\n", __func__); +} + /** * synaptics_rmi4_probe() - Initialze the i2c-client touchscreen driver * @i2c: i2c client structure pointer @@ -926,19 +1097,17 @@ static int __devinit synaptics_rmi4_probe goto err_input; } - rmi4_data->regulator = regulator_get(&client->dev, "vdd"); - if (IS_ERR(rmi4_data->regulator)) { - dev_err(&client->dev, "%s:get regulator failed\n", - __func__); - retval = PTR_ERR(rmi4_data->regulator); - goto err_get_regulator; - } - retval = regulator_enable(rmi4_data->regulator); - if (retval < 0) { - dev_err(&client->dev, "%s:regulator enable failed\n", - __func__); - goto err_regulator_enable; + if (platformdata->regulator_en) { + rmi4_data->regulator = regulator_get(&client->dev, "vdd"); + if (IS_ERR(rmi4_data->regulator)) { + dev_err(&client->dev, "%s:get regulator failed\n", + __func__); + retval = PTR_ERR(rmi4_data->regulator); + goto err_regulator; + } + regulator_enable(rmi4_data->regulator); } + init_waitqueue_head(&rmi4_data->wait); /* * Copy i2c_client pointer into RTID's i2c_client pointer for @@ -986,7 +1155,16 @@ static int __devinit synaptics_rmi4_probe input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_Y, 0, rmi4_data->sensor_max_y, 0, 0); input_set_abs_params(rmi4_data->input_dev, ABS_MT_TOUCH_MAJOR, 0, - MAX_TOUCH_MAJOR, 0, 0); + max(rmi4_data->sensor_max_y, rmi4_data->sensor_max_y), + 0, 0); + input_set_abs_params(rmi4_data->input_dev, ABS_MT_WIDTH_MAJOR, 0, + MAX_WIDTH_MAJOR, 0, 0); + + retval = input_register_device(rmi4_data->input_dev); + if (retval) { + dev_err(&client->dev, "%s:input register failed\n", __func__); + goto err_input_register; + } /* Clear interrupts */ synaptics_rmi4_i2c_block_read(rmi4_data, @@ -999,24 +1177,34 @@ static int __devinit synaptics_rmi4_probe if (retval) { dev_err(&client->dev, "%s:Unable to get attn irq %d\n", __func__, platformdata->irq_number); - goto err_query_dev; + goto err_request_irq; } - retval = input_register_device(rmi4_data->input_dev); + INIT_WORK(&rmi4_data->resume_wq_handler, synaptics_rmi4_resume_handler); + + /* sysfs implementation for dynamic enable/disable the input event */ + retval = sysfs_create_group(&client->dev.kobj, + &synaptics_rmi4_attr_group); if (retval) { - dev_err(&client->dev, "%s:input register failed\n", __func__); - goto err_free_irq; + dev_err(&client->dev, "failed to create sysfs entries\n"); + goto err_sysfs; } - + rmi4_data->enable = true; return retval; -err_free_irq: +err_sysfs: + cancel_work_sync(&rmi4_data->resume_wq_handler); +err_request_irq: free_irq(platformdata->irq_number, rmi4_data); + input_unregister_device(rmi4_data->input_dev); +err_input_register: + i2c_set_clientdata(client, NULL); err_query_dev: - regulator_disable(rmi4_data->regulator); -err_regulator_enable: - regulator_put(rmi4_data->regulator); -err_get_regulator: + if (platformdata->regulator_en) { + regulator_disable(rmi4_data->regulator); + regulator_put(rmi4_data->regulator); + } +err_regulator: input_free_device(rmi4_data->input_dev); rmi4_data->input_dev = NULL; err_input: @@ -1036,12 +1224,16 @@ static int __devexit synaptics_rmi4_remove(struct i2c_client *client) struct synaptics_rmi4_data *rmi4_data = i2c_get_clientdata(client); const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; + sysfs_remove_group(&client->dev.kobj, &synaptics_rmi4_attr_group); rmi4_data->touch_stopped = true; wake_up(&rmi4_data->wait); + cancel_work_sync(&rmi4_data->resume_wq_handler); free_irq(pdata->irq_number, rmi4_data); input_unregister_device(rmi4_data->input_dev); - regulator_disable(rmi4_data->regulator); - regulator_put(rmi4_data->regulator); + if (pdata->regulator_en) { + regulator_disable(rmi4_data->regulator); + regulator_put(rmi4_data->regulator); + } kfree(rmi4_data); return 0; @@ -1058,31 +1250,11 @@ static int __devexit synaptics_rmi4_remove(struct i2c_client *client) static int synaptics_rmi4_suspend(struct device *dev) { /* Touch sleep mode */ - int retval; - unsigned char intr_status; struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); - const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; - - rmi4_data->touch_stopped = true; - disable_irq(pdata->irq_number); - - retval = synaptics_rmi4_i2c_block_read(rmi4_data, - rmi4_data->fn01_data_base_addr + 1, - &intr_status, - rmi4_data->number_of_interrupt_register); - if (retval < 0) - return retval; - - retval = synaptics_rmi4_i2c_byte_write(rmi4_data, - rmi4_data->fn01_ctrl_base_addr + 1, - (intr_status & ~TOUCHPAD_CTRL_INTR)); - if (retval < 0) - return retval; - - regulator_disable(rmi4_data->regulator); - return 0; + return synaptics_rmi4_disable(rmi4_data); } + /** * synaptics_rmi4_resume() - resume the touch screen controller * @dev: pointer to device structure @@ -1092,28 +1264,9 @@ static int synaptics_rmi4_suspend(struct device *dev) */ static int synaptics_rmi4_resume(struct device *dev) { - int retval; - unsigned char intr_status; struct synaptics_rmi4_data *rmi4_data = dev_get_drvdata(dev); - const struct synaptics_rmi4_platform_data *pdata = rmi4_data->board; - - regulator_enable(rmi4_data->regulator); - enable_irq(pdata->irq_number); - rmi4_data->touch_stopped = false; - - retval = synaptics_rmi4_i2c_block_read(rmi4_data, - rmi4_data->fn01_data_base_addr + 1, - &intr_status, - rmi4_data->number_of_interrupt_register); - if (retval < 0) - return retval; - - retval = synaptics_rmi4_i2c_byte_write(rmi4_data, - rmi4_data->fn01_ctrl_base_addr + 1, - (intr_status | TOUCHPAD_CTRL_INTR)); - if (retval < 0) - return retval; + schedule_work(&rmi4_data->resume_wq_handler); return 0; } diff --git a/include/linux/input/bu21013.h b/include/linux/input/bu21013.h index 05e03284b92..143f433b9ee 100644 --- a/include/linux/input/bu21013.h +++ b/include/linux/input/bu21013.h @@ -1,5 +1,5 @@ /* - * Copyright (C) ST-Ericsson SA 2010 + * Copyright (C) ST-Ericsson SA 2009 * Author: Naveen Kumar G for ST-Ericsson * License terms:GNU General Public License (GPL) version 2 */ @@ -9,32 +9,36 @@ /** * struct bu21013_platform_device - Handle the platform data - * @cs_en: pointer to the cs enable function - * @cs_dis: pointer to the cs disable function - * @irq_read_val: pointer to read the pen irq value function + * @cs_en: pointer to the cs enable function + * @cs_dis: pointer to the cs disable function + * @irq_read_val: pointer to read the pen irq value function + * @x_max_res: xmax resolution + * @y_max_res: ymax resolution * @touch_x_max: touch x max * @touch_y_max: touch y max * @cs_pin: chip select pin * @irq: irq pin - * @ext_clk: external clock flag + * @has_ext_clk: has external clock + * @enable_ext_clk: enable external clock + * @portrait: portrait mode flag * @x_flip: x flip flag * @y_flip: y flip flag - * @wakeup: wakeup flag - * * This is used to handle the platform data - */ + **/ struct bu21013_platform_device { int (*cs_en)(int reset_pin); int (*cs_dis)(int reset_pin); int (*irq_read_val)(void); + int x_max_res; + int y_max_res; int touch_x_max; int touch_y_max; unsigned int cs_pin; unsigned int irq; - bool ext_clk; + bool has_ext_clk; + bool enable_ext_clk; + bool portrait; bool x_flip; bool y_flip; - bool wakeup; }; - #endif -- cgit v1.2.3 From 14a2887a2508960eced005137362c76ab5c79138 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 10:36:12 +0100 Subject: i2c: nomadic-i2c driver updates for clock and power, busy management Moreover driver documentation is added in DocBook Signed-off-by: Jonas Aaberg Signed-off-by: Mian Yousaf Kaukab --- Documentation/DocBook/i2c.tmpl | 116 +++++++++++++++++++++++++++++++++++++++ drivers/i2c/busses/i2c-nomadik.c | 6 ++ 2 files changed, 122 insertions(+) create mode 100644 Documentation/DocBook/i2c.tmpl diff --git a/Documentation/DocBook/i2c.tmpl b/Documentation/DocBook/i2c.tmpl new file mode 100644 index 00000000000..8a4cb49204e --- /dev/null +++ b/Documentation/DocBook/i2c.tmpl @@ -0,0 +1,116 @@ + + + + + + I2C + + + + Srinidhi + Kasagar + +
+ srinidhi.kasagar@stericsson.com +
+
+
+ + Sachin + Verma + +
+ sachin.verma@st.com +
+
+
+
+ + + 2009-2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + +
+ + + + Introduction + + This Documentation describes the API's provided by the I2C controller Driver. + Since this driver registers the transferfunction with kernel framework, there + are only private functions in this I2C bus driver. This driver currently + works only in master mode and does 7 bit adderssing only. There is no support + for 10 bit addressing. The driver currently supports standard mode (100KHz) + and Fast mode (400KHz) operation. + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Public Functions Provided + + Not Applicable + + + + + Private Functions + + This Section lists the functions used internally by the I2C controller driver. + +!Idrivers/i2c/busses/i2c-nomadik.c + + +
diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 0c731ca69f1..5a579222754 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -265,6 +265,9 @@ static int init_hw(struct nmk_i2c_dev *dev) dev->cli.operation = I2C_NO_OPERATION; exit: + clk_disable(dev->clk); + + udelay(I2C_DELAY); return stat; } @@ -632,6 +635,8 @@ static int nmk_i2c_xfer(struct i2c_adapter *i2c_adap, clk_enable(dev->clk); + dev->busy = true; + status = init_hw(dev); if (status) goto out; @@ -1043,6 +1048,7 @@ static struct platform_driver nmk_i2c_driver = { }, .probe = nmk_i2c_probe, .remove = __devexit_p(nmk_i2c_remove), + .suspend = nmk_i2c_suspend, }; static int __init nmk_i2c_init(void) -- cgit v1.2.3 From 4695009d93c874c1ea81f01c5cd23e154affd510 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 10:39:49 +0100 Subject: nmk-i2c: use pm_runtime API Use the pm_runtime API for pins control. ST-Ericsson Linux next: - ST-Ericsson ID: ER323382 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ib847143b96950c25221c6aa25dae541017840677 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16045 Tested-by: Rabin VINCENT Reviewed-by: Srinidhi KASAGAR Reviewed-by: Jonas ABERG --- drivers/i2c/busses/i2c-nomadik.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 5a579222754..a0cfe9e0ec4 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -251,6 +251,8 @@ static int init_hw(struct nmk_i2c_dev *dev) { int stat; + clk_enable(dev->clk); + stat = flush_i2c_fifo(dev); if (stat) goto exit; @@ -431,9 +433,10 @@ static int read_i2c(struct nmk_i2c_dev *dev) } if (timeout == 0) { - /* Controller timed out */ - dev_err(&dev->pdev->dev, "read from slave 0x%x timed out\n", + /* controller has timedout, re-init the h/w */ + dev_err(&dev->pdev->dev, "Read from Slave 0x%x timed out\n", dev->cli.slave_adr); + (void) init_hw(dev); status = -ETIMEDOUT; } return status; @@ -518,9 +521,10 @@ static int write_i2c(struct nmk_i2c_dev *dev) } if (timeout == 0) { - /* Controller timed out */ - dev_err(&dev->pdev->dev, "write to slave 0x%x timed out\n", + /* controller has timedout, re-init the h/w */ + dev_err(&dev->pdev->dev, "Write to slave 0x%x timed out\n", dev->cli.slave_adr); + (void) init_hw(dev); status = -ETIMEDOUT; } -- cgit v1.2.3 From 735f684480f4f3eeb9de757778ce5a7733d2ecdd Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 11:31:20 +0100 Subject: i2c-nomadik: Add code to retry on timeout failure and set i2c clock to 400KHz It is seen that i2c-nomadik controller randomly stops generating the interrupts leading to a i2c timeout. As a workaround to this problem add retries, to the on going transfer on failure. Since this behaviour does not get changed with a wait for longer duration, the timeout can be reduced to a lower value, leading to a quicker retry attempt. ST-Ericsson ID: ER 325661 Signed-off-by: Virupax Sadashivpetimath Change-Id: Ic9db2b15ff5ce7e656cbbdfeb19270da9e32f9d1 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16972 Tested-by: Virupax SADASHIVPETIMATH Reviewed-by: Jonas ABERG --- drivers/i2c/busses/i2c-nomadik.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index a0cfe9e0ec4..398bce2d5c7 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -251,8 +251,6 @@ static int init_hw(struct nmk_i2c_dev *dev) { int stat; - clk_enable(dev->clk); - stat = flush_i2c_fifo(dev); if (stat) goto exit; @@ -267,8 +265,6 @@ static int init_hw(struct nmk_i2c_dev *dev) dev->cli.operation = I2C_NO_OPERATION; exit: - clk_disable(dev->clk); - udelay(I2C_DELAY); return stat; } -- cgit v1.2.3 From 891312c04c1207fd2b8913d4cec459e602f7f53f Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 11:32:50 +0100 Subject: i2c-nomadik: Change the TX and RX Threshold 1) Increase RX FIFO threshold so that there is a reduction in the number of interrupts handled to complete a transaction. 2) Fill TX FIFO in the write function. ST-Ericsson ID: ER 326383 Change-Id: Ib75946bed8fdb3f81dfa0e2ffeb29d6ba0be36bb Signed-off-by: Virupax Sadashivpetimath Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17473 Reviewed-by: Jonas ABERG --- arch/arm/mach-ux500/board-mop500.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index cd54abaccd9..c0f01d90082 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -343,13 +343,13 @@ static struct nmk_i2c_controller u8500_i2c##id##_data = { \ /* * The board uses 4 i2c controllers, initialize all of * them with slave data setup time of 250 ns, - * Tx & Rx FIFO threshold values as 8 and standard + * Tx & Rx FIFO threshold values as 1 and standard * mode of operation */ -U8500_I2C_CONTROLLER(0, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); -U8500_I2C_CONTROLLER(1, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); -U8500_I2C_CONTROLLER(2, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); -U8500_I2C_CONTROLLER(3, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(0, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(1, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(2, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(3, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); static void __init mop500_i2c_init(void) { -- cgit v1.2.3 From 423fee66f9fa1f7c65d06977d7af8e23edfa567d Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 13:26:43 +0100 Subject: mach-ux500: platform data for HED54XXU11 Hall effect sensor Hall effect sensor is managed as an input event (SW_LID), same management as proximity sensor Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/board-mop500.c | 36 +++++++++++++++++++++--------------- arch/arm/mach-ux500/board-mop500.h | 1 + 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index c0f01d90082..153a0b48700 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -366,18 +366,25 @@ static struct gpio_keys_button mop500_gpio_keys[] = { .code = SW_FRONT_PROXIMITY, .active_low = 0, .can_disable = 1, + }, + { + .desc = "HED54XXU11 Hall Effect Sensor", + .type = EV_SW, + .code = SW_LID, /* FIXME arbitrary usage */ + .active_low = 0, + .can_disable = 1, } }; -static struct regulator *prox_regulator; -static int mop500_prox_activate(struct device *dev); -static void mop500_prox_deactivate(struct device *dev); +static struct regulator *sensors1p_regulator; +static int mop500_sensors1p_activate(struct device *dev); +static void mop500_sensors1p_deactivate(struct device *dev); static struct gpio_keys_platform_data mop500_gpio_keys_data = { .buttons = mop500_gpio_keys, .nbuttons = ARRAY_SIZE(mop500_gpio_keys), - .enable = mop500_prox_activate, - .disable = mop500_prox_deactivate, + .enable = mop500_sensors1p_activate, + .disable = mop500_sensors1p_deactivate, }; static struct platform_device mop500_gpio_keys_device = { @@ -388,23 +395,22 @@ static struct platform_device mop500_gpio_keys_device = { }, }; -static int mop500_prox_activate(struct device *dev) +static int mop500_sensors1p_activate(struct device *dev) { - prox_regulator = regulator_get(&mop500_gpio_keys_device.dev, + sensors1p_regulator = regulator_get(&mop500_gpio_keys_device.dev, "vcc"); - if (IS_ERR(prox_regulator)) { - dev_err(&mop500_gpio_keys_device.dev, - "no regulator\n"); - return PTR_ERR(prox_regulator); + if (IS_ERR(sensors1p_regulator)) { + dev_err(&mop500_gpio_keys_device.dev, "no regulator\n"); + return PTR_ERR(sensors1p_regulator); } - regulator_enable(prox_regulator); + regulator_enable(sensors1p_regulator); return 0; } -static void mop500_prox_deactivate(struct device *dev) +static void mop500_sensors1p_deactivate(struct device *dev) { - regulator_disable(prox_regulator); - regulator_put(prox_regulator); + regulator_disable(sensors1p_regulator); + regulator_put(sensors1p_regulator); } /* add any platform devices here - TODO */ diff --git a/arch/arm/mach-ux500/board-mop500.h b/arch/arm/mach-ux500/board-mop500.h index ee77a8970c3..656f9297cfe 100644 --- a/arch/arm/mach-ux500/board-mop500.h +++ b/arch/arm/mach-ux500/board-mop500.h @@ -29,6 +29,7 @@ #define MOP500_EGPIO(x) (NOMADIK_NR_GPIO + (x)) #define GPIO_SDMMC_CD MOP500_EGPIO(3) #define GPIO_PROX_SENSOR MOP500_EGPIO(7) +#define GPIO_HAL_SENSOR MOP500_EGPIO(8) #define GPIO_BU21013_CS MOP500_EGPIO(13) #define GPIO_SDMMC_EN MOP500_EGPIO(17) #define GPIO_SDMMC_1V8_3V_SEL MOP500_EGPIO(18) -- cgit v1.2.3 From 342d95b34ddeb20d958d5c6bd78866aad75c3739 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 15:15:43 +0100 Subject: MMCI: Fix incorrect handling of HW FC for SDIO For SDIO writes smaller than 8 bytes (only SDIO case), hardware flow control was turned off, but was never turned on again which meant that if a large read request followed, SDIO would randomly give buffer overrun errors since there was no flow control. ST-Ericsson ID: ER280840 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie95323361ab3d3c19649ea5901f75127251d5432 Signed-off-by: Stefan Nilsson XK Signed-off-by: Ulf Hansson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/9447 Reviewed-by: Ulf HANSSON Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/10931 Reviewed-by: QATOOLS Reviewed-by: Jonas ABERG Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/11204 --- drivers/mmc/host/mmci.c | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index fe140724a02..b792371d8a5 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -514,11 +514,29 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) irqmask = MCI_TXFIFOHALFEMPTYMASK; } - /* The ST Micro variants has a special bit to enable SDIO */ if (variant->sdio && host->mmc->card) - if (mmc_card_sdio(host->mmc->card)) + if (mmc_card_sdio(host->mmc->card)) { + /* + * The ST Micro variants has a special bit + * to enable SDIO. + */ datactrl |= MCI_ST_DPSM_SDIOEN; + /* + * The ST Micro variant for SDIO transfer sizes + * less then 8 bytes should have clock H/W flow + * control disabled. + */ + if ((host->size < 8) && (data->flags & MMC_DATA_WRITE)) + writel(readl(host->base + MMCICLOCK) & + ~variant->clkreg_enable, + host->base + MMCICLOCK); + else + writel(readl(host->base + MMCICLOCK) | + variant->clkreg_enable, + host->base + MMCICLOCK); + } + writel(datactrl, base + MMCIDATACTRL); writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0); mmci_set_mask1(host, irqmask); @@ -688,23 +706,6 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int rem variant->fifosize : variant->fifohalfsize; count = min(remain, maxcnt); - /* - * The ST Micro variant for SDIO transfer sizes - * less then 8 bytes should have clock H/W flow - * control disabled. - */ - if (variant->sdio && - mmc_card_sdio(host->mmc->card)) { - if (count < 8) - writel(readl(host->base + MMCICLOCK) & - ~variant->clkreg_enable, - host->base + MMCICLOCK); - else - writel(readl(host->base + MMCICLOCK) | - variant->clkreg_enable, - host->base + MMCICLOCK); - } - /* * SDIO especially may want to send something that is * not divisible by 4 (as opposed to card sectors -- cgit v1.2.3 From 6f04fbec38bbbfd5592e1d7685d147ba0a727a3a Mon Sep 17 00:00:00 2001 From: Sebastian Rasmussen Date: Fri, 14 Jan 2011 19:08:24 +0100 Subject: MMCI: Put power register deviations in variant data. Use variant data to store hardware controller deviations concerning power registers to improve readability of the code. ST-Ericsson ID: ER282562 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I6285df49bc4dceb443df5af618ee980b0e62f59f Signed-off-by: Sebastian Rasmussen Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/12543 Reviewed-by: QATOOLS Reviewed-by: Linus WALLEIJ --- drivers/mmc/host/mmci.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index b792371d8a5..3d1fbaa086d 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -52,6 +52,7 @@ static unsigned int fmax = 515633; * @sdio: variant supports SDIO * @st_clkdiv: true if using a ST-specific clock divider algorithm * @blksz_datactrl16: true if Block size is at b16..b30 position in datactrl register + * @pwrreg_powerup: power up value for MMCIPOWER register */ struct variant_data { unsigned int clkreg; @@ -62,12 +63,14 @@ struct variant_data { bool sdio; bool st_clkdiv; bool blksz_datactrl16; + unsigned int pwrreg_powerup; }; static struct variant_data variant_arm = { .fifosize = 16 * 4, .fifohalfsize = 8 * 4, .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, }; static struct variant_data variant_arm_extended_fifo = { @@ -82,6 +85,7 @@ static struct variant_data variant_u300 = { .clkreg_enable = MCI_ST_U300_HWFCEN, .datalength_bits = 16, .sdio = true, + .pwrreg_powerup = MCI_PWR_ON, }; static struct variant_data variant_ux500 = { @@ -92,6 +96,7 @@ static struct variant_data variant_ux500 = { .datalength_bits = 24, .sdio = true, .st_clkdiv = true, + .pwrreg_powerup = MCI_PWR_ON, }; static struct variant_data variant_ux500v2 = { @@ -884,6 +889,7 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; u32 pwr = 0; unsigned long flags; int ret; @@ -910,11 +916,15 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) if (host->plat->vdd_handler) pwr |= host->plat->vdd_handler(mmc_dev(mmc), ios->vdd, ios->power_mode); - /* The ST version does not have this, fall through to POWER_ON */ - if (host->hw_designer != AMBA_VENDOR_ST) { - pwr |= MCI_PWR_UP; - break; - } + + /* + * The ST Micro variant doesn't have the PL180s MCI_PWR_UP + * and instead uses MCI_PWR_ON so apply whatever value is + * configured in the variant data. + */ + pwr |= variant->pwrreg_powerup; + + break; case MMC_POWER_ON: pwr |= MCI_PWR_ON; break; -- cgit v1.2.3 From 0e7418a8afeb12de3a161201da0ec614735248fa Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 17:12:21 +0100 Subject: MMCI: Add DDR support This adds support for eMMC DDR. ST-Ericsson ID: 255732 ST-Ericsson FOSS-OUT ID: NA Change-Id: If5007f81090c229dfe1d442eeffc046aa06e7014 Signed-off-by: Mikael Larsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/11224 Reviewed-by: QATOOLS Reviewed-by: Jonas ABERG --- drivers/mmc/host/mmci.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 3d1fbaa086d..9693af20f33 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -197,8 +197,22 @@ static void mmci_set_mask1(struct mmci_host *host, unsigned int mask) static void mmci_stop_data(struct mmci_host *host) { + u32 clk; + writel(0, host->base + MMCIDATACTRL); mmci_set_mask1(host, 0); + + /* Needed for DDR */ + if (host->mmc->card && mmc_card_ddr_mode(host->mmc->card)) { + clk = readl(host->base + MMCICLOCK); + if (clk & MCI_ST_UX500_NEG_EDGE) + clk &= ~(MCI_ST_UX500_NEG_EDGE); + if (clk & MCI_ST_UX500_CLK_INV) + clk &= ~(MCI_ST_UX500_CLK_INV); + + writel(clk, (host->base + MMCICLOCK)); + } + host->data = NULL; } @@ -463,6 +477,7 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) unsigned long long clks; void __iomem *base; int blksz_bits; + u32 clk; dev_dbg(mmc_dev(host->mmc), "blksz %04x blks %04x flags %08x\n", data->blksz, data->blocks, data->flags); @@ -491,6 +506,18 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) if (data->flags & MMC_DATA_READ) datactrl |= MCI_DPSM_DIRECTION; + if (host->mmc->card && mmc_card_ddr_mode(host->mmc->card)) { + datactrl |= MCI_ST_DPSM_DDRMODE; + + /* Needed for DDR */ + clk = readl(base + MMCICLOCK); + clk |= MCI_ST_UX500_CLK_INV; + if ((data->flags & MMC_DATA_READ)) + clk |= MCI_ST_UX500_NEG_EDGE; + + writel(clk, (base + MMCICLOCK)); + } + /* * Attempt to use DMA operation mode, if this * should fail, fall back to PIO mode -- cgit v1.2.3 From 63d95eb38dc7f61c7c069639602ec65a4cfdcd91 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 22 Mar 2011 10:43:59 +0100 Subject: MMCI: Add SDIO irq support (FIXME partial merge, TODO dynamic clocking managment) SDIO irq support. The SDIO mode enable bit in the DATACTRL register together with SDIO interrupt mask are set to be able to generate SDIO interrupts from the DAT1 line. In sleep mode, SDIO interrupts are not handled and thus not being generated. Returning from sleep mode turns on SDIO interrupt handling again. ST-Ericsson ID: 319566 Change-Id: Ib919ad7b38dc999f5fa613f2c679ba9a9970c715 Signed-off-by: Stefan Nilsson XK Signed-off-by: Ulf Hansson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/12912 Conflicts: drivers/mmc/host/mmci.c --- drivers/mmc/host/mmci.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 9693af20f33..d15feeac394 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -198,8 +198,20 @@ static void mmci_set_mask1(struct mmci_host *host, unsigned int mask) static void mmci_stop_data(struct mmci_host *host) { u32 clk; + unsigned int datactrl = 0; - writel(0, host->base + MMCIDATACTRL); + /* + * The ST Micro variants has a special bit + * to enable SDIO mode. This bit must remain set even when not + * doing data transfers, otherwise no SDIO interrupts can be + * received. + */ + if (host->variant->sdio && + host->mmc->card && + mmc_card_sdio(host->mmc->card)) + datactrl |= MCI_ST_DPSM_SDIOEN; + + writel(datactrl, host->base + MMCIDATACTRL); mmci_set_mask1(host, 0); /* Needed for DDR */ @@ -550,7 +562,11 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) if (mmc_card_sdio(host->mmc->card)) { /* * The ST Micro variants has a special bit - * to enable SDIO. + * to enable SDIO mode. This bit is set the first time + * a SDIO data transfer is done and must remain set + * after the data transfer is completed. The reason is + * because of otherwise no SDIO interrupts can be + * received. */ datactrl |= MCI_ST_DPSM_SDIOEN; @@ -869,6 +885,9 @@ static irqreturn_t mmci_irq(int irq, void *dev_id) dev_dbg(mmc_dev(host->mmc), "irq0 (data+cmd) %08x\n", status); + if (status & MCI_ST_SDIOIT) + mmc_signal_sdio_irq(host->mmc); + data = host->data; if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN| MCI_RXOVERRUN|MCI_DATAEND|MCI_DATABLOCKEND) && data) @@ -1022,11 +1041,35 @@ static irqreturn_t mmci_cd_irq(int irq, void *dev_id) return IRQ_HANDLED; } +static void mmci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + unsigned long flags; + struct mmci_host *host = mmc_priv(mmc); + + if (enable) { + /* + * Since the host is not claimed when doing enable + * we must handle it here. + */ + spin_lock_irqsave(&host->lock, flags); + + writel((readl(host->base + MMCIMASK0) | MCI_ST_SDIOIT), + (host->base + MMCIMASK0)); + + spin_unlock_irqrestore(&host->lock, flags); + } else { + /* We assumes the host is claimed when doing disable. */ + writel((readl(host->base + MMCIMASK0) & ~MCI_ST_SDIOIT), + (host->base + MMCIMASK0)); + } +} + static const struct mmc_host_ops mmci_ops = { .request = mmci_request, .set_ios = mmci_set_ios, .get_ro = mmci_get_ro, .get_cd = mmci_get_cd, + .enable_sdio_irq = mmci_enable_sdio_irq, }; static int __devinit mmci_probe(struct amba_device *dev, -- cgit v1.2.3 From 9799e5bdbee3ce90690bccfd48d56371aae1c94b Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 22 Mar 2011 08:58:20 +0100 Subject: MMCI: Fixup sg buffer handling in pio_write Earlier code in pio_write was expecting that each scattergather buffer was 4-bytes aligned which is not the case. This patch fix the problem by using a 4 bytes buffer to cache unaligned data between each pio_write operation. Change-Id: I2e506ec1f5a7fadf9780c18d8d31573eaf048273 Signed-off-by: Ulf Hansson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13860 Reviewed-by: QATOOLS Tested-by: Henrik CARLING Reviewed-by: Henrik CARLING --- drivers/mmc/host/mmci.c | 90 ++++++++++++++++++++++++++++++++++++++++++------- drivers/mmc/host/mmci.h | 3 ++ 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index d15feeac394..4a18f2d9f59 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -497,6 +497,8 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) host->data = data; host->size = data->blksz * data->blocks; data->bytes_xfered = 0; + host->cache_len = 0; + host->cache = 0; clks = (unsigned long long)data->timeout_ns * host->cclk; do_div(clks, 1000000000UL); @@ -747,25 +749,89 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int rem void __iomem *base = host->base; char *ptr = buffer; - do { - unsigned int count, maxcnt; + unsigned int data_left = host->size; + unsigned int count, maxcnt; + char *cache_ptr; + int i; + do { maxcnt = status & MCI_TXFIFOEMPTY ? variant->fifosize : variant->fifohalfsize; - count = min(remain, maxcnt); /* - * SDIO especially may want to send something that is - * not divisible by 4 (as opposed to card sectors - * etc), and the FIFO only accept full 32-bit writes. - * So compensate by adding +3 on the count, a single - * byte become a 32bit write, 7 bytes will be two - * 32bit writes etc. + * A write to the FIFO must always be done of 4 bytes aligned + * data. If the buffer is not 4 bytes aligned we must pad the + * data, but this must only be done for the final write for the + * entire data transfer, otherwise we will corrupt the data. + * Thus a buffer cache of four bytes is needed to temporary + * store data. */ - writesl(base + MMCIFIFO, ptr, (count + 3) >> 2); - ptr += count; - remain -= count; + if (host->cache_len) { + cache_ptr = (char *)&host->cache; + cache_ptr = cache_ptr + host->cache_len; + data_left += host->cache_len; + + while ((host->cache_len < 4) && (remain > 0)) { + *cache_ptr = *ptr; + cache_ptr++; + ptr++; + host->cache_len++; + remain--; + } + + if ((host->cache_len == 4) || + (data_left == host->cache_len)) { + + writesl(base + MMCIFIFO, &host->cache, 1); + if (data_left == host->cache_len) + break; + + host->cache = 0; + host->cache_len = 0; + maxcnt -= 4; + data_left -= 4; + } + + if (remain == 0) + break; + } + + count = min(remain, maxcnt); + + if (!(count % 4) || (data_left == count)) { + /* + * The data is either 4-bytes aligned or it is the + * last data to write. It is thus fine to potentially + * pad the data if needed. + */ + writesl(base + MMCIFIFO, ptr, (count + 3) >> 2); + ptr += count; + remain -= count; + data_left -= count; + + } else { + + host->cache_len = count % 4; + count = (count >> 2) << 2; + + if (count) + writesl(base + MMCIFIFO, ptr, count >> 2); + + ptr += count; + remain -= count; + data_left -= count; + + i = 0; + cache_ptr = (char *)&host->cache; + while (i < host->cache_len) { + *cache_ptr = *ptr; + cache_ptr++; + ptr++; + remain--; + i++; + } + } if (remain == 0) break; diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index 2164e8c6476..588744c0279 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -196,6 +196,9 @@ struct mmci_host { /* pio stuff */ struct sg_mapping_iter sg_miter; unsigned int size; + unsigned int cache; + unsigned int cache_len; + struct regulator *vcc; #ifdef CONFIG_DMA_ENGINE -- cgit v1.2.3 From 7e269cd78e948d8dfb55855a6ecb32fda0f7505c Mon Sep 17 00:00:00 2001 From: Stefan Nilsson XK Date: Sun, 5 Dec 2010 15:59:57 +0100 Subject: MMCI: Safe check pio RW in case of spurious IRQs After seeing IRQ:s coming in to indicate TXFIFO empty even when status register indicates that TXFIFO is not empty, a safer approach is now taken. ST-Ericsson ID: ER280840 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Idb2f26c83ce488237c264577c99e0dac3f378f3a Signed-off-by: Stefan Nilsson XK Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/12505 Reviewed-by: Mikael GULLBERG Tested-by: Mikael GULLBERG Reviewed-by: Sebastian RASMUSSEN --- drivers/mmc/host/mmci.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 4a18f2d9f59..a68943eb31b 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -712,14 +712,14 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, } } -static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int remain) +static int mmci_pio_read(struct mmci_host *host, char *buffer, + unsigned int remain, u32 status) { void __iomem *base = host->base; char *ptr = buffer; - u32 status; int host_remain = host->size; - do { + while (status & MCI_RXDATAAVLBL) { int count = host_remain - (readl(base + MMCIFIFOCNT) << 2); if (count > remain) @@ -738,25 +738,27 @@ static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int rema break; status = readl(base + MMCISTATUS); - } while (status & MCI_RXDATAAVLBL); + } return ptr - buffer; } -static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int remain, u32 status) +static int mmci_pio_write(struct mmci_host *host, char *buffer, + unsigned int remain, u32 status) { struct variant_data *variant = host->variant; void __iomem *base = host->base; - char *ptr = buffer; unsigned int data_left = host->size; unsigned int count, maxcnt; + char *ptr = buffer; + + while (status & MCI_TXFIFOHALFEMPTY) { + unsigned int count; char *cache_ptr; int i; - do { - maxcnt = status & MCI_TXFIFOEMPTY ? - variant->fifosize : variant->fifohalfsize; + count = min(remain, variant->fifohalfsize); /* * A write to the FIFO must always be done of 4 bytes aligned @@ -797,7 +799,6 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int rem break; } - count = min(remain, maxcnt); if (!(count % 4) || (data_left == count)) { /* @@ -837,7 +838,7 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int rem break; status = readl(base + MMCISTATUS); - } while (status & MCI_TXFIFOHALFEMPTY); + }; return ptr - buffer; } @@ -882,7 +883,7 @@ static irqreturn_t mmci_pio_irq(int irq, void *dev_id) len = 0; if (status & MCI_RXACTIVE) - len = mmci_pio_read(host, buffer, remain); + len = mmci_pio_read(host, buffer, remain, status); if (status & MCI_TXACTIVE) len = mmci_pio_write(host, buffer, remain, status); -- cgit v1.2.3 From 8c1b4e800a959e6fbb4f6dbdb6cf265b3ab3b5da Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 22 Mar 2011 10:52:14 +0100 Subject: mmci: Fix warning introduced in previous commit --- drivers/mmc/host/mmci.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index a68943eb31b..234edf2a84f 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -750,13 +750,13 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, void __iomem *base = host->base; unsigned int data_left = host->size; - unsigned int count, maxcnt; + unsigned int maxcnt; char *ptr = buffer; while (status & MCI_TXFIFOHALFEMPTY) { unsigned int count; - char *cache_ptr; - int i; + char *cache_ptr; + int i; count = min(remain, variant->fifohalfsize); -- cgit v1.2.3 From 0e1e55541ca59f8b726b414134effa41008314ba Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 22 Mar 2011 11:36:59 +0100 Subject: MMCI: Add support for non-power-of-two block sizes Adds support for any block size over SDIO. ST-Ericsson ID: AP272999 Change-Id: I5a2654360a2d38e6ab929b099c2bc04668ae9e85 Signed-off-by: Stefan Nilsson XK Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/15091 Reviewed-by: Jonas ABERG --- drivers/mmc/host/mmci.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 234edf2a84f..ba6f324c093 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -53,6 +53,8 @@ static unsigned int fmax = 515633; * @st_clkdiv: true if using a ST-specific clock divider algorithm * @blksz_datactrl16: true if Block size is at b16..b30 position in datactrl register * @pwrreg_powerup: power up value for MMCIPOWER register + * @non_power_of_2_blksize: variant supports block sizes that are not + * a power of two. */ struct variant_data { unsigned int clkreg; @@ -64,6 +66,7 @@ struct variant_data { bool st_clkdiv; bool blksz_datactrl16; unsigned int pwrreg_powerup; + bool non_power_of_2_blksize; }; static struct variant_data variant_arm = { @@ -97,6 +100,7 @@ static struct variant_data variant_ux500 = { .sdio = true, .st_clkdiv = true, .pwrreg_powerup = MCI_PWR_ON, + .non_power_of_2_blksize = true, }; static struct variant_data variant_ux500v2 = { @@ -510,7 +514,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) writel(host->size, base + MMCIDATALENGTH); blksz_bits = ffs(data->blksz) - 1; - BUG_ON(1 << blksz_bits != data->blksz); if (variant->blksz_datactrl16) datactrl = MCI_DPSM_ENABLE | (data->blksz << 16); @@ -975,11 +978,18 @@ static irqreturn_t mmci_irq(int irq, void *dev_id) static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; unsigned long flags; WARN_ON(host->mrq != NULL); - if (mrq->data && !is_power_of_2(mrq->data->blksz)) { + if (mrq->data && + (!variant->non_power_of_2_blksize || +#ifdef CONFIG_ARCH_U8500 + !cpu_is_u8500v2() || +#endif + (mmc->card && mmc_card_ddr_mode(mmc->card))) && + !is_power_of_2(mrq->data->blksz)) { dev_err(mmc_dev(mmc), "unsupported block size (%d bytes)\n", mrq->data->blksz); mrq->cmd->error = -EINVAL; -- cgit v1.2.3 From 34bb85ea55a6a4dba9ef6a9ecd0f8eb8899181d0 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 22 Mar 2011 11:53:06 +0100 Subject: SDIO: Add workaround for client with broken CMD53 Adds a workaround which can be turned on for SDIO devices that do not support 512 byte requests in byte mode during CMD53. These requests will always be sent in block mode instead. ST-Ericsson ID: AP272999 Change-Id: I9923e87d2be43e0139af6648f3add522d65ab5ad Signed-off-by: Stefan Nilsson XK Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/15092 Reviewed-by: Jonas ABERG --- drivers/mmc/core/sdio_ops.c | 9 +++++++-- include/linux/mmc/host.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index f087d876c57..a29f99a30c4 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -144,10 +144,15 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, cmd.arg |= fn << 28; cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; cmd.arg |= addr << 9; - if (blocks == 1 && blksz <= 512) - cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ + + if (blocks == 1 && blksz < 512) + cmd.arg |= blksz; /* byte mode */ + else if (blocks == 1 && blksz == 512 && + !(card->host->caps & MMC_CAP_BROKEN_SDIO_CMD53)) + cmd.arg |= 0; /* byte mode, 0=512 */ else cmd.arg |= 0x08000000 | blocks; /* block mode */ + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; data.blksz = blksz; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 1ee4424462e..5ce91778d6d 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -211,6 +211,7 @@ struct mmc_host { #define MMC_CAP_MAX_CURRENT_600 (1 << 28) /* Host max current limit is 600mA */ #define MMC_CAP_MAX_CURRENT_800 (1 << 29) /* Host max current limit is 800mA */ #define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */ +#define MMC_CAP_BROKEN_SDIO_CMD53 (1 << 15) /* Broken CMD53 byte mode */ mmc_pm_flag_t pm_caps; /* supported pm features */ -- cgit v1.2.3 From 69a86710f29f2b55bff0bc84d627a8bb72067c86 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 22 Mar 2011 14:27:12 +0100 Subject: mach-ux500: Add SDIO WLAN support on sdi1 --- arch/arm/mach-ux500/board-mop500-sdi.c | 49 ++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-sdi.c b/arch/arm/mach-ux500/board-mop500-sdi.c index d0cb9e5eb87..92e2e0a5e7e 100644 --- a/arch/arm/mach-ux500/board-mop500-sdi.c +++ b/arch/arm/mach-ux500/board-mop500-sdi.c @@ -20,6 +20,7 @@ #include "devices-db8500.h" #include "board-mop500.h" #include "ste-dma40-db8500.h" +#include "../../../drivers/mmc/host/mmci.h" /* to avoid MCI_ST* redefinition */ /* * SDI 0 (MicroSD slot) @@ -59,8 +60,8 @@ static u32 mop500_sdi0_vdd_handler(struct device *dev, unsigned int vdd, break; } - return MCI_FBCLKEN | MCI_CMDDIREN | MCI_DATA0DIREN | - MCI_DATA2DIREN | MCI_DATA31DIREN; + return MCI_ST_FBCLKEN | MCI_ST_CMDDIREN | MCI_ST_DATA0DIREN | + MCI_ST_DATA2DIREN; } #ifdef CONFIG_STE_DMA40 @@ -131,6 +132,50 @@ void mop500_sdi_tc35892_init(void) sdi0_configure(); } +/* + * SDI1 (SDIO WLAN) + */ +#ifdef CONFIG_STE_DMA40 +#ifdef MMC_WITH_DMA +static struct stedma40_chan_cfg sdi1_dma_cfg_rx = { + .dir = STEDMA40_PERIPH_TO_MEM, + .src_dev_type = DB8500_DMA_DEV32_SD_MM1_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; + +static struct stedma40_chan_cfg sdi1_dma_cfg_tx = { + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB8500_DMA_DEV32_SD_MM1_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; +#endif +#endif + +/* + * TODO 1: SDIO power management not fully supported. + * TODO 2: SDIO with DMA not yet supported. + */ +static struct mmci_platform_data mop500_sdi1_data = { + .ocr_mask = MMC_VDD_29_30, + .f_max = 15000000, + .capabilities = MMC_CAP_4_BIT_DATA | + MMC_CAP_SDIO_IRQ | + MMC_CAP_BROKEN_SDIO_CMD53, + .gpio_cd = -1, + .gpio_wp = -1, +#ifdef MMC_WITH_DMA /* To be verified. */ +#ifdef CONFIG_STE_DMA40 + .dma_filter = stedma40_filter, + .dma_rx_param = &sdi1_dma_cfg_rx, + .dma_tx_param = &sdi1_dma_cfg_tx, +#endif +#endif +}; + /* * SDI 2 (POP eMMC, not on DB8500ed) */ -- cgit v1.2.3 From e36be56ab4994f19bce00da913a7cafc0c65a31d Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 22 Mar 2011 17:06:53 +0100 Subject: STM: MIPI System Trace Module char driver Signed-off-by: Philippe Langlais --- Documentation/trace/stm-trace.txt | 193 +++++++++ drivers/misc/Kconfig | 1 + drivers/misc/Kconfig.stm | 114 ++++++ drivers/misc/Makefile | 1 + drivers/misc/stm.c | 797 ++++++++++++++++++++++++++++++++++++++ include/Kbuild | 1 + include/trace/Kbuild | 1 + include/trace/stm.h | 223 +++++++++++ 8 files changed, 1331 insertions(+) create mode 100644 Documentation/trace/stm-trace.txt create mode 100644 drivers/misc/Kconfig.stm create mode 100644 drivers/misc/stm.c create mode 100644 include/trace/Kbuild create mode 100644 include/trace/stm.h diff --git a/Documentation/trace/stm-trace.txt b/Documentation/trace/stm-trace.txt new file mode 100644 index 00000000000..cd73c2b87b7 --- /dev/null +++ b/Documentation/trace/stm-trace.txt @@ -0,0 +1,193 @@ + MIPI System Trace Module driver + =============================== + +Copyright (C) ST-Ericsson SA 2011 + Authors: Pierre Peiffer + Philippe Langlais + License: The GNU Free Documentation License, Version 1.2 + (dual licensed under the GPL v2) + +Hardware overview +================= + This hardware collects and provides simple tracepoints, + so a system processor (in our case the main ARM CPU, + or some small CPUs and DSPs) can write some data, + up to 8 bytes, into a register and out comes a log entry + with a time stamp (20ns resolution) on one of 256 channels. Also + hardware tracepoints are supported. + + This module external interface is a pad on the chip + which complies to the MIPI System Trace Protocol v1.0 + (see http://www.mipi.org/specifications/debug) + and the actual trace output can be read by an + electronic probe, not by software so it cannot be intercepted by + the CPU and reach Linux userspace. + + Bandwidth depends on number of lines & bus frequency (for example on ux500 + SoC 4 lines at max 100MHz eg max 400Mbit/s shared between 7 cores). + Transmit FIFO size: 256 samples up to 8 bytes. + On ux500 platform there is 2 contiguous STM blocks (eg 512 channels) + +Software Overview +================= + Write atomicity and write order on STM trace channels is ensured by the fact + we try to allocate one channel by execution thread (no concurrent access). + There is 2 modes one lossless but intrusive aka Software mode and + another lossy mode less intrusive aka Hardware mode, by default + all sources are configured in Hardware mode and enabled. + The end of data packet is marked by a time stamp on latest byte(s) only. + +Kernel API +---------- + Configuration functions: + output trace clock frequency, trace mode, output port configuration + and enable/disable STM trace sources + Expose a debugfs interface too for STM trace control + + Alloc/free STM trace channel functions + + Set of low level atomic trace functions for 1, 2, 4 or 8 bytes + with & w/o time stamp + + Higher level lockless trace functions: + stm_trace_buffer: + allocate a channel in 128 highest channels available + output the trace buffer with arbitrary length + (latest byte(s) automatically time stamped) then free the channel + stm_trace_buffer_onchannel: + use given channel to output the trace buffer + with arbitrary length (latest byte(s) automatically time stamped) + + File IO output console like interface (open, close, write) + + See & drivers/misc/stm.c for more detail + +debugfs API +----------- +clockdiv: + This is used to set or display the current clock divisor + that is configured + +connection: + This is used to set or display the current output connector + that is configured (common values, 0 not connected, 1 for default + connection, 3 on Ux500 for APE MIPI34 connection) + +free_channels: + This is used to display the total number of free channels + +masters_enable: + This sets or displays whether the STM trace sources + are activated. Each bits represent the state of corresponding source: + 0 for disable or 1 to enable it. + +masters_modes: + This sets or displays the STM trace sources modes. + Each bits represent the mode of corresponding source: + 0 for Sofware lossless mode or 1 for Hardware lossy mode. + +User API +-------- + IOCTLs or debugfs for controls + 2 levels API for tracing: + - Standard write function, in this case a channel is automatically + allocated at first write, after you can channel number + with IOCTL STM_GET_CHANNEL_NO + - mmap for direct access of all STM trace channels port plus + a set of IOCTLs for alloc/free channels, in this case you can + write your own lib to easiest its usage + +Examples of using the STM +========================= +First mount debugfs with: +mount -t debugfs none /sys/kernel/debug + +In a shell scipt +---------------- +It's as easy as: + echo "My trace point" > /dev/stm + +To avoid trace overflow, increase STM clock by decreasing the clockdiv with: + echo 1 >/sys/kernel/debug/stm/clockdiv # now use DIV2 instead of default DIV8 +If not enough you can disable some sources with: + echo YourEnableSources > /sys/kernel/debug/stm/masters_enable +If always not enough the ultime intrusive way is to change the sources mode +and set the corresponding sources in Software mode (set corresponding source +bit to 0) with: + echo YourModeSources > /sys/kernel/debug/stm/masters_modes + (be aware some source doesn't support Software mode => keep it in HW mode) + +NB: on Ux500 platform, first you have to configure STM output port to switch +APE tracing on MIPI34 connector with: + echo 3 > /sys/kernel/debug/stm/connection + +In C language +------------- + +The easy way more intrusive (with STM buffer recopy): + +#include + +int fd, i; +char buf[1024]; // Try to align this buffer on 64 bits if possible + + fd = open("/dev/stm", O_WRONLY); + snprintf(buf, 1024, "STM0 Hello world\n"); + write(fd, buf, strlen(buf)); + ioctl(fd, STM_GET_CHANNEL_NO, &i); + snprintf(buf, 1024, "Use channel #%d\n", i); + write(fd, buf, strlen(buf)); + close(fd); + +NB: You can call open("/dev/stm", O_WRONLY) as many times as necessary +to allocate a different channel to avoid concurrency in your +multithreaded application. + +The more efficient way, use mmap'ed STM channels memory (to put in a lib): + +#include + +int fd, i, c, l, maxChannels; +char buf[1024]; // Try to align this buffer on 64 bits if possible +volatile struct stm_channel *channels; // mmap'ed channels area + + fd = open("/dev/stm", O_RDWR); + ioctl(fd0, STM_GET_NB_MAX_CHANNELS, &maxChannels); + channels = (struct stm_channel *)mmap(0, maxChannels*sizeof(*channels), + PROT_WRITE, MAP_SHARED, fd, 0); + assert(channels != MAP_FAILED); + + if (!ioctl(fd, STM_GET_FREE_CHANNEL, &c)) { + l = snprintf(buf, 1024, "STM0 Hello world on channel #%d\n", c); + // lazy implementation you have to send buffer by 8 Bytes when possible + // and be sure you don't share this channel with others threads + for (i=0; i for ST-Ericsson. + * Philippe Langlais for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* STM Registers */ +#define STM_CR (stm.virtbase) +#define STM_MMC (stm.virtbase + 0x008) +#define STM_TER (stm.virtbase + 0x010) +#define STMPERIPHID0 (stm.virtbase + 0xFC0) +#define STMPERIPHID1 (stm.virtbase + 0xFC8) +#define STMPERIPHID2 (stm.virtbase + 0xFD0) +#define STMPERIPHID3 (stm.virtbase + 0xFD8) +#define STMPCELLID0 (stm.virtbase + 0xFE0) +#define STMPCELLID1 (stm.virtbase + 0xFE8) +#define STMPCELLID2 (stm.virtbase + 0xFF0) +#define STMPCELLID3 (stm.virtbase + 0xFF8) + +#define STM_CLOCK_SHIFT 6 +#define STM_CLOCK_MASK 0x1C0 + +/* Hardware mode for all sources */ +#define STM_MMC_DEFAULT 0xFFFFFFFF + +/* Max number of channels (multiple of 256) */ +#define STM_NUMBER_OF_CHANNEL CONFIG_STM_NUMBER_OF_CHANNEL + +/* # dynamically allocated channel with stm_trace_buffer */ +#define NB_KERNEL_DYNAMIC_CHANNEL 128 + +static struct stm_device { + const struct stm_platform_data *pdata; + void __iomem *virtbase; + /* Used to register the allocated channels */ + DECLARE_BITMAP(ch_bitmap, STM_NUMBER_OF_CHANNEL); +} stm; + +volatile struct stm_channel __iomem *stm_channels; + +static struct cdev cdev; +static struct class *stm_class; +static int stm_major; + +static DEFINE_SPINLOCK(lock); + +/* Middle value for clock divisor */ +static enum clock_div stm_clockdiv = STM_CLOCK_DIV8; + +/* Default value for STM output connection */ +static enum stm_connection_type stm_connection = STM_DEFAULT_CONNECTION; + +#define STM_BUFSIZE 256 +struct channel_data { + DECLARE_BITMAP(bitmap, STM_NUMBER_OF_CHANNEL); + int numero; + spinlock_t lock; + u8 data_buffer[STM_BUFSIZE]; +}; + +static u64 stm_printk_buf[1024/sizeof(u64)]; +static arch_spinlock_t stm_buf_lock = + (arch_spinlock_t)__ARCH_SPIN_LOCK_UNLOCKED; + +int stm_alloc_channel(int offset) +{ + int channel; + + /* Look for a free channel from offset */ + do { + channel = find_next_zero_bit(stm.ch_bitmap, + STM_NUMBER_OF_CHANNEL, offset); + } while ((channel < STM_NUMBER_OF_CHANNEL) + && test_and_set_bit(channel, stm.ch_bitmap)); + return channel; +} +EXPORT_SYMBOL(stm_alloc_channel); + +void stm_free_channel(int channel) +{ + clear_bit(channel, stm.ch_bitmap); +} +EXPORT_SYMBOL(stm_free_channel); + +static int stm_get_channel(struct channel_data *ch_data, int __user *arg) +{ + int channel, err; + + channel = stm_alloc_channel(0); + if (channel < STM_NUMBER_OF_CHANNEL) { + /* One free found ! */ + err = put_user(channel, arg); + if (err) + stm_free_channel(channel); + else + /* Register it in the context of the file */ + set_bit(channel, ch_data->bitmap); + } else + err = -ENOMEM; + return err; +} + +static int stm_release_channel(struct channel_data *ch_data, int channel) +{ + if ((channel < 0) || (channel >= STM_NUMBER_OF_CHANNEL)) + return -EINVAL; + stm_free_channel(channel); + clear_bit(channel, ch_data->bitmap); + return 0; +} + +/* + * Trace a buffer on a given channel + * with auto time stamping on last byte(s) only + */ +int stm_trace_buffer_onchannel(int channel, + const void *data, size_t length) +{ + int i, mod64; + volatile struct stm_channel __iomem *pch; + + if (channel >= STM_NUMBER_OF_CHANNEL || !stm_channels) + return 0; + + pch = &stm_channels[channel]; + + /* Align data pointer to u64 & time stamp last byte(s) */ + mod64 = (int)data & 7; + i = length - 8 + mod64; + switch (mod64) { + case 0: + if (i) + pch->no_stamp64 = *(u64 *)data; + else { + pch->stamp64 = *(u64 *)data; + return length; + } + data += 8; + break; + case 1: + pch->no_stamp8 = *(u8 *)data; + pch->no_stamp16 = *(u16 *)(data+1); + if (i) + pch->no_stamp32 = *(u32 *)(data+3); + else { + pch->stamp32 = *(u32 *)(data+3); + return length; + } + data += 7; + break; + case 2: + pch->no_stamp16 = *(u16 *)data; + if (i) + pch->no_stamp32 = *(u32 *)(data+2); + else { + pch->stamp32 = *(u32 *)(data+2); + return length; + } + data += 6; + break; + case 3: + pch->no_stamp8 = *(u8 *)data; + if (i) + pch->no_stamp32 = *(u32 *)(data+1); + else { + pch->stamp32 = *(u32 *)(data+1); + return length; + } + data += 5; + break; + case 4: + if (i) + pch->no_stamp32 = *(u32 *)data; + else { + pch->stamp32 = *(u32 *)data; + return length; + } + data += 4; + break; + case 5: + pch->no_stamp8 = *(u8 *)data; + if (i) + pch->no_stamp16 = *(u16 *)(data+1); + else { + pch->stamp16 = *(u16 *)(data+1); + return length; + } + data += 3; + break; + case 6: + if (i) + pch->no_stamp16 = *(u16 *)data; + else { + pch->stamp16 = *(u16 *)data; + return length; + } + data += 2; + break; + case 7: + if (i) + pch->no_stamp8 = *(u8 *)data; + else { + pch->stamp8 = *(u8 *)data; + return length; + } + data++; + break; + } + for (;;) { + if (i > 8) { + pch->no_stamp64 = *(u64 *)data; + data += 8; + i -= 8; + } else if (i == 8) { + pch->stamp64 = *(u64 *)data; + break; + } else if (i > 4) { + pch->no_stamp32 = *(u32 *)data; + data += 4; + i -= 4; + } else if (i == 4) { + pch->stamp32 = *(u32 *)data; + break; + } else if (i > 2) { + pch->no_stamp16 = *(u16 *)data; + data += 2; + i -= 2; + } else if (i == 2) { + pch->stamp16 = *(u16 *)data; + break; + } else { + pch->stamp8 = *(u8 *)data; + break; + } + } + return length; +} +EXPORT_SYMBOL(stm_trace_buffer_onchannel); + +static int stm_open(struct inode *inode, struct file *file) +{ + struct channel_data *channel_data; + + channel_data = kzalloc(sizeof(struct channel_data), GFP_KERNEL); + if (channel_data == NULL) + return -ENOMEM; + + spin_lock_init(&channel_data->lock); + channel_data->numero = -1; /* Channel not yet allocated */ + file->private_data = channel_data; + + return 0; +} + +static int stm_release(struct inode *inode, struct file *file) +{ + struct channel_data *channel; + + channel = (struct channel_data *)file->private_data; + + /* Free allocated channel if necessary */ + if (channel->numero != -1) + stm_free_channel(channel->numero); + + bitmap_andnot(stm.ch_bitmap, stm.ch_bitmap, + channel->bitmap, STM_NUMBER_OF_CHANNEL); + + kfree(channel); + return 0; +} + +static ssize_t stm_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct channel_data *channel = file->private_data; + + /* Alloc channel at first write */ + if (channel->numero == -1) { + channel->numero = stm_alloc_channel(0); + if (channel->numero > STM_NUMBER_OF_CHANNEL) + return -ENOMEM; + } + + if (size > STM_BUFSIZE) + size = STM_BUFSIZE; + + spin_lock(&channel->lock); + + if (copy_from_user + (channel->data_buffer, (void __user *) buf, size)) { + spin_unlock(&channel->lock); + return -EFAULT; + } + size = stm_trace_buffer_onchannel(channel->numero, + channel->data_buffer, size); + + spin_unlock(&channel->lock); + + return size; +} + +static int stm_mmap(struct file *file, struct vm_area_struct *vma) +{ + /* + * Don't allow a mapping that covers more than the STM channels + */ + if ((vma->vm_end - vma->vm_start) > + STM_NUMBER_OF_CHANNEL*sizeof(struct stm_channel)) + return -EINVAL; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (io_remap_pfn_range(vma, vma->vm_start, + stm.pdata->channels_phys_base>>PAGE_SHIFT, + STM_NUMBER_OF_CHANNEL*sizeof(struct stm_channel), + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +/* Enable the trace for given sources (bitfield) */ +static void stm_enable_src(unsigned int v) +{ + unsigned int cr_val; + spin_lock(&lock); + cr_val = readl(STM_CR); + cr_val &= ~STM_CLOCK_MASK; + writel(cr_val|(stm_clockdiv<private_data; + + switch (cmd) { + + case STM_CONNECTION: + if (stm.pdata->stm_connection) + stm.pdata->stm_connection(arg); + stm_connection = arg; + break; + + case STM_DISABLE: + stm_disable_src(); + break; + + case STM_GET_NB_MAX_CHANNELS: + err = put_user(STM_NUMBER_OF_CHANNEL, (unsigned int *)arg); + break; + + case STM_GET_NB_FREE_CHANNELS: + err = put_user(stm_nb_free_channels(), (unsigned int *)arg); + break; + + case STM_GET_CHANNEL_NO: + err = put_user(channel->numero, (unsigned int *)arg); + break; + + case STM_SET_CLOCK_DIV: + err = stm_set_ckdiv((enum clock_div) arg); + break; + + case STM_SET_MODE: + stm_set_modes(arg); + break; + + case STM_GET_MODE: + err = put_user(stm_get_modes(), (unsigned int *)arg); + break; + + case STM_GET_CTRL_REG: + err = put_user(stm_get_cr(), (unsigned int *)arg); + break; + + case STM_ENABLE_SRC: + stm_enable_src(arg); + break; + + case STM_GET_FREE_CHANNEL: + err = stm_get_channel(channel, (int *)arg); + break; + + case STM_RELEASE_CHANNEL: + err = stm_release_channel(channel, arg); + break; + + default: + err = -EINVAL; + break; + } + + return err; +} + +/* + * Trace a buffer on a dynamically allocated channel + * with auto time stamping on the first byte(s) only + * Dynamic channel number >= + * STM_NUMBER_OF_CHANNEL - NB_KERNEL_DYNAMIC_CHANNEL + */ +int stm_trace_buffer(const void *data, size_t length) +{ + int channel; + + channel = stm_alloc_channel(STM_NUMBER_OF_CHANNEL + - NB_KERNEL_DYNAMIC_CHANNEL); + if (channel < STM_NUMBER_OF_CHANNEL) { + length = stm_trace_buffer_onchannel(channel, data, length); + stm_free_channel(channel); + return length; + } + return 0; +} +EXPORT_SYMBOL(stm_trace_buffer); + +static const struct file_operations stm_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = stm_ioctl, + .open = stm_open, + .llseek = no_llseek, + .write = stm_write, + .release = stm_release, + .mmap = stm_mmap, +}; + +/* + * Init and deinit driver + */ + +static int __devinit stm_probe(struct platform_device *pdev) +{ + int retval = 0; + + if (!pdev || !pdev->dev.platform_data) { + pr_alert("No device/platform_data found on STM driver\n"); + return -ENODEV; + } + + stm.pdata = pdev->dev.platform_data; + + cdev_init(&cdev, &stm_fops); + cdev.owner = THIS_MODULE; + + stm_channels = + ioremap_nocache(stm.pdata->channels_phys_base, + STM_NUMBER_OF_CHANNEL*sizeof(*stm_channels)); + if (stm_channels == NULL) { + dev_err(&pdev->dev, "could not remap STM Msg register\n"); + return -ENODEV; + } + + stm.virtbase = ioremap_nocache(stm.pdata->regs_phys_base, SZ_4K); + if (stm.virtbase == NULL) { + retval = -EIO; + dev_err(&pdev->dev, "could not remap STM Register\n"); + goto err_channels; + } + + retval = cdev_add(&cdev, MKDEV(stm_major, 0), 1); + if (retval) { + dev_err(&pdev->dev, "chardev registration failed\n"); + goto err_channels; + } + + if (IS_ERR(device_create(stm_class, &pdev->dev, + MKDEV(stm_major, 0), NULL, STM_DEV_NAME))) + dev_err(&pdev->dev, "can't create device\n"); + + /* Check chip IDs if necessary */ + if (stm.pdata->id_mask) { + u32 periph_id, cell_id; + + periph_id = (readb(STMPERIPHID3)<<24) + + (readb(STMPERIPHID2)<<16) + + (readb(STMPERIPHID1)<<8) + + readb(STMPERIPHID0); + cell_id = (readb(STMPCELLID3)<<24) + + (readb(STMPCELLID2)<<16) + + (readb(STMPCELLID1)<<8) + + readb(STMPCELLID0); + /* Only warns if it isn't a ST-Ericsson supported one */ + if ((periph_id & stm.pdata->id_mask) != 0x00080dec || + cell_id != 0xb105f00d) { + dev_warn(&pdev->dev, "STM-Trace IC not compatible\n"); + dev_warn(&pdev->dev, "periph_id=%x\n", periph_id); + dev_warn(&pdev->dev, "pcell_id=%x\n", cell_id); + } + } + + /* Reserve channels if necessary */ + if (stm.pdata->channels_reserved_sz) { + int i; + + for (i = 0; i < stm.pdata->channels_reserved_sz; i++) { + set_bit(stm.pdata->channels_reserved[i], + stm.ch_bitmap); + } + } + /* Reserve kernel trace channels on demand */ +#ifdef CONFIG_STM_PRINTK + set_bit(CONFIG_STM_PRINTK_CHANNEL, stm.ch_bitmap); +#endif +#ifdef CONFIG_STM_FTRACE + set_bit(CONFIG_STM_FTRACE_CHANNEL, stm.ch_bitmap); +#endif +#ifdef CONFIG_STM_CTX_SWITCH + set_bit(CONFIG_STM_CTX_SWITCH_CHANNEL, stm.ch_bitmap); +#endif +#ifdef CONFIG_STM_WAKEUP + set_bit(CONFIG_STM_WAKEUP_CHANNEL, stm.ch_bitmap); +#endif +#ifdef CONFIG_STM_STACK_TRACE + set_bit(CONFIG_STM_STACK_TRACE_CHANNEL, stm.ch_bitmap); +#endif +#ifdef CONFIG_STM_TRACE_PRINTK + set_bit(CONFIG_STM_TRACE_PRINTK_CHANNEL, stm.ch_bitmap); + set_bit(CONFIG_STM_TRACE_BPRINTK_CHANNEL, stm.ch_bitmap); +#endif + + if (stm.pdata->stm_connection) { + retval = stm.pdata->stm_connection(stm_connection); + if (retval) { + dev_err(&pdev->dev, "failed to connect STM output\n"); + goto err_channels; + } + } + + /* Enable STM Masters given in pdata */ + if (stm.pdata->masters_enabled) + stm_enable_src(stm.pdata->masters_enabled); + + stm_set_modes(STM_MMC_DEFAULT); /* Set all sources in HW mode */ + + dev_info(&pdev->dev, "STM-Trace driver probed successfully\n"); + stm_printk("STM-Trace driver initialized\n"); + return 0; + +err_channels: + iounmap(stm_channels); + return retval; +} + +static int __devexit stm_remove(struct platform_device *pdev) +{ + device_destroy(stm_class, MKDEV(stm_major, 0)); + cdev_del(&cdev); + + if (stm.pdata->stm_connection) + (void) stm.pdata->stm_connection(STM_DISCONNECT); + + stm_disable_src(); + iounmap(stm.virtbase); + iounmap(stm_channels); + + return 0; +} + +int stm_printk(const char *fmt, ...) +{ + int ret; + size_t size; + va_list args; + + va_start(args, fmt); + arch_spin_lock(&stm_buf_lock); + size = vscnprintf((char *)stm_printk_buf, + sizeof(stm_printk_buf), fmt, args); + ret = stm_trace_buffer(stm_printk_buf, size); + arch_spin_unlock(&stm_buf_lock); + va_end(args); + return ret; +} +EXPORT_SYMBOL(stm_printk); + +/* + * Debugfs interface + */ + +static int stm_connection_show(void *data, u64 *val) +{ + *val = stm_connection; + return 0; +} + +static int stm_connection_set(void *data, u64 val) +{ + if (stm.pdata->stm_connection) { + stm_connection = val; + stm.pdata->stm_connection(val); + } + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(stm_connection_fops, stm_connection_show, + stm_connection_set, "%llu\n"); + +static int stm_clockdiv_show(void *data, u64 *val) +{ + *val = stm_clockdiv; + return 0; +} + +static int stm_clockdiv_set(void *data, u64 val) +{ + stm_set_ckdiv(val); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(stm_clockdiv_fops, stm_clockdiv_show, + stm_clockdiv_set, "%llu\n"); + +static int stm_masters_enable_show(void *data, u64 *val) +{ + *val = readl(STM_TER); + return 0; +} + +static int stm_masters_enable_set(void *data, u64 val) +{ + stm_enable_src(val); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(stm_masters_enable_fops, stm_masters_enable_show, + stm_masters_enable_set, "%08llx\n"); + +static int stm_masters_modes_show(void *data, u64 *val) +{ + *val = stm_get_modes(); + return 0; +} + +static int stm_masters_modes_set(void *data, u64 val) +{ + stm_set_modes(val); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(stm_masters_modes_fops, stm_masters_modes_show, + stm_masters_modes_set, "%08llx\n"); + +/* Count # of free channels */ +static int stm_free_channels_show(void *data, u64 *val) +{ + *val = stm_nb_free_channels(); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(stm_free_channels_fops, stm_free_channels_show, + NULL, "%lld\n"); + +static __init int stm_init_debugfs(void) +{ + struct dentry *d_stm; + + d_stm = debugfs_create_dir(STM_DEV_NAME, NULL); + if (!d_stm) + return -ENOMEM; + + (void) debugfs_create_file("connection", S_IRUGO | S_IWUGO, d_stm, + NULL, &stm_connection_fops); + (void) debugfs_create_file("clockdiv", S_IRUGO | S_IWUGO, d_stm, + NULL, &stm_clockdiv_fops); + (void) debugfs_create_file("masters_enable", S_IRUGO | S_IWUGO, d_stm, + NULL, &stm_masters_enable_fops); + (void) debugfs_create_file("masters_modes", S_IRUGO | S_IWUGO, d_stm, + NULL, &stm_masters_modes_fops); + (void) debugfs_create_file("free_channels", S_IRUGO, d_stm, + NULL, &stm_free_channels_fops); + return 0; +} +fs_initcall(stm_init_debugfs); + +static struct platform_driver stm_driver = { + .probe = stm_probe, + .remove = __devexit_p(stm_remove), + .driver = { + .name = STM_DEV_NAME, + .owner = THIS_MODULE, + } +}; + +static int __init stm_init(void) +{ + int retval; + dev_t dev; + + stm_class = class_create(THIS_MODULE, STM_DEV_NAME); + if (IS_ERR(stm_class)) { + pr_err("stm: can't register stm class\n"); + return PTR_ERR(stm_class); + } + + retval = alloc_chrdev_region(&dev, 0, 1, STM_DEV_NAME); + if (retval) { + pr_err("stm: can't register character device\n"); + class_destroy(stm_class); + return retval; + } + stm_major = MAJOR(dev); + return platform_driver_register(&stm_driver); +} + +static void __exit stm_exit(void) +{ + platform_driver_unregister(&stm_driver); + unregister_chrdev_region(MKDEV(stm_major, 0), 1); + class_destroy(stm_class); +} + +arch_initcall(stm_init); /* STM init ASAP need to wait GPIO init */ +module_exit(stm_exit); + +MODULE_AUTHOR("Paul Ghaleb - ST Microelectronics"); +MODULE_AUTHOR("Pierre Peiffer - ST-Ericsson"); +MODULE_AUTHOR("Philippe Langlais - ST-Ericsson"); +MODULE_DESCRIPTION("System Trace Module driver"); +MODULE_ALIAS("stm"); +MODULE_ALIAS("stm-trace"); +MODULE_LICENSE("GPL v2"); diff --git a/include/Kbuild b/include/Kbuild index 8d226bfa269..506f6d7dba7 100644 --- a/include/Kbuild +++ b/include/Kbuild @@ -10,3 +10,4 @@ header-y += video/ header-y += drm/ header-y += xen/ header-y += scsi/ +header-y += trace/ diff --git a/include/trace/Kbuild b/include/trace/Kbuild new file mode 100644 index 00000000000..7e8b704d610 --- /dev/null +++ b/include/trace/Kbuild @@ -0,0 +1 @@ +header-y += stm.h diff --git a/include/trace/stm.h b/include/trace/stm.h new file mode 100644 index 00000000000..73048bc97c5 --- /dev/null +++ b/include/trace/stm.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * ST-Ericsson STM Trace driver + * + * Author: Pierre Peiffer for ST-Ericsson. + * Philippe Langlais for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef STM_H +#define STM_H + +#define STM_DEV_NAME "stm" + +/* One single channel mapping */ +struct stm_channel { + union { + __u8 no_stamp8; + __u16 no_stamp16; + __u32 no_stamp32; + __u64 no_stamp64; + }; + union { + __u8 stamp8; + __u16 stamp16; + __u32 stamp32; + __u64 stamp64; + }; +}; + +/* Possible trace modes */ +#define STM_SW_LOSSLESS 0 /* Software mode: lossless data but intrusive */ +#define STM_HW_LOSSY 1 /* Hardware mode: lossy data but less intrusive */ + +/* Possible clock setting */ +enum clock_div { + STM_CLOCK_DIV2 = 0, + STM_CLOCK_DIV4, + STM_CLOCK_DIV6, + STM_CLOCK_DIV8, + STM_CLOCK_DIV10, + STM_CLOCK_DIV12, + STM_CLOCK_DIV14, + STM_CLOCK_DIV16, +}; + +/* ioctl commands */ +#define STM_CONNECTION _IOW('t', 0, enum stm_connection_type) +#define STM_DISABLE _IO('t', 1) +#define STM_GET_NB_MAX_CHANNELS _IOR('t', 2, int) +#define STM_GET_NB_FREE_CHANNELS _IOR('t', 3, int) +#define STM_GET_CHANNEL_NO _IOR('t', 4, int) +#define STM_SET_CLOCK_DIV _IOW('t', 5, enum clock_div) +#define STM_GET_CTRL_REG _IOR('t', 6, int) +#define STM_ENABLE_SRC _IOWR('t', 7, int) +#define STM_GET_FREE_CHANNEL _IOW('t', 8, int) +#define STM_RELEASE_CHANNEL _IOW('t', 9, int) +#define STM_SET_MODE _IOWR('t', 10, int) +#define STM_GET_MODE _IOR('t', 11, int) + +enum stm_connection_type { + STM_DISCONNECT = 0, + STM_DEFAULT_CONNECTION = 1, + STM_STE_MODEM_ON_MIPI34_NONE_ON_MIPI60 = 2, + STM_STE_APE_ON_MIPI34_NONE_ON_MIPI60 = 3, + STM_STE_MODEM_ON_MIPI34_APE_ON_MIPI60 = 4 +}; + +#ifdef __KERNEL__ + +struct stm_platform_data { + u32 regs_phys_base; + u32 channels_phys_base; + u32 id_mask; + u32 masters_enabled; + const s16 *channels_reserved; + int channels_reserved_sz; + int (*stm_connection)(enum stm_connection_type); +}; + +/* Channels base address */ +extern volatile struct stm_channel __iomem *stm_channels; + +/* Provides stm_trace_XX() and stm_tracet_XX() trace API */ +#define DEFLLTFUN(size) \ +static inline void stm_trace_##size(int channel, __u##size data) \ +{ \ + stm_channels[channel].no_stamp##size = data; \ +} \ +static inline void stm_tracet_##size(int channel, __u##size data) \ +{ \ + stm_channels[channel].stamp##size = data; \ +} \ + +DEFLLTFUN(8); +DEFLLTFUN(16); +DEFLLTFUN(32); +DEFLLTFUN(64); + +/* + * Trace a buffer on a given channel + * with auto time stamping on the last byte(s) only + */ +int stm_trace_buffer_onchannel(int channel, const void *data, size_t length); +/* + * Trace a buffer on a dynamically allocated channel + * with auto time stamping on the last byte(s) only + * Dynamic channel are allocated in the 128 highest channels + */ +int stm_trace_buffer(const void *data, size_t length); + +/* printk equivalent for STM */ +int stm_printk(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); + +#if defined(CONFIG_STM_PRINTK) +#define stm_dup_printk(buf, length) \ + stm_trace_buffer_onchannel(CONFIG_STM_PRINTK_CHANNEL, buf, length) + +#else +static inline int stm_dup_printk(char *buf, size_t size) +{ + return 0; +} +#endif + +#if defined(CONFIG_STM_TRACE_PRINTK) +static inline int stm_trace_printk_buf( + unsigned long ip, const char *buf, size_t size) +{ + stm_trace_32(CONFIG_STM_TRACE_PRINTK_CHANNEL, ip); + return stm_trace_buffer_onchannel(CONFIG_STM_TRACE_PRINTK_CHANNEL, + buf, size); +} + +static inline int stm_trace_bprintk_buf( + unsigned long ip, const char *fmt, const void *buf, size_t size) +{ + stm_trace_64(CONFIG_STM_TRACE_BPRINTK_CHANNEL, ((u64)ip<<32)+(u32)fmt); + return stm_trace_buffer_onchannel(CONFIG_STM_TRACE_PRINTK_CHANNEL, + buf, size); +} +#else +static inline int stm_trace_printk_buf( + unsigned long ip, const char *buf, size_t size) +{ + return 0; +} + +static inline int stm_trace_bprintk_buf( + unsigned long ip, const char *fmt, const char *buf, size_t size) +{ + return 0; +} +#endif + +#if defined(CONFIG_STM_FTRACE) +static inline void stm_ftrace(unsigned long ip, unsigned long parent_ip) +{ + stm_tracet_64(CONFIG_STM_FTRACE_CHANNEL, (((__u64)ip)<<32) + parent_ip); +} +#else +static inline void stm_ftrace(unsigned long ip, unsigned long parent_ip) +{ +} +#endif + +#if defined(CONFIG_STM_CTX_SWITCH) +static inline void stm_sched_switch(u32 prev_pid, u8 prev_prio, u8 prev_state, + u32 next_pid, u8 next_prio, u8 next_state, u32 next_cpu) +{ + stm_trace_64(CONFIG_STM_CTX_SWITCH_CHANNEL, + (((__u64)prev_pid)<<32) + next_pid); + stm_tracet_64(CONFIG_STM_CTX_SWITCH_CHANNEL, (((__u64)next_cpu)<<32) + + (prev_prio<<24) + (prev_state<<16) + + (next_prio<<8) + next_state); +} +#else +static inline void stm_sched_switch(u32 prev_pid, u8 prev_prio, u8 prev_state, + u32 next_pid, u8 next_prio, u8 next_state, u32 next_cpu) +{ +} +#endif + +#if defined(CONFIG_STM_WAKEUP) +static inline void stm_sched_wakeup(u32 prev_pid, u8 prev_prio, u8 prev_state, + u32 next_pid, u8 next_prio, u8 next_state, u32 next_cpu) +{ + stm_trace_64(CONFIG_STM_WAKEUP_CHANNEL, + (((__u64)prev_pid)<<32) + next_pid); + stm_tracet_64(CONFIG_STM_WAKEUP_CHANNEL, (((__u64)next_cpu)<<32) + + (prev_prio<<24) + (prev_state<<16) + + (next_prio<<8) + next_state); +} +#else +static inline void stm_sched_wakeup(u32 prev_pid, u8 prev_prio, u8 prev_state, + u32 next_pid, u8 next_prio, u8 next_state, u32 next_cpu) +{ +} +#endif + +#if defined(CONFIG_STM_STACK_TRACE) +static inline void stm_stack_trace(unsigned long *callers) +{ + while (*(callers + 1) != ULONG_MAX) { + stm_trace_32(CONFIG_STM_STACK_TRACE_CHANNEL, *callers++); + } + /* Time stamp the latest */ + stm_tracet_32(CONFIG_STM_STACK_TRACE_CHANNEL, *callers); +} +#else +static inline void stm_stack_trace(unsigned long *callers) +{ +} +#endif + +/* Alloc/Free STM channel */ +int stm_alloc_channel(int offset); +void stm_free_channel(int channel); + +#endif /* __KERNEL__ */ + +#endif /* STM_H */ -- cgit v1.2.3 From 942465cbbab1e6e6867ab5e95e62e035ff769c96 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 17 Mar 2011 09:42:52 +0100 Subject: mach-ux500: MIPI System Trace Module platform dependent driver part U5500 is not yet supported. Signed-off-by: Pierre Peiffer Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/cpu-db5500.c | 4 + arch/arm/mach-ux500/cpu-db8500.c | 4 + arch/arm/mach-ux500/devices-db8500.c | 181 +++++++++++++++++++++++++++++ arch/arm/mach-ux500/include/mach/devices.h | 1 + 4 files changed, 190 insertions(+) diff --git a/arch/arm/mach-ux500/cpu-db5500.c b/arch/arm/mach-ux500/cpu-db5500.c index 22705d246fc..4b1d579183c 100644 --- a/arch/arm/mach-ux500/cpu-db5500.c +++ b/arch/arm/mach-ux500/cpu-db5500.c @@ -217,6 +217,10 @@ static int usb_db5500_tx_dma_cfg[] = { void __init u5500_init_devices(void) { +#ifdef CONFIG_STM_TRACE + /* Early init for STM tracing */ + platform_device_register(&ux500_stm_device); +#endif db5500_add_gpios(); db5500_dma_init(); db5500_add_rtc(); diff --git a/arch/arm/mach-ux500/cpu-db8500.c b/arch/arm/mach-ux500/cpu-db8500.c index 15bb1a085ce..d5cccfc3890 100644 --- a/arch/arm/mach-ux500/cpu-db8500.c +++ b/arch/arm/mach-ux500/cpu-db8500.c @@ -376,6 +376,10 @@ static int usb_db8500_tx_dma_cfg[] = { */ void __init u8500_init_devices(void) { +#ifdef CONFIG_STM_TRACE + /* Early init for STM tracing */ + platform_device_register(&ux500_stm_device); +#endif if (cpu_is_u8500ed()) dma40_u8500ed_fixup(); diff --git a/arch/arm/mach-ux500/devices-db8500.c b/arch/arm/mach-ux500/devices-db8500.c index 73b17404b19..40330c711d5 100644 --- a/arch/arm/mach-ux500/devices-db8500.c +++ b/arch/arm/mach-ux500/devices-db8500.c @@ -2,6 +2,10 @@ * Copyright (C) ST-Ericsson SA 2010 * * Author: Rabin Vincent for ST-Ericsson + * + * Author: Pierre Peiffer for ST-Ericsson. + * for the System Trace Module part. + * * License terms: GNU General Public License (GPL) version 2 */ @@ -12,11 +16,18 @@ #include #include #include +#include #include +#include #include #include +#include + +#include + +#include "pins-db8500.h" #include "ste-dma40-db8500.h" @@ -195,3 +206,173 @@ struct platform_device u8500_ske_keypad_device = { .num_resources = ARRAY_SIZE(keypad_resources), .resource = keypad_resources, }; + +#ifdef CONFIG_STM_TRACE +static pin_cfg_t mop500_stm_mipi34_pins[] = { + GPIO70_STMAPE_CLK, + GPIO71_STMAPE_DAT3, + GPIO72_STMAPE_DAT2, + GPIO73_STMAPE_DAT1, + GPIO74_STMAPE_DAT0, + GPIO75_U2_RXD, + GPIO76_U2_TXD, +}; + +static pin_cfg_t mop500_stm_mipi60_pins[] = { + GPIO153_U2_RXD, + GPIO154_U2_TXD, + GPIO155_STMAPE_CLK, + GPIO156_STMAPE_DAT3, + GPIO157_STMAPE_DAT2, + GPIO158_STMAPE_DAT1, + GPIO159_STMAPE_DAT0, +}; + +static pin_cfg_t mop500_ske_pins[] = { + GPIO153_KP_I7, + GPIO154_KP_I6, + GPIO155_KP_I5, + GPIO156_KP_I4, + GPIO157_KP_O7, + GPIO158_KP_O6, + GPIO159_KP_O5, +}; + +static int stm_ste_disable_ape_on_mipi60(void) +{ + int retval; + + retval = nmk_config_pins_sleep(ARRAY_AND_SIZE(mop500_stm_mipi60_pins)); + if (retval) + pr_err("STM: Failed to disable MIPI60\n"); + else { + retval = nmk_config_pins(ARRAY_AND_SIZE(mop500_ske_pins)); + if (retval) + pr_err("STM: Failed to enable SKE gpio\n"); + } + return retval; +} + +/* + * Manage STM output pins connection (MIP34/MIPI60 connectors) + */ +static int stm_ste_connection(enum stm_connection_type con_type) +{ + int retval = -EINVAL; + u32 gpiocr = readl(PRCM_GPIOCR); + + if (con_type != STM_DISCONNECT) { + /* Always enable MIPI34 GPIO pins */ + retval = nmk_config_pins( + ARRAY_AND_SIZE(mop500_stm_mipi34_pins)); + if (retval) { + pr_err("STM: Failed to enable MIPI34\n"); + return retval; + } + } + + switch (con_type) { + case STM_DEFAULT_CONNECTION: + case STM_STE_MODEM_ON_MIPI34_NONE_ON_MIPI60: + /* Enable altC3 on GPIO70-74 (STMMOD) & GPIO75-76 (UARTMOD) */ + gpiocr |= (PRCM_GPIOCR_DBG_STM_MOD_CMD1 + | PRCM_GPIOCR_DBG_UARTMOD_CMD0); + writel(gpiocr, PRCM_GPIOCR); + retval = stm_ste_disable_ape_on_mipi60(); + break; + + case STM_STE_APE_ON_MIPI34_NONE_ON_MIPI60: + /* Disable altC3 on GPIO70-74 (STMMOD) & GPIO75-76 (UARTMOD) */ + gpiocr &= ~(PRCM_GPIOCR_DBG_STM_MOD_CMD1 + | PRCM_GPIOCR_DBG_UARTMOD_CMD0); + writel(gpiocr, PRCM_GPIOCR); + retval = stm_ste_disable_ape_on_mipi60(); + break; + + case STM_STE_MODEM_ON_MIPI34_APE_ON_MIPI60: + /* Enable altC3 on GPIO70-74 (STMMOD) and GPIO75-76 (UARTMOD) */ + gpiocr |= (PRCM_GPIOCR_DBG_STM_MOD_CMD1 + | PRCM_GPIOCR_DBG_UARTMOD_CMD0); + writel(gpiocr, PRCM_GPIOCR); + + /* Enable APE on MIPI60 */ + retval = nmk_config_pins_sleep(ARRAY_AND_SIZE(mop500_ske_pins)); + if (retval) + pr_err("STM: Failed to disable SKE GPIO\n"); + else { + retval = nmk_config_pins( + ARRAY_AND_SIZE(mop500_stm_mipi60_pins)); + if (retval) + pr_err("STM: Failed to enable MIPI60\n"); + } + break; + + case STM_DISCONNECT: + retval = nmk_config_pins_sleep( + ARRAY_AND_SIZE(mop500_stm_mipi34_pins)); + if (retval) + pr_err("STM: Failed to disable MIPI34\n"); + + retval = stm_ste_disable_ape_on_mipi60(); + break; + + default: + pr_err("STM: bad connection type\n"); + break; + } + return retval; +} + +/* Possible STM sources (masters) on ux500 */ +enum stm_master { + STM_ARM0 = 0, + STM_ARM1 = 1, + STM_SVA = 2, + STM_SIA = 3, + STM_SIA_XP70 = 4, + STM_PRCMU = 5, + STM_MCSBAG = 9 +}; + +#define STM_ENABLE_ARM0 BIT(STM_ARM0) +#define STM_ENABLE_ARM1 BIT(STM_ARM1) +#define STM_ENABLE_SVA BIT(STM_SVA) +#define STM_ENABLE_SIA BIT(STM_SIA) +#define STM_ENABLE_SIA_XP70 BIT(STM_SIA_XP70) +#define STM_ENABLE_PRCMU BIT(STM_PRCMU) +#define STM_ENABLE_MCSBAG BIT(STM_MCSBAG) + +/* + * These are the channels used by NMF and some external softwares + * expect the NMF traces to be output on these channels + * For legacy reason, we need to reserve them. + */ +static const s16 stm_channels_reserved[] = { + 100, /* NMF MPCEE channel */ + 101, /* NMF CM channel */ + 151, /* NMF HOSTEE channel */ +}; + +/* On Ux500 we 2 consecutive STMs therefore 512 channels available */ +static struct stm_platform_data stm_pdata = { + .regs_phys_base = U8500_STM_REG_BASE, + .channels_phys_base = U8500_STM_BASE, + .id_mask = 0x000fffff, /* Ignore revisions differences */ + .channels_reserved = stm_channels_reserved, + .channels_reserved_sz = ARRAY_SIZE(stm_channels_reserved), + /* Enable all except MCSBAG */ + .masters_enabled = STM_ENABLE_ARM0 | STM_ENABLE_ARM1 | + STM_ENABLE_SVA | STM_ENABLE_PRCMU | + STM_ENABLE_SIA | STM_ENABLE_SIA_XP70, + /* Provide function for MIPI34/MIPI60 STM connection */ + .stm_connection = stm_ste_connection, +}; + +struct platform_device ux500_stm_device = { + .name = "stm", + .id = -1, + .dev = { + .platform_data = &stm_pdata, + }, +}; +#endif /* CONFIG_UX500_STM */ diff --git a/arch/arm/mach-ux500/include/mach/devices.h b/arch/arm/mach-ux500/include/mach/devices.h index 020b6369a30..c246312da4b 100644 --- a/arch/arm/mach-ux500/include/mach/devices.h +++ b/arch/arm/mach-ux500/include/mach/devices.h @@ -13,6 +13,7 @@ struct amba_device; extern struct platform_device u5500_gpio_devs[]; extern struct platform_device u8500_gpio_devs[]; +extern struct platform_device ux500_stm_device; extern struct amba_device ux500_pl031_device; extern struct platform_device u8500_dma40_device; -- cgit v1.2.3 From 18b2697377290b6408d1fb9625bf2cb76fc3c01e Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 14 Mar 2011 15:40:02 +0100 Subject: trace: add STM trace hooks for printk, trace_printk, trace_function, stack trace and scheduler wakeup/context switch tracing Signed-off-by: Philippe Langlais --- kernel/printk.c | 5 +++++ kernel/trace/trace.c | 10 ++++++++++ kernel/trace/trace_events.c | 3 +++ kernel/trace/trace_sched_switch.c | 10 ++++++++++ 4 files changed, 28 insertions(+) diff --git a/kernel/printk.c b/kernel/printk.c index 35185392173..90ddb222c12 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -41,6 +41,8 @@ #include #include #include +#include +#include #include @@ -893,6 +895,9 @@ asmlinkage int vprintk(const char *fmt, va_list args) } } + /* Send printk buffer to MIPI STM trace hardware too if enable */ + stm_dup_printk(printk_buf, printed_len); + /* * Copy the output into log_buf. If the caller didn't provide * the appropriate log prefix, we insert them here diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index ee9c921d7f2..8b4b6d756d5 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "trace.h" #include "trace_output.h" @@ -1222,6 +1223,8 @@ trace_function(struct trace_array *tr, if (!filter_check_discard(call, entry, buffer, event)) ring_buffer_unlock_commit(buffer, event); + + stm_ftrace(ip, parent_ip); } void @@ -1256,8 +1259,11 @@ static void __ftrace_trace_stack(struct ring_buffer *buffer, trace.entries = entry->caller; save_stack_trace(&trace); + if (!filter_check_discard(call, entry, buffer, event)) ring_buffer_unlock_commit(buffer, event); + + stm_stack_trace(trace.entries); } void ftrace_trace_stack(struct ring_buffer *buffer, unsigned long flags, @@ -1413,6 +1419,8 @@ int trace_vbprintk(unsigned long ip, const char *fmt, va_list args) ftrace_trace_stack(buffer, flags, 6, pc); } + stm_trace_bprintk_buf(ip, fmt, trace_buf, sizeof(u32) * len); + out_unlock: arch_spin_unlock(&trace_buf_lock); local_irq_restore(flags); @@ -1489,6 +1497,8 @@ int trace_array_vprintk(struct trace_array *tr, ftrace_trace_stack(buffer, irq_flags, 6, pc); } + stm_trace_printk_buf(ip, trace_buf, len); + out_unlock: arch_spin_unlock(&trace_buf_lock); raw_local_irq_restore(irq_flags); diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 686ec399f2a..4d30dce15ef 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -19,6 +19,7 @@ #include #include +#include #include "trace_output.h" @@ -1645,6 +1646,8 @@ function_test_events_call(unsigned long ip, unsigned long parent_ip) trace_nowake_buffer_unlock_commit(buffer, event, flags, pc); + stm_ftrace(ip, parent_ip); + out: atomic_dec(&per_cpu(ftrace_test_event_disable, cpu)); preempt_enable_notrace(); diff --git a/kernel/trace/trace_sched_switch.c b/kernel/trace/trace_sched_switch.c index 7e62c0a1845..a136fd86533 100644 --- a/kernel/trace/trace_sched_switch.c +++ b/kernel/trace/trace_sched_switch.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "trace.h" @@ -47,6 +48,10 @@ tracing_sched_switch_trace(struct trace_array *tr, if (!filter_check_discard(call, entry, buffer, event)) trace_buffer_unlock_commit(buffer, event, flags, pc); + + stm_sched_switch(entry->prev_pid, entry->prev_prio, entry->prev_state, + entry->next_pid, entry->next_prio, entry->next_state, + entry->next_cpu); } static void @@ -103,6 +108,11 @@ tracing_sched_wakeup_trace(struct trace_array *tr, if (!filter_check_discard(call, entry, buffer, event)) ring_buffer_unlock_commit(buffer, event); + + stm_sched_wakeup(entry->prev_pid, entry->prev_prio, entry->prev_state, + entry->next_pid, entry->next_prio, entry->next_state, + entry->next_cpu); + ftrace_trace_stack(tr->buffer, flags, 6, pc); ftrace_trace_userstack(tr->buffer, flags, pc); } -- cgit v1.2.3 From 56e7ee03819e3a43da7eb13428c19714c4d914c2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 16 Feb 2011 21:53:17 +0100 Subject: PM: Add support for device power domains The platform bus type is often used to handle Systems-on-a-Chip (SoC) where all devices are represented by objects of type struct platform_device. In those cases the same "platform" device driver may be used with multiple different system configurations, but the actions needed to put the devices it handles into a low-power state and back into the full-power state may depend on the design of the given SoC. The driver, however, cannot possibly include all the information necessary for the power management of its device on all the systems it is used with. Moreover, the device hierarchy in its current form also is not suitable for representing this kind of information. The patch below attempts to address this problem by introducing objects of type struct dev_power_domain that can be used for representing power domains within a SoC. Every struct dev_power_domain object provides a sets of device power management callbacks that can be used to perform what's needed for device power management in addition to the operations carried out by the device's driver and subsystem. Namely, if a struct dev_power_domain object is pointed to by the pwr_domain field in a struct device, the callbacks provided by its ops member will be executed in addition to the corresponding callbacks provided by the device's subsystem and driver during all power transitions. Signed-off-by: Rafael J. Wysocki Tested-and-acked-by: Kevin Hilman ST-Ericsson Linux next: - ST-Ericsson ID: ER323382 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I1f21456339aaa75905694bf4d87912b762c9d85e Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16674 Tested-by: Rabin VINCENT Reviewed-by: Jonas ABERG --- drivers/base/power/main.c | 7 +++++++ drivers/base/power/runtime.c | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 06f09bf89cb..d0ca74d7f36 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -885,6 +885,13 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) pm_dev_dbg(dev, state, "legacy "); error = legacy_suspend(dev, state, dev->bus->suspend); } + if (error) + goto End; + } + + if (dev->pwr_domain) { + pm_dev_dbg(dev, state, "power domain "); + pm_op(dev, &dev->pwr_domain->ops, state); } End: diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 0d4587b15c5..69d6f1d4805 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -168,6 +168,7 @@ static int rpm_check_suspend_allowed(struct device *dev) static int rpm_idle(struct device *dev, int rpmflags) { int (*callback)(struct device *); + int (*domain_callback)(struct device *); int retval; retval = rpm_check_suspend_allowed(dev); @@ -224,10 +225,19 @@ static int rpm_idle(struct device *dev, int rpmflags) else callback = NULL; - if (callback) { + if (dev->pwr_domain) + domain_callback = dev->pwr_domain->ops.runtime_idle; + else + domain_callback = NULL; + + if (callback || domain_callback) { spin_unlock_irq(&dev->power.lock); - callback(dev); + if (domain_callback) + retval = domain_callback(dev); + + if (!retval && callback) + callback(dev); spin_lock_irq(&dev->power.lock); } @@ -394,6 +404,8 @@ static int rpm_suspend(struct device *dev, int rpmflags) else pm_runtime_cancel_pending(dev); } else { + if (dev->pwr_domain) + rpm_callback(dev->pwr_domain->ops.runtime_suspend, dev); no_callback: __update_runtime_status(dev, RPM_SUSPENDED); pm_runtime_deactivate_timer(dev); -- cgit v1.2.3 From 306dbf38fec61021dcd0bde73bd6d142d5cf3d8c Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Tue, 5 Oct 2010 10:31:54 +0530 Subject: input: Dynamic enable or disable of SKE keypad events Added support for dynamic enable or disable of SKE keypad events by using sysfs commands. Enable SKE keypad event: echo 1 >/sys/devices/platform/nmk-ske-keypad/enable Disable SKE keypad event: echo 0 >/sys/devices/platform/nmk-ske-keypad/enable ST-Ericsson Id: WP 273474 Change-Id:Ia7c98d67609129ee661eec2b3f036bc24402b166 Signed-off-by: Naveen Kumar Gaddipati Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/6202 Reviewed-by: Linus WALLEIJ --- drivers/input/keyboard/nomadik-ske-keypad.c | 198 ++++++++++++++++++++-------- 1 file changed, 140 insertions(+), 58 deletions(-) diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 6e0f2309136..4261f8647de 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -56,6 +56,7 @@ * @board: keypad platform device * @keymap: matrix scan code table for keycodes * @clk: clock structure pointer + * @enable: flag to enable the driver event */ struct ske_keypad { int irq; @@ -65,6 +66,7 @@ struct ske_keypad { unsigned short keymap[SKE_KPD_KEYMAP_SIZE]; struct clk *clk; spinlock_t ske_keypad_lock; + bool enable; }; static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, @@ -82,6 +84,54 @@ static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, spin_unlock(&keypad->ske_keypad_lock); } +static ssize_t ske_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + return sprintf(buf, "%d\n", keypad->enable); +} + +static ssize_t ske_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (keypad->enable != val) { + keypad->enable = val ? true : false; + if (!keypad->enable) { + disable_irq(keypad->irq); + ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); + clk_disable(keypad->clk); + } else { + clk_enable(keypad->clk); + enable_irq(keypad->irq); + ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); + } + } + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + ske_show_attr_enable, ske_store_attr_enable); + +static struct attribute *ske_keypad_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group ske_attr_group = { + .attrs = ske_keypad_attrs, +}; + /* * ske_keypad_chip_init: init keypad controller configuration * @@ -90,7 +140,7 @@ static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, static int __devinit ske_keypad_chip_init(struct ske_keypad *keypad) { u32 value; - int timeout = 50; + int timeout = keypad->board->debounce_ms; /* check SKE_RIS to be 0 */ while ((readl(keypad->reg_base + SKE_RIS) != 0x00000000) && timeout--) @@ -199,12 +249,14 @@ static irqreturn_t ske_keypad_irq(int irq, void *dev_id) static int __devinit ske_keypad_probe(struct platform_device *pdev) { - const struct ske_keypad_platform_data *plat = pdev->dev.platform_data; struct ske_keypad *keypad; + struct resource *res = NULL; struct input_dev *input; - struct resource *res; + struct clk *clk; + void __iomem *reg_base; + int ret = 0; int irq; - int error; + struct ske_keypad_platform_data *plat = pdev->dev.platform_data; if (!plat) { dev_err(&pdev->dev, "invalid keypad platform data\n"); @@ -218,42 +270,43 @@ static int __devinit ske_keypad_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { + if (res == NULL) { dev_err(&pdev->dev, "missing platform resources\n"); - return -EINVAL; + return -ENXIO; } - keypad = kzalloc(sizeof(struct ske_keypad), GFP_KERNEL); - input = input_allocate_device(); - if (!keypad || !input) { - dev_err(&pdev->dev, "failed to allocate keypad memory\n"); - error = -ENOMEM; - goto err_free_mem; + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (!res) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + return -EBUSY; } - keypad->irq = irq; - keypad->board = plat; - keypad->input = input; - spin_lock_init(&keypad->ske_keypad_lock); + reg_base = ioremap(res->start, resource_size(res)); + if (!reg_base) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + ret = -ENXIO; + goto out_freerequest_memregions; + } - if (!request_mem_region(res->start, resource_size(res), pdev->name)) { - dev_err(&pdev->dev, "failed to request I/O memory\n"); - error = -EBUSY; - goto err_free_mem; + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to clk_get\n"); + ret = PTR_ERR(clk); + goto out_freeioremap; } - keypad->reg_base = ioremap(res->start, resource_size(res)); - if (!keypad->reg_base) { - dev_err(&pdev->dev, "failed to remap I/O memory\n"); - error = -ENXIO; - goto err_free_mem_region; + /* resources are sane; we begin allocation */ + keypad = kzalloc(sizeof(struct ske_keypad), GFP_KERNEL); + if (!keypad) { + dev_err(&pdev->dev, "failed to allocate keypad memory\n"); + goto out_freeclk; } - keypad->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(keypad->clk)) { - dev_err(&pdev->dev, "failed to get clk\n"); - error = PTR_ERR(keypad->clk); - goto err_iounmap; + input = input_allocate_device(); + if (!input) { + dev_err(&pdev->dev, "failed to input_allocate_device\n"); + ret = -ENOMEM; + goto out_freekeypad; } input->id.bustype = BUS_HOST; @@ -265,60 +318,88 @@ static int __devinit ske_keypad_probe(struct platform_device *pdev) input->keycodemax = ARRAY_SIZE(keypad->keymap); input_set_capability(input, EV_MSC, MSC_SCAN); + input_set_drvdata(input, keypad); __set_bit(EV_KEY, input->evbit); if (!plat->no_autorepeat) __set_bit(EV_REP, input->evbit); matrix_keypad_build_keymap(plat->keymap_data, SKE_KEYPAD_ROW_SHIFT, - input->keycode, input->keybit); + input->keycode, input->keybit); + ret = input_register_device(input); + if (ret) { + dev_err(&pdev->dev, + "unable to register input device: %d\n", ret); + goto out_freeinput; + } + + keypad->board = plat; + keypad->irq = irq; + keypad->board = plat; + keypad->input = input; + keypad->reg_base = reg_base; + keypad->clk = clk; + keypad->enable = true; + + /* allocations are sane, we begin HW initialization */ clk_enable(keypad->clk); - /* go through board initialization helpers */ - if (keypad->board->init) - keypad->board->init(); + if (!keypad->board->init) { + dev_err(&pdev->dev, "NULL board initialization helper\n"); + ret = -EINVAL; + goto out_unregisterinput; + } + + if (keypad->board->init() < 0) { + dev_err(&pdev->dev, "unable to set keypad board config\n"); + ret = -EINVAL; + goto out_unregisterinput; + } - error = ske_keypad_chip_init(keypad); - if (error) { + ret = ske_keypad_chip_init(keypad); + if (ret < 0) { dev_err(&pdev->dev, "unable to init keypad hardware\n"); - goto err_clk_disable; + goto out_unregisterinput; } - error = request_threaded_irq(keypad->irq, NULL, ske_keypad_irq, - IRQF_ONESHOT, "ske-keypad", keypad); - if (error) { + ret = request_threaded_irq(keypad->irq, NULL, ske_keypad_irq, + IRQF_ONESHOT, "ske-keypad", keypad); + if (ret) { dev_err(&pdev->dev, "allocate irq %d failed\n", keypad->irq); - goto err_clk_disable; + goto out_unregisterinput; } - error = input_register_device(input); - if (error) { - dev_err(&pdev->dev, - "unable to register input device: %d\n", error); - goto err_free_irq; + /* sysfs implementation for dynamic enable/disable the input event */ + ret = sysfs_create_group(&pdev->dev.kobj, &ske_attr_group); + if (ret) { + dev_err(&pdev->dev, "failed to create sysfs entries\n"); + goto out_free_irq; } - if (plat->wakeup_enable) - device_init_wakeup(&pdev->dev, true); + device_init_wakeup(&pdev->dev, true); platform_set_drvdata(pdev, keypad); return 0; -err_free_irq: +out_free_irq: free_irq(keypad->irq, keypad); -err_clk_disable: +out_unregisterinput: + input_unregister_device(input); + input = NULL; clk_disable(keypad->clk); - clk_put(keypad->clk); -err_iounmap: - iounmap(keypad->reg_base); -err_free_mem_region: - release_mem_region(res->start, resource_size(res)); -err_free_mem: +out_freeinput: input_free_device(input); +out_freekeypad: kfree(keypad); - return error; +out_freeclk: + clk_put(keypad->clk); +out_freeioremap: + iounmap(reg_base); +out_freerequest_memregions: + release_mem_region(res->start, resource_size(res)); + return ret; } static int __devexit ske_keypad_remove(struct platform_device *pdev) @@ -330,6 +411,7 @@ static int __devexit ske_keypad_remove(struct platform_device *pdev) input_unregister_device(keypad->input); + sysfs_remove_group(&pdev->dev.kobj, &ske_attr_group); clk_disable(keypad->clk); clk_put(keypad->clk); @@ -392,7 +474,7 @@ struct platform_driver ske_keypad_driver = { static int __init ske_keypad_init(void) { - return platform_driver_probe(&ske_keypad_driver, ske_keypad_probe); + return platform_driver_register(&ske_keypad_driver); } module_init(ske_keypad_init); -- cgit v1.2.3 From 0ca13d6e4e4ca2d75797d918ac8056b4400fa6d4 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 23 Mar 2011 09:15:40 +0100 Subject: u5500: add DB5500 keypad driver ST-Ericsson ID: AP273221 Change-Id: I0f096b8364797595084a84cb61bf13de61a3c0a2 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/5408 Tested-by: Rabin VINCENT Tested-by: build servers Reviewed-by: Linus WALLEIJ --- arch/arm/mach-ux500/board-u5500.c | 31 ++ arch/arm/mach-ux500/devices-db5500.h | 10 + arch/arm/mach-ux500/include/mach/db5500-keypad.h | 25 ++ drivers/input/keyboard/Kconfig | 10 + drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/db5500_keypad.c | 422 +++++++++++++++++++++++ 6 files changed, 499 insertions(+) create mode 100644 arch/arm/mach-ux500/include/mach/db5500-keypad.h create mode 100644 drivers/input/keyboard/db5500_keypad.c diff --git a/arch/arm/mach-ux500/board-u5500.c b/arch/arm/mach-ux500/board-u5500.c index 44fd3b5c33e..0abdd6d46bc 100644 --- a/arch/arm/mach-ux500/board-u5500.c +++ b/arch/arm/mach-ux500/board-u5500.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -17,9 +18,37 @@ #include #include #include +#include #include "devices-db5500.h" +/* + * Keypad + */ + +static const unsigned int u5500_keymap[] = { + KEY(4, 0, KEY_CAMERA), /* Camera2 */ + KEY(4, 1, KEY_CAMERA_FOCUS), /* Camera1 */ + KEY(4, 2, KEY_MENU), + KEY(4, 3, KEY_BACK), + KEY(5, 2, KEY_SEND), + KEY(5, 3, KEY_HOME), + KEY(8, 0, KEY_END), + KEY(8, 1, KEY_VOLUMEUP), + KEY(8, 2, KEY_VOLUMEDOWN), +}; + +static struct matrix_keymap_data u5500_keymap_data = { + .keymap = u5500_keymap, + .keymap_size = ARRAY_SIZE(u5500_keymap), +}; + +static struct db5500_keypad_platform_data u5500_keypad_board = { + .keymap_data = &u5500_keymap_data, + .no_autorepeat = true, + .debounce_ms = 40, /* milliseconds */ +}; + static void __init u5500_uart_init(void) { db5500_add_uart0(NULL); @@ -33,6 +62,8 @@ static void __init u5500_init_machine(void) u5500_sdi_init(); u5500_uart_init(); + + db5500_add_keypad(&u5500_keypad_board); } MACHINE_START(U5500, "ST-Ericsson U5500 Platform") diff --git a/arch/arm/mach-ux500/devices-db5500.h b/arch/arm/mach-ux500/devices-db5500.h index 0c4bccd02b9..c1314c2acce 100644 --- a/arch/arm/mach-ux500/devices-db5500.h +++ b/arch/arm/mach-ux500/devices-db5500.h @@ -17,6 +17,16 @@ #define db5500_add_i2c3(pdata) \ dbx500_add_i2c(3, U5500_I2C3_BASE, IRQ_DB5500_I2C3, pdata) +struct db5500_keypad_platform_data; + +static inline struct platform_device * +db5500_add_keypad(struct db5500_keypad_platform_data *pdata) +{ + return dbx500_add_platform_device_4k1irq("db5500-keypad", -1, + U5500_KEYPAD_BASE, + IRQ_DB5500_KBD, pdata); +} + #define db5500_add_msp0_i2s(pdata) \ dbx500_add_msp_i2s(0, U5500_MSP0_BASE, IRQ_DB5500_MSP0, pdata) #define db5500_add_msp1_i2s(pdata) \ diff --git a/arch/arm/mach-ux500/include/mach/db5500-keypad.h b/arch/arm/mach-ux500/include/mach/db5500-keypad.h new file mode 100644 index 00000000000..66b4c07f838 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/db5500-keypad.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: GNU General Public License, version 2 + * Author: Sundar Iyer for ST-Ericsson + */ + +#ifndef __DB5500_KEYPAD_H +#define __DB5500_KEYPAD_H + +#include + +/** + * struct db5500_keypad_platform_data - structure for platform specific data + * @keymap_data: matrix scan code table for keycodes + * @debounce_ms: platform specific debounce time + * @no_autorepeat: flag for auto repetition + */ +struct db5500_keypad_platform_data { + const struct matrix_keymap_data *keymap_data; + u8 debounce_ms; + bool no_autorepeat; +}; + +#endif diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index b4dee9d5a05..a910d10c5e2 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -151,6 +151,16 @@ config KEYBOARD_BFIN To compile this driver as a module, choose M here: the module will be called bf54x-keys. +config KEYBOARD_DB5500 + tristate "DB5500 keyboard" + depends on UX500_SOC_DB5500 + help + Say Y here to enable the on-chip keypad controller on the + ST-Ericsson U5500 platform. + + To compile this driver as a module, choose M here: the + module will be called db5500_keypad. + config KEYBOARD_LKKBD tristate "DECstation/VAXstation LK201/LK401 keyboard" select SERIO diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index ddde0fd476f..c2d3e9c0aeb 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_KEYBOARD_ATARI) += atakbd.o obj-$(CONFIG_KEYBOARD_ATKBD) += atkbd.o obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o obj-$(CONFIG_KEYBOARD_DAVINCI) += davinci_keyscan.o +obj-$(CONFIG_KEYBOARD_DB5500) += db5500_keypad.o obj-$(CONFIG_KEYBOARD_EP93XX) += ep93xx_keypad.o obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o obj-$(CONFIG_KEYBOARD_GPIO_POLLED) += gpio_keys_polled.o diff --git a/drivers/input/keyboard/db5500_keypad.c b/drivers/input/keyboard/db5500_keypad.c new file mode 100644 index 00000000000..a6d0fe08961 --- /dev/null +++ b/drivers/input/keyboard/db5500_keypad.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: GNU General Public License, version 2 + * Author: Sundar Iyer for ST-Ericsson + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KEYPAD_CTR 0x0 +#define KEYPAD_IRQ_CLEAR 0x4 +#define KEYPAD_INT_ENABLE 0x8 +#define KEYPAD_INT_STATUS 0xC +#define KEYPAD_ARRAY_01 0x18 + +#define KEYPAD_NUM_ARRAY_REGS 5 + +#define KEYPAD_CTR_WRITE_IRQ_ENABLE (1 << 10) +#define KEYPAD_CTR_SCAN_ENABLE (1 << 7) + +#define KEYPAD_ARRAY_CHANGEBIT (1 << 15) + +#define KEYPAD_DEBOUNCE_PERIOD_MIN 5 /* ms */ +#define KEYPAD_DEBOUNCE_PERIOD_MAX 80 /* ms */ + +#define KEYPAD_GND_ROW 8 + +#define KEYPAD_MAX_ROWS 9 +#define KEYPAD_MAX_COLS 8 +#define KEYPAD_ROW_SHIFT 4 +#define KEYPAD_KEYMAP_SIZE \ + (KEYPAD_MAX_ROWS * KEYPAD_MAX_COLS) + +/** + * struct db5500_keypad - data structure used by keypad driver + * @irq: irq number + * @base: keypad registers base address + * @input: pointer to input device object + * @board: keypad platform data + * @keymap: matrix scan code table for keycodes + * @clk: clock structure pointer + * @previous_set: previous set of registers + */ +struct db5500_keypad { + int irq; + void __iomem *base; + struct input_dev *input; + const struct db5500_keypad_platform_data *board; + unsigned short keymap[KEYPAD_KEYMAP_SIZE]; + struct clk *clk; + u8 previous_set[KEYPAD_MAX_ROWS]; +}; + +/* + * By default all column reads are 1111 1111b. Any press will pull the column + * down, leading to a 0 in any of these locations. We invert these values so + * that a 1 means means "column pressed". + * + * If curr changes from the previous from 0 to 1, we report it as a key press. + * If curr changes from the previous from 1 to 0, we report it as a key + * release. + */ +static void db5500_keypad_report(struct db5500_keypad *keypad, int row, + u8 curr, u8 previous) +{ + struct input_dev *input = keypad->input; + u8 changed = curr ^ previous; + + while (changed) { + int col = __ffs(changed); + bool press = curr & BIT(col); + int code = MATRIX_SCAN_CODE(row, col, KEYPAD_ROW_SHIFT); + + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], press); + input_sync(input); + + changed &= ~BIT(col); + } +} + +static irqreturn_t db5500_keypad_irq(int irq, void *dev_id) +{ + struct db5500_keypad *keypad = dev_id; + u8 current_set[ARRAY_SIZE(keypad->previous_set)]; + int tries = 100; + bool changebit; + u32 data_reg; + u8 allrows; + u8 common; + int i; + + writel(0x1, keypad->base + KEYPAD_IRQ_CLEAR); + +again: + if (!tries--) { + dev_warn(&keypad->input->dev, "values failed to stabilize\n"); + return IRQ_HANDLED; + } + + changebit = readl(keypad->base + KEYPAD_ARRAY_01) + & KEYPAD_ARRAY_CHANGEBIT; + + for (i = 0; i < KEYPAD_NUM_ARRAY_REGS; i++) { + data_reg = readl(keypad->base + KEYPAD_ARRAY_01 + 4 * i); + + /* If the change bit changed, we need to reread the data */ + if (changebit != !!(data_reg & KEYPAD_ARRAY_CHANGEBIT)) + goto again; + + current_set[2 * i] = ~(data_reg & 0xff); + + /* Last array reg has only one valid set of columns */ + if (i != KEYPAD_NUM_ARRAY_REGS - 1) + current_set[2 * i + 1] = ~((data_reg & 0xff0000) >> 16); + } + + allrows = current_set[KEYPAD_GND_ROW]; + + /* + * Sometimes during a GND row release, an incorrect report is received + * where the ARRAY8 all rows setting does not match the other ARRAY* + * rows. Ignore this report; the correct one has been observed to + * follow it. + */ + common = 0xff; + for (i = 0; i < KEYPAD_GND_ROW; i++) + common &= current_set[i]; + + if ((allrows & common) != common) + return IRQ_HANDLED; + + for (i = 0; i < ARRAY_SIZE(current_set); i++) { + /* + * If there is an allrows press (GND row), we need to ignore + * the allrows values from the reset of the ARRAYs. + */ + if (i < KEYPAD_GND_ROW && allrows) + current_set[i] &= ~allrows; + + if (keypad->previous_set[i] == current_set[i]) + continue; + + db5500_keypad_report(keypad, i, current_set[i], + keypad->previous_set[i]); + } + + /* update the reference set of array registers */ + memcpy(keypad->previous_set, current_set, sizeof(keypad->previous_set)); + + return IRQ_HANDLED; +} + +static int __devinit db5500_keypad_chip_init(struct db5500_keypad *keypad) +{ + int debounce = keypad->board->debounce_ms; + int debounce_hits = 0; + int timeout = 100; + u32 val; + + if (debounce < KEYPAD_DEBOUNCE_PERIOD_MIN) + debounce = KEYPAD_DEBOUNCE_PERIOD_MIN; + + if (debounce > KEYPAD_DEBOUNCE_PERIOD_MAX) { + debounce_hits = DIV_ROUND_UP(debounce, + KEYPAD_DEBOUNCE_PERIOD_MAX) - 1; + debounce = KEYPAD_DEBOUNCE_PERIOD_MAX; + } + + /* Convert the milliseconds to the bit mask */ + debounce = DIV_ROUND_UP(debounce, KEYPAD_DEBOUNCE_PERIOD_MIN) - 1; + + writel(KEYPAD_CTR_SCAN_ENABLE + | ((debounce_hits & 0x7) << 4) + | debounce, keypad->base + KEYPAD_CTR); + + do { + val = readl(keypad->base + KEYPAD_CTR); + } while ((!(val & KEYPAD_CTR_WRITE_IRQ_ENABLE)) && --timeout); + + if (!timeout) + return -EINVAL; + + writel(0x1, keypad->base + KEYPAD_INT_ENABLE); + + return 0; +} + +static int __devinit db5500_keypad_probe(struct platform_device *pdev) +{ + const struct db5500_keypad_platform_data *plat; + struct db5500_keypad *keypad; + struct resource *res; + struct input_dev *input; + void __iomem *base; + struct clk *clk; + int ret; + int irq; + + plat = pdev->dev.platform_data; + if (!plat) { + dev_err(&pdev->dev, "invalid keypad platform data\n"); + ret = -EINVAL; + goto out_ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "failed to get keypad irq\n"); + ret = -EINVAL; + goto out_ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "missing platform resources\n"); + ret = -EINVAL; + goto out_ret; + } + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (!res) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + ret = -EBUSY; + goto out_ret; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + dev_err(&pdev->dev, "failed to remap I/O memory\n"); + ret = -ENXIO; + goto out_freerequest_memregions; + } + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to clk_get\n"); + + /* + * FIXME: error out here once DB5500 clock framework is in + * place, and remove all the !IS_ERR(clk) checks. + */ + } + + keypad = kzalloc(sizeof(struct db5500_keypad), GFP_KERNEL); + if (!keypad) { + dev_err(&pdev->dev, "failed to allocate keypad memory\n"); + ret = -ENOMEM; + goto out_freeclk; + } + + input = input_allocate_device(); + if (!input) { + dev_err(&pdev->dev, "failed to input_allocate_device\n"); + ret = -ENOMEM; + goto out_freekeypad; + } + + input->id.bustype = BUS_HOST; + input->name = "db5500-keypad"; + input->dev.parent = &pdev->dev; + + input->keycode = keypad->keymap; + input->keycodesize = sizeof(keypad->keymap[0]); + input->keycodemax = ARRAY_SIZE(keypad->keymap); + + input_set_capability(input, EV_MSC, MSC_SCAN); + + __set_bit(EV_KEY, input->evbit); + if (!plat->no_autorepeat) + __set_bit(EV_REP, input->evbit); + + matrix_keypad_build_keymap(plat->keymap_data, KEYPAD_ROW_SHIFT, + input->keycode, input->keybit); + + ret = input_register_device(input); + if (ret) { + dev_err(&pdev->dev, + "unable to register input device: %d\n", ret); + goto out_freeinput; + } + + keypad->irq = irq; + keypad->board = plat; + keypad->input = input; + keypad->base = base; + keypad->clk = clk; + + /* allocations are sane, we begin HW initialization */ + if (!IS_ERR(keypad->clk)) + clk_enable(keypad->clk); + + ret = db5500_keypad_chip_init(keypad); + if (ret < 0) { + dev_err(&pdev->dev, "unable to init keypad hardware\n"); + goto out_unregisterinput; + } + + ret = request_threaded_irq(keypad->irq, NULL, db5500_keypad_irq, + IRQF_ONESHOT, "db5500-keypad", keypad); + if (ret) { + dev_err(&pdev->dev, "allocate irq %d failed\n", keypad->irq); + goto out_unregisterinput; + } + + device_init_wakeup(&pdev->dev, true); + + platform_set_drvdata(pdev, keypad); + + return 0; + +out_unregisterinput: + input_unregister_device(input); + input = NULL; + if (!IS_ERR(keypad->clk)) + clk_disable(keypad->clk); +out_freeinput: + input_free_device(input); +out_freekeypad: + kfree(keypad); +out_freeclk: + if (!IS_ERR(clk)) + clk_put(clk); + iounmap(base); +out_freerequest_memregions: + release_mem_region(res->start, resource_size(res)); +out_ret: + return ret; +} + +static int __devexit db5500_keypad_remove(struct platform_device *pdev) +{ + struct db5500_keypad *keypad = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + free_irq(keypad->irq, keypad); + input_unregister_device(keypad->input); + + if (!IS_ERR(keypad->clk)) { + clk_disable(keypad->clk); + clk_put(keypad->clk); + } + + iounmap(keypad->base); + release_mem_region(res->start, resource_size(res)); + kfree(keypad); + + return 0; +} + +#ifdef CONFIG_PM +static int db5500_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct db5500_keypad *keypad = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(dev)) + enable_irq_wake(irq); + else + /* disable IRQ here */ + + return 0; +} + +static int db5500_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct db5500_keypad *keypad = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + if (device_may_wakeup(dev)) + disable_irq_wake(irq); + else + /* enable IRQ here */ + + return 0; +} + +static const struct dev_pm_ops db5500_keypad_dev_pm_ops = { + .suspend = db5500_keypad_suspend, + .resume = db5500_keypad_resume, +}; +#endif + +static struct platform_driver db5500_keypad_driver = { + .driver = { + .name = "db5500-keypad", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &db5500_keypad_dev_pm_ops, +#endif + }, + .probe = db5500_keypad_probe, + .remove = __devexit_p(db5500_keypad_remove), +}; + +static int __init db5500_keypad_init(void) +{ + return platform_driver_register(&db5500_keypad_driver); +} +module_init(db5500_keypad_init); + +static void __exit db5500_keypad_exit(void) +{ + platform_driver_unregister(&db5500_keypad_driver); +} +module_exit(db5500_keypad_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sundar Iyer "); +MODULE_DESCRIPTION("DB5500 Keypad Driver"); +MODULE_ALIAS("platform:db5500-keypad"); -- cgit v1.2.3 From b2bb2688431beabcf817afbe958ee947a4032981 Mon Sep 17 00:00:00 2001 From: Bade Appala Naidu Date: Wed, 6 Oct 2010 15:10:57 +0530 Subject: input: Dynamic enable or disable of STMPE1601 keypad events Added support for dynamic enable or disable of STMPE1601 keypad events by using sysfs commands. Enable STMPE1601 keypad event: echo 1 > /sys/devices/platform/nmk-i2c.0/i2c-0/0-0040/stmpe-keypad.1/enable Disable STMPE1601 keypad event: echo 0 > /sys/devices/platform/nmk-i2c.0/i2c-0/0-0040/stmpe-keypad.1/enable ST-Ericsson Id: WP 273474 Change-Id:I12281c9a3ae3b275b3e1efaeecfb7817c93290b9 Signed-off-by: Bade Appala Naidu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/6266 Reviewed-by: Linus WALLEIJ --- drivers/input/keyboard/stmpe-keypad.c | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c index ab7610ca10e..f386e07cf31 100644 --- a/drivers/input/keyboard/stmpe-keypad.c +++ b/drivers/input/keyboard/stmpe-keypad.c @@ -108,10 +108,52 @@ struct stmpe_keypad { unsigned int rows; unsigned int cols; + bool enable; unsigned short keymap[STMPE_KEYPAD_KEYMAP_SIZE]; }; +static ssize_t stmpe_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + return sprintf(buf, "%d\n", keypad->enable); +} + +static ssize_t stmpe_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + struct stmpe *stmpe = keypad->stmpe; + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if (keypad->enable != val) { + keypad->enable = val; + if (!val) + stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD); + else + stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD); + } + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + stmpe_show_attr_enable, stmpe_store_attr_enable); + +static struct attribute *stmpe_keypad_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group stmpe_attr_group = { + .attrs = stmpe_keypad_attrs, +}; + static int stmpe_keypad_read_data(struct stmpe_keypad *keypad, u8 *data) { const struct stmpe_keypad_variant *variant = keypad->variant; @@ -331,11 +373,21 @@ static int __devinit stmpe_keypad_probe(struct platform_device *pdev) dev_err(&pdev->dev, "unable to get irq: %d\n", ret); goto out_unregisterinput; } + /* sysfs implementation for dynamic enable/disable the input event */ + ret = sysfs_create_group(&pdev->dev.kobj, &stmpe_attr_group); + if (ret) { + dev_err(&pdev->dev, "failed to create sysfs entries\n"); + goto out_free_irq; + } + + keypad->enable = true; platform_set_drvdata(pdev, keypad); return 0; +out_free_irq: + free_irq(irq, keypad); out_unregisterinput: input_unregister_device(input); input = NULL; @@ -354,6 +406,7 @@ static int __devexit stmpe_keypad_remove(struct platform_device *pdev) stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD); + sysfs_remove_group(&pdev->dev.kobj, &stmpe_attr_group); free_irq(irq, keypad); input_unregister_device(keypad->input); platform_set_drvdata(pdev, NULL); -- cgit v1.2.3 From 03ddab67cc213fc8d99f9ffd91bb2fe311a4ed74 Mon Sep 17 00:00:00 2001 From: Sundar Iyer Date: Fri, 22 Oct 2010 11:43:17 +0530 Subject: input/keypad-stmpe: add suspend/resume support ST-Ericcson ID: TASK_ER170552 Change-Id: Id814bec06a86e8a3215ef662d5f01a7ee929d26a Signed-off-by: Sundar Iyer Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/7526 Reviewed-by: Jonas ABERG --- drivers/input/keyboard/stmpe-keypad.c | 38 +++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/drivers/input/keyboard/stmpe-keypad.c b/drivers/input/keyboard/stmpe-keypad.c index f386e07cf31..a01a18f5735 100644 --- a/drivers/input/keyboard/stmpe-keypad.c +++ b/drivers/input/keyboard/stmpe-keypad.c @@ -373,14 +373,14 @@ static int __devinit stmpe_keypad_probe(struct platform_device *pdev) dev_err(&pdev->dev, "unable to get irq: %d\n", ret); goto out_unregisterinput; } - /* sysfs implementation for dynamic enable/disable the input event */ + + /* sysfs implementation for dynamic enable/disable the input event */ ret = sysfs_create_group(&pdev->dev.kobj, &stmpe_attr_group); if (ret) { dev_err(&pdev->dev, "failed to create sysfs entries\n"); goto out_free_irq; } - keypad->enable = true; platform_set_drvdata(pdev, keypad); @@ -415,9 +415,43 @@ static int __devexit stmpe_keypad_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int stmpe_keypad_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + struct stmpe *stmpe = keypad->stmpe; + + if (!device_may_wakeup(stmpe->dev)) + stmpe_disable(stmpe, STMPE_BLOCK_KEYPAD); + + return 0; +} + +static int stmpe_keypad_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stmpe_keypad *keypad = platform_get_drvdata(pdev); + struct stmpe *stmpe = keypad->stmpe; + + if (!device_may_wakeup(stmpe->dev)) + stmpe_enable(stmpe, STMPE_BLOCK_KEYPAD); + + return 0; +} + +static const struct dev_pm_ops stmpe_keypad_dev_pm_ops = { + .suspend = stmpe_keypad_suspend, + .resume = stmpe_keypad_resume, +}; +#endif + static struct platform_driver stmpe_keypad_driver = { .driver.name = "stmpe-keypad", .driver.owner = THIS_MODULE, +#if CONFIG_PM + .driver.pm = &stmpe_keypad_dev_pm_ops, +#endif .probe = stmpe_keypad_probe, .remove = __devexit_p(stmpe_keypad_remove), }; -- cgit v1.2.3 From 8a470a5c42e6c26d07bbf61c823bfaaaa3abe2e5 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Wed, 17 Nov 2010 13:31:56 +0530 Subject: input: Multi key press support is added for SKE keypad Added the multi key press support for SKE keypad by modifying the irq function for handling the two different keys on the same column and also pressing the two different keys of different columns on the same ASR register. ST-Ericsson Id: ER 279197 Signed-off-by: Naveen Kumar Gaddipati Change-Id: Ia83a8f6ba1f5fee47f97b9476bb59cf2460b8e14 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13192 Reviewed-by: QATOOLS Reviewed-by: Srinidhi KASAGAR --- drivers/input/keyboard/Makefile | 2 +- drivers/input/keyboard/nomadik-ske-keypad.c | 65 ++++++++++++++++++----------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index c2d3e9c0aeb..e675356acb2 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -31,7 +31,7 @@ obj-$(CONFIG_KEYBOARD_MAX7359) += max7359_keypad.o obj-$(CONFIG_KEYBOARD_MCS) += mcs_touchkey.o obj-$(CONFIG_KEYBOARD_MPR121) += mpr121_touchkey.o obj-$(CONFIG_KEYBOARD_NEWTON) += newtonkbd.o -obj-$(CONFIG_KEYBOARD_NOMADIK) += nomadik-ske-keypad.o +obj-$(CONFIG_KEYBOARD_NOMADIK_SKE) += nomadik-ske-keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_OMAP4) += omap4-keypad.o obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 4261f8647de..13cf2d45ebb 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -183,12 +183,37 @@ static int __devinit ske_keypad_chip_init(struct ske_keypad *keypad) return 0; } -static void ske_keypad_read_data(struct ske_keypad *keypad) +static void ske_keypad_report(struct ske_keypad *keypad, u8 status, int col) { + int row = 0, code, pos; struct input_dev *input = keypad->input; - u16 status; - int col = 0, row = 0, code; - int ske_asr, ske_ris, key_pressed, i; + u32 ske_ris; + int key_pressed; + int num_of_rows; + + /* find out the row */ + num_of_rows = hweight8(status); + do { + pos = __ffs(status); + row = pos; + status &= ~(1 << pos); + + code = MATRIX_SCAN_CODE(row, col, SKE_KEYPAD_ROW_SHIFT); + ske_ris = readl(keypad->reg_base + SKE_RIS); + key_pressed = ske_ris & SKE_KPRISA; + + input_event(input, EV_MSC, MSC_SCAN, code); + input_report_key(input, keypad->keymap[code], key_pressed); + input_sync(input); + num_of_rows--; + } while (num_of_rows); +} + +static void ske_keypad_read_data(struct ske_keypad *keypad) +{ + u8 status; + int col = 0; + int ske_asr, i; /* * Read the auto scan registers @@ -202,25 +227,17 @@ static void ske_keypad_read_data(struct ske_keypad *keypad) if (!ske_asr) continue; - /* now that ASRx is zero, find out the column x and row y*/ - if (ske_asr & 0xff) { + /* now that ASRx is zero, find out the coloumn x and row y */ + status = ske_asr & 0xff; + if (status) { col = i * 2; - status = ske_asr & 0xff; - } else { + ske_keypad_report(keypad, status, col); + } + status = (ske_asr & 0xff00) >> 8; + if (status) { col = (i * 2) + 1; - status = (ske_asr & 0xff00) >> 8; + ske_keypad_report(keypad, status, col); } - - /* find out the row */ - row = __ffs(status); - - code = MATRIX_SCAN_CODE(row, col, SKE_KEYPAD_ROW_SHIFT); - ske_ris = readl(keypad->reg_base + SKE_RIS); - key_pressed = ske_ris & SKE_KPRISA; - - input_event(input, EV_MSC, MSC_SCAN, code); - input_report_key(input, keypad->keymap[code], key_pressed); - input_sync(input); } } @@ -234,12 +251,10 @@ static irqreturn_t ske_keypad_irq(int irq, void *dev_id) ske_keypad_set_bits(keypad, SKE_ICR, 0x0, SKE_KPICA); while ((readl(keypad->reg_base + SKE_CR) & SKE_KPASON) && --retries) - msleep(5); + cpu_relax(); - if (retries) { - /* SKEx registers are stable and can be read */ - ske_keypad_read_data(keypad); - } + /* SKEx registers are stable and can be read */ + ske_keypad_read_data(keypad); /* enable auto scan interrupts */ ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); -- cgit v1.2.3 From 1edd70314cd1d33f43a9f9fc744357a941cb3fc9 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Mon, 31 Jan 2011 19:01:22 +0530 Subject: input:ske: Suspend and resume support for ske keypad Suspend and resume support for ske keypad by using disable or enable of keypad. ST-Ericsson Id: ER 320090 Change-Id: I5ae732be0e2f6074048fa6351a9f7b9ee33f380f Signed-off-by: Naveen Kumar Gaddipati Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13977 Reviewed-by: QATOOLS Reviewed-by: Srinidhi KASAGAR --- drivers/input/keyboard/nomadik-ske-keypad.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 13cf2d45ebb..1286007ac23 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -392,7 +392,8 @@ static int __devinit ske_keypad_probe(struct platform_device *pdev) goto out_free_irq; } - device_init_wakeup(&pdev->dev, true); + if (plat->wakeup_enable) + device_init_wakeup(&pdev->dev, true); platform_set_drvdata(pdev, keypad); @@ -449,8 +450,11 @@ static int ske_keypad_suspend(struct device *dev) if (device_may_wakeup(dev)) enable_irq_wake(irq); - else + else { + disable_irq(keypad->irq); ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); + clk_disable(keypad->clk); + } return 0; } @@ -463,8 +467,11 @@ static int ske_keypad_resume(struct device *dev) if (device_may_wakeup(dev)) disable_irq_wake(irq); - else - ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); + else { + clk_enable(keypad->clk); + enable_irq(keypad->irq); + ske_keypad_chip_init(keypad); + } return 0; } -- cgit v1.2.3 From 0e868177c80500cf5f64e95195170d896ebebd71 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Fri, 4 Feb 2011 14:46:08 +0530 Subject: input:ske: Remove multiple interrupts for each keypress Remove the multiple interrupts for each key press of the ske keypad. ST-Ericsson Id: ER 323157 Change-Id: Ib0d9827895c72c47e83006a1498401849c1c16db Signed-off-by: Naveen Kumar Gaddipati Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/14599 Reviewed-by: Srinidhi KASAGAR --- drivers/input/keyboard/nomadik-ske-keypad.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 1286007ac23..8025b236c58 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -244,18 +244,21 @@ static void ske_keypad_read_data(struct ske_keypad *keypad) static irqreturn_t ske_keypad_irq(int irq, void *dev_id) { struct ske_keypad *keypad = dev_id; - int retries = 20; /* disable auto scan interrupt; mask the interrupt generated */ ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); ske_keypad_set_bits(keypad, SKE_ICR, 0x0, SKE_KPICA); - while ((readl(keypad->reg_base + SKE_CR) & SKE_KPASON) && --retries) + while (readl(keypad->reg_base + SKE_CR) & SKE_KPASON) cpu_relax(); /* SKEx registers are stable and can be read */ ske_keypad_read_data(keypad); + /* wait one debounce ms */ + while (readl(keypad->reg_base + SKE_RIS)) + cpu_relax(); + /* enable auto scan interrupts */ ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); @@ -349,7 +352,6 @@ static int __devinit ske_keypad_probe(struct platform_device *pdev) goto out_freeinput; } - keypad->board = plat; keypad->irq = irq; keypad->board = plat; keypad->input = input; -- cgit v1.2.3 From d495cc5e9ce163292d39f906535563cbf9f49071 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Tue, 8 Feb 2011 18:56:54 +0530 Subject: input:ske:Decrease the CPU load during continue key press Decrease the CPU load during continuous key press in SKE keypad driver. ST-Ericsson Id: ER 323157 Signed-off-by: Naveen Kumar Gaddipati Change-Id: Ic450157de6cb21d3d36a51ffb54e5d3467163335 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/14705 Reviewed-by: QATOOLS Reviewed-by: Rikard OLSSON Reviewed-by: Srinidhi KASAGAR --- drivers/input/keyboard/nomadik-ske-keypad.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 8025b236c58..19d6b66d31e 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -47,6 +47,7 @@ #define SKE_ASR3 0x2C #define SKE_NUM_ASRX_REGISTERS (4) +#define KEY_PRESSED_DELAY 10 /** * struct ske_keypad - data structure used by keypad driver @@ -244,20 +245,21 @@ static void ske_keypad_read_data(struct ske_keypad *keypad) static irqreturn_t ske_keypad_irq(int irq, void *dev_id) { struct ske_keypad *keypad = dev_id; + int timeout = keypad->board->debounce_ms; /* disable auto scan interrupt; mask the interrupt generated */ ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); ske_keypad_set_bits(keypad, SKE_ICR, 0x0, SKE_KPICA); - while (readl(keypad->reg_base + SKE_CR) & SKE_KPASON) + while ((readl(keypad->reg_base + SKE_CR) & SKE_KPASON) && --timeout) cpu_relax(); /* SKEx registers are stable and can be read */ ske_keypad_read_data(keypad); - /* wait one debounce ms */ - while (readl(keypad->reg_base + SKE_RIS)) - cpu_relax(); + /* wait until raw interrupt is clear */ + while ((readl(keypad->reg_base + SKE_RIS)) && --timeout) + msleep(KEY_PRESSED_DELAY); /* enable auto scan interrupts */ ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); -- cgit v1.2.3 From f9458f72bdeb1db16d2a4d1167edadb7812daa77 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Wed, 9 Feb 2011 18:58:57 +0530 Subject: input:ske:Add the regulator support in ske driver Add the regulator calls for the ske keypad controller driver. ST-Ericsson Id: AP 323445 Change-Id: I950ce9c9415bc283e2d9c3174f7178b2f5555248 Signed-off-by: Naveen Kumar Gaddipati Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/14806 Reviewed-by: Srinidhi KASAGAR --- drivers/input/keyboard/nomadik-ske-keypad.c | 140 +++++++++++++++++----------- 1 file changed, 84 insertions(+), 56 deletions(-) diff --git a/drivers/input/keyboard/nomadik-ske-keypad.c b/drivers/input/keyboard/nomadik-ske-keypad.c index 19d6b66d31e..6d0955dba5b 100644 --- a/drivers/input/keyboard/nomadik-ske-keypad.c +++ b/drivers/input/keyboard/nomadik-ske-keypad.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -58,6 +59,7 @@ * @keymap: matrix scan code table for keycodes * @clk: clock structure pointer * @enable: flag to enable the driver event + * @regulator: pointer to the regulator used for ske kyepad */ struct ske_keypad { int irq; @@ -68,6 +70,7 @@ struct ske_keypad { struct clk *clk; spinlock_t ske_keypad_lock; bool enable; + struct regulator *regulator; }; static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, @@ -85,54 +88,6 @@ static void ske_keypad_set_bits(struct ske_keypad *keypad, u16 addr, spin_unlock(&keypad->ske_keypad_lock); } -static ssize_t ske_show_attr_enable(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct platform_device *pdev = to_platform_device(dev); - struct ske_keypad *keypad = platform_get_drvdata(pdev); - return sprintf(buf, "%d\n", keypad->enable); -} - -static ssize_t ske_store_attr_enable(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct platform_device *pdev = to_platform_device(dev); - struct ske_keypad *keypad = platform_get_drvdata(pdev); - unsigned long val; - - if (strict_strtoul(buf, 0, &val)) - return -EINVAL; - - if ((val != 0) && (val != 1)) - return -EINVAL; - - if (keypad->enable != val) { - keypad->enable = val ? true : false; - if (!keypad->enable) { - disable_irq(keypad->irq); - ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); - clk_disable(keypad->clk); - } else { - clk_enable(keypad->clk); - enable_irq(keypad->irq); - ske_keypad_set_bits(keypad, SKE_IMSC, 0x0, SKE_KPIMA); - } - } - return count; -} - -static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, - ske_show_attr_enable, ske_store_attr_enable); - -static struct attribute *ske_keypad_attrs[] = { - &dev_attr_enable.attr, - NULL, -}; - -static struct attribute_group ske_attr_group = { - .attrs = ske_keypad_attrs, -}; - /* * ske_keypad_chip_init: init keypad controller configuration * @@ -184,6 +139,61 @@ static int __devinit ske_keypad_chip_init(struct ske_keypad *keypad) return 0; } +static void ske_enable(struct ske_keypad *keypad) +{ + if (keypad->enable) { + regulator_enable(keypad->regulator); + clk_enable(keypad->clk); + enable_irq(keypad->irq); + ske_keypad_chip_init(keypad); + } else { + disable_irq(keypad->irq); + ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); + clk_disable(keypad->clk); + regulator_disable(keypad->regulator); + } +} + +static ssize_t ske_show_attr_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + return sprintf(buf, "%d\n", keypad->enable); +} + +static ssize_t ske_store_attr_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ske_keypad *keypad = platform_get_drvdata(pdev); + unsigned long val; + + if (strict_strtoul(buf, 0, &val)) + return -EINVAL; + + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (keypad->enable != val) { + keypad->enable = val ? true : false; + ske_enable(keypad); + } + return count; +} + +static DEVICE_ATTR(enable, S_IWUSR | S_IRUGO, + ske_show_attr_enable, ske_store_attr_enable); + +static struct attribute *ske_keypad_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; + +static struct attribute_group ske_attr_group = { + .attrs = ske_keypad_attrs, +}; + static void ske_keypad_report(struct ske_keypad *keypad, u8 status, int col) { int row = 0, code, pos; @@ -329,6 +339,19 @@ static int __devinit ske_keypad_probe(struct platform_device *pdev) goto out_freekeypad; } + keypad->regulator = regulator_get(&pdev->dev, "v-ape"); + if (IS_ERR(keypad->regulator)) { + dev_err(&pdev->dev, "regulator_get failed\n"); + keypad->regulator = NULL; + goto out_regulator_get; + } else { + ret = regulator_enable(keypad->regulator); + if (ret < 0) { + dev_err(&pdev->dev, "regulator_enable failed\n"); + goto out_regulator_enable; + } + } + input->id.bustype = BUS_HOST; input->name = "ux500-ske-keypad"; input->dev.parent = &pdev->dev; @@ -410,6 +433,10 @@ out_unregisterinput: input = NULL; clk_disable(keypad->clk); out_freeinput: + regulator_disable(keypad->regulator); +out_regulator_enable: + regulator_put(keypad->regulator); +out_regulator_get: input_free_device(input); out_freekeypad: kfree(keypad); @@ -438,6 +465,9 @@ static int __devexit ske_keypad_remove(struct platform_device *pdev) if (keypad->board->exit) keypad->board->exit(); + regulator_disable(keypad->regulator); + regulator_put(keypad->regulator); + iounmap(keypad->reg_base); release_mem_region(res->start, resource_size(res)); kfree(keypad); @@ -454,10 +484,9 @@ static int ske_keypad_suspend(struct device *dev) if (device_may_wakeup(dev)) enable_irq_wake(irq); - else { - disable_irq(keypad->irq); - ske_keypad_set_bits(keypad, SKE_IMSC, ~SKE_KPIMA, 0x0); - clk_disable(keypad->clk); + else if (keypad->enable) { + keypad->enable = false; + ske_enable(keypad); } return 0; @@ -471,10 +500,9 @@ static int ske_keypad_resume(struct device *dev) if (device_may_wakeup(dev)) disable_irq_wake(irq); - else { - clk_enable(keypad->clk); - enable_irq(keypad->irq); - ske_keypad_chip_init(keypad); + else if (!keypad->enable) { + keypad->enable = true; + ske_enable(keypad); } return 0; -- cgit v1.2.3 From a7663cebded709fc9f1ece2c095d2871ef205c75 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 28 Apr 2011 10:52:59 +0200 Subject: AB8500 GPIO : Fix & clean duplicate define Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/board-mop500.h | 4 ++++ drivers/gpio/ab8500-gpio.c | 29 ++++++++++++++++++++++++++++- include/linux/mfd/ab8500.h | 7 ------- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500.h b/arch/arm/mach-ux500/board-mop500.h index 656f9297cfe..c7f1e6fee3b 100644 --- a/arch/arm/mach-ux500/board-mop500.h +++ b/arch/arm/mach-ux500/board-mop500.h @@ -38,6 +38,10 @@ /* GPIOs on the AB8500 mixed-signals circuit */ #define MOP500_AB8500_GPIO(x) (MOP500_EGPIO_END + (x)) +/* GPIOs on AB8500 */ +#define MOP500_EGPIO_END MOP500_EGPIO(24) +#define MOP500_AB8500_GPIO(x) (MOP500_EGPIO_END + (x)) + struct i2c_board_info; extern void mop500_sdi_init(void); diff --git a/drivers/gpio/ab8500-gpio.c b/drivers/gpio/ab8500-gpio.c index 970053c89ff..24c960e1512 100644 --- a/drivers/gpio/ab8500-gpio.c +++ b/drivers/gpio/ab8500-gpio.c @@ -133,7 +133,7 @@ static void ab8500_gpio_set(struct gpio_chip *chip, unsigned offset, int val) struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); int ret; /* Write the data */ - ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, 1); + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, val); if (ret < 0) dev_err(ab8500_gpio->dev, "%s write failed\n", __func__); } @@ -494,6 +494,33 @@ static int __devexit ab8500_gpio_remove(struct platform_device *pdev) return 0; } +/* + * ab8500_gpio_configpulldown() - configure pull down + * Either the function is used to enable pull down (enable true) + * or to leave it dangling (enable false) or the function it used + * to enable pull up (enable true) or to leave it dangling (enable false) + * @pdev :Platform device registered + * @gpio :gpio number + * @enable:pull down enabled (True) or disabled (False) + */ +int ab8500_config_pull_up_or_down(struct platform_device *pdev, + unsigned gpio, bool enable) +{ + struct ab8500_gpio *ab8500_gpio = platform_get_drvdata(pdev); + u8 offset = gpio - ab8500_gpio->chip.base; + u8 pos = offset % 8; + u8 val = enable ? 0 : 1; + u8 reg = AB8500_GPIO_PUD1_REG + (offset / 8); + int ret; + + ret = abx500_mask_and_set_register_interruptible(ab8500_gpio->dev, + AB8500_MISC, reg, 1 << pos, val << pos); + if (ret < 0) + dev_err(&pdev->dev, "%s write failed\n", __func__); + return ret; +} +EXPORT_SYMBOL(ab8500_config_pull_up_or_down); + static struct platform_driver ab8500_gpio_driver = { .driver = { .name = "ab8500-gpio", diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index b3184307519..7c5cdc83abf 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -74,13 +74,6 @@ #define AB8500_INT_ACC_DETECT_21DB_F 37 #define AB8500_INT_ACC_DETECT_21DB_R 38 #define AB8500_INT_GP_SW_ADC_CONV_END 39 -#define AB8500_INT_ACC_DETECT_1DB_F 33 -#define AB8500_INT_ACC_DETECT_1DB_R 34 -#define AB8500_INT_ACC_DETECT_22DB_F 35 -#define AB8500_INT_ACC_DETECT_22DB_R 36 -#define AB8500_INT_ACC_DETECT_21DB_F 37 -#define AB8500_INT_ACC_DETECT_21DB_R 38 -#define AB8500_INT_GP_SW_ADC_CONV_END 39 #define AB8500_INT_GPIO6R 40 #define AB8500_INT_GPIO7R 41 #define AB8500_INT_GPIO8R 42 -- cgit v1.2.3 From a24ef4cf25aade339e087d42aff5ac8e14e422d4 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 23 Mar 2011 15:18:00 +0100 Subject: input:ske: Fix rename CONFIG_KEYBOARD_NOMADIK to CONFIG_KEYBOARD_NOMADIK_SKE --- drivers/input/keyboard/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index a910d10c5e2..ba0ae47b275 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -375,7 +375,7 @@ config KEYBOARD_NEWTON To compile this driver as a module, choose M here: the module will be called newtonkbd. -config KEYBOARD_NOMADIK +config KEYBOARD_NOMADIK_SKE tristate "ST-Ericsson Nomadik SKE keyboard" depends on PLAT_NOMADIK help -- cgit v1.2.3 From 853909f0d9ebd3e4bc680088342348e8cd7c4eed Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Wed, 20 Oct 2010 19:22:29 +0200 Subject: add html doc for ux500 gpio Signed-off-by: Mian Yousaf Kaukab --- Documentation/DocBook/gpio.tmpl | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 Documentation/DocBook/gpio.tmpl diff --git a/Documentation/DocBook/gpio.tmpl b/Documentation/DocBook/gpio.tmpl new file mode 100644 index 00000000000..b69c2770210 --- /dev/null +++ b/Documentation/DocBook/gpio.tmpl @@ -0,0 +1,112 @@ + + + + + + GPIO1B + + + + Alessandro + Rubini + +
+ rubini@unipv.it +
+
+
+ + Prafulla + WADASKAR + +
+ prafulla.wadaskar@st.com +
+
+
+
+ + + 2008-2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + +
+ + + + Introduction + + This Documentation describes the API's provided by the GPIO controller Driver. + + + Only the API specific to the Ux500 platform is listed here. For the generic GPIO + API, see Documentation/gpio.txt in the kernel source tree. + + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Public Interface + + This Section lists the API's provided by the GPIO controller driver to client drivers. + + + Only the API specific to the Ux500 platform is listed here. For the generic GPIO + API, see Documentation/gpio.txt in the kernel source tree. + +!Earch/arm/plat-nomadik/gpio.c + +
-- cgit v1.2.3 From 81bf5a1eab649baccffe62a35acf5ce1e562e0cb Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 23 Mar 2011 15:37:29 +0100 Subject: keyboard: add tc35893 driver This patch is based on the following work: Keypad tc35893: Fix the Keypad Driver interrupt enable/disable ST-Ericsson ID: ER274519 Author: Ankur Vaish Keypad: Disable EV_REP to disable reporting repeated key events ST-Ericsson Id:ER 264416 Author: Jayeeta Banerjee Add support for TC35893 Keypad Controller ST-Ericsson ID: AP261147 Author: Jayeeta Banerjee Signed-off-by: Mian Yousaf Kaukab --- Documentation/DocBook/Makefile | 3 +- Documentation/DocBook/tc_keypad.tmpl | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 Documentation/DocBook/tc_keypad.tmpl diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index f9b3b24cb8e..815eb52da02 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,7 +15,8 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml media.xml drm.xml \ - i2s.xml msp.xml shrm.xml stmpe.xml touchp.xml + i2s.xml msp.xml shrm.xml stmpe.xml touchp.xml \ + tc_keypad.xml ### # The build process is as follows (targets): diff --git a/Documentation/DocBook/tc_keypad.tmpl b/Documentation/DocBook/tc_keypad.tmpl new file mode 100644 index 00000000000..8c69345df44 --- /dev/null +++ b/Documentation/DocBook/tc_keypad.tmpl @@ -0,0 +1,113 @@ + + + + + + TC35893 Keypad + + + + Jayeeta + Banerjee + +
+ jayeeta.banerjee@stericsson.com +
+
+
+
+ + + 2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + +
+ + + + + Introduction + + This documentation describes the API provided by the keypad driver for TC35893 controller + + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Structures + + This chapter contains the autogenerated documentation of the structures which are + used in the keypad driver. + +!Iarch/arm/mach-ux500/include/mach/tc35893-keypad.h + + + + Public Functions Provided + + Not Applicable. + + + + + Internal Functions Provided + + This chapter contains the autogenerated documentation of the internal functions. + +!Idrivers/input/keyboard/tc35893-keypad.c + + +
-- cgit v1.2.3 From 27bbb0d76d242bdc64074cc1d19aa0ba0de05fdf Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 23 Mar 2011 15:47:22 +0100 Subject: backlight/leds: add low threshold to pwm backlight/led The intensity of the backlight/led can be varied from a range of max_brightness to zero. Though most, if not all the pwm based backlight/led devices start flickering at lower brightness value. And also for each device there exists a brightness value below which the backlight appears to be turned off though the value is not equal to zero. If the range of brightness for a device is from zero to max_brightness. A graph is plotted for brightness Vs intensity fo the pwm based backlight/led device has to be a linear graph. intensity | / | / | / |/ --------- 0 max_brightness But pratically on measuring the above we note that the intensity of backlight/led goes to zero(OFF) when the value in not zero almost nearing to zero(some x%). so the graph looks like intensity | / | / | / | | ------------ 0 x max_brightness In order to overcome this drawback knowing this x% i.e nothing but the low threshold beyond which the backlight/led is off and will have no effect, the brightness value is being offset by the low threshold value(retaining the linearity of the graph). Now the graph becomes intensity | / | / | / | / ------------- 0 max_brightness With this for each and every digit increment in the brightness from zero there is a change in the intensity of backlight/led. Devices having this behaviour can set the low threshold brightness(lth_brightness) and pass the same as platform data else can have it as zero. ST-Ericsson ID: Task168737 Change-Id: I7198ec89aa69e0c687d329b21f723fd8d5368928 Signed-off-by: Prajadevi H Signed-off-by: Arun Murthy Acked-by: Linus Walleij Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/5231 Reviewed-by: Linus WALLEIJ --- drivers/leds/leds-pwm.c | 8 +++++++- include/linux/leds_pwm.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 666daf77872..775cd67c604 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -27,6 +27,7 @@ struct led_pwm_data { struct led_classdev cdev; struct pwm_device *pwm; unsigned int active_low; + unsigned int lth_brightness; unsigned int period; }; @@ -42,7 +43,10 @@ static void led_pwm_set(struct led_classdev *led_cdev, pwm_config(led_dat->pwm, 0, period); pwm_disable(led_dat->pwm); } else { - pwm_config(led_dat->pwm, brightness * period / max, period); + brightness = led_dat->lth_brightness + (brightness * + (led_dat->period - led_dat->lth_brightness) / max); + pwm_config(led_dat->pwm, brightness, led_dat->period); + pwm_enable(led_dat->pwm); } } @@ -79,6 +83,8 @@ static int led_pwm_probe(struct platform_device *pdev) led_dat->cdev.default_trigger = cur_led->default_trigger; led_dat->active_low = cur_led->active_low; led_dat->period = cur_led->pwm_period_ns; + led_dat->lth_brightness = cur_led->lth_brightness * + (cur_led->pwm_period_ns / cur_led->max_brightness); led_dat->cdev.brightness_set = led_pwm_set; led_dat->cdev.brightness = LED_OFF; led_dat->cdev.max_brightness = cur_led->max_brightness; diff --git a/include/linux/leds_pwm.h b/include/linux/leds_pwm.h index 33a07116748..9c5eab6e086 100644 --- a/include/linux/leds_pwm.h +++ b/include/linux/leds_pwm.h @@ -11,6 +11,7 @@ struct led_pwm { u8 active_low; unsigned max_brightness; unsigned pwm_period_ns; + unsigned int lth_brightness; }; struct led_pwm_platform_data { -- cgit v1.2.3 From 879dcc0f428710b469017a2e6033ce8b3f513ca3 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 23 Mar 2011 16:39:57 +0100 Subject: mach-ux500: add platform data for PWM leds driver --- arch/arm/mach-ux500/board-mop500.c | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 153a0b48700..7fee708888c 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include @@ -413,10 +415,90 @@ static void mop500_sensors1p_deactivate(struct device *dev) regulator_put(sensors1p_regulator); } +#ifdef CONFIG_LEDS_PWM +static struct led_pwm pwm_leds_data[] = { + [0] = { + .name = "lcd-backlight", + .pwm_id = 1, + .max_brightness = 255, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, +#ifdef CONFIG_DISPLAY_GENERIC_DSI_SECONDARY + [1] = { + .name = "sec-lcd-backlight", + .pwm_id = 2, + .max_brightness = 255, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, +#endif +}; + +static struct led_pwm_platform_data u8500_leds_data = { +#ifdef CONFIG_DISPLAY_GENERIC_DSI_SECONDARY + .num_leds = 2, +#else + .num_leds = 1, +#endif + .leds = pwm_leds_data, +}; + +static struct platform_device ux500_leds_device = { + .name = "leds_pwm", + .dev = { + .platform_data = &u8500_leds_data, + }, +}; +#endif + +#ifdef CONFIG_BACKLIGHT_PWM +static struct platform_pwm_backlight_data u8500_backlight_data[] = { + [0] = { + .pwm_id = 1, + .max_brightness = 255, + .dft_brightness = 200, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, + [1] = { + .pwm_id = 2, + .max_brightness = 255, + .dft_brightness = 200, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, +}; + +static struct platform_device ux500_backlight_device[] = { + [0] = { + .name = "pwm-backlight", + .id = 0, + .dev = { + .platform_data = &u8500_backlight_data[0], + }, + }, + [1] = { + .name = "pwm-backlight", + .id = 1, + .dev = { + .platform_data = &u8500_backlight_data[1], + }, + }, +}; +#endif + /* add any platform devices here - TODO */ static struct platform_device *mop500_platform_devs[] __initdata = { &mop500_gpio_keys_device, &ab8500_device, +#ifdef CONFIG_LEDS_PWM + &ux500_leds_device, +#endif +#ifdef CONFIG_BACKLIGHT_PWM + &ux500_backlight_device[0], + &ux500_backlight_device[1], +#endif }; #ifdef CONFIG_STE_DMA40 -- cgit v1.2.3 From d7eba7ba65243a76eac29dd06c457a16755ffe72 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 23 Mar 2011 16:45:18 +0100 Subject: mach-ux500: add platform data for LM3530 leds in board-u5500 --- arch/arm/mach-ux500/board-u5500.c | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/arch/arm/mach-ux500/board-u5500.c b/arch/arm/mach-ux500/board-u5500.c index 0abdd6d46bc..fc370df12dd 100644 --- a/arch/arm/mach-ux500/board-u5500.c +++ b/arch/arm/mach-ux500/board-u5500.c @@ -10,11 +10,15 @@ #include #include #include +#include +#include #include #include #include +#include + #include #include #include @@ -22,6 +26,78 @@ #include "devices-db5500.h" +/* + * leds LM3530 + */ + +static struct lm3530_platform_data u5500_als_platform_data = { + .mode = LM3530_BL_MODE_MANUAL, + .als_input_mode = LM3530_INPUT_ALS1, + .max_current = LM3530_FS_CURR_26mA, + .pwm_pol_hi = true, + .als_avrg_time = LM3530_ALS_AVRG_TIME_512ms, + .brt_ramp_law = 1, /* Linear */ + .brt_ramp_fall = LM3530_RAMP_TIME_1ms, + .brt_ramp_rise = LM3530_RAMP_TIME_1ms, + .als1_resistor_sel = LM3530_ALS_IMPD_2_27kOhm, + .als2_resistor_sel = LM3530_ALS_IMPD_2_27kOhm, + .brt_val = 0x7F, /* Max brightness */ +}; + +/* + * I2C + */ + +#define U5500_I2C_CONTROLLER(id, _slsu, _tft, _rft, clk, _sm) \ +static struct nmk_i2c_controller u5500_i2c##id##_data = { \ + /* \ + * slave data setup time, which is \ + * 250 ns,100ns,10ns which is 14,6,2 \ + * respectively for a 48 Mhz \ + * i2c clock \ + */ \ + .slsu = _slsu, \ + /* Tx FIFO threshold */ \ + .tft = _tft, \ + /* Rx FIFO threshold */ \ + .rft = _rft, \ + /* std. mode operation */ \ + .clk_freq = clk, \ + .sm = _sm, \ +} + +/* + * The board uses 3 i2c controllers, initialize all of + * them with slave data setup time of 250 ns, + * Tx & Rx FIFO threshold values as 1 and standard + * mode of operation + */ + +U5500_I2C_CONTROLLER(1, 0xe, 1, 1, 400000, I2C_FREQ_MODE_FAST); +U5500_I2C_CONTROLLER(2, 0xe, 1, 1, 400000, I2C_FREQ_MODE_FAST); +U5500_I2C_CONTROLLER(3, 0xe, 1, 1, 400000, I2C_FREQ_MODE_FAST); + +static struct i2c_board_info __initdata u5500_i2c1_devices[] = { +}; + +static struct i2c_board_info __initdata u5500_i2c2_devices[] = { + { + /* Backlight */ + I2C_BOARD_INFO("lm3530-led", 0x36), + .platform_data = &u5500_als_platform_data, + }, +}; + +static void __init u5500_i2c_init(void) +{ + db5500_add_i2c1(&u5500_i2c1_data); + db5500_add_i2c2(&u5500_i2c2_data); + db5500_add_i2c3(&u5500_i2c3_data); + + i2c_register_board_info(1, ARRAY_AND_SIZE(u5500_i2c1_devices)); + i2c_register_board_info(2, ARRAY_AND_SIZE(u5500_i2c2_devices)); +} + /* * Keypad */ @@ -59,6 +135,7 @@ static void __init u5500_uart_init(void) static void __init u5500_init_machine(void) { u5500_init_devices(); + u5500_i2c_init(); u5500_sdi_init(); u5500_uart_init(); -- cgit v1.2.3 From 8976ebe1b8a893be33e8d8f810bfcb19715b4205 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Tue, 19 Oct 2010 20:17:35 +0200 Subject: sound: add ab8500 audio codec This patch is based on the following work: Add a power management scheme for AB3550 and fix bugs that hinder simultaneous playback/capture. ST-Ericsson ID: WP 259100 Author: Xie Xiaolei msp: add configuration param for MSP_IODLY ST-Ericsson ID: CR261462 Author: Rabin Vincent ab8500-acodec: remove unused dma variables ST-Ericsson ID: AP259210 Author: Rabin Vincent Signed-off-by: Mian Yousaf Kaukab --- arch/arm/mach-ux500/include/mach/ab8500_codec.h | 327 + arch/arm/mach-ux500/include/mach/ab8500_codec_p.h | 3082 +++++++++ .../mach-ux500/include/mach/ab8500_codec_p_v1_0.h | 3037 +++++++++ .../mach-ux500/include/mach/ab8500_codec_v1_0.h | 329 + .../mach-ux500/include/mach/u8500_acodec_ab8500.h | 284 + sound/Kconfig | 40 + sound/Makefile | 7 + sound/ab8500_codec.c | 6697 ++++++++++++++++++++ sound/ab8500_codec_v1_0.c | 6405 +++++++++++++++++++ sound/u8500_acodec_ab8500.c | 2522 ++++++++ 10 files changed, 22730 insertions(+) create mode 100644 arch/arm/mach-ux500/include/mach/ab8500_codec.h create mode 100644 arch/arm/mach-ux500/include/mach/ab8500_codec_p.h create mode 100644 arch/arm/mach-ux500/include/mach/ab8500_codec_p_v1_0.h create mode 100644 arch/arm/mach-ux500/include/mach/ab8500_codec_v1_0.h create mode 100644 arch/arm/mach-ux500/include/mach/u8500_acodec_ab8500.h create mode 100644 sound/ab8500_codec.c create mode 100644 sound/ab8500_codec_v1_0.c create mode 100644 sound/u8500_acodec_ab8500.c diff --git a/arch/arm/mach-ux500/include/mach/ab8500_codec.h b/arch/arm/mach-ux500/include/mach/ab8500_codec.h new file mode 100644 index 00000000000..d45dea66b5d --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ab8500_codec.h @@ -0,0 +1,327 @@ +/*****************************************************************************/ +/** +* © ST-Ericsson, 2009 - All rights reserved +* Reproduction and Communication of this document is strictly prohibited +* unless specifically authorized in writing by ST-Ericsson +* +* \brief Public header file for AB8500 Codec +* \author ST-Ericsson +*/ +/*****************************************************************************/ + +#ifndef _AB8500_CODEC_H_ +#define _AB8500_CODEC_H_ + +/*--------------------------------------------------------------------- + * Includes + *--------------------------------------------------------------------*/ +#include "hcl_defs.h" +#include "debug.h" +#include + +/*--------------------------------------------------------------------- + * Define + *--------------------------------------------------------------------*/ +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { + AB8500_CODEC_OK, + AB8500_CODEC_ERROR, + AB8500_CODEC_UNSUPPORTED_FEATURE, + AB8500_CODEC_INVALID_PARAMETER, + AB8500_CODEC_CONFIG_NOT_COHERENT, + AB8500_CODEC_TRANSACTION_FAILED + } t_ab8500_codec_error; + + typedef enum { + AB8500_CODEC_MASTER_MODE_DISABLE, + AB8500_CODEC_MASTER_MODE_ENABLE + } t_ab8500_codec_master_mode; + + typedef enum { + AB8500_CODEC_SLOT0, + AB8500_CODEC_SLOT1, + AB8500_CODEC_SLOT2, + AB8500_CODEC_SLOT3, + AB8500_CODEC_SLOT4, + AB8500_CODEC_SLOT5, + AB8500_CODEC_SLOT6, + AB8500_CODEC_SLOT7, + AB8500_CODEC_SLOT8, + AB8500_CODEC_SLOT9, + AB8500_CODEC_SLOT10, + AB8500_CODEC_SLOT11, + AB8500_CODEC_SLOT12, + AB8500_CODEC_SLOT13, + AB8500_CODEC_SLOT14, + AB8500_CODEC_SLOT15, + AB8500_CODEC_SLOT16, + AB8500_CODEC_SLOT17, + AB8500_CODEC_SLOT18, + AB8500_CODEC_SLOT19, + AB8500_CODEC_SLOT20, + AB8500_CODEC_SLOT21, + AB8500_CODEC_SLOT22, + AB8500_CODEC_SLOT23, + AB8500_CODEC_SLOT24, + AB8500_CODEC_SLOT25, + AB8500_CODEC_SLOT26, + AB8500_CODEC_SLOT27, + AB8500_CODEC_SLOT28, + AB8500_CODEC_SLOT29, + AB8500_CODEC_SLOT30, + AB8500_CODEC_SLOT31, + AB8500_CODEC_SLOT_UNDEFINED + } t_ab8500_codec_slot; + + typedef enum { + AB8500_CODEC_DA_CHANNEL_NUMBER_1, + AB8500_CODEC_DA_CHANNEL_NUMBER_2, + AB8500_CODEC_DA_CHANNEL_NUMBER_3, + AB8500_CODEC_DA_CHANNEL_NUMBER_4, + AB8500_CODEC_DA_CHANNEL_NUMBER_5, + AB8500_CODEC_DA_CHANNEL_NUMBER_6, + AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED + } t_ab8500_codec_da_channel_number; + + typedef enum { + AB8500_CODEC_SRC_STATE_DISABLE, + AB8500_CODEC_SRC_STATE_ENABLE + } t_ab8500_codec_src_state; + + typedef enum { + AB8500_CODEC_DEST_STATE_DISABLE, + AB8500_CODEC_DEST_STATE_ENABLE + } t_ab8500_codec_dest_state; + + typedef struct { + t_ab8500_codec_cr27_if1_bitclk_osr cr27_if1_bitclk_osr; + t_ab8500_codec_cr27_if0_bitclk_osr cr27_if0_bitclk_osr; + t_ab8500_codec_cr28_if0wl cr28_if0wl; + t_ab8500_codec_cr30_if1wl cr30_if1wl; + t_ab8500_codec_cr28_bitclk0p cr28_bitclk0p; + t_ab8500_codec_cr28_if0del cr28_if0del; + } t_ab8500_codec_tdm_config; + + typedef struct { + t_ab8500_codec_cr104_bfifoint cr104_bfifoint; + t_ab8500_codec_cr105_bfifotx cr105_bfifotx; + t_ab8500_codec_cr106_bfifofsext cr106_bfifofsext; + t_ab8500_codec_cr106_bfifomsk cr106_bfifomsk; + t_ab8500_codec_cr106_bfifomstr cr106_bfifomstr; + t_ab8500_codec_cr106_bfifostrt cr106_bfifostrt; + t_ab8500_codec_cr107_bfifosampnr cr107_bfifosampnr; + t_ab8500_codec_cr108_bfifowakeup cr108_bfifowakeup; + } t_ab8500_codec_burst_fifo_config; + +/************************************************************/ +/*--------------------------------------------------------------------- + * Exported APIs + *--------------------------------------------------------------------*/ +/* Initialization */ + t_ab8500_codec_error AB8500_CODEC_Init(IN t_uint8 + slave_address_of_codec); + t_ab8500_codec_error AB8500_CODEC_Reset(void); + +/* Audio Codec basic configuration */ + t_ab8500_codec_error AB8500_CODEC_SetModeAndDirection(IN + t_ab8500_codec_direction + ab8500_codec_direction, + IN + t_ab8500_codec_mode + ab8500_codec_mode_in, + IN + t_ab8500_codec_mode + ab8500_codec_mode_out, + IN + t_ab8500_codec_tdm_config + const *const + p_tdm_config); + t_ab8500_codec_error AB8500_CODEC_SelectInput(IN t_ab8500_codec_src + ab8500_codec_src); + t_ab8500_codec_error AB8500_CODEC_SelectOutput(IN t_ab8500_codec_dest + ab8500_codec_dest); + +/* Burst FIFO configuration */ + t_ab8500_codec_error AB8500_CODEC_ConfigureBurstFifo(IN + t_ab8500_codec_burst_fifo_config + const *const + p_burst_fifo_config); + t_ab8500_codec_error AB8500_CODEC_EnableBurstFifo(void); + t_ab8500_codec_error AB8500_CODEC_DisableBurstFifo(void); + +/* Audio Codec Master mode configuration */ + t_ab8500_codec_error AB8500_CODEC_SetMasterMode(IN + t_ab8500_codec_master_mode + mode); + +/* APIs to be implemented by user */ + t_ab8500_codec_error AB8500_CODEC_Write(IN t_uint8 register_offset, + IN t_uint8 count, + IN t_uint8 * p_data); + t_ab8500_codec_error AB8500_CODEC_Read(IN t_uint8 register_offset, + IN t_uint8 count, + IN t_uint8 * p_dummy_data, + IN t_uint8 * p_data); + +/* Volume Management */ + t_ab8500_codec_error AB8500_CODEC_SetSrcVolume(IN t_ab8500_codec_src + src_device, + IN t_uint8 + in_left_volume, + IN t_uint8 + in_right_volume); + t_ab8500_codec_error AB8500_CODEC_SetDestVolume(IN t_ab8500_codec_dest + dest_device, + IN t_uint8 + out_left_volume, + IN t_uint8 + out_right_volume); + +/* Power management */ + t_ab8500_codec_error AB8500_CODEC_PowerDown(void); + t_ab8500_codec_error AB8500_CODEC_PowerUp(void); + +/* Interface Management */ + t_ab8500_codec_error AB8500_CODEC_SelectInterface(IN + t_ab8500_codec_audio_interface + audio_interface); + t_ab8500_codec_error AB8500_CODEC_GetInterface(OUT + t_ab8500_codec_audio_interface + * p_audio_interface); + +/* Slot Allocation */ + t_ab8500_codec_error AB8500_CODEC_ADSlotAllocation(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); + t_ab8500_codec_error AB8500_CODEC_DASlotAllocation(IN + t_ab8500_codec_da_channel_number + channel_number, + IN + t_ab8500_codec_cr51_to_cr56_sltoda + slot); + +/* Loopback Management */ + t_ab8500_codec_error AB8500_CODEC_SetAnalogLoopback(IN t_uint8 + out_left_volume, + IN t_uint8 + out_right_volume); + t_ab8500_codec_error AB8500_CODEC_RemoveAnalogLoopback(void); + +/* Bypass Management */ + t_ab8500_codec_error AB8500_CODEC_EnableBypassMode(void); + t_ab8500_codec_error AB8500_CODEC_DisableBypassMode(void); + +/* Power Control Management */ + t_ab8500_codec_error AB8500_CODEC_SrcPowerControl(IN t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state); + t_ab8500_codec_error AB8500_CODEC_DestPowerControl(IN + t_ab8500_codec_dest + dest_device, + t_ab8500_codec_dest_state + state); + +/* Version Management */ + t_ab8500_codec_error AB8500_CODEC_GetVersion(OUT t_version * p_version); + +#if 0 +/* Debug management */ + t_ab8500_codec_error AB8500_CODEC_SetDbgLevel(IN t_dbg_level dbg_level); + t_ab8500_codec_error AB8500_CODEC_GetDbgLevel(OUT t_dbg_level * + p_dbg_level); +#endif + +/* +** following is added by $kardad$ +*/ + +/* duplicate copy of enum from msp.h */ +/* for MSPConfiguration.in_clock_freq parameter to select msp clock freq */ + typedef enum { + CODEC_MSP_INPUT_FREQ_1MHZ = 1024, + CODEC_MSP_INPUT_FREQ_2MHZ = 2048, + CODEC_MSP_INPUT_FREQ_3MHZ = 3072, + CODEC_MSP_INPUT_FREQ_4MHZ = 4096, + CODEC_MSP_INPUT_FREQ_5MHZ = 5760, + CODEC_MSP_INPUT_FREQ_6MHZ = 6144, + CODEC_MSP_INPUT_FREQ_8MHZ = 8192, + CODEC_MSP_INPUT_FREQ_11MHZ = 11264, + CODEC_MSP_INPUT_FREQ_12MHZ = 12288, + CODEC_MSP_INPUT_FREQ_16MHZ = 16384, + CODEC_MSP_INPUT_FREQ_22MHZ = 22579, + CODEC_MSP_INPUT_FREQ_24MHZ = 24576, + CODEC_MSP_INPUT_FREQ_48MHZ = 49152 + } codec_msp_in_clock_freq_type; + +/* msp clock source internal/external for srg_clock_sel */ + typedef enum { + CODEC_MSP_APB_CLOCK = 0, + CODEC_MSP_SCK_CLOCK = 2, + CODEC_MSP_SCK_SYNC_CLOCK = 3 + } codec_msp_srg_clock_sel_type; + +/* Sample rate supported by Codec */ + + typedef enum { + CODEC_FREQUENCY_DONT_CHANGE = -100, + CODEC_SAMPLING_FREQ_RESET = -1, + CODEC_SAMPLING_FREQ_MINLIMIT = 7, + CODEC_SAMPLING_FREQ_8KHZ = 8, /*default */ + CODEC_SAMPLING_FREQ_11KHZ = 11, + CODEC_SAMPLING_FREQ_12KHZ = 12, + CODEC_SAMPLING_FREQ_16KHZ = 16, + CODEC_SAMPLING_FREQ_22KHZ = 22, + CODEC_SAMPLING_FREQ_24KHZ = 24, + CODEC_SAMPLING_FREQ_32KHZ = 32, + CODEC_SAMPLING_FREQ_44KHZ = 44, + CODEC_SAMPLING_FREQ_48KHZ = 48, + CODEC_SAMPLING_FREQ_64KHZ = 64, /*the frequencies below this line are not supported in stw5094A */ + CODEC_SAMPLING_FREQ_88KHZ = 88, + CODEC_SAMPLING_FREQ_96KHZ = 96, + CODEC_SAMPLING_FREQ_128KHZ = 128, + CODEC_SAMPLING_FREQ_176KHZ = 176, + CODEC_SAMPLING_FREQ_192KHZ = 192, + CODEC_SAMPLING_FREQ_MAXLIMIT = 193 + } t_codec_sample_frequency; + +#define RESET -1 +#define DEFAULT -100 +/***********************************************************/ +/* +** following stuff is added to compile code without debug print support $kardad$ +*/ + +#define DBGEXIT(cr) +#define DBGEXIT0(cr) +#define DBGEXIT1(cr,ch,p1) +#define DBGEXIT2(cr,ch,p1,p2) +#define DBGEXIT3(cr,ch,p1,p2,p3) +#define DBGEXIT4(cr,ch,p1,p2,p3,p4) +#define DBGEXIT5(cr,ch,p1,p2,p3,p4,p5) +#define DBGEXIT6(cr,ch,p1,p2,p3,p4,p5,p6) + +#define DBGENTER() +#define DBGENTER0() +#define DBGENTER1(ch,p1) +#define DBGENTER2(ch,p1,p2) +#define DBGENTER3(ch,p1,p2,p3) +#define DBGENTER4(ch,p1,p2,p3,p4) +#define DBGENTER5(ch,p1,p2,p3,p4,p5) +#define DBGENTER6(ch,p1,p2,p3,p4,p5,p6) + +#define DBGPRINT(dbg_level,dbg_string) +#define DBGPRINTHEX(dbg_level,dbg_string,uint32) +#define DBGPRINTDEC(dbg_level,dbg_string,uint32) +/***********************************************************/ + +#ifdef __cplusplus +} /* allow C++ to use these headers */ +#endif /* __cplusplus */ +#endif /* _AB8500_CODEC_H_ */ +/* End of file ab8500_codec.h*/ diff --git a/arch/arm/mach-ux500/include/mach/ab8500_codec_p.h b/arch/arm/mach-ux500/include/mach/ab8500_codec_p.h new file mode 100644 index 00000000000..847a1729e44 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ab8500_codec_p.h @@ -0,0 +1,3082 @@ +/*****************************************************************************/ +/** +* © ST-Ericsson, 2009 - All rights reserved +* Reproduction and Communication of this document is strictly prohibited +* unless specifically authorized in writing by ST-Ericsson + * +* \brief Private Header file for AB8500 CODEC +* \author ST-Ericsson + */ +/*****************************************************************************/ + +#ifndef _AB8500_CODECP_H_ +#define _AB8500_CODECP_H_ + +/*---------------------------------------------------------------------------- + * Includes + *---------------------------------------------------------------------------*/ +#include "hcl_defs.h" + +#define AB8500_CODEC_HCL_VERSION_ID 2 +#define AB8500_CODEC_HCL_MAJOR_ID 0 +#define AB8500_CODEC_HCL_MINOR_ID 0 + +#define AB8500_CODEC_MASK_ONE_BIT 0x1UL +#define AB8500_CODEC_MASK_TWO_BITS 0x3UL +#define AB8500_CODEC_MASK_THREE_BITS 0x7UL +#define AB8500_CODEC_MASK_FOUR_BITS 0xFUL +#define AB8500_CODEC_MASK_FIVE_BITS 0x1FUL +#define AB8500_CODEC_MASK_SIX_BITS 0x3FUL +#define AB8500_CODEC_MASK_SEVEN_BITS 0x7FUL +#define AB8500_CODEC_MASK_EIGHT_BITS 0xFFUL + +#define AB8500_CODEC_WRITE_BITS(reg, val, bit_nb, pos) (reg) = ((t_uint8) ((((reg) & (~(bit_nb << pos))) | (((val) & bit_nb) << pos)))) + +#define AB8500_CODEC_BLOCK 0x0D + +#define AB8500_CODEC_MASK_TWO_MS_BITS 0xC0UL +#define AB8500_CODEC_MASK_SIX_LS_BITS 0x3FUL + +/* Genepi AudioCodec Control Registers */ + +#define AB8500_CODEC_CR0 0x00 +#define AB8500_CODEC_CR1 0x01 +#define AB8500_CODEC_CR2 0x02 +#define AB8500_CODEC_CR3 0x03 +#define AB8500_CODEC_CR4 0x04 +#define AB8500_CODEC_CR5 0x05 +#define AB8500_CODEC_CR6 0x06 +#define AB8500_CODEC_CR7 0x07 +#define AB8500_CODEC_CR8 0x08 +#define AB8500_CODEC_CR9 0x09 +#define AB8500_CODEC_CR10 0x0A +#define AB8500_CODEC_CR11 0x0B +#define AB8500_CODEC_CR12 0x0C +#define AB8500_CODEC_CR13 0x0D +#define AB8500_CODEC_CR14 0x0E +#define AB8500_CODEC_CR15 0x0F +#define AB8500_CODEC_CR16 0x10 +#define AB8500_CODEC_CR17 0x11 +#define AB8500_CODEC_CR18 0x12 +#define AB8500_CODEC_CR19 0x13 +#define AB8500_CODEC_CR20 0x14 +#define AB8500_CODEC_CR21 0x15 +#define AB8500_CODEC_CR22 0x16 +#define AB8500_CODEC_CR23 0x17 +#define AB8500_CODEC_CR24 0x18 +#define AB8500_CODEC_CR25 0x19 +#define AB8500_CODEC_CR26 0x1A +#define AB8500_CODEC_CR27 0x1B +#define AB8500_CODEC_CR28 0x1C +#define AB8500_CODEC_CR29 0x1D +#define AB8500_CODEC_CR30 0x1E +#define AB8500_CODEC_CR31 0x1F +#define AB8500_CODEC_CR32 0x20 +#define AB8500_CODEC_CR33 0x21 +#define AB8500_CODEC_CR34 0x22 +#define AB8500_CODEC_CR35 0x23 +#define AB8500_CODEC_CR36 0x24 +#define AB8500_CODEC_CR37 0x25 +#define AB8500_CODEC_CR38 0x26 +#define AB8500_CODEC_CR39 0x27 +#define AB8500_CODEC_CR40 0x28 +#define AB8500_CODEC_CR41 0x29 +#define AB8500_CODEC_CR42 0x2A +#define AB8500_CODEC_CR43 0x2B +#define AB8500_CODEC_CR44 0x2C +#define AB8500_CODEC_CR45 0x2D +#define AB8500_CODEC_CR46 0x2E +#define AB8500_CODEC_CR47 0x2F +#define AB8500_CODEC_CR48 0x30 +#define AB8500_CODEC_CR49 0x31 +#define AB8500_CODEC_CR50 0x32 +#define AB8500_CODEC_CR51 0x33 +#define AB8500_CODEC_CR52 0x34 +#define AB8500_CODEC_CR53 0x35 +#define AB8500_CODEC_CR54 0x36 +#define AB8500_CODEC_CR55 0x37 +#define AB8500_CODEC_CR56 0x38 +#define AB8500_CODEC_CR57 0x39 +#define AB8500_CODEC_CR58 0x3A +#define AB8500_CODEC_CR59 0x3B +#define AB8500_CODEC_CR60 0x3C +#define AB8500_CODEC_CR61 0x3D +#define AB8500_CODEC_CR62 0x3E +#define AB8500_CODEC_CR63 0x3F +#define AB8500_CODEC_CR64 0x40 +#define AB8500_CODEC_CR65 0x41 +#define AB8500_CODEC_CR66 0x42 +#define AB8500_CODEC_CR67 0x43 +#define AB8500_CODEC_CR68 0x44 +#define AB8500_CODEC_CR69 0x45 +#define AB8500_CODEC_CR70 0x46 +#define AB8500_CODEC_CR71 0x47 +#define AB8500_CODEC_CR72 0x48 +#define AB8500_CODEC_CR73 0x49 +#define AB8500_CODEC_CR74 0x4A +#define AB8500_CODEC_CR75 0x4B +#define AB8500_CODEC_CR76 0x4C +#define AB8500_CODEC_CR77 0x4D +#define AB8500_CODEC_CR78 0x4E +#define AB8500_CODEC_CR79 0x4F +#define AB8500_CODEC_CR80 0x50 +#define AB8500_CODEC_CR81 0x51 +#define AB8500_CODEC_CR82 0x52 +#define AB8500_CODEC_CR83 0x53 +#define AB8500_CODEC_CR84 0x54 +#define AB8500_CODEC_CR85 0x55 +#define AB8500_CODEC_CR86 0x56 +#define AB8500_CODEC_CR87 0x57 +#define AB8500_CODEC_CR88 0x58 +#define AB8500_CODEC_CR89 0x59 +#define AB8500_CODEC_CR90 0x5A +#define AB8500_CODEC_CR91 0x5B +#define AB8500_CODEC_CR92 0x5C +#define AB8500_CODEC_CR93 0x5D +#define AB8500_CODEC_CR94 0x5E +#define AB8500_CODEC_CR95 0x5F +#define AB8500_CODEC_CR96 0x60 +#define AB8500_CODEC_CR97 0x61 +#define AB8500_CODEC_CR98 0x62 +#define AB8500_CODEC_CR99 0x63 +#define AB8500_CODEC_CR100 0x64 +#define AB8500_CODEC_CR101 0x65 +#define AB8500_CODEC_CR102 0x66 +#define AB8500_CODEC_CR103 0x67 +#define AB8500_CODEC_CR104 0x68 +#define AB8500_CODEC_CR105 0x69 +#define AB8500_CODEC_CR106 0x6A +#define AB8500_CODEC_CR107 0x6B +#define AB8500_CODEC_CR108 0x6C +#define AB8500_CODEC_CR109 0x6D + +/* CR0-CR0x0000 */ +#define AB8500_CODEC_CR0_POWERUP 7 +#define AB8500_CODEC_CR0_ENAANA 3 + +/* CR1-CR0x0001 */ +#define AB8500_CODEC_CR1_SWRESET 7 + +/* CR2-CR0x0002 */ +#define AB8500_CODEC_CR2_ENAD1 7 +#define AB8500_CODEC_CR2_ENAD2 6 +#define AB8500_CODEC_CR2_ENAD3 5 +#define AB8500_CODEC_CR2_ENAD4 4 +#define AB8500_CODEC_CR2_ENAD5 3 +#define AB8500_CODEC_CR2_ENAD6 2 + +/* CR3-CR0x0003 */ +#define AB8500_CODEC_CR3_ENDA1 7 +#define AB8500_CODEC_CR3_ENDA2 6 +#define AB8500_CODEC_CR3_ENDA3 5 +#define AB8500_CODEC_CR3_ENDA4 4 +#define AB8500_CODEC_CR3_ENDA5 3 +#define AB8500_CODEC_CR3_ENDA6 2 + +/* CR4-CR0x0004 */ +#define AB8500_CODEC_CR4_LOWPOWHS 7 +#define AB8500_CODEC_CR4_LOWPOWDACHS 5 +#define AB8500_CODEC_CR4_LOWPOWEAR 4 +#define AB8500_CODEC_CR4_EAR_SEL_CM 2 +#define AB8500_CODEC_CR4_HS_HP_DIS 1 +#define AB8500_CODEC_CR4_EAR_HP_DIS 0 + +/* CR5-CR0x0005 */ +#define AB8500_CODEC_CR5_ENMIC1 7 +#define AB8500_CODEC_CR5_ENMIC2 6 +#define AB8500_CODEC_CR5_ENLINL 5 +#define AB8500_CODEC_CR5_ENLINR 4 +#define AB8500_CODEC_CR5_MUTMIC1 3 +#define AB8500_CODEC_CR5_MUTMIC2 2 +#define AB8500_CODEC_CR5_MUTELINL 1 +#define AB8500_CODEC_CR5_MUTELINR 0 + +/* CR6-CR0x0006 */ +#define AB8500_CODEC_CR6_ENDMIC1 7 +#define AB8500_CODEC_CR6_ENDMIC2 6 +#define AB8500_CODEC_CR6_ENDMIC3 5 +#define AB8500_CODEC_CR6_ENDMIC4 4 +#define AB8500_CODEC_CR6_ENDMIC5 3 +#define AB8500_CODEC_CR6_ENDMIC6 2 + +/* CR7-CR0x0007 */ +#define AB8500_CODEC_CR7_MIC1SEL 7 +#define AB8500_CODEC_CR7_LINRSEL 6 +#define AB8500_CODEC_CR7_ENDRVHSL 5 +#define AB8500_CODEC_CR7_ENDRVHSR 4 +#define AB8500_CODEC_CR7_ENADCMIC 2 +#define AB8500_CODEC_CR7_ENADCLINL 1 +#define AB8500_CODEC_CR7_ENADCLINR 0 + +/* CR8-CR0x0008 */ +#define AB8500_CODEC_CR8_CP_DIS_PLDWN 7 +#define AB8500_CODEC_CR8_ENEAR 6 +#define AB8500_CODEC_CR8_ENHSL 5 +#define AB8500_CODEC_CR8_ENHSR 4 +#define AB8500_CODEC_CR8_ENHFL 3 +#define AB8500_CODEC_CR8_ENHFR 2 +#define AB8500_CODEC_CR8_ENVIBL 1 +#define AB8500_CODEC_CR8_ENVIBR 0 + +/* CR9-CR0x0009 */ +#define AB8500_CODEC_CR9_ENADACEAR 6 +#define AB8500_CODEC_CR9_ENADACHSL 5 +#define AB8500_CODEC_CR9_ENADACHSR 4 +#define AB8500_CODEC_CR9_ENADACHFL 3 +#define AB8500_CODEC_CR9_ENADACHFR 2 +#define AB8500_CODEC_CR9_ENADACVIBL 1 +#define AB8500_CODEC_CR9_ENADACVIBR 0 + +/* CR10-CR0x000A */ +#define AB8500_CODEC_CR10_MUTEEAR 6 +#define AB8500_CODEC_CR10_MUTEHSL 5 +#define AB8500_CODEC_CR10_MUTEHSR 4 +#define AB8500_CODEC_CR10_MUTEHFL 3 +#define AB8500_CODEC_CR10_MUTEHFR 2 +#define AB8500_CODEC_CR10_MUTEVIBL 1 +#define AB8500_CODEC_CR10_MUTEVIBR 0 + +/* CR11-CR0x000B */ +#define AB8500_CODEC_CR11_ENSHORTPWD 7 +#define AB8500_CODEC_CR11_EARSHORTDIS 6 +#define AB8500_CODEC_CR11_HSLSHORTDIS 5 +#define AB8500_CODEC_CR11_HSRSHORTDIS 4 +#define AB8500_CODEC_CR11_HFLSHORTDIS 3 +#define AB8500_CODEC_CR11_HFRSHORTDIS 2 +#define AB8500_CODEC_CR11_VIBLSHORTDIS 1 +#define AB8500_CODEC_CR11_VIBRSHORTDIS 0 + +/* CR12-CR0x000C */ +#define AB8500_CODEC_CR12_ENCPHS 7 +#define AB8500_CODEC_CR12_HSAUTOTIME 4 +#define AB8500_CODEC_CR12_HSAUTOENSEL 1 +#define AB8500_CODEC_CR12_HSAUTOEN 0 + +/* CR13-CR0x000D */ +#define AB8500_CODEC_CR13_ENVDET_HTHRESH 4 +#define AB8500_CODEC_CR13_ENVDET_LTHRESH 0 + +/* CR14-CR0x000E */ +#define AB8500_CODEC_CR14_SMPSLVEN 7 +#define AB8500_CODEC_CR14_ENVDETSMPSEN 6 +#define AB8500_CODEC_CR14_CPLVEN 5 +#define AB8500_CODEC_CR14_ENVDETCPEN 4 +#define AB8500_CODEC_CR14_ENVDET_TIME 0 + +/* CR15-CR0x000F */ +#define AB8500_CODEC_CR15_PWMTOVIBL 7 +#define AB8500_CODEC_CR15_PWMTOVIBR 6 +#define AB8500_CODEC_CR15_PWMLCTRL 5 +#define AB8500_CODEC_CR15_PWMRCTRL 4 +#define AB8500_CODEC_CR15_PWMNLCTRL 3 +#define AB8500_CODEC_CR15_PWMPLCTRL 2 +#define AB8500_CODEC_CR15_PWMNRCTRL 1 +#define AB8500_CODEC_CR15_PWMPRCTRL 0 + +/* CR16-CR0x0010 */ +#define AB8500_CODEC_CR16_PWMNLPOL 7 +#define AB8500_CODEC_CR16_PWMNLDUTYCYCLE 0 + +/* CR17-CR0x0011 */ +#define AB8500_CODEC_CR17_PWMPLPOL 7 +#define AB8500_CODEC_CR17_PWMLPDUTYCYCLE 0 + +/* CR18-CR0x0012 */ +#define AB8500_CODEC_CR18_PWMNRPOL 7 +#define AB8500_CODEC_CR18_PWMNRDUTYCYCLE 0 + +/* CR19-CR0x0013 */ +#define AB8500_CODEC_CR19_PWMPRPOL 7 +#define AB8500_CODEC_CR19_PWMRPDUTYCYCLE 0 + +/* CR20-CR0x0014 */ +#define AB8500_CODEC_CR20_EN_SE_MIC1 7 +#define AB8500_CODEC_CR20_MIC1_GAIN 0 + +/* CR21-CR0x0015 */ +#define AB8500_CODEC_CR21_EN_SE_MIC2 7 +#define AB8500_CODEC_CR21_MIC2_GAIN 0 + +/* CR22-CR0x0016 */ +#define AB8500_CODEC_CR22_HSL_GAIN 5 +#define AB8500_CODEC_CR22_LINL_GAIN 0 + +/* CR23-CR0x0017 */ +#define AB8500_CODEC_CR23_HSR_GAIN 5 +#define AB8500_CODEC_CR23_LINR_GAIN 0 + +/* CR24-CR0x0018 */ +#define AB8500_CODEC_CR24_LINTOHSL_GAIN 0 + +/* CR25-CR0x0019 */ +#define AB8500_CODEC_CR25_LINTOHSR_GAIN 0 + +/* CR26-CR0x001A */ +#define AB8500_CODEC_CR26_AD1NH 7 +#define AB8500_CODEC_CR26_AD2NH 6 +#define AB8500_CODEC_CR26_AD3NH 5 +#define AB8500_CODEC_CR26_AD4NH 4 +#define AB8500_CODEC_CR26_AD1_VOICE 3 +#define AB8500_CODEC_CR26_AD2_VOICE 2 +#define AB8500_CODEC_CR26_AD3_VOICE 1 +#define AB8500_CODEC_CR26_AD4_VOICE 0 + +/* CR27-CR0x001B */ +#define AB8500_CODEC_CR27_EN_MASTGEN 7 +#define AB8500_CODEC_CR27_IF1_BITCLK_OSR 5 +#define AB8500_CODEC_CR27_ENFS_BITCLK1 4 +#define AB8500_CODEC_CR27_IF0_BITCLK_OSR 1 +#define AB8500_CODEC_CR27_ENFS_BITCLK0 0 + +/* CR28-CR0x001C */ +#define AB8500_CODEC_CR28_FSYNC0P 6 +#define AB8500_CODEC_CR28_BITCLK0P 5 +#define AB8500_CODEC_CR28_IF0DEL 4 +#define AB8500_CODEC_CR28_IF0FORMAT 2 +#define AB8500_CODEC_CR28_IF0WL 0 + +/* CR29-CR0x001D */ +#define AB8500_CODEC_CR29_IF0DATOIF1AD 7 +#define AB8500_CODEC_CR29_IF0CKTOIF1CK 6 +#define AB8500_CODEC_CR29_IF1MASTER 5 +#define AB8500_CODEC_CR29_IF1DATOIF0AD 3 +#define AB8500_CODEC_CR29_IF1CKTOIF0CK 2 +#define AB8500_CODEC_CR29_IF0MASTER 1 +#define AB8500_CODEC_CR29_IF0BFIFOEN 0 + +/* CR30-CR0x001E */ +#define AB8500_CODEC_CR30_FSYNC1P 6 +#define AB8500_CODEC_CR30_BITCLK1P 5 +#define AB8500_CODEC_CR30_IF1DEL 4 +#define AB8500_CODEC_CR30_IF1FORMAT 2 +#define AB8500_CODEC_CR30_IF1WL 0 + +/* CR31-CR0x001F */ +#define AB8500_CODEC_CR31_ADOTOSLOT1 4 +#define AB8500_CODEC_CR31_ADOTOSLOT0 0 + +/* CR32-CR0x0020 */ +#define AB8500_CODEC_CR32_ADOTOSLOT3 4 +#define AB8500_CODEC_CR32_ADOTOSLOT2 0 + +/* CR33-CR0x0021 */ +#define AB8500_CODEC_CR33_ADOTOSLOT5 4 +#define AB8500_CODEC_CR33_ADOTOSLOT4 0 + +/* CR34-CR0x0022 */ +#define AB8500_CODEC_CR34_ADOTOSLOT7 4 +#define AB8500_CODEC_CR34_ADOTOSLOT6 0 + +/* CR35-CR0x0023 */ +#define AB8500_CODEC_CR35_ADOTOSLOT9 4 +#define AB8500_CODEC_CR35_ADOTOSLOT8 0 + +/* CR36-CR0x0024 */ +#define AB8500_CODEC_CR36_ADOTOSLOT11 4 +#define AB8500_CODEC_CR36_ADOTOSLOT10 0 + +/* CR37-CR0x0025 */ +#define AB8500_CODEC_CR37_ADOTOSLOT13 4 +#define AB8500_CODEC_CR37_ADOTOSLOT12 0 + +/* CR38-CR0x0026 */ +#define AB8500_CODEC_CR38_ADOTOSLOT15 4 +#define AB8500_CODEC_CR38_ADOTOSLOT14 0 + +/* CR39-CR0x0027 */ +#define AB8500_CODEC_CR39_ADOTOSLOT17 4 +#define AB8500_CODEC_CR39_ADOTOSLOT16 0 + +/* CR40-CR0x0028 */ +#define AB8500_CODEC_CR40_ADOTOSLOT19 4 +#define AB8500_CODEC_CR40_ADOTOSLOT18 0 + +/* CR41-CR0x0029 */ +#define AB8500_CODEC_CR41_ADOTOSLOT21 4 +#define AB8500_CODEC_CR41_ADOTOSLOT20 0 + +/* CR42-CR0x002A */ +#define AB8500_CODEC_CR42_ADOTOSLOT23 4 +#define AB8500_CODEC_CR42_ADOTOSLOT22 0 + +/* CR43-CR0x002B */ +#define AB8500_CODEC_CR43_ADOTOSLOT25 4 +#define AB8500_CODEC_CR43_ADOTOSLOT24 0 + +/* CR44-CR0x002C */ +#define AB8500_CODEC_CR44_ADOTOSLOT27 4 +#define AB8500_CODEC_CR44_ADOTOSLOT26 0 + +/* CR45-CR0x002D */ +#define AB8500_CODEC_CR45_ADOTOSLOT29 4 +#define AB8500_CODEC_CR45_ADOTOSLOT28 0 + +/* CR46-CR0x002E */ +#define AB8500_CODEC_CR46_ADOTOSLOT31 4 +#define AB8500_CODEC_CR46_ADOTOSLOT30 0 + +/* CR47-CR0x002F */ +#define AB8500_CODEC_CR47_HIZ_SL7 7 +#define AB8500_CODEC_CR47_HIZ_SL6 6 +#define AB8500_CODEC_CR47_HIZ_SL5 5 +#define AB8500_CODEC_CR47_HIZ_SL4 4 +#define AB8500_CODEC_CR47_HIZ_SL3 3 +#define AB8500_CODEC_CR47_HIZ_SL2 2 +#define AB8500_CODEC_CR47_HIZ_SL1 1 +#define AB8500_CODEC_CR47_HIZ_SL0 0 + +/* CR48-CR0x0030 */ +#define AB8500_CODEC_CR48_HIZ_SL15 7 +#define AB8500_CODEC_CR48_HIZ_SL14 6 +#define AB8500_CODEC_CR48_HIZ_SL13 5 +#define AB8500_CODEC_CR48_HIZ_SL12 4 +#define AB8500_CODEC_CR48_HIZ_SL11 3 +#define AB8500_CODEC_CR48_HIZ_SL10 2 +#define AB8500_CODEC_CR48_HIZ_SL9 1 +#define AB8500_CODEC_CR48_HIZ_SL8 0 + +/* CR49-CR0x0031 */ +#define AB8500_CODEC_CR49_HIZ_SL23 7 +#define AB8500_CODEC_CR49_HIZ_SL22 6 +#define AB8500_CODEC_CR49_HIZ_SL21 5 +#define AB8500_CODEC_CR49_HIZ_SL20 4 +#define AB8500_CODEC_CR49_HIZ_SL19 3 +#define AB8500_CODEC_CR49_HIZ_SL18 2 +#define AB8500_CODEC_CR49_HIZ_SL17 1 +#define AB8500_CODEC_CR49_HIZ_SL16 0 + +/* CR50-CR0x0032 */ +#define AB8500_CODEC_CR50_HIZ_SL31 7 +#define AB8500_CODEC_CR50_HIZ_SL30 6 +#define AB8500_CODEC_CR50_HIZ_SL29 5 +#define AB8500_CODEC_CR50_HIZ_SL28 4 +#define AB8500_CODEC_CR50_HIZ_SL27 3 +#define AB8500_CODEC_CR50_HIZ_SL26 2 +#define AB8500_CODEC_CR50_HIZ_SL25 1 +#define AB8500_CODEC_CR50_HIZ_SL24 0 + +/* CR51-CR0x0033 */ +#define AB8500_CODEC_CR51_DA12_VOICE 7 +#define AB8500_CODEC_CR51_SLDAI1TOSLADO1 5 +#define AB8500_CODEC_CR51_SLTODA1 0 + +/* CR52-CR0x0034 */ +#define AB8500_CODEC_CR52_SLDAI1TOSLADO2 5 +#define AB8500_CODEC_CR52_SLTODA2 0 + +/* CR53-CR0x0035 */ +#define AB8500_CODEC_CR53_DA34_VOICE 7 +#define AB8500_CODEC_CR53_SLDAI1TOSLADO3 5 +#define AB8500_CODEC_CR53_SLTODA3 0 + +/* CR54-CR0x0036 */ +#define AB8500_CODEC_CR54_SLDAI1TOSLADO4 5 +#define AB8500_CODEC_CR54_SLTODA4 0 + +/* CR55-CR0x0037 */ +#define AB8500_CODEC_CR55_DA56_VOICE 7 +#define AB8500_CODEC_CR55_SLDAI1TOSLADO5 5 +#define AB8500_CODEC_CR55_SLTODA5 0 + +/* CR56-CR0x0038 */ +#define AB8500_CODEC_CR56_SLDAI1TOSLADO6 5 +#define AB8500_CODEC_CR56_SLTODA6 0 + +/* CR57-CR0x0039 */ +#define AB8500_CODEC_CR57_BFIFULL_MSK 6 +#define AB8500_CODEC_CR57_BFIEMPT_MSK 5 +#define AB8500_CODEC_CR57_DACHAN_MSK 4 +#define AB8500_CODEC_CR57_GAIN_MSK 3 +#define AB8500_CODEC_CR57_DSPAD_MSK 2 +#define AB8500_CODEC_CR57_DSPDA_MSK 1 +#define AB8500_CODEC_CR57_STFIR_MSK 0 + +/* CR58-CR0x003A */ +#define AB8500_CODEC_CR58_BFIFULL_EV 6 +#define AB8500_CODEC_CR58_BFIEMPT_EV 5 +#define AB8500_CODEC_CR58_DACHAN_EV 4 +#define AB8500_CODEC_CR58_GAIN_EV 3 +#define AB8500_CODEC_CR58_DSPAD_EV 2 +#define AB8500_CODEC_CR58_DSPDA_EV 1 +#define AB8500_CODEC_CR58_STFIR_EV 0 + +/* CR59-CR0x003B */ +#define AB8500_CODEC_CR59_VSSREADY_MSK 7 +#define AB8500_CODEC_CR59_SHRTVIBL_MSK 6 +#define AB8500_CODEC_CR59_SHRTVIBR_MSK 5 +#define AB8500_CODEC_CR59_SHRTHFL_MSK 4 +#define AB8500_CODEC_CR59_SHRTHFR_MSK 3 +#define AB8500_CODEC_CR59_SHRTHSL_MSK 2 +#define AB8500_CODEC_CR59_SHRTHSR_MSK 1 +#define AB8500_CODEC_CR59_SHRTEAR_MSK 0 + +/* CR60-CR0x003C */ +#define AB8500_CODEC_CR60_VSSREADY_EV 7 +#define AB8500_CODEC_CR60_SHRTVIBL_EV 6 +#define AB8500_CODEC_CR60_SHRTVIBR_EV 5 +#define AB8500_CODEC_CR60_SHRTHFL_EV 4 +#define AB8500_CODEC_CR60_SHRTHFR_EV 3 +#define AB8500_CODEC_CR60_SHRTHSL_EV 2 +#define AB8500_CODEC_CR60_SHRTHSR_EV 1 +#define AB8500_CODEC_CR60_SHRTEAR_EV 0 + +/* CR61-CR0x003D */ +#define AB8500_CODEC_CR61_REVISION 2 +#define AB8500_CODEC_CR61_FADE_SPEED 0 + +/* CR62-CR0x003E */ +#define AB8500_CODEC_CR62_DMIC1SINC3 5 +#define AB8500_CODEC_CR62_DMIC2SINC3 4 +#define AB8500_CODEC_CR62_DMIC3SINC3 3 +#define AB8500_CODEC_CR62_DMIC4SINC3 2 +#define AB8500_CODEC_CR62_DMIC5SINC3 1 +#define AB8500_CODEC_CR62_DMIC6SINC3 0 + +/* CR63-CR0x003F */ +#define AB8500_CODEC_CR63_DATOHSLEN 7 +#define AB8500_CODEC_CR63_DATOHSREN 6 +#define AB8500_CODEC_CR63_AD1SEL 5 +#define AB8500_CODEC_CR63_AD2SEL 4 +#define AB8500_CODEC_CR63_AD3SEL 3 +#define AB8500_CODEC_CR63_AD5SEL 2 +#define AB8500_CODEC_CR63_AD6SEL 1 +#define AB8500_CODEC_CR63_ANCSEL 0 + +/* CR64-CR0x0040 */ +#define AB8500_CODEC_CR64_DATOHFREN 7 +#define AB8500_CODEC_CR64_DATOHFLEN 6 +#define AB8500_CODEC_CR64_HFRSEL 5 +#define AB8500_CODEC_CR64_HFLSEL 4 +#define AB8500_CODEC_CR64_STFIR1SEL 2 +#define AB8500_CODEC_CR64_STFIR2SEL 0 + +/* CR65-CR0x0041 */ +#define AB8500_CODEC_CR65_FADEDIS_AD1 6 +#define AB8500_CODEC_CR65_AD1GAIN 0 + +/* CR66-CR0x0042 */ +#define AB8500_CODEC_CR66_FADEDIS_AD2 6 +#define AB8500_CODEC_CR66_AD2GAIN 0 + +/* CR67-CR0x0043 */ +#define AB8500_CODEC_CR67_FADEDIS_AD3 6 +#define AB8500_CODEC_CR67_AD3GAIN 0 + +/* CR68-CR0x0044 */ +#define AB8500_CODEC_CR68_FADEDIS_AD4 6 +#define AB8500_CODEC_CR68_AD4GAIN 0 + +/* CR69-CR0x0045 */ +#define AB8500_CODEC_CR69_FADEDIS_AD5 6 +#define AB8500_CODEC_CR69_AD5GAIN 0 + +/* CR70-CR0x0046 */ +#define AB8500_CODEC_CR70_FADEDIS_AD6 6 +#define AB8500_CODEC_CR70_AD6GAIN 0 + +/* CR71-CR0x0047 */ +#define AB8500_CODEC_CR71_FADEDIS_DA1 6 +#define AB8500_CODEC_CR71_DA1GAIN 0 + +/* CR72-CR0x0048 */ +#define AB8500_CODEC_CR72_FADEDIS_DA2 6 +#define AB8500_CODEC_CR72_DA2GAIN 0 + +/* CR73-CR0x0049 */ +#define AB8500_CODEC_CR73_FADEDIS_DA3 6 +#define AB8500_CODEC_CR73_DA3GAIN 0 + +/* CR74-CR0x004A */ +#define AB8500_CODEC_CR74_FADEDIS_DA4 6 +#define AB8500_CODEC_CR74_DA4GAIN 0 + +/* CR75-CR0x004B */ +#define AB8500_CODEC_CR75_FADEDIS_DA5 6 +#define AB8500_CODEC_CR75_DA5GAIN 0 + +/* CR76-CR0x004C */ +#define AB8500_CODEC_CR76_FADEDIS_DA6 6 +#define AB8500_CODEC_CR76_DA6GAIN 0 + +/* CR77-CR0x004D */ +#define AB8500_CODEC_CR77_FADEDIS_AD1L 6 +#define AB8500_CODEC_CR77_AD1LBGAIN 0 + +/* CR78-CR0x004E */ +#define AB8500_CODEC_CR78_FADEDIS_AD2L 6 +#define AB8500_CODEC_CR78_AD2LBGAIN 0 + +/* CR79-CR0x004F */ +#define AB8500_CODEC_CR79_HSSINC1 7 +#define AB8500_CODEC_CR79_FADEDIS_HSL 4 +#define AB8500_CODEC_CR79_HSLDGAIN 0 + +/* CR80-CR0x0050 */ +#define AB8500_CODEC_CR80_FADEDIS_HSR 4 +#define AB8500_CODEC_CR80_HSRDGAIN 0 + +/* CR81-CR0x0051 */ +#define AB8500_CODEC_CR81_STFIR1GAIN 0 + +/* CR82-CR0x0052 */ +#define AB8500_CODEC_CR82_STFIR2GAIN 0 + +/* CR83-CR0x0053 */ +#define AB8500_CODEC_CR83_ENANC 2 +#define AB8500_CODEC_CR83_ANCIIRINIT 1 +#define AB8500_CODEC_CR83_ANCFIRUPDATE 0 + +/* CR84-CR0x0054 */ +#define AB8500_CODEC_CR84_ANCINSHIFT 0 + +/* CR85-CR0x0055 */ +#define AB8500_CODEC_CR85_ANCFIROUTSHIFT 0 + +/* CR86-CR0x0056 */ +#define AB8500_CODEC_CR86_ANCSHIFTOUT 0 + +/* CR87-CR0x0057 */ +#define AB8500_CODEC_CR87_ANCFIRCOEFF_MSB 0 + +/* CR88-CR0x0058 */ +#define AB8500_CODEC_CR88_ANCFIRCOEFF_LSB 0 + +/* CR89-CR0x0059 */ +#define AB8500_CODEC_CR89_ANCIIRCOEFF_MSB 0 + +/* CR90-CR0x005A */ +#define AB8500_CODEC_CR90_ANCIIRCOEFF_LSB 0 + +/* CR91-CR0x005B */ +#define AB8500_CODEC_CR91_ANCWARPDEL_MSB 0 + +/* CR92-CR0x005C */ +#define AB8500_CODEC_CR92_ANCWARPDEL_LSB 0 + +/* CR93-CR0x005D */ +#define AB8500_CODEC_CR93_ANCFIRPEAK_MSB 0 + +/* CR94-CR0x005E */ +#define AB8500_CODEC_CR94_ANCFIRPEAK_LSB 0 + +/* CR95-CR0x005F */ +#define AB8500_CODEC_CR95_ANCIIRPEAK_MSB 0 + +/* CR96-CR0x0060 */ +#define AB8500_CODEC_CR96_ANCIIRPEAK_LSB 0 + +/* CR97-CR0x0061 */ +#define AB8500_CODEC_CR97_STFIR_SET 7 +#define AB8500_CODEC_CR97_STFIR_ADDR 0 + +/* CR98-CR0x0062 */ +#define AB8500_CODEC_CR98_STFIR_COEFF_MSB 0 + +/* CR99-CR0x0063 */ +#define AB8500_CODEC_CR99_STFIR_COEFF_LSB 0 + +/* CR100-CR0x0064 */ +#define AB8500_CODEC_CR100_ENSTFIRS 2 +#define AB8500_CODEC_CR100_STFIRSTOIF1 1 +#define AB8500_CODEC_CR100_STFIR_BUSY 0 + +/* CR101-CR0x0065 */ +#define AB8500_CODEC_CR101_PARLHF 7 +#define AB8500_CODEC_CR101_PARLVIB 6 +#define AB8500_CODEC_CR101_CLASSDVIBLSWAPEN 3 +#define AB8500_CODEC_CR101_CLASSDVIBRSWAPEN 2 +#define AB8500_CODEC_CR101_CLASSDHFLSWAPEN 1 +#define AB8500_CODEC_CR101_CLASSDHFRSWAPEN 0 + +/* CR102-CR0x0066 */ +#define AB8500_CODEC_CR102_CLASSD_FIRBYP 4 +#define AB8500_CODEC_CR102_CLASSD_HIGHVOLEN 0 + +/* CR103-CR0x0067 */ +#define AB8500_CODEC_CR103_CLASSD_DITHERHPGAIN 4 +#define AB8500_CODEC_CR103_CLASSD_DITHERWGAIN 0 + +/* CR104-CR0x0068 */ +#define AB8500_CODEC_CR104_BFIFOINT 0 + +/* CR105-CR0x0069 */ +#define AB8500_CODEC_CR105_BFIFOTX 0 + +/* CR106-CR0x006A */ +#define AB8500_CODEC_CR106_BFIFOFSEXT 4 +#define AB8500_CODEC_CR106_BFIFOMSK 2 +#define AB8500_CODEC_CR106_BFIFOMSTR 1 +#define AB8500_CODEC_CR106_BFIFOSTRT 0 + +/* CR107-CR0x006B */ +#define AB8500_CODEC_CR107_BFIFOSAMPNR 0 + +/* CR108-CR0x006C */ +#define AB8500_CODEC_CR108_BFIFOWAKEUP 0 + +/* CR109-CR0x006D */ +#define AB8500_CODEC_CR109_BFIFOSAMPLES 0 + +/* For SetVolume API*/ +#define AB8500_CODEC_MAX_VOLUME 100 + +/* Analog MIC1 & MIC2 */ +#define AB8500_CODEC_MIC_VOLUME_MAX 31 +#define AB8500_CODEC_MIC_VOLUME_MEDIUM 15 +#define AB8500_CODEC_MIC_VOLUME_MIN 0 + +/* Line-in */ +#define AB8500_CODEC_LINEIN_VOLUME_MAX 31 +#define AB8500_CODEC_LINEIN_VOLUME_MEDIUM 15 +#define AB8500_CODEC_LINEIN_VOLUME_MIN 0 + +/* HeadSet */ +#define AB8500_CODEC_HEADSET_VOLUME_MAX 0 +#define AB8500_CODEC_HEADSET_VOLUME_MEDIUM 3 +#define AB8500_CODEC_HEADSET_VOLUME_MIN 7 + +/* HeadSet Digital */ +#define AB8500_CODEC_HEADSET_D_VOLUME_MAX 0 +#define AB8500_CODEC_HEADSET_D_VOLUME_MEDIUM 7 +#define AB8500_CODEC_HEADSET_D_VOLUME_MIN 15 +#define AB8500_CODEC_HEADSET_D_VOLUME_0DB 8 + +/* Digital AD Path */ +#define AB8500_CODEC_AD_D_VOLUME_MAX 0 +#define AB8500_CODEC_AD_D_VOLUME_MEDIUM 31 +#define AB8500_CODEC_AD_D_VOLUME_MIN 63 + +/* Digital DA Path */ +#define AB8500_CODEC_DA_D_VOLUME_MAX 0 +#define AB8500_CODEC_DA_D_VOLUME_MEDIUM 31 +#define AB8500_CODEC_DA_D_VOLUME_MIN 63 + +/* EarPiece Digital */ +#define AB8500_CODEC_EARPIECE_D_VOLUME_MAX 0 +#define AB8500_CODEC_EARPIECE_D_VOLUME_MEDIUM 7 +#define AB8500_CODEC_EARPIECE_D_VOLUME_MIN 15 + +/* AD1 loopback to HFL & HFR Digital */ +#define AB8500_CODEC_AD_LB_TO_HF_L_R_VOLUME_MAX 0 +#define AB8500_CODEC_AD_LB_TO_HF_L_R_VOLUME_MEDIUM 31 +#define AB8500_CODEC_AD_LB_TO_HF_L_R_VOLUME_MIN 63 + +/* Line-in to HSL & HSR */ +#define AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MAX 0 +#define AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MEDIUM 9 +#define AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN 18 +#define AB8500_CODEC_LINEIN_TO_HS_L_R_LOOP_OPEN 19 + +/* Vibrator */ +#define AB8500_CODEC_VIBRATOR_VOLUME_MAX 100 +#define AB8500_CODEC_VIBRATOR_VOLUME_MEDIUM 50 +#define AB8500_CODEC_VIBRATOR_VOLUME_MIN 0 + +/* CR0 - 7 */ +typedef enum { + AB8500_CODEC_CR0_POWERUP_OFF, + AB8500_CODEC_CR0_POWERUP_ON +} t_ab8500_codec_cr0_powerup; + +/* CR0 - 3 */ +typedef enum { + AB8500_CODEC_CR0_ENAANA_OFF, + AB8500_CODEC_CR0_ENAANA_ON +} t_ab8500_codec_cr0_enaana; + +/* CR1 - 7 */ +typedef enum { + AB8500_CODEC_CR1_SWRESET_DISABLED, + AB8500_CODEC_CR1_SWRESET_ENABLED +} t_ab8500_codec_cr1_swreset; + +/* CR2 - 7 */ +typedef enum { + AB8500_CODEC_CR2_ENAD1_DISABLED, + AB8500_CODEC_CR2_ENAD1_ENABLED +} t_ab8500_codec_cr2_enad1; + +/* CR2 - 6 */ +typedef enum { + AB8500_CODEC_CR2_ENAD2_DISABLED, + AB8500_CODEC_CR2_ENAD2_ENABLED +} t_ab8500_codec_cr2_enad2; + +/* CR2 - 5 */ +typedef enum { + AB8500_CODEC_CR2_ENAD3_DISABLED, + AB8500_CODEC_CR2_ENAD3_ENABLED +} t_ab8500_codec_cr2_enad3; + +/* CR2 - 4 */ +typedef enum { + AB8500_CODEC_CR2_ENAD4_DISABLED, + AB8500_CODEC_CR2_ENAD4_ENABLED +} t_ab8500_codec_cr2_enad4; + +/* CR2 - 3 */ +typedef enum { + AB8500_CODEC_CR2_ENAD5_DISABLED, + AB8500_CODEC_CR2_ENAD5_ENABLED +} t_ab8500_codec_cr2_enad5; + +/* CR2 - 2 */ +typedef enum { + AB8500_CODEC_CR2_ENAD6_DISABLED, + AB8500_CODEC_CR2_ENAD6_ENABLED +} t_ab8500_codec_cr2_enad6; + +/* CR3 - 7 */ +typedef enum { + AB8500_CODEC_CR3_ENDA1_DISABLED, + AB8500_CODEC_CR3_ENDA1_ENABLED +} t_ab8500_codec_cr3_enda1; + +/* CR3 - 6 */ +typedef enum { + AB8500_CODEC_CR3_ENDA2_DISABLED, + AB8500_CODEC_CR3_ENDA2_ENABLED +} t_ab8500_codec_cr3_enda2; + +/* CR3 - 5 */ +typedef enum { + AB8500_CODEC_CR3_ENDA3_DISABLED, + AB8500_CODEC_CR3_ENDA3_ENABLED +} t_ab8500_codec_cr3_enda3; + +/* CR3 - 4 */ +typedef enum { + AB8500_CODEC_CR3_ENDA4_DISABLED, + AB8500_CODEC_CR3_ENDA4_ENABLED +} t_ab8500_codec_cr3_enda4; + +/* CR3 - 3 */ +typedef enum { + AB8500_CODEC_CR3_ENDA5_DISABLED, + AB8500_CODEC_CR3_ENDA5_ENABLED +} t_ab8500_codec_cr3_enda5; + +/* CR3 - 2 */ +typedef enum { + AB8500_CODEC_CR3_ENDA6_DISABLED, + AB8500_CODEC_CR3_ENDA6_ENABLED +} t_ab8500_codec_cr3_enda6; + +/* CR4 - 7 */ +typedef enum { + AB8500_CODEC_CR4_LOWPOWHS_NORMAL, + AB8500_CODEC_CR4_LOWPOWHS_LP +} t_ab8500_codec_cr4_lowpowhs; + +/* CR4 - 6:5 */ +typedef enum { + AB8500_CODEC_CR4_LOWPOWDACHS_NORMAL, + AB8500_CODEC_CR4_LOWPOWDACHS_DRIVERS_LP, + AB8500_CODEC_CR4_LOWPOWDACHS_LP, + AB8500_CODEC_CR4_LOWPOWDACHS_BOTH_LP +} t_ab8500_codec_cr4_lowpowdachs; + +/* CR4 - 4 */ +typedef enum { + AB8500_CODEC_CR4_LOWPOWEAR_NORMAL, + AB8500_CODEC_CR4_LOWPOWEAR_LP +} t_ab8500_codec_cr4_lowpowear; + +/* CR4 - 3:2 */ +typedef enum { + AB8500_CODEC_CR4_EAR_SEL_CM_0_95V, + AB8500_CODEC_CR4_EAR_SEL_CM_1_1V, + AB8500_CODEC_CR4_EAR_SEL_CM_1_27V, + AB8500_CODEC_CR4_EAR_SEL_CM_1_58V +} t_ab8500_codec_cr4_ear_sel_cm; + +/* CR4 - 1 */ +typedef enum { + AB8500_CODEC_CR4_HS_HP_DIS_FILTER_ENABLED, + AB8500_CODEC_CR4_HS_HP_DIS_FILTER_DISABLED +} t_ab8500_codec_cr4_hs_hp_dis; + +/* CR4 - 0 */ +typedef enum { + AB8500_CODEC_CR4_EAR_HP_DIS_FILTER_ENABLED, + AB8500_CODEC_CR4_EAR_HP_DIS_FILTER_DISABLED +} t_ab8500_codec_cr4_ear_hp_dis; + +/* CR5 - 7 */ +typedef enum { + AB8500_CODEC_CR5_ENMIC1_DISABLED, + AB8500_CODEC_CR5_ENMIC1_ENABLED +} t_ab8500_codec_cr5_enmic1; + +/* CR5 - 6 */ +typedef enum { + AB8500_CODEC_CR5_ENMIC2_DISABLED, + AB8500_CODEC_CR5_ENMIC2_ENABLED +} t_ab8500_codec_cr5_enmic2; + +/* CR5 - 5 */ +typedef enum { + AB8500_CODEC_CR5_ENLINL_DISABLED, + AB8500_CODEC_CR5_ENLINL_ENABLED +} t_ab8500_codec_cr5_enlinl; + +/* CR5 - 4 */ +typedef enum { + AB8500_CODEC_CR5_ENLINR_DISABLED, + AB8500_CODEC_CR5_ENLINR_ENABLED +} t_ab8500_codec_cr5_enlinr; + +/* CR5 - 3 */ +typedef enum { + AB8500_CODEC_CR5_MUTMIC1_DISABLED, + AB8500_CODEC_CR5_MUTMIC1_ENABLED +} t_ab8500_codec_cr5_mutmic1; + +/* CR5 - 2 */ +typedef enum { + AB8500_CODEC_CR5_MUTMIC2_DISABLED, + AB8500_CODEC_CR5_MUTMIC2_ENABLED +} t_ab8500_codec_cr5_mutmic2; + +/* CR5 - 1 */ +typedef enum { + AB8500_CODEC_CR5_MUTLINL_DISABLED, + AB8500_CODEC_CR5_MUTLINL_ENABLED +} t_ab8500_codec_cr5_mutlinl; + +/* CR5 - 0 */ +typedef enum { + AB8500_CODEC_CR5_MUTLINR_DISABLED, + AB8500_CODEC_CR5_MUTLINR_ENABLED +} t_ab8500_codec_cr5_mutlinr; + +/* CR6 - 7 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC1_DISABLED, + AB8500_CODEC_CR6_ENDMIC1_ENABLED +} t_ab8500_codec_cr6_endmic1; + +/* CR6 - 6 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC2_DISABLED, + AB8500_CODEC_CR6_ENDMIC2_ENABLED +} t_ab8500_codec_cr6_endmic2; + +/* CR6 - 5 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC3_DISABLED, + AB8500_CODEC_CR6_ENDMIC3_ENABLED +} t_ab8500_codec_cr6_endmic3; + +/* CR6 - 4 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC4_DISABLED, + AB8500_CODEC_CR6_ENDMIC4_ENABLED +} t_ab8500_codec_cr6_endmic4; + +/* CR6 - 3 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC5_DISABLED, + AB8500_CODEC_CR6_ENDMIC5_ENABLED +} t_ab8500_codec_cr6_endmic5; + +/* CR6 - 2 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC6_DISABLED, + AB8500_CODEC_CR6_ENDMIC6_ENABLED +} t_ab8500_codec_cr6_endmic6; + +/* CR7 - 7 */ +typedef enum { + AB8500_CODEC_CR7_MIC1SEL_MIC1A, + AB8500_CODEC_CR7_MIC1SEL_MIC1B +} t_ab8500_codec_cr7_mic1sel; + +/* CR7 - 6 */ +typedef enum { + AB8500_CODEC_CR7_LINRSEL_MIC2, + AB8500_CODEC_CR7_LINRSEL_LINR +} t_ab8500_codec_cr7_linrsel; + +/* CR7 - 5 */ +typedef enum { + AB8500_CODEC_CR7_ENDRVHSL_DISABLED, + AB8500_CODEC_CR7_ENDRVHSL_ENABLED +} t_ab8500_codec_cr7_endrvhsl; + +/* CR7 - 4 */ +typedef enum { + AB8500_CODEC_CR7_ENDRVHSR_DISABLED, + AB8500_CODEC_CR7_ENDRVHSR_ENABLED +} t_ab8500_codec_cr7_endrvhsr; + +/* CR7 - 2 */ +typedef enum { + AB8500_CODEC_CR7_ENADCMIC_DISABLED, + AB8500_CODEC_CR7_ENADCMIC_ENABLED +} t_ab8500_codec_cr7_enadcmic; + +/* CR7 - 1 */ +typedef enum { + AB8500_CODEC_CR7_ENADCLINL_DISABLED, + AB8500_CODEC_CR7_ENADCLINL_ENABLED +} t_ab8500_codec_cr7_enadclinl; + +/* CR7 - 0 */ +typedef enum { + AB8500_CODEC_CR7_ENADCLINR_DISABLED, + AB8500_CODEC_CR7_ENADCLINR_ENABLED +} t_ab8500_codec_cr7_enadclinr; + +/* CR8 - 7 */ +typedef enum { + AB8500_CODEC_CR8_CP_DIS_PLDWN_ENABLED, + AB8500_CODEC_CR8_CP_DIS_PLDWN_DISABLED +} t_ab8500_codec_cr8_cp_dis_pldwn; + +/* CR8 - 6 */ +typedef enum { + AB8500_CODEC_CR8_ENEAR_DISABLED, + AB8500_CODEC_CR8_ENEAR_ENABLED +} t_ab8500_codec_cr8_enear; + +/* CR8 - 5 */ +typedef enum { + AB8500_CODEC_CR8_ENHSL_DISABLED, + AB8500_CODEC_CR8_ENHSL_ENABLED +} t_ab8500_codec_cr8_enhsl; + +/* CR8 - 4 */ +typedef enum { + AB8500_CODEC_CR8_ENHSR_DISABLED, + AB8500_CODEC_CR8_ENHSR_ENABLED +} t_ab8500_codec_cr8_enhsr; + +/* CR8 - 3 */ +typedef enum { + AB8500_CODEC_CR8_ENHFL_DISABLED, + AB8500_CODEC_CR8_ENHFL_ENABLED +} t_ab8500_codec_cr8_enhfl; + +/* CR8 - 2 */ +typedef enum { + AB8500_CODEC_CR8_ENHFR_DISABLED, + AB8500_CODEC_CR8_ENHFR_ENABLED +} t_ab8500_codec_cr8_enhfr; + +/* CR8 - 1 */ +typedef enum { + AB8500_CODEC_CR8_ENVIBL_DISABLED, + AB8500_CODEC_CR8_ENVIBL_ENABLED +} t_ab8500_codec_cr8_envibl; + +/* CR8 - 0 */ +typedef enum { + AB8500_CODEC_CR8_ENVIBR_DISABLED, + AB8500_CODEC_CR8_ENVIBR_ENABLED +} t_ab8500_codec_cr8_envibr; + +/* CR9 - 6 */ +typedef enum { + AB8500_CODEC_CR9_ENDACEAR_DISABLED, + AB8500_CODEC_CR9_ENDACEAR_ENABLED +} t_ab8500_codec_cr9_endacear; + +/* CR9 - 5 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHSL_DISABLED, + AB8500_CODEC_CR9_ENDACHSL_ENABLED +} t_ab8500_codec_cr9_endachsl; + +/* CR9 - 4 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHSR_DISABLED, + AB8500_CODEC_CR9_ENDACHSR_ENABLED +} t_ab8500_codec_cr9_endachsr; + +/* CR9 - 3 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHFL_DISABLED, + AB8500_CODEC_CR9_ENDACHFL_ENABLED +} t_ab8500_codec_cr9_endachfl; + +/* CR9 - 2 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHFR_DISABLED, + AB8500_CODEC_CR9_ENDACHFR_ENABLED +} t_ab8500_codec_cr9_endachfr; + +/* CR9 - 1 */ +typedef enum { + AB8500_CODEC_CR9_ENDACVIBL_DISABLED, + AB8500_CODEC_CR9_ENDACVIBL_ENABLED +} t_ab8500_codec_cr9_endacvibl; + +/* CR9 - 0 */ +typedef enum { + AB8500_CODEC_CR9_ENDACVIBR_DISABLED, + AB8500_CODEC_CR9_ENDACVIBR_ENABLED +} t_ab8500_codec_cr9_endacvibr; + +/* CR10 - 6 */ +typedef enum { + AB8500_CODEC_CR10_MUTEEAR_DISABLED, + AB8500_CODEC_CR10_MUTEEAR_ENABLED +} t_ab8500_codec_cr10_muteear; + +/* CR10 - 5 */ +typedef enum { + AB8500_CODEC_CR10_MUTEHSL_DISABLED, + AB8500_CODEC_CR10_MUTEHSL_ENABLED +} t_ab8500_codec_cr10_mutehsl; + +/* CR10 - 4 */ +typedef enum { + AB8500_CODEC_CR10_MUTEHSR_DISABLED, + AB8500_CODEC_CR10_MUTEHSR_ENABLED +} t_ab8500_codec_cr10_mutehsr; + +/* CR10 - 3 */ +typedef enum { + AB8500_CODEC_CR10_MUTEHFL_DISABLED, + AB8500_CODEC_CR10_MUTEHFL_ENABLED +} t_ab8500_codec_cr10_mutehfl; + +/* CR10 - 2 */ +typedef enum { + AB8500_CODEC_CR10_MUTEHFR_DISABLED, + AB8500_CODEC_CR10_MUTEHFR_ENABLED +} t_ab8500_codec_cr10_mutehfr; + +/* CR10 - 1 */ +typedef enum { + AB8500_CODEC_CR10_MUTEVIBL_DISABLED, + AB8500_CODEC_CR10_MUTEVIBL_ENABLED +} t_ab8500_codec_cr10_mutevibl; + +/* CR10 - 0 */ +typedef enum { + AB8500_CODEC_CR10_MUTEVIBR_DISABLED, + AB8500_CODEC_CR10_MUTEVIBR_ENABLED +} t_ab8500_codec_cr10_mutevibr; + +/* CR11 - 7 */ +typedef enum { + AB8500_CODEC_CR11_EARSHORTPWD_DISABLED, + AB8500_CODEC_CR11_EARSHORTPWD_ENABLED +} t_ab8500_codec_cr11_earshortpwd; + +/* CR11 - 6 */ +typedef enum { + AB8500_CODEC_CR11_EARSHORTDIS_ENABLED, + AB8500_CODEC_CR11_EARSHORTDIS_DISABLED +} t_ab8500_codec_cr11_earshortdis; + +/* CR11 - 5 */ +typedef enum { + AB8500_CODEC_CR11_HSLSHORTDIS_ENABLED, + AB8500_CODEC_CR11_HSLSHORTDIS_DISABLED +} t_ab8500_codec_cr11_hslshortdis; + +/* CR11 - 4 */ +typedef enum { + AB8500_CODEC_CR11_HSRSHORTDIS_ENABLED, + AB8500_CODEC_CR11_HSRSHORTDIS_DISABLED +} t_ab8500_codec_cr11_hsrshortdis; + +/* CR11 - 3 */ +typedef enum { + AB8500_CODEC_CR11_HFLSHORTDIS_ENABLED, + AB8500_CODEC_CR11_HFLSHORTDIS_DISABLED +} t_ab8500_codec_cr11_hflshortdis; + +/* CR11 - 2 */ +typedef enum { + AB8500_CODEC_CR11_HFRSHORTDIS_ENABLED, + AB8500_CODEC_CR11_HFRSHORTDIS_DISABLED +} t_ab8500_codec_cr11_hfrshortdis; + +/* CR11 - 1 */ +typedef enum { + AB8500_CODEC_CR11_VIBLSHORTDIS_ENABLED, + AB8500_CODEC_CR11_VIBLSHORTDIS_DISABLED +} t_ab8500_codec_cr11_viblshortdis; + +/* CR11 - 0 */ +typedef enum { + AB8500_CODEC_CR11_VIBRSHORTDIS_ENABLED, + AB8500_CODEC_CR11_VIBRSHORTDIS_DISABLED +} t_ab8500_codec_cr11_vibrshortdis; + +/* CR12 - 7 */ +typedef enum { + AB8500_CODEC_CR12_ENCPHS_DISABLED, + AB8500_CODEC_CR12_ENCPHS_ENABLED +} t_ab8500_codec_cr12_encphs; + +/* CR12 - 6:4 */ +typedef enum { + AB8500_CODEC_CR12_HSAUTOTIME_6_6USEC, + AB8500_CODEC_CR12_HSAUTOTIME_13_3USEC, + AB8500_CODEC_CR12_HSAUTOTIME_26_6USEC, + AB8500_CODEC_CR12_HSAUTOTIME_53_2USEC, + AB8500_CODEC_CR12_HSAUTOTIME_106_4USEC, + AB8500_CODEC_CR12_HSAUTOTIME_212_8USEC, + AB8500_CODEC_CR12_HSAUTOTIME_425_6USEC, + AB8500_CODEC_CR12_HSAUTOTIME_851_2USEC, +} t_ab8500_codec_cr12_hsautotime; + +/* CR12 - 1 */ +typedef enum { + AB8500_CODEC_CR12_HSAUTOENSEL_DISABLED, + AB8500_CODEC_CR12_HSAUTOENSEL_ENABLED +} t_ab8500_codec_cr12_hsautoensel; + +/* CR12 - 0 */ +typedef enum { + AB8500_CODEC_CR12_HSAUTOEN_DISABLED, + AB8500_CODEC_CR12_HSAUTOEN_ENABLED +} t_ab8500_codec_cr12_hsautoen; + +/* CR13 - 7:4 */ +typedef enum { + AB8500_CODEC_CR13_ENVDET_HTHRESH_25, + AB8500_CODEC_CR13_ENVDET_HTHRESH_50, + AB8500_CODEC_CR13_ENVDET_HTHRESH_100, + AB8500_CODEC_CR13_ENVDET_HTHRESH_150, + AB8500_CODEC_CR13_ENVDET_HTHRESH_200, + AB8500_CODEC_CR13_ENVDET_HTHRESH_250, + AB8500_CODEC_CR13_ENVDET_HTHRESH_300, + AB8500_CODEC_CR13_ENVDET_HTHRESH_350, + AB8500_CODEC_CR13_ENVDET_HTHRESH_400, + AB8500_CODEC_CR13_ENVDET_HTHRESH_450, + AB8500_CODEC_CR13_ENVDET_HTHRESH_500, + AB8500_CODEC_CR13_ENVDET_HTHRESH_550, + AB8500_CODEC_CR13_ENVDET_HTHRESH_600, + AB8500_CODEC_CR13_ENVDET_HTHRESH_650, + AB8500_CODEC_CR13_ENVDET_HTHRESH_700, + AB8500_CODEC_CR13_ENVDET_HTHRESH_750 +} t_ab8500_codec_cr13_envdet_hthresh; + +/* CR13 - 3:0 */ +typedef enum { + AB8500_CODEC_CR13_ENVDET_LTHRESH_25, + AB8500_CODEC_CR13_ENVDET_LTHRESH_50, + AB8500_CODEC_CR13_ENVDET_LTHRESH_100, + AB8500_CODEC_CR13_ENVDET_LTHRESH_150, + AB8500_CODEC_CR13_ENVDET_LTHRESH_200, + AB8500_CODEC_CR13_ENVDET_LTHRESH_250, + AB8500_CODEC_CR13_ENVDET_LTHRESH_300, + AB8500_CODEC_CR13_ENVDET_LTHRESH_350, + AB8500_CODEC_CR13_ENVDET_LTHRESH_400, + AB8500_CODEC_CR13_ENVDET_LTHRESH_450, + AB8500_CODEC_CR13_ENVDET_LTHRESH_500, + AB8500_CODEC_CR13_ENVDET_LTHRESH_550, + AB8500_CODEC_CR13_ENVDET_LTHRESH_600, + AB8500_CODEC_CR13_ENVDET_LTHRESH_650, + AB8500_CODEC_CR13_ENVDET_LTHRESH_700, + AB8500_CODEC_CR13_ENVDET_LTHRESH_750 +} t_ab8500_codec_cr13_envdet_lthresh; + +/* CR14 - 7 */ +typedef enum { + AB8500_CODEC_CR14_SMPSLVEN_HIGHVOLTAGE, + AB8500_CODEC_CR14_SMPSLVEN_LOWVOLTAGE +} t_ab8500_codec_cr14_smpslven; + +/* CR14 - 6 */ +typedef enum { + AB8500_CODEC_CR14_ENVDETSMPSEN_DISABLED, + AB8500_CODEC_CR14_ENVDETSMPSEN_ENABLED +} t_ab8500_codec_cr14_envdetsmpsen; + +/* CR14 - 5 */ +typedef enum { + AB8500_CODEC_CR14_CPLVEN_HIGHVOLTAGE, + AB8500_CODEC_CR14_CPLVEN_LOWVOLTAGE +} t_ab8500_codec_cr14_cplven; + +/* CR14 - 4 */ +typedef enum { + AB8500_CODEC_CR14_ENVDETCPEN_DISABLED, + AB8500_CODEC_CR14_ENVDETCPEN_ENABLED +} t_ab8500_codec_cr14_envdetcpen; + +/* CR14 - 3:0 */ +typedef enum { + AB8500_CODEC_CR14_ENVET_TIME_27USEC, + AB8500_CODEC_CR14_ENVET_TIME_53USEC, + AB8500_CODEC_CR14_ENVET_TIME_106USEC, + AB8500_CODEC_CR14_ENVET_TIME_212USEC, + AB8500_CODEC_CR14_ENVET_TIME_424USEC, + AB8500_CODEC_CR14_ENVET_TIME_848USEC, + AB8500_CODEC_CR14_ENVET_TIME_1MSEC, + AB8500_CODEC_CR14_ENVET_TIME_3MSEC, + AB8500_CODEC_CR14_ENVET_TIME_6MSEC, + AB8500_CODEC_CR14_ENVET_TIME_13MSEC, + AB8500_CODEC_CR14_ENVET_TIME_27MSEC, + AB8500_CODEC_CR14_ENVET_TIME_54MSEC, + AB8500_CODEC_CR14_ENVET_TIME_109MSEC, + AB8500_CODEC_CR14_ENVET_TIME_218MSEC, + AB8500_CODEC_CR14_ENVET_TIME_436MSEC, + AB8500_CODEC_CR14_ENVET_TIME_872MSEC, +} t_ab8500_codec_cr14_envet_time; + +/* CR15 - 7 */ +typedef enum { + AB8500_CODEC_CR15_PWMTOVIBL_DA_PATH, + AB8500_CODEC_CR15_PWMTOVIBL_PWM +} t_ab8500_codec_cr15_pwmtovibl; + +/* CR15 - 6 */ +typedef enum { + AB8500_CODEC_CR15_PWMTOVIBR_DA_PATH, + AB8500_CODEC_CR15_PWMTOVIBR_PWM +} t_ab8500_codec_cr15_pwmtovibr; + +/* CR15 - 5 */ +typedef enum { + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLGPOL, + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE +} t_ab8500_codec_cr15_pwmlctrl; + +/* CR15 - 4 */ +typedef enum { + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRGPOL, + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE +} t_ab8500_codec_cr15_pwmrctrl; + +/* CR15 - 3 */ +typedef enum { + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLGPOL, + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE +} t_ab8500_codec_cr15_pwmnlctrl; + +/* CR15 - 2 */ +typedef enum { + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLGPOL, + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE +} t_ab8500_codec_cr15_pwmplctrl; + +/* CR15 - 1 */ +typedef enum { + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRGPOL, + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE +} t_ab8500_codec_cr15_pwmnrctrl; + +/* CR15 - 0 */ +typedef enum { + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRGPOL, + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE +} t_ab8500_codec_cr15_pwmprctrl; + +/* CR16 - 7 */ +typedef enum { + AB8500_CODEC_CR16_PWMNLPOL_GNDVIB, + AB8500_CODEC_CR16_PWMNLPOL_VINVIB +} t_ab8500_codec_cr16_pwmnlpol; + +/* CR16 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr16_pwmnldutycycle; + +/* CR17 - 7 */ +typedef enum { + AB8500_CODEC_CR17_PWMPLPOL_GNDVIB, + AB8500_CODEC_CR17_PWMPLPOL_VINVIB +} t_ab8500_codec_cr17_pwmplpol; + +/* CR17 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr17_pwmpldutycycle; + +/* CR18 - 7 */ +typedef enum { + AB8500_CODEC_CR18_PWMNRPOL_GNDVIB, + AB8500_CODEC_CR18_PWMNRPOL_VINVIB +} t_ab8500_codec_cr18_pwmnrpol; + +/* CR18 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr18_pwmnrdutycycle; + +/* CR19 - 7 */ +typedef enum { + AB8500_CODEC_CR19_PWMPRPOL_GNDVIB, + AB8500_CODEC_CR19_PWMPRPOL_VINVIB +} t_ab8500_codec_cr19_pwmprpol; + +/* CR19 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr19_pwmprdutycycle; + +/* CR20 - 7 */ +typedef enum { + AB8500_CODEC_CR20_EN_SE_MIC1_DIFFERENTIAL, + AB8500_CODEC_CR20_EN_SE_MIC1_SINGLE +} t_ab8500_codec_cr20_en_se_mic1; + +/* CR20 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr20_mic1_gain; + +/* CR21 - 7 */ +typedef enum { + AB8500_CODEC_CR21_EN_SE_MIC2_DIFFERENTIAL, + AB8500_CODEC_CR21_EN_SE_MIC2_SINGLE +} t_ab8500_codec_cr21_en_se_mic2; + +/* CR21 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr21_mic2_gain; + +/* CR22 - 7:5 */ +typedef t_uint8 t_ab8500_codec_cr22_hsl_gain; + +/* CR22 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr22_linl_gain; + +/* CR23 - 7:5 */ +typedef t_uint8 t_ab8500_codec_cr23_hsr_gain; + +/* CR23 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr23_linr_gain; + +/* CR24 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr24_lintohsl_gain; + +/* CR25 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr25_lintohsr_gain; + +/* CR26 - 7 */ +typedef enum { + AB8500_CODEC_CR26_AD1NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD1NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad1nh; + +/* CR26 - 6 */ +typedef enum { + AB8500_CODEC_CR26_AD2NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD2NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad2nh; + +/* CR26 - 5 */ +typedef enum { + AB8500_CODEC_CR26_AD3NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD3NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad3nh; + +/* CR26 - 4 */ +typedef enum { + AB8500_CODEC_CR26_AD4NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD4NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad4nh; + +/* CR26 - 3 */ +typedef enum { + AB8500_CODEC_CR26_AD1_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD1_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad1_voice; + +/* CR26 - 2 */ +typedef enum { + AB8500_CODEC_CR26_AD2_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD2_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad2_voice; + +/* CR26 - 1 */ +typedef enum { + AB8500_CODEC_CR26_AD3_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD3_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad3_voice; + +/* CR26 - 0 */ +typedef enum { + AB8500_CODEC_CR26_AD4_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD4_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad4_voice; + +/* CR27 - 7 */ +typedef enum { + AB8500_CODEC_CR27_EN_MASTGEN_DISABLED, + AB8500_CODEC_CR27_EN_MASTGEN_ENABLED +} t_ab8500_codec_cr27_en_mastgen; + +/* CR27 - 6:5 */ +typedef enum { + AB8500_CODEC_CR27_IF1_BITCLK_OSR_32, + AB8500_CODEC_CR27_IF1_BITCLK_OSR_64, + AB8500_CODEC_CR27_IF1_BITCLK_OSR_128, + AB8500_CODEC_CR27_IF1_BITCLK_OSR_256 +} t_ab8500_codec_cr27_if1_bitclk_osr; + +/* CR27 - 4 */ +typedef enum { + AB8500_CODEC_CR27_ENFS_BITCLK1_DISABLED, + AB8500_CODEC_CR27_ENFS_BITCLK1_ENABLED +} t_ab8500_codec_cr27_enfs_bitclk1; + +/* CR27 - 2:1 */ +typedef enum { + AB8500_CODEC_CR27_IF0_BITCLK_OSR_32, + AB8500_CODEC_CR27_IF0_BITCLK_OSR_64, + AB8500_CODEC_CR27_IF0_BITCLK_OSR_128, + AB8500_CODEC_CR27_IF0_BITCLK_OSR_256 +} t_ab8500_codec_cr27_if0_bitclk_osr; + +/* CR27 - 0 */ +typedef enum { + AB8500_CODEC_CR27_ENFS_BITCLK0_DISABLED, + AB8500_CODEC_CR27_ENFS_BITCLK0_ENABLED +} t_ab8500_codec_cr27_enfs_bitclk0; + +/* CR28 - 6 */ +typedef enum { + AB8500_CODEC_CR28_FSYNC0P_RISING_EDGE, + AB8500_CODEC_CR28_FSYNC0P_FALLING_EDGE +} t_ab8500_codec_cr28_fsync0p; + +/* CR28 - 5 */ +typedef enum { + AB8500_CODEC_CR28_BITCLK0P_RISING_EDGE, + AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE +} t_ab8500_codec_cr28_bitclk0p; + +/* CR28 - 4 */ +typedef enum { + AB8500_CODEC_CR28_IF0DEL_NOT_DELAYED, + AB8500_CODEC_CR28_IF0DEL_DELAYED +} t_ab8500_codec_cr28_if0del; + +/* CR28 - 3:2 */ +typedef enum { + AB8500_CODEC_CR28_IF0FORMAT_DISABLED, + AB8500_CODEC_CR28_IF0FORMAT_TDM, + AB8500_CODEC_CR28_IF0FORMAT_I2S_LEFTALIGNED +} t_ab8500_codec_cr28_if0format; + +/* CR28 - 1:0 */ +typedef enum { + AB8500_CODEC_CR28_IF0WL_16BITS, + AB8500_CODEC_CR28_IF0WL_20BITS, + AB8500_CODEC_CR28_IF0WL_24BITS, + AB8500_CODEC_CR28_IF0WL_32BITS +} t_ab8500_codec_cr28_if0wl; + +/* CR29 - 7 */ +typedef enum { + AB8500_CODEC_CR29_IF0DATOIF1AD_NOTSENT, + AB8500_CODEC_CR29_IF0DATOIF1AD_SENT +} t_ab8500_codec_cr29_if0datoif1ad; + +/* CR29 - 6 */ +typedef enum { + AB8500_CODEC_CR29_IF0CKTOIF1CK_NOTSENT, + AB8500_CODEC_CR29_IF0CKTOIF1CK_SENT +} t_ab8500_codec_cr29_if0cktoif1ck; + +/* CR29 - 5 */ +typedef enum { + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_INPUT, + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_OUTPUT +} t_ab8500_codec_cr29_if1master; + +/* CR29 - 3 */ +typedef enum { + AB8500_CODEC_CR29_IF1DATOIF0AD_NOTSENT, + AB8500_CODEC_CR29_IF1DATOIF0AD_SENT +} t_ab8500_codec_cr29_if1datoif0ad; + +/* CR29 - 2 */ +typedef enum { + AB8500_CODEC_CR29_IF1CKTOIF0CK_NOTSENT, + AB8500_CODEC_CR29_IF1CKTOIF0CK_SENT +} t_ab8500_codec_cr29_if1cktoif0ck; + +/* CR29 - 1 */ +typedef enum { + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_INPUT, + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_OUTPUT +} t_ab8500_codec_cr29_if0master; + +/* CR29 - 0 */ +typedef enum { + AB8500_CODEC_CR29_IF0BFIFOEN_NORMAL_MODE, + AB8500_CODEC_CR29_IF0BFIFOEN_BURST_MODE +} t_ab8500_codec_cr29_if0bfifoen; + +/* CR30 - 6 */ +typedef enum { + AB8500_CODEC_CR30_FSYNC1P_RISING_EDGE, + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE +} t_ab8500_codec_cr30_fsync1p; + +/* CR30 - 5 */ +typedef enum { + AB8500_CODEC_CR30_BITCLK1P_RISING_EDGE, + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE +} t_ab8500_codec_cr30_bitclk1p; + +/* CR30 - 4 */ +typedef enum { + AB8500_CODEC_CR30_IF1DEL_NOT_DELAYED, + AB8500_CODEC_CR30_IF1DEL_DELAYED +} t_ab8500_codec_cr30_if1del; + +/* CR30 - 3:2 */ +typedef enum { + AB8500_CODEC_CR30_IF1FORMAT_DISABLED, + AB8500_CODEC_CR30_IF1FORMAT_TDM, + AB8500_CODEC_CR30_IF1FORMAT_I2S_LEFTALIGNED +} t_ab8500_codec_cr30_if1format; + +/* CR30 - 1:0 */ +typedef enum { + AB8500_CODEC_CR30_IF1WL_16BITS, + AB8500_CODEC_CR30_IF1WL_20BITS, + AB8500_CODEC_CR30_IF1WL_24BITS, + AB8500_CODEC_CR30_IF1WL_32BITS +} t_ab8500_codec_cr30_if1wl; + +/* CR31:46 - 7:4 or 3:0 */ +/* In ab8500_codec.h */ + +/* CR47:50 - 7/6/5/4/3/2/1/0 */ +typedef enum { + AB8500_CODEC_CR47_TO_CR50_HIZ_SL_LOW_IMPEDANCE, + AB8500_CODEC_CR47_TO_CR50_HIZ_SL_HIGH_IMPEDANCE, +} t_ab8500_codec_cr47_to_cr50_hiz_sl; + +/* CR51 - 7 */ +typedef enum { + AB8500_CODEC_CR51_DA12_VOICE_AUDIOFILTER, + AB8500_CODEC_CR51_DA12_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr51_da12_voice; + +/* CR51 - 5 */ +typedef enum { + AB8500_CODEC_CR51_SLDAI1TOSLADO1_NOT_LOOPEDBACK, + AB8500_CODEC_CR51_SLDAI1TOSLADO1_LOOPEDBACK +} t_ab8500_codec_cr51_sldai1toslado1; + +/* CR51:56 - 4:0 */ +/* In ab8500_codec.h */ + +/* CR52 - 5 */ +typedef enum { + AB8500_CODEC_CR52_SLDAI2TOSLADO2_NOT_LOOPEDBACK, + AB8500_CODEC_CR52_SLDAI2TOSLADO2_LOOPEDBACK +} t_ab8500_codec_cr52_sldai2toslado2; + +/* CR53 - 7 */ +typedef enum { + AB8500_CODEC_CR53_DA34_VOICE_AUDIOFILTER, + AB8500_CODEC_CR53_DA34_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr53_da34_voice; + +/* CR53 - 5 */ +typedef enum { + AB8500_CODEC_CR53_SLDAI3TOSLADO3_NOT_LOOPEDBACK, + AB8500_CODEC_CR53_SLDAI3TOSLADO3_LOOPEDBACK +} t_ab8500_codec_cr53_sldai3toslado3; + +/* CR54 - 5 */ +typedef enum { + AB8500_CODEC_CR54_SLDAI4TOSLADO4_NOT_LOOPEDBACK, + AB8500_CODEC_CR54_SLDAI4TOSLADO4_LOOPEDBACK +} t_ab8500_codec_cr54_sldai4toslado4; + +/* CR55 - 7 */ +typedef enum { + AB8500_CODEC_CR55_DA56_VOICE_AUDIOFILTER, + AB8500_CODEC_CR55_DA56_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr55_da56_voice; + +/* CR55 - 6:5 */ +typedef enum { + AB8500_CODEC_CR55_SLDAI5TOSLADO5_NOT_LOOPEDBACK, + AB8500_CODEC_CR55_SLDAI5TOSLADO5_DA_IN1_LOOPEDBACK, + AB8500_CODEC_CR55_SLDAI5TOSLADO5_DA_IN3_LOOPEDBACK, + AB8500_CODEC_CR55_SLDAI5TOSLADO5_DA_IN5_LOOPEDBACK +} t_ab8500_codec_cr55_sldai5toslado5; + +/* CR56 - 6:5 */ +typedef enum { + AB8500_CODEC_CR56_SLDAI6TOSLADO7_NOT_LOOPEDBACK, + AB8500_CODEC_CR56_SLDAI6TOSLADO7_DA_IN2_LOOPEDBACK, + AB8500_CODEC_CR56_SLDAI6TOSLADO7_DA_IN4_LOOPEDBACK, + AB8500_CODEC_CR56_SLDAI6TOSLADO7_DA_IN6_LOOPEDBACK +} t_ab8500_codec_cr56_sldai6toslado7; + +/* CR57 - 6 */ +typedef enum { + AB8500_CODEC_CR57_BFIFULL_MSK_MASKED, + AB8500_CODEC_CR57_BFIFULL_MSK_ENABLED +} t_ab8500_codec_cr57_bfifull_msk; + +/* CR57 - 5 */ +typedef enum { + AB8500_CODEC_CR57_BFIEMPT_MSK_MASKED, + AB8500_CODEC_CR57_BFIEMPT_MSK_ENABLED +} t_ab8500_codec_cr57_bfiempt_msk; + +/* CR57 - 4 */ +typedef enum { + AB8500_CODEC_CR57_DACHAN_MSK_MASKED, + AB8500_CODEC_CR57_DACHAN_MSK_ENABLED +} t_ab8500_codec_cr57_dachan_msk; + +/* CR57 - 3 */ +typedef enum { + AB8500_CODEC_CR57_GAIN_MSK_MASKED, + AB8500_CODEC_CR57_GAIN_MSK_ENABLED +} t_ab8500_codec_cr57_gain_msk; + +/* CR57 - 2 */ +typedef enum { + AB8500_CODEC_CR57_DSPAD_MSK_MASKED, + AB8500_CODEC_CR57_DSPAD_MSK_ENABLED +} t_ab8500_codec_cr57_dspad_msk; + +/* CR57 - 1 */ +typedef enum { + AB8500_CODEC_CR57_DSPDA_MSK_MASKED, + AB8500_CODEC_CR57_DSPDA_MSK_ENABLED +} t_ab8500_codec_cr57_dspda_msk; + +/* CR57 - 0 */ +typedef enum { + AB8500_CODEC_CR57_STFIR_MSK_MASKED, + AB8500_CODEC_CR57_STFIR_MSK_ENABLED +} t_ab8500_codec_cr57_stfir_msk; + +/* CR58 - Read Only */ +/* CR58 - 6 */ +typedef enum { + AB8500_CODEC_CR58_BFIFULL_EV_NOT_FULL, + AB8500_CODEC_CR58_BFIFULL_EV_FULL +} t_ab8500_codec_cr58_bfifull_ev; + +/* CR58 - 5 */ +typedef enum { + AB8500_CODEC_CR58_BFIEMPT_EV_NOT_EMPTY, + AB8500_CODEC_CR58_BFIEMPT_EV_EMPTY +} t_ab8500_codec_cr58_bfiempt_ev; + +/* CR58 - 4 */ +typedef enum { + AB8500_CODEC_CR58_DACHAN_EV_NO_SATURATION, + AB8500_CODEC_CR58_DACHAN_EV_SATURATION +} t_ab8500_codec_cr58_dachan_ev; + +/* CR58 - 3 */ +typedef enum { + AB8500_CODEC_CR58_GAIN_EV_NO_SATURATION, + AB8500_CODEC_CR58_GAIN_EV_SATURATION +} t_ab8500_codec_cr58_gain_ev; + +/* CR58 - 2 */ +typedef enum { + AB8500_CODEC_CR58_DSPAD_EV_NO_SATURATION, + AB8500_CODEC_CR58_DSPAD_EV_SATURATION +} t_ab8500_codec_cr58_dspad_ev; + +/* CR58 - 1 */ +typedef enum { + AB8500_CODEC_CR58_DSPDA_EV_NO_SATURATION, + AB8500_CODEC_CR58_DSPDA_EV_SATURATION +} t_ab8500_codec_cr58_dspda_ev; + +/* CR58 - 0 */ +typedef enum { + AB8500_CODEC_CR58_STFIR_EV_NO_SATURATION, + AB8500_CODEC_CR58_STFIR_EV_SATURATION +} t_ab8500_codec_cr58_stfir_ev; + +/* CR59 - 7 */ +typedef enum { + AB8500_CODEC_CR59_VSSREADY_MSK_MASKED, + AB8500_CODEC_CR59_VSSREADY_MSK_ENABLED +} t_ab8500_codec_cr59_vssready_msk; + +/* CR59 - 6 */ +typedef enum { + AB8500_CODEC_CR59_SHRTVIBL_MSK_MASKED, + AB8500_CODEC_CR59_SHRTVIBL_MSK_ENABLED +} t_ab8500_codec_cr59_shrtvibl_msk; + +/* CR59 - 5 */ +typedef enum { + AB8500_CODEC_CR59_SHRTVIBR_MSK_MASKED, + AB8500_CODEC_CR59_SHRTVIBR_MSK_ENABLED +} t_ab8500_codec_cr59_shrtvibr_msk; + +/* CR59 - 4 */ +typedef enum { + AB8500_CODEC_CR59_SHRTHFL_MSK_MASKED, + AB8500_CODEC_CR59_SHRTHFL_MSK_ENABLED +} t_ab8500_codec_cr59_shrthfl_msk; + +/* CR59 - 3 */ +typedef enum { + AB8500_CODEC_CR59_SHRTHFR_MSK_MASKED, + AB8500_CODEC_CR59_SHRTHFR_MSK_ENABLED +} t_ab8500_codec_cr59_shrthfr_msk; + +/* CR59 - 2 */ +typedef enum { + AB8500_CODEC_CR59_SHRTHSL_MSK_MASKED, + AB8500_CODEC_CR59_SHRTHSL_MSK_ENABLED +} t_ab8500_codec_cr59_shrthsl_msk; + +/* CR59 - 1 */ +typedef enum { + AB8500_CODEC_CR59_SHRTHSR_MSK_MASKED, + AB8500_CODEC_CR59_SHRTHSR_MSK_ENABLED +} t_ab8500_codec_cr59_shrthsr_msk; + +/* CR59 - 0 */ +typedef enum { + AB8500_CODEC_CR59_SHRTEAR_MSK_MASKED, + AB8500_CODEC_CR59_SHRTEAR_MSK_ENABLED +} t_ab8500_codec_cr59_shrtear_msk; + +/* CR60 - Read Only */ +/* CR60 - 7 */ +typedef enum { + AB8500_CODEC_CR60_VSSREADY_EV_NOT_READY, + AB8500_CODEC_CR60_VSSREADY_EV_READY +} t_ab8500_codec_cr60_vssready_ev; + +/* CR60 - 6 */ +typedef enum { + AB8500_CODEC_CR60_SHRTVIBL_EV_NO_SHORTCIRCUIT, + AB8500_CODEC_CR60_SHRTVIBL_EV_SHORTCIRCUIT +} t_ab8500_codec_cr60_shrtvibl_ev; + +/* CR60 - 5 */ +typedef enum { + AB8500_CODEC_CR60_SHRTVIBR_EV_NO_SHORTCIRCUIT, + AB8500_CODEC_CR60_SHRTVIBR_EV_SHORTCIRCUIT +} t_ab8500_codec_cr60_shrtvibr_ev; + +/* CR60 - 4 */ +typedef enum { + AB8500_CODEC_CR60_SHRTHFL_EV_NO_SHORTCIRCUIT, + AB8500_CODEC_CR60_SHRTHFL_EV_SHORTCIRCUIT +} t_ab8500_codec_cr60_shrthfl_ev; + +/* CR60 - 3 */ +typedef enum { + AB8500_CODEC_CR60_SHRTHFR_EV_NO_SHORTCIRCUIT, + AB8500_CODEC_CR60_SHRTHFR_EV_SHORTCIRCUIT +} t_ab8500_codec_cr60_shrthfr_ev; + +/* CR60 - 2 */ +typedef enum { + AB8500_CODEC_CR60_SHRTHSL_EV_NO_SHORTCIRCUIT, + AB8500_CODEC_CR60_SHRTHSL_EV_SHORTCIRCUIT +} t_ab8500_codec_cr60_shrthsl_ev; + +/* CR60 - 1 */ +typedef enum { + AB8500_CODEC_CR60_SHRTHSR_EV_NO_SHORTCIRCUIT, + AB8500_CODEC_CR60_SHRTHSR_EV_SHORTCIRCUIT +} t_ab8500_codec_cr60_shrthsr_ev; + +/* CR60 - 0 */ +typedef enum { + AB8500_CODEC_CR60_SHRTEAR_EV_NO_SHORTCIRCUIT, + AB8500_CODEC_CR60_SHRTEAR_EV_SHORTCIRCUIT +} t_ab8500_codec_cr60_shrtear_ev; + +/* CR61 - 6:2 - Read Only */ +typedef enum { + AB8500_CODEC_CR61_REVISION_1_0, + AB8500_CODEC_CR61_REVISION_TBD +} t_ab8500_codec_cr61_revision; + +/* CR61 - 1:0 */ +typedef enum { + AB8500_CODEC_CR61_FADE_SPEED_1MS, + AB8500_CODEC_CR61_FADE_SPEED_4MS, + AB8500_CODEC_CR61_FADE_SPEED_8MS, + AB8500_CODEC_CR61_FADE_SPEED_16MS +} t_ab8500_codec_cr61_fade_speed; + +/* CR62 - Read Only */ +/* CR62 - 5 */ +typedef enum { + AB8500_CODEC_CR62_DMIC1SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC1SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic1sinc3; + +/* CR62 - 4 */ +typedef enum { + AB8500_CODEC_CR62_DMIC2SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC2SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic2sinc3; + +/* CR62 - 3 */ +typedef enum { + AB8500_CODEC_CR62_DMIC3SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC3SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic3sinc3; + +/* CR62 - 2 */ +typedef enum { + AB8500_CODEC_CR62_DMIC4SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC4SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic4sinc3; + +/* CR62 - 1 */ +typedef enum { + AB8500_CODEC_CR62_DMIC5SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC5SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic5sinc3; + +/* CR62 - 0 */ +typedef enum { + AB8500_CODEC_CR62_DMIC6SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC6SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic6sinc3; + +/* CR63 - 7 */ +typedef enum { + AB8500_CODEC_CR63_DATOHSLEN_DISABLED, + AB8500_CODEC_CR63_DATOHSLEN_ENABLED +} t_ab8500_codec_cr63_datohslen; + +/* CR63 - 6 */ +typedef enum { + AB8500_CODEC_CR63_DATOHSREN_DISABLED, + AB8500_CODEC_CR63_DATOHSREN_ENABLED +} t_ab8500_codec_cr63_datohsren; + +/* CR63 - 5 */ +typedef enum { + AB8500_CODEC_CR63_AD1SEL_LINLADL_SELECTED, + AB8500_CODEC_CR63_AD1SEL_DMIC1_SELECTED +} t_ab8500_codec_cr63_ad1sel; + +/* CR63 - 4 */ +typedef enum { + AB8500_CODEC_CR63_AD2SEL_LINRADR_SELECTED, + AB8500_CODEC_CR63_AD2SEL_DMIC2_SELECTED +} t_ab8500_codec_cr63_ad2sel; + +/* CR63 - 3 */ +typedef enum { + AB8500_CODEC_CR63_AD3SEL_ADMO_SELECTED, + AB8500_CODEC_CR63_AD3SEL_DMIC3_SELECTED +} t_ab8500_codec_cr63_ad3sel; + +/* CR63 - 2 */ +typedef enum { + AB8500_CODEC_CR63_AD5SEL_AMADR_SELECTED, + AB8500_CODEC_CR63_AD5SEL_DMIC5_SELECTED +} t_ab8500_codec_cr63_ad5sel; + +/* CR63 - 1 */ +typedef enum { + AB8500_CODEC_CR63_AD6SEL_ADMO_SELECTED, + AB8500_CODEC_CR63_AD6SEL_DMIC6_SELECTED +} t_ab8500_codec_cr63_ad6sel; + +/* CR63 - 0 */ +typedef enum { + AB8500_CODEC_CR63_ANCSEL_NOT_MIXED_IN_EAR, + AB8500_CODEC_CR63_ANCSEL_MIXED_IN_EAR +} t_ab8500_codec_cr63_ancsel; + +/* CR64 - 7 */ +typedef enum { + AB8500_CODEC_CR64_DATOHFREN_NOT_MIXED_TO_HFR, + AB8500_CODEC_CR64_DATOHFREN_MIXED_TO_HFR +} t_ab8500_codec_cr64_datohfren; + +/* CR64 - 6 */ +typedef enum { + AB8500_CODEC_CR64_DATOHFLEN_NOT_MIXED_TO_HFL, + AB8500_CODEC_CR64_DATOHFLEN_MIXED_TO_HFL +} t_ab8500_codec_cr64_datohflen; + +/* CR64 - 5 */ +typedef enum { + AB8500_CODEC_CR64_HFRSEL_DA4_MIXED_TO_HFR, + AB8500_CODEC_CR64_HFRSEL_ANC_MIXED_TO_HFR +} t_ab8500_codec_cr64_hfrsel; + +/* CR64 - 4 */ +typedef enum { + AB8500_CODEC_CR64_HFLSEL_DA3_MIXED_TO_HFL, + AB8500_CODEC_CR64_HFLSEL_ANC_MIXED_TO_HFL +} t_ab8500_codec_cr64_hflsel; + +/* CR64 - 3:2 */ +typedef enum { + AB8500_CODEC_CR64_STFIR1SEL_AD_OUT1_SELECTED, + AB8500_CODEC_CR64_STFIR1SEL_AD_OUT3_SELECTED, + AB8500_CODEC_CR64_STFIR1SEL_DA_IN1_SELECTED +} t_ab8500_codec_cr64_stfir1sel; + +/* CR64 - 1:0 */ +typedef enum { + AB8500_CODEC_CR64_STFIR2SEL_AD_OUT2_SELECTED, + AB8500_CODEC_CR64_STFIR2SEL_AD_OUT4_SELECTED, + AB8500_CODEC_CR64_STFIR2SEL_DA_IN2_SELECTED +} t_ab8500_codec_cr64_stfir2sel; + +/* CR65 - 6 */ +typedef enum { + AB8500_CODEC_CR65_FADEDIS_AD1_ENABLED, + AB8500_CODEC_CR65_FADEDIS_AD1_DISABLED +} t_ab8500_codec_cr65_fadedis_ad1; + +/* CR65 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr65_ad1gain; + +/* CR66 - 6 */ +typedef enum { + AB8500_CODEC_CR66_FADEDIS_AD2_ENABLED, + AB8500_CODEC_CR66_FADEDIS_AD2_DISABLED +} t_ab8500_codec_cr66_fadedis_ad2; + +/* CR66 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr66_ad2gain; + +/* CR67 - 6 */ +typedef enum { + AB8500_CODEC_CR67_FADEDIS_AD3_ENABLED, + AB8500_CODEC_CR67_FADEDIS_AD3_DISABLED +} t_ab8500_codec_cr67_fadedis_ad3; + +/* CR67 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr67_ad3gain; + +/* CR68 - 6 */ +typedef enum { + AB8500_CODEC_CR68_FADEDIS_AD4_ENABLED, + AB8500_CODEC_CR68_FADEDIS_AD4_DISABLED +} t_ab8500_codec_cr68_fadedis_ad4; + +/* CR68 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr68_ad4gain; + +/* CR69 - 6 */ +typedef enum { + AB8500_CODEC_CR69_FADEDIS_AD5_ENABLED, + AB8500_CODEC_CR69_FADEDIS_AD5_DISABLED +} t_ab8500_codec_cr69_fadedis_ad5; + +/* CR69 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr69_ad5gain; + +/* CR70 - 6 */ +typedef enum { + AB8500_CODEC_CR70_FADEDIS_AD6_ENABLED, + AB8500_CODEC_CR70_FADEDIS_AD6_DISABLED +} t_ab8500_codec_cr70_fadedis_ad6; + +/* CR70 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr70_ad6gain; + +/* CR71 - 6 */ +typedef enum { + AB8500_CODEC_CR71_FADEDIS_DA1_ENABLED, + AB8500_CODEC_CR71_FADEDIS_DA1_DISABLED +} t_ab8500_codec_cr71_fadedis_da1; + +/* CR71 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr71_da1gain; + +/* CR72 - 6 */ +typedef enum { + AB8500_CODEC_CR72_FADEDIS_DA2_ENABLED, + AB8500_CODEC_CR72_FADEDIS_DA2_DISABLED +} t_ab8500_codec_cr72_fadedis_da2; + +/* CR72 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr72_da2gain; + +/* CR73 - 6 */ +typedef enum { + AB8500_CODEC_CR73_FADEDIS_DA3_ENABLED, + AB8500_CODEC_CR73_FADEDIS_DA3_DISABLED +} t_ab8500_codec_cr73_fadedis_da3; + +/* CR73 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr73_da3gain; + +/* CR74 - 6 */ +typedef enum { + AB8500_CODEC_CR74_FADEDIS_DA4_ENABLED, + AB8500_CODEC_CR74_FADEDIS_DA4_DISABLED +} t_ab8500_codec_cr74_fadedis_da4; + +/* CR74 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr74_da4gain; + +/* CR75 - 6 */ +typedef enum { + AB8500_CODEC_CR75_FADEDIS_DA5_ENABLED, + AB8500_CODEC_CR75_FADEDIS_DA5_DISABLED +} t_ab8500_codec_cr75_fadedis_da5; + +/* CR75 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr75_da5gain; + +/* CR76 - 6 */ +typedef enum { + AB8500_CODEC_CR76_FADEDIS_DA6_ENABLED, + AB8500_CODEC_CR76_FADEDIS_DA6_DISABLED +} t_ab8500_codec_cr76_fadedis_da6; + +/* CR76 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr76_da6gain; + +/* CR77 - 6 */ +typedef enum { + AB8500_CODEC_CR77_FADEDIS_AD1L_TO_HFL_ENABLED, + AB8500_CODEC_CR77_FADEDIS_AD1L_TO_HFL_DISABLED +} t_ab8500_codec_cr77_fadedis_ad1l; + +/* CR77 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr77_ad1lbgain_to_hfl; + +/* CR78 - 6 */ +typedef enum { + AB8500_CODEC_CR78_FADEDIS_AD2L_TO_HFR_ENABLED, + AB8500_CODEC_CR78_FADEDIS_AD2L_TO_HFR_DISABLED +} t_ab8500_codec_cr78_fadedis_ad2l; + +/* CR78 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr78_ad2lbgain_to_hfr; + +/* CR79 - 7 */ +typedef enum { + AB8500_CODEC_CR79_HSSINC1_SINC3_CHOOSEN, + AB8500_CODEC_CR79_HSSINC1_SINC1_CHOOSEN +} t_ab8500_codec_cr79_hssinc1; + +/* CR79 - 4 */ +typedef enum { + AB8500_CODEC_CR79_FADEDIS_HSL_ENABLED, + AB8500_CODEC_CR79_FADEDIS_HSL_DISABLED +} t_ab8500_codec_cr79_fadedis_hsl; + +/* CR79 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr79_hsldgain; + +/* CR80 - 4 */ +typedef enum { + AB8500_CODEC_CR80_FADEDIS_HSR_ENABLED, + AB8500_CODEC_CR80_FADEDIS_HSR_DISABLED +} t_ab8500_codec_cr80_fadedis_hsr; + +/* CR80 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr80_hsrdgain; + +/* CR81 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr81_stfir1gain; + +/* CR82 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr82_stfir2gain; + +/* CR83 - 2 */ +typedef enum { + AB8500_CODEC_CR83_ENANC_DISABLED, + AB8500_CODEC_CR83_ENANC_ENABLED +} t_ab8500_codec_cr83_enanc; + +/* CR83 - 1 */ +typedef enum { + AB8500_CODEC_CR83_ANCIIRINIT_NOT_STARTED, + AB8500_CODEC_CR83_ANCIIRINIT_STARTED +} t_ab8500_codec_cr83_anciirinit; + +/* CR83 - 0 */ +typedef enum { + AB8500_CODEC_CR83_ANCFIRUPDATE_RESETTED, + AB8500_CODEC_CR83_ANCFIRUPDATE_NOT_RESETTED +} t_ab8500_codec_cr83_ancfirupdate; + +/* CR84 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr84_ancinshift; + +/* CR85 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr85_ancfiroutshift; + +/* CR86 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr86_ancshiftout; + +/* CR87 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr87_ancfircoeff_msb; + +/* CR88 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr88_ancfircoeff_lsb; + +/* CR89 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr89_anciircoeff_msb; + +/* CR90 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr90_anciircoeff_lsb; + +/* CR91 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr91_ancwarpdel_msb; + +/* CR92 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr92_ancwarpdel_lsb; + +/* CR93 - Read Only */ +/* CR93 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr93_ancfirpeak_msb; + +/* CR94 - Read Only */ +/* CR94 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr94_ancfirpeak_lsb; + +/* CR95 - Read Only */ +/* CR95 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr95_anciirpeak_msb; + +/* CR96 - Read Only */ +/* CR96 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr96_anciirpeak_lsb; + +/* CR97 - 7 */ +typedef enum { + AB8500_CODEC_CR97_STFIR_SET_LAST_NOT_APPLIED, + AB8500_CODEC_CR97_STFIR_SET_LAST_APPLIED +} t_ab8500_codec_cr97_stfir_set; + +/* CR97 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr97_stfir_addr; + +/* CR98 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr98_stfir_coeff_msb; + +/* CR99 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr99_stfir_coeff_lsb; + +/* CR100 - 2 */ +typedef enum { + AB8500_CODEC_CR100_ENSTFIRS_DISABLED, + AB8500_CODEC_CR100_ENSTFIRS_ENABLED +} t_ab8500_codec_cr100_enstfirs; + +/* CR100 - 1 */ +typedef enum { + AB8500_CODEC_CR100_STFIRSTOIF1_AUD_IF0_DATA_RATE, + AB8500_CODEC_CR100_STFIRSTOIF1_AUD_IF1_DATA_RATE +} t_ab8500_codec_cr100_stfirstoif1; + +/* CR100 - 0 */ +typedef enum { + AB8500_CODEC_CR100_STFIR_BUSY_READY, + AB8500_CODEC_CR100_STFIR_BUSY_NOT_READY +} t_ab8500_codec_cr100_stfir_busy; + +/* CR101 - 7 */ +typedef enum { + AB8500_CODEC_CR101_PARLHF_INDEPENDENT, + AB8500_CODEC_CR101_PARLHF_BRIDGED +} t_ab8500_codec_cr101_parlhf; + +/* CR101 - 6 */ +typedef enum { + AB8500_CODEC_CR101_PARLVIB_INDEPENDENT, + AB8500_CODEC_CR101_PARLVIB_BRIDGED +} t_ab8500_codec_cr101_parlvib; + +/* CR101 - 3 */ +typedef enum { + AB8500_CODEC_CR101_CLASSD_VIBLSWAPEN_DISABLED, + AB8500_CODEC_CR101_CLASSD_VIBLSWAPEN_ENABLED +} t_ab8500_codec_cr101_classd_viblswapen; + +/* CR101 - 2 */ +typedef enum { + AB8500_CODEC_CR101_CLASSD_VIBRSWAPEN_DISABLED, + AB8500_CODEC_CR101_CLASSD_VIBRSWAPEN_ENABLED +} t_ab8500_codec_cr101_classd_vibrswapen; + +/* CR101 - 1 */ +typedef enum { + AB8500_CODEC_CR101_CLASSD_HFLSWAPEN_DISABLED, + AB8500_CODEC_CR101_CLASSD_HFLSWAPEN_ENABLED +} t_ab8500_codec_cr101_classd_hflswapen; + +/* CR101 - 0 */ +typedef enum { + AB8500_CODEC_CR101_CLASSD_HFRSWAPEN_DISABLED, + AB8500_CODEC_CR101_CLASSD_HFRSWAPEN_ENABLED +} t_ab8500_codec_cr101_classd_hfrswapen; + +/* CR102 - 7:4 */ +typedef enum { + AB8500_CODEC_CR102_CLASSD_FIRBYP_ALL_ENABLED = 0, + AB8500_CODEC_CR102_CLASSD_FIRBYP_HFL_BYPASSED = 1, + AB8500_CODEC_CR102_CLASSD_FIRBYP_HFR_BYPASSED = 2, + AB8500_CODEC_CR102_CLASSD_FIRBYP_VIBL_BYPASSED = 4, + AB8500_CODEC_CR102_CLASSD_FIRBYP_VIBR_BYPASSED = 8 +} t_ab8500_codec_cr102_classd_firbyp; + +/* CR102 - 3:0 */ +typedef enum { + AB8500_CODEC_CR102_CLASSD_HIGHVOLEN_DISABLED = 0, + AB8500_CODEC_CR102_CLASSD_HIGHVOLEN_HFL_HIGHVOL = 1, + AB8500_CODEC_CR102_CLASSD_HIGHVOLEN_HFR_HIGHVOL = 2, + AB8500_CODEC_CR102_CLASSD_HIGHVOLEN_VIBL_HIGHVOL = 4, + AB8500_CODEC_CR102_CLASSD_HIGHVOLEN_VIBR_HIGHVOL = 8 +} t_ab8500_codec_cr102_classd_highvolen; + +/* CR103 - 7:4 */ +typedef t_uint8 t_ab8500_codec_cr103_classd_ditherhpgain; + +/* CR103 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr103_classd_ditherwgain; + +/* CR104 - 5:0 */ +/* In ab8500_codec.h */ + +/* CR105 - 7:0 */ +/* In ab8500_codec.h */ + +/* CR106 - 6:4 */ +/* In ab8500_codec.h */ + +/* CR106 - 2 */ +/* In ab8500_codec.h */ + +/* CR106 - 1 */ +/* In ab8500_codec.h */ + +/* CR106 - 0 */ +/* In ab8500_codec.h */ + +/* CR107 - 7:0 */ +/* In ab8500_codec.h */ + +/* CR108 - 7:0 */ +/* In ab8500_codec.h */ + +/* CR109 - Read Only */ +/* CR109 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr109_bfifosamples; + +typedef enum { + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT1, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT2, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT3, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT4, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT5, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT6, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT7, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT8, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_ZEROS, + AB8500_CODEC_CR31_TO_CR46_SLOT_IS_TRISTATE = 15, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_UNDEFINED +} t_ab8500_codec_cr31_to_cr46_ad_data_allocation; + +typedef enum { + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT00, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT01, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT02, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT03, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT04, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT05, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT06, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT07, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT08, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT09, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT10, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT11, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT12, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT13, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT14, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT15, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT16, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT17, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT18, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT19, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT20, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT21, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT22, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT23, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT24, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT25, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT26, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT27, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT28, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT29, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT30, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT31, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT_UNDEFINED +} t_ab8500_codec_cr51_to_cr56_sltoda; + +/* CR104 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr104_bfifoint; + +/* CR105 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr105_bfifotx; + +/* CR106 - 6:4 */ +typedef enum { + AB8500_CODEC_CR106_BFIFOFSEXT_NO_EXTRA_CLK, + AB8500_CODEC_CR106_BFIFOFSEXT_1SLOT_EXTRA_CLK, + AB8500_CODEC_CR106_BFIFOFSEXT_2SLOT_EXTRA_CLK, + AB8500_CODEC_CR106_BFIFOFSEXT_3SLOT_EXTRA_CLK, + AB8500_CODEC_CR106_BFIFOFSEXT_4SLOT_EXTRA_CLK, + AB8500_CODEC_CR106_BFIFOFSEXT_5SLOT_EXTRA_CLK, + AB8500_CODEC_CR106_BFIFOFSEXT_6SLOT_EXTRA_CLK +} t_ab8500_codec_cr106_bfifofsext; + +/* CR106 - 2 */ +typedef enum { + AB8500_CODEC_CR106_BFIFOMSK_AD_DATA0_UNMASKED, + AB8500_CODEC_CR106_BFIFOMSK_AD_DATA0_MASKED +} t_ab8500_codec_cr106_bfifomsk; + +/* CR106 - 1 */ +typedef enum { + AB8500_CODEC_CR106_BFIFOMSTR_SLAVE_MODE, + AB8500_CODEC_CR106_BFIFOMSTR_MASTER_MODE +} t_ab8500_codec_cr106_bfifomstr; + +/* CR106 - 0 */ +typedef enum { + AB8500_CODEC_CR106_BFIFOSTRT_STOPPED, + AB8500_CODEC_CR106_BFIFOSTRT_RUNNING +} t_ab8500_codec_cr106_bfifostrt; + +/* CR107 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr107_bfifosampnr; + +/* CR108 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr108_bfifowakeup; + +/*configuration structure for AB8500 Codec*/ +typedef struct { + /* CR0 */ + t_ab8500_codec_cr0_powerup cr0_powerup; + t_ab8500_codec_cr0_enaana cr0_enaana; + + /* CR1 */ + t_ab8500_codec_cr1_swreset cr1_swreset; + + /* CR2 */ + t_ab8500_codec_cr2_enad1 cr2_enad1; + t_ab8500_codec_cr2_enad2 cr2_enad2; + t_ab8500_codec_cr2_enad3 cr2_enad3; + t_ab8500_codec_cr2_enad4 cr2_enad4; + t_ab8500_codec_cr2_enad5 cr2_enad5; + t_ab8500_codec_cr2_enad6 cr2_enad6; + + /* CR3 */ + t_ab8500_codec_cr3_enda1 cr3_enda1; + t_ab8500_codec_cr3_enda2 cr3_enda2; + t_ab8500_codec_cr3_enda3 cr3_enda3; + t_ab8500_codec_cr3_enda4 cr3_enda4; + t_ab8500_codec_cr3_enda5 cr3_enda5; + t_ab8500_codec_cr3_enda6 cr3_enda6; + + /* CR4 */ + t_ab8500_codec_cr4_lowpowhs cr4_lowpowhs; + t_ab8500_codec_cr4_lowpowdachs cr4_lowpowdachs; + t_ab8500_codec_cr4_lowpowear cr4_lowpowear; + t_ab8500_codec_cr4_ear_sel_cm cr4_ear_sel_cm; + t_ab8500_codec_cr4_hs_hp_dis cr4_hs_hp_dis; + t_ab8500_codec_cr4_ear_hp_dis cr4_ear_hp_dis; + + /* CR5 */ + t_ab8500_codec_cr5_enmic1 cr5_enmic1; + t_ab8500_codec_cr5_enmic2 cr5_enmic2; + t_ab8500_codec_cr5_enlinl cr5_enlinl; + t_ab8500_codec_cr5_enlinr cr5_enlinr; + t_ab8500_codec_cr5_mutmic1 cr5_mutmic1; + t_ab8500_codec_cr5_mutmic2 cr5_mutmic2; + t_ab8500_codec_cr5_mutlinl cr5_mutlinl; + t_ab8500_codec_cr5_mutlinr cr5_mutlinr; + + /* CR6 */ + t_ab8500_codec_cr6_endmic1 cr6_endmic1; + t_ab8500_codec_cr6_endmic2 cr6_endmic2; + t_ab8500_codec_cr6_endmic3 cr6_endmic3; + t_ab8500_codec_cr6_endmic4 cr6_endmic4; + t_ab8500_codec_cr6_endmic5 cr6_endmic5; + t_ab8500_codec_cr6_endmic6 cr6_endmic6; + + /* CR7 */ + t_ab8500_codec_cr7_mic1sel cr7_mic1sel; + t_ab8500_codec_cr7_linrsel cr7_linrsel; + t_ab8500_codec_cr7_endrvhsl cr7_endrvhsl; + t_ab8500_codec_cr7_endrvhsr cr7_endrvhsr; + t_ab8500_codec_cr7_enadcmic cr7_enadcmic; + t_ab8500_codec_cr7_enadclinl cr7_enadclinl; + t_ab8500_codec_cr7_enadclinr cr7_enadclinr; + + /* CR8 */ + t_ab8500_codec_cr8_cp_dis_pldwn cr8_cp_dis_pldwn; + t_ab8500_codec_cr8_enear cr8_enear; + t_ab8500_codec_cr8_enhsl cr8_enhsl; + t_ab8500_codec_cr8_enhsr cr8_enhsr; + t_ab8500_codec_cr8_enhfl cr8_enhfl; + t_ab8500_codec_cr8_enhfr cr8_enhfr; + t_ab8500_codec_cr8_envibl cr8_envibl; + t_ab8500_codec_cr8_envibr cr8_envibr; + + /* CR9 */ + t_ab8500_codec_cr9_endacear cr9_endacear; + t_ab8500_codec_cr9_endachsl cr9_endachsl; + t_ab8500_codec_cr9_endachsr cr9_endachsr; + t_ab8500_codec_cr9_endachfl cr9_endachfl; + t_ab8500_codec_cr9_endachfr cr9_endachfr; + t_ab8500_codec_cr9_endacvibl cr9_endacvibl; + t_ab8500_codec_cr9_endacvibr cr9_endacvibr; + + /* CR10 */ + t_ab8500_codec_cr10_muteear cr10_muteear; + t_ab8500_codec_cr10_mutehsl cr10_mutehsl; + t_ab8500_codec_cr10_mutehsr cr10_mutehsr; + t_ab8500_codec_cr10_mutehfl cr10_mutehfl; + t_ab8500_codec_cr10_mutehfr cr10_mutehfr; + t_ab8500_codec_cr10_mutevibl cr10_mutevibl; + t_ab8500_codec_cr10_mutevibr cr10_mutevibr; + + /* CR11 */ + t_ab8500_codec_cr11_earshortpwd cr11_earshortpwd; + t_ab8500_codec_cr11_earshortdis cr11_earshortdis; + t_ab8500_codec_cr11_hslshortdis cr11_hslshortdis; + t_ab8500_codec_cr11_hsrshortdis cr11_hsrshortdis; + t_ab8500_codec_cr11_hflshortdis cr11_hflshortdis; + t_ab8500_codec_cr11_hfrshortdis cr11_hfrshortdis; + t_ab8500_codec_cr11_viblshortdis cr11_viblshortdis; + t_ab8500_codec_cr11_vibrshortdis cr11_vibrshortdis; + + /* CR12 */ + t_ab8500_codec_cr12_encphs cr12_encphs; + t_ab8500_codec_cr12_hsautotime cr12_hsautotime; + t_ab8500_codec_cr12_hsautoensel cr12_hsautoensel; + t_ab8500_codec_cr12_hsautoen cr12_hsautoen; + + /* CR13 */ + t_ab8500_codec_cr13_envdet_hthresh cr13_envdet_hthresh; + t_ab8500_codec_cr13_envdet_lthresh cr13_envdet_lthresh; + + /* CR14 */ + t_ab8500_codec_cr14_smpslven cr14_smpslven; + t_ab8500_codec_cr14_envdetsmpsen cr14_envdetsmpsen; + t_ab8500_codec_cr14_cplven cr14_cplven; + t_ab8500_codec_cr14_envdetcpen cr14_envdetcpen; + t_ab8500_codec_cr14_envet_time cr14_envet_time; + + /* CR15 */ + t_ab8500_codec_cr15_pwmtovibl cr15_pwmtovibl; + t_ab8500_codec_cr15_pwmtovibr cr15_pwmtovibr; + t_ab8500_codec_cr15_pwmlctrl cr15_pwmlctrl; + t_ab8500_codec_cr15_pwmrctrl cr15_pwmrctrl; + t_ab8500_codec_cr15_pwmnlctrl cr15_pwmnlctrl; + t_ab8500_codec_cr15_pwmplctrl cr15_pwmplctrl; + t_ab8500_codec_cr15_pwmnrctrl cr15_pwmnrctrl; + t_ab8500_codec_cr15_pwmprctrl cr15_pwmprctrl; + + /* CR16 */ + t_ab8500_codec_cr16_pwmnlpol cr16_pwmnlpol; + t_ab8500_codec_cr16_pwmnldutycycle cr16_pwmnldutycycle; + + /* CR17 */ + t_ab8500_codec_cr17_pwmplpol cr17_pwmplpol; + t_ab8500_codec_cr17_pwmpldutycycle cr17_pwmpldutycycle; + + /* CR18 */ + t_ab8500_codec_cr18_pwmnrpol cr18_pwmnrpol; + t_ab8500_codec_cr18_pwmnrdutycycle cr18_pwmnrdutycycle; + + /* CR19 */ + t_ab8500_codec_cr19_pwmprpol cr19_pwmprpol; + t_ab8500_codec_cr19_pwmprdutycycle cr19_pwmprdutycycle; + + /* CR20 */ + t_ab8500_codec_cr20_en_se_mic1 cr20_en_se_mic1; + t_ab8500_codec_cr20_mic1_gain cr20_mic1_gain; + + /* CR21 */ + t_ab8500_codec_cr21_en_se_mic2 cr21_en_se_mic2; + t_ab8500_codec_cr21_mic2_gain cr21_mic2_gain; + + /* CR22 */ + t_ab8500_codec_cr22_hsl_gain cr22_hsl_gain; + t_ab8500_codec_cr22_linl_gain cr22_linl_gain; + + /* CR23 */ + t_ab8500_codec_cr23_hsr_gain cr23_hsr_gain; + t_ab8500_codec_cr23_linr_gain cr23_linr_gain; + + /* CR24 */ + t_ab8500_codec_cr24_lintohsl_gain cr24_lintohsl_gain; + + /* CR25 */ + t_ab8500_codec_cr25_lintohsr_gain cr25_lintohsr_gain; + + /* CR26 */ + t_ab8500_codec_cr26_ad1nh cr26_ad1nh; + t_ab8500_codec_cr26_ad2nh cr26_ad2nh; + t_ab8500_codec_cr26_ad3nh cr26_ad3nh; + t_ab8500_codec_cr26_ad4nh cr26_ad4nh; + t_ab8500_codec_cr26_ad1_voice cr26_ad1_voice; + t_ab8500_codec_cr26_ad2_voice cr26_ad2_voice; + t_ab8500_codec_cr26_ad3_voice cr26_ad3_voice; + t_ab8500_codec_cr26_ad4_voice cr26_ad4_voice; + + /* CR27 */ + t_ab8500_codec_cr27_en_mastgen cr27_en_mastgen; + t_ab8500_codec_cr27_if1_bitclk_osr cr27_if1_bitclk_osr; + t_ab8500_codec_cr27_enfs_bitclk1 cr27_enfs_bitclk1; + t_ab8500_codec_cr27_if0_bitclk_osr cr27_if0_bitclk_osr; + t_ab8500_codec_cr27_enfs_bitclk0 cr27_enfs_bitclk0; + + /* CR28 */ + t_ab8500_codec_cr28_fsync0p cr28_fsync0p; + t_ab8500_codec_cr28_bitclk0p cr28_bitclk0p; + t_ab8500_codec_cr28_if0del cr28_if0del; + t_ab8500_codec_cr28_if0format cr28_if0format; + t_ab8500_codec_cr28_if0wl cr28_if0wl; + + /* CR29 */ + t_ab8500_codec_cr29_if0datoif1ad cr29_if0datoif1ad; + t_ab8500_codec_cr29_if0cktoif1ck cr29_if0cktoif1ck; + t_ab8500_codec_cr29_if1master cr29_if1master; + t_ab8500_codec_cr29_if1datoif0ad cr29_if1datoif0ad; + t_ab8500_codec_cr29_if1cktoif0ck cr29_if1cktoif0ck; + t_ab8500_codec_cr29_if0master cr29_if0master; + t_ab8500_codec_cr29_if0bfifoen cr29_if0bfifoen; + + /* CR30 */ + t_ab8500_codec_cr30_fsync1p cr30_fsync1p; + t_ab8500_codec_cr30_bitclk1p cr30_bitclk1p; + t_ab8500_codec_cr30_if1del cr30_if1del; + t_ab8500_codec_cr30_if1format cr30_if1format; + t_ab8500_codec_cr30_if1wl cr30_if1wl; + + /* CR31 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr31_adotoslot1; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr31_adotoslot0; + + /* CR32 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr32_adotoslot3; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr32_adotoslot2; + + /* CR33 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr33_adotoslot5; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr33_adotoslot4; + + /* CR34 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr34_adotoslot7; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr34_adotoslot6; + + /* CR35 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr35_adotoslot9; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr35_adotoslot8; + + /* CR36 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr36_adotoslot11; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr36_adotoslot10; + + /* CR37 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr37_adotoslot13; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr37_adotoslot12; + + /* CR38 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr38_adotoslot15; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr38_adotoslot14; + + /* CR39 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr39_adotoslot17; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr39_adotoslot16; + + /* CR40 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr40_adotoslot19; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr40_adotoslot18; + + /* CR41 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr41_adotoslot21; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr41_adotoslot20; + + /* CR42 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr42_adotoslot23; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr42_adotoslot22; + + /* CR43 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr43_adotoslot25; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr43_adotoslot24; + + /* CR44 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr44_adotoslot27; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr44_adotoslot26; + + /* CR45 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr45_adotoslot29; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr45_adotoslot28; + + /* CR46 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr46_adotoslot31; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr46_adotoslot30; + + /* CR47 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl7; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl6; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl5; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl4; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl3; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl2; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl1; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl0; + + /* CR48 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl15; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl14; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl13; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl12; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl11; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl10; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl9; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl8; + + /* CR49 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl23; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl22; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl21; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl20; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl19; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl18; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl17; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl16; + + /* CR50 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl31; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl30; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl29; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl28; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl27; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl26; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl25; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl24; + + /* CR51 */ + t_ab8500_codec_cr51_da12_voice cr51_da12_voice; + t_ab8500_codec_cr51_sldai1toslado1 cr51_sldai1toslado1; + t_ab8500_codec_cr51_to_cr56_sltoda cr51_sltoda1; + + /* CR52 */ + t_ab8500_codec_cr52_sldai2toslado2 cr52_sldai2toslado2; + t_ab8500_codec_cr51_to_cr56_sltoda cr52_sltoda2; + + /* CR53 */ + t_ab8500_codec_cr53_da34_voice cr53_da34_voice; + t_ab8500_codec_cr53_sldai3toslado3 cr53_sldai3toslado3; + t_ab8500_codec_cr51_to_cr56_sltoda cr53_sltoda3; + + /* CR54 */ + t_ab8500_codec_cr54_sldai4toslado4 cr54_sldai4toslado4; + t_ab8500_codec_cr51_to_cr56_sltoda cr54_sltoda4; + + /* CR55 */ + t_ab8500_codec_cr55_da56_voice cr55_da56_voice; + t_ab8500_codec_cr55_sldai5toslado5 cr55_sldai5toslado5; + t_ab8500_codec_cr51_to_cr56_sltoda cr55_sltoda5; + + /* CR56 */ + t_ab8500_codec_cr56_sldai6toslado7 cr56_sldai6toslado7; + t_ab8500_codec_cr51_to_cr56_sltoda cr56_sltoda6; + + /* CR57 */ + t_ab8500_codec_cr57_bfifull_msk cr57_bfifull_msk; + t_ab8500_codec_cr57_bfiempt_msk cr57_bfiempt_msk; + t_ab8500_codec_cr57_dachan_msk cr57_dachan_msk; + t_ab8500_codec_cr57_gain_msk cr57_gain_msk; + t_ab8500_codec_cr57_dspad_msk cr57_dspad_msk; + t_ab8500_codec_cr57_dspda_msk cr57_dspda_msk; + t_ab8500_codec_cr57_stfir_msk cr57_stfir_msk; + + /* CR58 */ + t_ab8500_codec_cr58_bfifull_ev cr58_bfifull_ev; + t_ab8500_codec_cr58_bfiempt_ev cr58_bfiempt_ev; + t_ab8500_codec_cr58_dachan_ev cr58_dachan_ev; + t_ab8500_codec_cr58_gain_ev cr58_gain_ev; + t_ab8500_codec_cr58_dspad_ev cr58_dspad_ev; + t_ab8500_codec_cr58_dspda_ev cr58_dspda_ev; + t_ab8500_codec_cr58_stfir_ev cr58_stfir_ev; + + /* CR59 */ + t_ab8500_codec_cr59_vssready_msk cr59_vssready_msk; + t_ab8500_codec_cr59_shrtvibl_msk cr59_shrtvibl_msk; + t_ab8500_codec_cr59_shrtvibr_msk cr59_shrtvibr_msk; + t_ab8500_codec_cr59_shrthfl_msk cr59_shrthfl_msk; + t_ab8500_codec_cr59_shrthfr_msk cr59_shrthfr_msk; + t_ab8500_codec_cr59_shrthsl_msk cr59_shrthsl_msk; + t_ab8500_codec_cr59_shrthsr_msk cr59_shrthsr_msk; + t_ab8500_codec_cr59_shrtear_msk cr59_shrtear_msk; + + /* CR60 */ + t_ab8500_codec_cr60_vssready_ev cr60_vssready_ev; + t_ab8500_codec_cr60_shrtvibl_ev cr60_shrtvibl_ev; + t_ab8500_codec_cr60_shrtvibr_ev cr60_shrtvibr_ev; + t_ab8500_codec_cr60_shrthfl_ev cr60_shrthfl_ev; + t_ab8500_codec_cr60_shrthfr_ev cr60_shrthfr_ev; + t_ab8500_codec_cr60_shrthsl_ev cr60_shrthsl_ev; + t_ab8500_codec_cr60_shrthsr_ev cr60_shrthsr_ev; + t_ab8500_codec_cr60_shrtear_ev cr60_shrtear_ev; + + /* CR61 */ + t_ab8500_codec_cr61_revision cr61_revision; + t_ab8500_codec_cr61_fade_speed cr61_fade_speed; + + /* CR62 */ + t_ab8500_codec_cr62_dmic1sinc3 cr62_dmic1sinc3; + t_ab8500_codec_cr62_dmic2sinc3 cr62_dmic2sinc3; + t_ab8500_codec_cr62_dmic3sinc3 cr62_dmic3sinc3; + t_ab8500_codec_cr62_dmic4sinc3 cr62_dmic4sinc3; + t_ab8500_codec_cr62_dmic5sinc3 cr62_dmic5sinc3; + t_ab8500_codec_cr62_dmic6sinc3 cr62_dmic6sinc3; + + /* CR63 */ + t_ab8500_codec_cr63_datohslen cr63_datohslen; + t_ab8500_codec_cr63_datohsren cr63_datohsren; + t_ab8500_codec_cr63_ad1sel cr63_ad1sel; + t_ab8500_codec_cr63_ad2sel cr63_ad2sel; + t_ab8500_codec_cr63_ad3sel cr63_ad3sel; + t_ab8500_codec_cr63_ad5sel cr63_ad5sel; + t_ab8500_codec_cr63_ad6sel cr63_ad6sel; + t_ab8500_codec_cr63_ancsel cr63_ancsel; + + /* CR64 */ + t_ab8500_codec_cr64_datohfren cr64_datohfren; + t_ab8500_codec_cr64_datohflen cr64_datohflen; + t_ab8500_codec_cr64_hfrsel cr64_hfrsel; + t_ab8500_codec_cr64_hflsel cr64_hflsel; + t_ab8500_codec_cr64_stfir1sel cr64_stfir1sel; + t_ab8500_codec_cr64_stfir2sel cr64_stfir2sel; + + /* CR65 */ + t_ab8500_codec_cr65_fadedis_ad1 cr65_fadedis_ad1; + t_ab8500_codec_cr65_ad1gain cr65_ad1gain; + + /* CR66 */ + t_ab8500_codec_cr66_fadedis_ad2 cr66_fadedis_ad2; + t_ab8500_codec_cr66_ad2gain cr66_ad2gain; + + /* CR67 */ + t_ab8500_codec_cr67_fadedis_ad3 cr67_fadedis_ad3; + t_ab8500_codec_cr67_ad3gain cr67_ad3gain; + + /* CR68 */ + t_ab8500_codec_cr68_fadedis_ad4 cr68_fadedis_ad4; + t_ab8500_codec_cr68_ad4gain cr68_ad4gain; + + /* CR69 */ + t_ab8500_codec_cr69_fadedis_ad5 cr69_fadedis_ad5; + t_ab8500_codec_cr69_ad5gain cr69_ad5gain; + + /* CR70 */ + t_ab8500_codec_cr70_fadedis_ad6 cr70_fadedis_ad6; + t_ab8500_codec_cr70_ad6gain cr70_ad6gain; + + /* CR71 */ + t_ab8500_codec_cr71_fadedis_da1 cr71_fadedis_da1; + t_ab8500_codec_cr71_da1gain cr71_da1gain; + + /* CR72 */ + t_ab8500_codec_cr72_fadedis_da2 cr72_fadedis_da2; + t_ab8500_codec_cr72_da2gain cr72_da2gain; + + /* CR73 */ + t_ab8500_codec_cr73_fadedis_da3 cr73_fadedis_da3; + t_ab8500_codec_cr73_da3gain cr73_da3gain; + + /* CR74 */ + t_ab8500_codec_cr74_fadedis_da4 cr74_fadedis_da4; + t_ab8500_codec_cr74_da4gain cr74_da4gain; + + /* CR75 */ + t_ab8500_codec_cr75_fadedis_da5 cr75_fadedis_da5; + t_ab8500_codec_cr75_da5gain cr75_da5gain; + + /* CR76 */ + t_ab8500_codec_cr76_fadedis_da6 cr76_fadedis_da6; + t_ab8500_codec_cr76_da6gain cr76_da6gain; + + /* CR77 */ + t_ab8500_codec_cr77_fadedis_ad1l cr77_fadedis_ad1l; + t_ab8500_codec_cr77_ad1lbgain_to_hfl cr77_ad1lbgain_to_hfl; + + /* CR78 */ + t_ab8500_codec_cr78_fadedis_ad2l cr78_fadedis_ad2l; + t_ab8500_codec_cr78_ad2lbgain_to_hfr cr78_ad2lbgain_to_hfr; + + /* CR79 */ + t_ab8500_codec_cr79_hssinc1 cr79_hssinc1; + t_ab8500_codec_cr79_fadedis_hsl cr79_fadedis_hsl; + t_ab8500_codec_cr79_hsldgain cr79_hsldgain; + + /* CR80 */ + t_ab8500_codec_cr80_fadedis_hsr cr80_fadedis_hsr; + t_ab8500_codec_cr80_hsrdgain cr80_hsrdgain; + + /* CR81 */ + t_ab8500_codec_cr81_stfir1gain cr81_stfir1gain; + + /* CR82 */ + t_ab8500_codec_cr82_stfir2gain cr82_stfir2gain; + + /* CR83 */ + t_ab8500_codec_cr83_enanc cr83_enanc; + t_ab8500_codec_cr83_anciirinit cr83_anciirinit; + t_ab8500_codec_cr83_ancfirupdate cr83_ancfirupdate; + + /* CR84 */ + t_ab8500_codec_cr84_ancinshift cr84_ancinshift; + + /* CR85 */ + t_ab8500_codec_cr85_ancfiroutshift cr85_ancfiroutshift; + + /* CR86 */ + t_ab8500_codec_cr86_ancshiftout cr86_ancshiftout; + + /* CR87 */ + t_ab8500_codec_cr87_ancfircoeff_msb cr87_ancfircoeff_msb; + + /* CR88 */ + t_ab8500_codec_cr88_ancfircoeff_lsb cr88_ancfircoeff_lsb; + + /* CR89 */ + t_ab8500_codec_cr89_anciircoeff_msb cr89_anciircoeff_msb; + + /* CR90 */ + t_ab8500_codec_cr90_anciircoeff_lsb cr90_anciircoeff_lsb; + + /* CR91 */ + t_ab8500_codec_cr91_ancwarpdel_msb cr91_ancwarpdel_msb; + + /* CR92 */ + t_ab8500_codec_cr92_ancwarpdel_lsb cr92_ancwarpdel_lsb; + + /* CR93 */ + t_ab8500_codec_cr93_ancfirpeak_msb cr93_ancfirpeak_msb; + + /* CR94 */ + t_ab8500_codec_cr94_ancfirpeak_lsb cr94_ancfirpeak_lsb; + + /* CR95 */ + t_ab8500_codec_cr95_anciirpeak_msb cr95_anciirpeak_msb; + + /* CR96 */ + t_ab8500_codec_cr96_anciirpeak_lsb cr96_anciirpeak_lsb; + + /* CR97 */ + t_ab8500_codec_cr97_stfir_set cr97_stfir_set; + t_ab8500_codec_cr97_stfir_addr cr97_stfir_addr; + + /* CR98 */ + t_ab8500_codec_cr98_stfir_coeff_msb cr98_stfir_coeff_msb; + + /* CR99 */ + t_ab8500_codec_cr99_stfir_coeff_lsb cr99_stfir_coeff_lsb; + + /* CR100 */ + t_ab8500_codec_cr100_enstfirs cr100_enstfirs; + t_ab8500_codec_cr100_stfirstoif1 cr100_stfirstoif1; + t_ab8500_codec_cr100_stfir_busy cr100_stfir_busy; + + /* CR101 */ + t_ab8500_codec_cr101_parlhf cr101_parlhf; + t_ab8500_codec_cr101_parlvib cr101_parlvib; + t_ab8500_codec_cr101_classd_viblswapen cr101_classd_viblswapen; + t_ab8500_codec_cr101_classd_vibrswapen cr101_classd_vibrswapen; + t_ab8500_codec_cr101_classd_hflswapen cr101_classd_hflswapen; + t_ab8500_codec_cr101_classd_hfrswapen cr101_classd_hfrswapen; + + /* CR102 */ + t_ab8500_codec_cr102_classd_firbyp cr102_classd_firbyp; + t_ab8500_codec_cr102_classd_highvolen cr102_classd_highvolen; + + /* CR103 */ + t_ab8500_codec_cr103_classd_ditherhpgain cr103_classd_ditherhpgain; + t_ab8500_codec_cr103_classd_ditherwgain cr103_classd_ditherwgain; + + /* CR104 */ + t_ab8500_codec_cr104_bfifoint cr104_bfifoint; + + /* CR105 */ + t_ab8500_codec_cr105_bfifotx cr105_bfifotx; + + /* CR106 */ + t_ab8500_codec_cr106_bfifofsext cr106_bfifofsext; + t_ab8500_codec_cr106_bfifomsk cr106_bfifomsk; + t_ab8500_codec_cr106_bfifomstr cr106_bfifomstr; + t_ab8500_codec_cr106_bfifostrt cr106_bfifostrt; + + /* CR107 */ + t_ab8500_codec_cr107_bfifosampnr cr107_bfifosampnr; + + /* CR108 */ + t_ab8500_codec_cr108_bfifowakeup cr108_bfifowakeup; + + /* CR109 */ + t_ab8500_codec_cr109_bfifosamples cr109_bfifosamples; + +} t_ab8500_codec_configuration; + +typedef enum { + AB8500_CODEC_DIRECTION_IN, + AB8500_CODEC_DIRECTION_OUT, + AB8500_CODEC_DIRECTION_INOUT +} t_ab8500_codec_direction; + +typedef enum { + AB8500_CODEC_MODE_HIFI, + AB8500_CODEC_MODE_VOICE, + AB8500_CODEC_MODE_MANUAL_SETTING +} t_ab8500_codec_mode; + +typedef enum { + AB8500_CODEC_AUDIO_INTERFACE_0, + AB8500_CODEC_AUDIO_INTERFACE_1 +} t_ab8500_codec_audio_interface; + +typedef enum { + AB8500_CODEC_SRC_LINEIN, + AB8500_CODEC_SRC_MICROPHONE_1A, + AB8500_CODEC_SRC_MICROPHONE_1B, + AB8500_CODEC_SRC_MICROPHONE_2, + AB8500_CODEC_SRC_D_MICROPHONE_1, + AB8500_CODEC_SRC_D_MICROPHONE_2, + AB8500_CODEC_SRC_D_MICROPHONE_3, + AB8500_CODEC_SRC_D_MICROPHONE_4, + AB8500_CODEC_SRC_D_MICROPHONE_5, + AB8500_CODEC_SRC_D_MICROPHONE_6, + AB8500_CODEC_SRC_FM_RX, + AB8500_CODEC_SRC_ALL +} t_ab8500_codec_src; + +typedef enum { + AB8500_CODEC_DEST_HEADSET, + AB8500_CODEC_DEST_EARPIECE, + AB8500_CODEC_DEST_HANDSFREE, + AB8500_CODEC_DEST_VIBRATOR_L, + AB8500_CODEC_DEST_VIBRATOR_R, + AB8500_CODEC_DEST_ALL +} t_ab8500_codec_dest; + +typedef struct { + t_uint8 slave_address_of_ab8500_codec; + t_ab8500_codec_direction ab8500_codec_direction; + t_ab8500_codec_mode ab8500_codec_mode_in; + t_ab8500_codec_mode ab8500_codec_mode_out; + t_ab8500_codec_audio_interface audio_interface; + t_ab8500_codec_src ab8500_codec_src; + t_ab8500_codec_dest ab8500_codec_dest; + t_uint8 in_left_volume; + t_uint8 in_right_volume; + t_uint8 out_left_volume; + t_uint8 out_right_volume; + + t_ab8500_codec_configuration ab8500_codec_configuration; +} t_ab8500_codec_system_context; +#endif /* _AB8500_CODECP_H_ */ + +/* End of file AB8500_CODECP.h */ diff --git a/arch/arm/mach-ux500/include/mach/ab8500_codec_p_v1_0.h b/arch/arm/mach-ux500/include/mach/ab8500_codec_p_v1_0.h new file mode 100644 index 00000000000..866cd0c80f1 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ab8500_codec_p_v1_0.h @@ -0,0 +1,3037 @@ +/*****************************************************************************/ +/** +* © ST-Ericsson, 2009 - All rights reserved +* Reproduction and Communication of this document is strictly prohibited +* unless specifically authorized in writing by ST-Ericsson +* +* \brief Private Header file for AB8500 CODEC +* \author ST-Ericsson +*/ +/*****************************************************************************/ + +#ifndef _AB8500_CODECP_V1_0_H_ +#define _AB8500_CODECP_V1_0_H_ + +/*---------------------------------------------------------------------------- + * Includes + *---------------------------------------------------------------------------*/ +#include "hcl_defs.h" + +#define AB8500_CODEC_HCL_VERSION_ID 3 +#define AB8500_CODEC_HCL_MAJOR_ID 0 +#define AB8500_CODEC_HCL_MINOR_ID 0 + +#define AB8500_CODEC_MASK_ONE_BIT 0x1UL +#define AB8500_CODEC_MASK_TWO_BITS 0x3UL +#define AB8500_CODEC_MASK_THREE_BITS 0x7UL +#define AB8500_CODEC_MASK_FOUR_BITS 0xFUL +#define AB8500_CODEC_MASK_FIVE_BITS 0x1FUL +#define AB8500_CODEC_MASK_SIX_BITS 0x3FUL +#define AB8500_CODEC_MASK_SEVEN_BITS 0x7FUL +#define AB8500_CODEC_MASK_EIGHT_BITS 0xFFUL + +#define AB8500_CODEC_WRITE_BITS(reg, val, bit_nb, pos) (reg) = ((t_uint8) ((((reg) & (~(bit_nb << pos))) | (((val) & bit_nb) << pos)))) + +#define AB8500_CODEC_BLOCK 0x0D + +#define AB8500_CODEC_MASK_TWO_MS_BITS 0xC0UL +#define AB8500_CODEC_MASK_SIX_LS_BITS 0x3FUL + +/* Genepi AudioCodec Control Registers */ + +#define AB8500_CODEC_CR0 0x00 +#define AB8500_CODEC_CR1 0x01 +#define AB8500_CODEC_CR2 0x02 +#define AB8500_CODEC_CR3 0x03 +#define AB8500_CODEC_CR4 0x04 +#define AB8500_CODEC_CR5 0x05 +#define AB8500_CODEC_CR6 0x06 +#define AB8500_CODEC_CR7 0x07 +#define AB8500_CODEC_CR8 0x08 +#define AB8500_CODEC_CR9 0x09 +#define AB8500_CODEC_CR10 0x0A +#define AB8500_CODEC_CR11 0x0B +#define AB8500_CODEC_CR12 0x0C +#define AB8500_CODEC_CR13 0x0D +#define AB8500_CODEC_CR14 0x0E +#define AB8500_CODEC_CR15 0x0F +#define AB8500_CODEC_CR16 0x10 +#define AB8500_CODEC_CR17 0x11 +#define AB8500_CODEC_CR18 0x12 +#define AB8500_CODEC_CR19 0x13 +#define AB8500_CODEC_CR20 0x14 +#define AB8500_CODEC_CR21 0x15 +#define AB8500_CODEC_CR22 0x16 +#define AB8500_CODEC_CR23 0x17 +#define AB8500_CODEC_CR24 0x18 +#define AB8500_CODEC_CR25 0x19 +#define AB8500_CODEC_CR26 0x1A +#define AB8500_CODEC_CR27 0x1B +#define AB8500_CODEC_CR28 0x1C +#define AB8500_CODEC_CR29 0x1D +#define AB8500_CODEC_CR30 0x1E +#define AB8500_CODEC_CR31 0x1F +#define AB8500_CODEC_CR32 0x20 +#define AB8500_CODEC_CR33 0x21 +#define AB8500_CODEC_CR34 0x22 +#define AB8500_CODEC_CR35 0x23 +#define AB8500_CODEC_CR36 0x24 +#define AB8500_CODEC_CR37 0x25 +#define AB8500_CODEC_CR38 0x26 +#define AB8500_CODEC_CR39 0x27 +#define AB8500_CODEC_CR40 0x28 +#define AB8500_CODEC_CR41 0x29 +#define AB8500_CODEC_CR42 0x2A +#define AB8500_CODEC_CR43 0x2B +#define AB8500_CODEC_CR44 0x2C +#define AB8500_CODEC_CR45 0x2D +#define AB8500_CODEC_CR46 0x2E +#define AB8500_CODEC_CR47 0x2F +#define AB8500_CODEC_CR48 0x30 +#define AB8500_CODEC_CR49 0x31 +#define AB8500_CODEC_CR50 0x32 +#define AB8500_CODEC_CR51 0x33 +#define AB8500_CODEC_CR52 0x34 +#define AB8500_CODEC_CR53 0x35 +#define AB8500_CODEC_CR54 0x36 +#define AB8500_CODEC_CR55 0x37 +#define AB8500_CODEC_CR56 0x38 +#define AB8500_CODEC_CR57 0x39 +#define AB8500_CODEC_CR58 0x3A +#define AB8500_CODEC_CR59 0x3B +#define AB8500_CODEC_CR60 0x3C +#define AB8500_CODEC_CR61 0x3D +#define AB8500_CODEC_CR62 0x3E +#define AB8500_CODEC_CR63 0x3F +#define AB8500_CODEC_CR64 0x40 +#define AB8500_CODEC_CR65 0x41 +#define AB8500_CODEC_CR66 0x42 +#define AB8500_CODEC_CR67 0x43 +#define AB8500_CODEC_CR68 0x44 +#define AB8500_CODEC_CR69 0x45 +#define AB8500_CODEC_CR70 0x46 +#define AB8500_CODEC_CR71 0x47 +#define AB8500_CODEC_CR72 0x48 +#define AB8500_CODEC_CR73 0x49 +#define AB8500_CODEC_CR74 0x4A +#define AB8500_CODEC_CR75 0x4B +#define AB8500_CODEC_CR76 0x4C +#define AB8500_CODEC_CR77 0x4D +#define AB8500_CODEC_CR78 0x4E +#define AB8500_CODEC_CR79 0x4F +#define AB8500_CODEC_CR80 0x50 +#define AB8500_CODEC_CR81 0x51 +#define AB8500_CODEC_CR82 0x52 +#define AB8500_CODEC_CR83 0x53 +#define AB8500_CODEC_CR84 0x54 +#define AB8500_CODEC_CR85 0x55 +#define AB8500_CODEC_CR86 0x56 +#define AB8500_CODEC_CR87 0x57 +#define AB8500_CODEC_CR88 0x58 +#define AB8500_CODEC_CR89 0x59 +#define AB8500_CODEC_CR90 0x5A +#define AB8500_CODEC_CR91 0x5B +#define AB8500_CODEC_CR92 0x5C +#define AB8500_CODEC_CR93 0x5D +#define AB8500_CODEC_CR94 0x5E +#define AB8500_CODEC_CR95 0x5F +#define AB8500_CODEC_CR96 0x60 +#define AB8500_CODEC_CR97 0x61 +#define AB8500_CODEC_CR98 0x62 +#define AB8500_CODEC_CR99 0x63 +#define AB8500_CODEC_CR100 0x64 +#define AB8500_CODEC_CR101 0x65 +#define AB8500_CODEC_CR102 0x66 +#define AB8500_CODEC_CR103 0x67 +#define AB8500_CODEC_CR104 0x68 +#define AB8500_CODEC_CR105 0x69 +#define AB8500_CODEC_CR106 0x6A +#define AB8500_CODEC_CR107 0x6B +#define AB8500_CODEC_CR108 0x6C +#define AB8500_CODEC_CR109 0x6D +#define AB8500_CODEC_CR110 0x6E +#define AB8500_CODEC_CR111 0x6F + +/* CR0-CR0x0000 */ +#define AB8500_CODEC_CR0_POWERUP 7 +#define AB8500_CODEC_CR0_ENAANA 3 + +/* CR1-CR0x0001 */ +#define AB8500_CODEC_CR1_SWRESET 7 + +/* CR2-CR0x0002 */ +#define AB8500_CODEC_CR2_ENAD1 7 +#define AB8500_CODEC_CR2_ENAD2 6 +#define AB8500_CODEC_CR2_ENAD3 5 +#define AB8500_CODEC_CR2_ENAD4 4 +#define AB8500_CODEC_CR2_ENAD5 3 +#define AB8500_CODEC_CR2_ENAD6 2 + +/* CR3-CR0x0003 */ +#define AB8500_CODEC_CR3_ENDA1 7 +#define AB8500_CODEC_CR3_ENDA2 6 +#define AB8500_CODEC_CR3_ENDA3 5 +#define AB8500_CODEC_CR3_ENDA4 4 +#define AB8500_CODEC_CR3_ENDA5 3 +#define AB8500_CODEC_CR3_ENDA6 2 + +/* CR4-CR0x0004 */ +#define AB8500_CODEC_CR4_LOWPOWHS 7 +#define AB8500_CODEC_CR4_LOWPOWDACHS 5 +#define AB8500_CODEC_CR4_LOWPOWEAR 4 +#define AB8500_CODEC_CR4_EAR_SEL_CM 2 +#define AB8500_CODEC_CR4_HS_HP_EN 1 + +/* CR5-CR0x0005 */ +#define AB8500_CODEC_CR5_ENMIC1 7 +#define AB8500_CODEC_CR5_ENMIC2 6 +#define AB8500_CODEC_CR5_ENLINL 5 +#define AB8500_CODEC_CR5_ENLINR 4 +#define AB8500_CODEC_CR5_MUTMIC1 3 +#define AB8500_CODEC_CR5_MUTMIC2 2 +#define AB8500_CODEC_CR5_MUTELINL 1 +#define AB8500_CODEC_CR5_MUTELINR 0 + +/* CR6-CR0x0006 */ +#define AB8500_CODEC_CR6_ENDMIC1 7 +#define AB8500_CODEC_CR6_ENDMIC2 6 +#define AB8500_CODEC_CR6_ENDMIC3 5 +#define AB8500_CODEC_CR6_ENDMIC4 4 +#define AB8500_CODEC_CR6_ENDMIC5 3 +#define AB8500_CODEC_CR6_ENDMIC6 2 + +/* CR7-CR0x0007 */ +#define AB8500_CODEC_CR7_MIC1SEL 7 +#define AB8500_CODEC_CR7_LINRSEL 6 +#define AB8500_CODEC_CR7_ENDRVHSL 5 +#define AB8500_CODEC_CR7_ENDRVHSR 4 +#define AB8500_CODEC_CR7_ENADCMIC 2 +#define AB8500_CODEC_CR7_ENADCLINL 1 +#define AB8500_CODEC_CR7_ENADCLINR 0 + +/* CR8-CR0x0008 */ +#define AB8500_CODEC_CR8_CP_DIS_PLDWN 7 +#define AB8500_CODEC_CR8_ENEAR 6 +#define AB8500_CODEC_CR8_ENHSL 5 +#define AB8500_CODEC_CR8_ENHSR 4 +#define AB8500_CODEC_CR8_ENHFL 3 +#define AB8500_CODEC_CR8_ENHFR 2 +#define AB8500_CODEC_CR8_ENVIBL 1 +#define AB8500_CODEC_CR8_ENVIBR 0 + +/* CR9-CR0x0009 */ +#define AB8500_CODEC_CR9_ENADACEAR 6 +#define AB8500_CODEC_CR9_ENADACHSL 5 +#define AB8500_CODEC_CR9_ENADACHSR 4 +#define AB8500_CODEC_CR9_ENADACHFL 3 +#define AB8500_CODEC_CR9_ENADACHFR 2 +#define AB8500_CODEC_CR9_ENADACVIBL 1 +#define AB8500_CODEC_CR9_ENADACVIBR 0 + +/* CR10-CR0x000A */ +#define AB8500_CODEC_CR10_MUTEEAR 6 +#define AB8500_CODEC_CR10_MUTEHSL 5 +#define AB8500_CODEC_CR10_MUTEHSR 4 + +/* CR11-CR0x000B */ +#define AB8500_CODEC_CR11_ENSHORTPWD 7 +#define AB8500_CODEC_CR11_EARSHORTDIS 6 +#define AB8500_CODEC_CR11_HSSHORTDIS 5 +#define AB8500_CODEC_CR11_HSPULLDEN 4 +#define AB8500_CODEC_CR11_HSOSCEN 2 +#define AB8500_CODEC_CR11_HSFADEN 1 +#define AB8500_CODEC_CR11_HSZCDDIS 0 + +/* CR12-CR0x000C */ +#define AB8500_CODEC_CR12_ENCPHS 7 +#define AB8500_CODEC_CR12_HSAUTOEN 0 + +/* CR13-CR0x000D */ +#define AB8500_CODEC_CR13_ENVDET_HTHRESH 4 +#define AB8500_CODEC_CR13_ENVDET_LTHRESH 0 + +/* CR14-CR0x000E */ +#define AB8500_CODEC_CR14_SMPSLVEN 7 +#define AB8500_CODEC_CR14_ENVDETSMPSEN 6 +#define AB8500_CODEC_CR14_CPLVEN 5 +#define AB8500_CODEC_CR14_ENVDETCPEN 4 +#define AB8500_CODEC_CR14_ENVDET_TIME 0 + +/* CR15-CR0x000F */ +#define AB8500_CODEC_CR15_PWMTOVIBL 7 +#define AB8500_CODEC_CR15_PWMTOVIBR 6 +#define AB8500_CODEC_CR15_PWMLCTRL 5 +#define AB8500_CODEC_CR15_PWMRCTRL 4 +#define AB8500_CODEC_CR15_PWMNLCTRL 3 +#define AB8500_CODEC_CR15_PWMPLCTRL 2 +#define AB8500_CODEC_CR15_PWMNRCTRL 1 +#define AB8500_CODEC_CR15_PWMPRCTRL 0 + +/* CR16-CR0x0010 */ +#define AB8500_CODEC_CR16_PWMNLPOL 7 +#define AB8500_CODEC_CR16_PWMNLDUTYCYCLE 0 + +/* CR17-CR0x0011 */ +#define AB8500_CODEC_CR17_PWMPLPOL 7 +#define AB8500_CODEC_CR17_PWMLPDUTYCYCLE 0 + +/* CR18-CR0x0012 */ +#define AB8500_CODEC_CR18_PWMNRPOL 7 +#define AB8500_CODEC_CR18_PWMNRDUTYCYCLE 0 + +/* CR19-CR0x0013 */ +#define AB8500_CODEC_CR19_PWMPRPOL 7 +#define AB8500_CODEC_CR19_PWMRPDUTYCYCLE 0 + +/* CR20-CR0x0014 */ +#define AB8500_CODEC_CR20_EN_SE_MIC1 7 +#define AB8500_CODEC_CR20_LOW_POW_MIC1 6 +#define AB8500_CODEC_CR20_MIC1_GAIN 0 + +/* CR21-CR0x0015 */ +#define AB8500_CODEC_CR21_EN_SE_MIC2 7 +#define AB8500_CODEC_CR21_LOW_POW_MIC2 6 +#define AB8500_CODEC_CR21_MIC2_GAIN 0 + +/* CR22-CR0x0016 */ +#define AB8500_CODEC_CR22_HSL_GAIN 4 +#define AB8500_CODEC_CR22_HSR_GAIN 0 + +/* CR23-CR0x0017 */ +#define AB8500_CODEC_CR23_LINL_GAIN 4 +#define AB8500_CODEC_CR23_LINR_GAIN 0 + +/* CR24-CR0x0018 */ +#define AB8500_CODEC_CR24_LINTOHSL_GAIN 0 + +/* CR25-CR0x0019 */ +#define AB8500_CODEC_CR25_LINTOHSR_GAIN 0 + +/* CR26-CR0x001A */ +#define AB8500_CODEC_CR26_AD1NH 7 +#define AB8500_CODEC_CR26_AD2NH 6 +#define AB8500_CODEC_CR26_AD3NH 5 +#define AB8500_CODEC_CR26_AD4NH 4 +#define AB8500_CODEC_CR26_AD1_VOICE 3 +#define AB8500_CODEC_CR26_AD2_VOICE 2 +#define AB8500_CODEC_CR26_AD3_VOICE 1 +#define AB8500_CODEC_CR26_AD4_VOICE 0 + +/* CR27-CR0x001B */ +#define AB8500_CODEC_CR27_EN_MASTGEN 7 +#define AB8500_CODEC_CR27_IF1_BITCLK_OSR 5 +#define AB8500_CODEC_CR27_ENFS_BITCLK1 4 +#define AB8500_CODEC_CR27_IF0_BITCLK_OSR 1 +#define AB8500_CODEC_CR27_ENFS_BITCLK0 0 + +/* CR28-CR0x001C */ +#define AB8500_CODEC_CR28_FSYNC0P 6 +#define AB8500_CODEC_CR28_BITCLK0P 5 +#define AB8500_CODEC_CR28_IF0DEL 4 +#define AB8500_CODEC_CR28_IF0FORMAT 2 +#define AB8500_CODEC_CR28_IF0WL 0 + +/* CR29-CR0x001D */ +#define AB8500_CODEC_CR29_IF0DATOIF1AD 7 +#define AB8500_CODEC_CR29_IF0CKTOIF1CK 6 +#define AB8500_CODEC_CR29_IF1MASTER 5 +#define AB8500_CODEC_CR29_IF1DATOIF0AD 3 +#define AB8500_CODEC_CR29_IF1CKTOIF0CK 2 +#define AB8500_CODEC_CR29_IF0MASTER 1 +#define AB8500_CODEC_CR29_IF0BFIFOEN 0 + +/* CR30-CR0x001E */ +#define AB8500_CODEC_CR30_FSYNC1P 6 +#define AB8500_CODEC_CR30_BITCLK1P 5 +#define AB8500_CODEC_CR30_IF1DEL 4 +#define AB8500_CODEC_CR30_IF1FORMAT 2 +#define AB8500_CODEC_CR30_IF1WL 0 + +/* CR31-CR0x001F */ +#define AB8500_CODEC_CR31_ADOTOSLOT1 4 +#define AB8500_CODEC_CR31_ADOTOSLOT0 0 + +/* CR32-CR0x0020 */ +#define AB8500_CODEC_CR32_ADOTOSLOT3 4 +#define AB8500_CODEC_CR32_ADOTOSLOT2 0 + +/* CR33-CR0x0021 */ +#define AB8500_CODEC_CR33_ADOTOSLOT5 4 +#define AB8500_CODEC_CR33_ADOTOSLOT4 0 + +/* CR34-CR0x0022 */ +#define AB8500_CODEC_CR34_ADOTOSLOT7 4 +#define AB8500_CODEC_CR34_ADOTOSLOT6 0 + +/* CR35-CR0x0023 */ +#define AB8500_CODEC_CR35_ADOTOSLOT9 4 +#define AB8500_CODEC_CR35_ADOTOSLOT8 0 + +/* CR36-CR0x0024 */ +#define AB8500_CODEC_CR36_ADOTOSLOT11 4 +#define AB8500_CODEC_CR36_ADOTOSLOT10 0 + +/* CR37-CR0x0025 */ +#define AB8500_CODEC_CR37_ADOTOSLOT13 4 +#define AB8500_CODEC_CR37_ADOTOSLOT12 0 + +/* CR38-CR0x0026 */ +#define AB8500_CODEC_CR38_ADOTOSLOT15 4 +#define AB8500_CODEC_CR38_ADOTOSLOT14 0 + +/* CR39-CR0x0027 */ +#define AB8500_CODEC_CR39_ADOTOSLOT17 4 +#define AB8500_CODEC_CR39_ADOTOSLOT16 0 + +/* CR40-CR0x0028 */ +#define AB8500_CODEC_CR40_ADOTOSLOT19 4 +#define AB8500_CODEC_CR40_ADOTOSLOT18 0 + +/* CR41-CR0x0029 */ +#define AB8500_CODEC_CR41_ADOTOSLOT21 4 +#define AB8500_CODEC_CR41_ADOTOSLOT20 0 + +/* CR42-CR0x002A */ +#define AB8500_CODEC_CR42_ADOTOSLOT23 4 +#define AB8500_CODEC_CR42_ADOTOSLOT22 0 + +/* CR43-CR0x002B */ +#define AB8500_CODEC_CR43_ADOTOSLOT25 4 +#define AB8500_CODEC_CR43_ADOTOSLOT24 0 + +/* CR44-CR0x002C */ +#define AB8500_CODEC_CR44_ADOTOSLOT27 4 +#define AB8500_CODEC_CR44_ADOTOSLOT26 0 + +/* CR45-CR0x002D */ +#define AB8500_CODEC_CR45_ADOTOSLOT29 4 +#define AB8500_CODEC_CR45_ADOTOSLOT28 0 + +/* CR46-CR0x002E */ +#define AB8500_CODEC_CR46_ADOTOSLOT31 4 +#define AB8500_CODEC_CR46_ADOTOSLOT30 0 + +/* CR47-CR0x002F */ +#define AB8500_CODEC_CR47_HIZ_SL7 7 +#define AB8500_CODEC_CR47_HIZ_SL6 6 +#define AB8500_CODEC_CR47_HIZ_SL5 5 +#define AB8500_CODEC_CR47_HIZ_SL4 4 +#define AB8500_CODEC_CR47_HIZ_SL3 3 +#define AB8500_CODEC_CR47_HIZ_SL2 2 +#define AB8500_CODEC_CR47_HIZ_SL1 1 +#define AB8500_CODEC_CR47_HIZ_SL0 0 + +/* CR48-CR0x0030 */ +#define AB8500_CODEC_CR48_HIZ_SL15 7 +#define AB8500_CODEC_CR48_HIZ_SL14 6 +#define AB8500_CODEC_CR48_HIZ_SL13 5 +#define AB8500_CODEC_CR48_HIZ_SL12 4 +#define AB8500_CODEC_CR48_HIZ_SL11 3 +#define AB8500_CODEC_CR48_HIZ_SL10 2 +#define AB8500_CODEC_CR48_HIZ_SL9 1 +#define AB8500_CODEC_CR48_HIZ_SL8 0 + +/* CR49-CR0x0031 */ +#define AB8500_CODEC_CR49_HIZ_SL23 7 +#define AB8500_CODEC_CR49_HIZ_SL22 6 +#define AB8500_CODEC_CR49_HIZ_SL21 5 +#define AB8500_CODEC_CR49_HIZ_SL20 4 +#define AB8500_CODEC_CR49_HIZ_SL19 3 +#define AB8500_CODEC_CR49_HIZ_SL18 2 +#define AB8500_CODEC_CR49_HIZ_SL17 1 +#define AB8500_CODEC_CR49_HIZ_SL16 0 + +/* CR50-CR0x0032 */ +#define AB8500_CODEC_CR50_HIZ_SL31 7 +#define AB8500_CODEC_CR50_HIZ_SL30 6 +#define AB8500_CODEC_CR50_HIZ_SL29 5 +#define AB8500_CODEC_CR50_HIZ_SL28 4 +#define AB8500_CODEC_CR50_HIZ_SL27 3 +#define AB8500_CODEC_CR50_HIZ_SL26 2 +#define AB8500_CODEC_CR50_HIZ_SL25 1 +#define AB8500_CODEC_CR50_HIZ_SL24 0 + +/* CR51-CR0x0033 */ +#define AB8500_CODEC_CR51_DA12_VOICE 7 +#define AB8500_CODEC_CR51_SWAP_DA12_34 6 +#define AB8500_CODEC_CR51_SLDAI7TOSLADO1 5 +#define AB8500_CODEC_CR51_SLTODA1 0 + +/* CR52-CR0x0034 */ +#define AB8500_CODEC_CR52_SLDAI8TOSLADO2 5 +#define AB8500_CODEC_CR52_SLTODA2 0 + +/* CR53-CR0x0035 */ +#define AB8500_CODEC_CR53_DA34_VOICE 7 +#define AB8500_CODEC_CR53_SLDAI7TOSLADO3 5 +#define AB8500_CODEC_CR53_SLTODA3 0 + +/* CR54-CR0x0036 */ +#define AB8500_CODEC_CR54_SLDAI8TOSLADO4 5 +#define AB8500_CODEC_CR54_SLTODA4 0 + +/* CR55-CR0x0037 */ +#define AB8500_CODEC_CR55_DA56_VOICE 7 +#define AB8500_CODEC_CR55_SLDAI7TOSLADO5 5 +#define AB8500_CODEC_CR55_SLTODA5 0 + +/* CR56-CR0x0038 */ +#define AB8500_CODEC_CR56_SLDAI8TOSLADO6 5 +#define AB8500_CODEC_CR56_SLTODA6 0 + +/* CR57-CR0x0039 */ +#define AB8500_CODEC_CR57_SLDAI8TOSLADO7 5 +#define AB8500_CODEC_CR57_SLTODA7 0 + +/* CR58-CR0x003A */ +#define AB8500_CODEC_CR58_SLDAI7TOSLADO8 5 +#define AB8500_CODEC_CR58_SLTODA8 0 + +/* CR59-CR0x003B */ +#define AB8500_CODEC_CR59_PARLHF 7 +#define AB8500_CODEC_CR59_PARLVIB 6 +#define AB8500_CODEC_CR59_CLASSDVIB1SWAPEN 3 +#define AB8500_CODEC_CR59_CLASSDVIB2SWAPEN 2 +#define AB8500_CODEC_CR59_CLASSDHFLSWAPEN 1 +#define AB8500_CODEC_CR59_CLASSDHFRSWAPEN 0 + +/* CR60-CR0x003C */ +#define AB8500_CODEC_CR60_CLASSD_FIR_BYP 4 +#define AB8500_CODEC_CR60_CLASSD_HIGHVOL_EN 0 + +/* CR61-CR0x003D */ +#define AB8500_CODEC_CR61_CLASSD_DITH_HPGAIN 4 +#define AB8500_CODEC_CR61_CLASSD_DITH_WGAIN 0 + +/* CR62-CR0x003E */ +#define AB8500_CODEC_CR62_DMIC1SINC3 5 +#define AB8500_CODEC_CR62_DMIC2SINC3 4 +#define AB8500_CODEC_CR62_DMIC3SINC3 3 +#define AB8500_CODEC_CR62_DMIC4SINC3 2 +#define AB8500_CODEC_CR62_DMIC5SINC3 1 +#define AB8500_CODEC_CR62_DMIC6SINC3 0 + +/* CR63-CR0x003F */ +#define AB8500_CODEC_CR63_DATOHSLEN 7 +#define AB8500_CODEC_CR63_DATOHSREN 6 +#define AB8500_CODEC_CR63_AD1SEL 5 +#define AB8500_CODEC_CR63_AD2SEL 4 +#define AB8500_CODEC_CR63_AD3SEL 3 +#define AB8500_CODEC_CR63_AD5SEL 2 +#define AB8500_CODEC_CR63_AD6SEL 1 +#define AB8500_CODEC_CR63_ANCSEL 0 + +/* CR64-CR0x0040 */ +#define AB8500_CODEC_CR64_DATOHFREN 7 +#define AB8500_CODEC_CR64_DATOHFLEN 6 +#define AB8500_CODEC_CR64_HFRSEL 5 +#define AB8500_CODEC_CR64_HFLSEL 4 +#define AB8500_CODEC_CR64_STFIR1SEL 2 +#define AB8500_CODEC_CR64_STFIR2SEL 0 + +/* CR65-CR0x0041 */ +#define AB8500_CODEC_CR65_FADEDIS_AD1 6 +#define AB8500_CODEC_CR65_AD1GAIN 0 + +/* CR66-CR0x0042 */ +#define AB8500_CODEC_CR66_FADEDIS_AD2 6 +#define AB8500_CODEC_CR66_AD2GAIN 0 + +/* CR67-CR0x0043 */ +#define AB8500_CODEC_CR67_FADEDIS_AD3 6 +#define AB8500_CODEC_CR67_AD3GAIN 0 + +/* CR68-CR0x0044 */ +#define AB8500_CODEC_CR68_FADEDIS_AD4 6 +#define AB8500_CODEC_CR68_AD4GAIN 0 + +/* CR69-CR0x0045 */ +#define AB8500_CODEC_CR69_FADEDIS_AD5 6 +#define AB8500_CODEC_CR69_AD5GAIN 0 + +/* CR70-CR0x0046 */ +#define AB8500_CODEC_CR70_FADEDIS_AD6 6 +#define AB8500_CODEC_CR70_AD6GAIN 0 + +/* CR71-CR0x0047 */ +#define AB8500_CODEC_CR71_FADEDIS_DA1 6 +#define AB8500_CODEC_CR71_DA1GAIN 0 + +/* CR72-CR0x0048 */ +#define AB8500_CODEC_CR72_FADEDIS_DA2 6 +#define AB8500_CODEC_CR72_DA2GAIN 0 + +/* CR73-CR0x0049 */ +#define AB8500_CODEC_CR73_FADEDIS_DA3 6 +#define AB8500_CODEC_CR73_DA3GAIN 0 + +/* CR74-CR0x004A */ +#define AB8500_CODEC_CR74_FADEDIS_DA4 6 +#define AB8500_CODEC_CR74_DA4GAIN 0 + +/* CR75-CR0x004B */ +#define AB8500_CODEC_CR75_FADEDIS_DA5 6 +#define AB8500_CODEC_CR75_DA5GAIN 0 + +/* CR76-CR0x004C */ +#define AB8500_CODEC_CR76_FADEDIS_DA6 6 +#define AB8500_CODEC_CR76_DA6GAIN 0 + +/* CR77-CR0x004D */ +#define AB8500_CODEC_CR77_FADEDIS_AD1L 6 +#define AB8500_CODEC_CR77_AD1LBGAIN 0 + +/* CR78-CR0x004E */ +#define AB8500_CODEC_CR78_FADEDIS_AD2L 6 +#define AB8500_CODEC_CR78_AD2LBGAIN 0 + +/* CR79-CR0x004F */ +#define AB8500_CODEC_CR79_HSSINC1 7 +#define AB8500_CODEC_CR79_FADEDIS_HSL 4 +#define AB8500_CODEC_CR79_HSLDGAIN 0 + +/* CR80-CR0x0050 */ +#define AB8500_CODEC_CR80_FADE_SPEED 6 +#define AB8500_CODEC_CR80_FADEDIS_HSR 4 +#define AB8500_CODEC_CR80_HSRDGAIN 0 + +/* CR81-CR0x0051 */ +#define AB8500_CODEC_CR81_STFIR1GAIN 0 + +/* CR82-CR0x0052 */ +#define AB8500_CODEC_CR82_STFIR2GAIN 0 + +/* CR83-CR0x0053 */ +#define AB8500_CODEC_CR83_ENANC 2 +#define AB8500_CODEC_CR83_ANCIIRINIT 1 +#define AB8500_CODEC_CR83_ANCFIRUPDATE 0 + +/* CR84-CR0x0054 */ +#define AB8500_CODEC_CR84_ANCINSHIFT 0 + +/* CR85-CR0x0055 */ +#define AB8500_CODEC_CR85_ANCFIROUTSHIFT 0 + +/* CR86-CR0x0056 */ +#define AB8500_CODEC_CR86_ANCSHIFTOUT 0 + +/* CR87-CR0x0057 */ +#define AB8500_CODEC_CR87_ANCFIRCOEFF_MSB 0 + +/* CR88-CR0x0058 */ +#define AB8500_CODEC_CR88_ANCFIRCOEFF_LSB 0 + +/* CR89-CR0x0059 */ +#define AB8500_CODEC_CR89_ANCIIRCOEFF_MSB 0 + +/* CR90-CR0x005A */ +#define AB8500_CODEC_CR90_ANCIIRCOEFF_LSB 0 + +/* CR91-CR0x005B */ +#define AB8500_CODEC_CR91_ANCWARPDEL_MSB 0 + +/* CR92-CR0x005C */ +#define AB8500_CODEC_CR92_ANCWARPDEL_LSB 0 + +/* CR93-CR0x005D */ +#define AB8500_CODEC_CR93_ANCFIRPEAK_MSB 0 + +/* CR94-CR0x005E */ +#define AB8500_CODEC_CR94_ANCFIRPEAK_LSB 0 + +/* CR95-CR0x005F */ +#define AB8500_CODEC_CR95_ANCIIRPEAK_MSB 0 + +/* CR96-CR0x0060 */ +#define AB8500_CODEC_CR96_ANCIIRPEAK_LSB 0 + +/* CR97-CR0x0061 */ +#define AB8500_CODEC_CR97_STFIR_SET 7 +#define AB8500_CODEC_CR97_STFIR_ADDR 0 + +/* CR98-CR0x0062 */ +#define AB8500_CODEC_CR98_STFIR_COEFF_MSB 0 + +/* CR99-CR0x0063 */ +#define AB8500_CODEC_CR99_STFIR_COEFF_LSB 0 + +/* CR100-CR0x0064 */ +#define AB8500_CODEC_CR100_ENSTFIRS 2 +#define AB8500_CODEC_CR100_STFIRSTOIF1 1 +#define AB8500_CODEC_CR100_STFIR_BUSY 0 + +/* CR101-CR0x0065 */ +#define AB8500_CODEC_CR101_HSOFFSTMASK 7 +#define AB8500_CODEC_CR101_FIFOFULLMASK 6 +#define AB8500_CODEC_CR101_FIFOEMPTYMASK 5 +#define AB8500_CODEC_CR101_DASATMASK 4 +#define AB8500_CODEC_CR101_ADSATMASK 3 +#define AB8500_CODEC_CR101_ADDSPMASK 2 +#define AB8500_CODEC_CR101_DADSPMASK 1 +#define AB8500_CODEC_CR101_FIRSIDMASK 0 + +/* CR102-CR0x0066 */ +#define AB8500_CODEC_CR102_IT_HSOFFST 7 +#define AB8500_CODEC_CR102_IT_FIFOFULL 6 +#define AB8500_CODEC_CR102_IT_FIFOEMPTY 5 +#define AB8500_CODEC_CR102_IT_DASAT 4 +#define AB8500_CODEC_CR102_IT_ADSAT 3 +#define AB8500_CODEC_CR102_IT_ADDSP 2 +#define AB8500_CODEC_CR102_IT_DADSP 1 +#define AB8500_CODEC_CR102_IT_FIRSID 0 + +/* CR103-CR0x0067 */ +#define AB8500_CODEC_CR103_VSSREADYMASK 7 +#define AB8500_CODEC_CR103_SHORTHSLMASK 2 +#define AB8500_CODEC_CR103_SHORTHSRMASK 1 +#define AB8500_CODEC_CR103_SHORTEARMASK 0 + +/* CR104-CR0x0068 */ +#define AB8500_CODEC_CR104_IT_VSSREADY 7 +#define AB8500_CODEC_CR104_IT_SHORTHSL 2 +#define AB8500_CODEC_CR104_IT_SHORTHSR 1 +#define AB8500_CODEC_CR104_IT_SHORTEAR 0 + +/* CR105-CR0x0069 */ +#define AB8500_CODEC_CR105_BFIFOMASK 7 +#define AB8500_CODEC_CR105_BFIFOINT 0 + +/* CR106-CR0x006A */ +#define AB8500_CODEC_CR106_BFIFOTX 0 + +/* CR107-CR0x006B */ +#define AB8500_CODEC_CR107_BFIFOEXSL 5 +#define AB8500_CODEC_CR107_PREBITCLK0 2 +#define AB8500_CODEC_CR107_BFIFOMAST 1 +#define AB8500_CODEC_CR107_BFIFORUN 0 + +/* CR108-CR0x006C */ +#define AB8500_CODEC_CR108_BFIFOFRAMESW 0 + +/* CR109-CR0x006D */ +#define AB8500_CODEC_CR109_BFIFOWAKEUP 0 + +/* CR110-CR0x006E */ +#define AB8500_CODEC_CR110_BFIFOSAMPLE 0 + +/* CR111-CR0x006F */ +#define AB8500_CODEC_CR111_AUD_IP_REV 0 + +/* For SetVolume API*/ +#define AB8500_CODEC_MAX_VOLUME 100 + +/* Analog MIC1 & MIC2 */ +#define AB8500_CODEC_MIC_VOLUME_MAX 31 +#define AB8500_CODEC_MIC_VOLUME_MEDIUM 15 +#define AB8500_CODEC_MIC_VOLUME_MIN 0 + +/* Line-in */ +#define AB8500_CODEC_LINEIN_VOLUME_MAX 15 +#define AB8500_CODEC_LINEIN_VOLUME_MEDIUM 7 +#define AB8500_CODEC_LINEIN_VOLUME_MIN 0 + +/* HeadSet */ +#define AB8500_CODEC_HEADSET_VOLUME_MAX 13 +#define AB8500_CODEC_HEADSET_VOLUME_MEDIUM 6 +#define AB8500_CODEC_HEADSET_VOLUME_MIN 0 + +/* HeadSet Digital */ +#define AB8500_CODEC_HEADSET_D_VOLUME_MAX 0 +#define AB8500_CODEC_HEADSET_D_VOLUME_MEDIUM 7 +#define AB8500_CODEC_HEADSET_D_VOLUME_MIN 15 +#define AB8500_CODEC_HEADSET_D_VOLUME_0DB 8 + +/* Digital AD Path */ +#define AB8500_CODEC_AD_D_VOLUME_MAX 0 +#define AB8500_CODEC_AD_D_VOLUME_MEDIUM 31 +#define AB8500_CODEC_AD_D_VOLUME_MIN 63 + +/* Digital DA Path */ +#define AB8500_CODEC_DA_D_VOLUME_MAX 0 +#define AB8500_CODEC_DA_D_VOLUME_MEDIUM 31 +#define AB8500_CODEC_DA_D_VOLUME_MIN 63 + +/* EarPiece Digital */ +#define AB8500_CODEC_EARPIECE_D_VOLUME_MAX 0 +#define AB8500_CODEC_EARPIECE_D_VOLUME_MEDIUM 7 +#define AB8500_CODEC_EARPIECE_D_VOLUME_MIN 15 + +/* AD1 loopback to HFL & HFR Digital */ +#define AB8500_CODEC_AD_LB_TO_HF_L_R_VOLUME_MAX 0 +#define AB8500_CODEC_AD_LB_TO_HF_L_R_VOLUME_MEDIUM 31 +#define AB8500_CODEC_AD_LB_TO_HF_L_R_VOLUME_MIN 63 + +/* Line-in to HSL & HSR */ +#define AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MAX 0 +#define AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MEDIUM 9 +#define AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN 18 +#define AB8500_CODEC_LINEIN_TO_HS_L_R_LOOP_OPEN 19 + +/* Vibrator */ +#define AB8500_CODEC_VIBRATOR_VOLUME_MAX 100 +#define AB8500_CODEC_VIBRATOR_VOLUME_MEDIUM 50 +#define AB8500_CODEC_VIBRATOR_VOLUME_MIN 0 + +/* CR0 - 7 */ +typedef enum { + AB8500_CODEC_CR0_POWERUP_OFF, + AB8500_CODEC_CR0_POWERUP_ON +} t_ab8500_codec_cr0_powerup; + +/* CR0 - 3 */ +typedef enum { + AB8500_CODEC_CR0_ENAANA_OFF, + AB8500_CODEC_CR0_ENAANA_ON +} t_ab8500_codec_cr0_enaana; + +/* CR1 - 7 */ +typedef enum { + AB8500_CODEC_CR1_SWRESET_DISABLED, + AB8500_CODEC_CR1_SWRESET_ENABLED +} t_ab8500_codec_cr1_swreset; + +/* CR2 - 7 */ +typedef enum { + AB8500_CODEC_CR2_ENAD1_DISABLED, + AB8500_CODEC_CR2_ENAD1_ENABLED +} t_ab8500_codec_cr2_enad1; + +/* CR2 - 6 */ +typedef enum { + AB8500_CODEC_CR2_ENAD2_DISABLED, + AB8500_CODEC_CR2_ENAD2_ENABLED +} t_ab8500_codec_cr2_enad2; + +/* CR2 - 5 */ +typedef enum { + AB8500_CODEC_CR2_ENAD3_DISABLED, + AB8500_CODEC_CR2_ENAD3_ENABLED +} t_ab8500_codec_cr2_enad3; + +/* CR2 - 4 */ +typedef enum { + AB8500_CODEC_CR2_ENAD4_DISABLED, + AB8500_CODEC_CR2_ENAD4_ENABLED +} t_ab8500_codec_cr2_enad4; + +/* CR2 - 3 */ +typedef enum { + AB8500_CODEC_CR2_ENAD5_DISABLED, + AB8500_CODEC_CR2_ENAD5_ENABLED +} t_ab8500_codec_cr2_enad5; + +/* CR2 - 2 */ +typedef enum { + AB8500_CODEC_CR2_ENAD6_DISABLED, + AB8500_CODEC_CR2_ENAD6_ENABLED +} t_ab8500_codec_cr2_enad6; + +/* CR3 - 7 */ +typedef enum { + AB8500_CODEC_CR3_ENDA1_DISABLED, + AB8500_CODEC_CR3_ENDA1_ENABLED +} t_ab8500_codec_cr3_enda1; + +/* CR3 - 6 */ +typedef enum { + AB8500_CODEC_CR3_ENDA2_DISABLED, + AB8500_CODEC_CR3_ENDA2_ENABLED +} t_ab8500_codec_cr3_enda2; + +/* CR3 - 5 */ +typedef enum { + AB8500_CODEC_CR3_ENDA3_DISABLED, + AB8500_CODEC_CR3_ENDA3_ENABLED +} t_ab8500_codec_cr3_enda3; + +/* CR3 - 4 */ +typedef enum { + AB8500_CODEC_CR3_ENDA4_DISABLED, + AB8500_CODEC_CR3_ENDA4_ENABLED +} t_ab8500_codec_cr3_enda4; + +/* CR3 - 3 */ +typedef enum { + AB8500_CODEC_CR3_ENDA5_DISABLED, + AB8500_CODEC_CR3_ENDA5_ENABLED +} t_ab8500_codec_cr3_enda5; + +/* CR3 - 2 */ +typedef enum { + AB8500_CODEC_CR3_ENDA6_DISABLED, + AB8500_CODEC_CR3_ENDA6_ENABLED +} t_ab8500_codec_cr3_enda6; + +/* CR4 - 7 */ +typedef enum { + AB8500_CODEC_CR4_LOWPOWHS_NORMAL, + AB8500_CODEC_CR4_LOWPOWHS_LP +} t_ab8500_codec_cr4_lowpowhs; + +/* CR4 - 6:5 */ +typedef enum { + AB8500_CODEC_CR4_LOWPOWDACHS_NORMAL, + AB8500_CODEC_CR4_LOWPOWDACHS_DRIVERS_LP, + AB8500_CODEC_CR4_LOWPOWDACHS_LP, + AB8500_CODEC_CR4_LOWPOWDACHS_BOTH_LP +} t_ab8500_codec_cr4_lowpowdachs; + +/* CR4 - 4 */ +typedef enum { + AB8500_CODEC_CR4_LOWPOWEAR_NORMAL, + AB8500_CODEC_CR4_LOWPOWEAR_LP +} t_ab8500_codec_cr4_lowpowear; + +/* CR4 - 3:2 */ +typedef enum { + AB8500_CODEC_CR4_EAR_SEL_CM_0_95V, + AB8500_CODEC_CR4_EAR_SEL_CM_1_1V, + AB8500_CODEC_CR4_EAR_SEL_CM_1_27V, + AB8500_CODEC_CR4_EAR_SEL_CM_1_58V +} t_ab8500_codec_cr4_ear_sel_cm; + +/* CR4 - 1 */ +typedef enum { + AB8500_CODEC_CR4_HS_HP_EN_FILTER_DISABLED, + AB8500_CODEC_CR4_HS_HP_EN_FILTER_ENABLED +} t_ab8500_codec_cr4_hs_hp_en; + +/* CR5 - 7 */ +typedef enum { + AB8500_CODEC_CR5_ENMIC1_DISABLED, + AB8500_CODEC_CR5_ENMIC1_ENABLED +} t_ab8500_codec_cr5_enmic1; + +/* CR5 - 6 */ +typedef enum { + AB8500_CODEC_CR5_ENMIC2_DISABLED, + AB8500_CODEC_CR5_ENMIC2_ENABLED +} t_ab8500_codec_cr5_enmic2; + +/* CR5 - 5 */ +typedef enum { + AB8500_CODEC_CR5_ENLINL_DISABLED, + AB8500_CODEC_CR5_ENLINL_ENABLED +} t_ab8500_codec_cr5_enlinl; + +/* CR5 - 4 */ +typedef enum { + AB8500_CODEC_CR5_ENLINR_DISABLED, + AB8500_CODEC_CR5_ENLINR_ENABLED +} t_ab8500_codec_cr5_enlinr; + +/* CR5 - 3 */ +typedef enum { + AB8500_CODEC_CR5_MUTMIC1_DISABLED, + AB8500_CODEC_CR5_MUTMIC1_ENABLED +} t_ab8500_codec_cr5_mutmic1; + +/* CR5 - 2 */ +typedef enum { + AB8500_CODEC_CR5_MUTMIC2_DISABLED, + AB8500_CODEC_CR5_MUTMIC2_ENABLED +} t_ab8500_codec_cr5_mutmic2; + +/* CR5 - 1 */ +typedef enum { + AB8500_CODEC_CR5_MUTLINL_DISABLED, + AB8500_CODEC_CR5_MUTLINL_ENABLED +} t_ab8500_codec_cr5_mutlinl; + +/* CR5 - 0 */ +typedef enum { + AB8500_CODEC_CR5_MUTLINR_DISABLED, + AB8500_CODEC_CR5_MUTLINR_ENABLED +} t_ab8500_codec_cr5_mutlinr; + +/* CR6 - 7 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC1_DISABLED, + AB8500_CODEC_CR6_ENDMIC1_ENABLED +} t_ab8500_codec_cr6_endmic1; + +/* CR6 - 6 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC2_DISABLED, + AB8500_CODEC_CR6_ENDMIC2_ENABLED +} t_ab8500_codec_cr6_endmic2; + +/* CR6 - 5 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC3_DISABLED, + AB8500_CODEC_CR6_ENDMIC3_ENABLED +} t_ab8500_codec_cr6_endmic3; + +/* CR6 - 4 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC4_DISABLED, + AB8500_CODEC_CR6_ENDMIC4_ENABLED +} t_ab8500_codec_cr6_endmic4; + +/* CR6 - 3 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC5_DISABLED, + AB8500_CODEC_CR6_ENDMIC5_ENABLED +} t_ab8500_codec_cr6_endmic5; + +/* CR6 - 2 */ +typedef enum { + AB8500_CODEC_CR6_ENDMIC6_DISABLED, + AB8500_CODEC_CR6_ENDMIC6_ENABLED +} t_ab8500_codec_cr6_endmic6; + +/* CR7 - 7 */ +typedef enum { + AB8500_CODEC_CR7_MIC1SEL_MIC1A, + AB8500_CODEC_CR7_MIC1SEL_MIC1B +} t_ab8500_codec_cr7_mic1sel; + +/* CR7 - 6 */ +typedef enum { + AB8500_CODEC_CR7_LINRSEL_MIC2, + AB8500_CODEC_CR7_LINRSEL_LINR +} t_ab8500_codec_cr7_linrsel; + +/* CR7 - 5 */ +typedef enum { + AB8500_CODEC_CR7_ENDRVHSL_DISABLED, + AB8500_CODEC_CR7_ENDRVHSL_ENABLED +} t_ab8500_codec_cr7_endrvhsl; + +/* CR7 - 4 */ +typedef enum { + AB8500_CODEC_CR7_ENDRVHSR_DISABLED, + AB8500_CODEC_CR7_ENDRVHSR_ENABLED +} t_ab8500_codec_cr7_endrvhsr; + +/* CR7 - 2 */ +typedef enum { + AB8500_CODEC_CR7_ENADCMIC_DISABLED, + AB8500_CODEC_CR7_ENADCMIC_ENABLED +} t_ab8500_codec_cr7_enadcmic; + +/* CR7 - 1 */ +typedef enum { + AB8500_CODEC_CR7_ENADCLINL_DISABLED, + AB8500_CODEC_CR7_ENADCLINL_ENABLED +} t_ab8500_codec_cr7_enadclinl; + +/* CR7 - 0 */ +typedef enum { + AB8500_CODEC_CR7_ENADCLINR_DISABLED, + AB8500_CODEC_CR7_ENADCLINR_ENABLED +} t_ab8500_codec_cr7_enadclinr; + +/* CR8 - 7 */ +typedef enum { + AB8500_CODEC_CR8_CP_DIS_PLDWN_ENABLED, + AB8500_CODEC_CR8_CP_DIS_PLDWN_DISABLED +} t_ab8500_codec_cr8_cp_dis_pldwn; + +/* CR8 - 6 */ +typedef enum { + AB8500_CODEC_CR8_ENEAR_DISABLED, + AB8500_CODEC_CR8_ENEAR_ENABLED +} t_ab8500_codec_cr8_enear; + +/* CR8 - 5 */ +typedef enum { + AB8500_CODEC_CR8_ENHSL_DISABLED, + AB8500_CODEC_CR8_ENHSL_ENABLED +} t_ab8500_codec_cr8_enhsl; + +/* CR8 - 4 */ +typedef enum { + AB8500_CODEC_CR8_ENHSR_DISABLED, + AB8500_CODEC_CR8_ENHSR_ENABLED +} t_ab8500_codec_cr8_enhsr; + +/* CR8 - 3 */ +typedef enum { + AB8500_CODEC_CR8_ENHFL_DISABLED, + AB8500_CODEC_CR8_ENHFL_ENABLED +} t_ab8500_codec_cr8_enhfl; + +/* CR8 - 2 */ +typedef enum { + AB8500_CODEC_CR8_ENHFR_DISABLED, + AB8500_CODEC_CR8_ENHFR_ENABLED +} t_ab8500_codec_cr8_enhfr; + +/* CR8 - 1 */ +typedef enum { + AB8500_CODEC_CR8_ENVIBL_DISABLED, + AB8500_CODEC_CR8_ENVIBL_ENABLED +} t_ab8500_codec_cr8_envibl; + +/* CR8 - 0 */ +typedef enum { + AB8500_CODEC_CR8_ENVIBR_DISABLED, + AB8500_CODEC_CR8_ENVIBR_ENABLED +} t_ab8500_codec_cr8_envibr; + +/* CR9 - 6 */ +typedef enum { + AB8500_CODEC_CR9_ENDACEAR_DISABLED, + AB8500_CODEC_CR9_ENDACEAR_ENABLED +} t_ab8500_codec_cr9_endacear; + +/* CR9 - 5 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHSL_DISABLED, + AB8500_CODEC_CR9_ENDACHSL_ENABLED +} t_ab8500_codec_cr9_endachsl; + +/* CR9 - 4 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHSR_DISABLED, + AB8500_CODEC_CR9_ENDACHSR_ENABLED +} t_ab8500_codec_cr9_endachsr; + +/* CR9 - 3 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHFL_DISABLED, + AB8500_CODEC_CR9_ENDACHFL_ENABLED +} t_ab8500_codec_cr9_endachfl; + +/* CR9 - 2 */ +typedef enum { + AB8500_CODEC_CR9_ENDACHFR_DISABLED, + AB8500_CODEC_CR9_ENDACHFR_ENABLED +} t_ab8500_codec_cr9_endachfr; + +/* CR9 - 1 */ +typedef enum { + AB8500_CODEC_CR9_ENDACVIBL_DISABLED, + AB8500_CODEC_CR9_ENDACVIBL_ENABLED +} t_ab8500_codec_cr9_endacvibl; + +/* CR9 - 0 */ +typedef enum { + AB8500_CODEC_CR9_ENDACVIBR_DISABLED, + AB8500_CODEC_CR9_ENDACVIBR_ENABLED +} t_ab8500_codec_cr9_endacvibr; + +/* CR10 - 6 */ +typedef enum { + AB8500_CODEC_CR10_MUTEEAR_DISABLED, + AB8500_CODEC_CR10_MUTEEAR_ENABLED +} t_ab8500_codec_cr10_muteear; + +/* CR10 - 5 */ +typedef enum { + AB8500_CODEC_CR10_MUTEHSL_DISABLED, + AB8500_CODEC_CR10_MUTEHSL_ENABLED +} t_ab8500_codec_cr10_mutehsl; + +/* CR10 - 4 */ +typedef enum { + AB8500_CODEC_CR10_MUTEHSR_DISABLED, + AB8500_CODEC_CR10_MUTEHSR_ENABLED +} t_ab8500_codec_cr10_mutehsr; + +/* CR11 - 7 */ +typedef enum { + AB8500_CODEC_CR11_ENSHORTPWD_DISABLED, + AB8500_CODEC_CR11_ENSHORTPWD_ENABLED +} t_ab8500_codec_cr11_enshortpwd; + +/* CR11 - 6 */ +typedef enum { + AB8500_CODEC_CR11_EARSHORTDIS_ENABLED, + AB8500_CODEC_CR11_EARSHORTDIS_DISABLED +} t_ab8500_codec_cr11_earshortdis; + +/* CR11 - 5 */ +typedef enum { + AB8500_CODEC_CR11_HSSHORTDIS_ENABLED, + AB8500_CODEC_CR11_HSSHORTDIS_DISABLED +} t_ab8500_codec_cr11_hsshortdis; + +/* CR11 - 4 */ +typedef enum { + AB8500_CODEC_CR11_HSPULLDEN_HIGH, + AB8500_CODEC_CR11_HSPULLDEN_DOWN +} t_ab8500_codec_cr11_hspullden; + +/* CR11 - 2 */ +typedef enum { + AB8500_CODEC_CR11_HSOSCEN_SYSTEMCLOCK, + AB8500_CODEC_CR11_HSOSCEN_LOCALOSC +} t_ab8500_codec_cr11_hsoscen; + +/* CR11 - 1 */ +typedef enum { + AB8500_CODEC_CR11_HSFADEN_FADING, + AB8500_CODEC_CR11_HSFADEN_IMMEDIATELY +} t_ab8500_codec_cr11_hsfaden; + +/* CR11 - 0 */ +typedef enum { + AB8500_CODEC_CR11_HSZCDDIS_ONZEROCROSS, + AB8500_CODEC_CR11_HSZCDDIS_WITHOUTZEROCROSS +} t_ab8500_codec_cr11_hszcddis; + +/* CR12 - 7 */ +typedef enum { + AB8500_CODEC_CR12_ENCPHS_DISABLED, + AB8500_CODEC_CR12_ENCPHS_ENABLED +} t_ab8500_codec_cr12_encphs; + +/* CR12 - 0 */ +typedef enum { + AB8500_CODEC_CR12_HSAUTOEN_DISABLED, + AB8500_CODEC_CR12_HSAUTOEN_ENABLED +} t_ab8500_codec_cr12_hsautoen; + +/* CR13 - 7:4 */ +typedef enum { + AB8500_CODEC_CR13_ENVDET_HTHRESH_25, + AB8500_CODEC_CR13_ENVDET_HTHRESH_50, + AB8500_CODEC_CR13_ENVDET_HTHRESH_100, + AB8500_CODEC_CR13_ENVDET_HTHRESH_150, + AB8500_CODEC_CR13_ENVDET_HTHRESH_200, + AB8500_CODEC_CR13_ENVDET_HTHRESH_250, + AB8500_CODEC_CR13_ENVDET_HTHRESH_300, + AB8500_CODEC_CR13_ENVDET_HTHRESH_350, + AB8500_CODEC_CR13_ENVDET_HTHRESH_400, + AB8500_CODEC_CR13_ENVDET_HTHRESH_450, + AB8500_CODEC_CR13_ENVDET_HTHRESH_500, + AB8500_CODEC_CR13_ENVDET_HTHRESH_550, + AB8500_CODEC_CR13_ENVDET_HTHRESH_600, + AB8500_CODEC_CR13_ENVDET_HTHRESH_650, + AB8500_CODEC_CR13_ENVDET_HTHRESH_700, + AB8500_CODEC_CR13_ENVDET_HTHRESH_750 +} t_ab8500_codec_cr13_envdet_hthresh; + +/* CR13 - 3:0 */ +typedef enum { + AB8500_CODEC_CR13_ENVDET_LTHRESH_25, + AB8500_CODEC_CR13_ENVDET_LTHRESH_50, + AB8500_CODEC_CR13_ENVDET_LTHRESH_100, + AB8500_CODEC_CR13_ENVDET_LTHRESH_150, + AB8500_CODEC_CR13_ENVDET_LTHRESH_200, + AB8500_CODEC_CR13_ENVDET_LTHRESH_250, + AB8500_CODEC_CR13_ENVDET_LTHRESH_300, + AB8500_CODEC_CR13_ENVDET_LTHRESH_350, + AB8500_CODEC_CR13_ENVDET_LTHRESH_400, + AB8500_CODEC_CR13_ENVDET_LTHRESH_450, + AB8500_CODEC_CR13_ENVDET_LTHRESH_500, + AB8500_CODEC_CR13_ENVDET_LTHRESH_550, + AB8500_CODEC_CR13_ENVDET_LTHRESH_600, + AB8500_CODEC_CR13_ENVDET_LTHRESH_650, + AB8500_CODEC_CR13_ENVDET_LTHRESH_700, + AB8500_CODEC_CR13_ENVDET_LTHRESH_750 +} t_ab8500_codec_cr13_envdet_lthresh; + +/* CR14 - 7 */ +typedef enum { + AB8500_CODEC_CR14_SMPSLVEN_HIGHVOLTAGE, + AB8500_CODEC_CR14_SMPSLVEN_LOWVOLTAGE +} t_ab8500_codec_cr14_smpslven; + +/* CR14 - 6 */ +typedef enum { + AB8500_CODEC_CR14_ENVDETSMPSEN_DISABLED, + AB8500_CODEC_CR14_ENVDETSMPSEN_ENABLED +} t_ab8500_codec_cr14_envdetsmpsen; + +/* CR14 - 5 */ +typedef enum { + AB8500_CODEC_CR14_CPLVEN_HIGHVOLTAGE, + AB8500_CODEC_CR14_CPLVEN_LOWVOLTAGE +} t_ab8500_codec_cr14_cplven; + +/* CR14 - 4 */ +typedef enum { + AB8500_CODEC_CR14_ENVDETCPEN_DISABLED, + AB8500_CODEC_CR14_ENVDETCPEN_ENABLED +} t_ab8500_codec_cr14_envdetcpen; + +/* CR14 - 3:0 */ +typedef enum { + AB8500_CODEC_CR14_ENVET_TIME_27USEC, + AB8500_CODEC_CR14_ENVET_TIME_53USEC, + AB8500_CODEC_CR14_ENVET_TIME_106USEC, + AB8500_CODEC_CR14_ENVET_TIME_212USEC, + AB8500_CODEC_CR14_ENVET_TIME_424USEC, + AB8500_CODEC_CR14_ENVET_TIME_848USEC, + AB8500_CODEC_CR14_ENVET_TIME_1MSEC, + AB8500_CODEC_CR14_ENVET_TIME_3MSEC, + AB8500_CODEC_CR14_ENVET_TIME_6MSEC, + AB8500_CODEC_CR14_ENVET_TIME_13MSEC, + AB8500_CODEC_CR14_ENVET_TIME_27MSEC, + AB8500_CODEC_CR14_ENVET_TIME_54MSEC, + AB8500_CODEC_CR14_ENVET_TIME_109MSEC, + AB8500_CODEC_CR14_ENVET_TIME_218MSEC, + AB8500_CODEC_CR14_ENVET_TIME_436MSEC, + AB8500_CODEC_CR14_ENVET_TIME_872MSEC, +} t_ab8500_codec_cr14_envet_time; + +/* CR15 - 7 */ +typedef enum { + AB8500_CODEC_CR15_PWMTOVIBL_DA_PATH, + AB8500_CODEC_CR15_PWMTOVIBL_PWM +} t_ab8500_codec_cr15_pwmtovibl; + +/* CR15 - 6 */ +typedef enum { + AB8500_CODEC_CR15_PWMTOVIBR_DA_PATH, + AB8500_CODEC_CR15_PWMTOVIBR_PWM +} t_ab8500_codec_cr15_pwmtovibr; + +/* CR15 - 5 */ +typedef enum { + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLGPOL, + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE +} t_ab8500_codec_cr15_pwmlctrl; + +/* CR15 - 4 */ +typedef enum { + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRGPOL, + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE +} t_ab8500_codec_cr15_pwmrctrl; + +/* CR15 - 3 */ +typedef enum { + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLGPOL, + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE +} t_ab8500_codec_cr15_pwmnlctrl; + +/* CR15 - 2 */ +typedef enum { + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLGPOL, + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE +} t_ab8500_codec_cr15_pwmplctrl; + +/* CR15 - 1 */ +typedef enum { + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRGPOL, + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE +} t_ab8500_codec_cr15_pwmnrctrl; + +/* CR15 - 0 */ +typedef enum { + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRGPOL, + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE +} t_ab8500_codec_cr15_pwmprctrl; + +/* CR16 - 7 */ +typedef enum { + AB8500_CODEC_CR16_PWMNLPOL_GNDVIB, + AB8500_CODEC_CR16_PWMNLPOL_VINVIB +} t_ab8500_codec_cr16_pwmnlpol; + +/* CR16 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr16_pwmnldutycycle; + +/* CR17 - 7 */ +typedef enum { + AB8500_CODEC_CR17_PWMPLPOL_GNDVIB, + AB8500_CODEC_CR17_PWMPLPOL_VINVIB +} t_ab8500_codec_cr17_pwmplpol; + +/* CR17 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr17_pwmpldutycycle; + +/* CR18 - 7 */ +typedef enum { + AB8500_CODEC_CR18_PWMNRPOL_GNDVIB, + AB8500_CODEC_CR18_PWMNRPOL_VINVIB +} t_ab8500_codec_cr18_pwmnrpol; + +/* CR18 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr18_pwmnrdutycycle; + +/* CR19 - 7 */ +typedef enum { + AB8500_CODEC_CR19_PWMPRPOL_GNDVIB, + AB8500_CODEC_CR19_PWMPRPOL_VINVIB +} t_ab8500_codec_cr19_pwmprpol; + +/* CR19 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr19_pwmprdutycycle; + +/* CR20 - 7 */ +typedef enum { + AB8500_CODEC_CR20_EN_SE_MIC1_DIFFERENTIAL, + AB8500_CODEC_CR20_EN_SE_MIC1_SINGLE +} t_ab8500_codec_cr20_en_se_mic1; + +/* CR20 - 6 */ +typedef enum { + AB8500_CODEC_CR20_LOW_POW_MIC1_NORMAL, + AB8500_CODEC_CR20_LOW_POW_MIC1_LOW_POWER +} t_ab8500_codec_cr20_low_pow_mic1; + +/* CR20 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr20_mic1_gain; + +/* CR21 - 7 */ +typedef enum { + AB8500_CODEC_CR21_EN_SE_MIC2_DIFFERENTIAL, + AB8500_CODEC_CR21_EN_SE_MIC2_SINGLE +} t_ab8500_codec_cr21_en_se_mic2; + +/* CR21 - 6 */ +typedef enum { + AB8500_CODEC_CR21_LOW_POW_MIC2_NORMAL, + AB8500_CODEC_CR21_LOW_POW_MIC2_LOW_POWER +} t_ab8500_codec_cr21_low_pow_mic2; + +/* CR21 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr21_mic2_gain; + +/* CR22 - 7:4 */ +typedef t_uint8 t_ab8500_codec_cr22_hsl_gain; + +/* CR22 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr22_hsr_gain; + +/* CR23 - 7:4 */ +typedef t_uint8 t_ab8500_codec_cr23_linl_gain; + +/* CR23 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr23_linr_gain; + +/* CR24 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr24_lintohsl_gain; + +/* CR25 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr25_lintohsr_gain; + +/* CR26 - 7 */ +typedef enum { + AB8500_CODEC_CR26_AD1NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD1NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad1nh; + +/* CR26 - 6 */ +typedef enum { + AB8500_CODEC_CR26_AD2NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD2NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad2nh; + +/* CR26 - 5 */ +typedef enum { + AB8500_CODEC_CR26_AD3NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD3NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad3nh; + +/* CR26 - 4 */ +typedef enum { + AB8500_CODEC_CR26_AD4NH_FILTER_ENABLED, + AB8500_CODEC_CR26_AD4NH_FILTER_DISABLED +} t_ab8500_codec_cr26_ad4nh; + +/* CR26 - 3 */ +typedef enum { + AB8500_CODEC_CR26_AD1_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD1_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad1_voice; + +/* CR26 - 2 */ +typedef enum { + AB8500_CODEC_CR26_AD2_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD2_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad2_voice; + +/* CR26 - 1 */ +typedef enum { + AB8500_CODEC_CR26_AD3_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD3_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad3_voice; + +/* CR26 - 0 */ +typedef enum { + AB8500_CODEC_CR26_AD4_VOICE_AUDIOFILTER, + AB8500_CODEC_CR26_AD4_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr26_ad4_voice; + +/* CR27 - 7 */ +typedef enum { + AB8500_CODEC_CR27_EN_MASTGEN_DISABLED, + AB8500_CODEC_CR27_EN_MASTGEN_ENABLED +} t_ab8500_codec_cr27_en_mastgen; + +/* CR27 - 6:5 */ +/* In ab8500_codec.h */ + +/* CR27 - 4 */ +typedef enum { + AB8500_CODEC_CR27_ENFS_BITCLK1_DISABLED, + AB8500_CODEC_CR27_ENFS_BITCLK1_ENABLED +} t_ab8500_codec_cr27_enfs_bitclk1; + +/* CR27 - 2:1 */ +/* In ab8500_codec.h */ + +/* CR27 - 0 */ +typedef enum { + AB8500_CODEC_CR27_ENFS_BITCLK0_DISABLED, + AB8500_CODEC_CR27_ENFS_BITCLK0_ENABLED +} t_ab8500_codec_cr27_enfs_bitclk0; + +/* CR28 - 6 */ +typedef enum { + AB8500_CODEC_CR28_FSYNC0P_RISING_EDGE, + AB8500_CODEC_CR28_FSYNC0P_FALLING_EDGE +} t_ab8500_codec_cr28_fsync0p; + +/* CR28 - 5 */ +typedef enum { + AB8500_CODEC_CR28_BITCLK0P_RISING_EDGE, + AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE +} t_ab8500_codec_cr28_bitclk0p; + +/* CR28 - 4 */ +typedef enum { + AB8500_CODEC_CR28_IF0DEL_NOT_DELAYED, + AB8500_CODEC_CR28_IF0DEL_DELAYED +} t_ab8500_codec_cr28_if0del; + +/* CR28 - 3:2 */ +typedef enum { + AB8500_CODEC_CR28_IF0FORMAT_DISABLED, + AB8500_CODEC_CR28_IF0FORMAT_TDM, + AB8500_CODEC_CR28_IF0FORMAT_I2S_LEFTALIGNED +} t_ab8500_codec_cr28_if0format; + +/* CR28 - 1:0 */ +/* In ab8500_codec.h */ + +/* CR29 - 7 */ +typedef enum { + AB8500_CODEC_CR29_IF0DATOIF1AD_NOTSENT, + AB8500_CODEC_CR29_IF0DATOIF1AD_SENT +} t_ab8500_codec_cr29_if0datoif1ad; + +/* CR29 - 6 */ +typedef enum { + AB8500_CODEC_CR29_IF0CKTOIF1CK_NOTSENT, + AB8500_CODEC_CR29_IF0CKTOIF1CK_SENT +} t_ab8500_codec_cr29_if0cktoif1ck; + +/* CR29 - 5 */ +typedef enum { + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_INPUT, + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_OUTPUT +} t_ab8500_codec_cr29_if1master; + +/* CR29 - 3 */ +typedef enum { + AB8500_CODEC_CR29_IF1DATOIF0AD_NOTSENT, + AB8500_CODEC_CR29_IF1DATOIF0AD_SENT +} t_ab8500_codec_cr29_if1datoif0ad; + +/* CR29 - 2 */ +typedef enum { + AB8500_CODEC_CR29_IF1CKTOIF0CK_NOTSENT, + AB8500_CODEC_CR29_IF1CKTOIF0CK_SENT +} t_ab8500_codec_cr29_if1cktoif0ck; + +/* CR29 - 1 */ +typedef enum { + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_INPUT, + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_OUTPUT +} t_ab8500_codec_cr29_if0master; + +/* CR29 - 0 */ +typedef enum { + AB8500_CODEC_CR29_IF0BFIFOEN_NORMAL_MODE, + AB8500_CODEC_CR29_IF0BFIFOEN_BURST_MODE +} t_ab8500_codec_cr29_if0bfifoen; + +/* CR30 - 6 */ +typedef enum { + AB8500_CODEC_CR30_FSYNC1P_RISING_EDGE, + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE +} t_ab8500_codec_cr30_fsync1p; + +/* CR30 - 5 */ +typedef enum { + AB8500_CODEC_CR30_BITCLK1P_RISING_EDGE, + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE +} t_ab8500_codec_cr30_bitclk1p; + +/* CR30 - 4 */ +typedef enum { + AB8500_CODEC_CR30_IF1DEL_NOT_DELAYED, + AB8500_CODEC_CR30_IF1DEL_DELAYED +} t_ab8500_codec_cr30_if1del; + +/* CR30 - 3:2 */ +typedef enum { + AB8500_CODEC_CR30_IF1FORMAT_DISABLED, + AB8500_CODEC_CR30_IF1FORMAT_TDM, + AB8500_CODEC_CR30_IF1FORMAT_I2S_LEFTALIGNED +} t_ab8500_codec_cr30_if1format; + +/* CR30 - 1:0 */ +/* In ab8500_codec.h */ + +/* CR31:46 - 7:4 or 3:0 */ +/* In ab8500_codec.h */ + +/* CR47:50 - 7/6/5/4/3/2/1/0 */ +typedef enum { + AB8500_CODEC_CR47_TO_CR50_HIZ_SL_LOW_IMPEDANCE, + AB8500_CODEC_CR47_TO_CR50_HIZ_SL_HIGH_IMPEDANCE, +} t_ab8500_codec_cr47_to_cr50_hiz_sl; + +/* CR51 - 7 */ +typedef enum { + AB8500_CODEC_CR51_DA12_VOICE_AUDIOFILTER, + AB8500_CODEC_CR51_DA12_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr51_da12_voice; + +/* CR51 - 6 */ +typedef enum { + AB8500_CODEC_CR51_SWAPDA12_34_NORMAL, + AB8500_CODEC_CR51_SWAPDA12_34_SWAPPED +} t_ab8500_codec_cr51_swapda12_34; + +/* CR51 - 5 */ +typedef enum { + AB8500_CODEC_CR51_SLDAI7TOSLADO1_NOT_LOOPEDBACK, + AB8500_CODEC_CR51_SLDAI7TOSLADO1_LOOPEDBACK +} t_ab8500_codec_cr51_sldai7toslado1; + +/* CR51:58 - 4:0 */ +/* In ab8500_codec.h */ + +/* CR52 - 5 */ +typedef enum { + AB8500_CODEC_CR52_SLDAI8TOSLADO2_NOT_LOOPEDBACK, + AB8500_CODEC_CR52_SLDAI8TOSLADO2_LOOPEDBACK +} t_ab8500_codec_cr52_sldai8toslado2; + +/* CR53 - 7 */ +typedef enum { + AB8500_CODEC_CR53_DA34_VOICE_AUDIOFILTER, + AB8500_CODEC_CR53_DA34_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr53_da34_voice; + +/* CR53 - 5 */ +typedef enum { + AB8500_CODEC_CR53_SLDAI7TOSLADO3_NOT_LOOPEDBACK, + AB8500_CODEC_CR53_SLDAI7TOSLADO3_LOOPEDBACK +} t_ab8500_codec_cr53_sldai7toslado3; + +/* CR54 - 5 */ +typedef enum { + AB8500_CODEC_CR54_SLDAI8TOSLADO4_NOT_LOOPEDBACK, + AB8500_CODEC_CR54_SLDAI8TOSLADO4_LOOPEDBACK +} t_ab8500_codec_cr54_sldai8toslado4; + +/* CR55 - 7 */ +typedef enum { + AB8500_CODEC_CR55_DA56_VOICE_AUDIOFILTER, + AB8500_CODEC_CR55_DA56_VOICE_LOWLATENCYFILTER +} t_ab8500_codec_cr55_da56_voice; + +/* CR55 - 5 */ +typedef enum { + AB8500_CODEC_CR55_SLDAI7TOSLADO5_NOT_LOOPEDBACK, + AB8500_CODEC_CR55_SLDAI7TOSLADO5_LOOPEDBACK +} t_ab8500_codec_cr55_sldai7toslado5; + +/* CR56 - 5 */ +typedef enum { + AB8500_CODEC_CR56_SLDAI8TOSLADO6_NOT_LOOPEDBACK, + AB8500_CODEC_CR56_SLDAI8TOSLADO6_LOOPEDBACK +} t_ab8500_codec_cr56_sldai8toslado6; + +/* CR57 - 5 */ +typedef enum { + AB8500_CODEC_CR57_SLDAI8TOSLADO7_NOT_LOOPEDBACK, + AB8500_CODEC_CR57_SLDAI8TOSLADO7_LOOPEDBACK +} t_ab8500_codec_cr57_sldai8toslado7; + +/* CR58 - 5 */ +typedef enum { + AB8500_CODEC_CR58_SLDAI7TOSLADO8_NOT_LOOPEDBACK, + AB8500_CODEC_CR58_SLDAI7TOSLADO8_LOOPEDBACK +} t_ab8500_codec_cr58_sldai7toslado8; + +/* CR59 - 7 */ +typedef enum { + AB8500_CODEC_CR59_PARLHF_INDEPENDENT, + AB8500_CODEC_CR59_PARLHF_BRIDGED +} t_ab8500_codec_cr59_parlhf; + +/* CR59 - 6 */ +typedef enum { + AB8500_CODEC_CR59_PARLVIB_INDEPENDENT, + AB8500_CODEC_CR59_PARLVIB_BRIDGED +} t_ab8500_codec_cr59_parlvib; + +/* CR59 - 3 */ +typedef enum { + AB8500_CODEC_CR59_CLASSDVIB1_SWAPEN_DISABLED, + AB8500_CODEC_CR59_CLASSDVIB1_SWAPEN_ENABLED +} t_ab8500_codec_cr59_classdvib1_swapen; + +/* CR59 - 2 */ +typedef enum { + AB8500_CODEC_CR59_CLASSDVIB2_SWAPEN_DISABLED, + AB8500_CODEC_CR59_CLASSDVIB2_SWAPEN_ENABLED +} t_ab8500_codec_cr59_classdvib2_swapen; + +/* CR59 - 1 */ +typedef enum { + AB8500_CODEC_CR59_CLASSDHFL_SWAPEN_DISABLED, + AB8500_CODEC_CR59_CLASSDHFL_SWAPEN_ENABLED +} t_ab8500_codec_cr59_classdhfl_swapen; + +/* CR59 - 0 */ +typedef enum { + AB8500_CODEC_CR59_CLASSDHFR_SWAPEN_DISABLED, + AB8500_CODEC_CR59_CLASSDHFR_SWAPEN_ENABLED +} t_ab8500_codec_cr59_classdhfr_swapen; + +/* CR60 - 7:4 */ +typedef enum { + AB8500_CODEC_CR60_CLASSD_FIRBYP_ALL_ENABLED = 0, + AB8500_CODEC_CR60_CLASSD_FIRBYP_LEFT_HF_BYPASSED = 1, + AB8500_CODEC_CR60_CLASSD_FIRBYP_RIGHT_HF_BYPASSED = 2, + AB8500_CODEC_CR60_CLASSD_FIRBYP_VIBRA1_BYPASSED = 4, + AB8500_CODEC_CR60_CLASSD_FIRBYP_VIBRA2_BYPASSED = 8 +} t_ab8500_codec_cr60_classd_firbyp; + +/* CR60 - 3:0 */ +typedef enum { + AB8500_CODEC_CR60_CLASSD_HIGHVOLEN_DISABLED = 0, + AB8500_CODEC_CR60_CLASSD_HIGHVOLEN_LEFT_HF = 1, + AB8500_CODEC_CR60_CLASSD_HIGHVOLEN_RIGHT_HF = 2, + AB8500_CODEC_CR60_CLASSD_HIGHVOLEN_VIBRA1 = 4, + AB8500_CODEC_CR60_CLASSD_HIGHVOLEN_VIBRA2 = 8 +} t_ab8500_codec_cr60_classd_highvolen; + +/* CR61 - 7:4 */ +typedef t_uint8 t_ab8500_codec_cr61_classddith_hpgain; + +/* CR61 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr61_classddith_wgain; + +/* CR62 - Read Only */ +/* CR62 - 5 */ +typedef enum { + AB8500_CODEC_CR62_DMIC1SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC1SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic1sinc3; + +/* CR62 - 4 */ +typedef enum { + AB8500_CODEC_CR62_DMIC2SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC2SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic2sinc3; + +/* CR62 - 3 */ +typedef enum { + AB8500_CODEC_CR62_DMIC3SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC3SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic3sinc3; + +/* CR62 - 2 */ +typedef enum { + AB8500_CODEC_CR62_DMIC4SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC4SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic4sinc3; + +/* CR62 - 1 */ +typedef enum { + AB8500_CODEC_CR62_DMIC5SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC5SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic5sinc3; + +/* CR62 - 0 */ +typedef enum { + AB8500_CODEC_CR62_DMIC6SINC3_SINC5_SELECTED, + AB8500_CODEC_CR62_DMIC6SINC3_SINC3_SELECTED +} t_ab8500_codec_cr62_dmic6sinc3; + +/* CR63 - 7 */ +typedef enum { + AB8500_CODEC_CR63_DATOHSLEN_DISABLED, + AB8500_CODEC_CR63_DATOHSLEN_ENABLED +} t_ab8500_codec_cr63_datohslen; + +/* CR63 - 6 */ +typedef enum { + AB8500_CODEC_CR63_DATOHSREN_DISABLED, + AB8500_CODEC_CR63_DATOHSREN_ENABLED +} t_ab8500_codec_cr63_datohsren; + +/* CR63 - 5 */ +typedef enum { + AB8500_CODEC_CR63_AD1SEL_LINLADL_SELECTED, + AB8500_CODEC_CR63_AD1SEL_DMIC1_SELECTED +} t_ab8500_codec_cr63_ad1sel; + +/* CR63 - 4 */ +typedef enum { + AB8500_CODEC_CR63_AD2SEL_LINRADR_SELECTED, + AB8500_CODEC_CR63_AD2SEL_DMIC2_SELECTED +} t_ab8500_codec_cr63_ad2sel; + +/* CR63 - 3 */ +typedef enum { + AB8500_CODEC_CR63_AD3SEL_ADMO_SELECTED, + AB8500_CODEC_CR63_AD3SEL_DMIC3_SELECTED +} t_ab8500_codec_cr63_ad3sel; + +/* CR63 - 2 */ +typedef enum { + AB8500_CODEC_CR63_AD5SEL_AMADR_SELECTED, + AB8500_CODEC_CR63_AD5SEL_DMIC5_SELECTED +} t_ab8500_codec_cr63_ad5sel; + +/* CR63 - 1 */ +typedef enum { + AB8500_CODEC_CR63_AD6SEL_ADMO_SELECTED, + AB8500_CODEC_CR63_AD6SEL_DMIC6_SELECTED +} t_ab8500_codec_cr63_ad6sel; + +/* CR63 - 0 */ +typedef enum { + AB8500_CODEC_CR63_ANCSEL_NOT_MIXED_IN_EAR, + AB8500_CODEC_CR63_ANCSEL_MIXED_IN_EAR +} t_ab8500_codec_cr63_ancsel; + +/* CR64 - 7 */ +typedef enum { + AB8500_CODEC_CR64_DATOHFREN_NOT_MIXED_TO_HFR, + AB8500_CODEC_CR64_DATOHFREN_MIXED_TO_HFR +} t_ab8500_codec_cr64_datohfren; + +/* CR64 - 6 */ +typedef enum { + AB8500_CODEC_CR64_DATOHFLEN_NOT_MIXED_TO_HFL, + AB8500_CODEC_CR64_DATOHFLEN_MIXED_TO_HFL +} t_ab8500_codec_cr64_datohflen; + +/* CR64 - 5 */ +typedef enum { + AB8500_CODEC_CR64_HFRSEL_DA4_MIXED_TO_HFR, + AB8500_CODEC_CR64_HFRSEL_ANC_MIXED_TO_HFR +} t_ab8500_codec_cr64_hfrsel; + +/* CR64 - 4 */ +typedef enum { + AB8500_CODEC_CR64_HFLSEL_DA3_MIXED_TO_HFL, + AB8500_CODEC_CR64_HFLSEL_ANC_MIXED_TO_HFL +} t_ab8500_codec_cr64_hflsel; + +/* CR64 - 3:2 */ +typedef enum { + AB8500_CODEC_CR64_STFIR1SEL_AD_OUT1_SELECTED, + AB8500_CODEC_CR64_STFIR1SEL_AD_OUT3_SELECTED, + AB8500_CODEC_CR64_STFIR1SEL_DA_IN1_SELECTED +} t_ab8500_codec_cr64_stfir1sel; + +/* CR64 - 1:0 */ +typedef enum { + AB8500_CODEC_CR64_STFIR2SEL_AD_OUT2_SELECTED, + AB8500_CODEC_CR64_STFIR2SEL_AD_OUT4_SELECTED, + AB8500_CODEC_CR64_STFIR2SEL_DA_IN2_SELECTED +} t_ab8500_codec_cr64_stfir2sel; + +/* CR65 - 6 */ +typedef enum { + AB8500_CODEC_CR65_FADEDIS_AD1_ENABLED, + AB8500_CODEC_CR65_FADEDIS_AD1_DISABLED +} t_ab8500_codec_cr65_fadedis_ad1; + +/* CR65 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr65_ad1gain; + +/* CR66 - 6 */ +typedef enum { + AB8500_CODEC_CR66_FADEDIS_AD2_ENABLED, + AB8500_CODEC_CR66_FADEDIS_AD2_DISABLED +} t_ab8500_codec_cr66_fadedis_ad2; + +/* CR66 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr66_ad2gain; + +/* CR67 - 6 */ +typedef enum { + AB8500_CODEC_CR67_FADEDIS_AD3_ENABLED, + AB8500_CODEC_CR67_FADEDIS_AD3_DISABLED +} t_ab8500_codec_cr67_fadedis_ad3; + +/* CR67 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr67_ad3gain; + +/* CR68 - 6 */ +typedef enum { + AB8500_CODEC_CR68_FADEDIS_AD4_ENABLED, + AB8500_CODEC_CR68_FADEDIS_AD4_DISABLED +} t_ab8500_codec_cr68_fadedis_ad4; + +/* CR68 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr68_ad4gain; + +/* CR69 - 6 */ +typedef enum { + AB8500_CODEC_CR69_FADEDIS_AD5_ENABLED, + AB8500_CODEC_CR69_FADEDIS_AD5_DISABLED +} t_ab8500_codec_cr69_fadedis_ad5; + +/* CR69 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr69_ad5gain; + +/* CR70 - 6 */ +typedef enum { + AB8500_CODEC_CR70_FADEDIS_AD6_ENABLED, + AB8500_CODEC_CR70_FADEDIS_AD6_DISABLED +} t_ab8500_codec_cr70_fadedis_ad6; + +/* CR70 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr70_ad6gain; + +/* CR71 - 6 */ +typedef enum { + AB8500_CODEC_CR71_FADEDIS_DA1_ENABLED, + AB8500_CODEC_CR71_FADEDIS_DA1_DISABLED +} t_ab8500_codec_cr71_fadedis_da1; + +/* CR71 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr71_da1gain; + +/* CR72 - 6 */ +typedef enum { + AB8500_CODEC_CR72_FADEDIS_DA2_ENABLED, + AB8500_CODEC_CR72_FADEDIS_DA2_DISABLED +} t_ab8500_codec_cr72_fadedis_da2; + +/* CR72 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr72_da2gain; + +/* CR73 - 6 */ +typedef enum { + AB8500_CODEC_CR73_FADEDIS_DA3_ENABLED, + AB8500_CODEC_CR73_FADEDIS_DA3_DISABLED +} t_ab8500_codec_cr73_fadedis_da3; + +/* CR73 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr73_da3gain; + +/* CR74 - 6 */ +typedef enum { + AB8500_CODEC_CR74_FADEDIS_DA4_ENABLED, + AB8500_CODEC_CR74_FADEDIS_DA4_DISABLED +} t_ab8500_codec_cr74_fadedis_da4; + +/* CR74 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr74_da4gain; + +/* CR75 - 6 */ +typedef enum { + AB8500_CODEC_CR75_FADEDIS_DA5_ENABLED, + AB8500_CODEC_CR75_FADEDIS_DA5_DISABLED +} t_ab8500_codec_cr75_fadedis_da5; + +/* CR75 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr75_da5gain; + +/* CR76 - 6 */ +typedef enum { + AB8500_CODEC_CR76_FADEDIS_DA6_ENABLED, + AB8500_CODEC_CR76_FADEDIS_DA6_DISABLED +} t_ab8500_codec_cr76_fadedis_da6; + +/* CR76 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr76_da6gain; + +/* CR77 - 6 */ +typedef enum { + AB8500_CODEC_CR77_FADEDIS_AD1L_TO_HFL_ENABLED, + AB8500_CODEC_CR77_FADEDIS_AD1L_TO_HFL_DISABLED +} t_ab8500_codec_cr77_fadedis_ad1l; + +/* CR77 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr77_ad1lbgain_to_hfl; + +/* CR78 - 6 */ +typedef enum { + AB8500_CODEC_CR78_FADEDIS_AD2L_TO_HFR_ENABLED, + AB8500_CODEC_CR78_FADEDIS_AD2L_TO_HFR_DISABLED +} t_ab8500_codec_cr78_fadedis_ad2l; + +/* CR78 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr78_ad2lbgain_to_hfr; + +/* CR79 - 7 */ +typedef enum { + AB8500_CODEC_CR79_HSSINC1_SINC3_CHOOSEN, + AB8500_CODEC_CR79_HSSINC1_SINC1_CHOOSEN +} t_ab8500_codec_cr79_hssinc1; + +/* CR79 - 4 */ +typedef enum { + AB8500_CODEC_CR79_FADEDIS_HSL_ENABLED, + AB8500_CODEC_CR79_FADEDIS_HSL_DISABLED +} t_ab8500_codec_cr79_fadedis_hsl; + +/* CR79 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr79_hsldgain; + +/* CR80 - 7:6 */ +typedef enum { + AB8500_CODEC_CR80_FADE_SPEED_1MS, + AB8500_CODEC_CR80_FADE_SPEED_4MS, + AB8500_CODEC_CR80_FADE_SPEED_8MS, + AB8500_CODEC_CR80_FADE_SPEED_16MS, +} t_ab8500_codec_cr80_fade_speed; + +/* CR80 - 4 */ +typedef enum { + AB8500_CODEC_CR80_FADEDIS_HSR_ENABLED, + AB8500_CODEC_CR80_FADEDIS_HSR_DISABLED +} t_ab8500_codec_cr80_fadedis_hsr; + +/* CR80 - 3:0 */ +typedef t_uint8 t_ab8500_codec_cr80_hsrdgain; + +/* CR81 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr81_stfir1gain; + +/* CR82 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr82_stfir2gain; + +/* CR83 - 2 */ +typedef enum { + AB8500_CODEC_CR83_ENANC_DISABLED, + AB8500_CODEC_CR83_ENANC_ENABLED +} t_ab8500_codec_cr83_enanc; + +/* CR83 - 1 */ +typedef enum { + AB8500_CODEC_CR83_ANCIIRINIT_NOT_STARTED, + AB8500_CODEC_CR83_ANCIIRINIT_STARTED +} t_ab8500_codec_cr83_anciirinit; + +/* CR83 - 0 */ +typedef enum { + AB8500_CODEC_CR83_ANCFIRUPDATE_RESETTED, + AB8500_CODEC_CR83_ANCFIRUPDATE_NOT_RESETTED +} t_ab8500_codec_cr83_ancfirupdate; + +/* CR84 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr84_ancinshift; + +/* CR85 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr85_ancfiroutshift; + +/* CR86 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr86_ancshiftout; + +/* CR87 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr87_ancfircoeff_msb; + +/* CR88 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr88_ancfircoeff_lsb; + +/* CR89 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr89_anciircoeff_msb; + +/* CR90 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr90_anciircoeff_lsb; + +/* CR91 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr91_ancwarpdel_msb; + +/* CR92 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr92_ancwarpdel_lsb; + +/* CR93 - Read Only */ +/* CR93 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr93_ancfirpeak_msb; + +/* CR94 - Read Only */ +/* CR94 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr94_ancfirpeak_lsb; + +/* CR95 - Read Only */ +/* CR95 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr95_anciirpeak_msb; + +/* CR96 - Read Only */ +/* CR96 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr96_anciirpeak_lsb; + +/* CR97 - 7 */ +typedef enum { + AB8500_CODEC_CR97_STFIR_SET_LAST_NOT_APPLIED, + AB8500_CODEC_CR97_STFIR_SET_LAST_APPLIED +} t_ab8500_codec_cr97_stfir_set; + +/* CR97 - 6:0 */ +typedef t_uint8 t_ab8500_codec_cr97_stfir_addr; + +/* CR98 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr98_stfir_coeff_msb; + +/* CR99 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr99_stfir_coeff_lsb; + +/* CR100 - 2 */ +typedef enum { + AB8500_CODEC_CR100_ENSTFIRS_DISABLED, + AB8500_CODEC_CR100_ENSTFIRS_ENABLED +} t_ab8500_codec_cr100_enstfirs; + +/* CR100 - 1 */ +typedef enum { + AB8500_CODEC_CR100_STFIRSTOIF1_AUD_IF0_DATA_RATE, + AB8500_CODEC_CR100_STFIRSTOIF1_AUD_IF1_DATA_RATE +} t_ab8500_codec_cr100_stfirstoif1; + +/* CR100 - 0 */ +typedef enum { + AB8500_CODEC_CR100_STFIR_BUSY_READY, + AB8500_CODEC_CR100_STFIR_BUSY_NOT_READY +} t_ab8500_codec_cr100_stfir_busy; + +/* CR101 - 7 */ +typedef enum { + AB8500_CODEC_CR101_HSOFFST_MASK_MASKED, + AB8500_CODEC_CR101_HSOFFST_MASK_ENABLED +} t_ab8500_codec_cr101_hsoffst_mask; + +/* CR101 - 6 */ +typedef enum { + AB8500_CODEC_CR101_FIFOFULL_MASK_MASKED, + AB8500_CODEC_CR101_FIFOFULL_MASK_ENABLED +} t_ab8500_codec_cr101_fifofull_mask; + +/* CR101 - 5 */ +typedef enum { + AB8500_CODEC_CR101_FIFOEMPTY_MASK_MASKED, + AB8500_CODEC_CR101_FIFOEMPTY_MASK_ENABLED +} t_ab8500_codec_cr101_fifoempty_mask; + +/* CR101 - 4 */ +typedef enum { + AB8500_CODEC_CR101_DASAT_MASK_MASKED, + AB8500_CODEC_CR101_DASAT_MASK_ENABLED +} t_ab8500_codec_cr101_dasat_mask; + +/* CR101 - 3 */ +typedef enum { + AB8500_CODEC_CR101_ADSAT_MASK_MASKED, + AB8500_CODEC_CR101_ADSAT_MASK_ENABLED +} t_ab8500_codec_cr101_adsat_mask; + +/* CR101 - 2 */ +typedef enum { + AB8500_CODEC_CR101_ADDSP_MASK_MASKED, + AB8500_CODEC_CR101_ADDSP_MASK_ENABLED +} t_ab8500_codec_cr101_addsp_mask; + +/* CR101 - 1 */ +typedef enum { + AB8500_CODEC_CR101_DADSP_MASK_MASKED, + AB8500_CODEC_CR101_DADSP_MASK_ENABLED +} t_ab8500_codec_cr101_dadsp_mask; + +/* CR101 - 0 */ +typedef enum { + AB8500_CODEC_CR101_FIRSID_MASK_MASKED, + AB8500_CODEC_CR101_FIRSID_MASK_ENABLED +} t_ab8500_codec_cr101_firsid_mask; + +/* CR102 - Read Only */ +/* CR102 - 7 */ +typedef enum { + AB8500_CODEC_CR102_IT_HSOFFST_ON, + AB8500_CODEC_CR102_IT_HSOFFST_OFF +} t_ab8500_codec_cr102_it_hsoffst; + +/* CR102 - 6 */ +typedef enum { + AB8500_CODEC_CR102_IT_FIFOFULL_NOT_FULL, + AB8500_CODEC_CR102_IT_FIFOFULL_FULL +} t_ab8500_codec_cr102_it_fifofull; + +/* CR102 - 5 */ +typedef enum { + AB8500_CODEC_CR102_IT_FIFOEMPTY_NOT_EMPTY, + AB8500_CODEC_CR102_IT_FIFOEMPTY_EMPTY +} t_ab8500_codec_cr102_it_fifoempty; + +/* CR102 - 4 */ +typedef enum { + AB8500_CODEC_CR102_IT_DASAT_NO_SATURATION, + AB8500_CODEC_CR102_IT_DASAT_SATURATION +} t_ab8500_codec_cr102_it_dasat; + +/* CR102 - 3 */ +typedef enum { + AB8500_CODEC_CR102_IT_ADSAT_NO_SATURATION, + AB8500_CODEC_CR102_IT_ADSAT_SATURATION +} t_ab8500_codec_cr102_it_adsat; + +/* CR102 - 2 */ +typedef enum { + AB8500_CODEC_CR102_IT_ADDSP_NO_SATURATION, + AB8500_CODEC_CR102_IT_ADDSP_SATURATION +} t_ab8500_codec_cr102_it_addsp; + +/* CR102 - 1 */ +typedef enum { + AB8500_CODEC_CR102_IT_DADSP_NO_SATURATION, + AB8500_CODEC_CR102_IT_DADSP_SATURATION +} t_ab8500_codec_cr102_it_dadsp; + +/* CR102 - 0 */ +typedef enum { + AB8500_CODEC_CR102_IT_FIRSID_NO_SATURATION, + AB8500_CODEC_CR102_IT_FIRSID_SATURATION +} t_ab8500_codec_cr102_it_firsid; + +/* CR103 - 7 */ +typedef enum { + AB8500_CODEC_CR103_VSSREADY_MASK_MASKED, + AB8500_CODEC_CR103_VSSREADY_MASK_ENABLED +} t_ab8500_codec_cr103_vssready_mask; + +/* CR103 - 2 */ +typedef enum { + AB8500_CODEC_CR103_SHORTHSL_MASK_MASKED, + AB8500_CODEC_CR103_SHORTHSL_MASK_ENABLED +} t_ab8500_codec_cr103_shorthsl_mask; + +/* CR103 - 1 */ +typedef enum { + AB8500_CODEC_CR103_SHORTHSR_MASK_MASKED, + AB8500_CODEC_CR103_SHORTHSR_MASK_ENABLED +} t_ab8500_codec_cr103_shorthsr_mask; + +/* CR103 - 0 */ +typedef enum { + AB8500_CODEC_CR103_SHORTEAR_MASK_MASKED, + AB8500_CODEC_CR103_SHORTEAR_MASK_ENABLED +} t_ab8500_codec_cr103_shortear_mask; + +/* CR104 - Read Only */ +/* CR104 - 7 */ +typedef enum { + AB8500_CODEC_CR104_IT_VSSREADY_NOT_READY, + AB8500_CODEC_CR104_IT_VSSREADY_READY +} t_ab8500_codec_cr104_it_vssready; + +/* CR104 - 2 */ +typedef enum { + AB8500_CODEC_CR104_IT_SHORTHSL_NOT_DETECTED, + AB8500_CODEC_CR104_IT_SHORTHSL_DETECTED +} t_ab8500_codec_cr104_it_shorthsl; + +/* CR104 - 1 */ +typedef enum { + AB8500_CODEC_CR104_IT_SHORTHSR_NOT_DETECTED, + AB8500_CODEC_CR104_IT_SHORTHSR_DETECTED +} t_ab8500_codec_cr104_it_shorthsr; + +/* CR104 - 0 */ +typedef enum { + AB8500_CODEC_CR104_IT_SHORTEAR_NOT_DETECTED, + AB8500_CODEC_CR104_IT_SHORTEAR_DETECTED +} t_ab8500_codec_cr104_it_shortear; + +/* CR105 - 7 */ +/* In ab8500_codec.h */ + +/* CR105 - 5:0 */ +/* In ab8500_codec.h */ + +/* CR106 - 7:0 */ +/* In ab8500_codec.h */ + +/* CR107 - 7:0 */ +/* In ab8500_codec.h */ + +/* CR108 - 7:0 */ +/* In ab8500_codec.h */ + +/* CR109 - 7:0 */ +/* In ab8500_codec.h */ + +/* CR110 - Read Only */ +/* CR110 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr110_bfifosamples; + +/* CR111 - Read Only */ +/* CR111 - 4:0 */ +typedef t_uint8 t_ab8500_codec_cr111_aud_ip_rev; + +/* CR27 - 6:5 */ +typedef enum { + AB8500_CODEC_CR27_IF1_BITCLK_OSR_32, + AB8500_CODEC_CR27_IF1_BITCLK_OSR_64, + AB8500_CODEC_CR27_IF1_BITCLK_OSR_128, + AB8500_CODEC_CR27_IF1_BITCLK_OSR_256 +} t_ab8500_codec_cr27_if1_bitclk_osr; + +/* CR27 - 2:1 */ +typedef enum { + AB8500_CODEC_CR27_IF0_BITCLK_OSR_32, + AB8500_CODEC_CR27_IF0_BITCLK_OSR_64, + AB8500_CODEC_CR27_IF0_BITCLK_OSR_128, + AB8500_CODEC_CR27_IF0_BITCLK_OSR_256 +} t_ab8500_codec_cr27_if0_bitclk_osr; + +/* CR28 - 1:0 */ +typedef enum { + AB8500_CODEC_CR28_IF0WL_16BITS, + AB8500_CODEC_CR28_IF0WL_20BITS, + AB8500_CODEC_CR28_IF0WL_24BITS, + AB8500_CODEC_CR28_IF0WL_32BITS +} t_ab8500_codec_cr28_if0wl; + +/* CR30 - 1:0 */ +typedef enum { + AB8500_CODEC_CR30_IF1WL_16BITS, + AB8500_CODEC_CR30_IF1WL_20BITS, + AB8500_CODEC_CR30_IF1WL_24BITS, + AB8500_CODEC_CR30_IF1WL_32BITS +} t_ab8500_codec_cr30_if1wl; + +/* CR105 - 7 */ +typedef enum { + AB8500_CODEC_CR105_BFIFOMSK_AD_DATA0_UNMASKED, + AB8500_CODEC_CR105_BFIFOMSK_AD_DATA0_MASKED +} t_ab8500_codec_cr105_bfifomsk; + +/* CR105 - 5:0 */ +typedef t_uint8 t_ab8500_codec_cr105_bfifoint; + +/* CR106 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr106_bfifotx; + +/* CR107 - 7:5 */ +typedef enum { + AB8500_CODEC_CR107_BFIFOEXSL_0_EXTRA_SLOT, + AB8500_CODEC_CR107_BFIFOEXSL_1_EXTRA_SLOT, + AB8500_CODEC_CR107_BFIFOEXSL_2_EXTRA_SLOT, + AB8500_CODEC_CR107_BFIFOEXSL_3_EXTRA_SLOT, + AB8500_CODEC_CR107_BFIFOEXSL_4_EXTRA_SLOT, + AB8500_CODEC_CR107_BFIFOEXSL_5_EXTRA_SLOT, + AB8500_CODEC_CR107_BFIFOEXSL_6_EXTRA_SLOT, +} t_ab8500_codec_cr107_bfifoexsl; + +/* CR107 - 4:2 */ +typedef enum { + AB8500_CODEC_CR107_PREBITCLK0_0_EXTRA_CLK, + AB8500_CODEC_CR107_PREBITCLK0_1_EXTRA_CLK, + AB8500_CODEC_CR107_PREBITCLK0_2_EXTRA_CLK, + AB8500_CODEC_CR107_PREBITCLK0_3_EXTRA_CLK, + AB8500_CODEC_CR107_PREBITCLK0_4_EXTRA_CLK, + AB8500_CODEC_CR107_PREBITCLK0_5_EXTRA_CLK, + AB8500_CODEC_CR107_PREBITCLK0_6_EXTRA_CLK, + AB8500_CODEC_CR107_PREBITCLK0_7_EXTRA_CLK +} t_ab8500_codec_cr107_prebitclk0; + +/* CR107 - 1 */ +typedef enum { + AB8500_CODEC_CR107_BFIFOMAST_SLAVE_MODE, + AB8500_CODEC_CR107_BFIFOMAST_MASTER_MODE +} t_ab8500_codec_cr107_bfifomast; + +/* CR107 - 0 */ +typedef enum { + AB8500_CODEC_CR107_BFIFORUN_STOPPED, + AB8500_CODEC_CR107_BFIFORUN_RUNNING +} t_ab8500_codec_cr107_bfiforun; + +/* CR108 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr108_bfifoframsw; + +/* CR109 - 7:0 */ +typedef t_uint8 t_ab8500_codec_cr109_bfifowakeup; + +typedef enum { + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT1, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT2, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT3, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT4, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT5, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT6, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT7, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT8, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_ZEROS, + AB8500_CODEC_CR31_TO_CR46_SLOT_IS_TRISTATE = 15, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_UNDEFINED +} t_ab8500_codec_cr31_to_cr46_ad_data_allocation; + +typedef enum { + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT00, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT01, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT02, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT03, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT04, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT05, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT06, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT07, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT08, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT09, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT10, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT11, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT12, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT13, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT14, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT15, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT16, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT17, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT18, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT19, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT20, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT21, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT22, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT23, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT24, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT25, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT26, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT27, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT28, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT29, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT30, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT31, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT_UNDEFINED +} t_ab8500_codec_cr51_to_cr58_sltoda; + +/*configuration structure for AB8500 Codec*/ +typedef struct { + /* CR0 */ + t_ab8500_codec_cr0_powerup cr0_powerup; + t_ab8500_codec_cr0_enaana cr0_enaana; + + /* CR1 */ + t_ab8500_codec_cr1_swreset cr1_swreset; + + /* CR2 */ + t_ab8500_codec_cr2_enad1 cr2_enad1; + t_ab8500_codec_cr2_enad2 cr2_enad2; + t_ab8500_codec_cr2_enad3 cr2_enad3; + t_ab8500_codec_cr2_enad4 cr2_enad4; + t_ab8500_codec_cr2_enad5 cr2_enad5; + t_ab8500_codec_cr2_enad6 cr2_enad6; + + /* CR3 */ + t_ab8500_codec_cr3_enda1 cr3_enda1; + t_ab8500_codec_cr3_enda2 cr3_enda2; + t_ab8500_codec_cr3_enda3 cr3_enda3; + t_ab8500_codec_cr3_enda4 cr3_enda4; + t_ab8500_codec_cr3_enda5 cr3_enda5; + t_ab8500_codec_cr3_enda6 cr3_enda6; + + /* CR4 */ + t_ab8500_codec_cr4_lowpowhs cr4_lowpowhs; + t_ab8500_codec_cr4_lowpowdachs cr4_lowpowdachs; + t_ab8500_codec_cr4_lowpowear cr4_lowpowear; + t_ab8500_codec_cr4_ear_sel_cm cr4_ear_sel_cm; + t_ab8500_codec_cr4_hs_hp_en cr4_hs_hp_en; + + /* CR5 */ + t_ab8500_codec_cr5_enmic1 cr5_enmic1; + t_ab8500_codec_cr5_enmic2 cr5_enmic2; + t_ab8500_codec_cr5_enlinl cr5_enlinl; + t_ab8500_codec_cr5_enlinr cr5_enlinr; + t_ab8500_codec_cr5_mutmic1 cr5_mutmic1; + t_ab8500_codec_cr5_mutmic2 cr5_mutmic2; + t_ab8500_codec_cr5_mutlinl cr5_mutlinl; + t_ab8500_codec_cr5_mutlinr cr5_mutlinr; + + /* CR6 */ + t_ab8500_codec_cr6_endmic1 cr6_endmic1; + t_ab8500_codec_cr6_endmic2 cr6_endmic2; + t_ab8500_codec_cr6_endmic3 cr6_endmic3; + t_ab8500_codec_cr6_endmic4 cr6_endmic4; + t_ab8500_codec_cr6_endmic5 cr6_endmic5; + t_ab8500_codec_cr6_endmic6 cr6_endmic6; + + /* CR7 */ + t_ab8500_codec_cr7_mic1sel cr7_mic1sel; + t_ab8500_codec_cr7_linrsel cr7_linrsel; + t_ab8500_codec_cr7_endrvhsl cr7_endrvhsl; + t_ab8500_codec_cr7_endrvhsr cr7_endrvhsr; + t_ab8500_codec_cr7_enadcmic cr7_enadcmic; + t_ab8500_codec_cr7_enadclinl cr7_enadclinl; + t_ab8500_codec_cr7_enadclinr cr7_enadclinr; + + /* CR8 */ + t_ab8500_codec_cr8_cp_dis_pldwn cr8_cp_dis_pldwn; + t_ab8500_codec_cr8_enear cr8_enear; + t_ab8500_codec_cr8_enhsl cr8_enhsl; + t_ab8500_codec_cr8_enhsr cr8_enhsr; + t_ab8500_codec_cr8_enhfl cr8_enhfl; + t_ab8500_codec_cr8_enhfr cr8_enhfr; + t_ab8500_codec_cr8_envibl cr8_envibl; + t_ab8500_codec_cr8_envibr cr8_envibr; + + /* CR9 */ + t_ab8500_codec_cr9_endacear cr9_endacear; + t_ab8500_codec_cr9_endachsl cr9_endachsl; + t_ab8500_codec_cr9_endachsr cr9_endachsr; + t_ab8500_codec_cr9_endachfl cr9_endachfl; + t_ab8500_codec_cr9_endachfr cr9_endachfr; + t_ab8500_codec_cr9_endacvibl cr9_endacvibl; + t_ab8500_codec_cr9_endacvibr cr9_endacvibr; + + /* CR10 */ + t_ab8500_codec_cr10_muteear cr10_muteear; + t_ab8500_codec_cr10_mutehsl cr10_mutehsl; + t_ab8500_codec_cr10_mutehsr cr10_mutehsr; + + /* CR11 */ + t_ab8500_codec_cr11_enshortpwd cr11_enshortpwd; + t_ab8500_codec_cr11_earshortdis cr11_earshortdis; + t_ab8500_codec_cr11_hsshortdis cr11_hsshortdis; + t_ab8500_codec_cr11_hspullden cr11_hspullden; + t_ab8500_codec_cr11_hsoscen cr11_hsoscen; + t_ab8500_codec_cr11_hsfaden cr11_hsfaden; + t_ab8500_codec_cr11_hszcddis cr11_hszcddis; + + /* CR12 */ + t_ab8500_codec_cr12_encphs cr12_encphs; + t_ab8500_codec_cr12_hsautoen cr12_hsautoen; + + /* CR13 */ + t_ab8500_codec_cr13_envdet_hthresh cr13_envdet_hthresh; + t_ab8500_codec_cr13_envdet_lthresh cr13_envdet_lthresh; + + /* CR14 */ + t_ab8500_codec_cr14_smpslven cr14_smpslven; + t_ab8500_codec_cr14_envdetsmpsen cr14_envdetsmpsen; + t_ab8500_codec_cr14_cplven cr14_cplven; + t_ab8500_codec_cr14_envdetcpen cr14_envdetcpen; + t_ab8500_codec_cr14_envet_time cr14_envet_time; + + /* CR15 */ + t_ab8500_codec_cr15_pwmtovibl cr15_pwmtovibl; + t_ab8500_codec_cr15_pwmtovibr cr15_pwmtovibr; + t_ab8500_codec_cr15_pwmlctrl cr15_pwmlctrl; + t_ab8500_codec_cr15_pwmrctrl cr15_pwmrctrl; + t_ab8500_codec_cr15_pwmnlctrl cr15_pwmnlctrl; + t_ab8500_codec_cr15_pwmplctrl cr15_pwmplctrl; + t_ab8500_codec_cr15_pwmnrctrl cr15_pwmnrctrl; + t_ab8500_codec_cr15_pwmprctrl cr15_pwmprctrl; + + /* CR16 */ + t_ab8500_codec_cr16_pwmnlpol cr16_pwmnlpol; + t_ab8500_codec_cr16_pwmnldutycycle cr16_pwmnldutycycle; + + /* CR17 */ + t_ab8500_codec_cr17_pwmplpol cr17_pwmplpol; + t_ab8500_codec_cr17_pwmpldutycycle cr17_pwmpldutycycle; + + /* CR18 */ + t_ab8500_codec_cr18_pwmnrpol cr18_pwmnrpol; + t_ab8500_codec_cr18_pwmnrdutycycle cr18_pwmnrdutycycle; + + /* CR19 */ + t_ab8500_codec_cr19_pwmprpol cr19_pwmprpol; + t_ab8500_codec_cr19_pwmprdutycycle cr19_pwmprdutycycle; + + /* CR20 */ + t_ab8500_codec_cr20_en_se_mic1 cr20_en_se_mic1; + t_ab8500_codec_cr20_low_pow_mic1 cr20_low_pow_mic1; + t_ab8500_codec_cr20_mic1_gain cr20_mic1_gain; + + /* CR21 */ + t_ab8500_codec_cr21_en_se_mic2 cr21_en_se_mic2; + t_ab8500_codec_cr21_low_pow_mic2 cr21_low_pow_mic2; + t_ab8500_codec_cr21_mic2_gain cr21_mic2_gain; + + /* CR22 */ + t_ab8500_codec_cr22_hsl_gain cr22_hsl_gain; + t_ab8500_codec_cr22_hsr_gain cr22_hsr_gain; + + /* CR23 */ + t_ab8500_codec_cr23_linl_gain cr23_linl_gain; + t_ab8500_codec_cr23_linr_gain cr23_linr_gain; + + /* CR24 */ + t_ab8500_codec_cr24_lintohsl_gain cr24_lintohsl_gain; + + /* CR25 */ + t_ab8500_codec_cr25_lintohsr_gain cr25_lintohsr_gain; + + /* CR26 */ + t_ab8500_codec_cr26_ad1nh cr26_ad1nh; + t_ab8500_codec_cr26_ad2nh cr26_ad2nh; + t_ab8500_codec_cr26_ad3nh cr26_ad3nh; + t_ab8500_codec_cr26_ad4nh cr26_ad4nh; + t_ab8500_codec_cr26_ad1_voice cr26_ad1_voice; + t_ab8500_codec_cr26_ad2_voice cr26_ad2_voice; + t_ab8500_codec_cr26_ad3_voice cr26_ad3_voice; + t_ab8500_codec_cr26_ad4_voice cr26_ad4_voice; + + /* CR27 */ + t_ab8500_codec_cr27_en_mastgen cr27_en_mastgen; + t_ab8500_codec_cr27_if1_bitclk_osr cr27_if1_bitclk_osr; + t_ab8500_codec_cr27_enfs_bitclk1 cr27_enfs_bitclk1; + t_ab8500_codec_cr27_if0_bitclk_osr cr27_if0_bitclk_osr; + t_ab8500_codec_cr27_enfs_bitclk0 cr27_enfs_bitclk0; + + /* CR28 */ + t_ab8500_codec_cr28_fsync0p cr28_fsync0p; + t_ab8500_codec_cr28_bitclk0p cr28_bitclk0p; + t_ab8500_codec_cr28_if0del cr28_if0del; + t_ab8500_codec_cr28_if0format cr28_if0format; + t_ab8500_codec_cr28_if0wl cr28_if0wl; + + /* CR29 */ + t_ab8500_codec_cr29_if0datoif1ad cr29_if0datoif1ad; + t_ab8500_codec_cr29_if0cktoif1ck cr29_if0cktoif1ck; + t_ab8500_codec_cr29_if1master cr29_if1master; + t_ab8500_codec_cr29_if1datoif0ad cr29_if1datoif0ad; + t_ab8500_codec_cr29_if1cktoif0ck cr29_if1cktoif0ck; + t_ab8500_codec_cr29_if0master cr29_if0master; + t_ab8500_codec_cr29_if0bfifoen cr29_if0bfifoen; + + /* CR30 */ + t_ab8500_codec_cr30_fsync1p cr30_fsync1p; + t_ab8500_codec_cr30_bitclk1p cr30_bitclk1p; + t_ab8500_codec_cr30_if1del cr30_if1del; + t_ab8500_codec_cr30_if1format cr30_if1format; + t_ab8500_codec_cr30_if1wl cr30_if1wl; + + /* CR31 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr31_adotoslot1; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr31_adotoslot0; + + /* CR32 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr32_adotoslot3; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr32_adotoslot2; + + /* CR33 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr33_adotoslot5; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr33_adotoslot4; + + /* CR34 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr34_adotoslot7; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr34_adotoslot6; + + /* CR35 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr35_adotoslot9; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr35_adotoslot8; + + /* CR36 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr36_adotoslot11; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr36_adotoslot10; + + /* CR37 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr37_adotoslot13; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr37_adotoslot12; + + /* CR38 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr38_adotoslot15; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr38_adotoslot14; + + /* CR39 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr39_adotoslot17; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr39_adotoslot16; + + /* CR40 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr40_adotoslot19; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr40_adotoslot18; + + /* CR41 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr41_adotoslot21; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr41_adotoslot20; + + /* CR42 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr42_adotoslot23; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr42_adotoslot22; + + /* CR43 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr43_adotoslot25; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr43_adotoslot24; + + /* CR44 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr44_adotoslot27; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr44_adotoslot26; + + /* CR45 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr45_adotoslot29; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr45_adotoslot28; + + /* CR46 */ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr46_adotoslot31; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation cr46_adotoslot30; + + /* CR47 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl7; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl6; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl5; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl4; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl3; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl2; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl1; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr47_hiz_sl0; + + /* CR48 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl15; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl14; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl13; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl12; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl11; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl10; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl9; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr48_hiz_sl8; + + /* CR49 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl23; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl22; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl21; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl20; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl19; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl18; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl17; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr49_hiz_sl16; + + /* CR50 */ + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl31; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl30; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl29; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl28; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl27; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl26; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl25; + t_ab8500_codec_cr47_to_cr50_hiz_sl cr50_hiz_sl24; + + /* CR51 */ + t_ab8500_codec_cr51_da12_voice cr51_da12_voice; + t_ab8500_codec_cr51_swapda12_34 cr51_swapda12_34; + t_ab8500_codec_cr51_sldai7toslado1 cr51_sldai7toslado1; + t_ab8500_codec_cr51_to_cr58_sltoda cr51_sltoda1; + + /* CR52 */ + t_ab8500_codec_cr52_sldai8toslado2 cr52_sldai8toslado2; + t_ab8500_codec_cr51_to_cr58_sltoda cr52_sltoda2; + + /* CR53 */ + t_ab8500_codec_cr53_da34_voice cr53_da34_voice; + t_ab8500_codec_cr53_sldai7toslado3 cr53_sldai7toslado3; + t_ab8500_codec_cr51_to_cr58_sltoda cr53_sltoda3; + + /* CR54 */ + t_ab8500_codec_cr54_sldai8toslado4 cr54_sldai8toslado4; + t_ab8500_codec_cr51_to_cr58_sltoda cr54_sltoda4; + + /* CR55 */ + t_ab8500_codec_cr55_da56_voice cr55_da56_voice; + t_ab8500_codec_cr55_sldai7toslado5 cr55_sldai7toslado5; + t_ab8500_codec_cr51_to_cr58_sltoda cr55_sltoda5; + + /* CR56 */ + t_ab8500_codec_cr56_sldai8toslado6 cr56_sldai8toslado6; + t_ab8500_codec_cr51_to_cr58_sltoda cr56_sltoda6; + + /* CR57 */ + t_ab8500_codec_cr57_sldai8toslado7 cr57_sldai8toslado7; + t_ab8500_codec_cr51_to_cr58_sltoda cr57_sltoda7; + + /* CR58 */ + t_ab8500_codec_cr58_sldai7toslado8 cr58_sldai7toslado8; + t_ab8500_codec_cr51_to_cr58_sltoda cr58_sltoda8; + + /* CR59 */ + t_ab8500_codec_cr59_parlhf cr59_parlhf; + t_ab8500_codec_cr59_parlvib cr59_parlvib; + t_ab8500_codec_cr59_classdvib1_swapen cr59_classdvib1_swapen; + t_ab8500_codec_cr59_classdvib2_swapen cr59_classdvib2_swapen; + t_ab8500_codec_cr59_classdhfl_swapen cr59_classdhfl_swapen; + t_ab8500_codec_cr59_classdhfr_swapen cr59_classdhfr_swapen; + + /* CR60 */ + t_ab8500_codec_cr60_classd_firbyp cr60_classd_firbyp; + t_ab8500_codec_cr60_classd_highvolen cr60_classd_highvolen; + + /* CR61 */ + t_ab8500_codec_cr61_classddith_hpgain cr61_classddith_hpgain; + t_ab8500_codec_cr61_classddith_wgain cr61_classddith_wgain; + + /* CR62 */ + t_ab8500_codec_cr62_dmic1sinc3 cr62_dmic1sinc3; + t_ab8500_codec_cr62_dmic2sinc3 cr62_dmic2sinc3; + t_ab8500_codec_cr62_dmic3sinc3 cr62_dmic3sinc3; + t_ab8500_codec_cr62_dmic4sinc3 cr62_dmic4sinc3; + t_ab8500_codec_cr62_dmic5sinc3 cr62_dmic5sinc3; + t_ab8500_codec_cr62_dmic6sinc3 cr62_dmic6sinc3; + + /* CR63 */ + t_ab8500_codec_cr63_datohslen cr63_datohslen; + t_ab8500_codec_cr63_datohsren cr63_datohsren; + t_ab8500_codec_cr63_ad1sel cr63_ad1sel; + t_ab8500_codec_cr63_ad2sel cr63_ad2sel; + t_ab8500_codec_cr63_ad3sel cr63_ad3sel; + t_ab8500_codec_cr63_ad5sel cr63_ad5sel; + t_ab8500_codec_cr63_ad6sel cr63_ad6sel; + t_ab8500_codec_cr63_ancsel cr63_ancsel; + + /* CR64 */ + t_ab8500_codec_cr64_datohfren cr64_datohfren; + t_ab8500_codec_cr64_datohflen cr64_datohflen; + t_ab8500_codec_cr64_hfrsel cr64_hfrsel; + t_ab8500_codec_cr64_hflsel cr64_hflsel; + t_ab8500_codec_cr64_stfir1sel cr64_stfir1sel; + t_ab8500_codec_cr64_stfir2sel cr64_stfir2sel; + + /* CR65 */ + t_ab8500_codec_cr65_fadedis_ad1 cr65_fadedis_ad1; + t_ab8500_codec_cr65_ad1gain cr65_ad1gain; + + /* CR66 */ + t_ab8500_codec_cr66_fadedis_ad2 cr66_fadedis_ad2; + t_ab8500_codec_cr66_ad2gain cr66_ad2gain; + + /* CR67 */ + t_ab8500_codec_cr67_fadedis_ad3 cr67_fadedis_ad3; + t_ab8500_codec_cr67_ad3gain cr67_ad3gain; + + /* CR68 */ + t_ab8500_codec_cr68_fadedis_ad4 cr68_fadedis_ad4; + t_ab8500_codec_cr68_ad4gain cr68_ad4gain; + + /* CR69 */ + t_ab8500_codec_cr69_fadedis_ad5 cr69_fadedis_ad5; + t_ab8500_codec_cr69_ad5gain cr69_ad5gain; + + /* CR70 */ + t_ab8500_codec_cr70_fadedis_ad6 cr70_fadedis_ad6; + t_ab8500_codec_cr70_ad6gain cr70_ad6gain; + + /* CR71 */ + t_ab8500_codec_cr71_fadedis_da1 cr71_fadedis_da1; + t_ab8500_codec_cr71_da1gain cr71_da1gain; + + /* CR72 */ + t_ab8500_codec_cr72_fadedis_da2 cr72_fadedis_da2; + t_ab8500_codec_cr72_da2gain cr72_da2gain; + + /* CR73 */ + t_ab8500_codec_cr73_fadedis_da3 cr73_fadedis_da3; + t_ab8500_codec_cr73_da3gain cr73_da3gain; + + /* CR74 */ + t_ab8500_codec_cr74_fadedis_da4 cr74_fadedis_da4; + t_ab8500_codec_cr74_da4gain cr74_da4gain; + + /* CR75 */ + t_ab8500_codec_cr75_fadedis_da5 cr75_fadedis_da5; + t_ab8500_codec_cr75_da5gain cr75_da5gain; + + /* CR76 */ + t_ab8500_codec_cr76_fadedis_da6 cr76_fadedis_da6; + t_ab8500_codec_cr76_da6gain cr76_da6gain; + + /* CR77 */ + t_ab8500_codec_cr77_fadedis_ad1l cr77_fadedis_ad1l; + t_ab8500_codec_cr77_ad1lbgain_to_hfl cr77_ad1lbgain_to_hfl; + + /* CR78 */ + t_ab8500_codec_cr78_fadedis_ad2l cr78_fadedis_ad2l; + t_ab8500_codec_cr78_ad2lbgain_to_hfr cr78_ad2lbgain_to_hfr; + + /* CR79 */ + t_ab8500_codec_cr79_hssinc1 cr79_hssinc1; + t_ab8500_codec_cr79_fadedis_hsl cr79_fadedis_hsl; + t_ab8500_codec_cr79_hsldgain cr79_hsldgain; + + /* CR80 */ + t_ab8500_codec_cr80_fade_speed cr80_fade_speed; + t_ab8500_codec_cr80_fadedis_hsr cr80_fadedis_hsr; + t_ab8500_codec_cr80_hsrdgain cr80_hsrdgain; + + /* CR81 */ + t_ab8500_codec_cr81_stfir1gain cr81_stfir1gain; + + /* CR82 */ + t_ab8500_codec_cr82_stfir2gain cr82_stfir2gain; + + /* CR83 */ + t_ab8500_codec_cr83_enanc cr83_enanc; + t_ab8500_codec_cr83_anciirinit cr83_anciirinit; + t_ab8500_codec_cr83_ancfirupdate cr83_ancfirupdate; + + /* CR84 */ + t_ab8500_codec_cr84_ancinshift cr84_ancinshift; + + /* CR85 */ + t_ab8500_codec_cr85_ancfiroutshift cr85_ancfiroutshift; + + /* CR86 */ + t_ab8500_codec_cr86_ancshiftout cr86_ancshiftout; + + /* CR87 */ + t_ab8500_codec_cr87_ancfircoeff_msb cr87_ancfircoeff_msb; + + /* CR88 */ + t_ab8500_codec_cr88_ancfircoeff_lsb cr88_ancfircoeff_lsb; + + /* CR89 */ + t_ab8500_codec_cr89_anciircoeff_msb cr89_anciircoeff_msb; + + /* CR90 */ + t_ab8500_codec_cr90_anciircoeff_lsb cr90_anciircoeff_lsb; + + /* CR91 */ + t_ab8500_codec_cr91_ancwarpdel_msb cr91_ancwarpdel_msb; + + /* CR92 */ + t_ab8500_codec_cr92_ancwarpdel_lsb cr92_ancwarpdel_lsb; + + /* CR93 */ + t_ab8500_codec_cr93_ancfirpeak_msb cr93_ancfirpeak_msb; + + /* CR94 */ + t_ab8500_codec_cr94_ancfirpeak_lsb cr94_ancfirpeak_lsb; + + /* CR95 */ + t_ab8500_codec_cr95_anciirpeak_msb cr95_anciirpeak_msb; + + /* CR96 */ + t_ab8500_codec_cr96_anciirpeak_lsb cr96_anciirpeak_lsb; + + /* CR97 */ + t_ab8500_codec_cr97_stfir_set cr97_stfir_set; + t_ab8500_codec_cr97_stfir_addr cr97_stfir_addr; + + /* CR98 */ + t_ab8500_codec_cr98_stfir_coeff_msb cr98_stfir_coeff_msb; + + /* CR99 */ + t_ab8500_codec_cr99_stfir_coeff_lsb cr99_stfir_coeff_lsb; + + /* CR100 */ + t_ab8500_codec_cr100_enstfirs cr100_enstfirs; + t_ab8500_codec_cr100_stfirstoif1 cr100_stfirstoif1; + t_ab8500_codec_cr100_stfir_busy cr100_stfir_busy; + + /* CR101 */ + t_ab8500_codec_cr101_hsoffst_mask cr101_hsoffst_mask; + t_ab8500_codec_cr101_fifofull_mask cr101_fifofull_mask; + t_ab8500_codec_cr101_fifoempty_mask cr101_fifoempty_mask; + t_ab8500_codec_cr101_dasat_mask cr101_dasat_mask; + t_ab8500_codec_cr101_adsat_mask cr101_adsat_mask; + t_ab8500_codec_cr101_addsp_mask cr101_addsp_mask; + t_ab8500_codec_cr101_dadsp_mask cr101_dadsp_mask; + t_ab8500_codec_cr101_firsid_mask cr101_firsid_mask; + + /* CR102 */ + t_ab8500_codec_cr102_it_hsoffst cr102_it_hsoffst; + t_ab8500_codec_cr102_it_fifofull cr102_it_fifofull; + t_ab8500_codec_cr102_it_fifoempty cr102_it_fifoempty; + t_ab8500_codec_cr102_it_dasat cr102_it_dasat; + t_ab8500_codec_cr102_it_adsat cr102_it_adsat; + t_ab8500_codec_cr102_it_addsp cr102_it_addsp; + t_ab8500_codec_cr102_it_dadsp cr102_it_dadsp; + t_ab8500_codec_cr102_it_firsid cr102_it_firsid; + + /* CR103 */ + t_ab8500_codec_cr103_vssready_mask cr103_vssready_mask; + t_ab8500_codec_cr103_shorthsl_mask cr103_shorthsl_mask; + t_ab8500_codec_cr103_shorthsr_mask cr103_shorthsr_mask; + t_ab8500_codec_cr103_shortear_mask cr103_shortear_mask; + + /* CR104 */ + t_ab8500_codec_cr104_it_vssready cr104_it_vssready; + t_ab8500_codec_cr104_it_shorthsl cr104_it_shorthsl; + t_ab8500_codec_cr104_it_shorthsr cr104_it_shorthsr; + t_ab8500_codec_cr104_it_shortear cr104_it_shortear; + + /* CR105 */ + t_ab8500_codec_cr105_bfifomsk cr105_bfifomsk; + t_ab8500_codec_cr105_bfifoint cr105_bfifoint; + + /* CR106 */ + t_ab8500_codec_cr106_bfifotx cr106_bfifotx; + + /* CR107 */ + t_ab8500_codec_cr107_bfifoexsl cr107_bfifoexsl; + t_ab8500_codec_cr107_prebitclk0 cr107_prebitclk0; + t_ab8500_codec_cr107_bfifomast cr107_bfifomast; + t_ab8500_codec_cr107_bfiforun cr107_bfiforun; + + /* CR108 */ + t_ab8500_codec_cr108_bfifoframsw cr108_bfifoframsw; + + /* CR109 */ + t_ab8500_codec_cr109_bfifowakeup cr109_bfifowakeup; + + /* CR110 */ + t_ab8500_codec_cr110_bfifosamples cr110_bfifosamples; + + /* CR111 */ + t_ab8500_codec_cr111_aud_ip_rev cr111_aud_ip_rev; + +} t_ab8500_codec_configuration; + +typedef enum { + AB8500_CODEC_DIRECTION_IN, + AB8500_CODEC_DIRECTION_OUT, + AB8500_CODEC_DIRECTION_INOUT +} t_ab8500_codec_direction; + +typedef enum { + AB8500_CODEC_AUDIO_INTERFACE_0, + AB8500_CODEC_AUDIO_INTERFACE_1 +} t_ab8500_codec_audio_interface; + +typedef enum { + AB8500_CODEC_MODE_HIFI, + AB8500_CODEC_MODE_VOICE, + AB8500_CODEC_MODE_MANUAL_SETTING +} t_ab8500_codec_mode; + +typedef enum { + AB8500_CODEC_DEST_HEADSET, + AB8500_CODEC_DEST_EARPIECE, + AB8500_CODEC_DEST_HANDSFREE, + AB8500_CODEC_DEST_VIBRATOR_L, + AB8500_CODEC_DEST_VIBRATOR_R, + AB8500_CODEC_DEST_FM_TX, + AB8500_CODEC_DEST_ALL +} t_ab8500_codec_dest; + +typedef enum { + AB8500_CODEC_SRC_LINEIN, + AB8500_CODEC_SRC_MICROPHONE_1A, + AB8500_CODEC_SRC_MICROPHONE_1B, + AB8500_CODEC_SRC_MICROPHONE_2, + AB8500_CODEC_SRC_D_MICROPHONE_1, + AB8500_CODEC_SRC_D_MICROPHONE_2, + AB8500_CODEC_SRC_D_MICROPHONE_3, + AB8500_CODEC_SRC_D_MICROPHONE_4, + AB8500_CODEC_SRC_D_MICROPHONE_5, + AB8500_CODEC_SRC_D_MICROPHONE_6, + AB8500_CODEC_SRC_D_MICROPHONE_12, + AB8500_CODEC_SRC_D_MICROPHONE_34, + AB8500_CODEC_SRC_D_MICROPHONE_56, + AB8500_CODEC_SRC_FM_RX, + AB8500_CODEC_SRC_ALL +} t_ab8500_codec_src; + +typedef struct { + t_uint8 slave_address_of_ab8500_codec; + t_ab8500_codec_direction ab8500_codec_direction; + t_ab8500_codec_mode ab8500_codec_mode_in; + t_ab8500_codec_mode ab8500_codec_mode_out; + t_ab8500_codec_audio_interface audio_interface; + t_ab8500_codec_src ab8500_codec_src; + t_ab8500_codec_dest ab8500_codec_dest; + t_uint8 in_left_volume; + t_uint8 in_right_volume; + t_uint8 out_left_volume; + t_uint8 out_right_volume; + + t_ab8500_codec_configuration ab8500_codec_configuration; +} t_ab8500_codec_system_context; + +#endif /* _AB8500_CODECP_H_ */ + +/* End of file AB8500_CODECP.h */ diff --git a/arch/arm/mach-ux500/include/mach/ab8500_codec_v1_0.h b/arch/arm/mach-ux500/include/mach/ab8500_codec_v1_0.h new file mode 100644 index 00000000000..a5b8a57f341 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ab8500_codec_v1_0.h @@ -0,0 +1,329 @@ +/*****************************************************************************/ +/** +* © ST-Ericsson, 2009 - All rights reserved +* Reproduction and Communication of this document is strictly prohibited +* unless specifically authorized in writing by ST-Ericsson +* +* \brief Public header file for AB8500 Codec +* \author ST-Ericsson +*/ +/*****************************************************************************/ + +#ifndef _AB8500_CODEC_V1_0_H_ +#define _AB8500_CODEC_V1_0_H_ + +/*--------------------------------------------------------------------- + * Includes + *--------------------------------------------------------------------*/ +#include "hcl_defs.h" +#include "debug.h" +#include +/*--------------------------------------------------------------------- + * Define + *--------------------------------------------------------------------*/ +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { + AB8500_CODEC_OK, + AB8500_CODEC_ERROR, + AB8500_CODEC_UNSUPPORTED_FEATURE, + AB8500_CODEC_INVALID_PARAMETER, + AB8500_CODEC_CONFIG_NOT_COHERENT, + AB8500_CODEC_TRANSACTION_FAILED + } t_ab8500_codec_error; + + typedef enum { + AB8500_CODEC_SRC_STATE_DISABLE, + AB8500_CODEC_SRC_STATE_ENABLE + } t_ab8500_codec_src_state; + + typedef enum { + AB8500_CODEC_DEST_STATE_DISABLE, + AB8500_CODEC_DEST_STATE_ENABLE + } t_ab8500_codec_dest_state; + + typedef enum { + AB8500_CODEC_MASTER_MODE_DISABLE, + AB8500_CODEC_MASTER_MODE_ENABLE + } t_ab8500_codec_master_mode; + + typedef enum { + AB8500_CODEC_SLOT0, + AB8500_CODEC_SLOT1, + AB8500_CODEC_SLOT2, + AB8500_CODEC_SLOT3, + AB8500_CODEC_SLOT4, + AB8500_CODEC_SLOT5, + AB8500_CODEC_SLOT6, + AB8500_CODEC_SLOT7, + AB8500_CODEC_SLOT8, + AB8500_CODEC_SLOT9, + AB8500_CODEC_SLOT10, + AB8500_CODEC_SLOT11, + AB8500_CODEC_SLOT12, + AB8500_CODEC_SLOT13, + AB8500_CODEC_SLOT14, + AB8500_CODEC_SLOT15, + AB8500_CODEC_SLOT16, + AB8500_CODEC_SLOT17, + AB8500_CODEC_SLOT18, + AB8500_CODEC_SLOT19, + AB8500_CODEC_SLOT20, + AB8500_CODEC_SLOT21, + AB8500_CODEC_SLOT22, + AB8500_CODEC_SLOT23, + AB8500_CODEC_SLOT24, + AB8500_CODEC_SLOT25, + AB8500_CODEC_SLOT26, + AB8500_CODEC_SLOT27, + AB8500_CODEC_SLOT28, + AB8500_CODEC_SLOT29, + AB8500_CODEC_SLOT30, + AB8500_CODEC_SLOT31, + AB8500_CODEC_SLOT_UNDEFINED + } t_ab8500_codec_slot; + + typedef enum { + AB8500_CODEC_DA_CHANNEL_NUMBER_1, + AB8500_CODEC_DA_CHANNEL_NUMBER_2, + AB8500_CODEC_DA_CHANNEL_NUMBER_3, + AB8500_CODEC_DA_CHANNEL_NUMBER_4, + AB8500_CODEC_DA_CHANNEL_NUMBER_5, + AB8500_CODEC_DA_CHANNEL_NUMBER_6, + AB8500_CODEC_DA_CHANNEL_NUMBER_7, + AB8500_CODEC_DA_CHANNEL_NUMBER_8, + AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED + } t_ab8500_codec_da_channel_number; + + typedef struct { + t_ab8500_codec_cr105_bfifomsk cr105_bfifomsk; + t_ab8500_codec_cr105_bfifoint cr105_bfifoint; + t_ab8500_codec_cr106_bfifotx cr106_bfifotx; + t_ab8500_codec_cr107_bfifoexsl cr107_bfifoexsl; + t_ab8500_codec_cr107_prebitclk0 cr107_prebitclk0; + t_ab8500_codec_cr107_bfifomast cr107_bfifomast; + t_ab8500_codec_cr107_bfiforun cr107_bfiforun; + t_ab8500_codec_cr108_bfifoframsw cr108_bfifoframsw; + t_ab8500_codec_cr109_bfifowakeup cr109_bfifowakeup; + } t_ab8500_codec_burst_fifo_config; + + typedef struct { + t_ab8500_codec_cr27_if1_bitclk_osr cr27_if1_bitclk_osr; + t_ab8500_codec_cr27_if0_bitclk_osr cr27_if0_bitclk_osr; + t_ab8500_codec_cr28_if0wl cr28_if0wl; + t_ab8500_codec_cr30_if1wl cr30_if1wl; + t_ab8500_codec_cr28_bitclk0p cr28_bitclk0p; + t_ab8500_codec_cr28_if0del cr28_if0del; + } t_ab8500_codec_tdm_config; + +/************************************************************/ +/*--------------------------------------------------------------------- + * Exported APIs + *--------------------------------------------------------------------*/ +/* Initialization */ + t_ab8500_codec_error AB8500_CODEC_Init(IN t_uint8 + slave_address_of_codec); + t_ab8500_codec_error AB8500_CODEC_Reset(void); + +/* Audio Codec basic configuration */ + t_ab8500_codec_error AB8500_CODEC_SetModeAndDirection(IN + t_ab8500_codec_direction + ab8500_codec_direction, + IN + t_ab8500_codec_mode + ab8500_codec_mode_in, + IN + t_ab8500_codec_mode + ab8500_codec_mode_out, + IN + t_ab8500_codec_tdm_config + const *const + p_tdm_config); + t_ab8500_codec_error AB8500_CODEC_SelectInput(IN t_ab8500_codec_src + ab8500_codec_src); + t_ab8500_codec_error AB8500_CODEC_SelectOutput(IN t_ab8500_codec_dest + ab8500_codec_dest); + +/* Burst FIFO configuration */ + t_ab8500_codec_error AB8500_CODEC_ConfigureBurstFifo(IN + t_ab8500_codec_burst_fifo_config + const *const + p_burst_fifo_config); + t_ab8500_codec_error AB8500_CODEC_EnableBurstFifo(void); + t_ab8500_codec_error AB8500_CODEC_DisableBurstFifo(void); + +/* Audio Codec Master mode configuration */ + t_ab8500_codec_error AB8500_CODEC_SetMasterMode(IN + t_ab8500_codec_master_mode + mode); + +/* APIs to be implemented by user */ + t_ab8500_codec_error AB8500_CODEC_Write(IN t_uint8 register_offset, + IN t_uint8 count, + IN t_uint8 * p_data); + t_ab8500_codec_error AB8500_CODEC_Read(IN t_uint8 register_offset, + IN t_uint8 count, + IN t_uint8 * p_dummy_data, + IN t_uint8 * p_data); + +/* Volume Management */ + t_ab8500_codec_error AB8500_CODEC_SetSrcVolume(IN t_ab8500_codec_src + src_device, + IN t_uint8 + in_left_volume, + IN t_uint8 + in_right_volume); + t_ab8500_codec_error AB8500_CODEC_SetDestVolume(IN t_ab8500_codec_dest + dest_device, + IN t_uint8 + out_left_volume, + IN t_uint8 + out_right_volume); + +/* Power management */ + t_ab8500_codec_error AB8500_CODEC_PowerDown(void); + t_ab8500_codec_error AB8500_CODEC_PowerUp(void); + +/* Interface Management */ + t_ab8500_codec_error AB8500_CODEC_SelectInterface(IN + t_ab8500_codec_audio_interface + audio_interface); + t_ab8500_codec_error AB8500_CODEC_GetInterface(OUT + t_ab8500_codec_audio_interface + * p_audio_interface); + +/* Slot Allocation */ + t_ab8500_codec_error AB8500_CODEC_ADSlotAllocation(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); + t_ab8500_codec_error AB8500_CODEC_DASlotAllocation(IN + t_ab8500_codec_da_channel_number + channel_number, + IN + t_ab8500_codec_cr51_to_cr58_sltoda + slot); + +/* Loopback Management */ + t_ab8500_codec_error AB8500_CODEC_SetAnalogLoopback(IN t_uint8 + out_left_volume, + IN t_uint8 + out_right_volume); + t_ab8500_codec_error AB8500_CODEC_RemoveAnalogLoopback(void); + +/* Bypass Management */ + t_ab8500_codec_error AB8500_CODEC_EnableBypassMode(void); + t_ab8500_codec_error AB8500_CODEC_DisableBypassMode(void); + +/* Power Control Management */ + t_ab8500_codec_error AB8500_CODEC_SrcPowerControl(IN t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state); + t_ab8500_codec_error AB8500_CODEC_DestPowerControl(IN + t_ab8500_codec_dest + dest_device, + t_ab8500_codec_dest_state + state); + +/* Version Management */ + t_ab8500_codec_error AB8500_CODEC_GetVersion(OUT t_version * p_version); + +#if 0 +/* Debug management */ + t_ab8500_codec_error AB8500_CODEC_SetDbgLevel(IN t_dbg_level dbg_level); + t_ab8500_codec_error AB8500_CODEC_GetDbgLevel(OUT t_dbg_level * + p_dbg_level); +#endif + +/* +** following is added by $kardad$ +*/ + +/* duplicate copy of enum from msp.h */ +/* for MSPConfiguration.in_clock_freq parameter to select msp clock freq */ + typedef enum { + CODEC_MSP_INPUT_FREQ_1MHZ = 1024, + CODEC_MSP_INPUT_FREQ_2MHZ = 2048, + CODEC_MSP_INPUT_FREQ_3MHZ = 3072, + CODEC_MSP_INPUT_FREQ_4MHZ = 4096, + CODEC_MSP_INPUT_FREQ_5MHZ = 5760, + CODEC_MSP_INPUT_FREQ_6MHZ = 6144, + CODEC_MSP_INPUT_FREQ_8MHZ = 8192, + CODEC_MSP_INPUT_FREQ_11MHZ = 11264, + CODEC_MSP_INPUT_FREQ_12MHZ = 12288, + CODEC_MSP_INPUT_FREQ_16MHZ = 16384, + CODEC_MSP_INPUT_FREQ_22MHZ = 22579, + CODEC_MSP_INPUT_FREQ_24MHZ = 24576, + CODEC_MSP_INPUT_FREQ_48MHZ = 49152 + } codec_msp_in_clock_freq_type; + +/* msp clock source internal/external for srg_clock_sel */ + typedef enum { + CODEC_MSP_APB_CLOCK = 0, + CODEC_MSP_SCK_CLOCK = 2, + CODEC_MSP_SCK_SYNC_CLOCK = 3 + } codec_msp_srg_clock_sel_type; + +/* Sample rate supported by Codec */ + + typedef enum { + CODEC_FREQUENCY_DONT_CHANGE = -100, + CODEC_SAMPLING_FREQ_RESET = -1, + CODEC_SAMPLING_FREQ_MINLIMIT = 7, + CODEC_SAMPLING_FREQ_8KHZ = 8, /*default */ + CODEC_SAMPLING_FREQ_11KHZ = 11, + CODEC_SAMPLING_FREQ_12KHZ = 12, + CODEC_SAMPLING_FREQ_16KHZ = 16, + CODEC_SAMPLING_FREQ_22KHZ = 22, + CODEC_SAMPLING_FREQ_24KHZ = 24, + CODEC_SAMPLING_FREQ_32KHZ = 32, + CODEC_SAMPLING_FREQ_44KHZ = 44, + CODEC_SAMPLING_FREQ_48KHZ = 48, + CODEC_SAMPLING_FREQ_64KHZ = 64, /*the frequencies below this line are not supported in stw5094A */ + CODEC_SAMPLING_FREQ_88KHZ = 88, + CODEC_SAMPLING_FREQ_96KHZ = 96, + CODEC_SAMPLING_FREQ_128KHZ = 128, + CODEC_SAMPLING_FREQ_176KHZ = 176, + CODEC_SAMPLING_FREQ_192KHZ = 192, + CODEC_SAMPLING_FREQ_MAXLIMIT = 193 + } t_codec_sample_frequency; + +#define RESET -1 +#define DEFAULT -100 +/***********************************************************/ +/* +** following stuff is added to compile code without debug print support $kardad$ +*/ + +#define DBGEXIT(cr) +#define DBGEXIT0(cr) +#define DBGEXIT1(cr,ch,p1) +#define DBGEXIT2(cr,ch,p1,p2) +#define DBGEXIT3(cr,ch,p1,p2,p3) +#define DBGEXIT4(cr,ch,p1,p2,p3,p4) +#define DBGEXIT5(cr,ch,p1,p2,p3,p4,p5) +#define DBGEXIT6(cr,ch,p1,p2,p3,p4,p5,p6) + +#define DBGENTER() +#define DBGENTER0() +#define DBGENTER1(ch,p1) +#define DBGENTER2(ch,p1,p2) +#define DBGENTER3(ch,p1,p2,p3) +#define DBGENTER4(ch,p1,p2,p3,p4) +#define DBGENTER5(ch,p1,p2,p3,p4,p5) +#define DBGENTER6(ch,p1,p2,p3,p4,p5,p6) + +#define DBGPRINT(dbg_level,dbg_string) +#define DBGPRINTHEX(dbg_level,dbg_string,uint32) +#define DBGPRINTDEC(dbg_level,dbg_string,uint32) +/***********************************************************/ + +#ifdef __cplusplus +} /* allow C++ to use these headers */ +#endif /* __cplusplus */ +#endif /* _AB8500_CODEC_H_ */ +/* End of file ab8500_codec.h*/ diff --git a/arch/arm/mach-ux500/include/mach/u8500_acodec_ab8500.h b/arch/arm/mach-ux500/include/mach/u8500_acodec_ab8500.h new file mode 100644 index 00000000000..0575bbdb730 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/u8500_acodec_ab8500.h @@ -0,0 +1,284 @@ +/* Header file for u8500 audiocodec specific data structures, enums + * and private & public functions. + * Author: Deepak Karda + * Copyright (C) 2009 ST-Ericsson Pvt. Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _U8500_ACODEC_AB8500_H_ +#define _U8500_ACODEC_AB8500_H_ + +#include +#include + +#ifdef CONFIG_U8500_AB8500_CUT10 +#include +//#include +#else /*CONFIG_U8500_4500_ED */ +#include +#include +#endif + +#define NUMBER_OUTPUT_DEVICE 5 +#define NUMBER_INPUT_DEVICE 13 +#define NUMBER_LOOPBACK_STATE 2 +#define NUMBER_SWITCH_STATE 2 +#define NUMBER_POWER_STATE 2 +#define NUMBER_TDM_MODE_STATE 2 +#define NUMBER_DIRECT_RENDERING_STATE 2 +#define NUMBER_PCM_RENDERING_STATE 3 + +#define CODEC_MUTE 0x20 +#define DEFAULT_VOLUME 0x64 +#define DEFAULT_GAIN 0x32 +#define VOL_MAX 0x64 +#define VOL_MIN 0x00 +#define DEFAULT_OUTPUT_DEVICE AB8500_CODEC_DEST_HEADSET +#define DEFAULT_INPUT_DEVICE AB8500_CODEC_SRC_D_MICROPHONE_1 +#define DEFAULT_LOOPBACK_STATE DISABLE +#define DEFAULT_SWITCH_STATE DISABLE +#define DEFAULT_TDM8_CH_MODE_STATE DISABLE +#define DEFAULT_DIRECT_RENDERING_STATE DISABLE +#define DEFAULT_BURST_FIFO_STATE RENDERING_DISABLE +#define DEFAULT_FM_PLAYBACK_STATE RENDERING_DISABLE +#define DEFAULT_FM_TX_STATE RENDERING_DISABLE + +#define MIN_RATE_PLAYBACK 48000 +#define MAX_RATE_PLAYBACK 48000 +#define MIN_RATE_CAPTURE 48000 +#define MAX_RATE_CAPTURE 48000 +#define MAX_NO_OF_RATES 1 + +#define ALSA_MSP_BT_NUM 0 +#define ALSA_MSP_PCM_NUM 1 +#define ALSA_MSP_HDMI_NUM 2 + +#define I2S_CLIENT_MSP0 0 +#define I2S_CLIENT_MSP1 1 +#define I2S_CLIENT_MSP2 2 + +typedef enum { + DISABLE, + ENABLE +} t_u8500_bool_state; + +typedef enum { + RENDERING_DISABLE, + RENDERING_ENABLE, + RENDERING_PENDING +} t_u8500_pmc_rendering_state; + +typedef enum { + ACODEC_CONFIG_REQUIRED, + ACODEC_CONFIG_NOT_REQUIRED +} t_u8500_acodec_config_need; + +typedef enum { + CLASSICAL_MODE, + TDM_8_CH_MODE +} t_u8500_mode; + +typedef struct { + unsigned int left_volume; + unsigned int right_volume; + unsigned int mute_state; + t_u8500_bool_state power_state; +} u8500_io_dev_config_t; + +typedef enum { + NO_USER = 0, + USER_ALSA = 2, /*To make it equivalent to user id for MSP */ + USER_SAA, +} t_acodec_user; + +typedef struct { + u8500_io_dev_config_t output_config[NUMBER_OUTPUT_DEVICE]; + u8500_io_dev_config_t input_config[NUMBER_INPUT_DEVICE]; + //t_acodec_user user; + t_acodec_user cur_user; +} t_u8500_codec_system_context; + +typedef enum { + T_CODEC_SAMPLING_FREQ_48KHZ = 48, +} acodec_sample_frequency; + +struct acodec_configuration { + t_ab8500_codec_direction direction; + acodec_sample_frequency input_frequency; + acodec_sample_frequency output_frequency; + codec_msp_srg_clock_sel_type mspClockSel; + codec_msp_in_clock_freq_type mspInClockFreq; + u32 channels; + t_acodec_user user; + t_u8500_acodec_config_need acodec_config_need; + t_u8500_bool_state direct_rendering_mode; + t_u8500_bool_state tdm8_ch_mode; + t_u8500_bool_state digital_loopback; + void (*handler) (void *data); + void *tx_callback_data; + void *rx_callback_data; +}; + +typedef enum { + ACODEC_DISABLE_ALL, + ACODEC_DISABLE_TRANSMIT, + ACODEC_DISABLE_RECEIVE, +} t_acodec_disable; + +struct i2sdrv_data { + struct i2s_device *i2s; + spinlock_t i2s_lock; + /* buffer is NULL unless this device is open (users > 0) */ + int flag; + u32 tx_status; + u32 rx_status; +}; + +#define MAX_I2S_CLIENTS 3 //0=BT, 1=ACODEC, 2=HDMI + +/*extern t_ab8500_codec_error u8500_acodec_set_volume(int input_vol_left, + int input_vol_right, + int output_vol_left, + int output_vol_right, t_acodec_user user);*/ + +extern t_ab8500_codec_error u8500_acodec_open(int client_id, int stream_id); +//extern t_ab8500_codec_error u8500_acodec_pause_transfer(void); +//extern t_ab8500_codec_error u8500_acodec_unpause_transfer(void); +extern t_ab8500_codec_error u8500_acodec_send_data(int client_id, void *data, + size_t bytes, int dma_flag); +extern t_ab8500_codec_error u8500_acodec_receive_data(int client_id, void *data, + size_t bytes, + int dma_flag); +extern t_ab8500_codec_error u8500_acodec_close(int client_id, + t_acodec_disable flag); +extern t_ab8500_codec_error u8500_acodec_tx_rx_data(int client_id, + void *tx_data, + size_t tx_bytes, + void *rx_data, + size_t rx_bytes, + int dma_flag); + +extern t_ab8500_codec_error u8500_acodec_set_output_volume(t_ab8500_codec_dest + dest_device, + int left_volume, + int right_volume, + t_acodec_user user); + +extern t_ab8500_codec_error u8500_acodec_get_output_volume(t_ab8500_codec_dest + dest_device, + int *p_left_volume, + int *p_right_volume, + t_acodec_user user); + +extern t_ab8500_codec_error u8500_acodec_set_input_volume(t_ab8500_codec_src + src_device, + int left_volume, + int right_volume, + t_acodec_user user); + +extern t_ab8500_codec_error u8500_acodec_get_input_volume(t_ab8500_codec_src + src_device, + int *p_left_volume, + int *p_right_volume, + t_acodec_user user); + +extern t_ab8500_codec_error u8500_acodec_toggle_analog_lpbk(t_u8500_bool_state + lpbk_state, + t_acodec_user user); + +extern t_ab8500_codec_error u8500_acodec_toggle_digital_lpbk(t_u8500_bool_state + lpbk_state, + t_ab8500_codec_dest + dest_device, + t_ab8500_codec_src + src_device, + t_acodec_user user, + t_u8500_bool_state + tdm8_ch_mode); + +extern t_ab8500_codec_error +u8500_acodec_toggle_playback_mute_control(t_ab8500_codec_dest dest_device, + t_u8500_bool_state mute_state, + t_acodec_user user); +extern t_ab8500_codec_error +u8500_acodec_toggle_capture_mute_control(t_ab8500_codec_src src_device, + t_u8500_bool_state mute_state, + t_acodec_user user); + +extern t_ab8500_codec_error u8500_acodec_enable_audio_mode(struct + acodec_configuration + *acodec_config); +/*extern t_ab8500_codec_error u8500_acodec_enable_voice_mode(struct acodec_configuration *acodec_config);*/ + +extern t_ab8500_codec_error u8500_acodec_select_input(t_ab8500_codec_src + input_device, + t_acodec_user user, + t_u8500_mode mode); +extern t_ab8500_codec_error u8500_acodec_select_output(t_ab8500_codec_dest + output_device, + t_acodec_user user, + t_u8500_mode mode); + +extern t_ab8500_codec_error u8500_acodec_allocate_ad_slot(t_ab8500_codec_src + input_device, + t_u8500_mode mode); +extern t_ab8500_codec_error u8500_acodec_unallocate_ad_slot(t_ab8500_codec_src + input_device, + t_u8500_mode mode); +extern t_ab8500_codec_error u8500_acodec_allocate_da_slot(t_ab8500_codec_dest + output_device, + t_u8500_mode mode); +extern t_ab8500_codec_error u8500_acodec_unallocate_da_slot(t_ab8500_codec_dest + output_device, + t_u8500_mode mode); + +extern t_ab8500_codec_error u8500_acodec_set_src_power_cntrl(t_ab8500_codec_src + input_device, + t_u8500_bool_state + pwr_state); +extern t_ab8500_codec_error +u8500_acodec_set_dest_power_cntrl(t_ab8500_codec_dest output_device, + t_u8500_bool_state pwr_state); + +extern t_u8500_bool_state u8500_acodec_get_src_power_state(t_ab8500_codec_src + input_device); +extern t_u8500_bool_state u8500_acodec_get_dest_power_state(t_ab8500_codec_dest + output_device); +extern t_ab8500_codec_error +u8500_acodec_set_burst_mode_fifo(t_u8500_pmc_rendering_state fifo_state); + +extern t_ab8500_codec_error u8500_acodec_unsetuser(t_acodec_user user); +extern t_ab8500_codec_error u8500_acodec_setuser(t_acodec_user user); + +extern void codec_power_init(void); +extern void u8500_acodec_powerdown(void); + +//t_ab8500_codec_error acodec_msp_enable(t_touareg_codec_sample_frequency freq,int channels, t_acodec_user user); + +#define TRG_CODEC_ADDRESS_ON_SPI_BUS (0x0D) + +extern int ab8500_write(u8 block, u32 adr, u8 data); +extern int ab8500_read(u8 block, u32 adr); + +#if 0 +#define FUNC_ENTER() printk("\n -Enter : %s",__FUNCTION__) +#define FUNC_EXIT() printk("\n -Exit : %s",__FUNCTION__) +#else +#define FUNC_ENTER() +#define FUNC_EXIT() +#endif +#endif /*END OF HEADSER FILE */ diff --git a/sound/Kconfig b/sound/Kconfig index 1fef141ef8e..c34965dbbec 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -23,6 +23,46 @@ menuconfig SOUND and read ; the module will be called soundcore. +# added for U8500 audio codec device +config U8500_ACODEC + tristate "U8500 audio codec generic module (used both by SAA and ALSA)" + depends on STM_MSP_I2S + default Y + help + Say Y here if you have a U8500 based device + and want to use its audio codec chip. + + To compile this driver as a module, choose M here: the module + will be called u8500mod_acodec. + +choice + prompt "Audio codec type" + depends on U8500_ACODEC + default U8500_AB8500_ED + + config U8500_AB8500_ED + bool "U8500 ab8500 v0 audio codec" + + config U8500_AB8500_CUT10 + bool "U8500 ab8500 v1.x audio codec" + +endchoice + +choice + prompt "Driver mode" + depends on U8500_ACODEC + default U8500_ACODEC_DMA + + config U8500_ACODEC_DMA + bool "DMA mode" + + config U8500_ACODEC_POLL + bool "Polling mode" + + config U8500_ACODEC_INTR + bool "Interrupt mode" +endchoice + if SOUND config SOUND_OSS_CORE diff --git a/sound/Makefile b/sound/Makefile index ce9132b1c39..b5963c8b70d 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -16,4 +16,11 @@ ifeq ($(CONFIG_SND),y) obj-y += last.o endif +obj-$(CONFIG_U8500_ACODEC) += u8500mod_acodec.o +ifeq ($(CONFIG_U8500_AB8500_CUT10),y) + u8500mod_acodec-objs := u8500_acodec_ab8500.o ab8500_codec_v1_0.o +else + u8500mod_acodec-objs := u8500_acodec_ab8500.o ab8500_codec.o +endif + soundcore-objs := sound_core.o diff --git a/sound/ab8500_codec.c b/sound/ab8500_codec.c new file mode 100644 index 00000000000..fd4975bbdce --- /dev/null +++ b/sound/ab8500_codec.c @@ -0,0 +1,6697 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: ST-Ericsson + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + + /*---------------------------------------------------------------------------- + * Includes + *---------------------------------------------------------------------------*/ +#include +#include + +/*--------------------------------------------------------------------------* + * debug stuff * + *--------------------------------------------------------------------------*/ +#ifdef __DEBUG +#define MY_DEBUG_LEVEL_VAR_NAME myDebugLevel_AB8500_CODEC +#define MY_DEBUG_ID myDebugID_AB8500_CODEC +PRIVATE t_dbg_level MY_DEBUG_LEVEL_VAR_NAME = DEBUG_LEVEL0; +PRIVATE t_dbg_id MY_DEBUG_ID = AB8500_CODEC_HCL_DBG_ID; +#endif + +/*--------------------------------------------------------------------------* + * Global data for interrupt mode management * + *--------------------------------------------------------------------------*/ +PRIVATE t_ab8500_codec_system_context g_ab8500_codec_system_context; + +/*--------------------------------------------------------------------------* + * Default Values * + *--------------------------------------------------------------------------*/ +#define AB8500_CODEC_DEFAULT_SLAVE_ADDRESS_OF_CODEC 0xD +#define AB8500_CODEC_DEFAULT_DIRECTION AB8500_CODEC_DIRECTION_OUT + +#define AB8500_CODEC_DEFAULT_MODE_IN AB8500_CODEC_MODE_VOICE +#define AB8500_CODEC_DEFAULT_MODE_OUT AB8500_CODEC_MODE_VOICE + +#define AB8500_CODEC_DEFAULT_INPUT_SRC AB8500_CODEC_SRC_MICROPHONE_1A +#define AB8500_CODEC_DEFAULT_OUTPUT_DEST AB8500_CODEC_DEST_HEADSET + +#define AB8500_CODEC_DEFAULT_VOLUME_LEFT_IN 75 +#define AB8500_CODEC_DEFAULT_VOLUME_RIGHT_IN 75 +#define AB8500_CODEC_DEFAULT_VOLUME_LEFT_OUT 75 +#define AB8500_CODEC_DEFAULT_VOLUME_RIGHT_OUT 75 + +/*--------------------------------------------------------------------- + * PRIVATE APIs + *--------------------------------------------------------------------*/ +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch1(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch2(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch3(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch4(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch1(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state); +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch2(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state); +PRIVATE t_ab8500_codec_error ab8500_codec_SetModeAndDirectionUpdateCR(void); +PRIVATE t_ab8500_codec_error ab8500_codec_SetSrcVolumeUpdateCR(void); +PRIVATE t_ab8500_codec_error ab8500_codec_SetDestVolumeUpdateCR(void); +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionIN(void); +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionOUT(void); +PRIVATE t_ab8500_codec_error ab8500_codec_DestPowerControlUpdateCR(void); + +/********************************************************************************************/ +/* Name: ab8500_codec_SingleWrite */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_SingleWrite(t_uint8 register_offset, + t_uint8 data) +{ + return (t_ab8500_codec_error) (AB8500_CODEC_Write + (register_offset, 0x01, &data)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_SingleRead */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_SingleRead(t_uint8 register_offset, + t_uint8 data) +{ + t_uint8 dummy_data = 0xAA; + + return (t_ab8500_codec_error) (AB8500_CODEC_Read + (register_offset, 0x01, &dummy_data, + &data)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR0 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR0(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr0_powerup, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR0_POWERUP); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr0_enaana, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR0_ENAANA); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR0, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR1 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR1(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr1_swreset, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR1_SWRESET); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR1, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR2 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR2(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr2_enad1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR2_ENAD1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr2_enad2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR2_ENAD2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr2_enad3, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR2_ENAD3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr2_enad4, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR2_ENAD4); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr2_enad5, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR2_ENAD5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr2_enad6, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR2_ENAD6); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR2, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR3 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR3(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr3_enda1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR3_ENDA1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr3_enda2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR3_ENDA2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr3_enda3, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR3_ENDA3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr3_enda4, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR3_ENDA4); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr3_enda5, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR3_ENDA5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr3_enda6, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR3_ENDA6); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR3, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR4 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR4(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr4_lowpowhs, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR4_LOWPOWHS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr4_lowpowdachs, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR4_LOWPOWDACHS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr4_lowpowear, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR4_LOWPOWEAR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr4_ear_sel_cm, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR4_EAR_SEL_CM); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr4_hs_hp_dis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR4_HS_HP_DIS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr4_ear_hp_dis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR4_EAR_HP_DIS); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR4, value)); +} +#endif + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR5 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR5(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_enmic1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_ENMIC1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_enmic2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_ENMIC2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_enlinl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_ENLINL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_enlinr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_ENLINR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_mutmic1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_MUTMIC1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_mutmic2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_MUTMIC2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_mutlinl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_MUTELINL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr5_mutlinr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR5_MUTELINR); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR5, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR6 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR6(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr6_endmic1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR6_ENDMIC1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr6_endmic2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR6_ENDMIC2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr6_endmic3, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR6_ENDMIC3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr6_endmic4, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR6_ENDMIC4); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr6_endmic5, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR6_ENDMIC5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr6_endmic6, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR6_ENDMIC6); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR6, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR7 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR7(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr7_mic1sel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR7_MIC1SEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr7_linrsel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR7_LINRSEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr7_endrvhsl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR7_ENDRVHSL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr7_endrvhsr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR7_ENDRVHSR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr7_enadcmic, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR7_ENADCMIC); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr7_enadclinl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR7_ENADCLINL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr7_enadclinr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR7_ENADCLINR); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR7, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR8 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR8(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_cp_dis_pldwn, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_CP_DIS_PLDWN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_enear, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_ENEAR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_enhsl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_ENHSL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_enhsr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_ENHSR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_enhfl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_ENHFL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_enhfr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_ENHFR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_envibl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_ENVIBL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr8_envibr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR8_ENVIBR); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR8, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR9 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR9(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr9_endacear, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR9_ENADACEAR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr9_endachsl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR9_ENADACHSL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr9_endachsr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR9_ENADACHSR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr9_endachfl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR9_ENADACHFL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr9_endachfr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR9_ENADACHFR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr9_endacvibl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR9_ENADACVIBL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr9_endacvibr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR9_ENADACVIBR); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR9, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR10 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR10(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr10_muteear, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR10_MUTEEAR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr10_mutehsl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR10_MUTEHSL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr10_mutehsr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR10_MUTEHSR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr10_mutehfl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR10_MUTEHFL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr10_mutehfr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR10_MUTEHFR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr10_mutevibl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR10_MUTEVIBL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr10_mutevibr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR10_MUTEVIBR); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR10, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR11 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR11(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_earshortpwd, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_ENSHORTPWD); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_earshortdis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_EARSHORTDIS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_hslshortdis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_HSLSHORTDIS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_hsrshortdis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_HSRSHORTDIS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_hflshortdis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_HFLSHORTDIS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_hfrshortdis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_HFRSHORTDIS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_viblshortdis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_VIBLSHORTDIS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr11_vibrshortdis, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR11_VIBRSHORTDIS); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR11, value)); +} + +#endif + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR12 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR12(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr12_encphs, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR12_ENCPHS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr12_hsautotime, + AB8500_CODEC_MASK_THREE_BITS, AB8500_CODEC_CR12_HSAUTOTIME); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr12_hsautoensel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR12_HSAUTOENSEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr12_hsautoen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR12_HSAUTOEN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR12, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR13 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR13(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr13_envdet_hthresh, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR13_ENVDET_HTHRESH); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr13_envdet_lthresh, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR13_ENVDET_LTHRESH); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR13, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR14 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR14(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr14_smpslven, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR14_SMPSLVEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr14_envdetsmpsen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR14_ENVDETSMPSEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr14_cplven, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR14_CPLVEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr14_envdetcpen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR14_ENVDETCPEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr14_envet_time, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR14_ENVDET_TIME); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR14, value)); +} +#endif + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR15 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR15(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmtovibl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMTOVIBL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmtovibr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMTOVIBR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmlctrl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMLCTRL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmrctrl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMRCTRL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmnlctrl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMNLCTRL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmplctrl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMPLCTRL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmnrctrl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMNRCTRL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr15_pwmprctrl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR15_PWMPRCTRL); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR15, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR16 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR16(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr16_pwmnlpol, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR16_PWMNLPOL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr16_pwmnldutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, AB8500_CODEC_CR16_PWMNLDUTYCYCLE); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR16, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR17 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR17(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr17_pwmplpol, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR17_PWMPLPOL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr17_pwmpldutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, AB8500_CODEC_CR17_PWMLPDUTYCYCLE); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR17, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR18 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR18(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr18_pwmnrpol, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR18_PWMNRPOL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr18_pwmnrdutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, AB8500_CODEC_CR18_PWMNRDUTYCYCLE); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR18, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR19 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR19(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr19_pwmprpol, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR19_PWMPRPOL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr19_pwmprdutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, AB8500_CODEC_CR19_PWMRPDUTYCYCLE); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR19, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR20 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR20(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr20_en_se_mic1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR20_EN_SE_MIC1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr20_mic1_gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR20_MIC1_GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR20, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR21 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR21(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr21_en_se_mic2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR21_EN_SE_MIC2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr21_mic2_gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR21_MIC2_GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR21, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR22 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR22(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr22_hsl_gain, + AB8500_CODEC_MASK_THREE_BITS, AB8500_CODEC_CR22_HSL_GAIN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr22_linl_gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR22_LINL_GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR22, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR23 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR23(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr23_hsr_gain, + AB8500_CODEC_MASK_THREE_BITS, AB8500_CODEC_CR23_HSR_GAIN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr23_linr_gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR23_LINR_GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR23, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR24 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR24(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr24_lintohsl_gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR24_LINTOHSL_GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR24, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR25 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR25(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr25_lintohsr_gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR25_LINTOHSR_GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR25, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR26 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR26(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad1nh, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD1NH); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad2nh, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD2NH); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad3nh, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD3NH); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad4nh, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD4NH); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad1_voice, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD1_VOICE); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad2_voice, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD2_VOICE); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad3_voice, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD3_VOICE); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr26_ad4_voice, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR26_AD4_VOICE); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR26, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR27 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR27(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr27_en_mastgen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR27_EN_MASTGEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr27_if1_bitclk_osr, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR27_IF1_BITCLK_OSR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr27_enfs_bitclk1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR27_ENFS_BITCLK1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr27_if0_bitclk_osr, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR27_IF0_BITCLK_OSR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr27_enfs_bitclk0, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR27_ENFS_BITCLK0); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR27, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR28 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR28(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr28_fsync0p, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR28_FSYNC0P); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr28_bitclk0p, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR28_BITCLK0P); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr28_if0del, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR28_IF0DEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr28_if0format, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR28_IF0FORMAT); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr28_if0wl, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR28_IF0WL); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR28, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR29 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR29(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr29_if0datoif1ad, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR29_IF0DATOIF1AD); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr29_if0cktoif1ck, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR29_IF0CKTOIF1CK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr29_if1master, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR29_IF1MASTER); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr29_if1datoif0ad, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR29_IF1DATOIF0AD); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr29_if1cktoif0ck, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR29_IF1CKTOIF0CK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr29_if0master, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR29_IF0MASTER); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr29_if0bfifoen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR29_IF0BFIFOEN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR29, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR30 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR30(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr30_fsync1p, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR30_FSYNC1P); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr30_bitclk1p, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR30_BITCLK1P); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr30_if1del, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR30_IF1DEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr30_if1format, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR30_IF1FORMAT); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr30_if1wl, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR30_IF1WL); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR30, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR31 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR31(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr31_adotoslot1, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR31_ADOTOSLOT1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr31_adotoslot0, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR31_ADOTOSLOT0); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR31, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR32 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR32(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr32_adotoslot3, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR32_ADOTOSLOT3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr32_adotoslot2, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR32_ADOTOSLOT2); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR32, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR33 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR33(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr33_adotoslot5, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR33_ADOTOSLOT5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr33_adotoslot4, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR33_ADOTOSLOT4); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR33, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR34 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR34(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr34_adotoslot7, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR34_ADOTOSLOT7); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr34_adotoslot6, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR34_ADOTOSLOT6); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR34, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR35 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR35(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr35_adotoslot9, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR35_ADOTOSLOT9); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr35_adotoslot8, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR35_ADOTOSLOT8); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR35, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR36 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR36(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr36_adotoslot11, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR36_ADOTOSLOT11); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr36_adotoslot10, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR36_ADOTOSLOT10); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR36, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR37 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR37(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr37_adotoslot13, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR37_ADOTOSLOT13); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr37_adotoslot12, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR37_ADOTOSLOT12); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR37, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR38 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR38(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr38_adotoslot15, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR38_ADOTOSLOT15); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr38_adotoslot14, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR38_ADOTOSLOT14); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR38, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR39 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR39(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr39_adotoslot17, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR39_ADOTOSLOT17); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr39_adotoslot16, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR39_ADOTOSLOT16); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR39, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR40 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR40(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr40_adotoslot19, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR40_ADOTOSLOT19); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr40_adotoslot18, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR40_ADOTOSLOT18); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR40, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR41 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR41(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr41_adotoslot21, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR41_ADOTOSLOT21); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr41_adotoslot20, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR41_ADOTOSLOT20); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR41, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR42 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR42(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr42_adotoslot23, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR42_ADOTOSLOT23); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr42_adotoslot22, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR42_ADOTOSLOT22); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR42, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR43 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR43(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr43_adotoslot25, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR43_ADOTOSLOT25); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr43_adotoslot24, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR43_ADOTOSLOT24); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR43, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR44 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR44(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr44_adotoslot27, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR44_ADOTOSLOT27); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr44_adotoslot26, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR44_ADOTOSLOT26); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR44, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR45 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR45(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr45_adotoslot29, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR45_ADOTOSLOT29); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr45_adotoslot28, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR45_ADOTOSLOT28); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR45, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR46 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR46(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr46_adotoslot31, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR46_ADOTOSLOT31); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr46_adotoslot30, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR46_ADOTOSLOT30); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR46, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR47 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR47(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl7, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL7); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl6, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL6); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl5, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl4, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL4); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl3, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr47_hiz_sl0, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR47_HIZ_SL0); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR47, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR48 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR48(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl15, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL15); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl14, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL14); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl13, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL13); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl12, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL12); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl11, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL11); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl10, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL10); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl9, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL9); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr48_hiz_sl8, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR48_HIZ_SL8); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR48, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR49 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR49(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl23, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL23); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl22, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL22); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl21, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL21); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl20, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL20); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl19, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL19); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl18, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL18); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl17, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL17); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr49_hiz_sl16, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR49_HIZ_SL16); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR49, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR50 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR50(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl31, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL31); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl30, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL30); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl29, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL29); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl28, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL28); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl27, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL27); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl26, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL26); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl25, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL25); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr50_hiz_sl24, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR50_HIZ_SL24); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR50, value)); +} + +#endif + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR51 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR51(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr51_da12_voice, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR51_DA12_VOICE); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr51_sldai1toslado1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR51_SLDAI1TOSLADO1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr51_sltoda1, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR51_SLTODA1); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR51, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR52 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR52(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr52_sldai2toslado2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR52_SLDAI1TOSLADO2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr52_sltoda2, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR52_SLTODA2); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR52, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR53 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR53(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr53_da34_voice, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR53_DA34_VOICE); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr53_sldai3toslado3, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR53_SLDAI1TOSLADO3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr53_sltoda3, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR53_SLTODA3); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR53, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR54 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR54(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr54_sldai4toslado4, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR54_SLDAI1TOSLADO4); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr54_sltoda4, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR54_SLTODA4); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR54, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR55 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR55(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr55_da56_voice, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR55_DA56_VOICE); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr55_sldai5toslado5, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR55_SLDAI1TOSLADO5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr55_sltoda5, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR55_SLTODA5); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR55, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR56 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR56(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr56_sldai6toslado7, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR56_SLDAI1TOSLADO6); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr56_sltoda6, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR56_SLTODA6); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR56, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR57 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR57(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr57_bfifull_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR57_BFIFULL_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr57_bfiempt_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR57_BFIEMPT_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr57_dachan_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR57_DACHAN_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr57_gain_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR57_GAIN_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr57_dspad_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR57_DSPAD_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr57_dspda_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR57_DSPDA_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr57_stfir_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR57_STFIR_MSK); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR57, value)); +} + +/* CR58 is Read Only */ +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR59 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR59(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_vssready_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_VSSREADY_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_shrtvibl_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_SHRTVIBL_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_shrtvibr_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_SHRTVIBR_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_shrthfl_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_SHRTHFL_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_shrthfr_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_SHRTHFR_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_shrthsl_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_SHRTHSL_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_shrthsr_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_SHRTHSR_MSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr59_shrtear_msk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR59_SHRTEAR_MSK); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR59, value)); +} + +/* CR60 is Read Only */ +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR61 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR61(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + /* 5 bits are Read Only */ + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr61_fade_speed, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR61_FADE_SPEED); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR61, value)); +} +#endif +/* CR62 is Read Only */ +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR63 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR63(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_datohslen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_DATOHSLEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_datohsren, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_DATOHSREN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_ad1sel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_AD1SEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_ad2sel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_AD2SEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_ad3sel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_AD3SEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_ad5sel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_AD5SEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_ad6sel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_AD6SEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr63_ancsel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR63_ANCSEL); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR63, value)); +} + +#if 0 +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR64 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR64(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr64_datohfren, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR64_DATOHFREN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr64_datohflen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR64_DATOHFLEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr64_hfrsel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR64_HFRSEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr64_hflsel, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR64_HFLSEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr64_stfir1sel, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR64_STFIR1SEL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr64_stfir2sel, + AB8500_CODEC_MASK_TWO_BITS, AB8500_CODEC_CR64_STFIR2SEL); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR64, value)); +} + +#endif + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR65 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR65(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr65_fadedis_ad1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR65_FADEDIS_AD1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr65_ad1gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR65_AD1GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR65, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR66 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR66(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr66_fadedis_ad2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR66_FADEDIS_AD2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr66_ad2gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR66_AD2GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR66, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR67 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR67(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr67_fadedis_ad3, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR67_FADEDIS_AD3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr67_ad3gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR67_AD3GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR67, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR68 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR68(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr68_fadedis_ad4, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR68_FADEDIS_AD4); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr68_ad4gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR68_AD4GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR68, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR69 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR69(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr69_fadedis_ad5, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR69_FADEDIS_AD5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr69_ad5gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR69_AD5GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR69, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR70 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR70(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr70_fadedis_ad6, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR70_FADEDIS_AD6); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr70_ad6gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR70_AD6GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR70, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR71 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR71(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr71_fadedis_da1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR71_FADEDIS_DA1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr71_da1gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR71_DA1GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR71, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR72 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR72(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr72_fadedis_da2, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR72_FADEDIS_DA2); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr72_da2gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR72_DA2GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR72, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR73 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR73(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr73_fadedis_da3, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR73_FADEDIS_DA3); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr73_da3gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR73_DA3GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR73, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR74 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR74(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr74_fadedis_da4, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR74_FADEDIS_DA4); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr74_da4gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR74_DA4GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR74, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR75 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR75(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr75_fadedis_da5, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR75_FADEDIS_DA5); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr75_da5gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR75_DA5GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR75, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR76 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR76(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr76_fadedis_da6, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR76_FADEDIS_DA6); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr76_da6gain, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR76_DA6GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR76, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR77 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR77(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr77_fadedis_ad1l, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR77_FADEDIS_AD1L); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr77_ad1lbgain_to_hfl, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR77_AD1LBGAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR77, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR78 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR78(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr78_fadedis_ad2l, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR78_FADEDIS_AD2L); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr78_ad2lbgain_to_hfr, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR78_AD2LBGAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR78, value)); +} +#endif + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR79 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR79(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr79_hssinc1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR79_HSSINC1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr79_fadedis_hsl, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR79_FADEDIS_HSL); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr79_hsldgain, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR79_HSLDGAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR79, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR80 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR80(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr80_fadedis_hsr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR80_FADEDIS_HSR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr80_hsrdgain, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR80_HSRDGAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR80, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR81 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR81(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr81_stfir1gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR81_STFIR1GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR81, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR82 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR82(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr82_stfir2gain, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR82_STFIR2GAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR82, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR83 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR83(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr83_enanc, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR83_ENANC); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr83_anciirinit, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR83_ANCIIRINIT); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr83_ancfirupdate, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR83_ANCFIRUPDATE); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR83, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR84 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR84(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr84_ancinshift, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR84_ANCINSHIFT); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR84, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR85 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR85(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr85_ancfiroutshift, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR85_ANCFIROUTSHIFT); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR85, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR86 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR86(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr86_ancshiftout, + AB8500_CODEC_MASK_FIVE_BITS, AB8500_CODEC_CR86_ANCSHIFTOUT); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR86, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR87 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR87(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr87_ancfircoeff_msb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR87_ANCFIRCOEFF_MSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR87, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR88 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR88(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr88_ancfircoeff_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR88_ANCFIRCOEFF_LSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR88, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR89 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR89(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr89_anciircoeff_msb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR89_ANCIIRCOEFF_MSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR89, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR90 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR90(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr90_anciircoeff_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR90_ANCIIRCOEFF_LSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR90, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR91 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR91(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr91_ancwarpdel_msb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR91_ANCWARPDEL_MSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR91, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR92 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR92(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr92_ancwarpdel_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR92_ANCWARPDEL_LSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR92, value)); +} + +/* CR93 is Read Only */ +/* CR94 is Read Only */ +/* CR95 is Read Only */ +/* CR96 is Read Only */ +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR97 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR97(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr97_stfir_set, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR97_STFIR_SET); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr97_stfir_addr, + AB8500_CODEC_MASK_SEVEN_BITS, AB8500_CODEC_CR97_STFIR_ADDR); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR97, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR98 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR98(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr98_stfir_coeff_msb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR98_STFIR_COEFF_MSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR98, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR99 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR99(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr99_stfir_coeff_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR99_STFIR_COEFF_LSB); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR99, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR100 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR100(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr100_enstfirs, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR100_ENSTFIRS); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr100_stfirstoif1, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR100_STFIRSTOIF1); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr100_stfir_busy, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR100_STFIR_BUSY); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR100, value)); +} + +#endif +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR101 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR101(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr101_parlhf, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR101_PARLHF); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr101_parlvib, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR101_PARLVIB); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr101_classd_viblswapen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR101_CLASSDVIBLSWAPEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr101_classd_vibrswapen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR101_CLASSDVIBRSWAPEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr101_classd_hflswapen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR101_CLASSDHFLSWAPEN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr101_classd_hfrswapen, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR101_CLASSDHFRSWAPEN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR101, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR102 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR102(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr102_classd_firbyp, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR102_CLASSD_FIRBYP); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr102_classd_highvolen, + AB8500_CODEC_MASK_FOUR_BITS, AB8500_CODEC_CR102_CLASSD_HIGHVOLEN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR102, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR103 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR103(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr103_classd_ditherhpgain, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR103_CLASSD_DITHERHPGAIN); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr103_classd_ditherwgain, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR103_CLASSD_DITHERWGAIN); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR103, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR104 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR104(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr104_bfifoint, + AB8500_CODEC_MASK_SIX_BITS, AB8500_CODEC_CR104_BFIFOINT); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR104, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR105 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR105(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr105_bfifotx, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR105_BFIFOTX); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR105, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR106 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR106(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr106_bfifofsext, + AB8500_CODEC_MASK_THREE_BITS, AB8500_CODEC_CR106_BFIFOFSEXT); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr106_bfifomsk, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR106_BFIFOMSK); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr106_bfifomstr, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR106_BFIFOMSTR); + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr106_bfifostrt, + AB8500_CODEC_MASK_ONE_BIT, AB8500_CODEC_CR106_BFIFOSTRT); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR106, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR107 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR107(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr107_bfifosampnr, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR107_BFIFOSAMPNR); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR107, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR108 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR108(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr108_bfifowakeup, + AB8500_CODEC_MASK_EIGHT_BITS, AB8500_CODEC_CR108_BFIFOWAKEUP); + + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR108, value)); +} + +/* CR109 is Read Only */ +/********************************************************************************************/ +/* Name: ab8500_codec_Reset() */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_Reset(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + p_ab8500_codec_configuration->cr1_swreset = + AB8500_CODEC_CR1_SWRESET_ENABLED; + + ab8500_codec_error = ab8500_codec_UpdateCR1(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirection(IN + t_ab8500_codec_direction + ab8500_codec_direction) +{ /*only IN or OUT must be passed (not INOUT) */ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + if (AB8500_CODEC_DIRECTION_IN == ab8500_codec_direction) { + ab8500_codec_error = ab8500_codec_ProgramDirectionIN(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + if (AB8500_CODEC_DIRECTION_OUT == ab8500_codec_direction) { + ab8500_codec_error = ab8500_codec_ProgramDirectionOUT(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetDirection(IN + t_ab8500_codec_direction + ab8500_codec_direction) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + switch (ab8500_codec_direction) { + case AB8500_CODEC_DIRECTION_IN: + ab8500_codec_error = + ab8500_codec_ProgramDirection(AB8500_CODEC_DIRECTION_IN); + break; + + case AB8500_CODEC_DIRECTION_OUT: + ab8500_codec_error = + ab8500_codec_ProgramDirection(AB8500_CODEC_DIRECTION_OUT); + break; + + case AB8500_CODEC_DIRECTION_INOUT: + ab8500_codec_error = + ab8500_codec_ProgramDirection(AB8500_CODEC_DIRECTION_IN); + if (AB8500_CODEC_OK == ab8500_codec_error) { + ab8500_codec_error = + ab8500_codec_ProgramDirection + (AB8500_CODEC_DIRECTION_OUT); + } + break; + } + + if (ab8500_codec_error != AB8500_CODEC_OK) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR5(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR6(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR7(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR8(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR9(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR10(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR12(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR15(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR63(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_Init */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Initialize the global variables & stores the slave address of codec. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* slave_address_of_ab8500_codec: Audio codec slave address */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* Returns AB8500_CODEC_OK */ +/* COMMENTS: */ +/* 1) Saves the supplied slave_address_of_codec in global variable */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_Init(IN t_uint8 + slave_address_of_ab8500_codec) +{ + DBGENTER1(" (%lx)", slave_address_of_ab8500_codec); + + g_ab8500_codec_system_context.slave_address_of_ab8500_codec = + slave_address_of_ab8500_codec; + + DBGEXIT(AB8500_CODEC_OK); + return (AB8500_CODEC_OK); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_Reset */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Reset the global variables and clear audiocodec settings to default. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_Reset(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + DBGENTER(); + + g_ab8500_codec_system_context.ab8500_codec_direction = + AB8500_CODEC_DEFAULT_DIRECTION; + g_ab8500_codec_system_context.ab8500_codec_mode_in = + AB8500_CODEC_DEFAULT_MODE_IN; + g_ab8500_codec_system_context.ab8500_codec_mode_out = + AB8500_CODEC_DEFAULT_MODE_OUT; + + g_ab8500_codec_system_context.ab8500_codec_src = + AB8500_CODEC_DEFAULT_INPUT_SRC; + g_ab8500_codec_system_context.ab8500_codec_dest = + AB8500_CODEC_DEFAULT_OUTPUT_DEST; + + g_ab8500_codec_system_context.in_left_volume = + AB8500_CODEC_DEFAULT_VOLUME_LEFT_IN; + g_ab8500_codec_system_context.in_right_volume = + AB8500_CODEC_DEFAULT_VOLUME_RIGHT_IN; + g_ab8500_codec_system_context.out_left_volume = + AB8500_CODEC_DEFAULT_VOLUME_LEFT_OUT; + g_ab8500_codec_system_context.out_right_volume = + AB8500_CODEC_DEFAULT_VOLUME_RIGHT_OUT; + + ab8500_codec_error = ab8500_codec_Reset(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetModeAndDirection */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Configures the whole audio codec to work in audio mode */ +/* (using I2S protocol). */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* direction: select the direction (IN, OUT or INOUT) */ +/* in_mode: codec mode for recording. If direction is OUT only, */ +/* this parameter is ignored. */ +/* out_mode: codec mode for playing. If direction is IN only, */ +/* this parameter is ignored. */ +/* p_tdm_config: TDM configuration required to be configured by user */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: The API may not allow setting */ +/* 2 different modes, in which case it should return this value. */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetModeAndDirection + (IN t_ab8500_codec_direction ab8500_codec_direction, + IN t_ab8500_codec_mode ab8500_codec_mode_in, + IN t_ab8500_codec_mode ab8500_codec_mode_out, + IN t_ab8500_codec_tdm_config const *const p_tdm_config) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + DBGENTER3(" (%lx %lx %lx)", ab8500_codec_direction, + ab8500_codec_mode_in, ab8500_codec_mode_out); + + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) { + if (AB8500_CODEC_DIRECTION_OUT == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == ab8500_codec_direction) { + p_ab8500_codec_configuration->cr3_enda1 = + AB8500_CODEC_CR3_ENDA1_ENABLED; + p_ab8500_codec_configuration->cr3_enda2 = + AB8500_CODEC_CR3_ENDA2_ENABLED; + p_ab8500_codec_configuration->cr3_enda3 = + AB8500_CODEC_CR3_ENDA3_ENABLED; + p_ab8500_codec_configuration->cr3_enda4 = + AB8500_CODEC_CR3_ENDA4_ENABLED; + p_ab8500_codec_configuration->cr3_enda5 = + AB8500_CODEC_CR3_ENDA5_ENABLED; + p_ab8500_codec_configuration->cr3_enda6 = + AB8500_CODEC_CR3_ENDA6_ENABLED; + + p_ab8500_codec_configuration->cr27_if1_bitclk_osr = + p_tdm_config->cr27_if1_bitclk_osr; + + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_out) { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } else { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_TDM; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } + } + + if (AB8500_CODEC_DIRECTION_IN == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == ab8500_codec_direction) { + p_ab8500_codec_configuration->cr2_enad1 = + AB8500_CODEC_CR2_ENAD1_ENABLED; + p_ab8500_codec_configuration->cr2_enad2 = + AB8500_CODEC_CR2_ENAD2_ENABLED; + p_ab8500_codec_configuration->cr2_enad3 = + AB8500_CODEC_CR2_ENAD3_ENABLED; + p_ab8500_codec_configuration->cr2_enad4 = + AB8500_CODEC_CR2_ENAD4_ENABLED; + p_ab8500_codec_configuration->cr2_enad5 = + AB8500_CODEC_CR2_ENAD5_ENABLED; + p_ab8500_codec_configuration->cr2_enad6 = + AB8500_CODEC_CR2_ENAD6_ENABLED; + + p_ab8500_codec_configuration->cr27_if1_bitclk_osr = + p_tdm_config->cr27_if1_bitclk_osr; + + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_in) { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } else { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_RISING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_NOT_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_TDM; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } + } + } else { + if (AB8500_CODEC_DIRECTION_OUT == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == ab8500_codec_direction) { + p_ab8500_codec_configuration->cr3_enda1 = + AB8500_CODEC_CR3_ENDA1_ENABLED; + p_ab8500_codec_configuration->cr3_enda2 = + AB8500_CODEC_CR3_ENDA2_ENABLED; + p_ab8500_codec_configuration->cr3_enda3 = + AB8500_CODEC_CR3_ENDA3_ENABLED; + p_ab8500_codec_configuration->cr3_enda4 = + AB8500_CODEC_CR3_ENDA4_ENABLED; + p_ab8500_codec_configuration->cr3_enda5 = + AB8500_CODEC_CR3_ENDA5_ENABLED; + p_ab8500_codec_configuration->cr3_enda6 = + AB8500_CODEC_CR3_ENDA6_ENABLED; + + p_ab8500_codec_configuration->cr27_if0_bitclk_osr = + p_tdm_config->cr27_if0_bitclk_osr; + + p_ab8500_codec_configuration->cr63_datohslen = + AB8500_CODEC_CR63_DATOHSLEN_ENABLED; + p_ab8500_codec_configuration->cr63_datohsren = + AB8500_CODEC_CR63_DATOHSREN_ENABLED; + + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_out) { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_FALLING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } else { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_FALLING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_TDM; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } + } + + if (AB8500_CODEC_DIRECTION_IN == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == ab8500_codec_direction) { + p_ab8500_codec_configuration->cr2_enad1 = + AB8500_CODEC_CR2_ENAD1_ENABLED; + p_ab8500_codec_configuration->cr2_enad2 = + AB8500_CODEC_CR2_ENAD2_ENABLED; + p_ab8500_codec_configuration->cr2_enad3 = + AB8500_CODEC_CR2_ENAD3_ENABLED; + p_ab8500_codec_configuration->cr2_enad4 = + AB8500_CODEC_CR2_ENAD4_ENABLED; + p_ab8500_codec_configuration->cr2_enad5 = + AB8500_CODEC_CR2_ENAD5_ENABLED; + p_ab8500_codec_configuration->cr2_enad6 = + AB8500_CODEC_CR2_ENAD6_ENABLED; + + p_ab8500_codec_configuration->cr26_ad1_voice = + AB8500_CODEC_CR26_AD1_VOICE_LOWLATENCYFILTER; + p_ab8500_codec_configuration->cr26_ad2_voice = + AB8500_CODEC_CR26_AD2_VOICE_LOWLATENCYFILTER; + p_ab8500_codec_configuration->cr26_ad3_voice = + AB8500_CODEC_CR26_AD3_VOICE_LOWLATENCYFILTER; + p_ab8500_codec_configuration->cr26_ad4_voice = + AB8500_CODEC_CR26_AD4_VOICE_LOWLATENCYFILTER; + + p_ab8500_codec_configuration->cr27_if0_bitclk_osr = + p_tdm_config->cr27_if0_bitclk_osr; + + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_in) { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_RISING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_RISING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_NOT_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } else { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_RISING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_NOT_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_TDM; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } + } + } + + ab8500_codec_error = ab8500_codec_SetModeAndDirectionUpdateCR(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + g_ab8500_codec_system_context.ab8500_codec_direction = + ab8500_codec_direction; + g_ab8500_codec_system_context.ab8500_codec_mode_in = + ab8500_codec_mode_in; + g_ab8500_codec_system_context.ab8500_codec_mode_out = + ab8500_codec_mode_out; + + ab8500_codec_error = ab8500_codec_SetDirection(ab8500_codec_direction); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetSrcVolume */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Sets the record volumes. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* t_ab8500_codec_src: select source device for recording. */ +/* in_left_volume: record volume for left channel. */ +/* in_right_volume: record volume for right channel. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetSrcVolume + (IN t_ab8500_codec_src src_device, + IN t_uint8 in_left_volume, IN t_uint8 in_right_volume) { + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER3(" (%lx %lx %lx)", src_device, in_left_volume, + in_right_volume); + + if (in_left_volume > AB8500_CODEC_MAX_VOLUME) { + in_left_volume = AB8500_CODEC_MAX_VOLUME; + } + + if (in_right_volume > AB8500_CODEC_MAX_VOLUME) { + in_right_volume = AB8500_CODEC_MAX_VOLUME; + } + + g_ab8500_codec_system_context.in_left_volume = in_left_volume; + g_ab8500_codec_system_context.in_right_volume = in_right_volume; + + p_ab8500_codec_configuration->cr65_ad1gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr66_ad2gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr67_ad3gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr68_ad4gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr69_ad5gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr70_ad6gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + + /* Set mininimum volume if volume is zero */ + switch (src_device) { + case AB8500_CODEC_SRC_LINEIN: + p_ab8500_codec_configuration->cr22_linl_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr23_linr_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_right_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + break; + + case AB8500_CODEC_SRC_MICROPHONE_1A: + case AB8500_CODEC_SRC_MICROPHONE_1B: + p_ab8500_codec_configuration->cr20_mic1_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + break; + + case AB8500_CODEC_SRC_MICROPHONE_2: + p_ab8500_codec_configuration->cr21_mic2_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_1: + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_2: + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_3: + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_4: + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_5: + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_6: + break; + + case AB8500_CODEC_SRC_ALL: + p_ab8500_codec_configuration->cr22_linl_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr23_linr_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_right_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + + p_ab8500_codec_configuration->cr20_mic1_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + + p_ab8500_codec_configuration->cr21_mic2_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_SetSrcVolumeUpdateCR(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetDestVolume */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Sets the play volumes. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* out_left_volume: play volume for left channel. */ +/* out_right_volume: play volume for right channel. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetDestVolume + (IN t_ab8500_codec_dest dest_device, + IN t_uint8 out_left_volume, IN t_uint8 out_right_volume) { + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER3(" (%lx %lx %lx)", dest_device, out_left_volume, + out_right_volume); + + if (out_left_volume > AB8500_CODEC_MAX_VOLUME) { + out_left_volume = AB8500_CODEC_MAX_VOLUME; + } + + if (out_right_volume > AB8500_CODEC_MAX_VOLUME) { + out_right_volume = AB8500_CODEC_MAX_VOLUME; + } + + g_ab8500_codec_system_context.out_left_volume = out_left_volume; + g_ab8500_codec_system_context.out_right_volume = out_right_volume; + + p_ab8500_codec_configuration->cr71_da1gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr72_da2gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr73_da3gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr74_da4gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr75_da5gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr76_da6gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + + /* Set mininimum volume if volume is zero */ + switch (dest_device) { + case AB8500_CODEC_DEST_HEADSET: + p_ab8500_codec_configuration->cr22_hsl_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr23_hsr_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + + p_ab8500_codec_configuration->cr79_hsldgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + p_ab8500_codec_configuration->cr80_hsrdgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + break; + + case AB8500_CODEC_DEST_EARPIECE: + p_ab8500_codec_configuration->cr79_hsldgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + break; + + case AB8500_CODEC_DEST_HANDSFREE: + + p_ab8500_codec_configuration->cr101_parlhf = + AB8500_CODEC_CR101_PARLHF_INDEPENDENT; + p_ab8500_codec_configuration->cr101_parlvib = + AB8500_CODEC_CR101_PARLVIB_INDEPENDENT; + p_ab8500_codec_configuration->cr101_classd_viblswapen = + AB8500_CODEC_CR101_CLASSD_VIBLSWAPEN_DISABLED; + p_ab8500_codec_configuration->cr101_classd_vibrswapen = + AB8500_CODEC_CR101_CLASSD_VIBRSWAPEN_DISABLED; + p_ab8500_codec_configuration->cr101_classd_hflswapen = + AB8500_CODEC_CR101_CLASSD_HFLSWAPEN_DISABLED; + p_ab8500_codec_configuration->cr101_classd_hfrswapen = + AB8500_CODEC_CR101_CLASSD_HFRSWAPEN_DISABLED; + + p_ab8500_codec_configuration->cr102_classd_firbyp = + AB8500_CODEC_CR102_CLASSD_FIRBYP_ALL_ENABLED; + p_ab8500_codec_configuration->cr102_classd_highvolen = + AB8500_CODEC_CR102_CLASSD_HIGHVOLEN_DISABLED; + + p_ab8500_codec_configuration->cr103_classd_ditherhpgain = 0x8; + p_ab8500_codec_configuration->cr103_classd_ditherwgain = 0x4; + + break; + + case AB8500_CODEC_DEST_VIBRATOR_L: + p_ab8500_codec_configuration->cr16_pwmnldutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN; + p_ab8500_codec_configuration->cr17_pwmpldutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_VIBRATOR_VOLUME_MAX - + AB8500_CODEC_VIBRATOR_VOLUME_MIN)) / 100; + break; + + case AB8500_CODEC_DEST_VIBRATOR_R: + p_ab8500_codec_configuration->cr18_pwmnrdutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN; + p_ab8500_codec_configuration->cr19_pwmprdutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_VIBRATOR_VOLUME_MAX - + AB8500_CODEC_VIBRATOR_VOLUME_MIN)) / 100; + break; + + case AB8500_CODEC_DEST_ALL: + p_ab8500_codec_configuration->cr22_hsl_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr23_hsr_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + + p_ab8500_codec_configuration->cr79_hsldgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_SetDestVolumeUpdateCR(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetMasterMode */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Set the Audio Codec in Master mode. */ +/* */ +/* ARGUMENTS */ +/* IN: t_codec_master_mode: Enable/disable master mode */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: Call this API after calling AB8500_CODEC_SetModeAndDirection() API*/ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetMasterMode(IN + t_ab8500_codec_master_mode + mode) +{ + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) { + + p_ab8500_codec_configuration->cr27_en_mastgen = + AB8500_CODEC_CR27_EN_MASTGEN_ENABLED; + p_ab8500_codec_configuration->cr27_enfs_bitclk1 = + AB8500_CODEC_CR27_ENFS_BITCLK1_ENABLED; + + if (AB8500_CODEC_MASTER_MODE_ENABLE == mode) { + p_ab8500_codec_configuration->cr29_if1master = + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_OUTPUT; + } else { + p_ab8500_codec_configuration->cr29_if1master = + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_INPUT; + } + + } else { + + p_ab8500_codec_configuration->cr27_en_mastgen = + AB8500_CODEC_CR27_EN_MASTGEN_ENABLED; + p_ab8500_codec_configuration->cr27_enfs_bitclk0 = + AB8500_CODEC_CR27_ENFS_BITCLK0_ENABLED; + + if (AB8500_CODEC_MASTER_MODE_ENABLE == mode) { + p_ab8500_codec_configuration->cr29_if0master = + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_OUTPUT; + } else { + p_ab8500_codec_configuration->cr29_if0master = + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_INPUT; + } + + } + + ab8500_codec_error = ab8500_codec_UpdateCR27(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR29(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SelectInput */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Select input source for recording. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* input_src: select input source for recording when several sources */ +/* are supported in codec. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If input_src provided is invalid */ +/* by the codec hardware in use. */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SelectInput(IN t_ab8500_codec_src + ab8500_codec_src) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER1(" (%lx)", ab8500_codec_src); + + g_ab8500_codec_system_context.ab8500_codec_src = ab8500_codec_src; + + ab8500_codec_error = + ab8500_codec_SetDirection(AB8500_CODEC_DIRECTION_IN); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SelectOutput */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Select output desination for playing. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* output_dest: select output destination for playing when several are */ +/* supported by codec hardware. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If output_src provided is invalid */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SelectOutput(IN t_ab8500_codec_dest + ab8500_codec_dest) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + g_ab8500_codec_system_context.ab8500_codec_dest = ab8500_codec_dest; + DBGENTER1(" (%lx)", ab8500_codec_dest); + + ab8500_codec_error = + ab8500_codec_SetDirection(AB8500_CODEC_DIRECTION_OUT); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_PowerDown */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Shuts the audio codec down completely. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* OUT: */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_PowerDown(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + g_ab8500_codec_system_context.ab8500_codec_configuration.cr0_powerup = + AB8500_CODEC_CR0_POWERUP_OFF; + + ab8500_codec_error = ab8500_codec_UpdateCR0(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_PowerUp */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Switch on the audio codec. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_PowerUp(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + DBGENTER(); + + /*g_ab8500_codec_system_context.ab8500_codec_configuration.cr1_swreset = AB8500_CODEC_CR1_SWRESET_ENABLED; Removed by kardad + ab8500_codec_error = ab8500_codec_UpdateCR1(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return(ab8500_codec_error); + } */ + + g_ab8500_codec_system_context.ab8500_codec_configuration.cr0_powerup = + AB8500_CODEC_CR0_POWERUP_ON; + g_ab8500_codec_system_context.ab8500_codec_configuration.cr0_enaana = + AB8500_CODEC_CR0_ENAANA_ON; + + ab8500_codec_error = ab8500_codec_UpdateCR0(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SelectInterface */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Select the Audio Interface 0 or 1. */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_audio_interface: The selected interface */ +/* */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_OK: Always. */ +/* REMARK: Call this API before using a function of the low level drivers */ +/* to select the interface that you want to configure */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SelectInterface(IN + t_ab8500_codec_audio_interface + audio_interface) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + + g_ab8500_codec_system_context.audio_interface = audio_interface; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_GetInterface */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Get the Audio Interface 0 or 1. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: p_audio_interface: Store the selected interface */ +/* RETURN: */ +/* AB8500_CODEC_OK: Always */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_GetInterface(OUT + t_ab8500_codec_audio_interface + * p_audio_interface) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + + *p_audio_interface = g_ab8500_codec_system_context.audio_interface; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetAnalogLoopback */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Sets Line-In to HeadSet loopback with the required gain. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* out_left_volume: play volume for left channel. */ +/* out_right_volume: play volume for right channel. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetAnalogLoopback(IN t_uint8 + out_left_volume, + IN t_uint8 + out_right_volume) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER2(" (%lx %lx)", out_left_volume, out_right_volume); + + if (out_left_volume > AB8500_CODEC_MAX_VOLUME) { + out_left_volume = AB8500_CODEC_MAX_VOLUME; + } + + if (out_right_volume > AB8500_CODEC_MAX_VOLUME) { + out_right_volume = AB8500_CODEC_MAX_VOLUME; + } + + g_ab8500_codec_system_context.out_left_volume = out_left_volume; + g_ab8500_codec_system_context.out_right_volume = out_right_volume; + + p_ab8500_codec_configuration->cr24_lintohsl_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MAX - + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr25_lintohsr_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MAX - + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN)) / 100; + + ab8500_codec_error = ab8500_codec_UpdateCR24(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR25(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_RemoveAnalogLoopback */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Remove Line-In to HeadSet loopback. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_RemoveAnalogLoopback(void) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER0(); + + p_ab8500_codec_configuration->cr24_lintohsl_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_LOOP_OPEN; + p_ab8500_codec_configuration->cr25_lintohsr_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_LOOP_OPEN; + + ab8500_codec_error = ab8500_codec_UpdateCR24(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR25(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_EnableBypassMode */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enables IF0 to IF1 path or vice versa */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_EnableBypassMode(void) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER0(); + + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) { + p_ab8500_codec_configuration->cr29_if1datoif0ad = + AB8500_CODEC_CR29_IF1DATOIF0AD_SENT; + p_ab8500_codec_configuration->cr29_if1cktoif0ck = + AB8500_CODEC_CR29_IF1CKTOIF0CK_SENT; + } else { + p_ab8500_codec_configuration->cr29_if0datoif1ad = + AB8500_CODEC_CR29_IF0DATOIF1AD_SENT; + p_ab8500_codec_configuration->cr29_if0cktoif1ck = + AB8500_CODEC_CR29_IF0CKTOIF1CK_SENT; + } + + ab8500_codec_error = ab8500_codec_UpdateCR29(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DisableBypassMode */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Disables IF0 to IF1 path or vice versa */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DisableBypassMode(void) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER0(); + + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) { + p_ab8500_codec_configuration->cr29_if1datoif0ad = + AB8500_CODEC_CR29_IF1DATOIF0AD_NOTSENT; + p_ab8500_codec_configuration->cr29_if1cktoif0ck = + AB8500_CODEC_CR29_IF1CKTOIF0CK_NOTSENT; + } else { + p_ab8500_codec_configuration->cr29_if0datoif1ad = + AB8500_CODEC_CR29_IF0DATOIF1AD_NOTSENT; + p_ab8500_codec_configuration->cr29_if0cktoif1ck = + AB8500_CODEC_CR29_IF0CKTOIF1CK_NOTSENT; + } + + ab8500_codec_error = ab8500_codec_UpdateCR29(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SrcPowerControl */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enables/Disables & UnMute/Mute the desired source */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* t_ab8500_codec_src: select source device for enabling/disabling. */ +/* t_ab8500_codec_src_state: Enable/Disable */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SrcPowerControl(IN t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state) +{ + t_ab8500_codec_error ab8500_codec_error; + + DBGENTER2(" (%lx %lx)", src_device, state); + + if (src_device <= AB8500_CODEC_SRC_D_MICROPHONE_2) { + ab8500_codec_error = + ab8500_codec_SrcPowerControlSwitch1(src_device, state); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else if (src_device <= AB8500_CODEC_SRC_ALL) { + ab8500_codec_error = + ab8500_codec_SrcPowerControlSwitch2(src_device, state); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else { + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR5(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR6(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR7(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR63(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DestPowerControl */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enables/Disables & UnMute/Mute the desired destination */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* t_ab8500_codec_dest: select destination device for enabling/disabling. */ +/* t_ab8500_codec_dest_state: Enable/Disable */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DestPowerControl(IN t_ab8500_codec_dest + dest_device, + t_ab8500_codec_dest_state + state) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER2(" (%lx %lx)", dest_device, state); + + switch (dest_device) { + case AB8500_CODEC_DEST_HEADSET: + + if (AB8500_CODEC_DEST_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + } else { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + } + break; + + case AB8500_CODEC_DEST_EARPIECE: + + if (AB8500_CODEC_DEST_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + } else { + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_DISABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + } + break; + + case AB8500_CODEC_DEST_HANDSFREE: + + if (AB8500_CODEC_DEST_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_DISABLED; + } else { + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_DISABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_ENABLED; + } + break; + + case AB8500_CODEC_DEST_VIBRATOR_L: + + if (AB8500_CODEC_DEST_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_DISABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + } else { + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_ENABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLGPOL; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLGPOL; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLGPOL; + } + break; + + case AB8500_CODEC_DEST_VIBRATOR_R: + + if (AB8500_CODEC_DEST_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_DISABLED; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + } else { + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_ENABLED; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRGPOL; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRGPOL; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRGPOL; + } + break; + + case AB8500_CODEC_DEST_ALL: + + if (AB8500_CODEC_DEST_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_DISABLED; + + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + } else { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_DISABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_DISABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_DISABLED; + + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_ENABLED; + + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLGPOL; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLGPOL; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLGPOL; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRGPOL; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRGPOL; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRGPOL; + + } + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_DestPowerControlUpdateCR(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_GetVersion */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* This routine populates the pVersion structure with */ +/* the current version of HCL. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* p_version: this parameter is used to return current HCL version. */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_ERROR: if p_version is NULL. */ +/* AB8500_CODEC_OK: if successful */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_GetVersion(OUT t_version * p_version) +{ + DBGENTER1(" (%lx)", p_version); + if (p_version != NULL) { + p_version->minor = AB8500_CODEC_HCL_MINOR_ID; + p_version->major = AB8500_CODEC_HCL_MAJOR_ID; + p_version->version = AB8500_CODEC_HCL_VERSION_ID; + DBGEXIT0(AB8500_CODEC_OK); + return (AB8500_CODEC_OK); + } else { + DBGEXIT0(AB8500_CODEC_INVALID_PARAMETER); + return (AB8500_CODEC_INVALID_PARAMETER); + } +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetDbgLevel */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Set the debug level used by the debug module (mask-like value). */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* debug_level: debug level to be set */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_OK: always */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +/* +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetDbgLevel(IN t_dbg_level dbg_level) +{ + DBGENTER1(" (%d)", dbg_level); + dbg_level = dbg_level; +#ifdef __DEBUG + MY_DEBUG_LEVEL_VAR_NAME = dbg_level; +#endif + DBGEXIT(AB8500_CODEC_OK); + return(AB8500_CODEC_OK); +}*/ + +/****************************************************************************/ +/* NAME: AB8500_CODEC_GetDbgLevel */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Set the debug level used by the debug module (mask-like value). */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* p_dbg_level: this parameter is used to return debug level. */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_ERROR: if p_version is NULL. */ +/* AB8500_CODEC_OK: if successful */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +/* +PUBLIC t_ab8500_codec_error AB8500_CODEC_GetDbgLevel(OUT t_dbg_level *p_dbg_level) +{ + if (NULL == p_dbg_level) + { + DBGEXIT(AB8500_CODEC_INVALID_PARAMETER); + return(AB8500_CODEC_INVALID_PARAMETER); + } + +#ifdef __DEBUG + * p_dbg_level = MY_DEBUG_LEVEL_VAR_NAME; +#endif + DBGEXIT(AB8500_CODEC_OK); + return(AB8500_CODEC_OK); +} +*/ +/****************************************************************************/ +/* NAME: AB8500_CODEC_ADSlotAllocation */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* AD Data Allocation in slots. */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_slot: The slot to be allocated. */ +/* IN: t_ab8500_codec_cr31_to_cr46_ad_data_allocation: The value */ +/* to be allocated. */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If invalid slot number */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_ADSlotAllocation + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + DBGENTER2(" (%lx %lx)", ad_slot, value); + + if (ad_slot <= AB8500_CODEC_SLOT7) { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch1(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else if (ad_slot <= AB8500_CODEC_SLOT15) { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch2(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else if (ad_slot <= AB8500_CODEC_SLOT23) { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch3(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else if (ad_slot <= AB8500_CODEC_SLOT31) { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch4(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else { + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DASlotAllocation */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Allocate the Audio Interface slot for DA paths. */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_da_channel_number: Channel number 1/2/3/4/5/6 */ +/* IN: t_ab8500_codec_cr51_to_cr56_sltoda: Slot number */ +/* */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If invalid channel number */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DASlotAllocation + (IN t_ab8500_codec_da_channel_number channel_number, + IN t_ab8500_codec_cr51_to_cr56_sltoda slot) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + t_ab8500_codec_cr0_powerup ab8500_codec_cr0_powerup; + + DBGENTER2(" (%lx %lx)", channel_number, slot); + + p_ab8500_codec_configuration->cr51_da12_voice = + AB8500_CODEC_CR51_DA12_VOICE_LOWLATENCYFILTER; + + switch (channel_number) { + case AB8500_CODEC_DA_CHANNEL_NUMBER_1: + p_ab8500_codec_configuration->cr51_sltoda1 = slot; + break; + + case AB8500_CODEC_DA_CHANNEL_NUMBER_2: + p_ab8500_codec_configuration->cr52_sltoda2 = slot; + break; + + case AB8500_CODEC_DA_CHANNEL_NUMBER_3: + p_ab8500_codec_configuration->cr53_sltoda3 = slot; + break; + + case AB8500_CODEC_DA_CHANNEL_NUMBER_4: + p_ab8500_codec_configuration->cr54_sltoda4 = slot; + break; + + case AB8500_CODEC_DA_CHANNEL_NUMBER_5: + p_ab8500_codec_configuration->cr55_sltoda5 = slot; + break; + + case AB8500_CODEC_DA_CHANNEL_NUMBER_6: + p_ab8500_codec_configuration->cr56_sltoda6 = slot; + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_cr0_powerup = p_ab8500_codec_configuration->cr0_powerup; + + p_ab8500_codec_configuration->cr0_powerup = + AB8500_CODEC_CR0_POWERUP_OFF; + + ab8500_codec_error = ab8500_codec_UpdateCR0(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR51(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR52(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR53(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR54(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR55(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR56(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + p_ab8500_codec_configuration->cr0_powerup = ab8500_codec_cr0_powerup; + + ab8500_codec_error = ab8500_codec_UpdateCR0(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_ConfigureBurstFifo */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Configuration for Burst FIFO control */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_burst_fifo_config: structure for configuration of */ +/* burst FIFO */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If invalid parameter */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: If interface 1 selected */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +t_ab8500_codec_error AB8500_CODEC_ConfigureBurstFifo(IN + t_ab8500_codec_burst_fifo_config + const *const + p_burst_fifo_config) +{ + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER1(" (%lx)", p_burst_fifo_config); + + if (AB8500_CODEC_AUDIO_INTERFACE_0 == + g_ab8500_codec_system_context.audio_interface) { + if (AB8500_CODEC_CR27_EN_MASTGEN_ENABLED == + p_ab8500_codec_configuration->cr27_en_mastgen) { + p_ab8500_codec_configuration->cr104_bfifoint = + p_burst_fifo_config->cr104_bfifoint; + + p_ab8500_codec_configuration->cr105_bfifotx = + p_burst_fifo_config->cr105_bfifotx; + + p_ab8500_codec_configuration->cr106_bfifofsext = + p_burst_fifo_config->cr106_bfifofsext; + p_ab8500_codec_configuration->cr106_bfifomsk = + p_burst_fifo_config->cr106_bfifomsk; + p_ab8500_codec_configuration->cr106_bfifomstr = + p_burst_fifo_config->cr106_bfifomstr; + p_ab8500_codec_configuration->cr106_bfifostrt = + p_burst_fifo_config->cr106_bfifostrt; + + p_ab8500_codec_configuration->cr107_bfifosampnr = + p_burst_fifo_config->cr107_bfifosampnr; + + p_ab8500_codec_configuration->cr108_bfifowakeup = + p_burst_fifo_config->cr108_bfifowakeup; + + ab8500_codec_error = ab8500_codec_UpdateCR104(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR105(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR106(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR107(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR108(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else { + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else { + ab8500_codec_error = AB8500_CODEC_UNSUPPORTED_FEATURE; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_EnableBurstFifo */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enable the Burst FIFO for Interface 0 */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: If Interface 1 is selected */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_EnableBurstFifo(void) +{ + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + + if (AB8500_CODEC_AUDIO_INTERFACE_0 == + g_ab8500_codec_system_context.audio_interface) { + p_ab8500_codec_configuration->cr29_if0bfifoen = + AB8500_CODEC_CR29_IF0BFIFOEN_BURST_MODE; + + ab8500_codec_error = ab8500_codec_UpdateCR29(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else { + ab8500_codec_error = AB8500_CODEC_UNSUPPORTED_FEATURE; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DisableBurstFifo */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Disable the Burst FIFO for Interface 0 */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: If Interface 1 is selected */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DisableBurstFifo(void) +{ + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + + if (AB8500_CODEC_AUDIO_INTERFACE_0 == + g_ab8500_codec_system_context.audio_interface) { + p_ab8500_codec_configuration->cr29_if0bfifoen = + AB8500_CODEC_CR29_IF0BFIFOEN_NORMAL_MODE; + + ab8500_codec_error = ab8500_codec_UpdateCR29(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } else { + ab8500_codec_error = AB8500_CODEC_UNSUPPORTED_FEATURE; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch1 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (ad_slot) { + case AB8500_CODEC_SLOT0: + p_ab8500_codec_configuration->cr31_adotoslot0 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR31(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT1: + p_ab8500_codec_configuration->cr31_adotoslot1 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR31(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT2: + p_ab8500_codec_configuration->cr32_adotoslot2 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR32(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT3: + p_ab8500_codec_configuration->cr32_adotoslot3 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR32(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT4: + p_ab8500_codec_configuration->cr33_adotoslot4 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR33(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT5: + p_ab8500_codec_configuration->cr33_adotoslot5 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR33(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT6: + p_ab8500_codec_configuration->cr34_adotoslot6 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR34(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT7: + p_ab8500_codec_configuration->cr34_adotoslot7 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR34(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch2 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (ad_slot) { + case AB8500_CODEC_SLOT8: + p_ab8500_codec_configuration->cr35_adotoslot8 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR35(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT9: + p_ab8500_codec_configuration->cr35_adotoslot9 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR35(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT10: + p_ab8500_codec_configuration->cr36_adotoslot10 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR36(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT11: + p_ab8500_codec_configuration->cr36_adotoslot11 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR36(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT12: + p_ab8500_codec_configuration->cr37_adotoslot12 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR37(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT13: + p_ab8500_codec_configuration->cr37_adotoslot13 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR37(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT14: + p_ab8500_codec_configuration->cr38_adotoslot14 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR38(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT15: + p_ab8500_codec_configuration->cr38_adotoslot15 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR38(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch3 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (ad_slot) { + case AB8500_CODEC_SLOT16: + p_ab8500_codec_configuration->cr39_adotoslot16 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR39(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT17: + p_ab8500_codec_configuration->cr39_adotoslot17 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR39(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT18: + p_ab8500_codec_configuration->cr40_adotoslot18 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR40(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT19: + p_ab8500_codec_configuration->cr40_adotoslot19 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR40(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT20: + p_ab8500_codec_configuration->cr41_adotoslot20 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR41(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT21: + p_ab8500_codec_configuration->cr41_adotoslot21 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR41(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT22: + p_ab8500_codec_configuration->cr42_adotoslot22 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR42(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT23: + p_ab8500_codec_configuration->cr42_adotoslot23 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR42(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch4 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (ad_slot) { + case AB8500_CODEC_SLOT24: + p_ab8500_codec_configuration->cr43_adotoslot24 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR43(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT25: + p_ab8500_codec_configuration->cr43_adotoslot25 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR43(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT26: + p_ab8500_codec_configuration->cr44_adotoslot26 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR44(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT27: + p_ab8500_codec_configuration->cr44_adotoslot27 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR44(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT28: + p_ab8500_codec_configuration->cr45_adotoslot28 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR45(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT29: + p_ab8500_codec_configuration->cr45_adotoslot29 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR45(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT30: + p_ab8500_codec_configuration->cr46_adotoslot30 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR46(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + case AB8500_CODEC_SLOT31: + p_ab8500_codec_configuration->cr46_adotoslot31 = value; + + ab8500_codec_error = ab8500_codec_UpdateCR46(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch1(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (src_device) { + case AB8500_CODEC_SRC_LINEIN: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_LINR; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + } else { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_MIC2; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + } + break; + + case AB8500_CODEC_SRC_MICROPHONE_1A: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1A; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + } else { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_DISABLED; + } + break; + + case AB8500_CODEC_SRC_MICROPHONE_1B: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1B; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + } else { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_DISABLED; + } + break; + + case AB8500_CODEC_SRC_MICROPHONE_2: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_MIC2; + } else { + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_LINR; + } + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_1: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + + p_ab8500_codec_configuration->cr63_ad1sel = + AB8500_CODEC_CR63_AD1SEL_DMIC1_SELECTED; + } else { + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + + p_ab8500_codec_configuration->cr63_ad1sel = + AB8500_CODEC_CR63_AD1SEL_LINLADL_SELECTED; + } + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_2: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + + p_ab8500_codec_configuration->cr63_ad2sel = + AB8500_CODEC_CR63_AD2SEL_DMIC2_SELECTED; + } else { + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + + p_ab8500_codec_configuration->cr63_ad2sel = + AB8500_CODEC_CR63_AD2SEL_LINRADR_SELECTED; + } + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch2(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (src_device) { + case AB8500_CODEC_SRC_D_MICROPHONE_3: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + + p_ab8500_codec_configuration->cr63_ad3sel = + AB8500_CODEC_CR63_AD3SEL_DMIC3_SELECTED; + } else { + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + + p_ab8500_codec_configuration->cr63_ad3sel = + AB8500_CODEC_CR63_AD3SEL_ADMO_SELECTED; + } + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_4: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + } else { + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + } + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_5: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + + p_ab8500_codec_configuration->cr63_ad5sel = + AB8500_CODEC_CR63_AD5SEL_DMIC5_SELECTED; + } else { + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + + p_ab8500_codec_configuration->cr63_ad5sel = + AB8500_CODEC_CR63_AD5SEL_AMADR_SELECTED; + } + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_6: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + + p_ab8500_codec_configuration->cr63_ad6sel = + AB8500_CODEC_CR63_AD6SEL_DMIC6_SELECTED; + } else { + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + + p_ab8500_codec_configuration->cr63_ad6sel = + AB8500_CODEC_CR63_AD6SEL_ADMO_SELECTED; + } + break; + + case AB8500_CODEC_SRC_ALL: + + if (AB8500_CODEC_SRC_STATE_ENABLE == state) { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + } else { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_DISABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + } + break; + case AB8500_CODEC_SRC_FM_RX: + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetModeAndDirectionUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + t_ab8500_codec_cr0_powerup ab8500_codec_cr0_powerup; + + ab8500_codec_cr0_powerup = p_ab8500_codec_configuration->cr0_powerup; + + p_ab8500_codec_configuration->cr0_powerup = + AB8500_CODEC_CR0_POWERUP_OFF; + + ab8500_codec_error = ab8500_codec_UpdateCR0(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR2(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR3(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR26(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR28(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR30(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR63(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + p_ab8500_codec_configuration->cr0_powerup = ab8500_codec_cr0_powerup; + + ab8500_codec_error = ab8500_codec_UpdateCR0(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetSrcVolumeUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + ab8500_codec_error = ab8500_codec_UpdateCR20(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR21(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR22(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR23(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR65(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR66(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR67(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR68(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR69(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR70(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetDestVolumeUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + ab8500_codec_error = ab8500_codec_UpdateCR16(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR17(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR18(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR19(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR22(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR23(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR71(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR72(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR73(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR74(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR75(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR76(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR79(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR80(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR101(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR102(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR103(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionIN(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (g_ab8500_codec_system_context.ab8500_codec_src) { + case AB8500_CODEC_SRC_LINEIN: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_LINR; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_MICROPHONE_1A: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1A; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_MICROPHONE_1B: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1B; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_MICROPHONE_2: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_MIC2; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_1: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr63_ad1sel = + AB8500_CODEC_CR63_AD1SEL_DMIC1_SELECTED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_2: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr63_ad2sel = + AB8500_CODEC_CR63_AD2SEL_DMIC2_SELECTED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_3: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr63_ad3sel = + AB8500_CODEC_CR63_AD3SEL_DMIC3_SELECTED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_4: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_5: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr63_ad5sel = + AB8500_CODEC_CR63_AD5SEL_DMIC5_SELECTED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_D_MICROPHONE_6: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + + p_ab8500_codec_configuration->cr63_ad6sel = + AB8500_CODEC_CR63_AD6SEL_DMIC6_SELECTED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + + break; + + case AB8500_CODEC_SRC_ALL: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionOUT(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration *p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + + switch (g_ab8500_codec_system_context.ab8500_codec_dest) { + case AB8500_CODEC_DEST_HEADSET: + p_ab8500_codec_configuration->cr7_endrvhsl = + AB8500_CODEC_CR7_ENDRVHSL_ENABLED; + p_ab8500_codec_configuration->cr7_endrvhsr = + AB8500_CODEC_CR7_ENDRVHSR_ENABLED; + + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + + p_ab8500_codec_configuration->cr9_endachsl = + AB8500_CODEC_CR9_ENDACHSL_ENABLED; + p_ab8500_codec_configuration->cr9_endachsr = + AB8500_CODEC_CR9_ENDACHSR_ENABLED; + + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_ENABLED; + + p_ab8500_codec_configuration->cr12_encphs = + AB8500_CODEC_CR12_ENCPHS_ENABLED; + + break; + + case AB8500_CODEC_DEST_EARPIECE: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_ENABLED; + + break; + + case AB8500_CODEC_DEST_HANDSFREE: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_ENABLED; + + break; + + case AB8500_CODEC_DEST_VIBRATOR_L: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_ENABLED; + + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + + break; + + case AB8500_CODEC_DEST_VIBRATOR_R: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_ENABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_DISABLED; + + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + + break; + + case AB8500_CODEC_DEST_ALL: + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfl = + AB8500_CODEC_CR10_MUTEHFL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehfr = + AB8500_CODEC_CR10_MUTEHFR_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibl = + AB8500_CODEC_CR10_MUTEVIBL_DISABLED; + p_ab8500_codec_configuration->cr10_mutevibr = + AB8500_CODEC_CR10_MUTEVIBR_DISABLED; + + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + + break; + + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_DestPowerControlUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + + ab8500_codec_error = ab8500_codec_UpdateCR8(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR9(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR10(); + if (ab8500_codec_error != AB8500_CODEC_OK) { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + + ab8500_codec_error = ab8500_codec_UpdateCR15(); + + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} diff --git a/sound/ab8500_codec_v1_0.c b/sound/ab8500_codec_v1_0.c new file mode 100644 index 00000000000..5df1ca3eae4 --- /dev/null +++ b/sound/ab8500_codec_v1_0.c @@ -0,0 +1,6405 @@ +/*****************************************************************************/ + +/** +* © ST-Ericsson, 2009 - All rights reserved +* Reproduction and Communication of this document is strictly prohibited +* unless specifically authorized in writing by ST-Ericsson +* +* \brief This module provides some support routines for the AB8500 CODEC +* \author ST-Ericsson +*/ +/*****************************************************************************/ + +/*---------------------------------------------------------------------------- + * Includes + *---------------------------------------------------------------------------*/ + +#ifdef CONFIG_U8500_AB8500_CUT10 +#include +#include +#else /* */ +#include +#include +#endif /* */ + +/*--------------------------------------------------------------------------* + * debug stuff * + *--------------------------------------------------------------------------*/ +#ifdef __DEBUG +#define MY_DEBUG_LEVEL_VAR_NAME myDebugLevel_AB8500_CODEC +#define MY_DEBUG_ID myDebugID_AB8500_CODEC +PRIVATE t_dbg_level MY_DEBUG_LEVEL_VAR_NAME = DEBUG_LEVEL0; +PRIVATE t_dbg_id MY_DEBUG_ID = AB8500_CODEC_HCL_DBG_ID; + +#endif /* */ + +/*--------------------------------------------------------------------------* + * Global data for interrupt mode management * + *--------------------------------------------------------------------------*/ +PRIVATE t_ab8500_codec_system_context g_ab8500_codec_system_context; + +/*--------------------------------------------------------------------------* + * Default Values * + *--------------------------------------------------------------------------*/ +#define AB8500_CODEC_DEFAULT_SLAVE_ADDRESS_OF_CODEC 0xD +#define AB8500_CODEC_DEFAULT_DIRECTION AB8500_CODEC_DIRECTION_OUT + +#define AB8500_CODEC_DEFAULT_MODE_IN AB8500_CODEC_MODE_VOICE +#define AB8500_CODEC_DEFAULT_MODE_OUT AB8500_CODEC_MODE_VOICE + +#define AB8500_CODEC_DEFAULT_INPUT_SRC AB8500_CODEC_SRC_MICROPHONE_1A +#define AB8500_CODEC_DEFAULT_OUTPUT_DEST AB8500_CODEC_DEST_HEADSET + +#define AB8500_CODEC_DEFAULT_VOLUME_LEFT_IN 75 +#define AB8500_CODEC_DEFAULT_VOLUME_RIGHT_IN 75 +#define AB8500_CODEC_DEFAULT_VOLUME_LEFT_OUT 75 +#define AB8500_CODEC_DEFAULT_VOLUME_RIGHT_OUT 75 + +/*--------------------------------------------------------------------- + * PRIVATE APIs + *--------------------------------------------------------------------*/ +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch1(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch2(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch3(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch4(IN + t_ab8500_codec_slot + ad_slot, + IN + t_ab8500_codec_cr31_to_cr46_ad_data_allocation + value); +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch1(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state); +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch2(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state); +PRIVATE t_ab8500_codec_error ab8500_codec_SetModeAndDirectionUpdateCR(void); +PRIVATE t_ab8500_codec_error ab8500_codec_SetSrcVolumeUpdateCR(void); +PRIVATE t_ab8500_codec_error ab8500_codec_SetDestVolumeUpdateCR(void); +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionIN(void); +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionOUT(void); +PRIVATE t_ab8500_codec_error ab8500_codec_DestPowerControlUpdateCR(void); + +/********************************************************************************************/ +/* Name: ab8500_codec_SingleWrite */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_SingleWrite(t_uint8 + register_offset, + t_uint8 data) +{ + return (t_ab8500_codec_error) (AB8500_CODEC_Write + (register_offset, 0x01, &data)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_SingleRead */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_SingleRead(t_uint8 + register_offset, + t_uint8 data) +{ + t_uint8 dummy_data = 0xAA; + return (t_ab8500_codec_error) (AB8500_CODEC_Read + (register_offset, 0x01, &dummy_data, + &data)); +} + +#endif /* */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR0 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR0(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr0_powerup, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR0_POWERUP ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr0_enaana, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR0_ENAANA ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR0, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR1 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR1(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr1_swreset, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR1_SWRESET ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR1, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR2 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR2(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr2_enad1, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR2_ENAD1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr2_enad2, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR2_ENAD2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr2_enad3, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR2_ENAD3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr2_enad4, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR2_ENAD4 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr2_enad5, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR2_ENAD5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr2_enad6, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR2_ENAD6 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR2, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR3 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR3(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr3_enda1, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR3_ENDA1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr3_enda2, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR3_ENDA2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr3_enda3, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR3_ENDA3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr3_enda4, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR3_ENDA4 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr3_enda5, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR3_ENDA5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr3_enda6, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR3_ENDA6 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR3, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR4 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR4(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr4_lowpowhs, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR4_LOWPOWHS ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr4_lowpowdachs, + AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR4_LOWPOWDACHS ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr4_lowpowear, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR4_LOWPOWEAR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr4_ear_sel_cm, AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR4_EAR_SEL_CM ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr4_hs_hp_en, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR4_HS_HP_EN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR4, value)); +} + +#endif /* */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR5 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR5(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_enmic1, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_ENMIC1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_enmic2, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_ENMIC2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_enlinl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_ENLINL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_enlinr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_ENLINR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_mutmic1, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_MUTMIC1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_mutmic2, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_MUTMIC2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_mutlinl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_MUTELINL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr5_mutlinr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR5_MUTELINR ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR5, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR6 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR6(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr6_endmic1, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR6_ENDMIC1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr6_endmic2, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR6_ENDMIC2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr6_endmic3, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR6_ENDMIC3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr6_endmic4, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR6_ENDMIC4 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr6_endmic5, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR6_ENDMIC5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr6_endmic6, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR6_ENDMIC6 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR6, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR7 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR7(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr7_mic1sel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR7_MIC1SEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr7_linrsel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR7_LINRSEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr7_endrvhsl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR7_ENDRVHSL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr7_endrvhsr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR7_ENDRVHSR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr7_enadcmic, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR7_ENADCMIC ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr7_enadclinl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR7_ENADCLINL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr7_enadclinr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR7_ENADCLINR ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR7, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR8 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR8(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_cp_dis_pldwn, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_CP_DIS_PLDWN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_enear, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_ENEAR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_enhsl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_ENHSL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_enhsr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_ENHSR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_enhfl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_ENHFL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_enhfr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_ENHFR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_envibl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_ENVIBL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr8_envibr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR8_ENVIBR ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR8, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR9 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR9(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr9_endacear, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR9_ENADACEAR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr9_endachsl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR9_ENADACHSL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr9_endachsr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR9_ENADACHSR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr9_endachfl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR9_ENADACHFL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr9_endachfr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR9_ENADACHFR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr9_endacvibl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR9_ENADACVIBL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr9_endacvibr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR9_ENADACVIBR ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR9, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR10 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR10(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr10_muteear, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR10_MUTEEAR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr10_mutehsl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR10_MUTEHSL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr10_mutehsr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR10_MUTEHSR ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR10, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR11 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR11(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr11_earshortpwd, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR11_ENSHORTPWD ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr11_earshortdis, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR11_EARSHORTDIS ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr11_hsshortdis, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR11_HSSHORTDIS ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr11_hspullden, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR11_HSPULLDEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr11_hsoscen, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR11_HSOSCEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr11_hsfaden, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR11_HSFADEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr11_hszcddis, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR11_HSZCDDIS ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR11, value)); +} + +#endif /* */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR12 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR12(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr12_encphs, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR12_ENCPHS ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr12_hsautoen, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR12_HSAUTOEN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR12, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR13 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR13(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr13_envdet_hthresh, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR13_ENVDET_HTHRESH ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr13_envdet_lthresh, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR13_ENVDET_LTHRESH ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR13, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR14 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR14(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr14_smpslven, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR14_SMPSLVEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr14_envdetsmpsen, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR14_ENVDETSMPSEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr14_cplven, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR14_CPLVEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr14_envdetcpen, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR14_ENVDETCPEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr14_envet_time, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR14_ENVDET_TIME ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR14, value)); +} + +#endif /* */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR15 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR15(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmtovibl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMTOVIBL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmtovibr, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMTOVIBR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmlctrl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMLCTRL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmrctrl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMRCTRL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmnlctrl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMNLCTRL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmplctrl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMPLCTRL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmnrctrl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMNRCTRL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr15_pwmprctrl, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR15_PWMPRCTRL ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR15, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR16 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR16(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr16_pwmnlpol, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR16_PWMNLPOL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr16_pwmnldutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, + AB8500_CODEC_CR16_PWMNLDUTYCYCLE ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR16, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR17 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR17(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr17_pwmplpol, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR17_PWMPLPOL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr17_pwmpldutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, + AB8500_CODEC_CR17_PWMLPDUTYCYCLE ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR17, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR18 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR18(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr18_pwmnrpol, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR18_PWMNRPOL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr18_pwmnrdutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, + AB8500_CODEC_CR18_PWMNRDUTYCYCLE ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR18, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR19 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR19(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr19_pwmprpol, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR19_PWMPRPOL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr19_pwmprdutycycle, + AB8500_CODEC_MASK_SEVEN_BITS, + AB8500_CODEC_CR19_PWMRPDUTYCYCLE ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR19, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR20 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR20(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr20_en_se_mic1, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR20_EN_SE_MIC1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr20_low_pow_mic1, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR20_LOW_POW_MIC1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr20_mic1_gain, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR20_MIC1_GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR20, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR21 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR21(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr21_en_se_mic2, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR21_EN_SE_MIC2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr21_low_pow_mic2, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR21_LOW_POW_MIC2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr21_mic2_gain, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR21_MIC2_GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR21, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR22 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR22(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr22_hsl_gain, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR22_HSL_GAIN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr22_hsr_gain, AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR22_HSR_GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR22, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR23 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR23(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr23_linl_gain, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR23_LINL_GAIN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr23_linr_gain, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR23_LINR_GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR23, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR24 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR24(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr24_lintohsl_gain, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR24_LINTOHSL_GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR24, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR25 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR25(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr25_lintohsr_gain, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR25_LINTOHSR_GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR25, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR26 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR26(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad1nh, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD1NH ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad2nh, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD2NH ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad3nh, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD3NH ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad4nh, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD4NH ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad1_voice, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD1_VOICE ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad2_voice, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD2_VOICE ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad3_voice, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD3_VOICE ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr26_ad4_voice, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR26_AD4_VOICE ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR26, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR27 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR27(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr27_en_mastgen, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR27_EN_MASTGEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr27_if1_bitclk_osr, + AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR27_IF1_BITCLK_OSR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr27_enfs_bitclk1, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR27_ENFS_BITCLK1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr27_if0_bitclk_osr, + AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR27_IF0_BITCLK_OSR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr27_enfs_bitclk0, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR27_ENFS_BITCLK0 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR27, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR28 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR28(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr28_fsync0p, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR28_FSYNC0P ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr28_bitclk0p, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR28_BITCLK0P ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr28_if0del, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR28_IF0DEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr28_if0format, AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR28_IF0FORMAT ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr28_if0wl, AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR28_IF0WL ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR28, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR29 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR29(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr29_if0datoif1ad, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR29_IF0DATOIF1AD ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr29_if0cktoif1ck, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR29_IF0CKTOIF1CK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr29_if1master, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR29_IF1MASTER ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr29_if1datoif0ad, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR29_IF1DATOIF0AD ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr29_if1cktoif0ck, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR29_IF1CKTOIF0CK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr29_if0master, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR29_IF0MASTER ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr29_if0bfifoen, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR29_IF0BFIFOEN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR29, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR30 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR30(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr30_fsync1p, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR30_FSYNC1P ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr30_bitclk1p, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR30_BITCLK1P ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr30_if1del, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR30_IF1DEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr30_if1format, AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR30_IF1FORMAT ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr30_if1wl, AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR30_IF1WL ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR30, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR31 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR31(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr31_adotoslot1, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR31_ADOTOSLOT1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr31_adotoslot0, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR31_ADOTOSLOT0 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR31, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR32 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR32(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr32_adotoslot3, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR32_ADOTOSLOT3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr32_adotoslot2, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR32_ADOTOSLOT2 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR32, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR33 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR33(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr33_adotoslot5, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR33_ADOTOSLOT5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr33_adotoslot4, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR33_ADOTOSLOT4 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR33, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR34 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR34(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr34_adotoslot7, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR34_ADOTOSLOT7 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr34_adotoslot6, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR34_ADOTOSLOT6 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR34, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR35 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR35(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr35_adotoslot9, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR35_ADOTOSLOT9 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr35_adotoslot8, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR35_ADOTOSLOT8 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR35, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR36 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR36(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr36_adotoslot11, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR36_ADOTOSLOT11 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr36_adotoslot10, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR36_ADOTOSLOT10 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR36, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR37 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR37(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr37_adotoslot13, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR37_ADOTOSLOT13 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr37_adotoslot12, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR37_ADOTOSLOT12 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR37, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR38 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR38(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr38_adotoslot15, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR38_ADOTOSLOT15 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr38_adotoslot14, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR38_ADOTOSLOT14 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR38, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR39 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR39(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr39_adotoslot17, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR39_ADOTOSLOT17 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr39_adotoslot16, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR39_ADOTOSLOT16 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR39, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR40 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR40(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr40_adotoslot19, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR40_ADOTOSLOT19 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr40_adotoslot18, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR40_ADOTOSLOT18 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR40, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR41 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR41(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr41_adotoslot21, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR41_ADOTOSLOT21 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr41_adotoslot20, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR41_ADOTOSLOT20 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR41, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR42 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR42(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr42_adotoslot23, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR42_ADOTOSLOT23 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr42_adotoslot22, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR42_ADOTOSLOT22 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR42, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR43 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR43(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr43_adotoslot25, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR43_ADOTOSLOT25 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr43_adotoslot24, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR43_ADOTOSLOT24 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR43, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR44 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR44(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr44_adotoslot27, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR44_ADOTOSLOT27 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr44_adotoslot26, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR44_ADOTOSLOT26 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR44, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR45 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR45(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr45_adotoslot29, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR45_ADOTOSLOT29 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr45_adotoslot28, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR45_ADOTOSLOT28 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR45, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR46 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR46(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr46_adotoslot31, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR46_ADOTOSLOT31 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr46_adotoslot30, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR46_ADOTOSLOT30 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR46, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR47 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR47(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl7, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL7 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl6, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL6 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl5, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl4, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL4 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl3, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl2, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl1, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr47_hiz_sl0, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR47_HIZ_SL0 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR47, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR48 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR48(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl15, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL15 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl14, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL14 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl13, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL13 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl12, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL12 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl11, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL11 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl10, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL10 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl9, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL9 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr48_hiz_sl8, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR48_HIZ_SL8 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR48, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR49 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR49(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl23, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL23 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl22, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL22 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl21, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL21 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl20, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL20 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl19, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL19 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl18, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL18 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl17, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL17 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr49_hiz_sl16, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR49_HIZ_SL16 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR49, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR50 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR50(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl31, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL31 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl30, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL30 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl29, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL29 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl28, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL28 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl27, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL27 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl26, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL26 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl25, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL25 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr50_hiz_sl24, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR50_HIZ_SL24 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR50, value)); +} + +#endif /* */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR51 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR51(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr51_da12_voice, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR51_DA12_VOICE ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr51_swapda12_34, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR51_SWAP_DA12_34 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr51_sldai7toslado1, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR51_SLDAI7TOSLADO1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr51_sltoda1, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR51_SLTODA1 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR51, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR52 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR52(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr52_sldai8toslado2, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR52_SLDAI8TOSLADO2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr52_sltoda2, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR52_SLTODA2 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR52, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR53 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR53(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr53_da34_voice, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR53_DA34_VOICE ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr53_sldai7toslado3, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR53_SLDAI7TOSLADO3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr53_sltoda3, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR53_SLTODA3 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR53, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR54 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR54(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr54_sldai8toslado4, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR54_SLDAI8TOSLADO4 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr54_sltoda4, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR54_SLTODA4 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR54, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR55 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR55(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr55_da56_voice, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR55_DA56_VOICE ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr55_sldai7toslado5, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR55_SLDAI7TOSLADO5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr55_sltoda5, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR55_SLTODA5 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR55, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR56 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR56(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr56_sldai8toslado6, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR56_SLDAI8TOSLADO6 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr56_sltoda6, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR56_SLTODA6 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR56, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR57 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR57(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr57_sldai8toslado7, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR57_SLDAI8TOSLADO7 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr57_sltoda7, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR57_SLTODA7 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR57, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR58 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR58(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr58_sldai7toslado8, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR58_SLDAI7TOSLADO8 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr58_sltoda8, AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR58_SLTODA8 ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR58, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR59 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR59(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr59_parlhf, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR59_PARLHF ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr59_parlvib, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR59_PARLVIB ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr59_classdvib1_swapen, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR59_CLASSDVIB1SWAPEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr59_classdvib2_swapen, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR59_CLASSDVIB2SWAPEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr59_classdhfl_swapen, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR59_CLASSDHFLSWAPEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr59_classdhfr_swapen, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR59_CLASSDHFRSWAPEN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR59, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR60 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR60(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr60_classd_firbyp, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR60_CLASSD_FIR_BYP ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr60_classd_highvolen, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR60_CLASSD_HIGHVOL_EN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR60, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR61 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR61(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + + /* 5 bits are Read Only */ + AB8500_CODEC_WRITE_BITS + (value, + (t_uint8) p_ab8500_codec_configuration->cr61_classddith_hpgain, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR61_CLASSD_DITH_HPGAIN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr61_classddith_wgain, + AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR61_CLASSD_DITH_WGAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR61, value)); +} + +#endif /* */ + +/* CR62 is Read Only */ +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR63 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR63(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_datohslen, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_DATOHSLEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_datohsren, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_DATOHSREN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_ad1sel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_AD1SEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_ad2sel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_AD2SEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_ad3sel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_AD3SEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_ad5sel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_AD5SEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_ad6sel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_AD6SEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr63_ancsel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR63_ANCSEL ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR63, value)); +} + +#if 0 +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR64 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR64(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr64_datohfren, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR64_DATOHFREN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr64_datohflen, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR64_DATOHFLEN ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr64_hfrsel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR64_HFRSEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr64_hflsel, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR64_HFLSEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr64_stfir1sel, AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR64_STFIR1SEL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr64_stfir2sel, AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR64_STFIR2SEL ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR64, value)); +} + +#endif /* */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR65 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR65(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr65_fadedis_ad1, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR65_FADEDIS_AD1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr65_ad1gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR65_AD1GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR65, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR66 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR66(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr66_fadedis_ad2, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR66_FADEDIS_AD2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr66_ad2gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR66_AD2GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR66, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR67 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR67(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr67_fadedis_ad3, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR67_FADEDIS_AD3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr67_ad3gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR67_AD3GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR67, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR68 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR68(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr68_fadedis_ad4, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR68_FADEDIS_AD4 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr68_ad4gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR68_AD4GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR68, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR69 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR69(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr69_fadedis_ad5, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR69_FADEDIS_AD5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr69_ad5gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR69_AD5GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR69, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR70 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR70(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr70_fadedis_ad6, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR70_FADEDIS_AD6 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr70_ad6gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR70_AD6GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR70, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR71 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR71(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr71_fadedis_da1, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR71_FADEDIS_DA1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr71_da1gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR71_DA1GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR71, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR72 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR72(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr72_fadedis_da2, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR72_FADEDIS_DA2 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr72_da2gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR72_DA2GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR72, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR73 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR73(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr73_fadedis_da3, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR73_FADEDIS_DA3 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr73_da3gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR73_DA3GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR73, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR74 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR74(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr74_fadedis_da4, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR74_FADEDIS_DA4 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr74_da4gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR74_DA4GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR74, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR75 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR75(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr75_fadedis_da5, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR75_FADEDIS_DA5 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr75_da5gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR75_DA5GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR75, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR76 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR76(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr76_fadedis_da6, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR76_FADEDIS_DA6 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr76_da6gain, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR76_DA6GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR76, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR77 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR77(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr77_fadedis_ad1l, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR77_FADEDIS_AD1L ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr77_ad1lbgain_to_hfl, + AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR77_AD1LBGAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR77, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR78 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR78(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr78_fadedis_ad2l, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR78_FADEDIS_AD2L ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr78_ad2lbgain_to_hfr, + AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR78_AD2LBGAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR78, value)); +} + +#endif /* */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR79 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR79(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr79_hssinc1, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR79_HSSINC1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr79_fadedis_hsl, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR79_FADEDIS_HSL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr79_hsldgain, AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR79_HSLDGAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR79, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR80 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR80(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr80_fade_speed, + AB8500_CODEC_MASK_TWO_BITS, + AB8500_CODEC_CR80_FADE_SPEED ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr80_fadedis_hsr, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR80_FADEDIS_HSR ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr80_hsrdgain, AB8500_CODEC_MASK_FOUR_BITS, + AB8500_CODEC_CR80_HSRDGAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR80, value)); +} + +#if 0 + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR81 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR81(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr81_stfir1gain, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR81_STFIR1GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR81, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR82 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR82(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr82_stfir2gain, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR82_STFIR2GAIN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR82, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR83 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR83(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr83_enanc, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR83_ENANC ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr83_anciirinit, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR83_ANCIIRINIT ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr83_ancfirupdate, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR83_ANCFIRUPDATE ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR83, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR84 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR84(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr84_ancinshift, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR84_ANCINSHIFT ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR84, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR85 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR85(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr85_ancfiroutshift, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR85_ANCFIROUTSHIFT ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR85, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR86 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR86(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr86_ancshiftout, + AB8500_CODEC_MASK_FIVE_BITS, + AB8500_CODEC_CR86_ANCSHIFTOUT ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR86, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR87 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR87(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr87_ancfircoeff_msb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR87_ANCFIRCOEFF_MSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR87, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR88 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR88(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr88_ancfircoeff_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR88_ANCFIRCOEFF_LSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR88, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR89 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR89(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr89_anciircoeff_msb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR89_ANCIIRCOEFF_MSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR89, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR90 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR90(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr90_anciircoeff_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR90_ANCIIRCOEFF_LSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR90, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR91 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR91(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr91_ancwarpdel_msb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR91_ANCWARPDEL_MSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR91, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR92 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR92(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr92_ancwarpdel_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR92_ANCWARPDEL_LSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR92, value)); +} + +/* CR93 is Read Only */ +/* CR94 is Read Only */ +/* CR95 is Read Only */ +/* CR96 is Read Only */ +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR97 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR97(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr97_stfir_set, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR97_STFIR_SET ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr97_stfir_addr, + AB8500_CODEC_MASK_SEVEN_BITS, + AB8500_CODEC_CR97_STFIR_ADDR ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR97, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR98 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR98(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr98_stfir_coeff_msb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR98_STFIR_COEFF_MSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR98, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR99 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR99(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr99_stfir_coeff_lsb, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR99_STFIR_COEFF_LSB ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR99, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR100 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR100(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr100_enstfirs, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR100_ENSTFIRS ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr100_stfirstoif1, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR100_STFIRSTOIF1 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr100_stfir_busy, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR100_STFIR_BUSY ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR100, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR101 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR101(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_hsoffst_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_HSOFFSTMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_fifofull_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_FIFOFULLMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_fifoempty_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_FIFOEMPTYMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_dasat_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_DASATMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_adsat_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_ADSATMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_addsp_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_ADDSPMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_dadsp_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_DADSPMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr101_firsid_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR101_FIRSIDMASK ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR101, value)); +} + +/* CR102 is Read Only */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR103 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR103(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr103_vssready_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR103_VSSREADYMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr103_shorthsl_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR103_SHORTHSLMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr103_shorthsr_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR103_SHORTHSRMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr103_shortear_mask, + AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR103_SHORTEARMASK ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR103, value)); +} + +#endif /* */ + +/* CR104 is Read Only */ + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR105 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR105(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr105_bfifomsk, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR105_BFIFOMASK ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr105_bfifoint, AB8500_CODEC_MASK_SIX_BITS, + AB8500_CODEC_CR105_BFIFOINT ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR105, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR106 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR106(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr106_bfifotx, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR106_BFIFOTX ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR106, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR107 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR107(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr107_bfifoexsl, + AB8500_CODEC_MASK_THREE_BITS, + AB8500_CODEC_CR107_BFIFOEXSL ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr107_prebitclk0, + AB8500_CODEC_MASK_THREE_BITS, + AB8500_CODEC_CR107_PREBITCLK0 ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr107_bfifomast, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR107_BFIFOMAST ); + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr107_bfiforun, AB8500_CODEC_MASK_ONE_BIT, + AB8500_CODEC_CR107_BFIFORUN ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR107, value)); +} + + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR108 */ +/********************************************************************************************/ + PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR108(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr108_bfifoframsw, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR108_BFIFOFRAMESW ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR108, value)); +} + +/********************************************************************************************/ +/* Name: ab8500_codec_UpdateCR109 */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_UpdateCR109(void) +{ + t_uint8 value = 0x00; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + AB8500_CODEC_WRITE_BITS (value, + (t_uint8) p_ab8500_codec_configuration-> + cr109_bfifowakeup, + AB8500_CODEC_MASK_EIGHT_BITS, + AB8500_CODEC_CR109_BFIFOWAKEUP ); + return (ab8500_codec_SingleWrite(AB8500_CODEC_CR109, value)); +} + +/* CR110 is Read Only */ + +/* CR111 is Read Only */ + +/********************************************************************************************/ +/* Name: ab8500_codec_Reset() */ + +/********************************************************************************************/ +PRIVATE t_ab8500_codec_error ab8500_codec_Reset(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + p_ab8500_codec_configuration->cr1_swreset = + AB8500_CODEC_CR1_SWRESET_ENABLED; + ab8500_codec_error = ab8500_codec_UpdateCR1(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirection(IN + t_ab8500_codec_direction + ab8500_codec_direction) + /*only IN or OUT must be passed (not INOUT) */ +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + if (AB8500_CODEC_DIRECTION_IN == ab8500_codec_direction) + { + ab8500_codec_error = ab8500_codec_ProgramDirectionIN(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + if (AB8500_CODEC_DIRECTION_OUT == ab8500_codec_direction) + { + ab8500_codec_error = ab8500_codec_ProgramDirectionOUT(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetDirection(IN + t_ab8500_codec_direction + ab8500_codec_direction) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + switch (ab8500_codec_direction) + { + case AB8500_CODEC_DIRECTION_IN: + ab8500_codec_error = + ab8500_codec_ProgramDirection(AB8500_CODEC_DIRECTION_IN); + break; + case AB8500_CODEC_DIRECTION_OUT: + ab8500_codec_error = + ab8500_codec_ProgramDirection(AB8500_CODEC_DIRECTION_OUT); + break; + case AB8500_CODEC_DIRECTION_INOUT: + ab8500_codec_error = + ab8500_codec_ProgramDirection(AB8500_CODEC_DIRECTION_IN); + if (AB8500_CODEC_OK == ab8500_codec_error) + { + ab8500_codec_error = + ab8500_codec_ProgramDirection + (AB8500_CODEC_DIRECTION_OUT); + } + break; + } + if (ab8500_codec_error != AB8500_CODEC_OK) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR5(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR6(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR7(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR8(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR9(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR10(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR12(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR15(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR63(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_Init */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Initialize the global variables & stores the slave address of codec. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* slave_address_of_ab8500_codec: Audio codec slave address */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* Returns AB8500_CODEC_OK */ +/* COMMENTS: */ +/* 1) Saves the supplied slave_address_of_codec in global variable */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_Init(IN t_uint8 + slave_address_of_ab8500_codec) +{ + DBGENTER1(" (%lx)", slave_address_of_ab8500_codec); + g_ab8500_codec_system_context.slave_address_of_ab8500_codec = + slave_address_of_ab8500_codec; + DBGEXIT(AB8500_CODEC_OK); + return (AB8500_CODEC_OK); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_Reset */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Reset the global variables and clear audiocodec settings to default. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_Reset(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER(); + g_ab8500_codec_system_context.ab8500_codec_direction = + AB8500_CODEC_DEFAULT_DIRECTION; + g_ab8500_codec_system_context.ab8500_codec_mode_in = + AB8500_CODEC_DEFAULT_MODE_IN; + g_ab8500_codec_system_context.ab8500_codec_mode_out = + AB8500_CODEC_DEFAULT_MODE_OUT; + g_ab8500_codec_system_context.ab8500_codec_src = + AB8500_CODEC_DEFAULT_INPUT_SRC; + g_ab8500_codec_system_context.ab8500_codec_dest = + AB8500_CODEC_DEFAULT_OUTPUT_DEST; + g_ab8500_codec_system_context.in_left_volume = + AB8500_CODEC_DEFAULT_VOLUME_LEFT_IN; + g_ab8500_codec_system_context.in_right_volume = + AB8500_CODEC_DEFAULT_VOLUME_RIGHT_IN; + g_ab8500_codec_system_context.out_left_volume = + AB8500_CODEC_DEFAULT_VOLUME_LEFT_OUT; + g_ab8500_codec_system_context.out_right_volume = + AB8500_CODEC_DEFAULT_VOLUME_RIGHT_OUT; + ab8500_codec_error = ab8500_codec_Reset(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetModeAndDirection */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Configures the whole audio codec to work in audio mode */ +/* (using I2S protocol). */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* direction: select the direction (IN, OUT or INOUT) */ +/* in_mode: codec mode for recording. If direction is OUT only, */ +/* this parameter is ignored. */ +/* out_mode: codec mode for playing. If direction is IN only, */ +/* this parameter is ignored. */ +/* p_tdm_config: TDM configuration required to be configured by user */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: The API may not allow setting */ +/* 2 different modes, in which case it should return this value. */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetModeAndDirection + (IN t_ab8500_codec_direction ab8500_codec_direction, + IN t_ab8500_codec_mode ab8500_codec_mode_in, + IN t_ab8500_codec_mode ab8500_codec_mode_out, + IN t_ab8500_codec_tdm_config const *const p_tdm_config ) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + DBGENTER3(" (%lx %lx %lx)", ab8500_codec_direction, + ab8500_codec_mode_in, ab8500_codec_mode_out); + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) + { + if (AB8500_CODEC_DIRECTION_OUT == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == + ab8500_codec_direction ) + { + p_ab8500_codec_configuration->cr3_enda1 = + AB8500_CODEC_CR3_ENDA1_ENABLED; + p_ab8500_codec_configuration->cr3_enda2 = + AB8500_CODEC_CR3_ENDA2_ENABLED; + p_ab8500_codec_configuration->cr3_enda3 = + AB8500_CODEC_CR3_ENDA3_ENABLED; + p_ab8500_codec_configuration->cr3_enda4 = + AB8500_CODEC_CR3_ENDA4_ENABLED; + p_ab8500_codec_configuration->cr3_enda5 = + AB8500_CODEC_CR3_ENDA5_ENABLED; + p_ab8500_codec_configuration->cr3_enda6 = + AB8500_CODEC_CR3_ENDA6_ENABLED; + p_ab8500_codec_configuration->cr27_if1_bitclk_osr = + p_tdm_config->cr27_if1_bitclk_osr; + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_out) + { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } + + else + { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_TDM; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } + } + if (AB8500_CODEC_DIRECTION_IN == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == + ab8500_codec_direction ) + { + p_ab8500_codec_configuration->cr2_enad1 = + AB8500_CODEC_CR2_ENAD1_ENABLED; + p_ab8500_codec_configuration->cr2_enad2 = + AB8500_CODEC_CR2_ENAD2_ENABLED; + p_ab8500_codec_configuration->cr2_enad3 = + AB8500_CODEC_CR2_ENAD3_ENABLED; + p_ab8500_codec_configuration->cr2_enad4 = + AB8500_CODEC_CR2_ENAD4_ENABLED; + p_ab8500_codec_configuration->cr2_enad5 = + AB8500_CODEC_CR2_ENAD5_ENABLED; + p_ab8500_codec_configuration->cr2_enad6 = + AB8500_CODEC_CR2_ENAD6_ENABLED; + p_ab8500_codec_configuration->cr27_if1_bitclk_osr = + p_tdm_config->cr27_if1_bitclk_osr; + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_in) + { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } + + else + { + p_ab8500_codec_configuration->cr30_fsync1p = + AB8500_CODEC_CR30_FSYNC1P_RISING_EDGE; + p_ab8500_codec_configuration->cr30_bitclk1p = + AB8500_CODEC_CR30_BITCLK1P_FALLING_EDGE; + p_ab8500_codec_configuration->cr30_if1del = + AB8500_CODEC_CR30_IF1DEL_NOT_DELAYED; + p_ab8500_codec_configuration->cr30_if1format = + AB8500_CODEC_CR30_IF1FORMAT_TDM; + p_ab8500_codec_configuration->cr30_if1wl = + p_tdm_config->cr30_if1wl; + } + } + } + + else + { + if (AB8500_CODEC_DIRECTION_OUT == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == + ab8500_codec_direction ) + { + p_ab8500_codec_configuration->cr3_enda1 = + AB8500_CODEC_CR3_ENDA1_ENABLED; + p_ab8500_codec_configuration->cr3_enda2 = + AB8500_CODEC_CR3_ENDA2_ENABLED; + p_ab8500_codec_configuration->cr3_enda3 = + AB8500_CODEC_CR3_ENDA3_ENABLED; + p_ab8500_codec_configuration->cr3_enda4 = + AB8500_CODEC_CR3_ENDA4_ENABLED; + p_ab8500_codec_configuration->cr3_enda5 = + AB8500_CODEC_CR3_ENDA5_ENABLED; + p_ab8500_codec_configuration->cr3_enda6 = + AB8500_CODEC_CR3_ENDA6_ENABLED; + p_ab8500_codec_configuration->cr27_if0_bitclk_osr = + p_tdm_config->cr27_if0_bitclk_osr; + p_ab8500_codec_configuration->cr63_datohslen = + AB8500_CODEC_CR63_DATOHSLEN_ENABLED; + p_ab8500_codec_configuration->cr63_datohsren = + AB8500_CODEC_CR63_DATOHSREN_ENABLED; + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_out) + { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_FALLING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } + + else + { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_FALLING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_TDM; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } + } + if (AB8500_CODEC_DIRECTION_IN == ab8500_codec_direction + || AB8500_CODEC_DIRECTION_INOUT == + ab8500_codec_direction ) + { + p_ab8500_codec_configuration->cr2_enad1 = + AB8500_CODEC_CR2_ENAD1_ENABLED; + p_ab8500_codec_configuration->cr2_enad2 = + AB8500_CODEC_CR2_ENAD2_ENABLED; + p_ab8500_codec_configuration->cr2_enad3 = + AB8500_CODEC_CR2_ENAD3_ENABLED; + p_ab8500_codec_configuration->cr2_enad4 = + AB8500_CODEC_CR2_ENAD4_ENABLED; + p_ab8500_codec_configuration->cr2_enad5 = + AB8500_CODEC_CR2_ENAD5_ENABLED; + p_ab8500_codec_configuration->cr2_enad6 = + AB8500_CODEC_CR2_ENAD6_ENABLED; + p_ab8500_codec_configuration->cr26_ad1_voice = + AB8500_CODEC_CR26_AD1_VOICE_LOWLATENCYFILTER; + p_ab8500_codec_configuration->cr26_ad2_voice = + AB8500_CODEC_CR26_AD2_VOICE_LOWLATENCYFILTER; + p_ab8500_codec_configuration->cr26_ad3_voice = + AB8500_CODEC_CR26_AD3_VOICE_LOWLATENCYFILTER; + p_ab8500_codec_configuration->cr26_ad4_voice = + AB8500_CODEC_CR26_AD4_VOICE_LOWLATENCYFILTER; + p_ab8500_codec_configuration->cr27_if0_bitclk_osr = + p_tdm_config->cr27_if0_bitclk_osr; + if (AB8500_CODEC_MODE_HIFI == ab8500_codec_mode_in) + { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_RISING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_RISING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_NOT_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_I2S_LEFTALIGNED; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } + + else + { + p_ab8500_codec_configuration->cr28_fsync0p = + AB8500_CODEC_CR28_FSYNC0P_RISING_EDGE; + p_ab8500_codec_configuration->cr28_bitclk0p = p_tdm_config->cr28_bitclk0p; /*AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; */ + p_ab8500_codec_configuration->cr28_if0del = p_tdm_config->cr28_if0del; /*AB8500_CODEC_CR28_IF0DEL_NOT_DELAYED; */ + p_ab8500_codec_configuration->cr28_if0format = + AB8500_CODEC_CR28_IF0FORMAT_TDM; + p_ab8500_codec_configuration->cr28_if0wl = + p_tdm_config->cr28_if0wl; + } + } + } + ab8500_codec_error = ab8500_codec_SetModeAndDirectionUpdateCR(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + g_ab8500_codec_system_context.ab8500_codec_direction = + ab8500_codec_direction; + g_ab8500_codec_system_context.ab8500_codec_mode_in = + ab8500_codec_mode_in; + g_ab8500_codec_system_context.ab8500_codec_mode_out = + ab8500_codec_mode_out; + ab8500_codec_error = + ab8500_codec_SetDirection(ab8500_codec_direction); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetSrcVolume */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Sets the record volumes. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* t_ab8500_codec_src: select source device for recording. */ +/* in_left_volume: record volume for left channel. */ +/* in_right_volume: record volume for right channel. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetSrcVolume + (IN t_ab8500_codec_src src_device, IN t_uint8 in_left_volume, + IN t_uint8 in_right_volume ) { + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + DBGENTER3(" (%lx %lx %lx)", src_device, in_left_volume, + in_right_volume); + if (in_left_volume > AB8500_CODEC_MAX_VOLUME) + { + in_left_volume = AB8500_CODEC_MAX_VOLUME; + } + if (in_right_volume > AB8500_CODEC_MAX_VOLUME) + { + in_right_volume = AB8500_CODEC_MAX_VOLUME; + } + g_ab8500_codec_system_context.in_left_volume = in_left_volume; + g_ab8500_codec_system_context.in_right_volume = in_right_volume; + p_ab8500_codec_configuration->cr65_ad1gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr66_ad2gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr67_ad3gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr68_ad4gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr69_ad5gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr70_ad6gain = + AB8500_CODEC_AD_D_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_AD_D_VOLUME_MAX - + AB8500_CODEC_AD_D_VOLUME_MIN)) / 100; + + /* Set mininimum volume if volume is zero */ + switch (src_device) + { + case AB8500_CODEC_SRC_LINEIN: + p_ab8500_codec_configuration->cr23_linl_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr23_linr_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_right_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + break; + case AB8500_CODEC_SRC_MICROPHONE_1A: + case AB8500_CODEC_SRC_MICROPHONE_1B: + p_ab8500_codec_configuration->cr20_mic1_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + break; + case AB8500_CODEC_SRC_MICROPHONE_2: + p_ab8500_codec_configuration->cr21_mic2_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_1: + break; + case AB8500_CODEC_SRC_D_MICROPHONE_2: + break; + case AB8500_CODEC_SRC_D_MICROPHONE_3: + break; + case AB8500_CODEC_SRC_D_MICROPHONE_4: + break; + case AB8500_CODEC_SRC_D_MICROPHONE_5: + break; + case AB8500_CODEC_SRC_D_MICROPHONE_6: + break; + case AB8500_CODEC_SRC_ALL: + p_ab8500_codec_configuration->cr23_linl_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr23_linr_gain = + AB8500_CODEC_LINEIN_VOLUME_MIN + + (in_right_volume * + (AB8500_CODEC_LINEIN_VOLUME_MAX - + AB8500_CODEC_LINEIN_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr20_mic1_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr21_mic2_gain = + AB8500_CODEC_MIC_VOLUME_MIN + + (in_left_volume * + (AB8500_CODEC_MIC_VOLUME_MAX - + AB8500_CODEC_MIC_VOLUME_MIN)) / 100; + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_SetSrcVolumeUpdateCR(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetDestVolume */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Sets the play volumes. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* out_left_volume: play volume for left channel. */ +/* out_right_volume: play volume for right channel. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetDestVolume + (IN t_ab8500_codec_dest dest_device, IN t_uint8 out_left_volume, + IN t_uint8 out_right_volume ) { + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + DBGENTER3(" (%lx %lx %lx)", dest_device, out_left_volume, + out_right_volume); + if (out_left_volume > AB8500_CODEC_MAX_VOLUME) + { + out_left_volume = AB8500_CODEC_MAX_VOLUME; + } + if (out_right_volume > AB8500_CODEC_MAX_VOLUME) + { + out_right_volume = AB8500_CODEC_MAX_VOLUME; + } + g_ab8500_codec_system_context.out_left_volume = out_left_volume; + g_ab8500_codec_system_context.out_right_volume = out_right_volume; + p_ab8500_codec_configuration->cr71_da1gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr72_da2gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr73_da3gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr74_da4gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr75_da5gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr76_da6gain = + AB8500_CODEC_DA_D_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_DA_D_VOLUME_MAX - + AB8500_CODEC_DA_D_VOLUME_MIN)) / 100; + + /* Set mininimum volume if volume is zero */ + switch (dest_device) + { + case AB8500_CODEC_DEST_HEADSET: + p_ab8500_codec_configuration->cr22_hsl_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr22_hsr_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr79_hsldgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + p_ab8500_codec_configuration->cr80_hsrdgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + break; + case AB8500_CODEC_DEST_EARPIECE: + p_ab8500_codec_configuration->cr79_hsldgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + break; + case AB8500_CODEC_DEST_HANDSFREE: + break; + case AB8500_CODEC_DEST_VIBRATOR_L: + p_ab8500_codec_configuration->cr16_pwmnldutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN; + p_ab8500_codec_configuration->cr17_pwmpldutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_VIBRATOR_VOLUME_MAX - + AB8500_CODEC_VIBRATOR_VOLUME_MIN)) / 100; + break; + case AB8500_CODEC_DEST_VIBRATOR_R: + p_ab8500_codec_configuration->cr18_pwmnrdutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN; + p_ab8500_codec_configuration->cr19_pwmprdutycycle = + AB8500_CODEC_VIBRATOR_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_VIBRATOR_VOLUME_MAX - + AB8500_CODEC_VIBRATOR_VOLUME_MIN)) / 100; + break; + case AB8500_CODEC_DEST_ALL: + p_ab8500_codec_configuration->cr22_hsl_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr22_hsr_gain = + AB8500_CODEC_HEADSET_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_HEADSET_VOLUME_MAX - + AB8500_CODEC_HEADSET_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr79_hsldgain = + AB8500_CODEC_HEADSET_D_VOLUME_0DB; + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_SetDestVolumeUpdateCR(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetMasterMode */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Set the Audio Codec in Master mode. */ +/* */ +/* ARGUMENTS */ +/* IN: t_codec_master_mode: Enable/disable master mode */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: Call this API after calling AB8500_CODEC_SetModeAndDirection() API*/ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetMasterMode(IN + t_ab8500_codec_master_mode + mode) +{ + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) + { + p_ab8500_codec_configuration->cr27_en_mastgen = + AB8500_CODEC_CR27_EN_MASTGEN_ENABLED; + p_ab8500_codec_configuration->cr27_enfs_bitclk1 = + AB8500_CODEC_CR27_ENFS_BITCLK1_ENABLED; + if (AB8500_CODEC_MASTER_MODE_ENABLE == mode) + { + p_ab8500_codec_configuration->cr29_if1master = + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_OUTPUT; + } + + else + { + p_ab8500_codec_configuration->cr29_if1master = + AB8500_CODEC_CR29_IF1MASTER_FS1CK1_INPUT; + } + } + + else + { + p_ab8500_codec_configuration->cr27_en_mastgen = + AB8500_CODEC_CR27_EN_MASTGEN_ENABLED; + p_ab8500_codec_configuration->cr27_enfs_bitclk0 = + AB8500_CODEC_CR27_ENFS_BITCLK0_ENABLED; + if (AB8500_CODEC_MASTER_MODE_ENABLE == mode) + { + p_ab8500_codec_configuration->cr29_if0master = + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_OUTPUT; + } + + else + { + p_ab8500_codec_configuration->cr29_if0master = + AB8500_CODEC_CR29_IF0MASTER_FS0CK0_INPUT; + } + } + ab8500_codec_error = ab8500_codec_UpdateCR27(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR29(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SelectInput */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Select input source for recording. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* input_src: select input source for recording when several sources */ +/* are supported in codec. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If input_src provided is invalid */ +/* by the codec hardware in use. */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SelectInput(IN t_ab8500_codec_src + ab8500_codec_src) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER1(" (%lx)", ab8500_codec_src); + g_ab8500_codec_system_context.ab8500_codec_src = ab8500_codec_src; + ab8500_codec_error = + ab8500_codec_SetDirection(AB8500_CODEC_DIRECTION_IN); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SelectOutput */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Select output desination for playing. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* output_dest: select output destination for playing when several are */ +/* supported by codec hardware. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If output_src provided is invalid */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SelectOutput(IN t_ab8500_codec_dest + ab8500_codec_dest) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + g_ab8500_codec_system_context.ab8500_codec_dest = ab8500_codec_dest; + DBGENTER1(" (%lx)", ab8500_codec_dest); + ab8500_codec_error = + ab8500_codec_SetDirection(AB8500_CODEC_DIRECTION_OUT); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_PowerDown */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Shuts the audio codec down completely. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* OUT: */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_PowerDown(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + g_ab8500_codec_system_context.ab8500_codec_configuration.cr0_powerup = + AB8500_CODEC_CR0_POWERUP_OFF; + ab8500_codec_error = ab8500_codec_UpdateCR0(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_PowerUp */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Switch on the audio codec. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_PowerUp(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER(); + g_ab8500_codec_system_context.ab8500_codec_configuration.cr0_powerup = + AB8500_CODEC_CR0_POWERUP_ON; + g_ab8500_codec_system_context.ab8500_codec_configuration.cr0_enaana = + AB8500_CODEC_CR0_ENAANA_ON; + ab8500_codec_error = ab8500_codec_UpdateCR0(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SelectInterface */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Select the Audio Interface 0 or 1. */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_audio_interface: The selected interface */ +/* */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_OK: Always. */ +/* REMARK: Call this API before using a function of the low level drivers */ +/* to select the interface that you want to configure */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SelectInterface(IN + t_ab8500_codec_audio_interface + audio_interface) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + g_ab8500_codec_system_context.audio_interface = audio_interface; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_GetInterface */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Get the Audio Interface 0 or 1. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: p_audio_interface: Store the selected interface */ +/* RETURN: */ +/* AB8500_CODEC_OK: Always */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_GetInterface(OUT + t_ab8500_codec_audio_interface + * p_audio_interface) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + *p_audio_interface = g_ab8500_codec_system_context.audio_interface; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetAnalogLoopback */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Sets Line-In to HeadSet loopback with the required gain. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* out_left_volume: play volume for left channel. */ +/* out_right_volume: play volume for right channel. */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetAnalogLoopback(IN t_uint8 + out_left_volume, + IN t_uint8 + out_right_volume) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER2(" (%lx %lx)", out_left_volume, out_right_volume); + if (out_left_volume > AB8500_CODEC_MAX_VOLUME) + { + out_left_volume = AB8500_CODEC_MAX_VOLUME; + } + if (out_right_volume > AB8500_CODEC_MAX_VOLUME) + { + out_right_volume = AB8500_CODEC_MAX_VOLUME; + } + g_ab8500_codec_system_context.out_left_volume = out_left_volume; + g_ab8500_codec_system_context.out_right_volume = out_right_volume; + p_ab8500_codec_configuration->cr24_lintohsl_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN + + (out_left_volume * + (AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MAX - + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN)) / 100; + p_ab8500_codec_configuration->cr25_lintohsr_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN + + (out_right_volume * + (AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MAX - + AB8500_CODEC_LINEIN_TO_HS_L_R_VOLUME_MIN)) / 100; + ab8500_codec_error = ab8500_codec_UpdateCR24(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR25(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_RemoveAnalogLoopback */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Remove Line-In to HeadSet loopback. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_RemoveAnalogLoopback(void) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER0(); + p_ab8500_codec_configuration->cr24_lintohsl_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_LOOP_OPEN; + p_ab8500_codec_configuration->cr25_lintohsr_gain = + AB8500_CODEC_LINEIN_TO_HS_L_R_LOOP_OPEN; + ab8500_codec_error = ab8500_codec_UpdateCR24(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR25(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_EnableBypassMode */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enables IF0 to IF1 path or vice versa */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_EnableBypassMode(void) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER0(); + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) + { + p_ab8500_codec_configuration->cr29_if1datoif0ad = + AB8500_CODEC_CR29_IF1DATOIF0AD_SENT; + p_ab8500_codec_configuration->cr29_if1cktoif0ck = + AB8500_CODEC_CR29_IF1CKTOIF0CK_SENT; + } + + else + { + p_ab8500_codec_configuration->cr29_if0datoif1ad = + AB8500_CODEC_CR29_IF0DATOIF1AD_SENT; + p_ab8500_codec_configuration->cr29_if0cktoif1ck = + AB8500_CODEC_CR29_IF0CKTOIF1CK_SENT; + } + ab8500_codec_error = ab8500_codec_UpdateCR29(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DisableBypassMode */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Disables IF0 to IF1 path or vice versa */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DisableBypassMode(void) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER0(); + if (AB8500_CODEC_AUDIO_INTERFACE_1 == + g_ab8500_codec_system_context.audio_interface) + { + p_ab8500_codec_configuration->cr29_if1datoif0ad = + AB8500_CODEC_CR29_IF1DATOIF0AD_NOTSENT; + p_ab8500_codec_configuration->cr29_if1cktoif0ck = + AB8500_CODEC_CR29_IF1CKTOIF0CK_NOTSENT; + } + + else + { + p_ab8500_codec_configuration->cr29_if0datoif1ad = + AB8500_CODEC_CR29_IF0DATOIF1AD_NOTSENT; + p_ab8500_codec_configuration->cr29_if0cktoif1ck = + AB8500_CODEC_CR29_IF0CKTOIF1CK_NOTSENT; + } + ab8500_codec_error = ab8500_codec_UpdateCR29(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SrcPowerControl */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enables/Disables & UnMute/Mute the desired source */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* t_ab8500_codec_src: select source device for enabling/disabling. */ +/* t_ab8500_codec_src_state: Enable/Disable */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_SrcPowerControl(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state) +{ + t_ab8500_codec_error ab8500_codec_error; + DBGENTER2(" (%lx %lx)", src_device, state); + if (src_device <= AB8500_CODEC_SRC_D_MICROPHONE_2) + { + ab8500_codec_error = + ab8500_codec_SrcPowerControlSwitch1(src_device, state); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else if (src_device <= AB8500_CODEC_SRC_ALL) + { + ab8500_codec_error = + ab8500_codec_SrcPowerControlSwitch2(src_device, state); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else + { + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR5(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR6(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR7(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR63(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DestPowerControl */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enables/Disables & UnMute/Mute the desired destination */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* t_ab8500_codec_dest: select destination device for enabling/disabling. */ +/* t_ab8500_codec_dest_state: Enable/Disable */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_TRANSACTION_FAILED: If transaction fails. */ +/* AB8500_CODEC_OK: if successful. */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DestPowerControl(IN + t_ab8500_codec_dest + dest_device, + t_ab8500_codec_dest_state + state) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context.ab8500_codec_configuration; + DBGENTER2(" (%lx %lx)", dest_device, state); + switch (dest_device) + { + case AB8500_CODEC_DEST_HEADSET: + if (AB8500_CODEC_DEST_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + p_ab8500_codec_configuration->cr9_endachsl = + AB8500_CODEC_CR9_ENDACHSL_ENABLED; + p_ab8500_codec_configuration->cr9_endachsr = + AB8500_CODEC_CR9_ENDACHSR_ENABLED; + } + + else + { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr9_endachsl = + AB8500_CODEC_CR9_ENDACHSL_DISABLED; + p_ab8500_codec_configuration->cr9_endachsr = + AB8500_CODEC_CR9_ENDACHSR_DISABLED; + } + break; + case AB8500_CODEC_DEST_EARPIECE: + if (AB8500_CODEC_DEST_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + } + + else + { + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_DISABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + } + break; + case AB8500_CODEC_DEST_HANDSFREE: + if (AB8500_CODEC_DEST_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + } + + else + { + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_DISABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_DISABLED; + } + break; + case AB8500_CODEC_DEST_VIBRATOR_L: + if (AB8500_CODEC_DEST_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + } + + else + { + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_DISABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLGPOL; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLGPOL; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLGPOL; + } + break; + case AB8500_CODEC_DEST_VIBRATOR_R: + if (AB8500_CODEC_DEST_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + } + + else + { + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_DISABLED; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRGPOL; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRGPOL; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRGPOL; + } + break; + case AB8500_CODEC_DEST_ALL: + if (AB8500_CODEC_DEST_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + } + + else + { + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_DISABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_DISABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLGPOL; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLGPOL; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLGPOL; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_DA_PATH; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRGPOL; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRGPOL; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRGPOL; + } + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_DestPowerControlUpdateCR(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_GetVersion */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* This routine populates the pVersion structure with */ +/* the current version of HCL. */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* p_version: this parameter is used to return current HCL version. */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_ERROR: if p_version is NULL. */ +/* AB8500_CODEC_OK: if successful */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_GetVersion(OUT t_version * p_version) +{ + DBGENTER1(" (%lx)", p_version); + if (p_version != NULL) + { + p_version->minor = AB8500_CODEC_HCL_MINOR_ID; + p_version->major = AB8500_CODEC_HCL_MAJOR_ID; + p_version->version = AB8500_CODEC_HCL_VERSION_ID; + DBGEXIT0(AB8500_CODEC_OK); + return (AB8500_CODEC_OK); + } + + else + { + DBGEXIT0(AB8500_CODEC_INVALID_PARAMETER); + return (AB8500_CODEC_INVALID_PARAMETER); + } +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_SetDbgLevel */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Set the debug level used by the debug module (mask-like value). */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* debug_level: debug level to be set */ +/* OUT: */ +/* None */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_OK: always */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +/* +PUBLIC t_ab8500_codec_error AB8500_CODEC_SetDbgLevel(IN t_dbg_level dbg_level) +{ + DBGENTER1(" (%d)", dbg_level); + dbg_level = dbg_level; +#ifdef __DEBUG + MY_DEBUG_LEVEL_VAR_NAME = dbg_level; +#endif + DBGEXIT(AB8500_CODEC_OK); + return(AB8500_CODEC_OK); +} + */ + +/****************************************************************************/ +/* NAME: AB8500_CODEC_GetDbgLevel */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Set the debug level used by the debug module (mask-like value). */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* p_dbg_level: this parameter is used to return debug level. */ +/* */ +/* RETURN: */ +/* AB8500_CODEC_ERROR: if p_version is NULL. */ +/* AB8500_CODEC_OK: if successful */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Re-Entrant */ +/* REENTRANCY ISSUES: No Issues */ + +/****************************************************************************/ +/* +PUBLIC t_ab8500_codec_error AB8500_CODEC_GetDbgLevel(OUT t_dbg_level *p_dbg_level) +{ + if (NULL == p_dbg_level) + { + DBGEXIT(AB8500_CODEC_INVALID_PARAMETER); + return(AB8500_CODEC_INVALID_PARAMETER); + } + +#ifdef __DEBUG + * p_dbg_level = MY_DEBUG_LEVEL_VAR_NAME; +#endif + DBGEXIT(AB8500_CODEC_OK); + return(AB8500_CODEC_OK); +} +*/ +/****************************************************************************/ +/* NAME: AB8500_CODEC_ADSlotAllocation */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* AD Data Allocation in slots. */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_slot: The slot to be allocated. */ +/* IN: t_ab8500_codec_cr31_to_cr46_ad_data_allocation: The value */ +/* to be allocated. */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If invalid slot number */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_ADSlotAllocation + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value ) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER2(" (%lx %lx)", ad_slot, value); + if (ad_slot <= AB8500_CODEC_SLOT7) + { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch1(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else if (ad_slot <= AB8500_CODEC_SLOT15) + { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch2(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else if (ad_slot <= AB8500_CODEC_SLOT23) + { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch3(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else if (ad_slot <= AB8500_CODEC_SLOT31) + { + ab8500_codec_error = + ab8500_codec_ADSlotAllocationSwitch4(ad_slot, value); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else + { + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DASlotAllocation */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Allocate the Audio Interface slot for DA paths. */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_da_channel_number: Channel number 1/2/3/4/5/6 */ +/* IN: t_ab8500_codec_cr51_to_cr56_sltoda: Slot number */ +/* */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If invalid channel number */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DASlotAllocation + (IN t_ab8500_codec_da_channel_number channel_number, + IN t_ab8500_codec_cr51_to_cr58_sltoda slot ) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + t_ab8500_codec_cr0_powerup ab8500_codec_cr0_powerup; + DBGENTER2(" (%lx %lx)", channel_number, slot); + p_ab8500_codec_configuration->cr51_da12_voice = + AB8500_CODEC_CR51_DA12_VOICE_LOWLATENCYFILTER; + switch (channel_number) + { + case AB8500_CODEC_DA_CHANNEL_NUMBER_1: + p_ab8500_codec_configuration->cr51_sltoda1 = slot; + break; + case AB8500_CODEC_DA_CHANNEL_NUMBER_2: + p_ab8500_codec_configuration->cr52_sltoda2 = slot; + break; + case AB8500_CODEC_DA_CHANNEL_NUMBER_3: + p_ab8500_codec_configuration->cr53_sltoda3 = slot; + break; + case AB8500_CODEC_DA_CHANNEL_NUMBER_4: + p_ab8500_codec_configuration->cr54_sltoda4 = slot; + break; + case AB8500_CODEC_DA_CHANNEL_NUMBER_5: + p_ab8500_codec_configuration->cr55_sltoda5 = slot; + break; + case AB8500_CODEC_DA_CHANNEL_NUMBER_6: + p_ab8500_codec_configuration->cr56_sltoda6 = slot; + break; + case AB8500_CODEC_DA_CHANNEL_NUMBER_7: + p_ab8500_codec_configuration->cr57_sltoda7 = slot; + break; + case AB8500_CODEC_DA_CHANNEL_NUMBER_8: + p_ab8500_codec_configuration->cr58_sltoda8 = slot; + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_cr0_powerup = p_ab8500_codec_configuration->cr0_powerup; + p_ab8500_codec_configuration->cr0_powerup = + AB8500_CODEC_CR0_POWERUP_OFF; + ab8500_codec_error = ab8500_codec_UpdateCR0(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR51(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR52(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR53(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR54(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR55(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR56(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR57(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR58(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + p_ab8500_codec_configuration->cr0_powerup = ab8500_codec_cr0_powerup; + ab8500_codec_error = ab8500_codec_UpdateCR0(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_ConfigureBurstFifo */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Configuration for Burst FIFO control */ +/* */ +/* ARGUMENTS */ +/* IN: t_ab8500_codec_burst_fifo_config: structure for configuration of */ +/* burst FIFO */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_INVALID_PARAMETER: If invalid parameter */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: If interface 1 selected */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +t_ab8500_codec_error AB8500_CODEC_ConfigureBurstFifo(IN + t_ab8500_codec_burst_fifo_config + const *const + p_burst_fifo_config) +{ + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER1(" (%lx)", p_burst_fifo_config); + if (AB8500_CODEC_AUDIO_INTERFACE_0 == + g_ab8500_codec_system_context.audio_interface) + { + if (AB8500_CODEC_CR27_EN_MASTGEN_ENABLED == + p_ab8500_codec_configuration->cr27_en_mastgen) + { + p_ab8500_codec_configuration->cr105_bfifomsk = + p_burst_fifo_config->cr105_bfifomsk; + p_ab8500_codec_configuration->cr105_bfifoint = + p_burst_fifo_config->cr105_bfifoint; + p_ab8500_codec_configuration->cr106_bfifotx = + p_burst_fifo_config->cr106_bfifotx; + p_ab8500_codec_configuration->cr107_bfifoexsl = + p_burst_fifo_config->cr107_bfifoexsl; + p_ab8500_codec_configuration->cr107_bfifomast = + p_burst_fifo_config->cr107_bfifomast; + p_ab8500_codec_configuration->cr107_bfiforun = + p_burst_fifo_config->cr107_bfiforun; + p_ab8500_codec_configuration->cr108_bfifoframsw = + p_burst_fifo_config->cr108_bfifoframsw; + p_ab8500_codec_configuration->cr109_bfifowakeup = + p_burst_fifo_config->cr109_bfifowakeup; + ab8500_codec_error = ab8500_codec_UpdateCR105(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR106(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR107(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR108(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR109(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else + { + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else + { + ab8500_codec_error = AB8500_CODEC_UNSUPPORTED_FEATURE; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_EnableBurstFifo */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Enable the Burst FIFO for Interface 0 */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: If Interface 1 is selected */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_EnableBurstFifo(void) +{ + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + if (AB8500_CODEC_AUDIO_INTERFACE_0 == + g_ab8500_codec_system_context.audio_interface) + { + p_ab8500_codec_configuration->cr29_if0bfifoen = + AB8500_CODEC_CR29_IF0BFIFOEN_BURST_MODE; + ab8500_codec_error = ab8500_codec_UpdateCR29(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else + { + ab8500_codec_error = AB8500_CODEC_UNSUPPORTED_FEATURE; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +/****************************************************************************/ +/* NAME: AB8500_CODEC_DisableBurstFifo */ +/*--------------------------------------------------------------------------*/ +/* DESCRIPTION: */ +/* Disable the Burst FIFO for Interface 0 */ +/* */ +/* ARGUMENTS */ +/* IN: */ +/* None */ +/* OUT: */ +/* None */ +/* RETURN: */ +/* AB8500_CODEC_UNSUPPORTED_FEATURE: If Interface 1 is selected */ +/* AB8500_CODEC_OK: if successful. */ +/* REMARK: */ +/*--------------------------------------------------------------------------*/ +/* REENTRANCY: Non Re-Entrant */ + +/****************************************************************************/ +PUBLIC t_ab8500_codec_error AB8500_CODEC_DisableBurstFifo(void) +{ + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + DBGENTER0(); + if (AB8500_CODEC_AUDIO_INTERFACE_0 == + g_ab8500_codec_system_context.audio_interface) + { + p_ab8500_codec_configuration->cr29_if0bfifoen = + AB8500_CODEC_CR29_IF0BFIFOEN_NORMAL_MODE; + ab8500_codec_error = ab8500_codec_UpdateCR29(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + } + + else + { + ab8500_codec_error = AB8500_CODEC_UNSUPPORTED_FEATURE; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch1 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value ) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (ad_slot) + { + case AB8500_CODEC_SLOT0: + p_ab8500_codec_configuration->cr31_adotoslot0 = value; + ab8500_codec_error = ab8500_codec_UpdateCR31(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT1: + p_ab8500_codec_configuration->cr31_adotoslot1 = value; + ab8500_codec_error = ab8500_codec_UpdateCR31(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT2: + p_ab8500_codec_configuration->cr32_adotoslot2 = value; + ab8500_codec_error = ab8500_codec_UpdateCR32(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT3: + p_ab8500_codec_configuration->cr32_adotoslot3 = value; + ab8500_codec_error = ab8500_codec_UpdateCR32(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT4: + p_ab8500_codec_configuration->cr33_adotoslot4 = value; + ab8500_codec_error = ab8500_codec_UpdateCR33(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT5: + p_ab8500_codec_configuration->cr33_adotoslot5 = value; + ab8500_codec_error = ab8500_codec_UpdateCR33(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT6: + p_ab8500_codec_configuration->cr34_adotoslot6 = value; + ab8500_codec_error = ab8500_codec_UpdateCR34(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT7: + p_ab8500_codec_configuration->cr34_adotoslot7 = value; + ab8500_codec_error = ab8500_codec_UpdateCR34(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch2 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value ) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (ad_slot) + { + case AB8500_CODEC_SLOT8: + p_ab8500_codec_configuration->cr35_adotoslot8 = value; + ab8500_codec_error = ab8500_codec_UpdateCR35(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT9: + p_ab8500_codec_configuration->cr35_adotoslot9 = value; + ab8500_codec_error = ab8500_codec_UpdateCR35(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT10: + p_ab8500_codec_configuration->cr36_adotoslot10 = value; + ab8500_codec_error = ab8500_codec_UpdateCR36(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT11: + p_ab8500_codec_configuration->cr36_adotoslot11 = value; + ab8500_codec_error = ab8500_codec_UpdateCR36(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT12: + p_ab8500_codec_configuration->cr37_adotoslot12 = value; + ab8500_codec_error = ab8500_codec_UpdateCR37(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT13: + p_ab8500_codec_configuration->cr37_adotoslot13 = value; + ab8500_codec_error = ab8500_codec_UpdateCR37(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT14: + p_ab8500_codec_configuration->cr38_adotoslot14 = value; + ab8500_codec_error = ab8500_codec_UpdateCR38(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT15: + p_ab8500_codec_configuration->cr38_adotoslot15 = value; + ab8500_codec_error = ab8500_codec_UpdateCR38(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch3 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value ) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (ad_slot) + { + case AB8500_CODEC_SLOT16: + p_ab8500_codec_configuration->cr39_adotoslot16 = value; + ab8500_codec_error = ab8500_codec_UpdateCR39(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT17: + p_ab8500_codec_configuration->cr39_adotoslot17 = value; + ab8500_codec_error = ab8500_codec_UpdateCR39(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT18: + p_ab8500_codec_configuration->cr40_adotoslot18 = value; + ab8500_codec_error = ab8500_codec_UpdateCR40(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT19: + p_ab8500_codec_configuration->cr40_adotoslot19 = value; + ab8500_codec_error = ab8500_codec_UpdateCR40(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT20: + p_ab8500_codec_configuration->cr41_adotoslot20 = value; + ab8500_codec_error = ab8500_codec_UpdateCR41(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT21: + p_ab8500_codec_configuration->cr41_adotoslot21 = value; + ab8500_codec_error = ab8500_codec_UpdateCR41(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT22: + p_ab8500_codec_configuration->cr42_adotoslot22 = value; + ab8500_codec_error = ab8500_codec_UpdateCR42(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT23: + p_ab8500_codec_configuration->cr42_adotoslot23 = value; + ab8500_codec_error = ab8500_codec_UpdateCR42(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ADSlotAllocationSwitch4 + (IN t_ab8500_codec_slot ad_slot, + IN t_ab8500_codec_cr31_to_cr46_ad_data_allocation value ) { + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (ad_slot) + { + case AB8500_CODEC_SLOT24: + p_ab8500_codec_configuration->cr43_adotoslot24 = value; + ab8500_codec_error = ab8500_codec_UpdateCR43(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT25: + p_ab8500_codec_configuration->cr43_adotoslot25 = value; + ab8500_codec_error = ab8500_codec_UpdateCR43(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT26: + p_ab8500_codec_configuration->cr44_adotoslot26 = value; + ab8500_codec_error = ab8500_codec_UpdateCR44(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT27: + p_ab8500_codec_configuration->cr44_adotoslot27 = value; + ab8500_codec_error = ab8500_codec_UpdateCR44(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT28: + p_ab8500_codec_configuration->cr45_adotoslot28 = value; + ab8500_codec_error = ab8500_codec_UpdateCR45(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT29: + p_ab8500_codec_configuration->cr45_adotoslot29 = value; + ab8500_codec_error = ab8500_codec_UpdateCR45(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT30: + p_ab8500_codec_configuration->cr46_adotoslot30 = value; + ab8500_codec_error = ab8500_codec_UpdateCR46(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + case AB8500_CODEC_SLOT31: + p_ab8500_codec_configuration->cr46_adotoslot31 = value; + ab8500_codec_error = ab8500_codec_UpdateCR46(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch1(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (src_device) + { + case AB8500_CODEC_SRC_LINEIN: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_LINR; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + } + + else + { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_MIC2; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + } + break; + case AB8500_CODEC_SRC_MICROPHONE_1A: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1A; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + } + + else + { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_DISABLED; + } + break; + case AB8500_CODEC_SRC_MICROPHONE_1B: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1B; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + } + + else + { + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_DISABLED; + } + break; + case AB8500_CODEC_SRC_MICROPHONE_2: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_MIC2; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + } + + else + { + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_LINR; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_1: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + p_ab8500_codec_configuration->cr63_ad1sel = + AB8500_CODEC_CR63_AD1SEL_DMIC1_SELECTED; + } + + else + { + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr63_ad1sel = + AB8500_CODEC_CR63_AD1SEL_LINLADL_SELECTED; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_2: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + p_ab8500_codec_configuration->cr63_ad2sel = + AB8500_CODEC_CR63_AD2SEL_DMIC2_SELECTED; + } + + else + { + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr63_ad2sel = + AB8500_CODEC_CR63_AD2SEL_LINRADR_SELECTED; + } + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SrcPowerControlSwitch2(IN + t_ab8500_codec_src + src_device, + t_ab8500_codec_src_state + state) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (src_device) + { + case AB8500_CODEC_SRC_D_MICROPHONE_3: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + p_ab8500_codec_configuration->cr63_ad3sel = + AB8500_CODEC_CR63_AD3SEL_DMIC3_SELECTED; + } + + else + { + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr63_ad3sel = + AB8500_CODEC_CR63_AD3SEL_ADMO_SELECTED; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_4: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + } + + else + { + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_5: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + p_ab8500_codec_configuration->cr63_ad5sel = + AB8500_CODEC_CR63_AD5SEL_DMIC5_SELECTED; + } + + else + { + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr63_ad5sel = + AB8500_CODEC_CR63_AD5SEL_AMADR_SELECTED; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_6: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + p_ab8500_codec_configuration->cr63_ad6sel = + AB8500_CODEC_CR63_AD6SEL_DMIC6_SELECTED; + } + + else + { + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr63_ad6sel = + AB8500_CODEC_CR63_AD6SEL_ADMO_SELECTED; + } + break; + case AB8500_CODEC_SRC_ALL: + if (AB8500_CODEC_SRC_STATE_ENABLE == state) + { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + } + + else + { + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + } + break; + case AB8500_CODEC_SRC_FM_RX: + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetModeAndDirectionUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + t_ab8500_codec_cr0_powerup ab8500_codec_cr0_powerup; + ab8500_codec_cr0_powerup = p_ab8500_codec_configuration->cr0_powerup; + p_ab8500_codec_configuration->cr0_powerup = + AB8500_CODEC_CR0_POWERUP_OFF; + ab8500_codec_error = ab8500_codec_UpdateCR0(); + if (AB8500_CODEC_OK != ab8500_codec_error) + { + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR2(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR3(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR26(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR27(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR28(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR30(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR63(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + p_ab8500_codec_configuration->cr0_powerup = ab8500_codec_cr0_powerup; + ab8500_codec_error = ab8500_codec_UpdateCR0(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetSrcVolumeUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + ab8500_codec_error = ab8500_codec_UpdateCR20(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR21(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR23(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR65(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR66(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR67(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR68(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR69(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR70(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_SetDestVolumeUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + ab8500_codec_error = ab8500_codec_UpdateCR16(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR17(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR18(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR19(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR22(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR71(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR72(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR73(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR74(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR75(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR76(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR79(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR80(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionIN(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (g_ab8500_codec_system_context.ab8500_codec_src) + { + case AB8500_CODEC_SRC_LINEIN: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_LINR; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_MICROPHONE_1A: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1A; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_MICROPHONE_1B: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_mic1sel = + AB8500_CODEC_CR7_MIC1SEL_MIC1B; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_MICROPHONE_2: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_linrsel = + AB8500_CODEC_CR7_LINRSEL_MIC2; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_1: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr63_ad1sel = + AB8500_CODEC_CR63_AD1SEL_DMIC1_SELECTED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_2: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr63_ad2sel = + AB8500_CODEC_CR63_AD2SEL_DMIC2_SELECTED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_3: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr63_ad3sel = + AB8500_CODEC_CR63_AD3SEL_DMIC3_SELECTED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_4: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_5: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_DISABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr63_ad5sel = + AB8500_CODEC_CR63_AD5SEL_DMIC5_SELECTED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_6: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_DISABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_DISABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_DISABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_DISABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_DISABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_DISABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_DISABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_DISABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_DISABLED; + p_ab8500_codec_configuration->cr63_ad6sel = + AB8500_CODEC_CR63_AD6SEL_DMIC6_SELECTED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_ENABLED; + break; + case AB8500_CODEC_SRC_ALL: + p_ab8500_codec_configuration->cr5_enlinl = + AB8500_CODEC_CR5_ENLINL_ENABLED; + p_ab8500_codec_configuration->cr5_enlinr = + AB8500_CODEC_CR5_ENLINR_ENABLED; + p_ab8500_codec_configuration->cr5_enmic1 = + AB8500_CODEC_CR5_ENMIC1_ENABLED; + p_ab8500_codec_configuration->cr5_enmic2 = + AB8500_CODEC_CR5_ENMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic1 = + AB8500_CODEC_CR6_ENDMIC1_ENABLED; + p_ab8500_codec_configuration->cr6_endmic2 = + AB8500_CODEC_CR6_ENDMIC2_ENABLED; + p_ab8500_codec_configuration->cr6_endmic3 = + AB8500_CODEC_CR6_ENDMIC3_ENABLED; + p_ab8500_codec_configuration->cr6_endmic4 = + AB8500_CODEC_CR6_ENDMIC4_ENABLED; + p_ab8500_codec_configuration->cr6_endmic5 = + AB8500_CODEC_CR6_ENDMIC5_ENABLED; + p_ab8500_codec_configuration->cr6_endmic6 = + AB8500_CODEC_CR6_ENDMIC6_ENABLED; + p_ab8500_codec_configuration->cr7_enadcmic = + AB8500_CODEC_CR7_ENADCMIC_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinl = + AB8500_CODEC_CR7_ENADCLINL_ENABLED; + p_ab8500_codec_configuration->cr7_enadclinr = + AB8500_CODEC_CR7_ENADCLINR_ENABLED; + p_ab8500_codec_configuration->cr5_mutlinl = + AB8500_CODEC_CR5_MUTLINL_DISABLED; + p_ab8500_codec_configuration->cr5_mutlinr = + AB8500_CODEC_CR5_MUTLINR_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic1 = + AB8500_CODEC_CR5_MUTMIC1_DISABLED; + p_ab8500_codec_configuration->cr5_mutmic2 = + AB8500_CODEC_CR5_MUTMIC2_DISABLED; + break; + case AB8500_CODEC_SRC_D_MICROPHONE_12: + case AB8500_CODEC_SRC_D_MICROPHONE_34: + case AB8500_CODEC_SRC_D_MICROPHONE_56: + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_ProgramDirectionOUT(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + t_ab8500_codec_configuration * p_ab8500_codec_configuration = + &g_ab8500_codec_system_context. ab8500_codec_configuration; + switch (g_ab8500_codec_system_context.ab8500_codec_dest) + { + case AB8500_CODEC_DEST_HEADSET: + p_ab8500_codec_configuration->cr7_endrvhsl = + AB8500_CODEC_CR7_ENDRVHSL_ENABLED; + p_ab8500_codec_configuration->cr7_endrvhsr = + AB8500_CODEC_CR7_ENDRVHSR_ENABLED; + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + p_ab8500_codec_configuration->cr9_endachsl = + AB8500_CODEC_CR9_ENDACHSL_ENABLED; + p_ab8500_codec_configuration->cr9_endachsr = + AB8500_CODEC_CR9_ENDACHSR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + p_ab8500_codec_configuration->cr12_encphs = + AB8500_CODEC_CR12_ENCPHS_ENABLED; + break; + case AB8500_CODEC_DEST_EARPIECE: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + break; + case AB8500_CODEC_DEST_HANDSFREE: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + break; + case AB8500_CODEC_DEST_VIBRATOR_L: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_DISABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + break; + case AB8500_CODEC_DEST_VIBRATOR_R: + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_DISABLED; + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_DISABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_DISABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_DISABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_DISABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_DISABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_ENABLED; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + break; + case AB8500_CODEC_DEST_ALL: + p_ab8500_codec_configuration->cr8_enhsl = + AB8500_CODEC_CR8_ENHSL_ENABLED; + p_ab8500_codec_configuration->cr8_enhsr = + AB8500_CODEC_CR8_ENHSR_ENABLED; + p_ab8500_codec_configuration->cr8_enear = + AB8500_CODEC_CR8_ENEAR_ENABLED; + p_ab8500_codec_configuration->cr8_enhfl = + AB8500_CODEC_CR8_ENHFL_ENABLED; + p_ab8500_codec_configuration->cr8_enhfr = + AB8500_CODEC_CR8_ENHFR_ENABLED; + p_ab8500_codec_configuration->cr8_envibl = + AB8500_CODEC_CR8_ENVIBL_ENABLED; + p_ab8500_codec_configuration->cr8_envibr = + AB8500_CODEC_CR8_ENVIBR_ENABLED; + p_ab8500_codec_configuration->cr9_endacear = + AB8500_CODEC_CR9_ENDACEAR_ENABLED; + p_ab8500_codec_configuration->cr9_endachfl = + AB8500_CODEC_CR9_ENDACHFL_ENABLED; + p_ab8500_codec_configuration->cr9_endachfr = + AB8500_CODEC_CR9_ENDACHFR_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibl = + AB8500_CODEC_CR9_ENDACVIBL_ENABLED; + p_ab8500_codec_configuration->cr9_endacvibr = + AB8500_CODEC_CR9_ENDACVIBR_ENABLED; + p_ab8500_codec_configuration->cr10_mutehsl = + AB8500_CODEC_CR10_MUTEHSL_DISABLED; + p_ab8500_codec_configuration->cr10_mutehsr = + AB8500_CODEC_CR10_MUTEHSR_DISABLED; + p_ab8500_codec_configuration->cr10_muteear = + AB8500_CODEC_CR10_MUTEEAR_DISABLED; + p_ab8500_codec_configuration->cr15_pwmtovibl = + AB8500_CODEC_CR15_PWMTOVIBL_PWM; + p_ab8500_codec_configuration->cr15_pwmlctrl = + AB8500_CODEC_CR15_PWMLCTRL_PWMNPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnlctrl = + AB8500_CODEC_CR15_PWMNLCTRL_PWMNLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmplctrl = + AB8500_CODEC_CR15_PWMPLCTRL_PWMPLDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmtovibr = + AB8500_CODEC_CR15_PWMTOVIBR_PWM; + p_ab8500_codec_configuration->cr15_pwmrctrl = + AB8500_CODEC_CR15_PWMRCTRL_PWMNPRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmnrctrl = + AB8500_CODEC_CR15_PWMNRCTRL_PWMNRDUTYCYCLE; + p_ab8500_codec_configuration->cr15_pwmprctrl = + AB8500_CODEC_CR15_PWMPRCTRL_PWMPRDUTYCYCLE; + break; + default: + ab8500_codec_error = AB8500_CODEC_INVALID_PARAMETER; + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} + +PRIVATE t_ab8500_codec_error ab8500_codec_DestPowerControlUpdateCR(void) +{ + t_ab8500_codec_error ab8500_codec_error = AB8500_CODEC_OK; + ab8500_codec_error = ab8500_codec_UpdateCR8(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR9(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR10(); + if (ab8500_codec_error != AB8500_CODEC_OK) + { + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); + } + ab8500_codec_error = ab8500_codec_UpdateCR15(); + DBGEXIT(ab8500_codec_error); + return (ab8500_codec_error); +} diff --git a/sound/u8500_acodec_ab8500.c b/sound/u8500_acodec_ab8500.c new file mode 100644 index 00000000000..2c1ae702b79 --- /dev/null +++ b/sound/u8500_acodec_ab8500.c @@ -0,0 +1,2522 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Deepak Karda + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +/*----------------------------------------------------------------------------- +* Common Includes +*---------------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/*#include */ +#include +#include +#include + +#ifdef CONFIG_U8500_AB8500_CUT10 +#include +#endif +#ifdef CONFIG_U8500_AB8500_ED +#include +#endif + +#define ELEMENT_SIZE 0 +#define FRAME_SIZE -1 +#define MSP_NUM 0 + +/* Debugging stuff */ + +#define ACODEC_NAME "DRIVER ACODEC" +#define DRIVER_DEBUG CONFIG_STM_ACODEC_DEBUG /* enables/disables debug msgs */ +#define DRIVER_DEBUG_PFX ACODEC_NAME /* msg header represents this module */ +#define DRIVER_DBG KERN_ERR /* message level */ +#define NMDK_DEBUG CONFIG_STM_ACODEC_DEBUG +extern struct driver_debug_st DBG_ST; + +#if NMDK_DEBUG > 0 +t_ab8500_codec_error dump_acodec_registers(void); +t_ab8500_codec_error dump_msp_registers(void); +#endif + +#ifdef CONFIG_U8500_ACODEC_DMA +static void u8500_digital_lpbk_tx_dma_start(void); +static void u8500_digital_lpbk_rx_dma_start(void); +#endif + +int second_config; +/*---------------------------------------------------------------------------- +* global declarations +*---------------------------------------------------------------------------*/ +t_u8500_codec_system_context g_codec_system_context; + +int u8500_acodec_rates[MAX_NO_OF_RATES] = { 48000 }; + +char *codec_dest_texts[NUMBER_OUTPUT_DEVICE] = { + "CODEC_DEST_HEADSET", "CODEC_DEST_EARPIECE", "CODEC_DEST_HANDSFREE", + "CODEC_DEST_VIBRATOR1", "CODEC_DEST_VIBRATOR2" +}; + +char *codec_in_texts[NUMBER_INPUT_DEVICE] = { + "CODEC_SRC_LINEIN", "CODEC_SRC_MICROPHONE_1A", + "CODEC_SRC_MICROPHONE_1B", + "CODEC_SRC_MICROPHONE_2", "CODEC_SRC_D_MICROPHONE_1", + "CODEC_SRC_D_MICROPHONE_2", + "CODEC_SRC_D_MICROPHONE_3", "CODEC_SRC_D_MICROPHONE_4", + "CODEC_SRC_D_MICROPHONE_5", + "CODEC_SRC_D_MICROPHONE_6", "CODEC_SRC_D_MICROPHONE_12", + "CODEC_SRC_D_MICROPHONE_34", + "CODEC_SRC_D_MICROPHONE_56" +}; + +char *lpbk_state_in_texts[NUMBER_LOOPBACK_STATE] = { "DISABLE", "ENABLE" }; +char *switch_state_in_texts[NUMBER_SWITCH_STATE] = { "DISABLE", "ENABLE" }; +char *power_state_in_texts[NUMBER_POWER_STATE] = { "DISABLE", "ENABLE" }; +char *tdm_mode_state_in_texts[NUMBER_POWER_STATE] = { "DISABLE", "ENABLE" }; +char *direct_rendering_state_in_texts[NUMBER_DIRECT_RENDERING_STATE] = + { "DISABLE", "ENABLE" }; +char *pcm_rendering_state_in_texts[NUMBER_PCM_RENDERING_STATE] = + { "DISABLE", "ENABLE", "PENDING" }; + +EXPORT_SYMBOL(codec_dest_texts); +EXPORT_SYMBOL(codec_in_texts); + +static void ab8500_codec_power_init(void); +static int check_device_id(); +t_ab8500_codec_error perform_src_routing(t_ab8500_codec_src input_device); +t_ab8500_codec_error + u8500_acodec_allocate_all_mono_slots + (t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line1); +t_ab8500_codec_error + u8500_acodec_allocate_all_stereo_slots + (t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line1, + t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line2); + +#if 0 //from Arnaud +/* For Codec in Master mode for recording*/ +struct msp_protocol_desc protocol_desc_tdm_mode = { + MSP_DATA_TRANSFER_WIDTH_HALFWORD, /*rx_data_transfer_width */ + MSP_DATA_TRANSFER_WIDTH_HALFWORD, /*tx_data_transfer_width */ + MSP_SINGLE_PHASE, /*rx_phase_mode */ + MSP_SINGLE_PHASE, /*tx_phase_mode */ + MSP_PHASE2_START_MODE_IMEDIATE, /*rx_phase2_start_mode */ + MSP_PHASE2_START_MODE_IMEDIATE, /*tx_phase2_start_mode */ + MSP_BTF_MS_BIT_FIRST, /*rx_endianess */ + MSP_BTF_MS_BIT_FIRST, /*tx_endianess */ + MSP_FRAME_LENGTH_2, /*rx_frame_length_1 */ + MSP_FRAME_LENGTH_2, /*rx_frame_length_2 */ + MSP_FRAME_LENGTH_2, /*tx_frame_length_1 */ + MSP_FRAME_LENGTH_2, /*tx_frame_length_2 */ + MSP_ELEM_LENGTH_16, /*rx_element_length_1 */ + MSP_ELEM_LENGTH_16, /*rx_element_length_2 */ + MSP_ELEM_LENGTH_16, /*tx_element_length_1 */ + MSP_ELEM_LENGTH_16, /*tx_element_length_2 */ + MSP_DELAY_0, /*rx_data_delay */ + MSP_DELAY_0, /*tx_data_delay */ + MSP_FALLING_EDGE, /*rx_clock_pol */ + MSP_RISING_EDGE, /*tx_clock_pol */ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, /*rx_msp_frame_pol */ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, /*tx_msp_frame_pol */ + MSP_HWS_NO_SWAP, /*rx_half_word_swap */ + MSP_HWS_NO_SWAP, /*tx_half_word_swap */ + MSP_COMPRESS_MODE_LINEAR, /*compression_mode */ + MSP_EXPAND_MODE_LINEAR, /*expansion_mode */ + MSP_SPI_CLOCK_MODE_NON_SPI, /*spi_clk_mode */ + MSP_SPI_BURST_MODE_DISABLE, /*spi_burst_mode */ + 63, /*frame_period */ + 31, /*frame_width */ + 64, /*total_clocks_for_one_frame */ +}; +#endif + +#if 0 //from HCL +/* For Codec in Master mode for recording*/ +struct msp_protocol_desc protocol_desc_tdm_mode = { + MSP_DATA_TRANSFER_WIDTH_WORD, /*rx_data_transfer_width */ + MSP_DATA_TRANSFER_WIDTH_WORD, /*tx_data_transfer_width */ + MSP_DUAL_PHASE, /*rx_phase_mode */ + MSP_DUAL_PHASE, /*tx_phase_mode */ + MSP_PHASE2_START_MODE_FRAME_SYNC, /*rx_phase2_start_mode */ + MSP_PHASE2_START_MODE_FRAME_SYNC, /*tx_phase2_start_mode */ + MSP_BTF_MS_BIT_FIRST, /*rx_endianess */ + MSP_BTF_MS_BIT_FIRST, /*tx_endianess */ + MSP_FRAME_LENGTH_1, /*rx_frame_length_1 */ + MSP_FRAME_LENGTH_1, /*rx_frame_length_2 */ + MSP_FRAME_LENGTH_1, /*tx_frame_length_1 */ + MSP_FRAME_LENGTH_1, /*tx_frame_length_2 */ + MSP_ELEM_LENGTH_16, /*rx_element_length_1 */ + MSP_ELEM_LENGTH_16, /*rx_element_length_2 */ + MSP_ELEM_LENGTH_16, /*tx_element_length_1 */ + MSP_ELEM_LENGTH_16, /*tx_element_length_2 */ + MSP_DELAY_0, /*rx_data_delay */ + MSP_DELAY_0, /*tx_data_delay */ + MSP_RISING_EDGE, /*rx_clock_pol */ + MSP_RISING_EDGE, /*tx_clock_pol */ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, /*rx_msp_frame_pol */ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, /*tx_msp_frame_pol */ + MSP_HWS_NO_SWAP, /*rx_half_word_swap */ + MSP_HWS_NO_SWAP, /*tx_half_word_swap */ + MSP_COMPRESS_MODE_LINEAR, /*compression_mode */ + MSP_EXPAND_MODE_LINEAR, /*expansion_mode */ + MSP_SPI_CLOCK_MODE_NON_SPI, /*spi_clk_mode */ + MSP_SPI_BURST_MODE_DISABLE, /*spi_burst_mode */ + 255, /*frame_period */ + 0, /*frame_width */ + 256, /*total_clocks_for_one_frame */ +}; + +#endif + +#if 0 //from STS +struct msp_protocol_desc protocol_desc_tdm_mode = { + MSP_DATA_TRANSFER_WIDTH_HALFWORD, /*rx_data_transfer_width */ + MSP_DATA_TRANSFER_WIDTH_HALFWORD, /*tx_data_transfer_width */ + MSP_SINGLE_PHASE, /*rx_phase_mode */ + MSP_SINGLE_PHASE, /*tx_phase_mode */ + MSP_PHASE2_START_MODE_IMEDIATE, /*rx_phase2_start_mode */ + MSP_PHASE2_START_MODE_IMEDIATE, /*tx_phase2_start_mode */ + MSP_BTF_MS_BIT_FIRST, /*rx_endianess */ + MSP_BTF_MS_BIT_FIRST, /*tx_endianess */ + MSP_FRAME_LENGTH_2, /*rx_frame_length_1 */ + MSP_FRAME_LENGTH_1, /*rx_frame_length_2 */ + MSP_FRAME_LENGTH_2, /*tx_frame_length_1 */ + MSP_FRAME_LENGTH_1, /*tx_frame_length_2 */ + MSP_ELEM_LENGTH_16, /*rx_element_length_1 */ + MSP_ELEM_LENGTH_16, /*rx_element_length_2 */ + MSP_ELEM_LENGTH_16, /*tx_element_length_1 */ + MSP_ELEM_LENGTH_16, /*tx_element_length_2 */ + MSP_DELAY_0, /*rx_data_delay */ + MSP_DELAY_0, /*tx_data_delay */ + MSP_FALLING_EDGE, /*rx_clock_pol */ + MSP_RISING_EDGE, /*tx_clock_pol */ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, /*rx_msp_frame_pol */ + MSP_FRAME_SYNC_POL_ACTIVE_HIGH, /*tx_msp_frame_pol */ + MSP_HWS_NO_SWAP, /*rx_half_word_swap */ + MSP_HWS_NO_SWAP, /*tx_half_word_swap */ + MSP_COMPRESS_MODE_LINEAR, /*compression_mode */ + MSP_EXPAND_MODE_LINEAR, /*expansion_mode */ + MSP_SPI_CLOCK_MODE_NON_SPI, /*spi_clk_mode */ + MSP_SPI_BURST_MODE_DISABLE, /*spi_burst_mode */ + 25, /*frame_period */ + 32, /*frame_width */ + 32, /*total_clocks_for_one_frame */ +}; +#endif + +#define DIGITAL_LPBK_MAX_BIFFERS 3 + +#ifdef CONFIG_U8500_AB8500_CUT10 +#define NB_OF_CHANNEL_USED 8 +#else +#define NB_OF_CHANNEL_USED 6 +#endif + +#define MONO_SRC 1 +#define STEREO_SRC 2 + +typedef struct { + unsigned char *area; /* virtual pointer */ + dma_addr_t addr; /* physical address */ +} t_dma_buffer; + +typedef struct { + struct completion tx_dma_com; + struct completion rx_dma_com; + volatile int rx_active; + volatile int tx_active; + t_dma_buffer buffer; + int data_size; + int rx_index; + int tx_index; +} t_digital_lpbk_cnxt; + +const int play_flag = 1; +const int capture_flag = 2; + +t_digital_lpbk_cnxt digital_lpbk_cnxt; + +void u8500_set_defaults() +{ + int i; + + for (i = 0; i < NUMBER_INPUT_DEVICE; i++) { + g_codec_system_context.input_config[i].left_volume = 0; + g_codec_system_context.input_config[i].right_volume = 0; + g_codec_system_context.input_config[i].mute_state = DISABLE; + g_codec_system_context.input_config[i].power_state = DISABLE; + } + + for (i = 0; i < NUMBER_OUTPUT_DEVICE; i++) { + g_codec_system_context.output_config[i].left_volume = 0; + g_codec_system_context.output_config[i].right_volume = 0; + g_codec_system_context.output_config[i].mute_state = DISABLE; + g_codec_system_context.output_config[i].power_state = DISABLE; + } + +} //END OF FUNCTION + +struct i2sdrv_data *i2sdrv[MAX_I2S_CLIENTS]; + +t_ab8500_codec_error u8500_acodec_open(int client_id, int stream_id) +{ + struct i2sdrv_data *p_i2sdrv_data = NULL; + struct i2s_device *i2s; + + p_i2sdrv_data = i2sdrv[client_id]; + + if (!p_i2sdrv_data) + return (-1); + + i2s = p_i2sdrv_data->i2s; + + if (stream_id == 0) //PLAYBACK + { + if (p_i2sdrv_data->tx_status) + return -1; + else { + p_i2sdrv_data->tx_status = 1; + } + } else if (stream_id == 1) //CAPTURE + { + if (p_i2sdrv_data->rx_status) + return -1; + else { + p_i2sdrv_data->rx_status = 1; + } + } + + p_i2sdrv_data->flag = 0; + + return 0; +} + +t_ab8500_codec_error u8500_acodec_send_data(int client_id, void *data, + size_t bytes, int dma_flag) +{ + struct i2sdrv_data *p_i2sdrv_data = NULL; + struct i2s_device *i2s_dev = NULL; + int bytes_transmit; + struct i2s_message message; + + p_i2sdrv_data = i2sdrv[client_id]; + + if (!p_i2sdrv_data) + return (-1); + + i2s_dev = p_i2sdrv_data->i2s; + + if (p_i2sdrv_data->flag) { + stm_dbg(DBG_ST.acodec, " I2S controller not available\n"); + return -1; + } + message.txbytes = bytes; + message.txdata = data; + message.rxbytes = 0; + message.rxdata = NULL; + message.dma_flag = dma_flag; + + bytes_transmit = i2s_transfer(i2s_dev->controller, &message); + + if (bytes_transmit < 0) { + printk("error in transfer\n"); + return -1; + } + return bytes_transmit; + +} + +t_ab8500_codec_error u8500_acodec_loopback_configure(int client_id, void *data, + size_t bytes, int dma_flag) +{ + struct i2sdrv_data *p_i2sdrv_data = NULL; + struct i2s_device *i2s_dev = NULL; + int bytes_receive; + struct i2s_message message; + + p_i2sdrv_data = i2sdrv[client_id]; + + if (!p_i2sdrv_data) + return (-1); + + i2s_dev = p_i2sdrv_data->i2s; + + if (p_i2sdrv_data->flag) { + stm_dbg(DBG_ST.acodec, " I2S controller not available\n"); + return -1; + } + + message.rxbytes = bytes; + message.rxdata = data; + message.txbytes = bytes; + message.txdata = data; + message.dma_flag = dma_flag; + message.inf_loopback_xfer = true; + + bytes_receive = i2s_transfer(i2s_dev->controller, &message); + + if (bytes_receive < 0) { + printk(" not get\n"); + return -1; + } + return bytes_receive; + +} + +t_ab8500_codec_error u8500_acodec_receive_data(int client_id, void *data, + size_t bytes, int dma_flag) +{ + struct i2sdrv_data *p_i2sdrv_data = NULL; + struct i2s_device *i2s_dev = NULL; + int bytes_receive; + struct i2s_message message; + + p_i2sdrv_data = i2sdrv[client_id]; + + if (!p_i2sdrv_data) + return (-1); + + i2s_dev = p_i2sdrv_data->i2s; + + if (p_i2sdrv_data->flag) { + stm_dbg(DBG_ST.acodec, " I2S controller not available\n"); + return -1; + } + + message.rxbytes = bytes; + message.rxdata = data; + message.txbytes = 0; + message.txdata = NULL; + message.dma_flag = dma_flag; + + bytes_receive = i2s_transfer(i2s_dev->controller, &message); + + if (bytes_receive < 0) { + printk(" not get\n"); + return -1; + } + return bytes_receive; + +} + +t_ab8500_codec_error u8500_acodec_close(int client_id, t_acodec_disable flag) +{ + struct i2sdrv_data *p_i2sdrv_data = NULL; + struct i2s_device *i2s_dev = NULL; + int status = 0; + + p_i2sdrv_data = i2sdrv[client_id]; + + if (!p_i2sdrv_data) + return (-1); + + i2s_dev = p_i2sdrv_data->i2s; + + if (p_i2sdrv_data->flag) { + stm_dbg(DBG_ST.acodec, " I2S controller not available\n"); + return -1; + } + + if (flag == DISABLE_ALL) { + p_i2sdrv_data->flag = -1; + p_i2sdrv_data->tx_status = 0; + p_i2sdrv_data->rx_status = 0; + } else if (flag == DISABLE_TRANSMIT) { + p_i2sdrv_data->tx_status = 0; + } else if (flag == DISABLE_RECEIVE) { + p_i2sdrv_data->rx_status = 0; + } + status = i2s_cleanup(i2s_dev->controller, flag); + if (status) { + return -1; + } + + return 0; +} + +/** +* u8500_acodec_enable_audio_mode +* +* @direction - direction of data flow (from/to) audiocode +* @mspClockSel - clock for MSP +* @mspInClockFreq - input clock for MSP +* @channels - number of channel, 1 for mono and 2 for stereo +* +* It configures the audiocodec in audio mode. In this case,the I2S +* protocol is used for data exchanges. +*/ + +t_ab8500_codec_error u8500_acodec_enable_audio_mode(struct acodec_configuration + * acodec_config) +{ + struct i2s_device *i2s_dev = NULL; + t_ab8500_codec_error error_status = AB8500_CODEC_OK; + struct msp_config msp_config; + t_ab8500_codec_error codec_error; + t_ab8500_codec_mode codec_in_mode = AB8500_CODEC_MODE_MANUAL_SETTING; + t_ab8500_codec_mode codec_out_mode = AB8500_CODEC_MODE_MANUAL_SETTING; + t_ab8500_codec_direction codec_direction; +/*#ifdef CONFIG_U8500_AB8500_CUT10*/ +#if 1 + t_ab8500_codec_tdm_config tdm_config; +#endif + + memset(&msp_config, 0, sizeof(msp_config)); + + FUNC_ENTER(); + stm_dbg(DBG_ST.acodec, + " Entering in u8500_acodec_enable_audio_mode()\n"); + + if (i2sdrv[I2S_CLIENT_MSP1]->flag) { + stm_dbg(DBG_ST.acodec, " I2S controller not available\n"); + return -1; + } + + i2s_dev = i2sdrv[I2S_CLIENT_MSP1]->i2s; + + if (g_codec_system_context.cur_user == NO_USER) { + stm_error("Audiocodec not yet configured by any user\n"); + return (AB8500_CODEC_ERROR); + } else if (g_codec_system_context.cur_user != acodec_config->user) { + stm_error + (" Trying to acces audiocodec already in use by user %d\n", + g_codec_system_context.cur_user); + return (AB8500_CODEC_ERROR); + } + + switch (acodec_config->direction) { + case AB8500_CODEC_DIRECTION_INOUT: + codec_direction = AB8500_CODEC_DIRECTION_INOUT; + codec_in_mode = AB8500_CODEC_MODE_VOICE; //HIFI + codec_out_mode = AB8500_CODEC_MODE_VOICE; //VOICE + break; + case AB8500_CODEC_DIRECTION_IN: + codec_direction = AB8500_CODEC_DIRECTION_IN; + codec_in_mode = AB8500_CODEC_MODE_VOICE; //HIFI + break; + case AB8500_CODEC_DIRECTION_OUT: + codec_direction = AB8500_CODEC_DIRECTION_OUT; + codec_out_mode = AB8500_CODEC_MODE_VOICE; //HIFI + break; + default: + stm_error("Invalid direction\n"); + return AB8500_CODEC_ERROR; + } + + /* MSP configuration */ + + msp_config.tx_clock_sel = 0; //TX_CLK_SEL_SRG; + msp_config.rx_clock_sel = 0; //RX_CLK_SEL_SRG; + + msp_config.tx_frame_sync_sel = 0; //0x00000400; Frame synchronization signal is provided by an external source. MSPTFS is an input pin + msp_config.rx_frame_sync_sel = 0; //0: Rx Frame synchronization signal is provided by an external source. MSPRFS is an input pin + + msp_config.input_clock_freq = MSP_INPUT_FREQ_48MHZ; + + msp_config.srg_clock_sel = 0; //0x000C0000 + + //msp_config.rx_endianess = MSP_BIG_ENDIAN; + //msp_config.tx_endianess = MSP_BIG_ENDIAN; + + msp_config.rx_frame_sync_pol = RX_FIFO_SYNC_HI; + msp_config.tx_frame_sync_pol = TX_FIFO_SYNC_HI; + + //msp_config.rx_unexpect_frame_sync = MSP_UNEXPECTED_FS_IGNORE; + //msp_config.tx_unexpect_frame_sync = MSP_UNEXPECTED_FS_IGNORE; + + msp_config.rx_fifo_config = RX_FIFO_ENABLE; + msp_config.tx_fifo_config = TX_FIFO_ENABLE; + + msp_config.spi_clk_mode = SPI_CLK_MODE_NORMAL; + msp_config.spi_burst_mode = 0; + + msp_config.handler = acodec_config->handler; + msp_config.tx_callback_data = acodec_config->tx_callback_data; + msp_config.tx_data_enable = 0; + msp_config.rx_callback_data = acodec_config->rx_callback_data; + + msp_config.loopback_enable = 0; + msp_config.multichannel_configured = 0; + + msp_config.def_elem_len = 0; + //msp_config.loopback_enable = g_codec_system_context.msp_loopback; + + stm_dbg(DBG_ST.acodec, " msp_config.loopback_enable = 0x%x \n", + msp_config.loopback_enable); + +#if 0 + msp_config.default_protocol_desc = 1; +#else + msp_config.default_protocol_desc = 0; + msp_config.protocol_desc.rx_phase_mode = MSP_SINGLE_PHASE; + msp_config.protocol_desc.tx_phase_mode = MSP_SINGLE_PHASE; + msp_config.protocol_desc.rx_phase2_start_mode = + MSP_PHASE2_START_MODE_IMEDIATE; + msp_config.protocol_desc.tx_phase2_start_mode = + MSP_PHASE2_START_MODE_IMEDIATE; + msp_config.protocol_desc.rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST; + msp_config.protocol_desc.tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST; + msp_config.protocol_desc.rx_frame_length_1 = MSP_FRAME_LENGTH_1; + msp_config.protocol_desc.rx_frame_length_2 = MSP_FRAME_LENGTH_1; + msp_config.protocol_desc.tx_frame_length_1 = MSP_FRAME_LENGTH_1; + msp_config.protocol_desc.tx_frame_length_2 = MSP_FRAME_LENGTH_1; + msp_config.protocol_desc.rx_element_length_1 = MSP_ELEM_LENGTH_32; + msp_config.protocol_desc.rx_element_length_2 = MSP_ELEM_LENGTH_32; + msp_config.protocol_desc.tx_element_length_1 = MSP_ELEM_LENGTH_32; + msp_config.protocol_desc.tx_element_length_2 = MSP_ELEM_LENGTH_32; + msp_config.protocol_desc.rx_data_delay = MSP_DELAY_0; + msp_config.protocol_desc.tx_data_delay = MSP_DELAY_0; + msp_config.protocol_desc.rx_clock_pol = MSP_RISING_EDGE; + msp_config.protocol_desc.tx_clock_pol = MSP_FALLING_EDGE; + msp_config.protocol_desc.rx_frame_sync_pol = + MSP_FRAME_SYNC_POL_ACTIVE_HIGH; + msp_config.protocol_desc.tx_frame_sync_pol = + MSP_FRAME_SYNC_POL_ACTIVE_HIGH; + msp_config.protocol_desc.rx_half_word_swap = MSP_HWS_NO_SWAP; + msp_config.protocol_desc.tx_half_word_swap = MSP_HWS_NO_SWAP; + msp_config.protocol_desc.compression_mode = MSP_COMPRESS_MODE_LINEAR; + msp_config.protocol_desc.expansion_mode = MSP_EXPAND_MODE_LINEAR; + msp_config.protocol_desc.spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI; + msp_config.protocol_desc.spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE; + msp_config.protocol_desc.frame_sync_ignore = MSP_FRAME_SYNC_IGNORE; + msp_config.protocol_desc.frame_period = 63; + msp_config.protocol_desc.frame_width = 31; + msp_config.protocol_desc.total_clocks_for_one_frame = 64; + +#endif + + msp_config.direction = MSP_BOTH_T_R_MODE; + msp_config.protocol = MSP_PCM_PROTOCOL; //MSP_I2S_PROTOCOL + msp_config.frame_size = ELEMENT_SIZE; + // msp_config.frame_freq = freq; $kardad$ + msp_config.frame_freq = CODEC_SAMPLING_FREQ_48KHZ; + + /* enable msp for both tr and rx mode with dma data transfer. THIS IS NOW DONE SEPARATELY from SAA. */ + + if (acodec_config->channels == 1) + msp_config.data_size = MSP_DATA_SIZE_16BIT; + else + msp_config.data_size = MSP_DATA_SIZE_32BIT; + +#ifdef CONFIG_U8500_ACODEC_DMA + msp_config.work_mode = MSP_DMA_MODE; +#elif defined(CONFIG_U8500_ACODEC_POLL) + msp_config.work_mode = MSP_POLLING_MODE; +#else + msp_config.work_mode = MSP_INTERRUPT_MODE; +#endif + + if (DISABLE == acodec_config->direct_rendering_mode) { + msp_config.multichannel_configured = 1; + msp_config.multichannel_config.tx_multichannel_enable = 1; + if (acodec_config->channels == 1) { + msp_config.multichannel_config.tx_channel_0_enable = + 0x0000001; + } else { + msp_config.multichannel_config.tx_channel_0_enable = + 0x0000003; + } + msp_config.multichannel_config.tx_channel_1_enable = 0x0000000; + msp_config.multichannel_config.tx_channel_2_enable = 0x0000000; + msp_config.multichannel_config.tx_channel_3_enable = 0x0000000; + + msp_config.multichannel_config.rx_multichannel_enable = 1; + + if (acodec_config->channels == 1) { + msp_config.multichannel_config.rx_channel_0_enable = + 0x0000001; + } else { + msp_config.multichannel_config.rx_channel_0_enable = + 0x0000003; + } + msp_config.multichannel_config.rx_channel_1_enable = 0x0000000; + msp_config.multichannel_config.rx_channel_2_enable = 0x0000000; + msp_config.multichannel_config.rx_channel_3_enable = 0x0000000; + + if (acodec_config->tdm8_ch_mode == ENABLE) { + msp_config.def_elem_len = 1; + + msp_config.protocol_desc.tx_element_length_1 = + MSP_ELEM_LENGTH_20; + msp_config.protocol_desc.tx_frame_length_1 = + MSP_FRAME_LENGTH_8; + msp_config.protocol_desc.tx_data_delay = MSP_DELAY_1; + + msp_config.protocol_desc.tx_element_length_2 = + MSP_ELEM_LENGTH_8; + msp_config.protocol_desc.tx_frame_length_2 = + MSP_FRAME_LENGTH_1; + + msp_config.protocol_desc.rx_element_length_1 = + MSP_ELEM_LENGTH_20; + msp_config.protocol_desc.rx_frame_length_1 = + MSP_FRAME_LENGTH_8; + msp_config.protocol_desc.rx_data_delay = MSP_DELAY_1; + + msp_config.protocol_desc.rx_element_length_2 = + MSP_ELEM_LENGTH_8; + msp_config.protocol_desc.rx_frame_length_2 = + MSP_FRAME_LENGTH_1; + + msp_config.protocol_desc.frame_sync_ignore = + MSP_FRAME_SYNC_UNIGNORE; + msp_config.protocol_desc.rx_clock_pol = MSP_RISING_EDGE; + + //if(acodec_config->digital_loopback == ENABLE) { + if (1) { + msp_config.multichannel_config. + tx_channel_0_enable = + (1 << NB_OF_CHANNEL_USED) - 1; + msp_config.multichannel_config. + rx_channel_0_enable = + (1 << NB_OF_CHANNEL_USED) - 1; + } else { + msp_config.multichannel_config. + tx_channel_0_enable = 0x3; + msp_config.multichannel_config. + rx_channel_0_enable = 0x3; + } + } + + if (acodec_config->tdm8_ch_mode == ENABLE) { + /* TFSDLY = 2 delay units */ + msp_config.iodelay = 0x20; + } + + error_status = i2s_setup(i2s_dev->controller, &msp_config); + if (error_status < 0) { + stm_error("error in msp enable, error_status is %d\n", + error_status); + return error_status; + } + } else if (ENABLE == acodec_config->direct_rendering_mode) { + writel(0x00, ((char *)(IO_ADDRESS(U8500_MSP1_BASE) + 0x04))); //MSP_GCR + } + + if (ACODEC_CONFIG_REQUIRED == acodec_config->acodec_config_need) { + AB8500_CODEC_SelectInterface(AB8500_CODEC_AUDIO_INTERFACE_0); + + codec_error = AB8500_CODEC_PowerUp(); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_PowerUp failed\n"); + return AB8500_CODEC_ERROR; + } + +/*#ifdef CONFIG_U8500_AB8500_CUT10*/ +#if 1 + tdm_config.cr27_if1_bitclk_osr = + AB8500_CODEC_CR27_IF1_BITCLK_OSR_32; + tdm_config.cr27_if0_bitclk_osr = + AB8500_CODEC_CR27_IF0_BITCLK_OSR_32; + tdm_config.cr28_if0wl = AB8500_CODEC_CR28_IF0WL_16BITS; + tdm_config.cr30_if1wl = AB8500_CODEC_CR30_IF1WL_16BITS; + + switch (acodec_config->direction) { + case AB8500_CODEC_DIRECTION_INOUT: + tdm_config.cr28_bitclk0p = + AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; + tdm_config.cr28_if0del = + AB8500_CODEC_CR28_IF0DEL_DELAYED; + break; + case AB8500_CODEC_DIRECTION_IN: + tdm_config.cr28_bitclk0p = + AB8500_CODEC_CR28_BITCLK0P_RISING_EDGE; + tdm_config.cr28_if0del = + AB8500_CODEC_CR28_IF0DEL_NOT_DELAYED; + break; + case AB8500_CODEC_DIRECTION_OUT: + tdm_config.cr28_bitclk0p = + AB8500_CODEC_CR28_BITCLK0P_FALLING_EDGE; + tdm_config.cr28_if0del = + AB8500_CODEC_CR28_IF0DEL_DELAYED; + break; + default: + stm_error("Invalid direction\n"); + return AB8500_CODEC_ERROR; + } + + if (acodec_config->tdm8_ch_mode == ENABLE) { + tdm_config.cr27_if0_bitclk_osr = + AB8500_CODEC_CR27_IF0_BITCLK_OSR_256; + tdm_config.cr28_if0wl = AB8500_CODEC_CR28_IF0WL_20BITS; + tdm_config.cr28_bitclk0p = + AB8500_CODEC_CR28_BITCLK0P_RISING_EDGE; + tdm_config.cr28_if0del = + AB8500_CODEC_CR28_IF0DEL_DELAYED; + codec_in_mode = AB8500_CODEC_MODE_VOICE; + codec_out_mode = AB8500_CODEC_MODE_VOICE; + acodec_config->direction = AB8500_CODEC_DIRECTION_INOUT; + } + + codec_error = + AB8500_CODEC_SetModeAndDirection(acodec_config->direction, + codec_in_mode, + codec_out_mode, + &tdm_config); +#else + codec_error = + AB8500_CODEC_SetModeAndDirection(acodec_config->direction, + codec_in_mode, + codec_out_mode); +#endif + if (AB8500_CODEC_OK != codec_error) { + stm_error("set mode and direction failed\n"); + return AB8500_CODEC_ERROR; + } + + codec_error = + AB8500_CODEC_SetMasterMode(AB8500_CODEC_MASTER_MODE_ENABLE); + + if (AB8500_CODEC_OK != codec_error) { + stm_error("set mode and direction failed\n"); + return AB8500_CODEC_ERROR; + } + + /*codec_error = trg_codec_set_sample_frequency(0); */ + + /*u8500_acodec_set_volume(g_codec_system_context.in_left_volume, + g_codec_system_context.in_right_volume, + g_codec_system_context.out_left_volume, + g_codec_system_context.out_right_volume, + user); */ +#if 0 + if (AB8500_CODEC_DIRECTION_IN == acodec_config->direction + || AB8500_CODEC_DIRECTION_INOUT == + acodec_config->direction) { + + u8500_acodec_allocate_ad_slot + (AB8500_CODEC_SRC_D_MICROPHONE_1, TDM_8_CH_MODE); + u8500_acodec_allocate_ad_slot + (AB8500_CODEC_SRC_D_MICROPHONE_2, TDM_8_CH_MODE); + + /*codec_error = AB8500_CODEC_ADSlotAllocation (AB8500_CODEC_SLOT0, + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT3); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + codec_error = AB8500_CODEC_ADSlotAllocation (AB8500_CODEC_SLOT1, + AB8500_CODEC_CR31_TO_CR46_SLOT_IS_TRISTATE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } */ + + } + + if (AB8500_CODEC_DIRECTION_OUT == acodec_config->direction + || AB8500_CODEC_DIRECTION_INOUT == + acodec_config->direction) { + u8500_acodec_allocate_da_slot(AB8500_CODEC_DEST_HEADSET, + TDM_8_CH_MODE); + /*codec_error = AB8500_CODEC_DASlotAllocation (AB8500_CODEC_DA_CHANNEL_NUMBER_1, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT08); + if (AB8500_CODEC_OK != codec_error) { + stm_error + ("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + codec_error = AB8500_CODEC_DASlotAllocation (AB8500_CODEC_DA_CHANNEL_NUMBER_2, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT09); + if (AB8500_CODEC_OK != codec_error) { + stm_error + ("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } */ + + } +#endif + + } //END of if acodec_config_need + +/*#if DRIVER_DEBUG > 0 + { + dump_msp_registers(); + dump_acodec_registers(); + } +#endif*/ + + stm_dbg(DBG_ST.acodec, + "leaving in u8500_acodec_enable_audio_mode() \n"); + + FUNC_EXIT(); + return AB8500_CODEC_OK; +} + +/** +* u8500_acodec_set_output_volume - configures the volume level for both speakers +* @in_left_volume - volume for left channel of mic +* @in_right_volume - volume for right channel of mic +* @out_left_volume - volume for left speaker +* @out_right_volume - volume for right speaker +*/ +t_ab8500_codec_error u8500_acodec_set_output_volume(t_ab8500_codec_dest + dest_device, + int left_volume, + int right_volume, + t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, + " Entering in u8500_acodec_set_output_volume()\n"); + + FUNC_ENTER(); + + user = user; //keep compiler happy + + g_codec_system_context.output_config[dest_device].left_volume = + left_volume; + g_codec_system_context.output_config[dest_device].right_volume = + right_volume; + + AB8500_CODEC_SetDestVolume(dest_device, left_volume, right_volume); + + FUNC_EXIT(); + return codec_error; +} + +/*u8500_acodec_get_output_volume*/ + +t_ab8500_codec_error u8500_acodec_get_output_volume(t_ab8500_codec_dest + dest_device, + int *p_left_volume, + int *p_right_volume, + t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, + " Entering in u8500_acodec_set_output_volume()\n"); + + user = user; //keep compiler happy + + *p_left_volume = + g_codec_system_context.output_config[dest_device].left_volume; + *p_right_volume = + g_codec_system_context.output_config[dest_device].right_volume; + + return codec_error; +} + +/** +* u8500_acodec_set_input_volume - configures the volume level for both speakers +* @in_left_volume - volume for left channel of mic +* @in_right_volume - volume for right channel of mic +* @out_left_volume - volume for left speaker +* @out_right_volume - volume for right speaker +*/ +t_ab8500_codec_error u8500_acodec_set_input_volume(t_ab8500_codec_src + src_device, int left_volume, + int right_volume, + t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, + " Entering in u8500_acodec_set_input_volume()\n"); + + user = user; //keep compiler happy + + g_codec_system_context.input_config[src_device].left_volume = + left_volume; + g_codec_system_context.input_config[src_device].right_volume = + right_volume; + + AB8500_CODEC_SetSrcVolume(src_device, left_volume, right_volume); + + return codec_error; +} + +/** +* u8500_acodec_get_input_volume - configures the volume level for both speakers +* @in_left_volume - volume for left channel of mic +* @in_right_volume - volume for right channel of mic +* @out_left_volume - volume for left speaker +* @out_right_volume - volume for right speaker +*/ +t_ab8500_codec_error u8500_acodec_get_input_volume(t_ab8500_codec_src + src_device, + int *p_left_volume, + int *p_right_volume, + t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, + " Entering in u8500_acodec_get_input_volume()\n"); + + user = user; //keep compiler happy + + *p_left_volume = + g_codec_system_context.input_config[src_device].left_volume; + *p_right_volume = + g_codec_system_context.input_config[src_device].right_volume; + + return codec_error; +} + +/** +* u8500_acodec_toggle_playback_mute_control - configures the mute for both speakers +* @in_left_volume - volume for left channel of mic +* @in_right_volume - volume for right channel of mic +* @out_left_volume - volume for left speaker +* @out_right_volume - volume for right speaker +*/ +t_ab8500_codec_error +u8500_acodec_toggle_playback_mute_control(t_ab8500_codec_dest dest_device, + t_u8500_bool_state mute_state, + t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, + " Entering in u8500_acodec_toggle_playback_mute_control \n"); + + user = user; //keep compiler happy + + g_codec_system_context.output_config[dest_device].mute_state = + mute_state; + + if (ENABLE == mute_state) { + AB8500_CODEC_DestPowerControl(dest_device, + AB8500_CODEC_SRC_STATE_ENABLE); + } else { + AB8500_CODEC_DestPowerControl(dest_device, + AB8500_CODEC_SRC_STATE_DISABLE); + } + + return codec_error; +} + +/** +* u8500_acodec_toggle_capture_mute_control - configures the mute for both speakers +* @in_left_volume - volume for left channel of mic +* @in_right_volume - volume for right channel of mic +* @out_left_volume - volume for left speaker +* @out_right_volume - volume for right speaker +*/ +t_ab8500_codec_error u8500_acodec_toggle_capture_mute_control(t_ab8500_codec_src + src_device, + t_u8500_bool_state + mute_state, + t_acodec_user + user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, + " Entering in u8500_acodec_toggle_capture_mute_control \n"); + + user = user; //keep compiler happy + + g_codec_system_context.input_config[src_device].mute_state = mute_state; + + if (ENABLE == mute_state) { + AB8500_CODEC_SrcPowerControl(src_device, + AB8500_CODEC_SRC_STATE_ENABLE); + } else { + AB8500_CODEC_SrcPowerControl(src_device, + AB8500_CODEC_SRC_STATE_DISABLE); + } + + return codec_error; +} + +/** +* u8500_acodec_select_input +* @input_device: MIC or linein. +* +* This routine selects the input device mic or linein. +*/ + +t_ab8500_codec_error u8500_acodec_select_input(t_ab8500_codec_src + input_device, + t_acodec_user user, + t_u8500_mode mode) +{ + + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, " Entering u8500_acodec_select_input\n"); + + if (TDM_8_CH_MODE == mode) + u8500_acodec_allocate_ad_slot(input_device, TDM_8_CH_MODE); + else + u8500_acodec_allocate_ad_slot(input_device, CLASSICAL_MODE); + + codec_error = AB8500_CODEC_SelectInput(input_device); + + stm_dbg(DBG_ST.acodec, " leaving u8500_acodec_select_input\n"); + return codec_error; +} + +/** +* u8500_acodec_select_output +* @output_device: output device HP/LSP +* +* This routine selects the output device Headphone or loud speaker +*/ + +t_ab8500_codec_error u8500_acodec_select_output(t_ab8500_codec_dest + output_device, + t_acodec_user user, + t_u8500_mode mode) +{ + + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + FUNC_ENTER(); + stm_dbg(DBG_ST.acodec, " Entering u8500_acodec_select_output()\n"); + + if (TDM_8_CH_MODE == mode) + u8500_acodec_allocate_da_slot(output_device, TDM_8_CH_MODE); + else + u8500_acodec_allocate_da_slot(output_device, CLASSICAL_MODE); + + codec_error = AB8500_CODEC_SelectOutput(output_device); + + stm_dbg(DBG_ST.acodec, " leaving u8500_acodec_select_output()\n"); + FUNC_EXIT(); + return codec_error; +} + +t_ab8500_codec_error u8500_acodec_allocate_ad_slot(t_ab8500_codec_src + input_device, + t_u8500_mode mode) +{ + t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line1, + ad_data_line2; + t_ab8500_codec_slot slot1, slot2; + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + slot1 = AB8500_CODEC_SLOT_UNDEFINED; + slot2 = AB8500_CODEC_SLOT_UNDEFINED; + + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_UNDEFINED; + ad_data_line2 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_UNDEFINED; + + switch (input_device) { + case AB8500_CODEC_SRC_D_MICROPHONE_1: + { + slot1 = AB8500_CODEC_SLOT0; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT1; + } + break; + case AB8500_CODEC_SRC_MICROPHONE_2: + case AB8500_CODEC_SRC_D_MICROPHONE_2: + { + slot1 = AB8500_CODEC_SLOT1; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT2; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_3: + case AB8500_CODEC_SRC_MICROPHONE_1A: + case AB8500_CODEC_SRC_MICROPHONE_1B: + { + slot1 = AB8500_CODEC_SLOT2; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT3; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_4: + { + slot1 = AB8500_CODEC_SLOT3; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT4; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_5: + { + slot1 = AB8500_CODEC_SLOT4; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT5; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_6: + { + slot1 = AB8500_CODEC_SLOT5; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT6; + } + break; + case AB8500_CODEC_SRC_LINEIN: + { + slot1 = AB8500_CODEC_SLOT0; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT1; + + slot2 = AB8500_CODEC_SLOT1; + ad_data_line2 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT2; + } + case AB8500_CODEC_SRC_FM_RX: + { + slot1 = AB8500_CODEC_SLOT6; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT7; + + slot2 = AB8500_CODEC_SLOT7; + ad_data_line2 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT8; + } + break; + case AB8500_CODEC_SRC_ALL: + break; + } + + if ((AB8500_CODEC_SLOT_UNDEFINED != slot1) + && (AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_UNDEFINED != + ad_data_line1)) { + if (CLASSICAL_MODE == mode) { + slot1 = AB8500_CODEC_SLOT0; + } + codec_error = + AB8500_CODEC_ADSlotAllocation(slot1, ad_data_line1); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + if ((AB8500_CODEC_SLOT_UNDEFINED != slot2) + && (AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_UNDEFINED != + ad_data_line2)) { + if (CLASSICAL_MODE == mode) { + slot2 = AB8500_CODEC_SLOT1; + } + codec_error = + AB8500_CODEC_ADSlotAllocation(slot2, ad_data_line2); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + return AB8500_CODEC_OK; +} + +t_ab8500_codec_error u8500_acodec_unallocate_ad_slot(t_ab8500_codec_src + input_device, + t_u8500_mode mode) +{ + t_ab8500_codec_slot slot1, slot2; + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + slot1 = AB8500_CODEC_SLOT_UNDEFINED; + slot2 = AB8500_CODEC_SLOT_UNDEFINED; + + switch (input_device) { + case AB8500_CODEC_SRC_D_MICROPHONE_1: + { + slot1 = AB8500_CODEC_SLOT0; + } + break; + case AB8500_CODEC_SRC_MICROPHONE_2: + case AB8500_CODEC_SRC_D_MICROPHONE_2: + { + slot1 = AB8500_CODEC_SLOT1; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_3: + case AB8500_CODEC_SRC_MICROPHONE_1A: + case AB8500_CODEC_SRC_MICROPHONE_1B: + { + slot1 = AB8500_CODEC_SLOT2; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_4: + { + slot1 = AB8500_CODEC_SLOT3; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_5: + { + slot1 = AB8500_CODEC_SLOT4; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_6: + { + slot1 = AB8500_CODEC_SLOT5; + } + break; + case AB8500_CODEC_SRC_LINEIN: + { + slot1 = AB8500_CODEC_SLOT0; + slot2 = AB8500_CODEC_SLOT1; + } + break; + case AB8500_CODEC_SRC_ALL: + break; + } + + if (AB8500_CODEC_SLOT_UNDEFINED != slot1) { + if (CLASSICAL_MODE == mode) { + slot1 = AB8500_CODEC_SLOT0; + } + codec_error = + AB8500_CODEC_ADSlotAllocation(slot1, + AB8500_CODEC_CR31_TO_CR46_SLOT_IS_TRISTATE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + if (AB8500_CODEC_SLOT_UNDEFINED != slot2) { + if (CLASSICAL_MODE == mode) { + slot2 = AB8500_CODEC_SLOT1; + } + codec_error = + AB8500_CODEC_ADSlotAllocation(slot2, + AB8500_CODEC_CR31_TO_CR46_SLOT_IS_TRISTATE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + return AB8500_CODEC_OK; +} + +#ifdef CONFIG_U8500_AB8500_CUT10 +t_ab8500_codec_error u8500_acodec_allocate_da_slot(t_ab8500_codec_dest + output_device, + t_u8500_mode mode) +{ + t_ab8500_codec_da_channel_number da_ch_no1, da_ch_no2; + t_ab8500_codec_cr51_to_cr58_sltoda da_slot1, da_slot2; + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED; + da_ch_no2 = AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED; + + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT_UNDEFINED; + da_slot2 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT_UNDEFINED; + + switch (output_device) { + case AB8500_CODEC_DEST_HEADSET: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_1; + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT08; + + da_ch_no2 = AB8500_CODEC_DA_CHANNEL_NUMBER_2; + da_slot2 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT09; + } + break; + case AB8500_CODEC_DEST_EARPIECE: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_1; + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT08; + } + break; + case AB8500_CODEC_DEST_HANDSFREE: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_3; + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT10; + + da_ch_no2 = AB8500_CODEC_DA_CHANNEL_NUMBER_4; + da_slot2 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT11; + } + break; + case AB8500_CODEC_DEST_VIBRATOR_L: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_5; + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT12; + } + break; + case AB8500_CODEC_DEST_VIBRATOR_R: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_6; + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT13; + } + break; + + case AB8500_CODEC_DEST_FM_TX: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_7; + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT14; + + da_ch_no2 = AB8500_CODEC_DA_CHANNEL_NUMBER_8; + da_slot2 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT15; + } + + case AB8500_CODEC_DEST_ALL: + break; + } + + if ((AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED != da_ch_no1) + && (AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT_UNDEFINED != da_slot1)) { + if (CLASSICAL_MODE == mode) { + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT08; + } + codec_error = + AB8500_CODEC_DASlotAllocation(da_ch_no1, da_slot1); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + if ((AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED != da_ch_no2) + && (AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT_UNDEFINED != da_slot2)) { + if (CLASSICAL_MODE == mode) { + da_slot1 = AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT09; + } + codec_error = + AB8500_CODEC_DASlotAllocation(da_ch_no2, da_slot2); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + return AB8500_CODEC_OK; +} + +#else +t_ab8500_codec_error u8500_acodec_allocate_da_slot(t_ab8500_codec_dest + output_device, + t_u8500_mode mode) +{ + t_ab8500_codec_da_channel_number da_ch_no1, da_ch_no2; + t_ab8500_codec_cr51_to_cr56_sltoda da_slot1, da_slot2; + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED; + da_ch_no2 = AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED; + + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT_UNDEFINED; + da_slot2 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT_UNDEFINED; + + switch (output_device) { + case AB8500_CODEC_DEST_HEADSET: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_1; + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT08; + + da_ch_no2 = AB8500_CODEC_DA_CHANNEL_NUMBER_2; + da_slot2 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT09; + } + break; + case AB8500_CODEC_DEST_EARPIECE: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_1; + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT08; + } + break; + case AB8500_CODEC_DEST_HANDSFREE: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_3; + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT10; + + da_ch_no2 = AB8500_CODEC_DA_CHANNEL_NUMBER_4; + da_slot2 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT11; + } + break; + case AB8500_CODEC_DEST_VIBRATOR_L: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_5; + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT12; + } + break; + case AB8500_CODEC_DEST_VIBRATOR_R: + { + da_ch_no1 = AB8500_CODEC_DA_CHANNEL_NUMBER_6; + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT13; + } + break; + + case AB8500_CODEC_DEST_ALL: + break; + } + + if ((AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED != da_ch_no1) + && (AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT_UNDEFINED != da_slot1)) { + if (CLASSICAL_MODE == mode) { + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT08; + } + codec_error = + AB8500_CODEC_DASlotAllocation(da_ch_no1, da_slot1); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + if ((AB8500_CODEC_DA_CHANNEL_NUMBER_UNDEFINED != da_ch_no2) + && (AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT_UNDEFINED != da_slot2)) { + if (CLASSICAL_MODE == mode) { + da_slot1 = AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT09; + } + codec_error = + AB8500_CODEC_DASlotAllocation(da_ch_no2, da_slot2); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + + return AB8500_CODEC_OK; +} +#endif + +t_ab8500_codec_error u8500_acodec_unallocate_da_slot(t_ab8500_codec_dest + output_device, + t_u8500_mode mode) +{ + return AB8500_CODEC_OK; +} + +t_ab8500_codec_error u8500_acodec_set_src_power_cntrl(t_ab8500_codec_src + input_device, + t_u8500_bool_state + pwr_state) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + if (ENABLE == pwr_state) { + u8500_acodec_allocate_ad_slot(input_device, TDM_8_CH_MODE); + codec_error = + AB8500_CODEC_SrcPowerControl(input_device, + AB8500_CODEC_SRC_STATE_ENABLE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_SrcPowerControl failed\n"); + return AB8500_CODEC_ERROR; + } + g_codec_system_context.input_config[input_device].power_state = + ENABLE; + } else { + u8500_acodec_unallocate_ad_slot(input_device, TDM_8_CH_MODE); + codec_error = + AB8500_CODEC_SrcPowerControl(input_device, + AB8500_CODEC_SRC_STATE_DISABLE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_SrcPowerControl failed\n"); + return AB8500_CODEC_ERROR; + } + g_codec_system_context.input_config[input_device].power_state = + DISABLE; + } + + return AB8500_CODEC_OK; +} + +t_u8500_bool_state u8500_acodec_get_src_power_state(t_ab8500_codec_src + input_device) +{ + return (g_codec_system_context.input_config[input_device].power_state); +} + +t_ab8500_codec_error u8500_acodec_set_dest_power_cntrl(t_ab8500_codec_dest + output_device, + t_u8500_bool_state + pwr_state) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + if (ENABLE == pwr_state) { + AB8500_CODEC_SelectInterface(AB8500_CODEC_AUDIO_INTERFACE_0); + + codec_error = AB8500_CODEC_PowerUp(); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_PowerUp failed\n"); + return AB8500_CODEC_ERROR; + } + + u8500_acodec_allocate_da_slot(output_device, TDM_8_CH_MODE); + + codec_error = + AB8500_CODEC_DestPowerControl(output_device, + AB8500_CODEC_DEST_STATE_ENABLE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_DestPowerControl failed\n"); + return AB8500_CODEC_ERROR; + } + g_codec_system_context.output_config[output_device]. + power_state = ENABLE; + } else { + u8500_acodec_unallocate_da_slot(output_device, TDM_8_CH_MODE); + codec_error = + AB8500_CODEC_DestPowerControl(output_device, + AB8500_CODEC_DEST_STATE_DISABLE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_DestPowerControl failed\n"); + return AB8500_CODEC_ERROR; + } + g_codec_system_context.output_config[output_device]. + power_state = DISABLE; + } + + return AB8500_CODEC_OK; +} + +t_u8500_bool_state u8500_acodec_get_dest_power_state(t_ab8500_codec_dest + output_device) +{ + return (g_codec_system_context.output_config[output_device]. + power_state); +} + +/** +* u8500_acodec_toggle_analog_lpbk +* @output_device: output device HP/LSP +* +* This routine selects the output device Headphone or loud speaker +*/ +t_ab8500_codec_error u8500_acodec_toggle_analog_lpbk(t_u8500_bool_state + lpbk_state, + t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + stm_dbg(DBG_ST.acodec, + " Entering inu8500_acodec_toggle_analog_lpbk() \n"); + + user = user; //keep compiler happy + + if (ENABLE == lpbk_state) { + /* Reset CODEC */ + codec_error = AB8500_CODEC_Reset(); + + AB8500_CODEC_SelectInterface(AB8500_CODEC_AUDIO_INTERFACE_0); + + codec_error = AB8500_CODEC_PowerUp(); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_PowerUp failed\n"); + return AB8500_CODEC_ERROR; + } + + codec_error = AB8500_CODEC_SelectInput(AB8500_CODEC_SRC_LINEIN); + + codec_error = + AB8500_CODEC_SrcPowerControl(AB8500_CODEC_SRC_LINEIN, + AB8500_CODEC_SRC_STATE_ENABLE); + + //codec_error = AB8500_CODEC_SetSrcVolume(AB8500_CODEC_SRC_LINEIN,VOL_MAX,VOL_MAX); + + codec_error = + AB8500_CODEC_SelectOutput(AB8500_CODEC_DEST_HEADSET); + + codec_error = + AB8500_CODEC_DestPowerControl(AB8500_CODEC_DEST_HEADSET, + AB8500_CODEC_DEST_STATE_ENABLE); + + //codec_error = AB8500_CODEC_SetDestVolume(AB8500_CODEC_DEST_HEADSET,0,0); + + codec_error = AB8500_CODEC_SetAnalogLoopback(VOL_MAX, VOL_MAX); + + ab8500_write(AB8500_AUDIO, 0xd05, 0x30); + ab8500_write(AB8500_AUDIO, 0xd07, 0xf3); + ab8500_write(AB8500_AUDIO, 0xd16, 0xdd); + ab8500_write(AB8500_AUDIO, 0xd17, 0x55); + ab8500_write(AB8500_AUDIO, 0xd3f, 0xc0); + + } else { + codec_error = AB8500_CODEC_RemoveAnalogLoopback(); + } + +#if DRIVER_DEBUG > 0 + { + dump_acodec_registers(); + } +#endif + + return codec_error; +} + +#ifdef CONFIG_U8500_ACODEC_POLL + +static int digital_lpbk_msp_rx_tx_thread(void *data) +{ + t_digital_lpbk_cnxt *p_cnxt = (t_digital_lpbk_cnxt *) data; + unsigned int sample[8], count = 32; + + daemonize("digital_lpbk_msp_rx_tx_thread"); + allow_signal(SIGKILL); + + printk("\n Rx-Tx : digital_lpbk_msp_rx_tx_thread started \n"); + + while ((!signal_pending(current)) && (p_cnxt->rx_active)) { + +// ret_val = u8500_msp_receive_data(alsa_msp_adev,p_cnxt->buffer[p_cnxt->rx_index],p_cnxt->data_size); + + //u8500_msp_transceive_data(alsa_msp_adev,p_cnxt->buffer[0], p_cnxt->data_size,p_cnxt->buffer[1], p_cnxt->data_size); + + //u8500_msp_transceive_data(alsa_msp_adev,p_cnxt->buffer[1], p_cnxt->data_size,p_cnxt->buffer[0], p_cnxt->data_size); + +#if DRIVER_DEBUG > 1 + stm_dbg(DBG_ST.alsa, " Receiving \n"); +#endif + u8500_acodec_receive_data(I2S_CLIENT_MSP1, (void *)sample, + count, 0); + +#if DRIVER_DEBUG > 1 + stm_dbg(DBG_ST.alsa, " Transmitting \n"); +#endif + u8500_acodec_send_data(I2S_CLIENT_MSP1, (void *)sample, count, + 0); + + } + printk("\n Rx-Tx : digital_lpbk_msp_rx_tx_thread ended \n"); + return 0; +} + +#endif + +#ifdef CONFIG_U8500_ACODEC_DMA + +static void u8500_digital_lpbk_dma_start() +{ + u8500_acodec_loopback_configure(I2S_CLIENT_MSP1, + (void *)digital_lpbk_cnxt.buffer.addr, + digital_lpbk_cnxt.data_size, 1); + + stm_dbg(DBG_ST.alsa, " Rx DMA Transfer started\n"); + stm_dbg(DBG_ST.alsa, " Rx : add = %x size=%d\n", + (int)(digital_lpbk_cnxt.buffer.addr), + digital_lpbk_cnxt.data_size); + +} +#endif + +/** +* u8500_acodec_toggle_digital_lpbk +* @output_device: output device HP/LSP +* +* This routine selects the output device Headphone or loud speaker +*/ + +t_ab8500_codec_error u8500_acodec_toggle_digital_lpbk(t_u8500_bool_state + lpbk_state, + t_ab8500_codec_dest + dest_device, + t_ab8500_codec_src + src_device, + t_acodec_user user, + t_u8500_bool_state + tdm8_ch_mode) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + struct acodec_configuration acodec_config; + int status = 0; + + stm_dbg(DBG_ST.acodec, + " Entering u8500_acodec_toggle_digital_lpbk() \n"); + + user = user; //keep compiler happy + + if (ENABLE == lpbk_state) { + //data_size = 1024*100; + + //data[0] = (unsigned char *)kmalloc(data_size, GFP_KERNEL); + + codec_error = AB8500_CODEC_Reset(); + + //AB8500_CODEC_SelectInterface(AB8500_CODEC_AUDIO_INTERFACE_0); + + //codec_error = AB8500_CODEC_PowerUp(); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_PowerUp failed\n"); + return AB8500_CODEC_ERROR; + } + + status = u8500_acodec_open(I2S_CLIENT_MSP1, 0); + if (status) { + printk("failed in getting acodec playback open\n"); + return -1; + } + status = u8500_acodec_open(I2S_CLIENT_MSP1, 1); + if (status) { + printk("failed in getting acdoec capture open\n"); + return -1; + } + + u8500_acodec_setuser(USER_ALSA); + + if (ENABLE == tdm8_ch_mode) { + printk("\n 20 bit 8 ch Digital Loopback"); + printk("\n DMIC1 -> HS-L"); + printk("\n DMIC2 -> HS-R"); + printk("\n DMIC3 -> IHF-L"); + printk("\n DMIC5 -> Vibra-L\n"); + printk("\n DMIC6 -> Vibra-R\n"); + printk("\n FM -> FM Tx\n"); + } else { + printk("\n 16 bit 2 ch Digital Loopback"); + printk("\n DMIC1 -> HS-L"); + printk("\n DMIC2 -> HS-R"); + } + + stm_dbg(DBG_ST.alsa, "enabling audiocodec audio mode\n"); + acodec_config.direction = AB8500_CODEC_DIRECTION_INOUT; + acodec_config.input_frequency = T_CODEC_SAMPLING_FREQ_48KHZ; + acodec_config.output_frequency = T_CODEC_SAMPLING_FREQ_48KHZ; + acodec_config.mspClockSel = CODEC_MSP_APB_CLOCK; + acodec_config.mspInClockFreq = CODEC_MSP_INPUT_FREQ_48MHZ; + acodec_config.channels = 2; + acodec_config.user = 2; + acodec_config.acodec_config_need = ACODEC_CONFIG_REQUIRED; + acodec_config.direct_rendering_mode = DISABLE; + acodec_config.tdm8_ch_mode = tdm8_ch_mode; + acodec_config.digital_loopback = ENABLE; +#ifdef CONFIG_U8500_ACODEC_POLL + acodec_config.handler = NULL; + acodec_config.tx_callback_data = NULL; + acodec_config.rx_callback_data = NULL; +#endif + u8500_acodec_enable_audio_mode(&acodec_config); + + /*turn on src devices */ + + perform_src_routing(src_device); + +/* u8500_acodec_set_src_power_cntrl(src_device,ENABLE); + u8500_acodec_set_input_volume(src_device,50,50,USER_ALSA); + + u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_2,ENABLE); + u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_2,50,50,USER_ALSA); + + u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_3,ENABLE); + u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_3,50,50,USER_ALSA); + + u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_4,ENABLE); + u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_4,50,50,USER_ALSA); + + u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_5,ENABLE); + u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_5,50,50,USER_ALSA); + + u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_6,ENABLE); + u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_6,50,50,USER_ALSA); + + u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_D_MICROPHONE_6,ENABLE); + u8500_acodec_set_input_volume(AB8500_CODEC_SRC_D_MICROPHONE_6,50,50,USER_ALSA); + + u8500_acodec_set_src_power_cntrl(AB8500_CODEC_SRC_FM_RX,ENABLE); */ + + /*turn on dest devices */ + + //u8500_acodec_set_dest_power_cntrl(dest_device,ENABLE); + u8500_acodec_allocate_da_slot(dest_device, TDM_8_CH_MODE); + codec_error = AB8500_CODEC_SelectOutput(dest_device); + u8500_acodec_set_output_volume(dest_device, 100, 100, + USER_ALSA); + + /*u8500_acodec_set_dest_power_cntrl(AB8500_CODEC_DEST_HEADSET,ENABLE); + u8500_acodec_set_output_volume(AB8500_CODEC_DEST_HEADSET,100,100,USER_ALSA); */ + + /*u8500_acodec_set_dest_power_cntrl(AB8500_CODEC_DEST_HANDSFREE,ENABLE); + u8500_acodec_set_output_volume(AB8500_CODEC_DEST_HANDSFREE,100,100,USER_ALSA); */ + +#ifdef CONFIG_U8500_AB8500_CUT10 + codec_error = + AB8500_CODEC_DASlotAllocation + (AB8500_CODEC_DA_CHANNEL_NUMBER_5, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT12); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + + codec_error = + AB8500_CODEC_DASlotAllocation + (AB8500_CODEC_DA_CHANNEL_NUMBER_6, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT13); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + + codec_error = + AB8500_CODEC_DASlotAllocation + (AB8500_CODEC_DA_CHANNEL_NUMBER_7, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT14); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + + codec_error = + AB8500_CODEC_DASlotAllocation + (AB8500_CODEC_DA_CHANNEL_NUMBER_8, + AB8500_CODEC_CR51_TO_CR58_SLTODA_SLOT15); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } +#else + codec_error = + AB8500_CODEC_DASlotAllocation + (AB8500_CODEC_DA_CHANNEL_NUMBER_5, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT12); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + + codec_error = + AB8500_CODEC_DASlotAllocation + (AB8500_CODEC_DA_CHANNEL_NUMBER_6, + AB8500_CODEC_CR51_TO_CR56_SLTODA_SLOT13); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_daslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } +#endif + + digital_lpbk_cnxt.data_size = 2048; + + digital_lpbk_cnxt.rx_active = 1; + digital_lpbk_cnxt.tx_active = 1; + + digital_lpbk_cnxt.rx_index = 0; + digital_lpbk_cnxt.tx_index = 2; + + digital_lpbk_cnxt.buffer.area = + dma_alloc_coherent(NULL, digital_lpbk_cnxt.data_size, + &digital_lpbk_cnxt.buffer.addr, + GFP_KERNEL); + if (NULL == digital_lpbk_cnxt.buffer.area) { + printk("\n dma_alloc_coherent failed \n"); + } +#if DRIVER_DEBUG > 0 + { + dump_msp_registers(); + dump_acodec_registers(); + } +#endif + +#ifdef CONFIG_U8500_ACODEC_POLL + { + pid_t pid_rx_tx; + pid_rx_tx = + kernel_thread(digital_lpbk_msp_rx_tx_thread, + &digital_lpbk_cnxt, + CLONE_FS | CLONE_SIGHAND); + } +#elif defined(CONFIG_U8500_ACODEC_DMA) + { + u8500_digital_lpbk_dma_start(); + } +#endif + } else //lpbk is disable + { + + digital_lpbk_cnxt.rx_active = 0; + digital_lpbk_cnxt.tx_active = 0; + + dma_free_coherent(NULL, digital_lpbk_cnxt.data_size, + digital_lpbk_cnxt.buffer.area, + digital_lpbk_cnxt.buffer.addr); + + u8500_acodec_set_src_power_cntrl + (AB8500_CODEC_SRC_D_MICROPHONE_1, DISABLE); + u8500_acodec_set_src_power_cntrl + (AB8500_CODEC_SRC_D_MICROPHONE_2, DISABLE); + u8500_acodec_set_src_power_cntrl + (AB8500_CODEC_SRC_D_MICROPHONE_3, DISABLE); + u8500_acodec_set_src_power_cntrl + (AB8500_CODEC_SRC_D_MICROPHONE_4, DISABLE); + + u8500_acodec_set_dest_power_cntrl(AB8500_CODEC_DEST_HEADSET, + DISABLE); + u8500_acodec_set_dest_power_cntrl(AB8500_CODEC_DEST_HANDSFREE, + DISABLE); + + u8500_acodec_unsetuser(USER_ALSA); + u8500_acodec_close(I2S_CLIENT_MSP1, ACODEC_DISABLE_ALL); + } + return codec_error; +} + +t_ab8500_codec_error perform_src_routing(t_ab8500_codec_src input_device) +{ + int src_type = 0; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line1; + t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line2; + t_ab8500_codec_src input_device1; + t_ab8500_codec_src input_device2; + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + switch (input_device) { + case AB8500_CODEC_SRC_D_MICROPHONE_1: + { + src_type = MONO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT1; + } + break; + case AB8500_CODEC_SRC_MICROPHONE_2: + case AB8500_CODEC_SRC_D_MICROPHONE_2: + { + src_type = MONO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT2; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_3: + case AB8500_CODEC_SRC_MICROPHONE_1A: + case AB8500_CODEC_SRC_MICROPHONE_1B: + { + src_type = MONO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT3; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_4: + { + src_type = MONO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT4; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_5: + { + src_type = MONO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT5; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_6: + { + src_type = MONO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT6; + } + break; + case AB8500_CODEC_SRC_LINEIN: + { + src_type = STEREO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT1; + ad_data_line2 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT2; + input_device1 = AB8500_CODEC_SRC_LINEIN; + input_device2 = AB8500_CODEC_SRC_LINEIN; + } + break; +#ifdef CONFIG_U8500_AB8500_CUT10 + case AB8500_CODEC_SRC_D_MICROPHONE_12: + { + src_type = STEREO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT1; + ad_data_line2 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT2; + input_device1 = AB8500_CODEC_SRC_D_MICROPHONE_1; + input_device2 = AB8500_CODEC_SRC_D_MICROPHONE_2; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_34: + { + src_type = STEREO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT3; + ad_data_line2 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT4; + input_device1 = AB8500_CODEC_SRC_D_MICROPHONE_3; + input_device2 = AB8500_CODEC_SRC_D_MICROPHONE_4; + } + break; + case AB8500_CODEC_SRC_D_MICROPHONE_56: + { + src_type = STEREO_SRC; + ad_data_line1 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT5; + ad_data_line2 = + AB8500_CODEC_CR31_TO_CR46_SLOT_OUTPUTS_DATA_FROM_AD_OUT6; + input_device1 = AB8500_CODEC_SRC_D_MICROPHONE_5; + input_device2 = AB8500_CODEC_SRC_D_MICROPHONE_6; + } + break; +#endif /* #ifdef CONFIG_U8500_AB8500_CUT10 */ + } + if (STEREO_SRC == src_type) { + u8500_acodec_allocate_all_stereo_slots(ad_data_line1, + ad_data_line2); + codec_error = + AB8500_CODEC_SrcPowerControl(input_device1, + AB8500_CODEC_SRC_STATE_ENABLE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_SrcPowerControl failed\n"); + return AB8500_CODEC_ERROR; + } + g_codec_system_context.input_config[input_device1].power_state = + ENABLE; + + u8500_acodec_set_input_volume(input_device1, 50, 50, USER_ALSA); + + codec_error = + AB8500_CODEC_SrcPowerControl(input_device2, + AB8500_CODEC_SRC_STATE_ENABLE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_SrcPowerControl failed\n"); + return AB8500_CODEC_ERROR; + } + g_codec_system_context.input_config[input_device2].power_state = + ENABLE; + + u8500_acodec_set_input_volume(input_device2, 50, 50, USER_ALSA); + } else { + u8500_acodec_allocate_all_mono_slots(ad_data_line1); + codec_error = + AB8500_CODEC_SrcPowerControl(input_device, + AB8500_CODEC_SRC_STATE_ENABLE); + if (AB8500_CODEC_OK != codec_error) { + stm_error("AB8500_CODEC_SrcPowerControl failed\n"); + return AB8500_CODEC_ERROR; + } + g_codec_system_context.input_config[input_device].power_state = + ENABLE; + + u8500_acodec_set_input_volume(input_device, 50, 50, USER_ALSA); + } + return AB8500_CODEC_OK; +} + +t_ab8500_codec_error + u8500_acodec_allocate_all_mono_slots + (t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line1) { + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + int i; + + for (i = AB8500_CODEC_SLOT0; i <= AB8500_CODEC_SLOT7; i++) { + codec_error = AB8500_CODEC_ADSlotAllocation(i, ad_data_line1); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + return AB8500_CODEC_OK; +} + +t_ab8500_codec_error + u8500_acodec_allocate_all_stereo_slots + (t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line1, + t_ab8500_codec_cr31_to_cr46_ad_data_allocation ad_data_line2) { + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + int i; + + for (i = AB8500_CODEC_SLOT0; i <= AB8500_CODEC_SLOT7; i += 2) { + codec_error = AB8500_CODEC_ADSlotAllocation(i, ad_data_line1); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + codec_error = + AB8500_CODEC_ADSlotAllocation(i + 1, ad_data_line2); + if (AB8500_CODEC_OK != codec_error) { + stm_error("ab8500_codec_adslot_allocation failed\n"); + return AB8500_CODEC_ERROR; + } + } + return AB8500_CODEC_OK; +} + +#ifdef CONFIG_U8500_AB8500_CUT10 +t_ab8500_codec_error +u8500_acodec_set_burst_mode_fifo(t_u8500_pmc_rendering_state fifo_state) +{ + +} +#else +t_ab8500_codec_error +u8500_acodec_set_burst_mode_fifo(t_u8500_pmc_rendering_state fifo_state) +{ + t_ab8500_codec_error ab8500_codec_error; + t_ab8500_codec_burst_fifo_config burst_fifo_config; + + if (RENDERING_ENABLE == fifo_state) { + burst_fifo_config.cr104_bfifoint = 0x1; + burst_fifo_config.cr105_bfifotx = 0xC0; + burst_fifo_config.cr106_bfifofsext = + AB8500_CODEC_CR106_BFIFOFSEXT_6SLOT_EXTRA_CLK; + burst_fifo_config.cr106_bfifomsk = + AB8500_CODEC_CR106_BFIFOMSK_AD_DATA0_UNMASKED; + burst_fifo_config.cr106_bfifomstr = + AB8500_CODEC_CR106_BFIFOMSTR_MASTER_MODE; + burst_fifo_config.cr106_bfifostrt = + AB8500_CODEC_CR106_BFIFOSTRT_RUNNING; + burst_fifo_config.cr107_bfifosampnr = 0x100; + burst_fifo_config.cr108_bfifowakeup = 0x1; + + ab8500_codec_error = + AB8500_CODEC_ConfigureBurstFifo(&burst_fifo_config); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return ab8500_codec_error; + } + + ab8500_codec_error = AB8500_CODEC_EnableBurstFifo(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return ab8500_codec_error; + } + + printk("\n Burst mode activated\n"); + } else if (RENDERING_DISABLE == fifo_state) { + ab8500_codec_error = AB8500_CODEC_DisableBurstFifo(); + if (AB8500_CODEC_OK != ab8500_codec_error) { + return ab8500_codec_error; + } + printk("\n Burst mode deactivated\n"); + } + return AB8500_CODEC_OK; +} +#endif +/** +* u8500_acodec_set_user +* +* Set the current user for acodec. +*/ + +t_ab8500_codec_error u8500_acodec_setuser(t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + FUNC_ENTER(); + + if ((g_codec_system_context.cur_user == NO_USER) + || (g_codec_system_context.cur_user == user)) + g_codec_system_context.cur_user = user; + else { + stm_error + (" Trying to acces audiocodec already in use by user %d\n", + g_codec_system_context.cur_user); + return AB8500_CODEC_ERROR; + } + FUNC_EXIT(); + return (codec_error); +} + +/** +* u8500_acodec_unset_user +* +* Unset the current user for acodec. +*/ + +t_ab8500_codec_error u8500_acodec_unsetuser(t_acodec_user user) +{ + t_ab8500_codec_error codec_error = AB8500_CODEC_OK; + + if (g_codec_system_context.cur_user != user) { + stm_error + (" Trying to free audiocodec already in use by other user %d\n", + g_codec_system_context.cur_user); + return AB8500_CODEC_ERROR; + } else + g_codec_system_context.cur_user = NO_USER; + + return (codec_error); +} + +#if DRIVER_DEBUG > 0 +t_ab8500_codec_error dump_acodec_registers() +{ + u8 i; + + for (i = 0; i <= 0x6D; i++) + stm_dbg(DBG_ST.acodec, "block=0x0D, adr=%x = %x\n", i, + ab8500_read(AB8500_AUDIO, i)); + + /*for (i = 0; i < 0x5e; i++) + stm_dbg(DBG_ST.acodec,"\n block 1,reg =%d val %x", i, ab8500_read(AB8500_AUDIO, i)); + */ + return 0; +} + +t_ab8500_codec_error dump_msp_registers() +{ + int i; + + stm_dbg(DBG_ST.acodec, "\nMSP_1 base add = 0x%x\n", + (unsigned int)U8500_MSP1_BASE); + + for (i = 0; i < 0x40; i += 4) + stm_dbg(DBG_ST.acodec, "msp[0x%x]=0x%x\n", i, + readl((char *)(IO_ADDRESS(U8500_MSP1_BASE) + i))); + + return 0; +} + +EXPORT_SYMBOL(dump_msp_registers); +EXPORT_SYMBOL(dump_acodec_registers); +#endif + +/** +* u8500_acodec_powerdown +* +* This function power off the audio codec. +*/ +void u8500_acodec_powerdown() +{ + AB8500_CODEC_PowerDown(); +} + +/** +* u8500_acodec_init +* +* This is the init function for STW5098 audiocodec driver. +*/ + +static int i2sdrv_probe(struct i2s_device *i2s) +{ + + /* Allocate driver data */ + try_module_get(i2s->controller->dev.parent->driver->owner); + + /* Allocate memory to i2sdrv structure */ + i2sdrv[i2s->chip_select] = + kzalloc(sizeof(*i2sdrv[i2s->chip_select]), GFP_KERNEL); + if (!i2sdrv[i2s->chip_select]) + return -ENOMEM; + + /* Initialize the driver data */ + i2sdrv[i2s->chip_select]->i2s = i2s; + i2sdrv[i2s->chip_select]->flag = -1; + i2sdrv[i2s->chip_select]->tx_status = 0; + i2sdrv[i2s->chip_select]->rx_status = 0; + spin_lock_init(&i2sdrv[i2s->chip_select]->i2s_lock); + + i2s_set_drvdata(i2s, (void *)i2sdrv[i2s->chip_select]); + return 0; +} + +static int i2sdrv_remove(struct i2s_device *i2s) +{ + struct i2sdrv_data *i2sdrv = i2s_get_drvdata(i2s); + + spin_lock_irq(&i2sdrv->i2s_lock); + i2sdrv->i2s = NULL; + i2s_set_drvdata(i2s, NULL); + spin_unlock_irq(&i2sdrv->i2s_lock); + + stm_dbg(DBG_ST.acodec, "Entering AUDIOTRG_CODEC_DeIni\n"); + stm_dbg(DBG_ST.acodec, "leaving AUDIOTRG_CODEC_DeIni\n"); + module_put(i2s->controller->dev.parent->driver->owner); + printk("Remove of I2S gets called\n"); + return 0; +} +static const struct i2s_device_id acodec_id_table[] = { + {"i2s_device.2", 0, 0}, + {"i2s_device.1", 0, 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2s, acodec_id_table); + +static struct i2s_driver i2sdrv_i2s = { + .driver = { + .name = "u8500_acodec", + .owner = THIS_MODULE, + }, + .probe = i2sdrv_probe, + .remove = __devexit_p(i2sdrv_remove), + .id_table = acodec_id_table, + +}; + +static void ab8500_codec_power_init(void) +{ + __u8 data, old_data; + + old_data = + ab8500_read(AB8500_SYS_CTRL2_BLOCK, (AB8500_CTRL3_REG & 0xFF)); + + data = 0xFE & old_data; + ab8500_write(AB8500_SYS_CTRL2_BLOCK, (AB8500_CTRL3_REG & 0xFF), data); //0x0200 + + data = 0x02 | old_data; + ab8500_write(AB8500_SYS_CTRL2_BLOCK, (AB8500_CTRL3_REG & 0xFF), data); //0x0200 + + old_data = + ab8500_read(AB8500_SYS_CTRL2_BLOCK, + (AB8500_SYSULPCLK_CTRL1_REG & 0xFF)); +#ifdef CONFIG_U8500_AB8500_CUT10 + data = 0x18 | old_data; +#else + data = 0x10 | old_data; +#endif + ab8500_write(AB8500_SYS_CTRL2_BLOCK, (AB8500_SYSULPCLK_CTRL1_REG & 0xFF), data); //0x020B + + old_data = + ab8500_read(AB8500_REGU_CTRL1, (AB8500_REGU_MISC1_REG & 0xFF)); + data = 0x04 | old_data; + ab8500_write(AB8500_REGU_CTRL1, (AB8500_REGU_MISC1_REG & 0xFF), data); //0x380 + + old_data = + ab8500_read(AB8500_REGU_CTRL1, + (AB8500_REGU_VAUDIO_SUPPLY_REG & 0xFF)); + data = 0x5E | old_data; + ab8500_write(AB8500_REGU_CTRL1, (AB8500_REGU_VAUDIO_SUPPLY_REG & 0xFF), data); //0x0383 + +#ifdef CONFIG_U8500_AB8500_CUT10 + old_data = ab8500_read(AB8500_MISC, (AB8500_GPIO_DIR4_REG & 0xFF)); + data = 0x54 | old_data; + ab8500_write(AB8500_MISC, (AB8500_GPIO_DIR4_REG & 0xFF), data); //0x1013 +#endif +} + +/** +* u8500_acodec_deinit +* +* exit function for STW5098 audiocodec driver. +*/ +static int check_device_id() +{ + __u8 data; + + data = ab8500_read(AB8500_MISC, (0x80 & 0xFF)); + if (((data & 0xF0) == 0x10) || ((data & 0xF0) == 0x11)) { + /* V1 version */ +#ifndef CONFIG_U8500_AB8500_CUT10 + printk("ERROR: AB8500 hardware detected is CUT1x\n"); + return -ENODEV; +#endif + } else { +#ifndef CONFIG_U8500_AB8500_ED + /* ED version */ + printk("ERROR: AB8500 hardware detected is EarlyDrop\n"); + return -ENODEV; +#endif + } + return 0; +} + +static int __init u8500_acodec_init(void) +{ + int status, ret_val; + t_ab8500_codec_error error; + + ret_val = check_device_id(); + if (0 != ret_val) + return ret_val; + + status = i2s_register_driver(&i2sdrv_i2s); + if (status < 0) { + printk("Unable to register i2s driver\n"); + return status; + } + + /*Initialize Audiocodec */ + + ab8500_codec_power_init(); + + AB8500_CODEC_Init(TRG_CODEC_ADDRESS_ON_SPI_BUS); + + /* Reset CODEC */ + error = AB8500_CODEC_Reset(); + if (AB8500_CODEC_OK != error) { + stm_error("Error in AB8500_CODEC_Reset\n"); + return -1; + } + + stm_dbg(DBG_ST.acodec, " leaving u8500_acodec_init() \n"); + return 0; +} + +static void __exit u8500_acodec_deinit(void) +{ + stm_dbg(DBG_ST.acodec, "Entering AUDIOTRG_CODEC_DeIni\n"); + stm_dbg(DBG_ST.acodec, "leaving AUDIOTRG_CODEC_DeIni\n"); + i2s_unregister_driver(&i2sdrv_i2s); +} + +module_init(u8500_acodec_init); +module_exit(u8500_acodec_deinit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("AB8500 stw5098 audiocodec driver"); + +/* exported function by audiocodec to be used by SAA driver and ALSA driver */ + +EXPORT_SYMBOL(u8500_acodec_open); +EXPORT_SYMBOL(u8500_acodec_close); +EXPORT_SYMBOL(u8500_acodec_send_data); +EXPORT_SYMBOL(u8500_acodec_receive_data); +EXPORT_SYMBOL(u8500_acodec_rates); +EXPORT_SYMBOL(u8500_acodec_powerdown); +EXPORT_SYMBOL(u8500_acodec_setuser); +EXPORT_SYMBOL(u8500_acodec_unsetuser); +EXPORT_SYMBOL(u8500_acodec_enable_audio_mode); +//EXPORT_SYMBOL(u8500_acodec_enable_voice_mode); +EXPORT_SYMBOL(u8500_acodec_get_output_volume); +EXPORT_SYMBOL(u8500_acodec_get_input_volume); +EXPORT_SYMBOL(u8500_acodec_set_output_volume); +EXPORT_SYMBOL(u8500_acodec_set_input_volume); +EXPORT_SYMBOL(u8500_acodec_select_input); +EXPORT_SYMBOL(u8500_acodec_select_output); + +t_ab8500_codec_error AB8500_CODEC_Write(IN t_uint8 register_offset, + IN t_uint8 count, IN t_uint8 * ptr_data) +{ + int i; + u32 address; + + for (i = 0; i < count; i++) { + address = (AB8500_AUDIO << 8) | (register_offset + i); + ab8500_write(AB8500_AUDIO, address, ptr_data[i]); + } + return AB8500_CODEC_OK; +} + +t_ab8500_codec_error AB8500_CODEC_Read(IN t_uint8 register_offset, + IN t_uint8 count, + IN t_uint8 * dummy_data, + IN t_uint8 * ptr_data) +{ + int i; + u32 address; + + dummy_data = dummy_data; /*keep compiler happy */ + + for (i = 0; i < count; i++) { + address = (AB8500_AUDIO << 8) | (register_offset + i); + ptr_data[i] = ab8500_read(AB8500_AUDIO, address); + } + + return AB8500_CODEC_OK; +} + +EXPORT_SYMBOL(u8500_acodec_set_src_power_cntrl); +EXPORT_SYMBOL(u8500_acodec_set_burst_mode_fifo); +EXPORT_SYMBOL(u8500_acodec_get_dest_power_state); +EXPORT_SYMBOL(lpbk_state_in_texts); +EXPORT_SYMBOL(u8500_acodec_toggle_playback_mute_control); +EXPORT_SYMBOL(u8500_acodec_set_dest_power_cntrl); +EXPORT_SYMBOL(power_state_in_texts); +EXPORT_SYMBOL(u8500_acodec_toggle_capture_mute_control); +EXPORT_SYMBOL(u8500_acodec_toggle_analog_lpbk); +EXPORT_SYMBOL(u8500_acodec_toggle_digital_lpbk); +EXPORT_SYMBOL(tdm_mode_state_in_texts); +EXPORT_SYMBOL(switch_state_in_texts); +EXPORT_SYMBOL(pcm_rendering_state_in_texts); +EXPORT_SYMBOL(direct_rendering_state_in_texts); +EXPORT_SYMBOL(u8500_acodec_get_src_power_state); +EXPORT_SYMBOL(i2sdrv); +EXPORT_SYMBOL(second_config); -- cgit v1.2.3 From a42b93d8f7ad694b86b4508e5bda7dcaf25c8eb9 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 24 Mar 2011 14:12:01 +0100 Subject: Regulators: show consumers that holds a regulator To locate the consumer(s) that currently holds (ie have enabled) a regulator, a new sysfs entry is created. The consumer(s) are published in /sys/class/regulator/regulator.<#>/use Signed-off-by: Martin Persson Change-Id: Ief78276c9685d0bf5688294b9aed9e0698c3475f Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/6549 Reviewed-by: Bengt JONSSON Conflicts: drivers/regulator/core.c --- drivers/regulator/core.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index d3e38790906..a47238d1fe1 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -78,6 +78,7 @@ struct regulator { char *supply_name; struct device_attribute dev_attr; struct regulator_dev *rdev; + int use; }; static int _regulator_is_enabled(struct regulator_dev *rdev); @@ -563,6 +564,32 @@ static ssize_t regulator_suspend_standby_state_show(struct device *dev, static DEVICE_ATTR(suspend_standby_state, 0444, regulator_suspend_standby_state_show, NULL); +static ssize_t regulator_use_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + struct regulator *reg; + size_t size = 0; + + if (rdev->use_count == 0) + return sprintf(buf, "no users\n"); + + list_for_each_entry(reg, &rdev->consumer_list, list) { + if (!reg->use) + continue; + + if (reg->dev != NULL) + size += sprintf((buf + size), "%s (%d) ", + dev_name(reg->dev), reg->use); + else + size += sprintf((buf + size), "unknown (%d) ", + reg->use); + } + size += sprintf((buf + size), "\n"); + + return size; +} +static DEVICE_ATTR(use, 0444, regulator_use_show, NULL); /* * These are the only attributes are present for all regulators. @@ -1391,6 +1418,9 @@ int regulator_enable(struct regulator *regulator) mutex_lock(&rdev->mutex); ret = _regulator_enable(rdev); mutex_unlock(&rdev->mutex); + if (ret == 0) + regulator->use++; + return ret; } EXPORT_SYMBOL_GPL(regulator_enable); @@ -1474,6 +1504,9 @@ int regulator_disable(struct regulator *regulator) mutex_unlock(&rdev->mutex); } + if (ret == 0) + regulator->use--; + return ret; } EXPORT_SYMBOL_GPL(regulator_disable); @@ -2408,6 +2441,10 @@ static int add_regulator_attributes(struct regulator_dev *rdev) struct regulator_ops *ops = rdev->desc->ops; int status = 0; + status = device_create_file(dev, &dev_attr_use); + if (status < 0) + dev_warn(dev, "Create sysfs file \"use\" failed"); + /* some attributes need specific methods to be displayed */ if (ops->get_voltage || ops->get_voltage_sel) { status = device_create_file(dev, &dev_attr_microvolts); -- cgit v1.2.3 From 2c3ec8089a6b0c6bdffd21f71c9569cd4c94d615 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 28 Apr 2011 14:01:35 +0200 Subject: abx500: big merge from GLK 2.6.35 Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/include/mach/ab8500_gpadc.h | 36 + drivers/hwmon/Kconfig | 26 + drivers/hwmon/Makefile | 2 + drivers/hwmon/ab8500.c | 715 +++++++ drivers/hwmon/db8500.c | 403 ++++ drivers/input/misc/ab8500-ponkey.c | 123 +- drivers/mfd/Kconfig | 26 +- drivers/mfd/Makefile | 2 + drivers/mfd/ab5500-core.c | 2158 +++++++++++++++++++++ drivers/mfd/ab8500-core.c | 219 ++- drivers/mfd/ab8500-debugfs.c | 230 ++- drivers/mfd/ab8500-denc.c | 515 +++++ drivers/mfd/ab8500-gpadc.c | 12 +- drivers/mfd/ab8500-sysctrl.c | 22 + drivers/misc/ab8500-pwm.c | 45 +- drivers/power/Kconfig | 13 + drivers/power/Makefile | 1 + drivers/power/ab8500_btemp.c | 1080 +++++++++++ drivers/power/ab8500_chargalg.c | 1833 ++++++++++++++++++ drivers/power/ab8500_charger.c | 2368 +++++++++++++++++++++++ drivers/power/ab8500_fg.c | 1859 ++++++++++++++++++ drivers/regulator/Kconfig | 8 + drivers/regulator/Makefile | 1 + drivers/regulator/ab8500-debug.c | 2000 +++++++++++++++++++ include/linux/mfd/ab8500.h | 20 +- include/linux/mfd/ab8500/bm.h | 456 +++++ include/linux/mfd/ab8500/denc-regs.h | 357 ++++ include/linux/mfd/ab8500/denc.h | 82 + include/linux/mfd/ab8500/gpadc.h | 2 +- include/linux/mfd/ab8500/ux500_chargalg.h | 38 + include/linux/mfd/abx500.h | 58 +- include/linux/regulator/ab8500-debug.h | 21 + 32 files changed, 14594 insertions(+), 137 deletions(-) create mode 100644 arch/arm/mach-ux500/include/mach/ab8500_gpadc.h create mode 100644 drivers/hwmon/ab8500.c create mode 100755 drivers/hwmon/db8500.c create mode 100755 drivers/mfd/ab5500-core.c create mode 100644 drivers/mfd/ab8500-denc.c create mode 100644 drivers/power/ab8500_btemp.c create mode 100644 drivers/power/ab8500_chargalg.c create mode 100644 drivers/power/ab8500_charger.c create mode 100644 drivers/power/ab8500_fg.c create mode 100644 drivers/regulator/ab8500-debug.c create mode 100644 include/linux/mfd/ab8500/bm.h create mode 100644 include/linux/mfd/ab8500/denc-regs.h create mode 100644 include/linux/mfd/ab8500/denc.h create mode 100644 include/linux/mfd/ab8500/ux500_chargalg.h create mode 100644 include/linux/regulator/ab8500-debug.h diff --git a/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h b/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h new file mode 100644 index 00000000000..4289dcfc0aa --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h @@ -0,0 +1,36 @@ +/* + * ab8500_gpadc.c - AB8500 GPADC Driver + * + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Arun R Murthy + */ + +#ifndef _AB8500_GPADC_H +#define _Ab8500_GPADC_H + +/* GPADC source: From datasheer(ADCSwSel[4:0] in GPADCCtrl2) */ +#define BAT_CTRL 0x01 +#define ACC_DETECT1 0x04 +#define ACC_DETECT2 0x05 +#define MAIN_BAT_V 0x08 +#define BK_BAT_V 0x0C +#define VBUS_V 0x09 +#define MAIN_CHARGER_V 0x03 +#define MAIN_CHARGER_C 0x0A +#define USB_CHARGER_C 0x0B +#define DIE_TEMP 0x0D +#define BTEMP_BALL 0x02 + +struct ab8500_gpadc_device_info { + struct completion ab8500_gpadc_complete; + struct mutex ab8500_gpadc_lock; +#if defined(CONFIG_REGULATOR) + struct regulator *regu; +#endif +}; + +int ab8500_gpadc_conversion(int input); + +#endif /* _AB8500_GPADC_H */ diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5f888f7e7dc..1035e938c9f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -39,6 +39,32 @@ config HWMON_DEBUG_CHIP comment "Native drivers" +config SENSORS_AB8500 + tristate "AB8500 thermal monitoring" + depends on AB8500_CORE + default n + help + If you say yes here you get support for the thermal sensor part + of the AB8500 chip. The driver includes thermal management for + AB8500 die and two GPADC channels. The GPADC channel are preferably + used to access sensors outside the AB8500 chip. + + This driver can also be built as a module. If so, the module + will be called ab8500-temp. + +config SENSORS_DB8500 + tristate "DB8500 thermal monitoring" + depends on UX500_SOC_DB8500 + default n + help + If you say yes here you get support for the thermal sensor part + of the DB8500 chip. The driver includes thermal management for + DB8500 die. + + This driver can also be built as a module. If so, the module + will be called db8500_temp. + + config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 28061cfa0cd..3a730471e28 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,6 +19,8 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o +obj-$(CONFIG_SENSORS_AB8500) += ab8500.o +obj-$(CONFIG_SENSORS_DB8500) += db8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7414) += ad7414.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 00000000000..640a6638db4 --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Persson for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + * Note: + * + * AB8500 does not provide auto ADC, so to monitor the required + * temperatures, a periodic work is used. It is more important + * to not wake up the CPU than to perform this job, hence the use + * of a deferred delay. + * + * A deferred delay for thermal monitor is considered safe because: + * If the chip gets too hot during a sleep state it's most likely + * due to external factors, such as the surrounding temperature. + * I.e. no SW decisions will make any difference. + * + * If/when the AB8500 thermal warning temperature is reached (threshold + * cannot be changed by SW), an interrupt is set and the driver + * notifies user space via a sysfs event. If a shut down is not + * triggered by user space within a certain time frame, + * pm_power off is called. + * + * If/when AB8500 thermal shutdown temperature is reached a hardware + * shutdown of the AB8500 will occur. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * If AB8500 warm interrupt is set, user space will be notified. + * If user space doesn't shut down the platform within this time + * frame, this driver will. Time unit is ms. + */ +#define DEFAULT_POWER_OFF_DELAY 10000 + +#define NUM_SENSORS 4 + +/* The driver monitors GPADC - ADC_AUX1 and ADC_AUX2 */ +#define NUM_MONITORED_SENSORS 2 + +struct ab8500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + struct ab8500_gpadc *gpadc; + u8 gpadc_addr[NUM_SENSORS]; + unsigned long min[NUM_SENSORS]; + unsigned long max[NUM_SENSORS]; + unsigned long max_hyst[NUM_SENSORS]; + unsigned long crit[NUM_SENSORS]; + unsigned long min_alarm[NUM_SENSORS]; + unsigned long max_alarm[NUM_SENSORS]; + unsigned long max_hyst_alarm[NUM_SENSORS]; + unsigned long crit_alarm[NUM_SENSORS]; + struct delayed_work work; + struct delayed_work power_off_work; + struct mutex lock; + /* Delay (ms) between temperature readings */ + unsigned long gpadc_monitor_delay; + /* Delay (ms) before power off */ + unsigned long power_off_delay; +}; + +/* + * Thresholds are considered inactive if set to 0. + * To avoid confusion for user space applications, + * the temp monitor delay is set to 0 if all thresholds + * are 0. + */ +static bool find_active_thresholds(struct ab8500_temp *data) +{ + int i; + for (i = 0; i < NUM_MONITORED_SENSORS; i++) + if (data->max[i] != 0 || data->max_hyst[i] != 0 + || data->min[i] != 0) + return true; + + dev_dbg(&data->pdev->dev, "No active thresholds," + "cancel deferred job (if it exists)" + "and reset temp monitor delay\n"); + mutex_lock(&data->lock); + data->gpadc_monitor_delay = 0; + cancel_delayed_work_sync(&data->work); + mutex_unlock(&data->lock); + return false; +} + +static void thermal_power_off(struct work_struct *work) +{ + struct ab8500_temp *data = container_of(work, struct ab8500_temp, + power_off_work.work); + + dev_warn(&data->pdev->dev, "Power off due to AB8500 thermal warning\n"); + pm_power_off(); +} + +static void gpadc_monitor(struct work_struct *work) +{ + unsigned long delay_in_jiffies; + int val, i, ret; + /* Container for alarm node name */ + char alarm_node[30]; + + bool updated_min_alarm = false; + bool updated_max_alarm = false; + bool updated_max_hyst_alarm = false; + struct ab8500_temp *data = container_of(work, struct ab8500_temp, + work.work); + + for (i = 0; i < NUM_MONITORED_SENSORS; i++) { + /* Thresholds are considered inactive if set to 0 */ + if (data->max[i] == 0 && data->max_hyst[i] == 0 + && data->min[i] == 0) + continue; + + val = ab8500_gpadc_convert(data->gpadc, data->gpadc_addr[i]); + if (val < 0) { + dev_err(&data->pdev->dev, "GPADC read failed\n"); + continue; + } + + mutex_lock(&data->lock); + if (data->min[i] != 0) { + if (val < data->min[i]) { + if (data->min_alarm[i] == 0) { + data->min_alarm[i] = 1; + updated_min_alarm = true; + } + } else { + if (data->min_alarm[i] == 1) { + data->min_alarm[i] = 0; + updated_min_alarm = true; + } + } + + } + if (data->max[i] != 0) { + if (val > data->max[i]) { + if (data->max_alarm[i] == 0) { + data->max_alarm[i] = 1; + updated_max_alarm = true; + } + } else { + if (data->max_alarm[i] == 1) { + data->max_alarm[i] = 0; + updated_max_alarm = true; + } + } + + } + if (data->max_hyst[i] != 0) { + if (val > data->max_hyst[i]) { + if (data->max_hyst_alarm[i] == 0) { + data->max_hyst_alarm[i] = 1; + updated_max_hyst_alarm = true; + } + } else { + if (data->max_hyst_alarm[i] == 1) { + data->max_hyst_alarm[i] = 0; + updated_max_hyst_alarm = true; + } + } + } + mutex_unlock(&data->lock); + + /* hwmon attr index starts at 1, thus "i+1" below */ + if (updated_min_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_min_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_max_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_hyst_alarm) { + ret = snprintf(alarm_node, 21, "temp%d_max_hyst_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + } + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); +} + +static inline void gpadc_monitor_exit(struct platform_device *pdev) +{ + struct ab8500_temp *data = platform_get_drvdata(pdev); + cancel_delayed_work_sync(&data->work); +} + +static ssize_t set_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_jiffies, delay_in_s; + struct ab8500_temp *data = dev_get_drvdata(dev); + + if (!find_active_thresholds(data)) { + dev_dbg(dev, "No thresholds to monitor, disregarding delay\n"); + return count; + } + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->gpadc_monitor_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); + + return count; +} + +static ssize_t set_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_jiffies, delay_in_s; + struct ab8500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->power_off_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + + return count; +} + +static ssize_t show_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->gpadc_monitor_delay) / 1000); +} + +static ssize_t show_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + /* + * To avoid confusion between sensor label and chip name, the function + * "show_label" is not used to return the chip name. + */ + return sprintf(buf, "ab8500\n"); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + char *name; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + /* + * Make sure these labels correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR. + * Temperature sensors outside ab8500 (read via GPADC) are marked + * with prefix ext_ + */ + switch (index) { + case 1: + name = "ext_rtc_xtal"; + break; + case 2: + name = "ext_db8500"; + break; + case 3: + name = "battery"; + break; + case 4: + name = "ab8500"; + break; + default: + return -EINVAL; + } + return sprintf(buf, "%s\n", name); +} + +static ssize_t show_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + u8 gpadc_addr = data->gpadc_addr[attr->index - 1]; + + val = ab8500_gpadc_convert(data->gpadc, gpadc_addr); + if (val < 0) + dev_err(&data->pdev->dev, "GPADC read failed\n"); + + return sprintf(buf, "%d\n", val); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->min_alarm[attr->index - 1] = 0; + + data->min[attr->index - 1] = val; + mutex_unlock(&data->lock); + if (val == 0) + (void) find_active_thresholds(data); + + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_alarm[attr->index - 1] = 0; + + data->max[attr->index - 1] = val; + mutex_unlock(&data->lock); + if (val == 0) + (void) find_active_thresholds(data); + + return count; +} + +static ssize_t set_max_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_hyst_alarm[attr->index - 1] = 0; + + data->max_hyst[attr->index - 1] = val; + mutex_unlock(&data->lock); + if (val == 0) + (void) find_active_thresholds(data); + + return count; +} + +/* + * show functions (RO nodes) + * Notice that min/max/max_hyst refer to millivolts and not millidegrees + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max[attr->index - 1]); +} + +static ssize_t show_max_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst[attr->index - 1]); +} + +/* Alarms */ +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min_alarm[attr->index - 1]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_alarm[attr->index - 1]); +} + +static ssize_t show_max_hyst_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst_alarm[attr->index - 1]); +} + +static ssize_t show_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->crit_alarm[attr->index - 1]); +} + +/*These node are not included in the kernel hwmon sysfs interface */ +static SENSOR_DEVICE_ATTR(temp_monitor_delay, S_IRUGO | S_IWUSR, + show_temp_monitor_delay, set_temp_monitor_delay, 0); +static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, + show_temp_power_off_delay, + set_temp_power_off_delay, 0); + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* GPADC - ADC_AUX1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 1); + +/* GPADC - ADC_AUX2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 2); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 2); + +/* GPADC - BTEMP_BALL */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 3); + +/* AB8500 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, + show_crit_alarm, NULL, 4); + +static struct attribute *ab8500_temp_attributes[] = { + &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, + &sensor_dev_attr_temp_monitor_delay.dev_attr.attr, + &sensor_dev_attr_name.dev_attr.attr, + /* GPADC - ADC_AUX1 */ + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst_alarm.dev_attr.attr, + /* GPADC - ADC_AUX2 */ + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst_alarm.dev_attr.attr, + /* GPADC - BTEMP_BALL */ + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + /* AB8500 */ + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group ab8500_temp_group = { + .attrs = ab8500_temp_attributes, +}; + +static irqreturn_t ab8500_temp_irq_handler(int irq, void *irq_data) +{ + unsigned long delay_in_jiffies; + struct platform_device *pdev = irq_data; + struct ab8500_temp *data = platform_get_drvdata(pdev); + + /* + * Make sure the magic numbers below corresponds to the node + * used for AB8500 thermal warning from HW. + */ + mutex_lock(&data->lock); + data->crit_alarm[3] = 1; + mutex_unlock(&data->lock); + sysfs_notify(&pdev->dev.kobj, NULL, "temp4_crit_alarm"); + dev_info(&pdev->dev, "AB8500 thermal warning, power off in %lu s\n", + data->power_off_delay); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + schedule_delayed_work(&data->power_off_work, delay_in_jiffies); + return IRQ_HANDLED; +} + +static int setup_irqs(struct platform_device *pdev) +{ + int ret; + int irq = platform_get_irq_byname(pdev, "AB8500_TEMP_WARM"); + + if (irq < 0) + dev_err(&pdev->dev, "Get irq by name failed\n"); + + ret = request_threaded_irq(irq, NULL, ab8500_temp_irq_handler, + IRQF_NO_SUSPEND, "ab8500-temp", pdev); + if (ret < 0) + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + + return ret; +} + +static int __devinit ab8500_temp_probe(struct platform_device *pdev) +{ + struct ab8500_temp *data; + int err; + + data = kzalloc(sizeof(struct ab8500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = setup_irqs(pdev); + if (err < 0) + goto exit; + + data->gpadc = ab8500_gpadc_get(); + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + INIT_DELAYED_WORK_DEFERRABLE(&data->work, gpadc_monitor); + INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); + + /* + * Setup HW defined data. + * + * Reference hardware (HREF): + * + * GPADC - ADC_AUX1, connected to NTC R2148 next to RTC_XTAL on HREF + * GPADC - ADC_AUX2, connected to NTC R2150 near DB8500 on HREF + * Hence, temp#_min/max/max_hyst refer to millivolts and not + * millidegrees + * + * HREF HW does not support reading AB8500 temperature. BUT an + * AB8500 IRQ will be launched if die crit temp limit is reached. + * + * Also: + * Battery temperature thresholds will not be exposed via hwmon. + * + * Make sure indexes correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR + */ + data->gpadc_addr[0] = ADC_AUX1; + data->gpadc_addr[1] = ADC_AUX2; + data->gpadc_addr[2] = BTEMP_BALL; + + mutex_init(&data->lock); + data->pdev = pdev; + data->power_off_delay = DEFAULT_POWER_OFF_DELAY; + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &ab8500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + return 0; + +exit_platform_data: + platform_set_drvdata(pdev, NULL); +exit: + kfree(data); + return err; +} + +static int __devexit ab8500_temp_remove(struct platform_device *pdev) +{ + struct ab8500_temp *data = platform_get_drvdata(pdev); + + gpadc_monitor_exit(pdev); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &ab8500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver ab8500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ab8500-temp", + }, + .probe = ab8500_temp_probe, + .remove = __devexit_p(ab8500_temp_remove), +}; + +static int __init ab8500_temp_init(void) +{ + return platform_driver_register(&ab8500_temp_driver); +} + +static void __exit ab8500_temp_exit(void) +{ + platform_driver_unregister(&ab8500_temp_driver); +} + +MODULE_AUTHOR("Martin Persson "); +MODULE_DESCRIPTION("AB8500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(ab8500_temp_init) +module_exit(ab8500_temp_exit) diff --git a/drivers/hwmon/db8500.c b/drivers/hwmon/db8500.c new file mode 100755 index 00000000000..09630f2f707 --- /dev/null +++ b/drivers/hwmon/db8500.c @@ -0,0 +1,403 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + * + * Author: WenHai Fang for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * If DB8500 warm interrupt is set, user space will be notified. + * If user space doesn't shut down the platform within this time + * frame, this driver will. Time unit is ms. + */ +#define DEFAULT_POWER_OFF_DELAY 10000 + +/* + * Default measure period to 0xFF x cycle32k + */ +#define DEFAULT_MEASURE_TIME 0xFF + +/* This driver monitors DB thermal*/ +#define NUM_SENSORS 1 + +struct db8500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + unsigned char min[NUM_SENSORS]; + unsigned char max[NUM_SENSORS]; + unsigned char crit[NUM_SENSORS]; + unsigned short measure_time; + struct delayed_work power_off_work; + struct mutex lock; + /* Delay (ms) before power off */ + unsigned long power_off_delay; +}; + +static void thermal_power_off(struct work_struct *work) +{ + struct db8500_temp *data = container_of(work, struct db8500_temp, + power_off_work.work); + + dev_warn(&data->pdev->dev, "Power off due to DB8500 thermal warning\n"); + pm_power_off(); +} + +static ssize_t set_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_s; + struct db8500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) { + dev_warn(&data->pdev->dev, "Set power_off_delay wrong\n"); + return res; + } + + mutex_lock(&data->lock); + data->power_off_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "db8500\n"); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val > data->max[attr->index - 1]) + val = data->max[attr->index - 1]; + + data->min[attr->index - 1] = val; + + (void)prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + mutex_unlock(&data->lock); + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val < data->min[attr->index - 1]) + val = data->min[attr->index - 1]; + + data->max[attr->index - 1] = val; + + (void)prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_crit(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val = (val > 255) ? 0xFF : val; + + data->crit[attr->index - 1] = val; + (void)prcmu_config_hotdog(data->crit[attr->index - 1]); + mutex_unlock(&data->lock); + + return count; +} + +/* set functions (W nodes) */ +static ssize_t start_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->measure_time = val & 0xFFFF; + mutex_unlock(&data->lock); + + (void)prcmu_start_temp_sense(data->measure_time); + dev_dbg(&data->pdev->dev, "DB8500 thermal start measurement\n"); + return count; +} + +static ssize_t stop_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + (void)prcmu_stop_temp_sense(); + dev_dbg(&data->pdev->dev, "DB8500 thermal stop measurement\n"); + + return count; +} + +/* + * show functions (RO nodes) + * Notice that min/max/max_hyst refer to millivolts and not millidegrees + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->max[attr->index - 1]); +} + +static ssize_t show_crit(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->crit[attr->index - 1]); +} + + +/*These node are not included in the kernel hwmon sysfs interface */ +static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, + show_temp_power_off_delay, + set_temp_power_off_delay, 0); + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 0); +static SENSOR_DEVICE_ATTR(temp1_stop, S_IWUSR, NULL, stop_temp, 0); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, + show_crit, set_crit, 1); + +static struct attribute *db8500_temp_attributes[] = { + &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp1_start.dev_attr.attr, + &sensor_dev_attr_temp1_stop.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + NULL +}; + +static const struct attribute_group db8500_temp_group = { + .attrs = db8500_temp_attributes, +}; + +static irqreturn_t prcmu_hotmon_low_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + sysfs_notify(&pdev->dev.kobj, NULL, "prcmu_hotmon_low alarm"); + dev_dbg(&pdev->dev, "DB8500 thermal low warning\n"); + return IRQ_HANDLED; +} + +static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) +{ + unsigned long delay_in_jiffies; + struct platform_device *pdev = irq_data; + struct db8500_temp *data = platform_get_drvdata(pdev); + + sysfs_notify(&pdev->dev.kobj, NULL, "prcmu_hotmon_high alarm"); + dev_dbg(&pdev->dev, "DB8500 thermal warning, power off in %lu s\n", + (data->power_off_delay) / 1000); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + schedule_delayed_work(&data->power_off_work, delay_in_jiffies); + return IRQ_HANDLED; +} + +static int __devinit db8500_temp_probe(struct platform_device *pdev) +{ + struct db8500_temp *data; + int err = 0, i; + int irq; + + dev_dbg(&pdev->dev, "db8500_temp: Function db8500_temp_probe.\n"); + + data = kzalloc(sizeof(struct db8500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, prcmu_hotmon_low_irq_handler, + IRQF_NO_SUSPEND, "db8500_temp_low", pdev); + if (err < 0) { + dev_err(&pdev->dev, "db8500: Failed allocate HOTMON_LOW.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "db8500: Succeed allocate HOTMON_LOW.\n"); + } + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, prcmu_hotmon_high_irq_handler, + IRQF_NO_SUSPEND, "db8500_temp_high", pdev); + if (err < 0) { + dev_err(&pdev->dev, "db8500: Failed allocate HOTMON_HIGH.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "db8500: Succeed allocate HOTMON_HIGH.\n"); + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + for (i = 0; i < NUM_SENSORS; i++) { + data->min[i] = 0; + data->max[i] = 0xFF; + } + + mutex_init(&data->lock); + INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); + + data->pdev = pdev; + data->power_off_delay = DEFAULT_POWER_OFF_DELAY; + data->measure_time = DEFAULT_MEASURE_TIME; + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &db8500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + return 0; + +exit_platform_data: + platform_set_drvdata(pdev, NULL); +exit: + kfree(data); + return err; +} + +static int __devexit db8500_temp_remove(struct platform_device *pdev) +{ + struct db8500_temp *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &db8500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver db8500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500_temp", + }, + .probe = db8500_temp_probe, + .remove = __devexit_p(db8500_temp_remove), +}; + +static int __init db8500_temp_init(void) +{ + return platform_driver_register(&db8500_temp_driver); +} + +static void __exit db8500_temp_exit(void) +{ + platform_driver_unregister(&db8500_temp_driver); +} + +MODULE_AUTHOR("WenHai Fang "); +MODULE_DESCRIPTION("DB8500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(db8500_temp_init) +module_exit(db8500_temp_exit) diff --git a/drivers/input/misc/ab8500-ponkey.c b/drivers/input/misc/ab8500-ponkey.c index 3d3288a78fd..1cef5e670ca 100644 --- a/drivers/input/misc/ab8500-ponkey.c +++ b/drivers/input/misc/ab8500-ponkey.c @@ -6,7 +6,6 @@ * * AB8500 Power-On Key handler */ - #include #include #include @@ -16,13 +15,13 @@ #include /** - * struct ab8500_ponkey - ab8500 ponkey information + * struct ab8500_ponkey_info - ab8500 ponkey information * @input_dev: pointer to input device * @ab8500: ab8500 parent * @irq_dbf: irq number for falling transition * @irq_dbr: irq number for rising transition */ -struct ab8500_ponkey { +struct ab8500_ponkey_info { struct input_dev *idev; struct ab8500 *ab8500; int irq_dbf; @@ -32,14 +31,14 @@ struct ab8500_ponkey { /* AB8500 gives us an interrupt when ONKEY is held */ static irqreturn_t ab8500_ponkey_handler(int irq, void *data) { - struct ab8500_ponkey *ponkey = data; + struct ab8500_ponkey_info *info = data; - if (irq == ponkey->irq_dbf) - input_report_key(ponkey->idev, KEY_POWER, true); - else if (irq == ponkey->irq_dbr) - input_report_key(ponkey->idev, KEY_POWER, false); + if (irq == info->irq_dbf) + input_report_key(info->idev, KEY_POWER, true); + else if (irq == info->irq_dbr) + input_report_key(info->idev, KEY_POWER, false); - input_sync(ponkey->idev); + input_sync(info->idev); return IRQ_HANDLED; } @@ -47,87 +46,89 @@ static irqreturn_t ab8500_ponkey_handler(int irq, void *data) static int __devinit ab8500_ponkey_probe(struct platform_device *pdev) { struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); - struct ab8500_ponkey *ponkey; - struct input_dev *input; - int irq_dbf, irq_dbr; - int error; + struct ab8500_ponkey_info *info; + int irq_dbf, irq_dbr, ret; irq_dbf = platform_get_irq_byname(pdev, "ONKEY_DBF"); if (irq_dbf < 0) { - dev_err(&pdev->dev, "No IRQ for ONKEY_DBF, error=%d\n", irq_dbf); + dev_err(&pdev->dev, "No IRQ for ONKEY_DBF,error=%d\n", irq_dbf); return irq_dbf; } irq_dbr = platform_get_irq_byname(pdev, "ONKEY_DBR"); if (irq_dbr < 0) { - dev_err(&pdev->dev, "No IRQ for ONKEY_DBR, error=%d\n", irq_dbr); + dev_err(&pdev->dev, "No IRQ for ONKEY_DBR,error=%d\n", irq_dbr); return irq_dbr; } - ponkey = kzalloc(sizeof(struct ab8500_ponkey), GFP_KERNEL); - input = input_allocate_device(); - if (!ponkey || !input) { - error = -ENOMEM; - goto err_free_mem; - } + info = kzalloc(sizeof(struct ab8500_ponkey_info), GFP_KERNEL); + if (!info) + return -ENOMEM; - ponkey->idev = input; - ponkey->ab8500 = ab8500; - ponkey->irq_dbf = irq_dbf; - ponkey->irq_dbr = irq_dbr; + info->ab8500 = ab8500; + info->irq_dbf = irq_dbf; + info->irq_dbr = irq_dbr; - input->name = "AB8500 POn(PowerOn) Key"; - input->dev.parent = &pdev->dev; + info->idev = input_allocate_device(); + if (!info->idev) { + dev_err(ab8500->dev, "Failed to allocate input dev\n"); + ret = -ENOMEM; + goto out; + } - input_set_capability(input, EV_KEY, KEY_POWER); + info->idev->name = "AB8500 POn(PowerOn) Key"; + info->idev->dev.parent = &pdev->dev; + info->idev->evbit[0] = BIT_MASK(EV_KEY); + info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + + ret = input_register_device(info->idev); + if (ret) { + dev_err(ab8500->dev, "Can't register input device: %d\n", ret); + goto out_unfreedevice; + } - error = request_any_context_irq(ponkey->irq_dbf, ab8500_ponkey_handler, - 0, "ab8500-ponkey-dbf", ponkey); - if (error < 0) { + ret = request_threaded_irq(info->irq_dbf, NULL, ab8500_ponkey_handler, + IRQF_NO_SUSPEND, "ab8500-ponkey-dbf", + info); + if (ret < 0) { dev_err(ab8500->dev, "Failed to request dbf IRQ#%d: %d\n", - ponkey->irq_dbf, error); - goto err_free_mem; + info->irq_dbf, ret); + goto out_unregisterdevice; } - error = request_any_context_irq(ponkey->irq_dbr, ab8500_ponkey_handler, - 0, "ab8500-ponkey-dbr", ponkey); - if (error < 0) { + ret = request_threaded_irq(info->irq_dbr, NULL, ab8500_ponkey_handler, + IRQF_NO_SUSPEND, "ab8500-ponkey-dbr", + info); + if (ret < 0) { dev_err(ab8500->dev, "Failed to request dbr IRQ#%d: %d\n", - ponkey->irq_dbr, error); - goto err_free_dbf_irq; + info->irq_dbr, ret); + goto out_irq_dbf; } - error = input_register_device(ponkey->idev); - if (error) { - dev_err(ab8500->dev, "Can't register input device: %d\n", error); - goto err_free_dbr_irq; - } + platform_set_drvdata(pdev, info); - platform_set_drvdata(pdev, ponkey); return 0; -err_free_dbr_irq: - free_irq(ponkey->irq_dbr, ponkey); -err_free_dbf_irq: - free_irq(ponkey->irq_dbf, ponkey); -err_free_mem: - input_free_device(input); - kfree(ponkey); - - return error; +out_irq_dbf: + free_irq(info->irq_dbf, info); +out_unregisterdevice: + input_unregister_device(info->idev); + info->idev = NULL; +out_unfreedevice: + input_free_device(info->idev); +out: + kfree(info); + return ret; } static int __devexit ab8500_ponkey_remove(struct platform_device *pdev) { - struct ab8500_ponkey *ponkey = platform_get_drvdata(pdev); - - free_irq(ponkey->irq_dbf, ponkey); - free_irq(ponkey->irq_dbr, ponkey); - input_unregister_device(ponkey->idev); - kfree(ponkey); - - platform_set_drvdata(pdev, NULL); + struct ab8500_ponkey_info *info = platform_get_drvdata(pdev); + free_irq(info->irq_dbf, info); + free_irq(info->irq_dbr, info); + input_unregister_device(info->idev); + kfree(info); return 0; } diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6ca938a6bf9..f852bea5f81 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -295,6 +295,20 @@ 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 PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" depends on I2C=y @@ -560,6 +574,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 @@ -570,10 +592,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 AB3550_CORE bool "ST-Ericsson AB3550 Mixed Signal Circuit core functions" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index d7d47d2a4c7..2df4bcb2600 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 88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o @@ -75,6 +76,7 @@ obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB3550_CORE) += ab3550-core.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) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c new file mode 100755 index 00000000000..a4fa61d3d9b --- /dev/null +++ b/drivers/mfd/ab5500-core.c @@ -0,0 +1,2158 @@ +/* + * Copyright (C) 2007-2010 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Low-level core for exclusive access to the AB5500 IC on the I2C bus + * and some basic chip-configuration. + * Author: Bengt Jonsson + * Author: Mattias Nilsson + * Author: Mattias Wallin + * Author: Rickard Andersson + * Author: Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AB5500_NAME_STRING "ab5500" +#define AB5500_ID_FORMAT_STRING "AB5500 %s" +#define AB5500_NUM_EVENT_REG 23 + +/* These are the only registers inside AB5500 used in this main file */ + +/* Read/write operation values. */ +#define AB5500_PERM_RD (0x01) +#define AB5500_PERM_WR (0x02) + +/* Read/write permissions. */ +#define AB5500_PERM_RO (AB5500_PERM_RD) +#define AB5500_PERM_RW (AB5500_PERM_RD | AB5500_PERM_WR) + +#define AB5500_MASK_BASE (0x60) +#define AB5500_MASK_END (0x79) +#define AB5500_CHIP_ID (0x20) + +/** + * struct ab5500 + * @access_mutex: lock out concurrent accesses to the AB registers + * @dev: a pointer to the device struct for this chip driver + * @ab5500_irq: the analog baseband irq + * @irq_base: the platform configuration irq base for subdevices + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @mask_work: a worker for writing to mask registers + * @event_lock: a lock to protect the event_mask + * @abb_events: a local bit mask of the prcmu wakeup events + * @event_mask: a local copy of the mask event registers + * @last_event_mask: a copy of the last event_mask written to hardware + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + */ +struct ab5500 { + struct mutex access_mutex; + struct device *dev; + unsigned int ab5500_irq; + unsigned int irq_base; + char chip_name[32]; + u8 chip_id; + struct work_struct mask_work; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + struct work_struct irq_work; +#endif + spinlock_t event_lock; + u32 abb_events; + u8 event_mask[AB5500_NUM_EVENT_REG]; + u8 last_event_mask[AB5500_NUM_EVENT_REG]; + u8 startup_events[AB5500_NUM_EVENT_REG]; + bool startup_events_read; +#ifdef CONFIG_DEBUG_FS + unsigned int debug_bank; + unsigned int debug_address; +#endif +}; + +/** + * struct ab5500_bank + * @slave_addr: I2C slave_addr found in AB5500 specification + * @name: Documentation name of the bank. For reference + */ +struct ab5500_bank { + u8 slave_addr; + const char *name; +}; + +static const struct ab5500_bank bankinfo[AB5500_NUM_BANKS] = { + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = {0x4A, "VIT_IO_I2C_CLK_TST_OTP"}, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = {0x4B, "VDDDIG_IO_I2C_CLK_TST"}, + [AB5500_BANK_VDENC] = {0x06, "VDENC"}, + [AB5500_BANK_SIM_USBSIM] = {0x04, "SIM_USBSIM"}, + [AB5500_BANK_LED] = {0x10, "LED"}, + [AB5500_BANK_ADC] = {0x0A, "ADC"}, + [AB5500_BANK_RTC] = {0x0F, "RTC"}, + [AB5500_BANK_STARTUP] = {0x03, "STARTUP"}, + [AB5500_BANK_DBI_ECI] = {0x07, "DBI-ECI"}, + [AB5500_BANK_CHG] = {0x0B, "CHG"}, + [AB5500_BANK_FG_BATTCOM_ACC] = {0x0C, "FG_BATCOM_ACC"}, + [AB5500_BANK_USB] = {0x05, "USB"}, + [AB5500_BANK_IT] = {0x0E, "IT"}, + [AB5500_BANK_VIBRA] = {0x02, "VIBRA"}, + [AB5500_BANK_AUDIO_HEADSETUSB] = {0x0D, "AUDIO_HEADSETUSB"}, +}; + +/** + * struct ab5500_reg_range + * @first: the first address of the range + * @last: the last address of the range + * @perm: access permissions for the range + */ +struct ab5500_reg_range { + u8 first; + u8 last; + u8 perm; +}; + +/** + * struct ab5500_i2c_ranges + * @count: the number of ranges in the list + * @range: the list of register ranges + */ +struct ab5500_i2c_ranges { + u8 nranges; + u8 bankid; + const struct ab5500_reg_range *range; +}; + +/** + * struct ab5500_i2c_banks + * @count: the number of ranges in the list + * @range: the list of register ranges + */ +struct ab5500_i2c_banks { + u8 nbanks; + const struct ab5500_i2c_ranges *bank; +}; + +/* + * Permissible register ranges for reading and writing per device and bank. + * + * The ranges must be listed in increasing address order, and no overlaps are + * allowed. It is assumed that write permission implies read permission + * (i.e. only RO and RW permissions should be used). Ranges with write + * permission must not be split up. + */ + +#define NO_RANGE {.count = 0, .range = NULL,} + +static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_ADC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_ADC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x20, + .last = 0x58, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_LEDS] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + /* What registers should this device access?*/ + [AB5500_DEVID_POWER] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_CHG, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x10, + .last = 0x30, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_REGULATORS] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_STARTUP, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x50, + .last = 0xE0, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_SIM] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x19, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_RTC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_CHARGER] = { + .nbanks = 2, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_CHG, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x18, + .perm = AB5500_PERM_RW, + }, + }, + }, + { + .bankid = AB5500_BANK_ADC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x58, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_FUELGAUGE] = { + .nbanks = 2, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x10, + .perm = AB5500_PERM_RW, + }, + }, + }, + { + .bankid = AB5500_BANK_ADC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x20, + .last = 0x58, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_VIBRATOR] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_VIBRA, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_CODEC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x48, + .perm = AB5500_PERM_RW, + }, + + }, + }, + }, + }, +}; + +/* I appologize for the resource names beeing a mix of upper case + * and lower case but I want them to be exact as the documentation */ +static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_LEDS] = { + .name = "ab5500-leds", + .id = AB5500_DEVID_LEDS, + }, + [AB5500_DEVID_POWER] = { + .name = "ab5500-power", + .id = AB5500_DEVID_POWER, + }, + [AB5500_DEVID_REGULATORS] = { + .name = "ab5500-regulators", + .id = AB5500_DEVID_REGULATORS, + }, + [AB5500_DEVID_SIM] = { + .name = "ab5500-sim", + .id = AB5500_DEVID_SIM, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "SIMOFF", + .flags = IORESOURCE_IRQ, + .start = 16, /*rising*/ + .end = 17, /*falling*/ + }, + }, + }, + [AB5500_DEVID_RTC] = { + .name = "ab5500-rtc", + .id = AB5500_DEVID_RTC, + }, + [AB5500_DEVID_CHARGER] = { + .name = "ab5500-charger", + .id = AB5500_DEVID_CHARGER, + }, + [AB5500_DEVID_ADC] = { + .name = "ab5500-adc", + .id = AB5500_DEVID_ADC, + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "TRIGGER-0", + .flags = IORESOURCE_IRQ, + .start = 0, + .end = 0, + }, + { + .name = "TRIGGER-1", + .flags = IORESOURCE_IRQ, + .start = 1, + .end = 1, + }, + { + .name = "TRIGGER-2", + .flags = IORESOURCE_IRQ, + .start = 2, + .end = 2, + }, + { + .name = "TRIGGER-3", + .flags = IORESOURCE_IRQ, + .start = 3, + .end = 3, + }, + { + .name = "TRIGGER-4", + .flags = IORESOURCE_IRQ, + .start = 4, + .end = 4, + }, + { + .name = "TRIGGER-5", + .flags = IORESOURCE_IRQ, + .start = 5, + .end = 5, + }, + { + .name = "TRIGGER-6", + .flags = IORESOURCE_IRQ, + .start = 6, + .end = 6, + }, + { + .name = "TRIGGER-7", + .flags = IORESOURCE_IRQ, + .start = 7, + .end = 7, + }, + { + .name = "TRIGGER-VBAT-TXON", + .flags = IORESOURCE_IRQ, + .start = 9, + .end = 9, + }, + { + .name = "TRIGGER-VBAT", + .flags = IORESOURCE_IRQ, + .start = 8, + .end = 8, + }, + }, + }, + [AB5500_DEVID_FUELGAUGE] = { + .name = "ab5500-fuelgauge", + .id = AB5500_DEVID_FUELGAUGE, + .num_resources = 6, + .resources = (struct resource[]) { + { + .name = "Batt_attach", + .flags = IORESOURCE_IRQ, + .start = 61, + .end = 61, + }, + { + .name = "Batt_removal", + .flags = IORESOURCE_IRQ, + .start = 62, + .end = 62, + }, + { + .name = "UART_framing", + .flags = IORESOURCE_IRQ, + .start = 63, + .end = 63, + }, + { + .name = "UART_overrun", + .flags = IORESOURCE_IRQ, + .start = 64, + .end = 64, + }, + { + .name = "UART_Rdy_RX", + .flags = IORESOURCE_IRQ, + .start = 65, + .end = 65, + }, + { + .name = "UART_Rdy_TX", + .flags = IORESOURCE_IRQ, + .start = 66, + .end = 66, + }, + }, + }, + [AB5500_DEVID_VIBRATOR] = { + .name = "ab5500-vibrator", + .id = AB5500_DEVID_VIBRATOR, + }, + [AB5500_DEVID_CODEC] = { + .name = "ab5500-codec", + .id = AB5500_DEVID_CODEC, + .num_resources = 3, + .resources = (struct resource[]) { + { + .name = "audio_spkr1_ovc", + .flags = IORESOURCE_IRQ, + .start = 77, + .end = 77, + }, + { + .name = "audio_plllocked", + .flags = IORESOURCE_IRQ, + .start = 78, + .end = 78, + }, + { + .name = "audio_spkr2_ovc", + .flags = IORESOURCE_IRQ, + .start = 140, + .end = 140, + }, + }, + }, + [AB5500_DEVID_USB] = { + .name = "ab5500-usb", + .id = AB5500_DEVID_USB, + .num_resources = 35, + .resources = (struct resource[]) { + { + .name = "DCIO", + .flags = IORESOURCE_IRQ, + .start = 67, + .end = 68, + }, + { + .name = "VBUS", + .flags = IORESOURCE_IRQ, + .start = 69, + .end = 70, + }, + { + .name = "CHGstate_10_PCVBUSchg", + .flags = IORESOURCE_IRQ, + .start = 71, + .end = 71, + }, + { + .name = "DCIOreverse_ovc", + .flags = IORESOURCE_IRQ, + .start = 72, + .end = 72, + }, + { + .name = "USBCharDetDone", + .flags = IORESOURCE_IRQ, + .start = 73, + .end = 73, + }, + { + .name = "DCIO_no_limit", + .flags = IORESOURCE_IRQ, + .start = 74, + .end = 74, + }, + { + .name = "USB_suspend", + .flags = IORESOURCE_IRQ, + .start = 75, + .end = 75, + }, + { + .name = "DCIOreverse_fwdcurrent", + .flags = IORESOURCE_IRQ, + .start = 76, + .end = 76, + }, + { + .name = "Vbus_Imeasmax_change", + .flags = IORESOURCE_IRQ, + .start = 79, + .end = 80, + }, + { + .name = "OVV", + .flags = IORESOURCE_IRQ, + .start = 117, + .end = 117, + }, + { + .name = "USBcharging_NOTok", + .flags = IORESOURCE_IRQ, + .start = 123, + .end = 123, + }, + { + .name = "usb_adp_sensoroff", + .flags = IORESOURCE_IRQ, + .start = 126, + .end = 126, + }, + { + .name = "usb_adp_probeplug", + .flags = IORESOURCE_IRQ, + .start = 127, + .end = 127, + }, + { + .name = "usb_adp_sinkerror", + .flags = IORESOURCE_IRQ, + .start = 128, + .end = 128, + }, + { + .name = "usb_adp_sourceerror", + .flags = IORESOURCE_IRQ, + .start = 129, + .end = 129, + }, + { + .name = "usb_idgnd", + .flags = IORESOURCE_IRQ, + .start = 130, + .end = 131, + }, + { + .name = "usb_iddetR1", + .flags = IORESOURCE_IRQ, + .start = 132, + .end = 133, + }, + { + .name = "usb_iddetR2", + .flags = IORESOURCE_IRQ, + .start = 134, + .end = 135, + }, + { + .name = "usb_iddetR3", + .flags = IORESOURCE_IRQ, + .start = 136, + .end = 137, + }, + { + .name = "usb_iddetR4", + .flags = IORESOURCE_IRQ, + .start = 138, + .end = 139, + }, + { + .name = "CharTempWindowOk", + .flags = IORESOURCE_IRQ, + .start = 143, + .end = 144, + }, + { + .name = "USB_SprDetect", + .flags = IORESOURCE_IRQ, + .start = 145, + .end = 145, + }, + { + .name = "usb_adp_probe_unplug", + .flags = IORESOURCE_IRQ, + .start = 146, + .end = 146, + }, + { + .name = "VBUSChDrop", + .flags = IORESOURCE_IRQ, + .start = 147, + .end = 148, + }, + { + .name = "dcio_char_rec_done", + .flags = IORESOURCE_IRQ, + .start = 149, + .end = 149, + }, + { + .name = "Charging_stopped_by_temp", + .flags = IORESOURCE_IRQ, + .start = 150, + .end = 150, + }, + { + .name = "CHGstate_11_SafeModeVBUS", + .flags = IORESOURCE_IRQ, + .start = 169, + .end = 169, + }, + { + .name = "CHGstate_12_comletedVBUS", + .flags = IORESOURCE_IRQ, + .start = 170, + .end = 170, + }, + { + .name = "CHGstate_13_completedVBUS", + .flags = IORESOURCE_IRQ, + .start = 171, + .end = 171, + }, + { + .name = "CHGstate_14_FullChgDCIO", + .flags = IORESOURCE_IRQ, + .start = 172, + .end = 172, + }, + { + .name = "CHGstate_15_SafeModeDCIO", + .flags = IORESOURCE_IRQ, + .start = 173, + .end = 173, + }, + { + .name = "CHGstate_16_OFFsuspendDCIO", + .flags = IORESOURCE_IRQ, + .start = 174, + .end = 174, + }, + { + .name = "CHGstate_17_completedDCIO", + .flags = IORESOURCE_IRQ, + .start = 175, + .end = 175, + }, + { + .name = "o_it_dcio_char_rec_notok", + .flags = IORESOURCE_IRQ, + .start = 176, + .end = 176, + }, + { + .name = "usb_link_update", + .flags = IORESOURCE_IRQ, + .start = 177, + .end = 177, + }, + }, + }, + [AB5500_DEVID_OTP] = { + .name = "ab5500-otp", + .id = AB5500_DEVID_OTP, + }, + [AB5500_DEVID_VIDEO] = { + .name = "ab5500-video", + .id = AB5500_DEVID_VIDEO, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "plugTVdet", + .flags = IORESOURCE_IRQ, + .start = 111, + .end = 111, + }, + }, + }, + [AB5500_DEVID_DBIECI] = { + .name = "ab5500-dbieci", + .id = AB5500_DEVID_DBIECI, + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "COLL", + .flags = IORESOURCE_IRQ, + .start = 112, + .end = 112, + }, + { + .name = "RESERR", + .flags = IORESOURCE_IRQ, + .start = 113, + .end = 113, + }, + { + .name = "FRAERR", + .flags = IORESOURCE_IRQ, + .start = 114, + .end = 114, + }, + { + .name = "COMERR", + .flags = IORESOURCE_IRQ, + .start = 115, + .end = 115, + }, + { + .name = "BSI_indicator", + .flags = IORESOURCE_IRQ, + .start = 116, + .end = 116, + }, + { + .name = "SPDSET", + .flags = IORESOURCE_IRQ, + .start = 118, + .end = 118, + }, + { + .name = "DSENT", + .flags = IORESOURCE_IRQ, + .start = 119, + .end = 119, + }, + { + .name = "DREC", + .flags = IORESOURCE_IRQ, + .start = 120, + .end = 120, + }, + { + .name = "ACCINT", + .flags = IORESOURCE_IRQ, + .start = 121, + .end = 121, + }, + { + .name = "NOPINT", + .flags = IORESOURCE_IRQ, + .start = 122, + .end = 122, + }, + }, + }, +}; + +/* + * This stubbed prcmu functionality should be removed when the prcmu driver + * implements it. + */ +static u8 prcmu_event_buf[AB5500_NUM_EVENT_REG]; + +void prcmu_get_abb_event_buf(u8 **buf) +{ + *buf = prcmu_event_buf; +} + +/* + * Functionality for getting/setting register values. + */ +static int get_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, + u8 *value) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, reg, value, 1); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int get_register_page_interruptible(struct ab5500 *ab, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + /* The hardware limit for get page is 4 */ + if (numregs > 4) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, first_reg, + regvals, numregs); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int mask_and_set_register_interruptible(struct ab5500 *ab, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + int err = 0; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + if (bitmask) { + u8 buf; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + if (bitmask == 0xFF) /* No need to read in this case. */ + buf = bitvalues; + else { /* Read and modify the register value. */ + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, + reg, &buf, 1); + if (err) + return err; + + buf = ((~bitmask & buf) | (bitmask & bitvalues)); + } + /* Write the new value. */ + err = db5500_prcmu_abb_write(bankinfo[bank].slave_addr, reg, + &buf, 1); + + mutex_unlock(&ab->access_mutex); + } + return err; +} + +/* + * Read/write permission checking functions. + */ +static const struct ab5500_i2c_ranges *get_bankref(u8 devid, u8 bank) +{ + u8 i; + + if (devid < AB5500_NUM_DEVICES) { + for (i = 0; i < ab5500_bank_ranges[devid].nbanks; i++) { + if (ab5500_bank_ranges[devid].bank[i].bankid == bank) + return &ab5500_bank_ranges[devid].bank[i]; + } + } + return NULL; +} + +static bool page_write_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) +{ + u8 i; /* range loop index */ + const struct ab5500_i2c_ranges *bankref; + + bankref = get_bankref(devid, bank); + if (bankref == NULL || last_reg < first_reg) + return false; + + for (i = 0; i < bankref->nranges; i++) { + if (first_reg < bankref->range[i].first) + break; + if ((last_reg <= bankref->range[i].last) && + (bankref->range[i].perm & AB5500_PERM_WR)) + return true; + } + return false; +} + +static bool reg_write_allowed(u8 devid, u8 bank, u8 reg) +{ + return page_write_allowed(devid, bank, reg, reg); +} + +static bool page_read_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) +{ + u8 i; + const struct ab5500_i2c_ranges *bankref; + + bankref = get_bankref(devid, bank); + if (bankref == NULL || last_reg < first_reg) + return false; + + + /* Find the range (if it exists in the list) that includes first_reg. */ + for (i = 0; i < bankref->nranges; i++) { + if (first_reg < bankref->range[i].first) + return false; + if (first_reg <= bankref->range[i].last) + break; + } + /* Make sure that the entire range up to and including last_reg is + * readable. This may span several of the ranges in the list. + */ + while ((i < bankref->nranges) && + (bankref->range[i].perm & AB5500_PERM_RD)) { + if (last_reg <= bankref->range[i].last) + return true; + if ((++i >= bankref->nranges) || + (bankref->range[i].first != + (bankref->range[i - 1].last + 1))) { + break; + } + } + return false; +} + +static bool reg_read_allowed(u8 devid, u8 bank, u8 reg) +{ + return page_read_allowed(devid, bank, reg, reg); +} + + +/* + * The exported register access functionality. + */ +int ab5500_get_chip_id(struct device *dev) +{ + struct ab5500 *ab = dev_get_drvdata(dev->parent); + + return (int)ab->chip_id; +} + +int ab5500_mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !reg_write_allowed(pdev->id, bank, reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return mask_and_set_register_interruptible(ab, bank, reg, + bitmask, bitvalues); +} + +int ab5500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 value) +{ + return ab5500_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, + value); +} + +int ab5500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !reg_read_allowed(pdev->id, bank, reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_interruptible(ab, bank, reg, value); +} + +int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !page_read_allowed(pdev->id, bank, + first_reg, (first_reg + numregs - 1))) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_page_interruptible(ab, bank, first_reg, regvals, + numregs); +} + +int ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) +{ + struct ab5500 *ab; + + ab = dev_get_drvdata(dev->parent); + if (!ab->startup_events_read) + return -EAGAIN; /* Try again later */ + + memcpy(event, ab->startup_events, AB5500_NUM_EVENT_REG); + return 0; +} + +int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) +{ + struct ab5500 *ab; + bool val; + + ab = get_irq_chip_data(irq); + irq -= ab->irq_base; + val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0); + + return val; +} + +static struct abx500_ops ab5500_ops = { + .get_chip_id = ab5500_get_chip_id, + .get_register = ab5500_get_register_interruptible, + .set_register = ab5500_set_register_interruptible, + .get_register_page = ab5500_get_register_page_interruptible, + .set_register_page = NULL, + .mask_and_set_register = ab5500_mask_and_set_register_interruptible, + .event_registers_startup_state_get = + ab5500_event_registers_startup_state_get, + .startup_irq_enabled = ab5500_startup_irq_enabled, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) +static irqreturn_t ab5500_irq_handler(int irq, void *data) +{ + struct ab5500 *ab = data; + + /* + * Disable the IRQ and dispatch a worker to handle the + * event. Since the chip resides on I2C this is slow + * stuff and we will re-enable the interrupts once the + * worker has finished. + */ + disable_irq_nosync(irq); + schedule_work(&ab->irq_work); + return IRQ_HANDLED; +} + +static void ab5500_irq_work(struct work_struct *work) +{ + struct ab5500 *ab = container_of(work, struct ab5500, irq_work); + u8 i; + u8 *e = 0; + u8 events[AB5500_NUM_EVENT_REG]; + unsigned long flags; + + prcmu_get_abb_event_buf(&e); + + spin_lock_irqsave(&ab->event_lock, flags); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) + events[i] = e[i] & ~ab->event_mask[i]; + spin_unlock_irqrestore(&ab->event_lock, flags); + + local_irq_disable(); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + u8 bit; + u8 event_reg; + + dev_dbg(ab->dev, "IRQ Event[%d]: 0x%2x\n", + i, events[i]); + + event_reg = events[i]; + for (bit = 0; event_reg; bit++, event_reg /= 2) { + if (event_reg % 2) { + unsigned int irq; + struct irq_desc *desc; + + irq = ab->irq_base + (i * 8) + bit; + desc = irq_to_desc(irq); + if (desc->status & IRQ_DISABLED) + note_interrupt(irq, desc, IRQ_NONE); + else + desc->handle_irq(irq, desc); + } + } + } + local_irq_enable(); + /* By now the IRQ should be acked and deasserted so enable it again */ + enable_irq(ab->ab5500_irq); +} + +#else + +static irqreturn_t ab5500_irq_handler(int irq, void *data) +{ + struct ab5500 *ab = data; + u8 i; + u8 *e = 0; + u8 events[AB5500_NUM_EVENT_REG]; + + prcmu_get_abb_event_buf(&e); + + spin_lock(&ab->event_lock); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) + events[i] = e[i] & ~ab->event_mask[i]; + spin_unlock(&ab->event_lock); + + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + u8 bit; + u8 event_reg; + + dev_dbg(ab->dev, "IRQ Event[%d]: 0x%2x\n", + i, events[i]); + + event_reg = events[i]; + for (bit = 0; event_reg; bit++, event_reg /= 2) { + if (event_reg % 2) { + unsigned int irq; + + irq = ab->irq_base + (i * 8) + bit; + generic_handle_irq(irq); + } + } + } + + return IRQ_HANDLED; +} +#endif + +#ifdef CONFIG_DEBUG_FS +static struct ab5500_i2c_ranges debug_ranges[AB5500_NUM_BANKS] = { + [AB5500_BANK_LED] = { + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + }, + }, + }, + [AB5500_BANK_ADC] = { + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x24, + }, + { + .first = 0x26, + .last = 0x2D, + }, + { + .first = 0x2F, + .last = 0x35, + }, + { + .first = 0x37, + .last = 0x58, + }, + }, + }, + [AB5500_BANK_RTC] = { + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x0C, + }, + }, + }, + [AB5500_BANK_STARTUP] = { + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x1F, + .last = 0x1F, + }, + { + .first = 0x2E, + .last = 0x30, + }, + { + .first = 0x50, + .last = 0x51, + }, + { + .first = 0x60, + .last = 0x61, + }, + { + .first = 0x66, + .last = 0x8A, + }, + { + .first = 0x8C, + .last = 0x96, + }, + { + .first = 0xAA, + .last = 0xB4, + }, + { + .first = 0xB7, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xCA, + }, + { + .first = 0xD3, + .last = 0xE0, + }, + { + .first = 0xF0, + .last = 0xF8, + }, + }, + }, + [AB5500_BANK_DBI_ECI] = { + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x10, + }, + { + .first = 0x13, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_CHG] = { + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x11, + .last = 0x1B, + }, + }, + }, + [AB5500_BANK_FG_BATTCOM_ACC] = { + .nranges = 5, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x10, + }, + { + .first = 0x1A, + .last = 0x1D, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x23, + .last = 0x24, + }, + { + .first = 0xFC, + .last = 0xFE, + }, + }, + }, + [AB5500_BANK_USB] = { + .nranges = 13, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + }, + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8B, + }, + { + .first = 0x91, + .last = 0x94, + }, + { + .first = 0xA8, + .last = 0xB0, + }, + { + .first = 0xB2, + .last = 0xB2, + }, + { + .first = 0xB4, + .last = 0xBC, + }, + { + .first = 0xBF, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xC6, + }, + { + .first = 0xCD, + .last = 0xCD, + }, + { + .first = 0xD6, + .last = 0xDA, + }, + { + .first = 0xDC, + .last = 0xDC, + }, + { + .first = 0xE0, + .last = 0xE4, + }, + }, + }, + [AB5500_BANK_IT] = { + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + }, + { + .first = 0x20, + .last = 0x36, + }, + { + .first = 0x60, + .last = 0x76, + }, + { + .first = 0x80, + .last = 0x80, + }, + }, + }, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + .nranges = 7, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x02, + .last = 0x02, + }, + { + .first = 0x12, + .last = 0x12, + }, + { + .first = 0x30, + .last = 0x34, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x60, + .last = 0x64, + }, + { + .first = 0x70, + .last = 0x74, + }, + }, + }, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x02, + }, + { + .first = 0x0D, + .last = 0x0F, + }, + { + .first = 0x1C, + .last = 0x1C, + }, + { + .first = 0x1E, + .last = 0x1E, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x25, + .last = 0x25, + }, + { + .first = 0x28, + .last = 0x2A, + }, + { + .first = 0x30, + .last = 0x33, + }, + { + .first = 0x40, + .last = 0x43, + }, + { + .first = 0x50, + .last = 0x53, + }, + { + .first = 0x60, + .last = 0x63, + }, + { + .first = 0x70, + .last = 0x73, + }, + }, + }, + [AB5500_BANK_VIBRA] = { + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + }, + { + .first = 0xFE, + .last = 0xFE, + }, + }, + }, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x48, + }, + { + .first = 0xEB, + .last = 0xFB, + }, + }, + }, +}; + +static int ab5500_registers_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + unsigned int i; + u8 bank = (u8)ab->debug_bank; + + seq_printf(s, AB5500_NAME_STRING " register values:\n"); + + seq_printf(s, " bank %u, %s (0x%x):\n", bank, + bankinfo[bank].name, + bankinfo[bank].slave_addr); + for (i = 0; i < debug_ranges[bank].nranges; i++) { + u8 reg; + int err; + + for (reg = debug_ranges[bank].range[i].first; + reg <= debug_ranges[bank].range[i].last; + reg++) { + u8 value; + + err = get_register_interruptible(ab, bank, reg, + &value); + if (err < 0) { + dev_err(ab->dev, "get_reg failed %d, bank 0x%x" + ", reg 0x%x\n", err, bank, reg); + return err; + } + + err = seq_printf(s, " [%d/0x%02X]: 0x%02X\n", bank, + reg, value); + if (err < 0) { + dev_err(ab->dev, "seq_printf overflow\n"); + /* + * Error is not returned here since + * the output is wanted in any case + */ + return 0; + } + } + } + return 0; +} + +static int ab5500_registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_registers_print, inode->i_private); +} + +static const struct file_operations ab5500_registers_fops = { + .open = ab5500_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab5500_bank_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + + seq_printf(s, "%d\n", ab->debug_bank); + return 0; +} + +static int ab5500_bank_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_bank_print, inode->i_private); +} + +static ssize_t ab5500_bank_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_bank; + int err; + + /* 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_bank); + if (err) + return -EINVAL; + + if (user_bank >= AB5500_NUM_BANKS) { + dev_err(ab->dev, + "debugfs error input > number of banks\n"); + return -EINVAL; + } + + ab->debug_bank = user_bank; + + return buf_size; +} + +static int ab5500_address_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + + seq_printf(s, "0x%02X\n", ab->debug_address); + return 0; +} + +static int ab5500_address_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_address_print, inode->i_private); +} + +static ssize_t ab5500_address_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_address; + int err; + + /* 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_address); + if (err) + return -EINVAL; + if (user_address > 0xff) { + dev_err(ab->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + ab->debug_address = user_address; + return buf_size; +} + +static int ab5500_val_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + int err; + u8 regvalue; + + err = get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) { + dev_err(ab->dev, "get_reg failed %d, bank 0x%x" + ", reg 0x%x\n", err, ab->debug_bank, + ab->debug_address); + return -EINVAL; + } + seq_printf(s, "0x%02X\n", regvalue); + + return 0; +} + +static int ab5500_val_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_val_print, inode->i_private); +} + +static ssize_t ab5500_val_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + u8 regvalue; + + /* 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 > 0xff) { + dev_err(ab->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + err = mask_and_set_register_interruptible( + ab, (u8)ab->debug_bank, + (u8)ab->debug_address, 0xFF, (u8)user_val); + if (err) + return -EINVAL; + + get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) + return -EINVAL; + + return buf_size; +} + +static const struct file_operations ab5500_bank_fops = { + .open = ab5500_bank_open, + .write = ab5500_bank_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab5500_address_fops = { + .open = ab5500_address_open, + .write = ab5500_address_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab5500_val_fops = { + .open = ab5500_val_open, + .write = ab5500_val_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab5500_dir; +static struct dentry *ab5500_reg_file; +static struct dentry *ab5500_bank_file; +static struct dentry *ab5500_address_file; +static struct dentry *ab5500_val_file; + +static inline void ab5500_setup_debugfs(struct ab5500 *ab) +{ + ab->debug_bank = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP; + ab->debug_address = AB5500_CHIP_ID; + + ab5500_dir = debugfs_create_dir(AB5500_NAME_STRING, NULL); + if (!ab5500_dir) + goto exit_no_debugfs; + + ab5500_reg_file = debugfs_create_file("all-bank-registers", + S_IRUGO, ab5500_dir, ab, &ab5500_registers_fops); + if (!ab5500_reg_file) + goto exit_destroy_dir; + + ab5500_bank_file = debugfs_create_file("register-bank", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_bank_fops); + if (!ab5500_bank_file) + goto exit_destroy_reg; + + ab5500_address_file = debugfs_create_file("register-address", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_address_fops); + if (!ab5500_address_file) + goto exit_destroy_bank; + + ab5500_val_file = debugfs_create_file("register-value", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_val_fops); + if (!ab5500_val_file) + goto exit_destroy_address; + + return; + +exit_destroy_address: + debugfs_remove(ab5500_address_file); +exit_destroy_bank: + debugfs_remove(ab5500_bank_file); +exit_destroy_reg: + debugfs_remove(ab5500_reg_file); +exit_destroy_dir: + debugfs_remove(ab5500_dir); +exit_no_debugfs: + dev_err(ab->dev, "failed to create debugfs entries.\n"); + return; +} + +static inline void ab5500_remove_debugfs(void) +{ + debugfs_remove(ab5500_val_file); + debugfs_remove(ab5500_address_file); + debugfs_remove(ab5500_bank_file); + debugfs_remove(ab5500_reg_file); + debugfs_remove(ab5500_dir); +} + +#else /* !CONFIG_DEBUG_FS */ +static inline void ab5500_setup_debugfs(struct ab5500 *ab) +{ +} +static inline void ab5500_remove_debugfs(void) +{ +} +#endif + +/* + * Basic set-up, datastructure creation/destruction and I2C interface. + * This sets up a default config in the AB5500 chip so that it + * will work as expected. + */ +static int __init ab5500_setup(struct ab5500 *ab, + struct abx500_init_settings *settings, unsigned int size) +{ + int err = 0; + int i; + + for (i = 0; i < size; i++) { + err = mask_and_set_register_interruptible(ab, + settings[i].bank, + settings[i].reg, + 0xFF, settings[i].setting); + if (err) + goto exit_no_setup; + + /* If event mask register update the event mask in ab5500 */ + if ((settings[i].bank == AB5500_BANK_IT) && + (AB5500_MASK_BASE <= settings[i].reg) && + (settings[i].reg <= AB5500_MASK_END)) { + ab->event_mask[settings[i].reg - AB5500_MASK_BASE] = + settings[i].setting; + } + } +exit_no_setup: + return err; +} + +static void ab5500_mask_work(struct work_struct *work) +{ + struct ab5500 *ab = container_of(work, struct ab5500, mask_work); + int i; + int err; + unsigned long flags; + u8 mask[AB5500_NUM_EVENT_REG]; + int call_prcmu_event_readout = 0; + + spin_lock_irqsave(&ab->event_lock, flags); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) + mask[i] = ab->event_mask[i]; + spin_unlock_irqrestore(&ab->event_lock, flags); + + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + if (mask[i] != ab->last_event_mask[i]) { + err = mask_and_set_register_interruptible(ab, 0, + (AB5500_MASK_BASE + i), ~0, mask[i]); + if (err) { + dev_err(ab->dev, + "ab5500_mask_work failed 0x%x,0x%x\n", + (AB5500_MASK_BASE + i), mask[i]); + break; + } + + if (mask[i] == 0xFF) { + ab->abb_events &= ~BIT(i); + call_prcmu_event_readout = 1; + } else { + ab->abb_events |= BIT(i); + if (ab->last_event_mask[i] == 0xFF) + call_prcmu_event_readout = 1; + } + + ab->last_event_mask[i] = mask[i]; + } + } + if (call_prcmu_event_readout) { + err = db5500_prcmu_config_abb_event_readout(ab->abb_events); + if (err) + dev_err(ab->dev, + "prcmu_config_abb_event_readout failed\n"); + } +} + +static void ab5500_mask(unsigned int irq) +{ + unsigned long flags; + struct ab5500 *ab; + + ab = get_irq_chip_data(irq); + irq -= ab->irq_base; + + spin_lock_irqsave(&ab->event_lock, flags); + ab->event_mask[irq / 8] |= BIT(irq % 8); + spin_unlock_irqrestore(&ab->event_lock, flags); + + schedule_work(&ab->mask_work); +} + +static void ab5500_unmask(unsigned int irq) +{ + unsigned long flags; + struct ab5500 *ab; + + ab = get_irq_chip_data(irq); + irq -= ab->irq_base; + + spin_lock_irqsave(&ab->event_lock, flags); + ab->event_mask[irq / 8] &= ~BIT(irq % 8); + spin_unlock_irqrestore(&ab->event_lock, flags); + + schedule_work(&ab->mask_work); +} + +static void noop(unsigned int irq) +{ +} + +static struct irq_chip ab5500_irq_chip = { + .name = "ab5500-core", /* Keep the same name as the request */ + .startup = NULL, /* defaults to enable */ + .shutdown = NULL, /* defaults to disable */ + .enable = NULL, /* defaults to unmask */ + .disable = ab5500_mask, /* No default to mask in chip.c */ + .ack = noop, + .mask = ab5500_mask, + .unmask = ab5500_unmask, + .end = NULL, +}; + +struct ab_family_id { + u8 id; + char *name; +}; + +static const struct ab_family_id ids[] __initdata = { + /* AB5500 */ + { + .id = AB5500_1_0, + .name = "1.0" + }, + /* Terminator */ + { + .id = 0x00, + } +}; + +static int __init ab5500_probe(struct platform_device *pdev) +{ + struct ab5500 *ab; + struct ab5500_platform_data *ab5500_plf_data = + pdev->dev.platform_data; + struct resource *res; + int err; + int i; + + ab = kzalloc(sizeof(struct ab5500), GFP_KERNEL); + if (!ab) { + dev_err(&pdev->dev, + "could not allocate " AB5500_NAME_STRING " device\n"); + return -ENOMEM; + } + + /* Initialize data structure */ + mutex_init(&ab->access_mutex); + spin_lock_init(&ab->event_lock); + ab->dev = &pdev->dev; + ab->irq_base = ab5500_plf_data->irq.base; + + platform_set_drvdata(pdev, ab); + + /* Read chip ID register */ + err = get_register_interruptible(ab, AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + AB5500_CHIP_ID, &ab->chip_id); + if (err) { + dev_err(&pdev->dev, "could not communicate with the analog " + "baseband chip\n"); + goto exit_no_detect; + } + + for (i = 0; ids[i].id != 0x0; i++) { + if (ids[i].id == ab->chip_id) { + snprintf(&ab->chip_name[0], sizeof(ab->chip_name) - 1, + AB5500_ID_FORMAT_STRING, ids[i].name); + break; + } + } + + if (ids[i].id == 0x0) { + dev_err(&pdev->dev, "unknown analog baseband chip id: 0x%x\n", + ab->chip_id); + dev_err(&pdev->dev, "driver not started!\n"); + goto exit_no_detect; + } + + dev_info(&pdev->dev, "detected AB chip: %s\n", &ab->chip_name[0]); + + /* Readout ab->starup_events when prcmu driver is in place */ + ab->startup_events[0] = 0; + + err = ab5500_setup(ab, ab5500_plf_data->init_settings, + ab5500_plf_data->init_settings_sz); + if (err) { + dev_err(&pdev->dev, "ab5500_setup error\n"); + goto exit_no_setup; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + INIT_WORK(&ab->irq_work, ab5500_irq_work); +#endif + INIT_WORK(&ab->mask_work, ab5500_mask_work); + + for (i = 0; i < ab5500_plf_data->irq.count; i++) { + unsigned int irq; + + irq = ab5500_plf_data->irq.base + i; + set_irq_chip_data(irq, ab); + set_irq_chip_and_handler(irq, &ab5500_irq_chip, + handle_simple_irq); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 29) + set_irq_nested_thread(irq, 1); +#endif + set_irq_flags(irq, IRQF_VALID); + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + + if (!res) { + dev_err(&pdev->dev, "ab5500_platform_get_resource error\n"); + goto exit_no_irq; + } + ab->ab5500_irq = res->start; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + /* This really unpredictable IRQ is of course sampled for entropy. */ + err = request_irq(res->start, ab5500_irq_handler, + (IRQF_DISABLED | IRQF_SAMPLE_RANDOM), "ab5500-core", ab); + if (err) { + dev_err(&pdev->dev, "ab5500_request_irq error\n"); + goto exit_no_irq; + } + + /* We probably already got an irq here, but if not, + * we force a first time and save the startup events here.*/ + disable_irq_nosync(res->start); + schedule_work(&ab->irq_work); +#else + err = request_threaded_irq(res->start, ab5500_irq_handler, NULL, + IRQF_SAMPLE_RANDOM, "ab5500-core", ab); + /* This real unpredictable IRQ is of course sampled for entropy */ + rand_initialize_irq(res->start); + + if (err) { + dev_err(&pdev->dev, "ab5500_request_irq error\n"); + goto exit_no_irq; + } +#endif + + err = abx500_register_ops(&pdev->dev, &ab5500_ops); + if (err) { + dev_err(&pdev->dev, "ab5500_register ops error\n"); + goto exit_no_ops; + } + + /* Set up and register the platform devices. */ + for (i = 0; i < AB5500_NUM_DEVICES; i++) { + ab5500_devs[i].platform_data = ab5500_plf_data->dev_data[i]; + ab5500_devs[i].data_size = ab5500_plf_data->dev_data_sz[i]; + } + + err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, + ARRAY_SIZE(ab5500_devs), NULL, + ab5500_plf_data->irq.base); + if (err) { + dev_err(&pdev->dev, "ab5500_mfd_add_device error\n"); + goto exit_no_ops; + } + + ab5500_setup_debugfs(ab); + + return 0; + +exit_no_ops: +exit_no_irq: +exit_no_setup: +exit_no_detect: + kfree(ab); + return err; +} + +static int __exit ab5500_remove(struct platform_device *pdev) +{ + struct ab5500 *ab = platform_get_drvdata(pdev); + struct resource *res; + + /* + * At this point, all subscribers should have unregistered + * their notifiers so deactivate IRQ + */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + free_irq(res->start, ab); + + mfd_remove_devices(&pdev->dev); + ab5500_remove_debugfs(); + + kfree(ab); + return 0; +} + +static struct platform_driver ab5500_driver = { + .driver = { + .name = "ab5500-core", + .owner = THIS_MODULE, + }, + .remove = __exit_p(ab5500_remove), +}; + +static int __init ab5500_core_init(void) +{ + return platform_driver_probe(&ab5500_driver, ab5500_probe); +} + +static void __exit ab5500_core_exit(void) +{ + platform_driver_unregister(&ab5500_driver); +} + +subsys_initcall(ab5500_core_init); +module_exit(ab5500_core_exit); + +MODULE_AUTHOR("Mattias Wallin "); +MODULE_DESCRIPTION("AB5500 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index fc0c1af1566..b14441c4694 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -264,7 +264,6 @@ static void ab8500_irq_sync_unlock(struct irq_data *data) reg = AB8500_IT_MASK1_REG + ab8500_irq_regoffset[i]; set_register_interruptible(ab8500, AB8500_INTERRUPT, reg, new); } - mutex_unlock(&ab8500->irq_lock); } @@ -325,7 +324,6 @@ static irqreturn_t ab8500_irq(int irq, void *dev) value &= ~(1 << bit); } while (value); } - return IRQ_HANDLED; } @@ -417,19 +415,7 @@ static struct resource ab8500_poweronkey_db_resources[] = { }, }; -static struct resource ab8500_bm_resources[] = { - { - .name = "MAIN_EXT_CH_NOT_OK", - .start = AB8500_INT_MAIN_EXT_CH_NOT_OK, - .end = AB8500_INT_MAIN_EXT_CH_NOT_OK, - .flags = IORESOURCE_IRQ, - }, - { - .name = "BATT_OVV", - .start = AB8500_INT_BATT_OVV, - .end = AB8500_INT_BATT_OVV, - .flags = IORESOURCE_IRQ, - }, +static struct resource ab8500_charger_resources[] = { { .name = "MAIN_CH_UNPLUG_DET", .start = AB8500_INT_MAIN_CH_UNPLUG_DET, @@ -442,12 +428,6 @@ static struct resource ab8500_bm_resources[] = { .end = AB8500_INT_MAIN_CH_PLUG_DET, .flags = IORESOURCE_IRQ, }, - { - .name = "VBUS_DET_F", - .start = AB8500_INT_VBUS_DET_F, - .end = AB8500_INT_VBUS_DET_F, - .flags = IORESOURCE_IRQ, - }, { .name = "VBUS_DET_R", .start = AB8500_INT_VBUS_DET_R, @@ -455,15 +435,21 @@ static struct resource ab8500_bm_resources[] = { .flags = IORESOURCE_IRQ, }, { - .name = "BAT_CTRL_INDB", - .start = AB8500_INT_BAT_CTRL_INDB, - .end = AB8500_INT_BAT_CTRL_INDB, + .name = "VBUS_DET_F", + .start = AB8500_INT_VBUS_DET_F, + .end = AB8500_INT_VBUS_DET_F, .flags = IORESOURCE_IRQ, }, { - .name = "CH_WD_EXP", - .start = AB8500_INT_CH_WD_EXP, - .end = AB8500_INT_CH_WD_EXP, + .name = "USB_LINK_STATUS", + .start = AB8500_INT_USB_LINK_STATUS, + .end = AB8500_INT_USB_LINK_STATUS, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB_CHARGE_DET_DONE", + .start = AB8500_INT_USB_CHG_DET_DONE, + .end = AB8500_INT_USB_CHG_DET_DONE, .flags = IORESOURCE_IRQ, }, { @@ -473,21 +459,60 @@ static struct resource ab8500_bm_resources[] = { .flags = IORESOURCE_IRQ, }, { - .name = "NCONV_ACCU", - .start = AB8500_INT_CCN_CONV_ACC, - .end = AB8500_INT_CCN_CONV_ACC, + .name = "USB_CH_TH_PROT_R", + .start = AB8500_INT_USB_CH_TH_PROT_R, + .end = AB8500_INT_USB_CH_TH_PROT_R, .flags = IORESOURCE_IRQ, }, { - .name = "LOW_BAT_F", - .start = AB8500_INT_LOW_BAT_F, - .end = AB8500_INT_LOW_BAT_F, + .name = "USB_CH_TH_PROT_F", + .start = AB8500_INT_USB_CH_TH_PROT_F, + .end = AB8500_INT_USB_CH_TH_PROT_F, .flags = IORESOURCE_IRQ, }, { - .name = "LOW_BAT_R", - .start = AB8500_INT_LOW_BAT_R, - .end = AB8500_INT_LOW_BAT_R, + .name = "MAIN_EXT_CH_NOT_OK", + .start = AB8500_INT_MAIN_EXT_CH_NOT_OK, + .end = AB8500_INT_MAIN_EXT_CH_NOT_OK, + .flags = IORESOURCE_IRQ, + }, + { + .name = "MAIN_CH_TH_PROT_R", + .start = AB8500_INT_MAIN_CH_TH_PROT_R, + .end = AB8500_INT_MAIN_CH_TH_PROT_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "MAIN_CH_TH_PROT_F", + .start = AB8500_INT_MAIN_CH_TH_PROT_F, + .end = AB8500_INT_MAIN_CH_TH_PROT_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB_CHARGER_NOT_OKR", + .start = AB8500_INT_USB_CHARGER_NOT_OK, + .end = AB8500_INT_USB_CHARGER_NOT_OK, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB_CHARGER_NOT_OKF", + .start = AB8500_INT_USB_CHARGER_NOT_OKF, + .end = AB8500_INT_USB_CHARGER_NOT_OKF, + .flags = IORESOURCE_IRQ, + }, + { + .name = "CH_WD_EXP", + .start = AB8500_INT_CH_WD_EXP, + .end = AB8500_INT_CH_WD_EXP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource ab8500_btemp_resources[] = { + { + .name = "BAT_CTRL_INDB", + .start = AB8500_INT_BAT_CTRL_INDB, + .end = AB8500_INT_BAT_CTRL_INDB, .flags = IORESOURCE_IRQ, }, { @@ -503,37 +528,48 @@ static struct resource ab8500_bm_resources[] = { .flags = IORESOURCE_IRQ, }, { - .name = "USB_CHARGER_NOT_OKR", - .start = AB8500_INT_USB_CHARGER_NOT_OK, - .end = AB8500_INT_USB_CHARGER_NOT_OK, + .name = "BTEMP_LOW_MEDIUM", + .start = AB8500_INT_BTEMP_LOW_MEDIUM, + .end = AB8500_INT_BTEMP_LOW_MEDIUM, .flags = IORESOURCE_IRQ, }, { - .name = "USB_CHARGE_DET_DONE", - .start = AB8500_INT_USB_CHG_DET_DONE, - .end = AB8500_INT_USB_CHG_DET_DONE, + .name = "BTEMP_MEDIUM_HIGH", + .start = AB8500_INT_BTEMP_MEDIUM_HIGH, + .end = AB8500_INT_BTEMP_MEDIUM_HIGH, .flags = IORESOURCE_IRQ, }, +}; + +static struct resource ab8500_fg_resources[] = { { - .name = "USB_CH_TH_PROT_R", - .start = AB8500_INT_USB_CH_TH_PROT_R, - .end = AB8500_INT_USB_CH_TH_PROT_R, + .name = "NCONV_ACCU", + .start = AB8500_INT_CCN_CONV_ACC, + .end = AB8500_INT_CCN_CONV_ACC, .flags = IORESOURCE_IRQ, }, { - .name = "MAIN_CH_TH_PROT_R", - .start = AB8500_INT_MAIN_CH_TH_PROT_R, - .end = AB8500_INT_MAIN_CH_TH_PROT_R, + .name = "BATT_OVV", + .start = AB8500_INT_BATT_OVV, + .end = AB8500_INT_BATT_OVV, .flags = IORESOURCE_IRQ, }, { - .name = "USB_CHARGER_NOT_OKF", - .start = AB8500_INT_USB_CHARGER_NOT_OKF, - .end = AB8500_INT_USB_CHARGER_NOT_OKF, + .name = "LOW_BAT_F", + .start = AB8500_INT_LOW_BAT_F, + .end = AB8500_INT_LOW_BAT_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "LOW_BAT_R", + .start = AB8500_INT_LOW_BAT_R, + .end = AB8500_INT_LOW_BAT_R, .flags = IORESOURCE_IRQ, }, }; +static struct resource ab8500_chargalg_resources[] = {}; + static struct resource ab8500_debug_resources[] = { { .name = "IRQ_FIRST", @@ -580,6 +616,18 @@ static struct resource ab8500_usb_resources[] = { .end = AB8500_INT_USB_LINK_STATUS, .flags = IORESOURCE_IRQ, }, + { + .name = "USB_ADP_PROBE_PLUG", + .start = AB8500_INT_ADP_PROBE_PLUG, + .end = AB8500_INT_ADP_PROBE_PLUG, + .flags = IORESOURCE_IRQ, + }, + { + .name = "USB_ADP_PROBE_UNPLUG", + .start = AB8500_INT_ADP_PROBE_UNPLUG, + .end = AB8500_INT_ADP_PROBE_UNPLUG, + .flags = IORESOURCE_IRQ, + }, }; static struct resource ab8500_temp_resources[] = { @@ -591,6 +639,45 @@ static struct resource ab8500_temp_resources[] = { }, }; +static struct resource ab8500_codec_resources[] = { + { + .name = "ACC_DETECT_1DB_F", + .start = AB8500_INT_ACC_DETECT_1DB_F, + .end = AB8500_INT_ACC_DETECT_1DB_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ACC_DETECT_1DB_R", + .start = AB8500_INT_ACC_DETECT_1DB_R, + .end = AB8500_INT_ACC_DETECT_1DB_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ACC_DETECT_22DB_F", + .start = AB8500_INT_ACC_DETECT_22DB_F, + .end = AB8500_INT_ACC_DETECT_22DB_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ACC_DETECT_22DB_R", + .start = AB8500_INT_ACC_DETECT_22DB_R, + .end = AB8500_INT_ACC_DETECT_22DB_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ACC_DETECT_21DB_F", + .start = AB8500_INT_ACC_DETECT_21DB_F, + .end = AB8500_INT_ACC_DETECT_21DB_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ACC_DETECT_21DB_R", + .start = AB8500_INT_ACC_DETECT_21DB_R, + .end = AB8500_INT_ACC_DETECT_21DB_R, + .flags = IORESOURCE_IRQ, + } +}; + static struct mfd_cell ab8500_devs[] = { #ifdef CONFIG_DEBUG_FS { @@ -605,6 +692,9 @@ static struct mfd_cell ab8500_devs[] = { { .name = "ab8500-regulator", }, + { + .name = "ab8500-regulator-debug", + }, { .name = "ab8500-gpio", .num_resources = ARRAY_SIZE(ab8500_gpio_resources), @@ -621,11 +711,30 @@ static struct mfd_cell ab8500_devs[] = { .resources = ab8500_rtc_resources, }, { - .name = "ab8500-bm", - .num_resources = ARRAY_SIZE(ab8500_bm_resources), - .resources = ab8500_bm_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-codec", + .num_resources = ARRAY_SIZE(ab8500_codec_resources), + .resources = ab8500_codec_resources, }, - { .name = "ab8500-codec", }, { .name = "ab8500-usb", .num_resources = ARRAY_SIZE(ab8500_usb_resources), diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 64748e42ac0..c591901ceec 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include @@ -17,6 +20,13 @@ 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 @@ -49,7 +59,7 @@ struct ab8500_i2c_ranges { 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, @@ -353,6 +363,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 + */ + 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; @@ -533,6 +561,151 @@ static ssize_t ab8500_val_write(struct file *file, printk(KERN_ERR "abx500_set_reg failed %d, %d", err, __LINE__); return -EINVAL; } + return buf_size; +} + +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 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 buf_size; } @@ -564,17 +737,51 @@ 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 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_subscribe_file; +static struct dentry *ab8500_unsubscribe_file; static int __devinit ab8500_debug_probe(struct platform_device *plf) { 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; @@ -585,23 +792,38 @@ static int __devinit ab8500_debug_probe(struct platform_device *plf) goto exit_destroy_dir; ab8500_bank_file = debugfs_create_file("register-bank", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_bank_fops); + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_bank_fops); if (!ab8500_bank_file) goto exit_destroy_reg; ab8500_address_file = debugfs_create_file("register-address", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_address_fops); if (!ab8500_address_file) goto exit_destroy_bank; ab8500_val_file = debugfs_create_file("register-value", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_val_fops); + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_val_fops); if (!ab8500_val_file) goto exit_destroy_address; + ab8500_subscribe_file = debugfs_create_file("irq-subscribe", + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, + &ab8500_subscribe_fops); + if (!ab8500_subscribe_file) + goto exit_destroy_val; + + ab8500_unsubscribe_file = debugfs_create_file("irq-unsubscribe", + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, + &ab8500_unsubscribe_fops); + if (!ab8500_unsubscribe_file) + goto exit_destroy_subscribe; return 0; +exit_destroy_subscribe: + debugfs_remove(ab8500_subscribe_file); +exit_destroy_val: + debugfs_remove(ab8500_val_file); exit_destroy_address: debugfs_remove(ab8500_address_file); exit_destroy_bank: diff --git a/drivers/mfd/ab8500-denc.c b/drivers/mfd/ab8500-denc.c new file mode 100644 index 00000000000..06e4b282cbf --- /dev/null +++ b/drivers/mfd/ab8500-denc.c @@ -0,0 +1,515 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * ST-Ericsson AB8500 DENC base driver + * + * Author: Marcel Tunnissen + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +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" + "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" + "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" + , + 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) + ); + 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 "); +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 f16afb234ff..027d5205f23 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -57,7 +57,7 @@ #define SW_AVG_16 0x60 #define ADC_SW_CONV 0x04 #define EN_ICHAR 0x80 -#define BTEMP_PULL_UP 0x08 +#define BATTEMP_PULL_UP 0x04 #define EN_BUF 0x40 #define DIS_ZERO 0x00 #define GPADC_BUSY 0x01 @@ -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); diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index 392185965b3..e6de6a279a5 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -6,12 +6,29 @@ #include #include +#include +#include #include #include #include static struct device *sysctrl_dev; +void ab8500_power_off(void) +{ + sigset_t old; + sigset_t all; + + sigfillset(&all); + + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } +} + static inline bool valid_bank(u8 bank) { return ((bank == AB8500_SYS_CTRL1_BLOCK) || @@ -50,7 +67,12 @@ int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) static int __devinit ab8500_sysctrl_probe(struct platform_device *pdev) { + struct ab8500_platform_data *plat; + sysctrl_dev = &pdev->dev; + plat = dev_get_platdata(pdev->dev.parent); + if (plat->pm_power_off) + pm_power_off = ab8500_power_off; return 0; } diff --git a/drivers/misc/ab8500-pwm.c b/drivers/misc/ab8500-pwm.c index 54e3d05b63c..cac4051ce2e 100644 --- a/drivers/misc/ab8500-pwm.c +++ b/drivers/misc/ab8500-pwm.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -26,8 +27,10 @@ struct pwm_device { struct device *dev; struct list_head node; + struct clk *clk; const char *label; unsigned int pwm_id; + bool clk_enabled; }; static LIST_HEAD(pwm_list); @@ -66,9 +69,17 @@ int pwm_enable(struct pwm_device *pwm) { int ret; + if (!pwm->clk_enabled) { + ret = clk_enable(pwm->clk); + if (ret < 0) { + dev_err(pwm->dev, "failed to enable sysclk\n"); + return ret; + } + pwm->clk_enabled = true; + } ret = abx500_mask_and_set_register_interruptible(pwm->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, - 1 << (pwm->pwm_id-1), ENABLE_PWM); + 1 << (pwm->pwm_id-1), 1 << (pwm->pwm_id-1)); if (ret < 0) dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", pwm->label, ret); @@ -83,9 +94,27 @@ void pwm_disable(struct pwm_device *pwm) ret = abx500_mask_and_set_register_interruptible(pwm->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, 1 << (pwm->pwm_id-1), DISABLE_PWM); + /* + * Workaround to set PWM in disable. + * If enable bit is not toggled the PWM might output 50/50 duty cycle + * even though it should be disabled + */ + ret &= abx500_mask_and_set_register_interruptible(pwm->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (pwm->pwm_id-1), + ENABLE_PWM << (pwm->pwm_id-1)); + ret &= abx500_mask_and_set_register_interruptible(pwm->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (pwm->pwm_id-1), DISABLE_PWM); + if (ret < 0) dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", pwm->label, ret); + if (pwm->clk_enabled) { + clk_disable(pwm->clk); + pwm->clk_enabled = false; + } + return; } EXPORT_SYMBOL(pwm_disable); @@ -115,6 +144,8 @@ EXPORT_SYMBOL(pwm_free); static int __devinit ab8500_pwm_probe(struct platform_device *pdev) { struct pwm_device *pwm; + int ret = 0; + /* * Nothing to be done in probe, this is required to get the * device which is required for ab8500 read and write @@ -128,14 +159,24 @@ static int __devinit ab8500_pwm_probe(struct platform_device *pdev) pwm->pwm_id = pdev->id; list_add_tail(&pwm->node, &pwm_list); platform_set_drvdata(pdev, pwm); + + pwm->clk = clk_get(pwm->dev, "sysclk"); + if (IS_ERR(pwm->clk)) { + dev_err(pwm->dev, "clock request failed\n"); + ret = PTR_ERR(pwm->clk); + kfree(pwm); + return ret; + } + pwm->clk_enabled = false; dev_dbg(pwm->dev, "pwm probe successful\n"); - return 0; + return ret; } static int __devexit ab8500_pwm_remove(struct platform_device *pdev) { struct pwm_device *pwm = platform_get_drvdata(pdev); list_del(&pwm->node); + clk_put(pwm->clk); dev_dbg(&pdev->dev, "pwm driver removed\n"); kfree(pwm); return 0; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e57b50b3856..55a0135b990 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -235,4 +235,17 @@ config CHARGER_GPIO This driver can be build as a module. If so, the module will be called gpio-charger. +config AB8500_BM + bool "AB8500 Battery Management Driver" + depends on AB8500_CORE && AB8500_GPADC && ARCH_U8500 + help + Say Y to include support for AB8500 battery management. + +config AB8500_BATTERY_THERM_ON_BATCTRL + bool "Thermistor connected on BATCTRL ADC" + depends on AB8500_BM + help + Say Y to enable battery temperature measurements using + thermistor connected on BATCTRL ADC. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 009a90fa8ac..3eb466f6f7e 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -36,3 +36,4 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o ab8500_chargalg.o diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c new file mode 100644 index 00000000000..7d204b19742 --- /dev/null +++ b/drivers/power/ab8500_btemp.c @@ -0,0 +1,1080 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Battery temperature driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + * Author: Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VTVOUT_V 1800 + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_52 52 +#define BTEMP_THERMAL_HIGH_LIMIT_57 57 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define to_ab8500_btemp_device_info(x) container_of((x), \ + struct ab8500_btemp, btemp_psy); + +/** + * struct ab8500_btemp_interrupts - ab8500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_btemp_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_btemp_events { + bool batt_rem; + bool btemp_high; + bool btemp_medhigh; + bool btemp_lowmed; + bool btemp_low; + bool ac_conn; + bool usb_conn; +}; + +struct ab8500_btemp_ranges { + int btemp_high_limit; + int btemp_med_limit; + int btemp_low_limit; +}; + +/** + * struct ab8500_btemp - ab8500 BTEMP device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the AB8500 + * @curr_source: What current source we use, in uA + * @bat_temp: Battery temperature in degree Celcius + * @prev_bat_temp Last dispatched battery temperature + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_btemp platform data + * @bat: Pointer to the ab8500_bm platform data + * @btemp_psy: Structure for BTEMP specific battery properties + * @events: Structure for information about events triggered + * @btemp_ranges: Battery temperature range structure + * @btemp_wq: Work queue for measuring the temperature periodically + * @btemp_periodic_work: Work for measuring the temperature periodically + */ +struct ab8500_btemp { + struct device *dev; + u8 chip_id; + int curr_source; + int bat_temp; + int prev_bat_temp; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_btemp_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply btemp_psy; + struct ab8500_btemp_events events; + struct ab8500_btemp_ranges btemp_ranges; + struct workqueue_struct *btemp_wq; + struct delayed_work btemp_periodic_work; +}; + +/* BTEMP power supply properties */ +static enum power_supply_property ab8500_btemp_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, +}; + +/** + * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance + * @di: pointer to the ab8500_btemp structure + * @v_batctrl: measured batctrl voltage + * + * This function returns the battery resistance that is + * derived from the BATCTRL voltage. + * Returns value in Ohms. + */ +static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, + int v_batctrl) +{ + int rbs; + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + /* + * For ABB cut1.0 and 1.1 BAT_CTRL is internally + * connected to 1.8V through a 450k resistor + */ + rbs = (450000 * (v_batctrl)) / (1800 - v_batctrl); + break; + default: + if (di->bat->adc_therm == ADC_THERM_BATCTRL) { + /* + * If the battery has internal NTC, we use the current + * source to calculate the resistance, 7uA or 20uA + */ + rbs = v_batctrl * 1000 / di->curr_source; + } else { + /* + * BAT_CTRL is internally + * connected to 1.8V through a 80k resistor + */ + rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl); + } + break; + } + + return rbs; +} + +/** + * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage + * @di: pointer to the ab8500_btemp structure + * + * This function returns the voltage on BATCTRL. Returns value in mV. + */ +static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di) +{ + int vbtemp; + static int prev; + + vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL); + if (vbtemp < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value", + __func__); + return prev; + } + prev = vbtemp; + return vbtemp; +} + +/** + * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable the current source + * + * Enable or disable the current sources for the BatCtrl AD channel + */ +static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, + bool enable) +{ + int curr; + int ret = 0; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + if (di->chip_id == AB8500_CUT1P0 || di->chip_id == AB8500_CUT1P1) + return 0; + + /* Only do this for batteries with internal NTC */ + if (di->bat->adc_therm == ADC_THERM_BATCTRL && enable) { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA) + curr = BAT_CTRL_7U_ENA; + else + curr = BAT_CTRL_20U_ENA; + + dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed setting cmp_force\n", + __func__); + return ret; + } + + /* + * We have to wait one 32kHz cycle before enabling + * the current source, since ForceBatCtrlCmpHigh needs + * to be written in a separate cycle + */ + udelay(32); + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH | curr); + if (ret) { + dev_err(di->dev, "%s failed enabling current source\n", + __func__); + goto disable_curr_source; + } + } else if (di->bat->adc_therm == ADC_THERM_BATCTRL && !enable) { + dev_dbg(di->dev, "Disable BATCTRL curr source\n"); + + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + goto disable_curr_source; + } + + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + goto enable_pu_comp; + } + + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + goto disable_force_comp; + } + } + return ret; + + /* + * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time + * if we got an error above + */ +disable_curr_source: + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + return ret; + } +enable_pu_comp: + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + return ret; + } + +disable_force_comp: + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + return ret; + } + + return ret; +} + +/** + * ab8500_btemp_get_batctrl_res() - get battery resistance + * @di: pointer to the ab8500_btemp structure + * + * This function returns the battery pack identification resistance. + * Returns value in Ohms. + */ +static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) +{ + int ret; + int batctrl; + int res; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + ret = ab8500_btemp_curr_source_enable(di, true); + if (ret) { + dev_err(di->dev, "%s curr source enabled failed\n", __func__); + return ret; + } + + batctrl = ab8500_btemp_read_batctrl_voltage(di); + res = ab8500_btemp_batctrl_volt_to_res(di, batctrl); + + ret = ab8500_btemp_curr_source_enable(di, false); + if (ret) { + dev_err(di->dev, "%s curr source disable failed\n", __func__); + return ret; + } + + dev_dbg(di->dev, "%s batctrl: %d res: %d ", + __func__, batctrl, res); + + return res; +} + +/** + * ab8500_btemp_res_to_temp() - resistance to temperature + * @di: pointer to the ab8500_btemp structure + * @tbl: pointer to the resiatance to temperature table + * @tbl_size: size of the resistance to temperature table + * @res: resistance to calculate the temperature from + * + * This function returns the battery temperature in degrees Celcius + * based on the NTC resistance. + */ +static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di, + const struct res_to_temp *tbl, int tbl_size, int res) +{ + int i, temp; + /* + * Calculate the formula for the straight line + * Simple interpolation if we are within + * the resistance table limits, extrapolate + * if resistance is outside the limits. + */ + if (res > tbl[0].resist) + i = 0; + else if (res <= tbl[tbl_size - 1].resist) + i = tbl_size - 2; + else { + i = 0; + while (!(res <= tbl[i].resist && + res > tbl[i + 1].resist)) + i++; + } + + temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * + (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + return temp; +} + +/** + * ab8500_btemp_measure_temp() - measure battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature (on success) else the previous temperature + */ +static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) +{ + int temp; + static int prev; + int rbat, rntc, vntc; + u8 id; + + id = di->bat->batt_id; + + if (di->bat->adc_therm == ADC_THERM_BATCTRL && + id != BATTERY_UNKNOWN) { + + rbat = ab8500_btemp_get_batctrl_res(di); + if (rbat < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", + __func__); + /* + * Return out-of-range temperature so that + * charging is stopped + */ + return BTEMP_THERMAL_LOW_LIMIT; + } + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rbat); + } else { + vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL); + if (vntc < 0) { + dev_err(di->dev, + "%s gpadc conversion failed," + " using previous value\n", __func__); + return prev; + } + /* + * The PCB NTC is sourced from VTVOUT via a 230kOhm + * resistor. + */ + rntc = 230000 * vntc / (VTVOUT_V - vntc); + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rntc); + prev = temp; + } + dev_dbg(di->dev, "Battery temperature is %d\n", temp); + return temp; +} + +/** + * ab8500_btemp_id() - Identify the connected battery + * @di: pointer to the ab8500_btemp structure + * + * This function will try to identify the battery by reading the ID + * resistor. Some brands use a combined ID resistor with a NTC resistor to + * both be able to identify and to read the temperature of it. + */ +static int ab8500_btemp_id(struct ab8500_btemp *di) +{ + int res; + u8 i; + + di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; + di->bat->batt_id = BATTERY_UNKNOWN; + + res = ab8500_btemp_get_batctrl_res(di); + if (res < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", __func__); + return -ENXIO; + } + + /* BATTERY_UNKNOWN is defined on position 0, skip it! */ + for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) { + if ((res <= di->bat->bat_type[i].resis_high) && + (res >= di->bat->bat_type[i].resis_low)) { + dev_dbg(di->dev, "Battery detected on %s" + " low %d < res %d < high: %d" + " index: %d\n", + di->bat->adc_therm == ADC_THERM_BATCTRL ? + "BATCTRL" : "BATTEMP", + di->bat->bat_type[i].resis_low, res, + di->bat->bat_type[i].resis_high, i); + + di->bat->batt_id = i; + break; + } + } + + if (di->bat->batt_id == BATTERY_UNKNOWN) { + dev_warn(di->dev, "Battery identified as unknown" + ", resistance %d Ohm\n", res); + return -ENXIO; + } + + /* + * We only have to change current source if the + * detected type is Type 1, else we use the 7uA source + */ + if (di->bat->adc_therm == ADC_THERM_BATCTRL && di->bat->batt_id == 1) { + dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA; + } + + return di->bat->batt_id; +} + +/** + * ab8500_btemp_periodic_work() - Measuring the temperature periodically + * @work: pointer to the work_struct structure + * + * Work function for measuring the temperature periodically + */ +static void ab8500_btemp_periodic_work(struct work_struct *work) +{ + struct ab8500_btemp *di = container_of(work, + struct ab8500_btemp, btemp_periodic_work.work); + + di->bat_temp = ab8500_btemp_measure_temp(di); + + if (di->bat_temp != di->prev_bat_temp) { + di->prev_bat_temp = di->bat_temp; + power_supply_changed(&di->btemp_psy); + } + + /* Schedule a new measurement */ + queue_delayed_work(di->btemp_wq, + &di->btemp_periodic_work, + round_jiffies(20 * HZ)); +} + +/** + * ab8500_btemp_batctrlindb_handler() - battery removal detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + dev_err(di->dev, "Battery removal detected!\n"); + + di->events.batt_rem = true; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + case AB8500_CUT2P0: + dev_dbg(di->dev, "Ignore false btemp low irq" + " for ABB cut 1.0, 1.1 and 2.0\n"); + + break; + default: + dev_crit(di->dev, "Battery temperature lower than -10deg c\n"); + + di->events.btemp_low = true; + di->events.btemp_high = false; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + power_supply_changed(&di->btemp_psy); + + break; + } + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_temphigh_handler() - battery temp higher than max temp + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_crit(di->dev, "Battery temperature is higher than MAX temp\n"); + + di->events.btemp_high = true; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_lowmed_handler() - battery temp between low and medium + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between low and medium\n"); + + di->events.btemp_lowmed = true; + di->events.btemp_medhigh = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_medhigh_handler() - battery temp between medium and high + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between medium and high\n"); + + di->events.btemp_medhigh = true; + di->events.btemp_lowmed = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_periodic() - Periodic temperature measurements + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable periodic temperature measurements + * + * Starts of stops periodic temperature measurements. Periodic measurements + * should only be done when a charger is connected. + */ +static void ab8500_btemp_periodic(struct ab8500_btemp *di, + bool enable) +{ + dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", + enable); + + if (enable) + queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); + else + cancel_delayed_work_sync(&di->btemp_periodic_work); +} + +/** + * ab8500_btemp_get_temp() - get battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature + */ +static int ab8500_btemp_get_temp(struct ab8500_btemp *di) +{ + int temp = 0; + + /* + * The BTEMP events are not reliabe on AB8500 cut2.0 + * and prior versions + */ + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + case AB8500_CUT2P0: + temp = di->bat_temp * 10; + + break; + default: + if (di->events.btemp_low) { + if (temp > di->btemp_ranges.btemp_low_limit) + temp = di->btemp_ranges.btemp_low_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_high) { + if (temp < di->btemp_ranges.btemp_high_limit) + temp = di->btemp_ranges.btemp_high_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_lowmed) { + if (temp > di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_medhigh) { + if (temp < di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else + temp = di->bat_temp * 10; + + break; + } + return temp; +} + +/** + * ab8500_btemp_get_property() - get the btemp properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the btemp + * properties by reading the sysfs files. + * online: presence of the battery + * present: presence of the battery + * technology: battery technology + * temp: battery temperature + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_btemp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_btemp *di; + + di = to_ab8500_btemp_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + if (di->events.batt_rem) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = di->bat->bat_type[di->bat->batt_id].name; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = ab8500_btemp_get_temp(di); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_btemp *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_btemp_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && di->events.ac_conn) { + di->events.ac_conn = false; + if (!di->events.usb_conn) + ab8500_btemp_periodic(di, + false); + } + /* AC connected */ + else if (ret.intval && !di->events.ac_conn) { + di->events.ac_conn = true; + if (!di->events.usb_conn) + ab8500_btemp_periodic(di, true); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && di->events.usb_conn) { + di->events.usb_conn = false; + if (!di->events.ac_conn) + ab8500_btemp_periodic(di, + false); + } + /* USB connected */ + else if (ret.intval && !di->events.usb_conn) { + di->events.usb_conn = true; + if (!di->events.ac_conn) + ab8500_btemp_periodic(di, true); + } + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_btemp_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is pointing to the function pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in the external power + * supply to the btemp. + */ +static void ab8500_btemp_external_power_changed(struct power_supply *psy) +{ + struct ab8500_btemp *di = to_ab8500_btemp_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->btemp_psy, ab8500_btemp_get_ext_psy_data); +} + +/* ab8500 btemp driver interrupts and their respective isr */ +static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = { + {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler}, + {"BTEMP_LOW", ab8500_btemp_templow_handler}, + {"BTEMP_HIGH", ab8500_btemp_temphigh_handler}, + {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler}, + {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler}, +}; + +#if defined(CONFIG_PM) +static int ab8500_btemp_resume(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.ac_conn || di->events.usb_conn) + ab8500_btemp_periodic(di, true); + + return 0; +} + +static int ab8500_btemp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.ac_conn || di->events.usb_conn) + ab8500_btemp_periodic(di, false); + + return 0; +} +#else +#define ab8500_btemp_suspend NULL +#define ab8500_btemp_resume NULL +#endif + +static int __devexit ab8500_btemp_remove(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->btemp_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_btemp_probe(struct platform_device *pdev) +{ + int irq, i, ret = 0; + u8 val; + struct ab8500_platform_data *plat; + + struct ab8500_btemp *di = + kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + plat = dev_get_platdata(di->parent->dev); + + /* get btemp specific platform data */ + if (!plat->btemp) { + dev_err(di->dev, "no btemp platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->btemp; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* BTEMP supply */ + di->btemp_psy.name = "ab8500_btemp"; + di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->btemp_psy.properties = ab8500_btemp_props; + di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props); + di->btemp_psy.get_property = ab8500_btemp_get_property; + di->btemp_psy.supplied_to = di->pdata->supplied_to; + di->btemp_psy.num_supplicants = di->pdata->num_supplicants; + di->btemp_psy.external_power_changed = + ab8500_btemp_external_power_changed; + + + /* Create a work queue for the btemp */ + di->btemp_wq = + create_singlethread_workqueue("ab8500_btemp_wq"); + if (di->btemp_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for measuring temperature periodically */ + INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work, + ab8500_btemp_periodic_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_btemp_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB8500 CID is: 0x%02x\n", + di->chip_id); + + /* Identify the battery */ + if (ab8500_btemp_id(di) < 0) + dev_warn(di->dev, "failed to identify the battery\n"); + + /* Set BTEMP thermal limits. Low and Med are fixed */ + di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT; + di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_BTEMP_HIGH_TH, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + goto free_btemp_wq; + } + switch (val) { + case BTEMP_HIGH_TH_57_0: + case BTEMP_HIGH_TH_57_1: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_57; + break; + case BTEMP_HIGH_TH_52: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_52; + break; + case BTEMP_HIGH_TH_62: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_62; + break; + } + + /* Measure temperature once initially */ + di->bat_temp = ab8500_btemp_measure_temp(di); + + /* Register BTEMP power supply class */ + ret = power_supply_register(di->dev, &di->btemp_psy); + if (ret) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + goto free_btemp_wq; + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_btemp_irq[i].name, di); + + if (ret) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_btemp_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_btemp_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + return ret; + +free_irq: + power_supply_unregister(&di->btemp_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } +free_btemp_wq: + destroy_workqueue(di->btemp_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_btemp_driver = { + .probe = ab8500_btemp_probe, + .remove = __devexit_p(ab8500_btemp_remove), + .suspend = ab8500_btemp_suspend, + .resume = ab8500_btemp_resume, + .driver = { + .name = "ab8500-btemp", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_btemp_init(void) +{ + return platform_driver_register(&ab8500_btemp_driver); +} + +static void __exit ab8500_btemp_exit(void) +{ + platform_driver_unregister(&ab8500_btemp_driver); +} + +subsys_initcall_sync(ab8500_btemp_init); +module_exit(ab8500_btemp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-btemp"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c new file mode 100644 index 00000000000..2137f467064 --- /dev/null +++ b/drivers/power/ab8500_chargalg.c @@ -0,0 +1,1833 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Charging algorithm driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (60 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +#define to_ab8500_chargalg_device_info(x) container_of((x), \ + struct ab8500_chargalg, chargalg_psy); + +enum ab8500_chargers { + NO_CHG, + AC_CHG, + USB_CHG, +}; + +struct ab8500_chargalg_charger_info { + enum ab8500_chargers conn_chg; + enum ab8500_chargers prev_conn_chg; + enum ab8500_chargers online_chg; + enum ab8500_chargers prev_online_chg; + enum ab8500_chargers charger_type; + bool usb_chg_ok; + bool ac_chg_ok; + int usb_volt; + int usb_curr; + int ac_volt; + int ac_curr; +}; + +struct ab8500_chargalg_suspension_status { + bool suspended_change; + bool ac_suspended; + bool usb_suspended; +}; + +struct ab8500_chargalg_battery_data { + int temp; + int volt; + int avg_curr; + int inst_curr; + int percent; +}; + +enum ab8500_chargalg_states { + STATE_HANDHELD_INIT, + STATE_HANDHELD, + STATE_CHG_NOT_OK_INIT, + STATE_CHG_NOT_OK, + STATE_HW_TEMP_PROTECT_INIT, + STATE_HW_TEMP_PROTECT, + STATE_NORMAL_INIT, + STATE_NORMAL, + STATE_MAINTENANCE_A_INIT, + STATE_MAINTENANCE_A, + STATE_MAINTENANCE_B_INIT, + STATE_MAINTENANCE_B, + STATE_TEMP_UNDEROVER_INIT, + STATE_TEMP_UNDEROVER, + STATE_TEMP_LOWHIGH_INIT, + STATE_TEMP_LOWHIGH, + STATE_SUSPENDED_INIT, + STATE_SUSPENDED, + STATE_OVV_PROTECT_INIT, + STATE_OVV_PROTECT, + STATE_SAFETY_TIMER_EXPIRED_INIT, + STATE_SAFETY_TIMER_EXPIRED, + STATE_BATT_REMOVED_INIT, + STATE_BATT_REMOVED, + STATE_WD_EXPIRED_INIT, + STATE_WD_EXPIRED, +}; + +static const char *states[] = { + "HANDHELD_INIT", + "HANDHELD", + "CHG_NOT_OK_INIT", + "CHG_NOT_OK", + "HW_TEMP_PROTECT_INIT", + "HW_TEMP_PROTECT", + "NORMAL_INIT", + "NORMAL", + "MAINTENANCE_A_INIT", + "MAINTENANCE_A", + "MAINTENANCE_B_INIT", + "MAINTENANCE_B", + "TEMP_UNDEROVER_INIT", + "TEMP_UNDEROVER", + "TEMP_LOWHIGH_INIT", + "TEMP_LOWHIGH", + "SUSPENDED_INIT", + "SUSPENDED", + "OVV_PROTECT_INIT", + "OVV_PROTECT", + "SAFETY_TIMER_EXPIRED_INIT", + "SAFETY_TIMER_EXPIRED", + "BATT_REMOVED_INIT", + "BATT_REMOVED", + "WD_EXPIRED_INIT", + "WD_EXPIRED", +}; + +struct ab8500_chargalg_events { + bool batt_unknown; + bool mainextchnotok; + bool batt_ovv; + bool batt_rem; + bool btemp_underover; + bool btemp_lowhigh; + bool main_thermal_prot; + bool usb_thermal_prot; + bool main_ovv; + bool vbus_ovv; + bool usbchargernotok; + bool safety_timer_expired; + bool maintenance_timer_expired; + bool ac_wd_expired; + bool usb_wd_expired; + bool ac_cv_active; + bool usb_cv_active; +}; + +/** + * struct ab8500_charge_curr_maximization - Charger maximization parameters + * @original_iset: the non optimized/maximised charger current + * @current_iset: the charging current used at this moment + * @test_delta_i: the delta between the current we want to charge and the + current that is really going into the battery + * @condition_cnt: number of iterations needed before a new charger current + is set + * @max_current: maximum charger current + * @level: tells in how many steps the charging current has been + increased + */ +struct ab8500_charge_curr_maximization { + int original_iset; + int current_iset; + int test_delta_i; + int condition_cnt; + int max_current; + u8 level; +}; + +enum maxim_ret { + MAXIM_RET_NOACTION, + MAXIM_RET_CHANGE, + MAXIM_RET_IBAT_TOO_HIGH, +}; + +/** + * struct ab8500_chargalg - ab8500 Charging algorithm device information + * @dev: pointer to the structure device + * @charge_status: battery operating status + * @eoc_cnt: counter used to determine end-of_charge + * @maintenance_chg: indicate if maintenance charge is active + * @t_hyst_norm temperature hysteresis when the temperature has been + * over or under normal limits + * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * over or under the high or low limits + * @charge_state: current state of the charging algorithm + * @ccm charging current maximization parameters + * @chg_info: information about connected charger types + * @batt_data: data of the battery + * @susp_status: current charger suspension status + * @parent: pointer to the struct ab8500 + * @pdata: pointer to the ab8500_chargalg platform data + * @bat: pointer to the ab8500_bm platform data + * @chargalg_psy: structure that holds the battery properties exposed by + * the charging algorithm + * @events: structure for information about events triggered + * @chargalg_wq: work queue for running the charging algorithm + * @chargalg_periodic_work: work to run the charging algorithm periodically + * @chargalg_wd_work: work to kick the charger watchdog periodically + * @chargalg_work: work to run the charging algorithm instantly + * @safety_timer: charging safety timer + * @maintenance_timer: maintenance charging timer + * @chargalg_kobject: structure of type kobject + */ +struct ab8500_chargalg { + struct device *dev; + int charge_status; + int eoc_cnt; + bool maintenance_chg; + int t_hyst_norm; + int t_hyst_lowhigh; + enum ab8500_chargalg_states charge_state; + struct ab8500_charge_curr_maximization ccm; + struct ab8500_chargalg_charger_info chg_info; + struct ab8500_chargalg_battery_data batt_data; + struct ab8500_chargalg_suspension_status susp_status; + struct ab8500 *parent; + struct ab8500_chargalg_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply chargalg_psy; + struct ux500_charger *ac_chg; + struct ux500_charger *usb_chg; + struct ab8500_chargalg_events events; + struct workqueue_struct *chargalg_wq; + struct delayed_work chargalg_periodic_work; + struct delayed_work chargalg_wd_work; + struct work_struct chargalg_work; + struct timer_list safety_timer; + struct timer_list maintenance_timer; + struct kobject chargalg_kobject; +}; + +/* Main battery properties */ +static enum power_supply_property ab8500_chargalg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, +}; + +/** + * ab8500_chargalg_safety_timer_expired() - Expiration of the safety timer + * @data: pointer to the ab8500_chargalg structure + * + * This function gets called when the safety timer for the charger + * expires + */ +static void ab8500_chargalg_safety_timer_expired(unsigned long data) +{ + struct ab8500_chargalg *di = (struct ab8500_chargalg *) data; + dev_err(di->dev, "Safety timer expired\n"); + di->events.safety_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_maintenance_timer_expired() - Expiration of + * the maintenance timer + * @i: pointer to the ab8500_chargalg structure + * + * This function gets called when the maintenence timer + * expires + */ +static void ab8500_chargalg_maintenance_timer_expired(unsigned long data) +{ + + struct ab8500_chargalg *di = (struct ab8500_chargalg *) data; + dev_dbg(di->dev, "Maintenance timer expired\n"); + di->events.maintenance_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_state_to() - Change charge state + * @di: pointer to the ab8500_chargalg structure + * + * This function gets called when a charge state change should occur + */ +static void ab8500_chargalg_state_to(struct ab8500_chargalg *di, + enum ab8500_chargalg_states state) +{ + dev_dbg(di->dev, + "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", + di->charge_state == state ? "NO" : "YES", + di->charge_state, + states[di->charge_state], + state, + states[state]); + + di->charge_state = state; +} + +/** + * ab8500_chargalg_check_charger_connection() - Check charger connection change + * @di: pointer to the ab8500_chargalg structure + * + * This function will check if there is a change in the charger connection + * and change charge state accordingly. AC has precedence over USB. + */ +static int ab8500_chargalg_check_charger_connection(struct ab8500_chargalg *di) +{ + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || + di->susp_status.suspended_change) { + /* + * Charger state changed or suspension + * has changed since last update + */ + if ((di->chg_info.conn_chg & AC_CHG) && + !di->susp_status.ac_suspended) { + dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.charger_type != AC_CHG) { + di->chg_info.charger_type = AC_CHG; + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + } else if ((di->chg_info.conn_chg & USB_CHG) && + !di->susp_status.usb_suspended) { + dev_dbg(di->dev, "Charging source is USB\n"); + di->chg_info.charger_type = USB_CHG; + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else if (di->chg_info.conn_chg && + (di->susp_status.ac_suspended || + di->susp_status.usb_suspended)) { + dev_dbg(di->dev, "Charging is suspended\n"); + di->chg_info.charger_type = NO_CHG; + ab8500_chargalg_state_to(di, STATE_SUSPENDED_INIT); + } else { + dev_dbg(di->dev, "Charging source is OFF\n"); + di->chg_info.charger_type = NO_CHG; + ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + di->chg_info.prev_conn_chg = di->chg_info.conn_chg; + di->susp_status.suspended_change = false; + } + return di->chg_info.conn_chg; +} + +/** + * ab8500_chargalg_start_safety_timer() - Start charging safety timer + * @di: pointer to the ab8500_chargalg structure + * + * The safety timer is used to avoid overcharging of old or bad batteries. + * There are different timers for AC and USB + */ +static void ab8500_chargalg_start_safety_timer(struct ab8500_chargalg *di) +{ + unsigned long timer_expiration = 0; + + switch (di->chg_info.charger_type) { + case AC_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->main_safety_tmr_h * 3600 * HZ)); + break; + + case USB_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->usb_safety_tmr_h * 3600 * HZ)); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } + + di->events.safety_timer_expired = false; + di->safety_timer.expires = timer_expiration; + if (!timer_pending(&di->safety_timer)) + add_timer(&di->safety_timer); + else + mod_timer(&di->safety_timer, timer_expiration); +} + +/** + * ab8500_chargalg_stop_safety_timer() - Stop charging safety timer + * @di: pointer to the ab8500_chargalg structure + * + * The safety timer is stopped whenever the NORMAL state is exited + */ +static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di) +{ + di->events.safety_timer_expired = false; + del_timer(&di->safety_timer); +} + +/** + * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer + * @di: pointer to the ab8500_chargalg structure + * @duration: duration of ther maintenance timer in hours + * + * The maintenance timer is used to maintain the charge in the battery once + * the battery is considered full. These timers are chosen to match the + * discharge curve of the battery + */ +static void ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di, + int duration) +{ + unsigned long timer_expiration; + + /* Convert from hours to jiffies */ + timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); + + di->events.maintenance_timer_expired = false; + di->maintenance_timer.expires = timer_expiration; + if (!timer_pending(&di->maintenance_timer)) + add_timer(&di->maintenance_timer); + else + mod_timer(&di->maintenance_timer, timer_expiration); +} + +/** + * ab8500_chargalg_stop_maintenance_timer() - Stop maintenance timer + * @di: pointer to the ab8500_chargalg structure + * + * The maintenance timer is stopped whenever maintenance ends or when another + * state is entered + */ +static void ab8500_chargalg_stop_maintenance_timer(struct ab8500_chargalg *di) +{ + di->events.maintenance_timer_expired = false; + del_timer(&di->maintenance_timer); +} + +/** + * ab8500_chargalg_kick_watchdog() - Kick charger watchdog + * @di: pointer to the ab8500_chargalg structure + * + * The charger watchdog have to be kicked periodically whenever the charger is + * on, else the ABB will reset the system + */ +static int ab8500_chargalg_kick_watchdog(struct ab8500_chargalg *di) +{ + /* Check if charger exists and kick watchdog if charging */ + if (di->ac_chg && di->ac_chg->ops.kick_wd && + di->chg_info.online_chg & AC_CHG) + return di->ac_chg->ops.kick_wd(di->ac_chg); + else if (di->usb_chg && di->usb_chg->ops.kick_wd && + di->chg_info.online_chg & USB_CHG) + return di->usb_chg->ops.kick_wd(di->usb_chg); + + return -ENXIO; +} + +/** + * ab8500_chargalg_ac_en() - Turn on/off the AC charger + * @di: pointer to the ab8500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The AC charger will be turned on/off with the requested charge voltage and + * current + */ +static int ab8500_chargalg_ac_en(struct ab8500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->ac_chg || !di->ac_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->ac_chg->max_out_volt) + vset = min(vset, di->ac_chg->max_out_volt); + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); +} + +/** + * ab8500_chargalg_usb_en() - Turn on/off the USB charger + * @di: pointer to the ab8500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The USB charger will be turned on/off with the requested charge voltage and + * current + */ +static int ab8500_chargalg_usb_en(struct ab8500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->usb_chg || !di->usb_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->usb_chg->max_out_volt) + vset = min(vset, di->usb_chg->max_out_volt); + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); +} + +/** + * ab8500_chargalg_update_chg_curr() - Update charger current + * @di: pointer to the ab8500_chargalg structure + * @iset: requested charger output current + * + * The charger output current will be updated for the charger + * that is currently in use + */ +static int ab8500_chargalg_update_chg_curr(struct ab8500_chargalg *di, + int iset) +{ + /* Check if charger exists and update current if charging */ + if (di->ac_chg && di->ac_chg->ops.update_curr && + di->chg_info.charger_type & AC_CHG) + return di->ac_chg->ops.update_curr(di->ac_chg, iset); + else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + + return -ENXIO; +} + +/** + * ab8500_chargalg_stop_charging() - Stop charging + * @di: pointer to the ab8500_chargalg structure + * + * This function is called from any state where charging should be stopped. + * All charging is disabled and all status parameters and timers are changed + * accordingly + */ +static void ab8500_chargalg_stop_charging(struct ab8500_chargalg *di) +{ + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * ab8500_chargalg_start_charging() - Start the charger + * @di: pointer to the ab8500_chargalg structure + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * A charger will be enabled depending on the requested charger type that was + * detected previously. + */ +static void ab8500_chargalg_start_charging(struct ab8500_chargalg *di, + int vset, int iset) +{ + switch (di->chg_info.charger_type) { + case AC_CHG: + dev_dbg(di->dev, + "AC parameters: Vset %d, Ich %d\n", vset, iset); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_ac_en(di, true, vset, iset); + break; + + case USB_CHG: + dev_dbg(di->dev, + "USB parameters: Vset %d, Ich %d\n", vset, iset); + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, true, vset, iset); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } +} + +/** + * ab8500_chargalg_check_temp() - Check battery temperature ranges + * @di: pointer to the ab8500_chargalg structure + * + * The battery temperature is checked against the predefined limits and the + * charge state is changed accordingly + */ +static void ab8500_chargalg_check_temp(struct ab8500_chargalg *di) +{ + if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) && + di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) { + /* Temp OK! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = 0; + } else { + if (((di->batt_data.temp >= di->bat->temp_high) && + (di->batt_data.temp < + (di->bat->temp_over - di->t_hyst_lowhigh))) || + ((di->batt_data.temp > + (di->bat->temp_under + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= di->bat->temp_low))) { + /* TEMP minor!!!!! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = true; + di->t_hyst_norm = di->bat->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if (di->batt_data.temp <= di->bat->temp_under || + di->batt_data.temp >= di->bat->temp_over) { + /* TEMP major!!!!! */ + di->events.btemp_underover = true; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = di->bat->temp_hysteresis; + } else { + /* Within hysteresis */ + dev_dbg(di->dev, "Within hysteresis limit temp: %d " + "hyst_lowhigh %d, hyst normal %d\n", + di->batt_data.temp, di->t_hyst_lowhigh, + di->t_hyst_norm); + } + } +} + +/** + * ab8500_chargalg_check_charger_health() - Check charger health + * @di: pointer to the ab8500_chargalg structure + * + * Charger voltage and current is checked against maximum limits + */ +static void ab8500_chargalg_check_charger_health(struct ab8500_chargalg *di) +{ + if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max || + di->chg_info.usb_curr > di->bat->chg_params->usb_curr_max) + di->chg_info.usb_chg_ok = false; + else + di->chg_info.usb_chg_ok = true; + + if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max || + di->chg_info.ac_curr > di->bat->chg_params->ac_curr_max) + di->chg_info.ac_chg_ok = false; + else + di->chg_info.ac_chg_ok = true; + +} + +/** + * ab8500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled + * @di: pointer to the ab8500_chargalg structure + * + * End-of-charge criteria is fulfilled when the battery voltage is above a + * certain limit and the battery current is below a certain limit for a + * predefined number of consecutive seconds. If true, the battery is full + */ +static void ab8500_chargalg_end_of_charge(struct ab8500_chargalg *di) +{ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && + di->charge_state == STATE_NORMAL && + !di->maintenance_chg && (di->batt_data.volt >= + di->bat->bat_type[di->bat->batt_id].termination_vol || + di->events.usb_cv_active || di->events.ac_cv_active) && + di->batt_data.avg_curr < + di->bat->bat_type[di->bat->batt_id].termination_curr && + di->batt_data.avg_curr > 0) { + if (++di->eoc_cnt >= EOC_COND_CNT) { + di->eoc_cnt = 0; + di->charge_status = POWER_SUPPLY_STATUS_FULL; + di->maintenance_chg = true; + dev_dbg(di->dev, "EOC reached!\n"); + power_supply_changed(&di->chargalg_psy); + } else { + dev_dbg(di->dev, + " EOC limit reached for the %d" + " time, out of %d before EOC\n", + di->eoc_cnt, + EOC_COND_CNT); + } + } else { + di->eoc_cnt = 0; + } +} + +static void init_maxim_chg_curr(struct ab8500_chargalg *di) +{ + di->ccm.original_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.current_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.test_delta_i = di->bat->maxi->charger_curr_step; + di->ccm.max_current = di->bat->maxi->chg_curr; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; +} + +/** + * ab8500_chargalg_chg_curr_maxim - increases the charger current to + * compensate for the system load + * @di pointer to the ab8500_chargalg structure + * + * This maximization function is used to raise the charger current to get the + * battery current as close to the optimal value as possible. The battery + * current during charging is affected by the system load + */ +static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) +{ + int delta_i; + + if (!di->bat->maxi->ena_maxi) + return MAXIM_RET_NOACTION; + + delta_i = di->ccm.original_iset - di->batt_data.inst_curr; + dev_dbg(di->dev, "[CHARGALG_OPTI_DATA], Ucv ,%d, Dcv ,%d, cnt ,%d," + " cur iset ,%d, avg i ,%d, Ibat inst: ,%d,mA, Orig iset: ,%d, " + "delta_i ,%d, Max ,%d,mA" + " AC chg curr: ,%d,mA" + " USB chg curr: ,%d,mA\n", + di->events.usb_cv_active, + di->events.ac_cv_active, + di->ccm.condition_cnt, + di->ccm.current_iset, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->ccm.original_iset, + delta_i, + di->ccm.max_current, + di->chg_info.ac_curr, + di->chg_info.usb_curr); + + if ((di->batt_data.inst_curr > di->ccm.original_iset)) { + dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" + " (limit %dmA) (current iset: %dmA)!\n", + di->batt_data.inst_curr, di->ccm.original_iset, + di->ccm.current_iset); + + if (di->ccm.current_iset == di->ccm.original_iset) + return MAXIM_RET_NOACTION; + + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset = di->ccm.original_iset; + di->ccm.level = 0; + + return MAXIM_RET_IBAT_TOO_HIGH; + } + + /* Make sure first we are actually charging.. */ + if (di->charge_status != POWER_SUPPLY_STATUS_CHARGING || + (di->events.usb_cv_active || di->events.ac_cv_active)) { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; + di->ccm.current_iset = di->ccm.original_iset; + return MAXIM_RET_NOACTION; + } + + if (delta_i > di->ccm.test_delta_i && + (di->ccm.current_iset + di->ccm.test_delta_i) < + di->ccm.max_current) { + if (di->ccm.condition_cnt-- == 0) { + /* Increse the iset with cco.test_delta_i */ + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset += di->ccm.test_delta_i; + di->ccm.level++; + dev_dbg(di->dev, " Maximization needed, increase" + " with %d mA to %dmA (Optimal ibat: %d)" + " Level %d\n", + di->ccm.test_delta_i, + di->ccm.current_iset, + di->ccm.original_iset, + di->ccm.level); + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, " Maximization, wait for" + " counter, to go %d\n", di->ccm.condition_cnt); + return MAXIM_RET_NOACTION; + } + } else { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + return MAXIM_RET_NOACTION; + } +} + +static void handle_maxim_chg_curr(struct ab8500_chargalg *di) +{ + enum maxim_ret ret; + + ret = ab8500_chargalg_chg_curr_maxim(di); + switch (ret) { + case MAXIM_RET_CHANGE: + ab8500_chargalg_update_chg_curr(di, di->ccm.current_iset); + break; + case MAXIM_RET_IBAT_TOO_HIGH: + ab8500_chargalg_update_chg_curr(di, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + break; + + case MAXIM_RET_NOACTION: + default: + /* Do nothing..*/ + break; + } +} + +static int ab8500_chargalg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_chargalg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_chargalg_device_info(psy); + + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + /* Initialize chargers if not already done */ + if (!di->ac_chg && + ext->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + /* Battery present */ + if (ret.intval) + di->events.batt_rem = false; + /* Battery removed */ + else + di->events.batt_rem = true; + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~AC_CHG; + } + /* AC connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= AC_CHG; + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~USB_CHG; + } + /* USB connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= USB_CHG; + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_ONLINE: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC offline */ + if (!ret.intval && + (di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~AC_CHG; + } + /* AC online */ + else if (ret.intval && + !(di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= AC_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB offline */ + if (!ret.intval && + (di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~USB_CHG; + } + /* USB online */ + else if (ret.intval && + !(di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= USB_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.mainextchnotok = true; + di->events.main_thermal_prot = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.ac_wd_expired = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.main_thermal_prot = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.main_thermal_prot = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.main_ovv = true; + di->events.mainextchnotok = false; + di->events.main_thermal_prot = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.main_thermal_prot = false; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + default: + break; + } + break; + + case POWER_SUPPLY_TYPE_USB: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.usbchargernotok = true; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.usb_wd_expired = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.usb_thermal_prot = true; + di->events.usbchargernotok = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.vbus_ovv = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + default: + break; + } + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_volt = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.ac_cv_active = true; + else + di->events.ac_cv_active = false; + + break; + case POWER_SUPPLY_TYPE_USB: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.usb_cv_active = true; + else + di->events.usb_cv_active = false; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->events.batt_unknown = false; + else + di->events.batt_unknown = true; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + di->batt_data.temp = ret.intval / 10; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.inst_curr = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + di->batt_data.percent = ret.intval; + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_chargalg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_chargalg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_chargalg *di = to_ab8500_chargalg_device_info(psy); + + /* + * Trigger execution of the algorithm instantly and read + * all power_supply properties there instead + */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_algorithm() - Main function for the algorithm + * @di: pointer to the ab8500_chargalg structure + * + * This is the main control function for the charging algorithm. + * It is called periodically or when something happens that will + * trigger a state change + */ +static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) +{ + int charger_status; + + /* Collect data from all power_supply class devices */ + class_for_each_device(power_supply_class, NULL, + &di->chargalg_psy, ab8500_chargalg_get_ext_psy_data); + + ab8500_chargalg_end_of_charge(di); + ab8500_chargalg_check_temp(di); + ab8500_chargalg_check_charger_health(di); + charger_status = ab8500_chargalg_check_charger_connection(di); + + /* + * First check if we have a charger connected. + * Also we don't allow charging of unknown batteries if configured + * this way + */ + if (!charger_status || + (di->events.batt_unknown && !di->bat->chg_unknown_bat)) { + if (di->charge_state != STATE_HANDHELD) { + di->events.safety_timer_expired = false; + ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + } + + /* If suspended, we should not continue checking the flags */ + else if (di->charge_state == STATE_SUSPENDED_INIT || + di->charge_state == STATE_SUSPENDED) { + /* We don't do anything here, just don,t continue */ + } + + /* Safety timer expiration */ + else if (di->events.safety_timer_expired) { + if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) + ab8500_chargalg_state_to(di, + STATE_SAFETY_TIMER_EXPIRED_INIT); + } + /* + * Check if any interrupts has occured + * that will prevent us from charging + */ + + /* Battery removed */ + else if (di->events.batt_rem) { + if (di->charge_state != STATE_BATT_REMOVED) + ab8500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); + } + /* Main or USB charger not ok. */ + else if (di->events.mainextchnotok || di->events.usbchargernotok) { + if (di->charge_state != STATE_CHG_NOT_OK) + ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); + } + /* VBUS, Main or VBAT OVV. */ + else if (di->events.vbus_ovv || + di->events.main_ovv || + di->events.batt_ovv || + !di->chg_info.usb_chg_ok || + !di->chg_info.ac_chg_ok) { + if (di->charge_state != STATE_OVV_PROTECT) + ab8500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); + } + /* USB Thermal, stop charging */ + else if (di->events.main_thermal_prot || + di->events.usb_thermal_prot) { + if (di->charge_state != STATE_HW_TEMP_PROTECT) + ab8500_chargalg_state_to(di, + STATE_HW_TEMP_PROTECT_INIT); + } + /* Battery temp over/under */ + else if (di->events.btemp_underover) { + if (di->charge_state != STATE_TEMP_UNDEROVER) + ab8500_chargalg_state_to(di, + STATE_TEMP_UNDEROVER_INIT); + } + /* Watchdog expired */ + else if (di->events.ac_wd_expired || + di->events.usb_wd_expired) { + if (di->charge_state != STATE_WD_EXPIRED) + ab8500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); + } + /* Battery temp high/low */ + else if (di->events.btemp_lowhigh) { + if (di->charge_state != STATE_TEMP_LOWHIGH) + ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); + } + + dev_dbg(di->dev, + "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " + "State %s Active_chg %d Chg_status %d AC %d USB %d " + "AC_online %d USB_online %d " + "AC_CV %d USB_CV %d\n", + di->batt_data.volt, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->batt_data.temp, + di->batt_data.percent, + di->maintenance_chg, + states[di->charge_state], + di->chg_info.charger_type, + di->charge_status, + di->chg_info.conn_chg & AC_CHG, + di->chg_info.conn_chg & USB_CHG, + di->chg_info.online_chg & AC_CHG, + di->chg_info.online_chg & USB_CHG, + di->events.ac_cv_active, + di->events.usb_cv_active); + + switch (di->charge_state) { + case STATE_HANDHELD_INIT: + ab8500_chargalg_stop_charging(di); + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + ab8500_chargalg_state_to(di, STATE_HANDHELD); + /* Intentional fallthrough */ + + case STATE_HANDHELD: + break; + + case STATE_SUSPENDED_INIT: + if (di->susp_status.ac_suspended) + ab8500_chargalg_ac_en(di, false, 0, 0); + if (di->susp_status.usb_suspended) + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + ab8500_chargalg_state_to(di, STATE_SUSPENDED); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_SUSPENDED: + /* CHARGING is suspended */ + break; + + case STATE_BATT_REMOVED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_BATT_REMOVED); + /* Intentional fallthrough */ + + case STATE_BATT_REMOVED: + if (!di->events.batt_rem) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_HW_TEMP_PROTECT_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); + /* Intentional fallthrough */ + + case STATE_HW_TEMP_PROTECT: + if (!di->events.main_thermal_prot && + !di->events.usb_thermal_prot) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_OVV_PROTECT_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_OVV_PROTECT); + /* Intentional fallthrough */ + + case STATE_OVV_PROTECT: + if (!di->events.vbus_ovv && + !di->events.main_ovv && + !di->events.batt_ovv && + di->chg_info.usb_chg_ok && + di->chg_info.ac_chg_ok) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_CHG_NOT_OK_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK); + /* Intentional fallthrough */ + + case STATE_CHG_NOT_OK: + if (!di->events.mainextchnotok && + !di->events.usbchargernotok) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_SAFETY_TIMER_EXPIRED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); + /* Intentional fallthrough */ + + case STATE_SAFETY_TIMER_EXPIRED: + /* We exit this state when charger is removed */ + break; + + case STATE_NORMAL_INIT: + ab8500_chargalg_start_charging(di, + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + ab8500_chargalg_state_to(di, STATE_NORMAL); + ab8500_chargalg_start_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + init_maxim_chg_curr(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->eoc_cnt = 0; + di->maintenance_chg = false; + power_supply_changed(&di->chargalg_psy); + + break; + + case STATE_NORMAL: + handle_maxim_chg_curr(di); + + if (di->charge_status == POWER_SUPPLY_STATUS_FULL && + di->maintenance_chg) + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A_INIT); + + break; + + case STATE_MAINTENANCE_A_INIT: + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_chg_timer_h); + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_a_cur_lvl); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_A: + if (di->events.maintenance_timer_expired) { + ab8500_chargalg_stop_maintenance_timer(di); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); + } + break; + + case STATE_MAINTENANCE_B_INIT: + ab8500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_chg_timer_h); + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_b_cur_lvl); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_B: + if (di->events.maintenance_timer_expired) { + ab8500_chargalg_stop_maintenance_timer(di); + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + break; + + case STATE_TEMP_LOWHIGH_INIT: + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].low_high_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].low_high_cur_lvl); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_TEMP_LOWHIGH: + if (!di->events.btemp_lowhigh) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_WD_EXPIRED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_WD_EXPIRED); + /* Intentional fallthrough */ + + case STATE_WD_EXPIRED: + if (!di->events.ac_wd_expired && + !di->events.usb_wd_expired) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_TEMP_UNDEROVER_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); + /* Intentional fallthrough */ + + case STATE_TEMP_UNDEROVER: + if (!di->events.btemp_underover) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } + + /* Start charging directly if the new state is a charge state */ + if (di->charge_state == STATE_NORMAL_INIT || + di->charge_state == STATE_MAINTENANCE_A_INIT || + di->charge_state == STATE_MAINTENANCE_B_INIT) + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_periodic_work() - Periodic work for the algorithm + * @work: pointer to the work_struct structure + * + * Work queue function for the charging algorithm + */ +static void ab8500_chargalg_periodic_work(struct work_struct *work) +{ + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_periodic_work.work); + + ab8500_chargalg_algorithm(di); + + /* + * If a charger is connected then the battery has to be monitored + * frequently, else the work can be delayed. + */ + if (di->chg_info.conn_chg) + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_charging * HZ); + else + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_not_charging * HZ); +} + +/** + * ab8500_chargalg_wd_work() - periodic work to kick the charger watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog + */ +static void ab8500_chargalg_wd_work(struct work_struct *work) +{ + int ret; + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_wd_work.work); + + dev_dbg(di->dev, "ab8500_chargalg_wd_work\n"); + + ret = ab8500_chargalg_kick_watchdog(di); + if (ret < 0) + dev_err(di->dev, "failed to kick watchdog\n"); + + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, CHG_WD_INTERVAL); +} + +/** + * ab8500_chargalg_work() - Work to run the charging algorithm instantly + * @work: pointer to the work_struct structure + * + * Work queue function for calling the charging algorithm + */ +static void ab8500_chargalg_work(struct work_struct *work) +{ + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_work); + + ab8500_chargalg_algorithm(di); +} + +/** + * ab8500_chargalg_get_property() - get the chargalg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * chargalg properties by reading the sysfs files. + * status: charging/discharging/full/unknown + * health: health of the battery + * Returns error code in case of failure else 0 on success + */ +static int ab8500_chargalg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_chargalg *di; + + di = to_ab8500_chargalg_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (di->events.batt_ovv) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (di->events.btemp_underover) { + if (di->batt_data.temp <= di->bat->temp_under) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* Exposure to the sysfs interface */ + +/** + * ab8500_chargalg_sysfs_charger() - sysfs store operations + * @kobj: pointer to the struct kobject + * @attr: pointer to the struct attribute + * @buf: buffer that holds the parameter passed from userspace + * @length: length of the parameter passed + * + * Returns length of the buffer(input taken from user space) on success + * else error code on failure + * The operation to be performed on passing the parameters from the user space. + */ +static ssize_t ab8500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct ab8500_chargalg *di = container_of(kobj, + struct ab8500_chargalg, chargalg_kobject); + long int param; + int ac_usb; + int ret; + char entry = *attr->name; + + switch (entry) { + case 'c': + ret = strict_strtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); + }; + break; + }; + return strlen(buf); +} + +static struct attribute ab8500_chargalg_en_charger = \ +{ + .name = "chargalg", + .mode = S_IWUGO, +}; + +static struct attribute *ab8500_chargalg_chg[] = { + &ab8500_chargalg_en_charger, + NULL +}; + +const struct sysfs_ops ab8500_chargalg_sysfs_ops = { + .store = ab8500_chargalg_sysfs_charger, +}; + +static struct kobj_type ab8500_chargalg_ktype = { + .sysfs_ops = &ab8500_chargalg_sysfs_ops, + .default_attrs = ab8500_chargalg_chg, +}; + +/** + * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function removes the entry in sysfs. + */ +static void ab8500_chargalg_sysfs_exit(struct ab8500_chargalg *di) +{ + kobject_del(&di->chargalg_kobject); +} + +/** + * ab8500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_chargalg_sysfs_init(struct ab8500_chargalg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->chargalg_kobject, + &ab8500_chargalg_ktype, + NULL, "ab8500_chargalg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <> */ + +#if defined(CONFIG_PM) +static int ab8500_chargalg_resume(struct platform_device *pdev) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int ab8500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define ab8500_chargalg_suspend NULL +#define ab8500_chargalg_resume NULL +#endif + +static int __devexit ab8500_chargalg_remove(struct platform_device *pdev) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + ab8500_chargalg_sysfs_exit(di); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->chargalg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_chargalg_probe(struct platform_device *pdev) +{ + struct ab8500_platform_data *plat; + int ret = 0; + + struct ab8500_chargalg *di = + kzalloc(sizeof(struct ab8500_chargalg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + + plat = dev_get_platdata(di->parent->dev); + + /* get chargalg specific platform data */ + if (!plat->chargalg) { + dev_err(di->dev, "no chargalg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->chargalg; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* chargalg supply */ + di->chargalg_psy.name = "ab8500_chargalg"; + di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->chargalg_psy.properties = ab8500_chargalg_props; + di->chargalg_psy.num_properties = ARRAY_SIZE(ab8500_chargalg_props); + di->chargalg_psy.get_property = ab8500_chargalg_get_property; + di->chargalg_psy.supplied_to = di->pdata->supplied_to; + di->chargalg_psy.num_supplicants = di->pdata->num_supplicants; + di->chargalg_psy.external_power_changed = + ab8500_chargalg_external_power_changed; + + /* Initilialize safety timer */ + init_timer(&di->safety_timer); + di->safety_timer.function = ab8500_chargalg_safety_timer_expired; + di->safety_timer.data = (unsigned long) di; + + /* Initilialize maintenance timer */ + init_timer(&di->maintenance_timer); + di->maintenance_timer.function = + ab8500_chargalg_maintenance_timer_expired; + di->maintenance_timer.data = (unsigned long) di; + + /* Create a work queue for the chargalg */ + di->chargalg_wq = + create_singlethread_workqueue("ab8500_chargalg_wq"); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for chargalg */ + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work, + ab8500_chargalg_periodic_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work, + ab8500_chargalg_wd_work); + + /* Init work for chargalg */ + INIT_WORK(&di->chargalg_work, ab8500_chargalg_work); + + /* To detect charger at startup */ + di->chg_info.prev_conn_chg = -1; + + /* Register chargalg power supply class */ + ret = power_supply_register(di->dev, &di->chargalg_psy); + if (ret) { + dev_err(di->dev, "failed to register chargalg psy\n"); + goto free_chargalg_wq; + } + + platform_set_drvdata(pdev, di); + + /* sysfs interface to enable/disable charging from user space */ + ret = ab8500_chargalg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_psy; + } + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + return ret; + +free_psy: + power_supply_unregister(&di->chargalg_psy); +free_chargalg_wq: + destroy_workqueue(di->chargalg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_chargalg_driver = { + .probe = ab8500_chargalg_probe, + .remove = __devexit_p(ab8500_chargalg_remove), + .suspend = ab8500_chargalg_suspend, + .resume = ab8500_chargalg_resume, + .driver = { + .name = "ab8500-chargalg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_chargalg_init(void) +{ + return platform_driver_register(&ab8500_chargalg_driver); +} + +static void __exit ab8500_chargalg_exit(void) +{ + platform_driver_unregister(&ab8500_chargalg_driver); +} + +module_init(ab8500_chargalg_init); +module_exit(ab8500_chargalg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-chargalg"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c new file mode 100644 index 00000000000..194ff9d23d7 --- /dev/null +++ b/drivers/power/ab8500_charger.c @@ -0,0 +1,2368 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Charger driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + * Author: Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 1 +#define USB_PW_CONN 2 + +#define MAIN_WDOG_ENA 0x01 +#define MAIN_WDOG_KICK 0x02 +#define MAIN_WDOG_DIS 0x00 +#define CHARG_WD_KICK 0x01 +#define MAIN_CH_ENA 0x01 +#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 +#define USB_CH_ENA 0x01 +#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02 +#define MAIN_CH_DET 0x01 +#define MAIN_CH_CV_ON 0x04 +#define USB_CH_CV_ON 0x08 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 +#define OTP_ENABLE_WD 0x01 + +#define LED_INDICATOR_PWM_ENA 0x01 +#define LED_INDICATOR_PWM_DIS 0x00 +#define LED_IND_CUR_5MA 0x04 +#define LED_INDICATOR_PWM_DUTY_252_256 0xBF + +/* HW failure constants */ +#define MAIN_CH_TH_PROT 0x02 +#define VBUS_CH_NOK 0x08 +#define USB_CH_TH_PROT 0x02 +#define VBUS_OVV_TH 0x01 +#define MAIN_CH_NOK 0x01 +#define VBUS_DET 0x80 + +/* UsbLineStatus register bit masks */ +#define AB8500_USB_LINK_STATUS 0x78 +#define AB8500_STD_HOST_SUSP 0x18 + +/* Watchdog timeout constant */ +#define WD_TIMER 0x30 /* 4min */ +#define WD_KICK_INTERVAL (60 * HZ) + +/* Lowest charger voltage is 3.39V -> 0x4E */ +#define LOW_VOLT_REG 0x4E + +/* UsbLineStatus register - usb types */ +enum ab8500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, +}; + +enum ab8500_usb_state { + AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB8500_BM_USB_STATE_CONFIGURED, + AB8500_BM_USB_STATE_SUSPEND, + AB8500_BM_USB_STATE_RESUME, + AB8500_BM_USB_STATE_MAX, +}; + +#define to_ab8500_charger_usb_device_info(x) container_of((x), \ + struct ab8500_charger, usb_chg) +#define to_ab8500_charger_ac_device_info(x) container_of((x), \ + struct ab8500_charger, ac_chg) + +/** + * struct ab8500_charger_interrupts - ab8500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; +}; + +struct ab8500_charger_event_flags { + bool mainextchnotok; + bool main_thermal_prot; + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool chgwdexp; +}; + +struct ab8500_charger_usb_state { + bool usb_changed; + int usb_current; + enum ab8500_usb_state state; + spinlock_t usb_lock; +}; + +/** + * struct ab8500_charger - ab8500 Charger device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the AB8500 + * @max_usb_in_curr: Max USB charger input current + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @ac_conn: This will be true when the AC charger has been plugged + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_charger platform data + * @bat: Pointer to the ab8500_bm platform data + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @ac_chg: AC charger power supply + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @charger_wq: Work queue for the IRQs and checking HW state + * @check_hw_failure_work: Work for checking HW state + * @kick_wd_work: Work for kicking the charger watchdog in case + * of ABB rev 1.* due to the watchog logic bug + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + */ +struct ab8500_charger { + struct device *dev; + u8 chip_id; + int max_usb_in_curr; + bool vbus_detected; + bool vbus_detected_start; + bool ac_conn; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_charger_platform_data *pdata; + struct ab8500_bm_data *bat; + struct ab8500_charger_event_flags flags; + struct ab8500_charger_usb_state usb_state; + struct ux500_charger ac_chg; + struct ux500_charger usb_chg; + struct ab8500_charger_info ac; + struct ab8500_charger_info usb; + struct workqueue_struct *charger_wq; + struct delayed_work check_hw_failure_work; + struct delayed_work kick_wd_work; + struct work_struct ac_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct usb_state_changed_work; + struct work_struct check_usbchgnotok_work; + struct work_struct check_main_thermal_prot_work; + struct work_struct check_usb_thermal_prot_work; +}; + +/* + * TODO: This variable is static in order to get information + * about maximum current and USB state from the USB driver + * This should be solved in a better way + */ +static struct ab8500_charger *static_di; + +/* AC properties */ +static enum power_supply_property ab8500_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* USB properties */ +static enum power_supply_property ab8500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/** + * ab8500_charger_get_ac_voltage() - get ac charger voltage + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger voltage (on success) + */ +static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->ac.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed,\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_ac_cv() - check if the main charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_ac_cv(struct ab8500_charger *di) +{ + u8 val; + int ret = 0; + + /* Only check CV mode if the charger is online */ + if (di->ac.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & MAIN_CH_CV_ON) + ret = 1; + else + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab8500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab8500_charger_get_usb_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_get_ac_current() - get ac charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the ac charger current. + * Returns ac current (on success) and error code on failure. + */ +static int ab8500_charger_get_ac_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->ac.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_usb_cv() - check if the usb charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_usb_cv(struct ab8500_charger *di) +{ + int ret; + u8 val; + + /* Only check CV mode if the charger is online */ + if (di->usb.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & USB_CH_CV_ON) + ret = 1; + else + ret = 0; + } else { + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab8500_charger structure + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + */ +static int ab8500_charger_detect_chargers(struct ab8500_charger *di) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + + /* Check for AC charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (val & MAIN_CH_DET) + result = AC_PW_CONN; + + /* Check for USB charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (val & (VBUS_DET_DBNC100 | VBUS_DET_DBNC1)) + result |= USB_PW_CONN; + + return result; +} + +/** + * ab8500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab8500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, + enum ab8500_charger_link_status link_status) +{ + int ret = 0; + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + ret = -1; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + case USB_STAT_HOST_CHG_HS: + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (300mA). Closest level is 1100mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3; + break; + case USB_STAT_DEDICATED_CHG: + case USB_STAT_HOST_CHG_NM: + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; + break; + case USB_STAT_HM_IDGND: + case USB_STAT_NOT_CONFIGURED: + case USB_STAT_RESERVED: + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: 0x%02x", + link_status, di->max_usb_in_curr); + + return ret; +} + +/** + * ab8500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_read_usb_type(struct ab8500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/** + * ab8500_charger_detect_usb_type() - get the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) +{ + int i, ret; + u8 val; + + /* + * On getting the VBUS rising edge detect interrupt there + * is a 250ms delay after which the register UsbLineStatus + * is filled with valid data. + */ + for (i = 0; i < 10; i++) { + msleep(250); + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, + &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + /* + * Until the IT source register is read the UsbLineStatus + * register is not updated, hence doing the same + * Revisit this: + */ + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + if (val) + break; + } + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/* + * This array maps the raw hex value to charger voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +/* + * This array maps the raw hex value to charger current used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_current_map[] = { + 100 , + 200 , + 300 , + 400 , + 500 , + 600 , + 700 , + 800 , + 900 , + 1000 , + 1100 , + 1200 , + 1300 , + 1400 , + 1500 , +}; + +static int ab8500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.5V */ + if (voltage < ab8500_charger_voltage_map[0]) + return LOW_VOLT_REG; + + for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) { + if (voltage < ab8500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1; + if (voltage == ab8500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab8500_current_to_regval(int curr) +{ + int i; + + if (curr < ab8500_charger_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) { + if (curr < ab8500_charger_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_current_map) - 1; + if (curr == ab8500_charger_current_map[i]) + return i; + else + return -1; +} + +/** + * ab8500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab8500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) +{ + switch (di->usb_state.usb_current) { + case 100: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + return -1; + break; + }; + return 0; +} + +/** + * ab8500_charger_led_en() - turn on/off chargign led + * @di: pointer to the ab8500_charger structure + * @on: flag to turn on/off the chargign led + * + * Power ON/OFF charging LED indication + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_led_en(struct ab8500_charger *di, int on) +{ + int ret; + + if (on) { + /* Power ON charging LED indicator, set LED current to 5mA */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA)); + if (ret) { + dev_err(di->dev, "Power ON LED failed\n"); + return ret; + } + /* LED indicator PWM duty cycle 252/256 */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_DUTY, + LED_INDICATOR_PWM_DUTY_252_256); + if (ret) { + dev_err(di->dev, "Set LED PWM duty cycle failed\n"); + return ret; + } + } else { + /* Power off charging LED indicator */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + LED_INDICATOR_PWM_DIS); + if (ret) { + dev_err(di->dev, "Power-off LED failed\n"); + return ret; + } + } + + return ret; +} + +/** + * ab8500_charger_ac_en() - enable or disable ac charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @iset: charging current + * + * Enable/Disable AC/Mains charging and turns on/off the charging led + * respectively. + **/ +static int ab8500_charger_ac_en(struct ux500_charger *charger, + int enable, int vset, int iset) +{ + int ret; + int volt_index; + int curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); + + if (enable) { + /* Check if AC is connected */ + if (!di->ac.charger_connected) { + dev_err(di->dev, "AC charger not connected\n"); + return -ENXIO; + } + + /* Enable AC charging */ + dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(iset); + if (volt_index < 0 || curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: maximum battery charging voltage */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* MainChInputCurr: current that can be drawn from the charger*/ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_IPT_CURLVL_REG, MAIN_CH_IP_CUR_1P5A); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N; + + /* Enable Main Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->ac.charger_online = 1; + } else { + /* Disable AC charging */ + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + /* + * For ABB revision 1.0 and 1.1 there is a bug in the + * watchdog logic. That means we have to continously + * kick the charger watchdog even when no charger is + * connected. This is only valid once the AC charger + * has been enabled. This is a bug that is not handled + * by the algorithm and the watchdog have to be kicked + * by the charger driver when the AC charger + * is disabled + */ + if (di->ac_conn) { + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + + /* + * We can't turn off charging completely + * due to a bug in AB8500 cut1. + * If we do, charging will not start again. + * That is why we set the lowest voltage + * and current possible + */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + break; + + case AB8500_CUT2P0: + default: + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_MCH_CTRL1, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + break; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->ac.charger_online = 0; + di->ac.wd_expired = false; + dev_dbg(di->dev, "%s Disabled AC charging\n", __func__); + } + power_supply_changed(&di->ac_chg.psy); + + return ret; +} + +/** + * ab8500_charger_usb_en() - enable usb charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @ich_out: charger output current + * + * Enable/Disable USB charging and turns on/off the charging led respectively. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_en(struct ux500_charger *charger, + int enable, int vset, int ich_out) +{ + int ret; + int volt_index; + int curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); + + if (enable) { + /* Check if USB is connected */ + if (!di->usb.charger_connected) { + dev_err(di->dev, "USB charger not connected\n"); + return -ENXIO; + } + + /* Enable USB charging */ + dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(ich_out); + if (volt_index < 0 || curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* USBChInputCurr: current that can be drawn from the usb */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_IPT_CRNTLVL_REG, + di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = USB_CHG_NO_OVERSHOOT_ENA_N; + + /* Enable USB Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* If success power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->usb.charger_online = 1; + } else { + /* Disable USB charging */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->usb.charger_online = 0; + di->usb.wd_expired = false; + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + } + power_supply_changed(&di->usb_chg.psy); + + return ret; +} + +/** + * ab8500_charger_watchdog_kick() - kick charger watchdog + * @di: pointer to the ab8500_charger structure + * + * Kick charger watchdog + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + return ret; +} + +/** + * ab8500_charger_update_charger_current() - update charger current + * @di: pointer to the ab8500_charger structure + * + * Update the charger output current for the specified charger + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret; + int curr_index; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + curr_index = ab8500_current_to_regval(ich_out); + if (curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) + dev_err(di->dev, "%s write failed\n", __func__); + + return ret; +} + +/** + * ab8500_charger_check_hw_failure_work() - check main charger failure + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_hw_failure_work.work); + + /* Check if the status bits for HW failure is still active */ + if (di->flags.mainextchnotok) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & MAIN_CH_NOK)) { + di->flags.mainextchnotok = false; + power_supply_changed(&di->ac_chg.psy); + } + } + if (di->flags.vbus_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & VBUS_OVV_TH)) { + di->flags.vbus_ovv = false; + power_supply_changed(&di->usb_chg.psy); + } + } + /* If we still have a failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, round_jiffies(HZ)); + } +} + +/** + * ab8500_charger_kick_watchdog_work() - kick the watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog. + * + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ +static void ab8500_charger_kick_watchdog_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, kick_wd_work.work); + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* Schedule a new watchdog kick */ + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL)); +} + +/** + * ab8500_charger_ac_work() - work to get and set main charger status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_ac_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, ac_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if the main charger is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (ret & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + } else { + di->ac.charger_connected = 0; + } + + power_supply_changed(&di->ac_chg.psy); +} + +/** + * ab8500_charger_detect_usb_type_work() - work to detect USB type + * @work: Pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +void ab8500_charger_detect_usb_type_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, detect_usb_type_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + case AB8500_CUT2P0: + default: + /* For ABB cut2.0 and onwards we have an IRQ, + * USB_LINK_STATUS that will be triggered when the USB + * link status changes. The exception is USB connected + * during startup. Then we don't get a + * USB_LINK_STATUS IRQ + */ + if (di->vbus_detected_start) { + di->vbus_detected_start = false; + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + } + break; + } + } +} + +/** + * ab8500_charger_usb_link_status_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_usb_link_status_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_link_status_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + ret = ab8500_charger_read_usb_type(di); + if (!ret) { + /* Update maximum input current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_USBCH_IPT_CRNTLVL_REG, + di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return; + } + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } else if (ret == -ENXIO) { + /* No valid charger type detected */ + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } + } +} + +static void ab8500_charger_usb_state_changed_work(struct work_struct *work) +{ + int ret; + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_state_changed_work); + + if (!di->vbus_detected) + return; + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = false; + spin_unlock(&di->usb_state.usb_lock); + + /* + * wait for some time until you get updates from the usb stack + * and negotiations are completed + */ + msleep(250); + + if (di->usb_state.usb_changed) + return; + + dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", + __func__, di->usb_state.state, di->usb_state.usb_current); + + switch (di->usb_state.state) { + case AB8500_BM_USB_STATE_RESET_HS: + case AB8500_BM_USB_STATE_RESET_FS: + case AB8500_BM_USB_STATE_SUSPEND: + case AB8500_BM_USB_STATE_MAX: + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + break; + + case AB8500_BM_USB_STATE_RESUME: + /* + * when suspend->resume there should be delay + * of 1sec for enabling charging + */ + msleep(1000); + /* Intentional fall through */ + case AB8500_BM_USB_STATE_CONFIGURED: + /* + * USB is configured, enable charging with the charging + * input current obtained from USB driver + */ + if (!ab8500_charger_get_usb_cur(di)) { + /* Update maximum input current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_USBCH_IPT_CRNTLVL_REG, + di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return; + } + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + default: + break; + }; +} + +/** + * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB charger Not OK status + */ +static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usbchgnotok_work); + + /* Check if the status bit for usbchargernotok is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & VBUS_CH_NOK) + di->flags.usbchargernotok = true; + else + di->flags.usbchargernotok = false; + + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab8500_charger_check_main_thermal_prot_work() - check main thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the Main thermal prot status + */ +static void ab8500_charger_check_main_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_main_thermal_prot_work); + + /* Check if the status bit for main_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & MAIN_CH_TH_PROT) + di->flags.main_thermal_prot = true; + else + di->flags.main_thermal_prot = false; + + power_supply_changed(&di->ac_chg.psy); +} + +/** + * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB thermal prot status + */ +static void ab8500_charger_check_usb_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usb_thermal_prot_work); + + /* Check if the status bit for usb_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & USB_CH_TH_PROT) + di->flags.usb_thermal_prot = true; + else + di->flags.usb_thermal_prot = false; + + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab8500_charger_mainchunplugdet_handler() - main charger unplugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger unplugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchplugdet_handler() - main charger plugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger plugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainextchnotok_handler() - main charger not ok + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger not ok\n"); + di->flags.mainextchnotok = true; + power_supply_changed(&di->ac_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetf_handler() - VBUS falling detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS falling detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetr_handler() - VBUS rising detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + di->vbus_detected = true; + dev_dbg(di->dev, "VBUS rising detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usblinkstatus_handler() - USB link status has changed + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "USB link status changed\n"); + + queue_work(di->charger_wq, &di->usb_link_status_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchargernotokf_handler() - USB charger ok detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchargernotokf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Allowed USB charger detected\n"); + queue_work(di->charger_wq, &di->check_usbchgnotok_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Not allowed USB charger detected\n"); + queue_work(di->charger_wq, &di->check_usbchgnotok_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_chwdexp_handler() - Charger watchdog expired + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Charger watchdog expired\n"); + + /* + * The charger that was online when the watchdog expired + * needs to be restarted for charging to start again + */ + if (di->ac.charger_online) { + di->ac.wd_expired = true; + power_supply_changed(&di->ac_chg.psy); + } + if (di->usb.charger_online) { + di->usb.wd_expired = true; + power_supply_changed(&di->usb_chg.psy); + } + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS overvoltage detected\n"); + di->flags.vbus_ovv = true; + power_supply_changed(&di->usb_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_ac_get_property() - get the ac/mains properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the ac/mains + * properties by reading the sysfs files. + * AC/Mains properties are online, present and voltage. + * online: ac/mains charging is in progress or not + * present: presence of the ac/mains + * voltage: AC/Mains voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.mainextchnotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.main_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->ac.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->ac.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di); + val->intval = di->ac.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the AC charger + */ + di->ac.cv_active = ab8500_charger_ac_cv(di); + val->intval = di->ac.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_ac_current(di) * 1000; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_usb_get_property() - get the usb properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the usb + * properties by reading the sysfs files. + * USB properties are online, present and voltage. + * online: usb charging is in progress or not + * present: presence of the usb + * voltage: vbus voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.usbchargernotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.usb_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (di->flags.vbus_ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->usb.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di); + val->intval = di->usb.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the USB charger + */ + di->usb.cv_active = ab8500_charger_usb_cv(di); + val->intval = di->usb.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_usb_current(di) * 1000; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_init_hw_registers() - Set up charger related registers + * @di: pointer to the ab8500_charger structure + * + * Set up charger OVV, watchdog and maximum voltage registers as well as + * charging of the backup battery + */ +static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) +{ + int ret = 0; + + /* Setup maximum charger current and voltage for ABB cut2.0 */ + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + break; + case AB8500_CUT2P0: + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_VOLT_LVL_MAX_REG\n"); + goto out; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); + goto out; + } + + break; + default: + goto out; + } + + /* VBUS OVV set to 6.3V */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL2_REG, 0x78); + if (ret) { + dev_err(di->dev, "failed to set VBUS OVV\n"); + goto out; + } + + /* Enable main watchdog in OTP */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD); + if (ret) { + dev_err(di->dev, "failed to enable main WD in OTP\n"); + goto out; + } + + /* Enable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA); + if (ret) { + dev_err(di->dev, "faile to enable main watchdog\n"); + goto out; + } + + /* + * Due to internal synchronisation, Enable and Kick watchdog bits + * cannot be enabled in a single write. + * A minimum delay of 2*32 kHz period (62.5µs) must be inserted + * between writing Enable then Kick bits. + */ + udelay(63); + + /* Kick main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, + (MAIN_WDOG_ENA | MAIN_WDOG_KICK)); + if (ret) { + dev_err(di->dev, "failed to kick main watchdog\n"); + goto out; + } + + /* Disable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS); + if (ret) { + dev_err(di->dev, "failed to disable main watchdog\n"); + goto out; + } + + /* Set watchdog timeout */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_WD_TIMER_REG, WD_TIMER); + if (ret) { + dev_err(di->dev, "failed to set charger watchdog timeout\n"); + goto out; + } + + /* Backup battery voltage and current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_RTC, + AB8500_RTC_BACKUP_CHG_REG, + di->bat->bkup_bat_v | + di->bat->bkup_bat_i); + if (ret) { + dev_err(di->dev, "failed to setup backup battery charging\n"); + goto out; + } + + /* Enable backup battery charging */ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, + RTC_BUP_CH_ENA, RTC_BUP_CH_ENA); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + +out: + return ret; +} + +/* + * ab8500 charger driver interrupts and their respective isr + */ +static struct ab8500_charger_interrupts ab8500_charger_irq[] = { + {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler}, + {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler}, + {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler}, + {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler}, + {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler}, + {"VBUS_DET_F", ab8500_charger_vbusdetf_handler}, + {"VBUS_DET_R", ab8500_charger_vbusdetr_handler}, + {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler}, + {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler}, + {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler}, + {"USB_CHARGER_NOT_OKF", ab8500_charger_usbchargernotokf_handler}, + {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler}, + {"VBUS_OVV", ab8500_charger_vbusovv_handler}, + {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, +}; + +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ + struct ab8500_charger *di = static_di; + + dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", + __func__, bm_usb_state, mA); + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = true; + spin_unlock(&di->usb_state.usb_lock); + + di->usb_state.state = bm_usb_state; + di->usb_state.usb_current = mA; + + queue_work(di->charger_wq, &di->usb_state_changed_work); + + return; +} +EXPORT_SYMBOL(ab8500_charger_usb_state_changed); + +#if defined(CONFIG_PM) +static int ab8500_charger_resume(struct platform_device *pdev) +{ + int ret; + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + if (di->ac_conn && (di->chip_id == AB8500_CUT1P0 || + di->chip_id == AB8500_CUT1P1)) { + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* If not already pending start a new timer */ + if (!delayed_work_pending( + &di->kick_wd_work)) { + queue_delayed_work(di->charger_wq, &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + } + + /* If we still have a HW failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, 0); + } + + return 0; +} + +static int ab8500_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&di->check_hw_failure_work)) + cancel_delayed_work(&di->check_hw_failure_work); + + return 0; +} +#else +#define ab8500_charger_suspend NULL +#define ab8500_charger_resume NULL +#endif + +static int __devexit ab8500_charger_remove(struct platform_device *pdev) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + int i, irq, ret; + + /* Disable AC charging */ + ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); + + /* Disable USB charging */ + ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } + + /* Backup battery voltage and current disable */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->usb_chg.psy); + power_supply_unregister(&di->ac_chg.psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_charger_probe(struct platform_device *pdev) +{ + int irq, i, charger_status, ret = 0; + struct ab8500_platform_data *plat; + + struct ab8500_charger *di = + kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL); + if (!di) + return -ENOMEM; + + static_di = di; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + + plat = dev_get_platdata(di->parent->dev); + + /* get charger specific platform data */ + if (!plat->charger) { + dev_err(di->dev, "no charger platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->charger; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* AC supply */ + /* power_supply base class */ + di->ac_chg.psy.name = "ab8500_ac"; + di->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS; + di->ac_chg.psy.properties = ab8500_charger_ac_props; + di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props); + di->ac_chg.psy.get_property = ab8500_charger_ac_get_property; + di->ac_chg.psy.supplied_to = di->pdata->supplied_to; + di->ac_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->ac_chg.ops.enable = &ab8500_charger_ac_en; + di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->ac_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->ac_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + /* USB supply */ + /* power_supply base class */ + di->usb_chg.psy.name = "ab8500_usb"; + di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB; + di->usb_chg.psy.properties = ab8500_charger_usb_props; + di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props); + di->usb_chg.psy.get_property = ab8500_charger_usb_get_property; + di->usb_chg.psy.supplied_to = di->pdata->supplied_to; + di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->usb_chg.ops.enable = &ab8500_charger_usb_en; + di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->usb_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + + /* Create a work queue for the charger */ + di->charger_wq = + create_singlethread_workqueue("ab8500_charger_wq"); + if (di->charger_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for HW failure check */ + INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work, + ab8500_charger_check_hw_failure_work); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + INIT_DELAYED_WORK_DEFERRABLE(&di->kick_wd_work, + ab8500_charger_kick_watchdog_work); + + /* Init work for charger detection */ + INIT_WORK(&di->usb_link_status_work, + ab8500_charger_usb_link_status_work); + INIT_WORK(&di->ac_work, ab8500_charger_ac_work); + INIT_WORK(&di->detect_usb_type_work, + ab8500_charger_detect_usb_type_work); + + INIT_WORK(&di->usb_state_changed_work, + ab8500_charger_usb_state_changed_work); + + /* Init work for checking HW status */ + INIT_WORK(&di->check_usbchgnotok_work, + ab8500_charger_check_usbchargernotok_work); + INIT_WORK(&di->check_main_thermal_prot_work, + ab8500_charger_check_main_thermal_prot_work); + INIT_WORK(&di->check_usb_thermal_prot_work, + ab8500_charger_check_usb_thermal_prot_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_charger_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB8500 CID is: 0x%02x\n", di->chip_id); + + /* Initialize OVV, and other registers */ + ret = ab8500_charger_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize ABB registers\n"); + goto free_charger_wq; + } + + /* Register AC charger class */ + ret = power_supply_register(di->dev, &di->ac_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register AC charger\n"); + goto free_charger_wq; + } + + /* Register USB charger class */ + ret = power_supply_register(di->dev, &di->usb_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register USB charger\n"); + goto free_ac; + } + + /* Identify the connected charger types during startup */ + charger_status = ab8500_charger_detect_chargers(di); + if (charger_status & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + power_supply_changed(&di->ac_chg.psy); + } + + if (charger_status & USB_PW_CONN) { + dev_dbg(di->dev, "VBUS Detect during startup\n"); + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->detect_usb_type_work); + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + di->parent->charger = di; + + return ret; + +free_irq: + power_supply_unregister(&di->usb_chg.psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } +free_ac: + power_supply_unregister(&di->ac_chg.psy); +free_charger_wq: + destroy_workqueue(di->charger_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_charger_driver = { + .probe = ab8500_charger_probe, + .remove = __devexit_p(ab8500_charger_remove), + .suspend = ab8500_charger_suspend, + .resume = ab8500_charger_resume, + .driver = { + .name = "ab8500-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_charger_init(void) +{ + return platform_driver_register(&ab8500_charger_driver); +} + +static void __exit ab8500_charger_exit(void) +{ + platform_driver_unregister(&ab8500_charger_driver); +} + +subsys_initcall_sync(ab8500_charger_init); +module_exit(ab8500_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-charger"); +MODULE_DESCRIPTION("AB8500 charger management driver"); diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c new file mode 100644 index 00000000000..d141f44d2cd --- /dev/null +++ b/drivers/power/ab8500_fg.c @@ -0,0 +1,1859 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 1627 +#define QLSB_NANO_AMP_HOURS_X10 1129 + +#define SEC_TO_SAMPLE(S) (S * 4) + +#define NBR_AVG_SAMPLES 20 + +#define LOW_BAT_CHECK_INTERVAL (2 * HZ) + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +#define to_ab8500_fg_device_info(x) container_of((x), \ + struct ab8500_fg, fg_psy); + +/** + * struct ab8500_fg_interrupts - ab8500 fg interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_fg_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +enum ab8500_fg_discharge_state { + AB8500_FG_DISCHARGE_INIT, + AB8500_FG_DISCHARGE_INITMEASURING, + AB8500_FG_DISCHARGE_INIT_RECOVERY, + AB8500_FG_DISCHARGE_RECOVERY, + AB8500_FG_DISCHARGE_READOUT, + AB8500_FG_DISCHARGE_WAKEUP, +}; + +static char *discharge_state[] = { + "DISCHARGE_INIT", + "DISCHARGE_INITMEASURING", + "DISCHARGE_INIT_RECOVERY", + "DISCHARGE_RECOVERY", + "DISCHARGE_READOUT", + "DISCHARGE_WAKEUP", +}; + +enum ab8500_fg_charge_state { + AB8500_FG_CHARGE_INIT, + AB8500_FG_CHARGE_READOUT, +}; + +static char *charge_state[] = { + "CHARGE_INIT", + "CHARGE_READOUT", +}; + +struct ab8500_fg_avg_cap { + int avg; + int samples[NBR_AVG_SAMPLES]; + __kernel_time_t time_stamps[NBR_AVG_SAMPLES]; + int pos; + int nbr_samples; + int sum; +}; + +struct ab8500_fg_battery_capacity { + int max_mah_design; + int max_mah; + int mah; + int permille; + int level; + int prev_mah; + int prev_percent; + int prev_level; +}; + +struct ab8500_fg_flags { + bool fg_enabled; + bool conv_done; + bool charging; + bool fully_charged; + bool low_bat_delay; + bool low_bat; + bool bat_ovv; + bool batt_unknown; +}; + +/** + * struct ab8500_fg - ab8500 FG device information + * @dev: Pointer to the structure device + * @vbat: Battery voltage in mV + * @vbat_nom: Nominal battery voltage in mV + * @inst_curr: Instantenous battery current in mA + * @avg_curr: Average battery current in mA + * @fg_samples: Number of samples used in the FG accumulation + * @accu_charge: Accumulated charge from the last conversion + * @recovery_cnt: Counter for recovery mode + * @high_curr_cnt: Counter for high current mode + * @init_cnt: Counter for init mode + * @recovery_needed: Indicate if recovery is needed + * @high_curr_mode: Indicate if we're in high current mode + * @init_capacity: Indicate if initial capacity measuring should be done + * @discharge_state: Current discharge state + * @charge_state: Current charge state + * @flags: Structure for information about events triggered + * @bat_cap: Structure for battery capacity specific parameters + * @avg_cap: Average capacity filter + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_fg platform data + * @bat: Pointer to the ab8500_bm platform data + * @fg_psy: Structure that holds the FG specific battery properties + * @fg_wq: Work queue for running the FG algorithm + * @fg_periodic_work: Work to run the FG algorithm periodically + * @fg_low_bat_work: Work to check low bat condition + * @fg_work: Work to run the FG algorithm instantly + * @fg_acc_cur_work: Work to read the FG accumulator + * @cc_lock: Mutex for locking the CC + */ +struct ab8500_fg { + struct device *dev; + int vbat; + int vbat_nom; + int inst_curr; + int avg_curr; + int fg_samples; + int accu_charge; + int recovery_cnt; + int high_curr_cnt; + int init_cnt; + bool recovery_needed; + bool high_curr_mode; + bool init_capacity; + enum ab8500_fg_discharge_state discharge_state; + enum ab8500_fg_charge_state charge_state; + struct ab8500_fg_flags flags; + struct ab8500_fg_battery_capacity bat_cap; + struct ab8500_fg_avg_cap avg_cap; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_fg_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply fg_psy; + struct workqueue_struct *fg_wq; + struct delayed_work fg_periodic_work; + struct delayed_work fg_low_bat_work; + struct work_struct fg_work; + struct work_struct fg_acc_cur_work; + struct mutex cc_lock; +}; + +/* Main battery properties */ +static enum power_supply_property ab8500_fg_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +/* + * This array maps the raw hex value to lowbat voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_fg_lowbat_voltage_map[] = { + 2300 , + 2325 , + 2350 , + 2375 , + 2400 , + 2425 , + 2450 , + 2475 , + 2500 , + 2525 , + 2550 , + 2575 , + 2600 , + 2625 , + 2650 , + 2675 , + 2700 , + 2725 , + 2750 , + 2775 , + 2800 , + 2825 , + 2850 , + 2875 , + 2900 , + 2925 , + 2950 , + 2975 , + 3000 , + 3025 , + 3050 , + 3075 , + 3100 , + 3125 , + 3150 , + 3175 , + 3200 , + 3225 , + 3250 , + 3275 , + 3300 , + 3325 , + 3350 , + 3375 , + 3400 , + 3425 , + 3450 , + 3475 , + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3850 , +}; + +static u8 ab8500_volt_to_regval(int voltage) +{ + int i; + + if (voltage < ab8500_fg_lowbat_voltage_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) { + if (voltage < ab8500_fg_lowbat_voltage_map[i]) + return (u8) i - 1; + } + + /* If not captured above, return index of last element */ + return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1; +} + +/** + * ab8500_fg_is_low_curr() - Low or high current mode + * @di: pointer to the ab8500_fg structure + * @curr: the current to base or our decision on + * + * Low current mode if the current consumption is below a certain threshold + */ +static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr) +{ + /* + * We want to know if we're in low current mode + */ + if (curr > -di->bat->fg_params->high_curr_threshold) + return true; + else + return false; +} + +/** + * ab8500_fg_add_cap_sample() - Add capacity to average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to add to the filter + * + * A capacity is added to the filter and a new mean capacity is calculated and + * returned + */ +static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) +{ + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts.tv_sec; + avg->pos++; + + if (avg->pos == NBR_AVG_SAMPLES) + avg->pos = 0; + + if (avg->nbr_samples < NBR_AVG_SAMPLES) + avg->nbr_samples++; + + /* + * Check the time stamp for each sample. If too old, + * replace with latest sample + */ + } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + + avg->avg = avg->sum / avg->nbr_samples; + + return avg->avg; +} + +/** + * ab8500_fg_fill_cap_sample() - Fill average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to fill the filter with + * + * The capacity filter is filled with a capacity in mAh + */ +static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) +{ + int i; + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts.tv_sec; + } + + avg->pos = 0; + avg->nbr_samples = NBR_AVG_SAMPLES; + avg->sum = sample * NBR_AVG_SAMPLES; + avg->avg = sample; +} + +/** + * ab8500_fg_coulomb_counter() - enable coulomb counter + * @di: pointer to the ab8500_fg structure + * @enable: enable/disable + * + * Enable/Disable coulomb counter. + * On failure returns negative value. + */ +static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable) +{ + int ret = 0; + mutex_lock(&di->cc_lock); + if (enable) { + /* To be able to reprogram the number of samples, we have to + * first stop the CC and then enable it again */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0x00); + if (ret) + goto cc_err; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + di->fg_samples); + if (ret) + goto cc_err; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto cc_err; + + di->flags.fg_enabled = true; + } else { + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto cc_err; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto cc_err; + + di->flags.fg_enabled = false; + + } + dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", + enable, di->fg_samples); + + mutex_unlock(&di->cc_lock); + + return ret; +cc_err: + dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * + * Returns battery instantenous current(on success) else error code + */ +static int ab8500_fg_inst_curr(struct ab8500_fg *di) +{ + u8 low, high, reg_val; + static int val; + int ret = 0; + bool fg_off = false; + + mutex_lock(&di->cc_lock); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, ®_val); + if (ret < 0) + goto inst_curr_err; + + if (!(reg_val & CC_PWR_UP_ENA)) { + dev_dbg(di->dev, "%s Enable FG\n", __func__); + fg_off = true; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + SEC_TO_SAMPLE(10)); + if (ret) + goto inst_curr_err; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto inst_curr_err; + } + + /* Reset counter and Read request */ + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_CTRL_REG, (RESET_ACCU | READ_REQ)); + if (ret) + goto inst_curr_err; + + /* + * Since there is no interrupt for this, just wait for 250ms + * 250ms is one sample conversion time with 32.768 Khz RTC clock + */ + msleep(250); + + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVL_REG, &low); + if (ret < 0) + goto inst_curr_err; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVH_REG, &high); + if (ret < 0) + goto inst_curr_err; + + /* + * negative value for Discharging + * convert 2's compliment into decimal + */ + if (high & 0x10) + val = (low | (high << 8) | 0xFFFFE000); + else + val = (low | (high << 8)); + + /* + * Convert to unit value in mA + * Full scale input voltage is + * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA + * resistance is in mOhm + */ + val = ((val * 66660) / (4096 * di->bat->fg_res)); + + if (fg_off) { + dev_dbg(di->dev, "%s Disable FG\n", __func__); + + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto inst_curr_err; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto inst_curr_err; + } + + mutex_unlock(&di->cc_lock); + + return val; + +inst_curr_err: + dev_err(di->dev, "%s Get instanst current failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_acc_cur_work() - average battery current + * @work: pointer to the work_struct structure + * + * Updated the average battery current obtained from the + * coulomb counter. + */ +static void ab8500_fg_acc_cur_work(struct work_struct *work) +{ + int val; + int ret; + u8 low, med, high; + + struct ab8500_fg *di = container_of(work, + struct ab8500_fg, fg_acc_cur_work); + + mutex_lock(&di->cc_lock); + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ); + if (ret) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_LOW, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_MED, &med); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_HIGH, &high); + if (ret < 0) + goto exit; + + /* Check for sign bit in case of negative value, 2's compliment */ + if (high & 0x10) + val = (low | (med << 8) | (high << 16) | 0xFFE00000); + else + val = (low | (med << 8) | (high << 16)); + + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10)/10000; + + di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000); + di->flags.conv_done = true; + + mutex_unlock(&di->cc_lock); + + queue_work(di->fg_wq, + &di->fg_work); + + return; +exit: + dev_err(di->dev, + "Failed to read or write gas gauge registers\n"); + mutex_unlock(&di->cc_lock); + queue_work(di->fg_wq, + &di->fg_work); +} + +/** + * ab8500_fg_bat_voltage() - get battery voltage + * @di: pointer to the ab8500_fg structure + * + * Returns battery voltage(on success) else error code + */ +static int ab8500_fg_bat_voltage(struct ab8500_fg *di) +{ + int vbat; + static int prev; + + vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V); + if (vbat < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value\n", + __func__); + return prev; + } + + prev = vbat; + return vbat; +} + +/** + * ab8500_fg_volt_to_capacity() - Voltage based capacity + * @di: pointer to the ab8500_fg structure + * @voltage: The voltage to convert to a capacity + * + * Returns battery capacity in per mille based on voltage + */ +static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) +{ + int i, tbl_size; + struct v_to_cap *tbl; + int cap = 0; + + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (voltage > tbl[i].voltage) + break; + } + + if ((i > 0) && (i < tbl_size)) { + cap = interpolate(voltage, + tbl[i].voltage, + tbl[i].capacity * 10, + tbl[i-1].voltage, + tbl[i-1].capacity * 10); + } else if (i == 0) { + cap = 1000; + } else { + cap = 0; + } + + dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", + __func__, voltage, cap); + + return cap; +} + +/** + * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is not compensated + * for the voltage drop due to the load + */ +static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) +{ + di->vbat = ab8500_fg_bat_voltage(di); + return ab8500_fg_volt_to_capacity(di, di->vbat); +} + +/** + * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) +{ + int vbat_comp; + + di->inst_curr = ab8500_fg_inst_curr(di); + di->vbat = ab8500_fg_bat_voltage(di); + + /* Use Ohms law to get the load compensated voltage */ + vbat_comp = di->vbat - (di->inst_curr * + di->bat->bat_type[di->bat->batt_id].battery_resistance) / 1000; + + dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " + "R: %dmOhm, Current: %dmA\n", + __func__, + di->vbat, + vbat_comp, + di->bat->bat_type[di->bat->batt_id].battery_resistance, + di->inst_curr); + + return ab8500_fg_volt_to_capacity(di, vbat_comp); +} + +/** + * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in permille + */ +static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah) +{ + return (cap_mah * 1000) / di->bat_cap.max_mah_design; +} + +/** + * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh + * @di: pointer to the ab8500_fg structure + * @cap_pm: capacity in permille + * + * Converts capacity in permille to capacity in mAh + */ +static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm) +{ + return cap_pm * di->bat_cap.max_mah_design / 1000; +} + +/** + * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in uWh + */ +static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah) +{ + u64 div_res; + u32 div_rem; + + div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); + div_rem = do_div(div_res, 1000); + + /* Make sure to round upwards if necessary */ + if (div_rem >= 1000 / 2) + div_res++; + + return (int) div_res; +} + +/** + * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. The filter is filled with this capacity + */ +static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) +{ + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + /* + * We force capacity to 100% as long as the algorithm + * reports that it's full. + */ + if (di->bat_cap.mah >= di->bat_cap.max_mah_design || + di->flags.fully_charged) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + /* We need to update battery voltage and inst current when charging */ + di->vbat = ab8500_fg_bat_voltage(di); + di->inst_curr = ab8500_fg_inst_curr(di); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage + * @di: pointer to the ab8500_fg structure + * @comp: if voltage should be load compensated before capacity calc + * + * Return the capacity in mAh based on the battery voltage. The voltage can + * either be load compensated or not. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp) +{ + int permille, mah; + + if (comp) + permille = ab8500_fg_load_comp_volt_to_capacity(di); + else + permille = ab8500_fg_uncomp_volt_to_capacity(di); + + mah = ab8500_fg_convert_permille_to_mah(di, permille); + + di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di) +{ + int permille_volt, permille; + + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + if (di->bat_cap.mah >= di->bat_cap.max_mah_design) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + /* + * Check against voltage based capacity. It can not be lower + * than what the uncompensated voltage says + */ + permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + permille_volt = ab8500_fg_uncomp_volt_to_capacity(di); + + if (permille < permille_volt) { + di->bat_cap.permille = permille_volt; + di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di, + di->bat_cap.permille); + + dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", + __func__, + permille, + permille_volt); + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + } else { + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + } + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_capacity_level() - Get the battery capacity level + * @di: pointer to the ab8500_fg structure + * + * Get the battery capacity level based on the capacity in percent + */ +static int ab8500_fg_capacity_level(struct ab8500_fg *di) +{ + int ret, percent; + + percent = di->bat_cap.permille / 10; + + if (percent <= di->bat->cap_levels->critical || + di->flags.low_bat) + ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (percent <= di->bat->cap_levels->low) + ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (percent <= di->bat->cap_levels->normal) + ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (percent <= di->bat->cap_levels->high) + ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + return ret; +} + +/** + * ab8500_fg_check_capacity_limits() - Check if capacity has changed + * @di: pointer to the ab8500_fg structure + * @init: capacity is allowed to go up in init mode + * + * Check if capacity or capacity limit has changed and notify the system + * about it using the power_supply framework + */ +static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) +{ + bool changed = false; + + di->bat_cap.level = ab8500_fg_capacity_level(di); + + if (di->bat_cap.level != di->bat_cap.prev_level) { + /* + * We do not allow reported capacity level to go up + * unless we're charging or if we're in init + */ + if (!(!di->flags.charging && di->bat_cap.level > + di->bat_cap.prev_level) || init) { + dev_dbg(di->dev, "level changed from %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + di->bat_cap.prev_level = di->bat_cap.level; + changed = true; + } else { + dev_dbg(di->dev, "level not allowed to go up " + "since no charger is connected: %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + } + } + + /* + * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate + * shutdown + */ + if (di->flags.low_bat) { + dev_dbg(di->dev, "Battery low, set capacity to 0\n"); + di->bat_cap.prev_percent = 0; + di->bat_cap.permille = 0; + di->bat_cap.prev_mah = 0; + di->bat_cap.mah = 0; + changed = true; + } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) { + if (di->bat_cap.permille / 10 == 0) { + /* + * We will not report 0% unless we've got + * the LOW_BAT IRQ, no matter what the FG + * algorithm says. + */ + di->bat_cap.prev_percent = 1; + di->bat_cap.permille = 1; + di->bat_cap.prev_mah = 1; + di->bat_cap.mah = 1; + + changed = true; + } else if (!(!di->flags.charging && + (di->bat_cap.permille / 10) > + di->bat_cap.prev_percent) || init) { + /* + * We do not allow reported capacity to go up + * unless we're charging or if we're in init + */ + dev_dbg(di->dev, + "capacity changed from %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } else { + dev_dbg(di->dev, "capacity not allowed to go up since " + "no charger is connected: %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + } + } + + if (changed) + power_supply_changed(&di->fg_psy); + +} + +static void ab8500_fg_charge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", + di->charge_state, + charge_state[di->charge_state], + new_state, + charge_state[new_state]); + + di->charge_state = new_state; +} + +static void ab8500_fg_discharge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", + di->discharge_state, + discharge_state[di->discharge_state], + new_state, + discharge_state[new_state]); + + di->discharge_state = new_state; +} + +/** + * ab8500_fg_algorithm_charging() - FG algorithm for when charging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're charging + */ +static void ab8500_fg_algorithm_charging(struct ab8500_fg *di) +{ + /* + * If we change to discharge mode + * we should start with recovery + */ + if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY) + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INIT_RECOVERY); + + switch (di->charge_state) { + case AB8500_FG_CHARGE_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_charging); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT); + + break; + + case AB8500_FG_CHARGE_READOUT: + /* + * Read the FG and calculate the new capacity + */ + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + ab8500_fg_calc_cap_charging(di); + + break; + + default: + break; + } + + /* Check capacity limits */ + ab8500_fg_check_capacity_limits(di, false); +} + +/** + * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're discharging + */ +static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) +{ + int sleep_time; + + /* If we change to charge mode we should start with init */ + if (di->charge_state != AB8500_FG_CHARGE_INIT) + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + + switch (di->discharge_state) { + case AB8500_FG_DISCHARGE_INIT: + /* We use the FG IRQ to work on */ + di->init_cnt = 0; + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INITMEASURING); + + /* Intentional fallthrough */ + + case AB8500_FG_DISCHARGE_INITMEASURING: + /* + * Discard a number of samples during startup. + * After that, use compensated voltage for a few + * samples to get an initial capacity. + * Then go to READOUT + */ + sleep_time = di->bat->fg_params->init_timer; + + /* Discard the first [x] seconds */ + if (di->init_cnt > + di->bat->fg_params->init_discard_time) { + + ab8500_fg_calc_cap_discharge_voltage(di, true); + + ab8500_fg_check_capacity_limits(di, true); + } + + di->init_cnt += sleep_time; + if (di->init_cnt > + di->bat->fg_params->init_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + + break; + + case AB8500_FG_DISCHARGE_INIT_RECOVERY: + di->recovery_cnt = 0; + di->recovery_needed = true; + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + /* Intentional fallthrough */ + + case AB8500_FG_DISCHARGE_RECOVERY: + sleep_time = di->bat->fg_params->recovery_sleep_timer; + + /* + * We should check the power consumption + * If low, go to READOUT (after x min) or + * RECOVERY_SLEEP if time left. + * If high, go to READOUT + */ + di->inst_curr = ab8500_fg_inst_curr(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + if (di->recovery_cnt > + di->bat->fg_params->recovery_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + di->recovery_needed = false; + } else { + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + sleep_time * HZ); + } + di->recovery_cnt += sleep_time; + } else { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + + break; + + case AB8500_FG_DISCHARGE_READOUT: + di->inst_curr = ab8500_fg_inst_curr(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + /* Detect mode change */ + if (di->high_curr_mode) { + di->high_curr_mode = false; + di->high_curr_cnt = 0; + } + + if (di->recovery_needed) { + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + 0); + + break; + } + + ab8500_fg_calc_cap_discharge_voltage(di, true); + } else { + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + /* Detect mode change */ + if (!di->high_curr_mode) { + di->high_curr_mode = true; + di->high_curr_cnt = 0; + } + + di->high_curr_cnt += + di->bat->fg_params->accu_high_curr; + if (di->high_curr_cnt > + di->bat->fg_params->high_curr_time) + di->recovery_needed = true; + + ab8500_fg_calc_cap_discharge_fg(di); + } + + ab8500_fg_check_capacity_limits(di, false); + + break; + + case AB8500_FG_DISCHARGE_WAKEUP: + ab8500_fg_coulomb_counter(di, true); + di->inst_curr = ab8500_fg_inst_curr(di); + + ab8500_fg_calc_cap_discharge_voltage(di, true); + + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + /* Re-program number of samples set above */ + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_READOUT); + + ab8500_fg_check_capacity_limits(di, false); + + break; + + default: + break; + } +} + +/** + * ab8500_fg_algorithm() - Entry point for the FG algorithm + * @di: pointer to the ab8500_fg structure + * + * Entry point for the battery capacity calculation state machine + */ +static void ab8500_fg_algorithm(struct ab8500_fg *di) +{ + if (di->flags.charging) + ab8500_fg_algorithm_charging(di); + else + ab8500_fg_algorithm_discharging(di); + + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %d\n", + di->bat_cap.max_mah_design, + di->bat_cap.mah, + di->bat_cap.permille, + di->bat_cap.level, + di->bat_cap.prev_mah, + di->bat_cap.prev_percent, + di->bat_cap.prev_level, + di->vbat, + di->inst_curr, + di->avg_curr, + di->accu_charge, + di->flags.charging, + di->charge_state, + di->discharge_state, + di->high_curr_mode, + di->recovery_needed); +} + +/** + * ab8500_fg_periodic_work() - Run the FG state machine periodically + * @work: pointer to the work_struct structure + * + * Work queue function for periodic work + */ +static void ab8500_fg_periodic_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_periodic_work.work); + + if (di->init_capacity) { + /* A dummy read that will return 0 */ + di->inst_curr = ab8500_fg_inst_curr(di); + /* Get an initial capacity calculation */ + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_check_capacity_limits(di, true); + di->init_capacity = false; + } else { + ab8500_fg_algorithm(di); + } +} + +/** + * ab8500_fg_low_bat_work() - Check LOW_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the LOW_BAT condition + */ +static void ab8500_fg_low_bat_work(struct work_struct *work) +{ + int vbat; + + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_low_bat_work.work); + + vbat = ab8500_fg_bat_voltage(di); + + /* Check if LOW_BAT still fulfilled */ + if (vbat < di->bat->fg_params->lowbat_threshold) { + di->flags.low_bat = true; + dev_warn(di->dev, "Battery voltage still LOW\n"); + + /* + * We need to re-schedule this check to be able to detect + * if the voltage increases again during charging + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } else { + di->flags.low_bat = false; + dev_warn(di->dev, "Battery voltage OK again\n"); + } + + /* This is needed to dispatch LOW_BAT */ + ab8500_fg_check_capacity_limits(di, false); + + /* Set this flag to check if LOW_BAT IRQ still occurs */ + di->flags.low_bat_delay = false; +} + +/** + * ab8500_fg_instant_work() - Run the FG state machine instantly + * @work: pointer to the work_struct structure + * + * Work queue function for instant work + */ +static void ab8500_fg_instant_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work); + + ab8500_fg_algorithm(di); +} + +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + queue_work(di->fg_wq, &di->fg_acc_cur_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_batt_ovv_handler() - Battery OVV occured + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + dev_dbg(di->dev, "Battery OVV\n"); + di->flags.bat_ovv = true; + + power_supply_changed(&di->fg_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + if (!di->flags.low_bat_delay) { + dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); + di->flags.low_bat_delay = true; + /* + * Start a timer to check LOW_BAT again after some time + * This is done to avoid shutdown on single voltage dips + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + return IRQ_HANDLED; +} + +/** + * ab8500_fg_get_property() - get the fg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * fg properties by reading the sysfs files. + * voltage_now: battery voltage + * current_now: battery instant current + * current_avg: battery average current + * charge_full_design: capacity where battery is considered full + * charge_now: battery capacity in nAh + * capacity: capacity in percent + * capacity_level: capacity level + * + * Returns error code in case of failure else 0 on success + */ +static int ab8500_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + /* + * If battery is identified as unknown and charging of unknown + * batteries is disabled, we always report 100% capacity and + * capacity level UNKNOWN, since we can't calculate + * remaining capacity + */ + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->flags.bat_ovv) + val->intval = 47500000; + else + val->intval = di->vbat * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->inst_curr * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->avg_curr * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah_design); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + else + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.prev_mah); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->bat_cap.max_mah_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->bat_cap.max_mah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = di->bat_cap.max_mah; + else + val->intval = di->bat_cap.prev_mah; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = 100; + else + val->intval = di->bat_cap.prev_percent; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else + val->intval = di->bat_cap.prev_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_fg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_fg_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) + break; + di->flags.charging = false; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + }; + default: + break; + }; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->flags.batt_unknown = false; + else + di->flags.batt_unknown = true; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_fg_init_hw_registers() - Set up FG related registers + * @di: pointer to the ab8500_fg structure + * + * Set up battery OVV, low battery voltage registers + */ +static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) +{ + int ret; + + /* Set up VBAT OVV register */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_BATT_OVV, + (BATT_OVV_ENA | BATT_OVV_TH_4P75)); + if (ret) { + dev_err(di->dev, "failed to set BATT_OVV\n"); + goto out; + } + + /* Low Battery Voltage */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_LOW_BAT_REG, + ab8500_volt_to_regval( + di->bat->fg_params->lowbat_threshold) << 1 | + LOW_BAT_ENABLE); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + goto out; + } + +out: + return ret; +} + +/** + * ab8500_fg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_fg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_fg *di = to_ab8500_fg_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->fg_psy, ab8500_fg_get_ext_psy_data); +} + +#if defined(CONFIG_PM) +static int ab8500_fg_resume(struct platform_device *pdev) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab8500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab8500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab8500_fg_suspend NULL +#define ab8500_fg_resume NULL +#endif + +static int __devexit ab8500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* Disable coulomb counter */ + ret = ab8500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->fg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + return ret; +} + +/* ab8500 fg driver interrupts and their respective isr */ +static struct ab8500_fg_interrupts ab8500_fg_irq[] = { + {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, + {"BATT_OVV", ab8500_fg_batt_ovv_handler}, + {"LOW_BAT_F", ab8500_fg_lowbatf_handler}, +}; + +static int __devinit ab8500_fg_probe(struct platform_device *pdev) +{ + int i, irq; + struct ab8500_platform_data *plat; + int ret = 0; + + struct ab8500_fg *di = + kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + mutex_init(&di->cc_lock); + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + plat = dev_get_platdata(di->parent->dev); + + /* get fg specific platform data */ + if (!plat->fg) { + dev_err(di->dev, "no fg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->fg; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + di->fg_psy.name = "ab8500_fg"; + di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->fg_psy.properties = ab8500_fg_props; + di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props); + di->fg_psy.get_property = ab8500_fg_get_property; + di->fg_psy.supplied_to = di->pdata->supplied_to; + di->fg_psy.num_supplicants = di->pdata->num_supplicants; + di->fg_psy.external_power_changed = ab8500_fg_external_power_changed; + + di->bat_cap.max_mah_design = MILLI_TO_MICRO * + di->bat->bat_type[di->bat->batt_id].charge_full_design; + + di->bat_cap.max_mah = di->bat_cap.max_mah_design; + + di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage; + + di->init_capacity = true; + + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq"); + if (di->fg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for running the fg algorithm instantly */ + INIT_WORK(&di->fg_work, ab8500_fg_instant_work); + + /* Init work for getting the battery accumulated current */ + INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work); + + /* Work delayed Queue to run the state machine */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work, + ab8500_fg_periodic_work); + + /* Work to check low battery condition */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work, + ab8500_fg_low_bat_work); + + /* Initialize OVV, and other registers */ + ret = ab8500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto free_fg_wq; + } + + /* Consider battery unknown until we're informed otherwise */ + di->flags.batt_unknown = true; + + /* Register FG power supply class */ + ret = power_supply_register(di->dev, &di->fg_psy); + if (ret) { + dev_err(di->dev, "failed to register FG psy\n"); + goto free_fg_wq; + } + + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_fg_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_fg_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_fg_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + return ret; + +free_irq: + power_supply_unregister(&di->fg_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + free_irq(irq, di); + } +free_fg_wq: + destroy_workqueue(di->fg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_fg_driver = { + .probe = ab8500_fg_probe, + .remove = __devexit_p(ab8500_fg_remove), + .suspend = ab8500_fg_suspend, + .resume = ab8500_fg_resume, + .driver = { + .name = "ab8500-fg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_fg_init(void) +{ + return platform_driver_register(&ab8500_fg_driver); +} + +static void __exit ab8500_fg_exit(void) +{ + platform_driver_unregister(&ab8500_fg_driver); +} + +subsys_initcall_sync(ab8500_fg_init); +module_exit(ab8500_fg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-fg"); +MODULE_DESCRIPTION("AB8500 Fuel Gauge driver"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index d7ed20f293d..ef24627c1a3 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -281,6 +281,14 @@ config REGULATOR_DB8500_PRCMU This driver supports the voltage domain regulators controlled by the DB8500 PRCMU +config REGULATOR_AB8500_DEBUG + bool "AB8500 regulator debug" + depends on REGULATOR_AB8500 + help + Say Y here to add debug functionality for ST-Ericsson + ab8500 regulators. This is a module that exposes a + number of settings and debug output in debugfs. + config REGULATOR_TPS6586X tristate "TI TPS6586X Power regulators" depends on MFD_TPS6586X diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 3932d2ec38f..7fbe53b7a30 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -43,5 +43,6 @@ obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o +obj-$(CONFIG_REGULATOR_AB8500_DEBUG) += ab8500-debug.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/ab8500-debug.c b/drivers/regulator/ab8500-debug.c new file mode 100644 index 00000000000..253c7df85b0 --- /dev/null +++ b/drivers/regulator/ab8500-debug.c @@ -0,0 +1,2000 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Bengt Jonsson for ST-Ericsson. + * + * License Terms: GNU General Public License v2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* U8500_BACKUPRAM1_BASE */ + +/* board profile address - to determine if suspend-force is default */ +#define BOOT_INFO_BACKUPRAM1 (U8500_BACKUPRAM1_BASE + 0xffc) +#define BOARD_PROFILE_BACKUPRAM1 (0x3) + +/* board profile option */ +#define OPTION_BOARD_VERSION_POWER 0x80 + +/* for error prints */ +struct device *dev; +struct platform_device *pdev; + +/* setting for suspend force (disabled by default) */ +static bool setting_suspend_force; + +/* + * regulator states + */ +enum ab8500_regulator_state_id { + AB8500_REGULATOR_STATE_INIT, + AB8500_REGULATOR_STATE_SUSPEND, + AB8500_REGULATOR_STATE_SUSPEND_CORE, + AB8500_REGULATOR_STATE_RESUME_CORE, + AB8500_REGULATOR_STATE_RESUME, + AB8500_REGULATOR_STATE_CURRENT, + NUM_REGULATOR_STATE +}; + +static const char *regulator_state_name[NUM_REGULATOR_STATE] = { + [AB8500_REGULATOR_STATE_INIT] = "init", + [AB8500_REGULATOR_STATE_SUSPEND] = "suspend", + [AB8500_REGULATOR_STATE_SUSPEND_CORE] = "suspend-core", + [AB8500_REGULATOR_STATE_RESUME_CORE] = "resume-core", + [AB8500_REGULATOR_STATE_RESUME] = "resume", + [AB8500_REGULATOR_STATE_CURRENT] = "current", +}; + +/* + * regulator register definitions + */ +enum ab8500_register_id { + AB8500_REGU_NOUSE, /* if not defined */ + AB8500_REGU_REQUEST_CTRL1, + AB8500_REGU_REQUEST_CTRL2, + AB8500_REGU_REQUEST_CTRL3, + AB8500_REGU_REQUEST_CTRL4, + AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + AB8500_REGU_HW_HP_REQ1_VALID1, + AB8500_REGU_HW_HP_REQ1_VALID2, + AB8500_REGU_HW_HP_REQ2_VALID1, + AB8500_REGU_HW_HP_REQ2_VALID2, + AB8500_REGU_SW_HP_REQ_VALID1, + AB8500_REGU_SW_HP_REQ_VALID2, + AB8500_REGU_SYSCLK_REQ1_VALID, + AB8500_REGU_SYSCLK_REQ2_VALID, + AB8500_REGU_MISC1, + AB8500_REGU_OTG_SUPPLY_CTRL, + AB8500_REGU_VUSB_CTRL, + AB8500_REGU_VAUDIO_SUPPLY, + AB8500_REGU_CTRL1_VAMIC, + AB8500_REGU_ARM_REGU1, + AB8500_REGU_ARM_REGU2, + AB8500_REGU_VAPE_REGU, + AB8500_REGU_VSMPS1_REGU, + AB8500_REGU_VSMPS2_REGU, + AB8500_REGU_VSMPS3_REGU, + AB8500_REGU_VPLL_VANA_REGU, + AB8500_REGU_VREF_DDR, + AB8500_REGU_EXT_SUPPLY_REGU, + AB8500_REGU_VAUX12_REGU, + AB8500_REGU_VRF1_VAUX3_REGU, + AB8500_REGU_VARM_SEL1, + AB8500_REGU_VARM_SEL2, + AB8500_REGU_VARM_SEL3, + AB8500_REGU_VAPE_SEL1, + AB8500_REGU_VAPE_SEL2, + AB8500_REGU_VAPE_SEL3, + AB8500_REGU_VBB_SEL1, + AB8500_REGU_VBB_SEL2, + AB8500_REGU_VSMPS1_SEL1, + AB8500_REGU_VSMPS1_SEL2, + AB8500_REGU_VSMPS1_SEL3, + AB8500_REGU_VSMPS2_SEL1, + AB8500_REGU_VSMPS2_SEL2, + AB8500_REGU_VSMPS2_SEL3, + AB8500_REGU_VSMPS3_SEL1, + AB8500_REGU_VSMPS3_SEL2, + AB8500_REGU_VSMPS3_SEL3, + AB8500_REGU_VAUX1_SEL, + AB8500_REGU_VAUX2_SEL, + AB8500_REGU_VRF1_VAUX3_SEL, + AB8500_REGU_CTRL_EXT_SUP, + AB8500_REGU_VMOD_REGU, + AB8500_REGU_VMOD_SEL1, + AB8500_REGU_VMOD_SEL2, + AB8500_REGU_CTRL_DISCH, + AB8500_REGU_CTRL_DISCH2, + AB8500_OTHER_SYSCLK_CTRL, /* Other */ + AB8500_OTHER_VSIM_SYSCLK_CTRL, /* Other */ + AB8500_OTHER_SYSULPCLK_CTRL1, /* Other */ + AB8500_OTHER_TVOUT_CTRL, /* Other */ + NUM_AB8500_REGISTER +}; + +struct ab8500_register { + const char *name; + u8 bank; + u8 addr; +}; + +static struct ab8500_register + ab8500_register[NUM_AB8500_REGISTER] = { + [AB8500_REGU_REQUEST_CTRL1] = { + .name = "ReguRequestCtrl1", + .bank = 0x03, + .addr = 0x03, + }, + [AB8500_REGU_REQUEST_CTRL2] = { + .name = "ReguRequestCtrl2", + .bank = 0x03, + .addr = 0x04, + }, + [AB8500_REGU_REQUEST_CTRL3] = { + .name = "ReguRequestCtrl3", + .bank = 0x03, + .addr = 0x05, + }, + [AB8500_REGU_REQUEST_CTRL4] = { + .name = "ReguRequestCtrl4", + .bank = 0x03, + .addr = 0x06, + }, + [AB8500_REGU_SYSCLK_REQ1_HP_VALID1] = { + .name = "ReguSysClkReq1HPValid", + .bank = 0x03, + .addr = 0x07, + }, + [AB8500_REGU_SYSCLK_REQ1_HP_VALID2] = { + .name = "ReguSysClkReq1HPValid2", + .bank = 0x03, + .addr = 0x08, + }, + [AB8500_REGU_HW_HP_REQ1_VALID1] = { + .name = "ReguHwHPReq1Valid1", + .bank = 0x03, + .addr = 0x09, + }, + [AB8500_REGU_HW_HP_REQ1_VALID2] = { + .name = "ReguHwHPReq1Valid2", + .bank = 0x03, + .addr = 0x0a, + }, + [AB8500_REGU_HW_HP_REQ2_VALID1] = { + .name = "ReguHwHPReq2Valid1", + .bank = 0x03, + .addr = 0x0b, + }, + [AB8500_REGU_HW_HP_REQ2_VALID2] = { + .name = "ReguHwHPReq2Valid2", + .bank = 0x03, + .addr = 0x0c, + }, + [AB8500_REGU_SW_HP_REQ_VALID1] = { + .name = "ReguSwHPReqValid1", + .bank = 0x03, + .addr = 0x0d, + }, + [AB8500_REGU_SW_HP_REQ_VALID2] = { + .name = "ReguSwHPReqValid2", + .bank = 0x03, + .addr = 0x0e, + }, + [AB8500_REGU_SYSCLK_REQ1_VALID] = { + .name = "ReguSysClkReqValid1", + .bank = 0x03, + .addr = 0x0f, + }, + [AB8500_REGU_SYSCLK_REQ2_VALID] = { + .name = "ReguSysClkReqValid2", + .bank = 0x03, + .addr = 0x10, + }, + [AB8500_REGU_MISC1] = { + .name = "ReguMisc1", + .bank = 0x03, + .addr = 0x80, + }, + [AB8500_REGU_OTG_SUPPLY_CTRL] = { + .name = "OTGSupplyCtrl", + .bank = 0x03, + .addr = 0x81, + }, + [AB8500_REGU_VUSB_CTRL] = { + .name = "VusbCtrl", + .bank = 0x03, + .addr = 0x82, + }, + [AB8500_REGU_VAUDIO_SUPPLY] = { + .name = "VaudioSupply", + .bank = 0x03, + .addr = 0x83, + }, + [AB8500_REGU_CTRL1_VAMIC] = { + .name = "ReguCtrl1VAmic", + .bank = 0x03, + .addr = 0x84, + }, + [AB8500_REGU_ARM_REGU1] = { + .name = "ArmRegu1", + .bank = 0x04, + .addr = 0x00, + }, + [AB8500_REGU_ARM_REGU2] = { + .name = "ArmRegu2", + .bank = 0x04, + .addr = 0x01, + }, + [AB8500_REGU_VAPE_REGU] = { + .name = "VapeRegu", + .bank = 0x04, + .addr = 0x02, + }, + [AB8500_REGU_VSMPS1_REGU] = { + .name = "Vsmps1Regu", + .bank = 0x04, + .addr = 0x03, + }, + [AB8500_REGU_VSMPS2_REGU] = { + .name = "Vsmps2Regu", + .bank = 0x04, + .addr = 0x04, + }, + [AB8500_REGU_VSMPS3_REGU] = { + .name = "Vsmps3Regu", + .bank = 0x04, + .addr = 0x05, + }, + [AB8500_REGU_VPLL_VANA_REGU] = { + .name = "VpllVanaRegu", + .bank = 0x04, + .addr = 0x06, + }, + [AB8500_REGU_VREF_DDR] = { + .name = "VrefDDR", + .bank = 0x04, + .addr = 0x07, + }, + [AB8500_REGU_EXT_SUPPLY_REGU] = { + .name = "ExtSupplyRegu", + .bank = 0x04, + .addr = 0x08, + }, + [AB8500_REGU_VAUX12_REGU] = { + .name = "Vaux12Regu", + .bank = 0x04, + .addr = 0x09, + }, + [AB8500_REGU_VRF1_VAUX3_REGU] = { + .name = "VRF1Vaux3Regu", + .bank = 0x04, + .addr = 0x0a, + }, + [AB8500_REGU_VARM_SEL1] = { + .name = "VarmSel1", + .bank = 0x04, + .addr = 0x0b, + }, + [AB8500_REGU_VARM_SEL2] = { + .name = "VarmSel2", + .bank = 0x04, + .addr = 0x0c, + }, + [AB8500_REGU_VARM_SEL3] = { + .name = "VarmSel3", + .bank = 0x04, + .addr = 0x0d, + }, + [AB8500_REGU_VAPE_SEL1] = { + .name = "VapeSel1", + .bank = 0x04, + .addr = 0x0e, + }, + [AB8500_REGU_VAPE_SEL2] = { + .name = "VapeSel2", + .bank = 0x04, + .addr = 0x0f, + }, + [AB8500_REGU_VAPE_SEL3] = { + .name = "VapeSel3", + .bank = 0x04, + .addr = 0x10, + }, + [AB8500_REGU_VBB_SEL1] = { + .name = "VBBSel1", + .bank = 0x04, + .addr = 0x11, + }, + [AB8500_REGU_VBB_SEL2] = { + .name = "VBBSel2", + .bank = 0x04, + .addr = 0x12, + }, + [AB8500_REGU_VSMPS1_SEL1] = { + .name = "Vsmps1Sel1", + .bank = 0x04, + .addr = 0x13, + }, + [AB8500_REGU_VSMPS1_SEL2] = { + .name = "Vsmps1Sel2", + .bank = 0x04, + .addr = 0x14, + }, + [AB8500_REGU_VSMPS1_SEL3] = { + .name = "Vsmps1Sel3", + .bank = 0x04, + .addr = 0x15, + }, + [AB8500_REGU_VSMPS2_SEL1] = { + .name = "Vsmps2Sel1", + .bank = 0x04, + .addr = 0x17, + }, + [AB8500_REGU_VSMPS2_SEL2] = { + .name = "Vsmps2Sel2", + .bank = 0x04, + .addr = 0x18, + }, + [AB8500_REGU_VSMPS2_SEL3] = { + .name = "Vsmps2Sel3", + .bank = 0x04, + .addr = 0x19, + }, + [AB8500_REGU_VSMPS3_SEL1] = { + .name = "Vsmps3Sel1", + .bank = 0x04, + .addr = 0x1b, + }, + [AB8500_REGU_VSMPS3_SEL2] = { + .name = "Vsmps3Sel2", + .bank = 0x04, + .addr = 0x1c, + }, + [AB8500_REGU_VSMPS3_SEL3] = { + .name = "Vsmps3Sel3", + .bank = 0x04, + .addr = 0x1d, + }, + [AB8500_REGU_VAUX1_SEL] = { + .name = "Vaux1Sel", + .bank = 0x04, + .addr = 0x1f, + }, + [AB8500_REGU_VAUX2_SEL] = { + .name = "Vaux2Sel", + .bank = 0x04, + .addr = 0x20, + }, + [AB8500_REGU_VRF1_VAUX3_SEL] = { + .name = "VRF1Vaux3Sel", + .bank = 0x04, + .addr = 0x21, + }, + [AB8500_REGU_CTRL_EXT_SUP] = { + .name = "ReguCtrlExtSup", + .bank = 0x04, + .addr = 0x22, + }, + [AB8500_REGU_VMOD_REGU] = { + .name = "VmodRegu", + .bank = 0x04, + .addr = 0x40, + }, + [AB8500_REGU_VMOD_SEL1] = { + .name = "VmodSel1", + .bank = 0x04, + .addr = 0x41, + }, + [AB8500_REGU_VMOD_SEL2] = { + .name = "VmodSel2", + .bank = 0x04, + .addr = 0x42, + }, + [AB8500_REGU_CTRL_DISCH] = { + .name = "ReguCtrlDisch", + .bank = 0x04, + .addr = 0x43, + }, + [AB8500_REGU_CTRL_DISCH2] = { + .name = "ReguCtrlDisch2", + .bank = 0x04, + .addr = 0x44, + }, + /* Outside regulator banks */ + [AB8500_OTHER_SYSCLK_CTRL] = { + .name = "SysClkCtrl", + .bank = 0x02, + .addr = 0x0c, + }, + [AB8500_OTHER_VSIM_SYSCLK_CTRL] = { + .name = "VsimSysClkCtrl", + .bank = 0x02, + .addr = 0x33, + }, + [AB8500_OTHER_SYSULPCLK_CTRL1] = { + .name = "SysUlpClkCtrl1", + .bank = 0x02, + .addr = 0x0b, + }, + [AB8500_OTHER_TVOUT_CTRL] = { + .name = "TVoutCtrl", + .bank = 0x06, + .addr = 0x80, + }, +}; + +static u8 ab8500_register_state[NUM_REGULATOR_STATE][NUM_AB8500_REGISTER]; +static bool ab8500_register_state_saved[NUM_REGULATOR_STATE]; +static bool ab8500_register_state_save = true; + +static int ab8500_regulator_record_state(int state) +{ + u8 val; + int i; + int ret; + + /* check arguments */ + if ((state > NUM_REGULATOR_STATE) || (state < 0)) { + dev_err(dev, "Wrong state specified\n"); + return -EINVAL; + } + + /* record */ + if (!ab8500_register_state_save) + goto exit; + + ab8500_register_state_saved[state] = true; + + for (i = 1; i < NUM_AB8500_REGISTER; i++) { + ret = abx500_get_register_interruptible(dev, + ab8500_register[i].bank, + ab8500_register[i].addr, + &val); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + ab8500_register_state[state][i] = val; + } +exit: + return 0; +} + +/* + * regulator register dump + */ +static int ab8500_regulator_dump_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int state, reg_id, i; + int err; + + /* record current state */ + ab8500_regulator_record_state(AB8500_REGULATOR_STATE_CURRENT); + + /* print dump header */ + err = seq_printf(s, "ab8500-regulator dump:\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print states */ + for (state = NUM_REGULATOR_STATE - 1; state >= 0; state--) { + if (ab8500_register_state_saved[state]) + err = seq_printf(s, "%16s saved -------", + regulator_state_name[state]); + else + err = seq_printf(s, "%12s not saved -------", + regulator_state_name[state]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + for (i = 0; i < NUM_REGULATOR_STATE; i++) { + if (i < state) + err = seq_printf(s, "-----"); + else if (i == state) + err = seq_printf(s, "----+"); + else + err = seq_printf(s, " |"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", + __LINE__); + } + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + } + + /* print labels */ + err = seq_printf(s, "\n addr\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + /* dump registers */ + for (reg_id = 1; reg_id < NUM_AB8500_REGISTER; reg_id++) { + err = seq_printf(s, "%22s 0x%02x%02x:", + ab8500_register[reg_id].name, + ab8500_register[reg_id].bank, + ab8500_register[reg_id].addr); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + + for (state = 0; state < NUM_REGULATOR_STATE; state++) { + err = seq_printf(s, " 0x%02x", + ab8500_register_state[state][reg_id]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + } + + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + } + + return 0; +} + +static int ab8500_regulator_dump_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_regulator_dump_print, inode->i_private); +} + +static const struct file_operations ab8500_regulator_dump_fops = { + .open = ab8500_regulator_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +/* + * regulator status print + */ +enum ab8500_regulator_id { + AB8500_VARM, + AB8500_VBBP, + AB8500_VBBN, + AB8500_VAPE, + AB8500_VSMPS1, + AB8500_VSMPS2, + AB8500_VSMPS3, + AB8500_VPLL, + AB8500_VREFDDR, + AB8500_VMOD, + AB8500_VEXTSUPPLY1, + AB8500_VEXTSUPPLY2, + AB8500_VEXTSUPPLY3, + AB8500_VRF1, + AB8500_VANA, + AB8500_VAUX1, + AB8500_VAUX2, + AB8500_VAUX3, + AB8500_VINTCORE, + AB8500_VTVOUT, + AB8500_VAUDIO, + AB8500_VANAMIC1, + AB8500_VANAMIC2, + AB8500_VDMIC, + AB8500_VUSB, + AB8500_VOTG, + AB8500_VBUSBIS, + AB8500_NUM_REGULATORS, +}; + +/* + * regulator_voltage + */ +struct regulator_volt { + u8 value; + int volt; +}; + +struct regulator_volt_range { + struct regulator_volt start; + struct regulator_volt step; + struct regulator_volt end; +}; + +/* + * ab8500_regulator + * @name + * @update_regid + * @update_mask + * @update_val[4] {off, on, hw, lp} + * @hw_mode_regid + * @hw_mode_mask + * @hw_mode_val[4] {hp/lp, hp/off, hp, hp} + * @hw_valid_regid[4] {sysclkreq1, hw1, hw2, sw} + * @hw_valid_mask[4] {sysclkreq1, hw1, hw2, sw} + * @vsel_sel_regid + * @vsel_sel_mask + * @vsel_val[333] {sel1, sel2, sel3, sel3} + * @vsel_regid + * @vsel_mask + * @vsel_range + * @vsel_range_len + */ +struct ab8500_regulator { + const char *name; + int update_regid; + u8 update_mask; + u8 update_val[4]; + int hw_mode_regid; + u8 hw_mode_mask; + u8 hw_mode_val[4]; + int hw_valid_regid[4]; + u8 hw_valid_mask[4]; + int vsel_sel_regid; + u8 vsel_sel_mask; + u8 vsel_sel_val[4]; + int vsel_regid[3]; + u8 vsel_mask[3]; + struct regulator_volt_range const *vsel_range[3]; + int vsel_range_len[3]; +}; + +static const char *update_val_name[] = { + "off", + "on ", + "hw ", + "lp ", + " - " /* undefined value */ +}; + +static const char *hw_mode_val_name[] = { + "hp/lp ", + "hp/off", + "hp ", + "hp ", + "-/- ", /* undefined value */ +}; + +/* voltage selection */ +static const struct regulator_volt_range varm_vape_vmod_vsel[] = { + { {0x00, 700000}, {0x01, 12500}, {0x35, 1362500} }, + { {0x36, 1362500}, {0x01, 0}, {0x3f, 1362500} }, +}; + +static const struct regulator_volt_range vbbp_vsel[] = { + { {0x00, 0}, {0x10, 100000}, {0x40, 400000} }, + { {0x50, 400000}, {0x10, 0}, {0x70, 400000} }, + { {0x80, -400000}, {0x10, 0}, {0xb0, -400000} }, + { {0xc0, -400000}, {0x10, 100000}, {0xf0, -100000} }, +}; + +static const struct regulator_volt_range vbbn_vsel[] = { + { {0x00, 0}, {0x01, -100000}, {0x04, -400000} }, + { {0x05, -400000}, {0x01, 0}, {0x07, -400000} }, + { {0x08, 0}, {0x01, 100000}, {0x0c, 400000} }, + { {0x0d, 400000}, {0x01, 0}, {0x0f, 400000} }, +}; + +static const struct regulator_volt_range vsmps1_vsel[] = { + { {0x00, 1100000}, {0x01, 0}, {0x1f, 1100000} }, + { {0x20, 1100000}, {0x01, 12500}, {0x30, 1300000} }, + { {0x31, 1300000}, {0x01, 0}, {0x3f, 1300000} }, +}; + +static const struct regulator_volt_range vsmps2_vsel[] = { + { {0x00, 1800000}, {0x01, 0}, {0x38, 1800000} }, + { {0x39, 1800000}, {0x01, 12500}, {0x7f, 1875000} }, +}; + +static const struct regulator_volt_range vsmps3_vsel[] = { + { {0x00, 700000}, {0x01, 12500}, {0x35, 1363500} }, + { {0x36, 1363500}, {0x01, 0}, {0x7f, 1363500} }, +}; + +static const struct regulator_volt_range vaux1_vaux2_vsel[] = { + { {0x00, 1100000}, {0x01, 100000}, {0x04, 1500000} }, + { {0x05, 1800000}, {0x01, 50000}, {0x07, 1900000} }, + { {0x08, 2500000}, {0x01, 0}, {0x08, 2500000} }, + { {0x09, 2650000}, {0x01, 50000}, {0x0c, 2800000} }, + { {0x0d, 2900000}, {0x01, 100000}, {0x0e, 3000000} }, + { {0x0f, 3300000}, {0x01, 0}, {0x0f, 3300000} }, +}; + +static const struct regulator_volt_range vaux3_vsel[] = { + { {0x00, 1200000}, {0x01, 300000}, {0x03, 2100000} }, + { {0x04, 2500000}, {0x01, 250000}, {0x05, 2750000} }, + { {0x06, 2790000}, {0x01, 0}, {0x06, 2790000} }, + { {0x07, 2910000}, {0x01, 0}, {0x07, 2910000} }, +}; + +static const struct regulator_volt_range vrf1_vsel[] = { + { {0x00, 1800000}, {0x10, 200000}, {0x10, 2000000} }, + { {0x20, 2150000}, {0x10, 0}, {0x20, 2150000} }, + { {0x30, 2500000}, {0x10, 0}, {0x30, 2500000} }, +}; + +static const struct regulator_volt_range vintcore12_vsel[] = { + { {0x00, 1200000}, {0x08, 25000}, {0x30, 1350000} }, + { {0x38, 1350000}, {0x01, 0}, {0x38, 1350000} }, +}; + +/* regulators */ +static struct ab8500_regulator ab8500_regulator[AB8500_NUM_REGULATORS] = { + [AB8500_VARM] = { + .name = "Varm", + .update_regid = AB8500_REGU_ARM_REGU1, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x02, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VARM_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = varm_vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VARM_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = varm_vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[2] = AB8500_REGU_VARM_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = varm_vape_vmod_vsel, + .vsel_range_len[2] = ARRAY_SIZE(varm_vape_vmod_vsel), + }, + [AB8500_VBBP] = { + .name = "Vbbp", + .update_regid = AB8500_REGU_ARM_REGU2, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x00}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x04, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x10, + .vsel_sel_val = {0x00, 0x10, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VBB_SEL1, + .vsel_mask[0] = 0xf0, + .vsel_range[0] = vbbp_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vbbp_vsel), + .vsel_regid[1] = AB8500_REGU_VBB_SEL2, + .vsel_mask[1] = 0xf0, + .vsel_range[1] = vbbp_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vbbp_vsel), + }, + [AB8500_VBBN] = { + .name = "Vbbn", + .update_regid = AB8500_REGU_ARM_REGU2, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x00}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x04, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x20, + .vsel_sel_val = {0x00, 0x20, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VBB_SEL1, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vbbn_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vbbn_vsel), + .vsel_regid[1] = AB8500_REGU_VBB_SEL2, + .vsel_mask[1] = 0x0f, + .vsel_range[1] = vbbn_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vbbn_vsel), + }, + [AB8500_VAPE] = { + .name = "Vape", + .update_regid = AB8500_REGU_VAPE_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x01, + .vsel_sel_regid = AB8500_REGU_VAPE_REGU, + .vsel_sel_mask = 0x24, + .vsel_sel_val = {0x00, 0x04, 0x20, 0x24}, + .vsel_regid[0] = AB8500_REGU_VAPE_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = varm_vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VAPE_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = varm_vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[2] = AB8500_REGU_VAPE_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = varm_vape_vmod_vsel, + .vsel_range_len[2] = ARRAY_SIZE(varm_vape_vmod_vsel), + }, + [AB8500_VSMPS1] = { + .name = "Vsmps1", + .update_regid = AB8500_REGU_VSMPS1_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x01, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x01, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x04, + .vsel_sel_regid = AB8500_REGU_VSMPS1_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS1_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vsmps1_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps1_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS1_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vsmps1_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps1_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS1_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = vsmps1_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps1_vsel), + }, + [AB8500_VSMPS2] = { + .name = "Vsmps2", + .update_regid = AB8500_REGU_VSMPS2_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x02, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x02, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x08, + .vsel_sel_regid = AB8500_REGU_VSMPS2_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS2_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vsmps2_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps2_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS2_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vsmps2_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps2_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS2_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = vsmps2_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps2_vsel), + }, + [AB8500_VSMPS3] = { + .name = "Vsmps3", + .update_regid = AB8500_REGU_VSMPS3_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x04, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x04, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x04, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x10, + .vsel_sel_regid = AB8500_REGU_VSMPS3_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS3_SEL1, + .vsel_mask[0] = 0x7f, + .vsel_range[0] = vsmps3_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps3_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS3_SEL2, + .vsel_mask[1] = 0x7f, + .vsel_range[1] = vsmps3_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps3_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS3_SEL3, + .vsel_mask[2] = 0x7f, + .vsel_range[2] = vsmps3_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps3_vsel), + }, + [AB8500_VPLL] = { + .name = "Vpll", + .update_regid = AB8500_REGU_VPLL_VANA_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x10, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x10, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x10, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x40, + }, + [AB8500_VREFDDR] = { + .name = "VrefDDR", + .update_regid = AB8500_REGU_VREF_DDR, + .update_mask = 0x01, + .update_val = {0x00, 0x01, 0x00, 0x00}, + }, + [AB8500_VMOD] = { + .name = "Vmod", + .update_regid = AB8500_REGU_VMOD_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_VMOD_REGU, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x08, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x08, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x08, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x20, + .vsel_sel_regid = AB8500_REGU_VMOD_REGU, + .vsel_sel_mask = 0x04, + .vsel_sel_val = {0x00, 0x04, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VMOD_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = varm_vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VMOD_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = varm_vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(varm_vape_vmod_vsel), + }, + [AB8500_VEXTSUPPLY1] = { + .name = "Vextsupply1", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x10, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x01, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x04, + }, + [AB8500_VEXTSUPPLY2] = { + .name = "VextSupply2", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x20, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x02, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x08, + }, + [AB8500_VEXTSUPPLY3] = { + .name = "VextSupply3", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x30, + .update_val = {0x00, 0x10, 0x20, 0x30}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x40, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x04, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x04, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x10, + }, + [AB8500_VRF1] = { + .name = "Vrf1", + .update_regid = AB8500_REGU_VRF1_VAUX3_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VRF1_VAUX3_SEL, + .vsel_mask[0] = 0x30, + .vsel_range[0] = vrf1_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vrf1_vsel), + }, + [AB8500_VANA] = { + .name = "Vana", + .update_regid = AB8500_REGU_VPLL_VANA_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x08, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x08, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x08, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x20, + }, + [AB8500_VAUX1] = { + .name = "Vaux1", + .update_regid = AB8500_REGU_VAUX12_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x20, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x20, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x20, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x80, + .vsel_regid[0] = AB8500_REGU_VAUX1_SEL, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vaux1_vaux2_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vaux1_vaux2_vsel), + }, + [AB8500_VAUX2] = { + .name = "Vaux2", + .update_regid = AB8500_REGU_VAUX12_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x40, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x40, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x40, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x01, + .vsel_regid[0] = AB8500_REGU_VAUX2_SEL, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vaux1_vaux2_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vaux1_vaux2_vsel), + }, + [AB8500_VAUX3] = { + .name = "Vaux3", + .update_regid = AB8500_REGU_VRF1_VAUX3_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL4, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x80, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x80, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x80, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x02, + .vsel_regid[0] = AB8500_REGU_VRF1_VAUX3_SEL, + .vsel_mask[0] = 0x07, + .vsel_range[0] = vaux3_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vaux3_vsel), + }, + [AB8500_VINTCORE] = { + .name = "VintCore12", + .update_regid = AB8500_REGU_MISC1, + .update_mask = 0x44, + .update_val = {0x00, 0x04, 0x00, 0x44}, + .vsel_regid[0] = AB8500_REGU_MISC1, + .vsel_mask[0] = 0x38, + .vsel_range[0] = vintcore12_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vintcore12_vsel), + }, + [AB8500_VTVOUT] = { + .name = "VTVout", + .update_regid = AB8500_REGU_MISC1, + .update_mask = 0x82, + .update_val = {0x00, 0x02, 0x00, 0x82}, + }, + [AB8500_VAUDIO] = { + .name = "Vaudio", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x02, + .update_val = {0x00, 0x02, 0x00, 0x00}, + }, + [AB8500_VANAMIC1] = { + .name = "Vanamic1", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x08, + .update_val = {0x00, 0x08, 0x00, 0x00}, + }, + [AB8500_VANAMIC2] = { + .name = "Vanamic2", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x10, + .update_val = {0x00, 0x10, 0x00, 0x00}, + }, + [AB8500_VDMIC] = { + .name = "Vdmic", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x04, + .update_val = {0x00, 0x04, 0x00, 0x00}, + }, + [AB8500_VUSB] = { + .name = "Vusb", + .update_regid = AB8500_REGU_VUSB_CTRL, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x00, 0x03}, + }, + [AB8500_VOTG] = { + .name = "VOTG", + .update_regid = AB8500_REGU_OTG_SUPPLY_CTRL, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x00, 0x03}, + }, + [AB8500_VBUSBIS] = { + .name = "Vbusbis", + .update_regid = AB8500_REGU_OTG_SUPPLY_CTRL, + .update_mask = 0x08, + .update_val = {0x00, 0x08, 0x00, 0x00}, + }, +}; + +static int status_state; + +static int _get_voltage(struct regulator_volt_range const *volt_range, + u8 value, int *volt) +{ + u8 start = volt_range->start.value; + u8 end = volt_range->end.value; + u8 step = volt_range->step.value; + + /* Check if witin range */ + if (step == 0) { + if (value == start) { + *volt = volt_range->start.volt; + return 1; + } + } else { + if ((start <= value) && (value <= end)) { + if ((value - start)%step != 0) + return -EINVAL; /* invalid setting */ + *volt = volt_range->start.volt + + volt_range->step.volt + *((value - start)/step); + return 1; + } + } + + return 0; +} + +static int get_voltage(struct regulator_volt_range const *volt_range, + int volt_range_len, + u8 value) +{ + int volt; + int i, ret; + + for (i = 0; i < volt_range_len; i++) { + ret = _get_voltage(&volt_range[i], value, &volt); + if (ret < 0) + break; /* invalid setting */ + if (ret == 1) + return volt; /* successful */ + } + + return -EINVAL; +} + +static int ab8500_regulator_status_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int id, regid; + int i; + u8 val; + int err; + + /* record current state */ + ab8500_regulator_record_state(AB8500_REGULATOR_STATE_CURRENT); + + /* check if chosen state is recorded */ + if (!ab8500_register_state_saved[status_state]) { + seq_printf(s, "ab8500-regulator status is not recorded.\n"); + goto exit; + } + + /* print dump header */ + err = seq_printf(s, "ab8500-regulator status:\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print state */ + err = seq_printf(s, "%12s\n", + regulator_state_name[status_state]); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print labels */ + err = seq_printf(s, + "+-----------+----+--------------+-------------------------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "| name|man |auto |voltage |\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "+-----------+----+--------------+ +-----------------------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "| |mode|mode |0|1|2|3| | 1 | 2 | 3 |\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "+-----------+----+------+-+-+-+-+-+-------+-------+-------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + /* dump registers */ + for (id = 0; id < AB8500_NUM_REGULATORS; id++) { + /* print name */ + err = seq_printf(s, "|%11s|", + ab8500_regulator[id].name); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print manual mode */ + regid = ab8500_regulator[id].update_regid; + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].update_mask; + for (i = 0; i < 4; i++) { + if (val == ab8500_regulator[id].update_val[i]) + break; + } + err = seq_printf(s, "%4s|", + update_val_name[i]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print auto mode */ + regid = ab8500_regulator[id].hw_mode_regid; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].hw_mode_mask; + for (i = 0; i < 4; i++) { + if (val == ab8500_regulator[id].hw_mode_val[i]) + break; + } + err = seq_printf(s, "%6s|", + hw_mode_val_name[i]); + } else { + err = seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print valid bits */ + for (i = 0; i < 4; i++) { + regid = ab8500_regulator[id].hw_valid_regid[i]; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].hw_valid_mask[i]; + if (val) + err = seq_printf(s, "1|"); + else + err = seq_printf(s, "0|"); + } else { + err = seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + } + + /* print voltage selection */ + regid = ab8500_regulator[id].vsel_sel_regid; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].vsel_sel_mask; + for (i = 0; i < 3; i++) { + if (val == ab8500_regulator[id].vsel_sel_val[i]) + break; + } + if (i < 3) + seq_printf(s, "%i|", i + 1); + else + seq_printf(s, "-|"); + } else { + seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + + for (i = 0; i < 3; i++) { + int volt; + + regid = ab8500_regulator[id].vsel_regid[i]; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].vsel_mask[i]; + volt = get_voltage( + ab8500_regulator[id].vsel_range[i], + ab8500_regulator[id].vsel_range_len[i], + val); + seq_printf(s, "%7i|", volt); + } else { + seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + } + + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + + } + err = seq_printf(s, + "+-----------+----+------+-+-+-+-+-+-------+-------+-------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "Note! In HW mode, voltage selection is controlled by HW.\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + +exit: + return 0; +} + +static int ab8500_regulator_status_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + + /* copy user data */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* convert */ + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + + /* set suspend force setting */ + if (user_val > NUM_REGULATOR_STATE) { + dev_err(dev, "debugfs error input > number of states\n"); + return -EINVAL; + } + + status_state = user_val; + + return buf_size; +} + + +static int ab8500_regulator_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_regulator_status_print, + inode->i_private); +} + +static const struct file_operations ab8500_regulator_status_fops = { + .open = ab8500_regulator_status_open, + .write = ab8500_regulator_status_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_PM + +struct ab8500_force_reg { + char *name; + u8 bank; + u8 addr; + u8 mask; + u8 val; + bool restore; + u8 restore_val; +}; + +static struct ab8500_force_reg ab8500_force_reg[] = { + { + /* + * VpllVanaRegu + * OTP: 0x01, HSI: 0x02, suspend: 0x01/0x0f (value/mask) + * [3:2] VanaRegu[1:0] = Vana off + * [1:0] VpllRegu[1:0] = Vpll HP + */ + .name = "VpllVanaRegu", + .bank = 0x04, + .addr = 0x06, + .mask = 0x0f, + .val = 0x01, + }, + { + /* + * Vaux12Regu + * OTP: 0x04, HSI: 0x00, suspend: 0x00/0x0f (value/mask) + * [3:2] Vaux2Regu[1:0] = Vaux2 off + * [1:0] Vaux1Regu[1:0] = Vaux1 off + */ + .name = "Vaux12Regu", + .bank = 0x04, + .addr = 0x09, + .mask = 0x0f, + .val = 0x00, + }, + { + /* + * VRF1Vaux3Regu + * OTP: 0x04, HSI: 0x08, suspend: 0x08/0x0f (value/mask) + * [3:2] VRF1Regu[1:0] = VRF1 in HW control + * [1:0] Vaux3Regu[1:0] = Vaux3 off + */ + .name = "VRF1Vaux3Regu", + .bank = 0x04, + .addr = 0x0a, + .mask = 0x0f, + .val = 0x08, + }, + { + /* + * SysClkCtrl + * OTP: 0x00, HSI: 0x06, suspend: 0x00/0x07 (value/mask) + * [ 2] USBClkEna = disable SysClk path to USB block + * [ 1] TVoutClkEna = disable 27Mhz clock to TVout block + * [ 0] TVoutPllEna = disable TVout pll + * (generate 27Mhz from SysClk) + */ + .name = "SysClkCtrl", + .bank = 0x02, + .addr = 0x0c, + .mask = 0x07, + .val = 0x00, + }, + { + /* + * ReguMisc1 + * OTP: 0x10, HSI: 0x10, suspend: 0x10/0x3e (value/mask) + * [5:3] VintCore12Sel[2:0] = Vintcore 1.25v + * [ 2] VintCore12Ena = VintCore12 off + * [ 1] VTVoutEna = VTVout off + */ + .name = "ReguMisc1", + .bank = 0x03, + .addr = 0x80, + .mask = 0x3e, + .val = 0x10, + }, + { + /* + * VaudioSupply + * OTP: 0x00, HSI: 0x00, suspend: 0x00/0x1e (value/mask) + * [ 4] Vamic2Ena = Vamic2 off + * [ 3] Vamic1Ena = Vamic1 off + * [ 2] VdmicEna = Vdmic off + * [ 1] VaudioEna = Vaudio off + */ + .name = "VaudioSupply", + .bank = 0x03, + .addr = 0x83, + .mask = 0x1e, + .val = 0x00, + }, + { + /* + * ReguSysClkReq1HPValid2 + * OTP: 0x03, HSI: 0x40, suspend: 0x60/0x70 (value/mask) + * [ 5] VextSupply2SysClkReq1HPValid = Vext2 set by SysClkReq1 + */ + .name = "ReguSysClkReq1HPValid2", + .bank = 0x03, + .addr = 0x08, + .mask = 0x20, /* test and compare with 0x7f */ + .val = 0x20, + }, + { + /* + * ReguRequestCtrl3 + * OTP: 0x00, HSI: 0x00, suspend: 0x05/0x0f (value/mask) + * [1:0] VExtSupply2RequestCtrl[1:0] = VExt2 set in HP/OFF mode + */ + .name = "ReguRequestCtrl3", + .bank = 0x03, + .addr = 0x05, + .mask = 0x03, /* test and compare with 0xff */ + .val = 0x01, + }, + { + /* + * VsimSysClkCtrl + * OTP: 0x01, HSI: 0x21, suspend: 0x01/0xff (value/mask) + * [ 7] VsimSysClkReq8Valid = no connection + * [ 6] VsimSysClkReq7Valid = no connection + * [ 5] VsimSysClkReq6Valid = no connection + * [ 4] VsimSysClkReq5Valid = no connection + * [ 3] VsimSysClkReq4Valid = no connection + * [ 2] VsimSysClkReq3Valid = no connection + * [ 1] VsimSysClkReq2Valid = no connection + * [ 0] VsimSysClkReq1Valid = Vsim set by SysClkReq1 + */ + .name = "VsimSysClkCtrl", + .bank = 0x02, + .addr = 0x33, + .mask = 0xff, + .val = 0x01, + }, + { + /* + * ReguSysClkReq1HPValid1 + * OTP: 0x00, HSI: 0x16, suspend: 0x17/0xff (value/mask) + * [ 7] Vaux3SysClkReq1HPValid = no connection + * [ 6] Vaux2SysClkReq1HPValid = no connection + * [ 5] Vaux1SysClkReq1HPValid = no connection + * [ 4] VpllSysClkReq1HPValid = Vpll set by SysClkReq1 + * [ 3] VanaSysClkReq1HPValid = no connection + * [ 2] Vsmps3SysClkReq1HPValid = Vsmps3 set by SysClkReq1 + * [ 1] Vsmps2SysClkReq1HPValid = Vsmsp2 set by SysClkReq1 + * [ 0] Vsmps1SysClkReq1HPValid = Vsmps1 set by SysClkReq1 + */ + .name = "ReguSysClkReq1HPValid1", + .bank = 0x03, + .addr = 0x07, + .mask = 0xff, + .val = 0x17, + }, + { + /* + * Vsmps1Regu + * OTP: 0x05, HSI: 0x05, suspend: 0x06/0x0f (value/mask) + * [3:2] Vsmps1SelCtrl = Vsmps1 voltage set by Vsmps1Sel2 + * [1:0] Vsmps1Regu[1:0] = Vsmsp1 in HW control + */ + .name = "Vsmps1Regu", + .bank = 0x04, + .addr = 0x03, + .mask = 0x0f, + .val = 0x06, + }, + { + /* + * Vsmps2Regu + * OTP: 0x05, HSI: 0x06, suspend: 0x06/0x0f (value/mask) + * [3:2] Vsmps2SelCtrl[1:0] = Vsmps2 voltage set by Vsmps2Sel2 + * [1:0] Vsmps2Regu[1:0] = Vsmps2 in HW control + */ + .name = "Vsmps2Regu", + .bank = 0x04, + .addr = 0x04, + .mask = 0x0f, + .val = 0x06, + }, + { + /* + * Vsmsp3Regu + * OTP: 0x01, HSI: 0x01, suspend: 0x06/0x0f (value/mask) + * [3:2] Vsmps3SelCtrl[1:0] = Vsmps3 voltage set by Vsmps3Sel2 + * [1:0] Vsmps3Regu[1:0] = Vsmps3 in HW control + */ + .name = "Vsmps3Regu", + .bank = 0x04, + .addr = 0x05, + .mask = 0x0f, + .val = 0x06, + }, + { + /* + * VpllVanaRegu + * OTP: 0x01, HSI: 0x02, suspend: 0x02/0x0f (value/mask) + * [3:2] VanaRegu[1:0] = Vana off + * [1:0] VpllRegu[1:0] = Vpll in HW control + */ + .name = "VpllVanaRegu", + .bank = 0x04, + .addr = 0x06, + .mask = 0x0f, + .val = 0x02, + }, + { + /* + * SysUlpClkCtrl1 + * OTP: 0x00, HSI: 0x00, suspend: 0x00/0x0f (value/mask) + * [ 3] 4500SysClkReq = inactive + * [ 2] UlpClkReq = inactive + * [1:0] SysUlpClkIntSel[1:0] = no internal clock switching. + * Internal clock is SysClk. + */ + .name = "SysUlpClkCtrl1", + .bank = 0x02, + .addr = 0x0b, + .mask = 0x0f, + .val = 0x00, + }, + { + /* + * ExtSupplyRegu (HSI: 0x2a on v2-v40?) + * OTP: 0x15, HSI: 0x28, suspend: 0x28/0x3f (value/mask) + * [5:4] VExtSupply3Regu[1:0] = 10 = Vext3 off + * [3:2] VExtSupply2Regu[1:0] = 10 = Vext2 in HW control + * [1:0] VExtSupply1Regu[1:0] = 00 = Vext1 off + */ + .name = "ExtSupplyRegu", + .bank = 0x04, + .addr = 0x08, + .mask = 0x3f, + .val = 0x08, + }, + { + /* + * VBBSel1 + * OTP: 0x00, HSI: 0xdb, suspend: 0x00/0xff (value/mask) + * [7:4] VBBPSel1[3:0] = [VBBP = VBBPFB] + * [3:0] VBBNSel1[3:0] = [VBBN = 0 V] + */ + .name = "VBBSel1", + .bank = 0x04, + .addr = 0x11, + .mask = 0xff, + .val = 0x00, + }, + { + /* + * VBBSel2 + * OTP: 0x00, HSI: N/A, suspend: 0x28/0xff (value/mask) + * [7:4] VBBPSel2[3:0] = [VBBP = VBBPFB] + * [3:0] VBBNSel2[3:0] = [VBBN = 0 V] + */ + .name = "VBBSel2", + .bank = 0x04, + .addr = 0x12, + .mask = 0xff, + .val = 0x28, + }, + { + /* + * TVoutCtrl + * OTP: N/A, HSI: N/A, suspend: 0x00/0x03 (value/mask) + * [ 2] PlugTvOn = plug/unplug detection disabled + * [1:0] TvoutDacCtrl[1:0] = "0" forced on DAC input (test) + */ + .name = "TVoutCtrl", + .bank = 0x06, + .addr = 0x80, + .mask = 0x03, + .val = 0x00, + }, +}; + +void ab8500_regulator_debug_force(void) +{ + int ret, i; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_SUSPEND); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record suspend state.\n"); + + /* check if registers should be forced */ + if (!setting_suspend_force) + goto exit; + + /* + * Optimize href v2_v50_pwr board for ApSleep/ApDeepSleep + * power consumption measurements + */ + + for (i = 0; i < ARRAY_SIZE(ab8500_force_reg); i++) { + dev_vdbg(&pdev->dev, "Save and set %s: " + "0x%02x, 0x%02x, 0x%02x, 0x%02x.\n", + ab8500_force_reg[i].name, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].val); + + /* assume that register should be restored */ + ab8500_force_reg[i].restore = true; + + /* get register value before forcing it */ + ret = abx500_get_register_interruptible(&pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + &ab8500_force_reg[i].restore_val); + if (ret < 0) { + dev_err(dev, "Failed to read %s.\n", + ab8500_force_reg[i].name); + ab8500_force_reg[i].restore = false; + break; + } + + /* force register value */ + ret = abx500_mask_and_set_register_interruptible(&pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].val); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to write %s.\n", + ab8500_force_reg[i].name); + ab8500_force_reg[i].restore = false; + } + } + +exit: + /* save state of registers */ + ret = ab8500_regulator_record_state( + AB8500_REGULATOR_STATE_SUSPEND_CORE); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record suspend state.\n"); + + return; +} + +void ab8500_regulator_debug_restore(void) +{ + int ret, i; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_RESUME_CORE); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record resume state.\n"); + + for (i = 0; i < ARRAY_SIZE(ab8500_force_reg); i++) { + /* restore register value */ + if (ab8500_force_reg[i].restore) { + ret = abx500_mask_and_set_register_interruptible( + &pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].restore_val); + if (ret < 0) + dev_err(&pdev->dev, "Failed to restore %s.\n", + ab8500_force_reg[i].name); + dev_vdbg(&pdev->dev, "Restore %s: " + "0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + ab8500_force_reg[i].name, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].restore_val); + } + } + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_RESUME); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record resume state.\n"); + + return; +} + +#endif + +static int ab8500_regulator_suspend_force_show(struct seq_file *s, void *p) +{ + /* print suspend standby status */ + if (setting_suspend_force) + return seq_printf(s, "suspend force enabled\n"); + else + return seq_printf(s, "no suspend force\n"); +} + +static int ab8500_regulator_suspend_force_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + + /* copy user data */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* convert */ + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + + /* set suspend force setting */ + if (user_val > 1) { + dev_err(dev, "debugfs error input > 1\n"); + return -EINVAL; + } + + if (user_val) + setting_suspend_force = true; + else + setting_suspend_force = false; + + return buf_size; +} + +static int ab8500_regulator_suspend_force_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_regulator_suspend_force_show, + inode->i_private); +} + +static const struct file_operations ab8500_regulator_suspend_force_fops = { + .open = ab8500_regulator_suspend_force_open, + .write = ab8500_regulator_suspend_force_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab8500_regulator_dir; +static struct dentry *ab8500_regulator_dump_file; +static struct dentry *ab8500_regulator_status_file; +static struct dentry *ab8500_regulator_suspend_force_file; + +static int __devinit ab8500_regulator_debug_probe(struct platform_device *plf) +{ + void __iomem *boot_info_backupram; + int ret; + + /* setup dev pointers */ + dev = &plf->dev; + pdev = plf; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_INIT); + if (ret < 0) + dev_err(&plf->dev, "Failed to record init state.\n"); + + /* make suspend-force default if board profile is v5x-power */ + boot_info_backupram = ioremap(BOOT_INFO_BACKUPRAM1, 0x4); + + if (boot_info_backupram) { + u8 board_profile; + board_profile = readb( + boot_info_backupram + BOARD_PROFILE_BACKUPRAM1); + dev_dbg(dev, "Board profile is 0x%02x\n", board_profile); + + if (board_profile & OPTION_BOARD_VERSION_POWER) + setting_suspend_force = true; + + iounmap(boot_info_backupram); + } else { + dev_err(dev, "Failed to read backupram.\n"); + } + + /* create directory */ + ab8500_regulator_dir = debugfs_create_dir("ab8500-regulator", NULL); + if (!ab8500_regulator_dir) + goto exit_no_debugfs; + + /* create "dump" file */ + ab8500_regulator_dump_file = debugfs_create_file("dump", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_dump_fops); + if (!ab8500_regulator_dump_file) + goto exit_destroy_dir; + + /* create "status" file */ + ab8500_regulator_status_file = debugfs_create_file("status", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_status_fops); + if (!ab8500_regulator_status_file) + goto exit_destroy_dump_file; + + /* + * create "suspend-force-v5x" file. As indicated by the name, this is + * only applicable for v2_v5x hardware versions. + */ + ab8500_regulator_suspend_force_file = debugfs_create_file( + "suspend-force-v5x", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_suspend_force_fops); + if (!ab8500_regulator_suspend_force_file) + goto exit_destroy_status_file; + + return 0; + +exit_destroy_status_file: + debugfs_remove(ab8500_regulator_status_file); +exit_destroy_dump_file: + debugfs_remove(ab8500_regulator_dump_file); +exit_destroy_dir: + debugfs_remove(ab8500_regulator_dir); +exit_no_debugfs: + dev_err(&plf->dev, "failed to create debugfs entries.\n"); + return -ENOMEM; +} + +static int __devexit ab8500_regulator_debug_remove(struct platform_device *plf) +{ + debugfs_remove(ab8500_regulator_suspend_force_file); + debugfs_remove(ab8500_regulator_status_file); + debugfs_remove(ab8500_regulator_dump_file); + debugfs_remove(ab8500_regulator_dir); + + return 0; +} + +static struct platform_driver ab8500_regulator_debug_driver = { + .driver = { + .name = "ab8500-regulator-debug", + .owner = THIS_MODULE, + }, + .probe = ab8500_regulator_debug_probe, + .remove = __devexit_p(ab8500_regulator_debug_remove), +}; + +static int __init ab8500_regulator_debug_init(void) +{ + int ret; + + ret = platform_driver_register(&ab8500_regulator_debug_driver); + if (ret) + pr_err("Failed to register ab8500 regulator: %d\n", ret); + + return ret; +} +subsys_initcall(ab8500_regulator_debug_init); + +static void __exit ab8500_regulator_debug_exit(void) +{ + platform_driver_unregister(&ab8500_regulator_debug_driver); +} +module_exit(ab8500_regulator_debug_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bengt Jonsson + * Licensed under GPLv2. + */ + +#ifndef _AB8500_BM_H +#define _AB8500_BM_H + +/* + * System control 2 register offsets. + * bank = 0x02 + */ +#define AB8500_MAIN_WDOG_CTRL_REG 0x01 +#define AB8500_LOW_BAT_REG 0x03 + +/* + * USB/ULPI register offsets + * Bank : 0x5 + */ +#define AB8500_USB_LINE_STAT_REG 0x80 + +/* + * Charger / status register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_STATUS1_REG 0x00 +#define AB8500_CH_STATUS2_REG 0x01 +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define AB8500_CH_USBCH_STAT2_REG 0x03 +#define AB8500_CH_FSM_STAT_REG 0x04 +#define AB8500_CH_STAT_REG 0x05 + +/* + * Charger / control register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_VOLT_LVL_REG 0x40 +#define AB8500_CH_VOLT_LVL_MAX_REG 0x41 /*Only in Cut2.0*/ +#define AB8500_CH_OPT_CRNTLVL_REG 0x42 +#define AB8500_CH_OPT_CRNTLVL_MAX_REG 0x43 /*Only in Cut2.0*/ +#define AB8500_CH_WD_TIMER_REG 0x50 +#define AB8500_CHARG_WD_CTRL 0x51 +#define AB8500_BTEMP_HIGH_TH 0x52 +#define AB8500_LED_INDICATOR_PWM_CTRL 0x53 +#define AB8500_LED_INDICATOR_PWM_DUTY 0x54 +#define AB8500_BATT_OVV 0x55 +#define AB8500_BAT_CTRL_CURRENT_SOURCE 0x60 /*Only in Cut2.0*/ + +/* + * Charger / main control register offsets + * Bank : 0x0B + */ +#define AB8500_MCH_CTRL1 0x80 +#define AB8500_MCH_CTRL2 0x81 +#define AB8500_MCH_IPT_CURLVL_REG 0x82 +#define AB8500_CH_WD_REG 0x83 + +/* + * Charger / USB control register offsets + * Bank : 0x0B + */ +#define AB8500_USBCH_CTRL1_REG 0xC0 +#define AB8500_USBCH_CTRL2_REG 0xC1 +#define AB8500_USBCH_IPT_CRNTLVL_REG 0xC2 + +/* + * Gas Gauge register offsets + * Bank : 0x0C + */ +#define AB8500_GASG_CC_CTRL_REG 0x00 +#define AB8500_GASG_CC_ACCU1_REG 0x01 +#define AB8500_GASG_CC_ACCU2_REG 0x02 +#define AB8500_GASG_CC_ACCU3_REG 0x03 +#define AB8500_GASG_CC_ACCU4_REG 0x04 +#define AB8500_GASG_CC_SMPL_CNTRL_REG 0x05 +#define AB8500_GASG_CC_SMPL_CNTRH_REG 0x06 +#define AB8500_GASG_CC_SMPL_CNVL_REG 0x07 +#define AB8500_GASG_CC_SMPL_CNVH_REG 0x08 +#define AB8500_GASG_CC_CNTR_AVGOFF_REG 0x09 +#define AB8500_GASG_CC_OFFSET_REG 0x0A +#define AB8500_GASG_CC_NCOV_ACCU 0x10 +#define AB8500_GASG_CC_NCOV_ACCU_CTRL 0x11 +#define AB8500_GASG_CC_NCOV_ACCU_LOW 0x12 +#define AB8500_GASG_CC_NCOV_ACCU_MED 0x13 +#define AB8500_GASG_CC_NCOV_ACCU_HIGH 0x14 + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB8500_IT_SOURCE2_REG 0x01 +#define AB8500_IT_SOURCE21_REG 0x14 + +/* + * RTC register offsets + * Bank: 0x0F + */ +#define AB8500_RTC_BACKUP_CHG_REG 0x0C +#define AB8500_RTC_CC_CONF_REG 0x01 +#define AB8500_RTC_CTRL_REG 0x0B + +/* + * OTP register offsets + * Bank : 0x15 + */ +#define AB8500_OTP_CONF_15 0x0E + +/* GPADC constants from AB8500 spec, UM0836 */ +#define ADC_RESOLUTION 1024 +#define ADC_CH_MAIN_MIN 0 +#define ADC_CH_MAIN_MAX 20030 +#define ADC_CH_VBUS_MIN 0 +#define ADC_CH_VBUS_MAX 20030 +#define ADC_CH_VBAT_MIN 2300 +#define ADC_CH_VBAT_MAX 4800 +#define ADC_CH_BKBAT_MIN 0 +#define ADC_CH_BKBAT_MAX 3200 + +/* Main charge i/p current */ +#define MAIN_CH_IP_CUR_0P9A 0x80 +#define MAIN_CH_IP_CUR_1P0A 0x90 +#define MAIN_CH_IP_CUR_1P1A 0xA0 +#define MAIN_CH_IP_CUR_1P2A 0xB0 +#define MAIN_CH_IP_CUR_1P3A 0xC0 +#define MAIN_CH_IP_CUR_1P4A 0xD0 +#define MAIN_CH_IP_CUR_1P5A 0xE0 + +/* ChVoltLevel */ +#define CH_VOL_LVL_3P5 0x00 +#define CH_VOL_LVL_4P0 0x14 +#define CH_VOL_LVL_4P05 0x16 +#define CH_VOL_LVL_4P1 0x1B +#define CH_VOL_LVL_4P15 0x20 +#define CH_VOL_LVL_4P2 0x25 +#define CH_VOL_LVL_4P6 0x4D + +/* ChOutputCurrentLevel */ +#define CH_OP_CUR_LVL_0P1 0x00 +#define CH_OP_CUR_LVL_0P2 0x01 +#define CH_OP_CUR_LVL_0P3 0x02 +#define CH_OP_CUR_LVL_0P4 0x03 +#define CH_OP_CUR_LVL_0P5 0x04 +#define CH_OP_CUR_LVL_0P6 0x05 +#define CH_OP_CUR_LVL_0P7 0x06 +#define CH_OP_CUR_LVL_0P8 0x07 +#define CH_OP_CUR_LVL_0P9 0x08 +#define CH_OP_CUR_LVL_1P4 0x0D +#define CH_OP_CUR_LVL_1P5 0x0E +#define CH_OP_CUR_LVL_1P6 0x0F + +/* BTEMP High thermal limits */ +#define BTEMP_HIGH_TH_57_0 0x00 +#define BTEMP_HIGH_TH_52 0x01 +#define BTEMP_HIGH_TH_57_1 0x02 +#define BTEMP_HIGH_TH_62 0x03 + +/* current is mA */ +#define USB_0P1A 100 +#define USB_0P2A 200 +#define USB_0P3A 300 +#define USB_0P4A 400 +#define USB_0P5A 500 + +/* UsbChCurrLevel */ +#define USB_CH_IP_CUR_LVL_0P05 0x00 +#define USB_CH_IP_CUR_LVL_0P09 0x10 +#define USB_CH_IP_CUR_LVL_0P19 0x20 +#define USB_CH_IP_CUR_LVL_0P29 0x30 +#define USB_CH_IP_CUR_LVL_0P38 0x40 +#define USB_CH_IP_CUR_LVL_0P45 0x50 +#define USB_CH_IP_CUR_LVL_0P5 0x60 +#define USB_CH_IP_CUR_LVL_0P9 0xA0 +#define USB_CH_IP_CUR_LVL_1P0 0xB0 +#define USB_CH_IP_CUR_LVL_1P1 0xC0 +#define USB_CH_IP_CUR_LVL_1P3 0xD0 +#define USB_CH_IP_CUR_LVL_1P4 0xE0 +#define USB_CH_IP_CUR_LVL_1P5 0xF0 + +#define LOW_BAT_3P1V 0x20 +#define LOW_BAT_2P3V 0x00 +#define LOW_BAT_RESET 0x01 +#define LOW_BAT_ENABLE 0x01 + +/* Backup battery constants */ +#define BUP_ICH_SEL_50UA 0x00 +#define BUP_ICH_SEL_150UA 0x04 +#define BUP_ICH_SEL_300UA 0x08 +#define BUP_ICH_SEL_700UA 0x0C + +#define BUP_VCH_SEL_2P5V 0x00 +#define BUP_VCH_SEL_2P6V 0x01 +#define BUP_VCH_SEL_2P8V 0x02 +#define BUP_VCH_SEL_3P1V 0x03 + +/* Battery OVV constants */ +#define BATT_OVV_ENA 0x02 +#define BATT_OVV_TH_3P7 0x00 +#define BATT_OVV_TH_4P75 0x01 + +/* Fuel Gauge constants */ +#define RESET_ACCU 0x02 +#define READ_REQ 0x01 +#define CC_DEEP_SLEEP_ENA 0x02 +#define CC_PWR_UP_ENA 0x01 +#define CC_SAMPLES_40 0x28 +#define RD_NCONV_ACCU_REQ 0x01 + +/* RTC constants */ +#define RTC_BUP_CH_ENA 0x10 + +/* BatCtrl Current Source Constants */ +#define BAT_CTRL_7U_ENA 0x01 +#define BAT_CTRL_20U_ENA 0x02 +#define BAT_CTRL_CMP_ENA 0x04 +#define FORCE_BAT_CTRL_CMP_HIGH 0x08 +#define BAT_CTRL_PULL_UP_ENA 0x10 + +/* Battery type */ +#define BATTERY_UNKNOWN 00 + +/* + * ADC for the battery thermistor. + * When using the ADC_THERM_BATCTRL the battery ID resistor is combined with + * a NTC resistor to both identify the battery and to measure its temperature. + * Different phone manufactures uses different techniques to both identify the + * battery and to read its temperature. + */ +enum adc_therm { + ADC_THERM_BATCTRL, + ADC_THERM_BATTEMP, +}; + +/** + * struct res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celcius + * @resist: NTC resistor net total resistance + */ +struct res_to_temp { + int temp; + int resist; +}; + +/** + * struct v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct ab8500_fg; + +/** + * struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + */ +struct ab8500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; +}; + +/** + * struct ab8500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct ab8500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + */ +struct battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + struct res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + struct v_to_cap *v_to_cap_tbl; +}; + +/** + * struct ab8500_bm_capacity_levels - ab8500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct ab8500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct ab8500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct ab8500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct ab8500_bm_data - ab8500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @fg_res resistance of FG resistor in mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct ab8500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool chg_unknown_bat; + bool enable_overshoot; + enum adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + const struct ab8500_maxim_parameters *maxi; + const struct ab8500_bm_capacity_levels *cap_levels; + const struct battery_type *bat_type; + const struct ab8500_bm_charger_parameters *chg_params; + const struct ab8500_fg_parameters *fg_params; +}; + +struct ab8500_charger_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_btemp_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_fg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_chargalg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; +#ifdef CONFIG_AB8500_BM +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); +#else +static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ +} +#endif +#endif /* _AB8500_BM_H */ diff --git a/include/linux/mfd/ab8500/denc-regs.h b/include/linux/mfd/ab8500/denc-regs.h new file mode 100644 index 00000000000..a6683ca7470 --- /dev/null +++ b/include/linux/mfd/ab8500/denc-regs.h @@ -0,0 +1,357 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * ST-Ericsson AB8500 DENC related registers + * + * Author: Marcus Tunnissen + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef __AB8500_DENC_H +#define __AB8500_DENC_H + +#define AB8500_VAL2REG(__reg, __fld, __val) \ + (((__val) << __reg##_##__fld##_SHIFT) & __reg##_##__fld##_MASK) +#define AB8500_REG2VAL(__reg, __fld, __val) \ + (((__val) & __reg##_##__fld##_MASK) >> __reg##_##__fld##_SHIFT) + +#define AB8500_CTRL3 0x00000200 +#define AB8500_CTRL3_TH_SD_ENA_SHIFT 3 +#define AB8500_CTRL3_TH_SD_ENA_MASK 0x00000008 +#define AB8500_CTRL3_TH_SD_ENA(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, TH_SD_ENA, __x) +#define AB8500_CTRL3_RESET_DENC_N_SHIFT 2 +#define AB8500_CTRL3_RESET_DENC_N_MASK 0x00000004 +#define AB8500_CTRL3_RESET_DENC_N(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, RESET_DENC_N, __x) +#define AB8500_CTRL3_RESET_AUD_N_SHIFT 1 +#define AB8500_CTRL3_RESET_AUD_N_MASK 0x00000002 +#define AB8500_CTRL3_RESET_AUD_N(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, RESET_AUD_N, __x) +#define AB8500_CTRL3_CLK_32K_OUT2_IS_SHIFT 0 +#define AB8500_CTRL3_CLK_32K_OUT2_IS_MASK 0x00000001 +#define AB8500_CTRL3_CLK_32K_OUT2_IS(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, CLK_32K_OUT2_IS, __x) +#define AB8500_SYS_ULP_CLK_CONF 0x0000020A +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA_SHIFT 7 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA_MASK 0x00000080 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_PD_ENA, __x) +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA_SHIFT 6 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA_MASK 0x00000040 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_BUF_ENA, __x) +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE_SHIFT 5 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE_MASK 0x00000020 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, ULP_CLK_STRE, __x) +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV_SHIFT 4 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV_MASK 0x00000010 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_INV, __x) +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN_SHIFT 3 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN_MASK 0x00000008 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_DE_IN, __x) +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE_SHIFT 2 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE_MASK 0x00000004 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_STRE, __x) +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_SHIFT 0 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_MASK 0x00000003 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_NO_FUNC 0 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_AS_OUTPUT 1 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_AS_INPUT 2 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, ULP_CLK_CONF, \ + AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_##__x) +#define AB8500_SYS_CLK_CTRL 0x0000020C +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID_SHIFT 2 +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID_MASK 0x00000004 +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, USB_CLK_VALID, __x) +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID_SHIFT 1 +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID_MASK 0x00000002 +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, TVOUT_CLK_VALID, __x) +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA_SHIFT 0 +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA_MASK 0x00000001 +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, TVOUT_PLL_ENA, __x) +#define AB8500_REGU_MISC1 0x00000380 +#define AB8500_REGU_MISC1_V_TVOUT_LP_SHIFT 7 +#define AB8500_REGU_MISC1_V_TVOUT_LP_MASK 0x00000080 +#define AB8500_REGU_MISC1_V_TVOUT_LP(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_TVOUT_LP, __x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP_SHIFT 6 +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP_MASK 0x00000040 +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_LP, __x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_SHIFT 3 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_MASK 0x00000038 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_2V 0 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_225V 1 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_25V 2 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_275V 3 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_3V 4 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_325V 5 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_35V 6 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_SEL, \ + AB8500_REGU_MISC1_V_INT_CORE_12_SEL_##__x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA_SHIFT 2 +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA_MASK 0x00000004 +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_ENA, __x) +#define AB8500_REGU_MISC1_V_TVOUT_ENA_SHIFT 1 +#define AB8500_REGU_MISC1_V_TVOUT_ENA_MASK 0x00000002 +#define AB8500_REGU_MISC1_V_TVOUT_ENA(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_TVOUT_ENA, __x) +#define AB8500_VAUX12_REGU 0x00000409 +#define AB8500_VAUX12_REGU_VAUX_1_SHIFT 2 +#define AB8500_VAUX12_REGU_VAUX_1_MASK 0x0000000C +#define AB8500_VAUX12_REGU_VAUX_1_DISABLE 0 +#define AB8500_VAUX12_REGU_VAUX_1_FORCE_HP 1 +#define AB8500_VAUX12_REGU_VAUX_1_BY_CTRL_REG 2 +#define AB8500_VAUX12_REGU_VAUX_1_FORCE_LP 3 +#define AB8500_VAUX12_REGU_VAUX_1(__x) \ + AB8500_VAL2REG(AB8500_VAUX12_REGU, VAUX_1, \ + AB8500_VAUX12_REGU_VAUX_1_##__x) +#define AB8500_VAUX12_REGU_VAUX_2_SHIFT 0 +#define AB8500_VAUX12_REGU_VAUX_2_MASK 0x00000003 +#define AB8500_VAUX12_REGU_VAUX_2_DISABLE 0 +#define AB8500_VAUX12_REGU_VAUX_2_FORCE_HP 1 +#define AB8500_VAUX12_REGU_VAUX_2_BY_CTRL_REG 2 +#define AB8500_VAUX12_REGU_VAUX_2_FORCE_LP 3 +#define AB8500_VAUX12_REGU_VAUX_2(__x) \ + AB8500_VAL2REG(AB8500_VAUX12_REGU, VAUX_2, \ + AB8500_VAUX12_REGU_VAUX_2_##__x) +#define AB8500_VAUX1_SEL 0x0000041F +#define AB8500_VAUX1_SEL_VAL_SHIFT 0 +#define AB8500_VAUX1_SEL_VAL_MASK 0x0000000F +#define AB8500_VAUX1_SEL_VAL_1_1V 0 +#define AB8500_VAUX1_SEL_VAL_1_2V 1 +#define AB8500_VAUX1_SEL_VAL_1_3V 2 +#define AB8500_VAUX1_SEL_VAL_1_4V 3 +#define AB8500_VAUX1_SEL_VAL_1_5V 4 +#define AB8500_VAUX1_SEL_VAL_1_8V 5 +#define AB8500_VAUX1_SEL_VAL_1_85V 6 +#define AB8500_VAUX1_SEL_VAL_1_9V 7 +#define AB8500_VAUX1_SEL_VAL_2_5V 8 +#define AB8500_VAUX1_SEL_VAL_2_65V 9 +#define AB8500_VAUX1_SEL_VAL_2_7V 10 +#define AB8500_VAUX1_SEL_VAL_2_75V 11 +#define AB8500_VAUX1_SEL_VAL_2_8V 12 +#define AB8500_VAUX1_SEL_VAL_2_9V 13 +#define AB8500_VAUX1_SEL_VAL_3_0V 14 +#define AB8500_VAUX1_SEL_VAL_3_3V 15 +#define AB8500_VAUX1_SEL_VAL(__x) \ + AB8500_VAL2REG(AB8500_VAUX1_SEL, VAL, AB8500_VAUX1_SEL_VAL_##__x) +#define AB8500_DENC_CONF0 0x00000600 +#define AB8500_DENC_CONF0_STD_SHIFT 6 +#define AB8500_DENC_CONF0_STD_MASK 0x000000C0 +#define AB8500_DENC_CONF0_STD_PAL_BDGHI 0 +#define AB8500_DENC_CONF0_STD_PAL_N 1 +#define AB8500_DENC_CONF0_STD_NTSC_M 2 +#define AB8500_DENC_CONF0_STD_PAL_M 3 +#define AB8500_DENC_CONF0_STD(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF0, STD, AB8500_DENC_CONF0_STD_##__x) +#define AB8500_DENC_CONF0_SYNC_SHIFT 3 +#define AB8500_DENC_CONF0_SYNC_MASK 0x00000038 +#define AB8500_DENC_CONF0_SYNC_F_BASED_SLAVE 1 +#define AB8500_DENC_CONF0_SYNC_AUTO_TEST 7 +#define AB8500_DENC_CONF0_SYNC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF0, SYNC, AB8500_DENC_CONF0_SYNC_##__x) +#define AB8500_DENC_CONF1 0x00000601 +#define AB8500_DENC_CONF1_BLK_LI_SHIFT 7 +#define AB8500_DENC_CONF1_BLK_LI_MASK 0x00000080 +#define AB8500_DENC_CONF1_BLK_LI_PARTIAL 0 +#define AB8500_DENC_CONF1_BLK_LI_FULL 1 +#define AB8500_DENC_CONF1_BLK_LI(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, BLK_LI, \ + AB8500_DENC_CONF1_BLK_LI_##__x) +#define AB8500_DENC_CONF1_FLT_SHIFT 5 +#define AB8500_DENC_CONF1_FLT_MASK 0x00000060 +#define AB8500_DENC_CONF1_FLT_1_1MHZ 0 +#define AB8500_DENC_CONF1_FLT_1_3MHZ 1 +#define AB8500_DENC_CONF1_FLT_1_6MHZ 2 +#define AB8500_DENC_CONF1_FLT_1_9MHZ 3 +#define AB8500_DENC_CONF1_FLT(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, FLT, AB8500_DENC_CONF1_FLT_##__x) +#define AB8500_DENC_CONF1_CO_KI_SHIFT 3 +#define AB8500_DENC_CONF1_CO_KI_MASK 0x00000008 +#define AB8500_DENC_CONF1_CO_KI(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, CO_KI, __x) +#define AB8500_DENC_CONF1_SETUP_MAIN_SHIFT 2 +#define AB8500_DENC_CONF1_SETUP_MAIN_MASK 0x00000004 +#define AB8500_DENC_CONF1_SETUP_MAIN_BLACK_EQ_BLANK 0 +#define AB8500_DENC_CONF1_SETUP_MAIN_BLACK_GT_BLANK 1 +#define AB8500_DENC_CONF1_SETUP_MAIN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, SETUP_MAIN, \ + AB8500_DENC_CONF1_SETUP_MAIN_##__x) +#define AB8500_DENC_CONF1_CC_SHIFT 0 +#define AB8500_DENC_CONF1_CC_MASK 0x00000003 +#define AB8500_DENC_CONF1_CC_NONE 0 +#define AB8500_DENC_CONF1_CC_FIELD_1 1 +#define AB8500_DENC_CONF1_CC_FIELD_2 2 +#define AB8500_DENC_CONF1_CC_ALL 3 +#define AB8500_DENC_CONF1_CC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, CC, AB8500_DENC_CONF1_CC_##__x) +#define AB8500_DENC_CONF2 0x00000602 +#define AB8500_DENC_CONF2_N_INTRL_SHIFT 7 +#define AB8500_DENC_CONF2_N_INTRL_MASK 0x00000080 +#define AB8500_DENC_CONF2_N_INTRL(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, N_INTRL, __x) +#define AB8500_DENC_CONF2_EN_RST_SHIFT 6 +#define AB8500_DENC_CONF2_EN_RST_MASK 0x00000040 +#define AB8500_DENC_CONF2_EN_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, EN_RST, __x) +#define AB8500_DENC_CONF2_BURST_EN_SHIFT 5 +#define AB8500_DENC_CONF2_BURST_EN_MASK 0x00000020 +#define AB8500_DENC_CONF2_BURST_EN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, BURST_EN, __x) +#define AB8500_DENC_CONF2_SEL_RST_SHIFT 4 +#define AB8500_DENC_CONF2_SEL_RST_MASK 0x00000010 +#define AB8500_DENC_CONF2_SEL_RST_USE_HW_VAL 0 +#define AB8500_DENC_CONF2_SEL_RST_USE_PROG_VAL 1 +#define AB8500_DENC_CONF2_SEL_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, SEL_RST, \ + AB8500_DENC_CONF2_SEL_RST_##__x) +#define AB8500_DENC_CONF2_RST_OSC_BUF_SHIFT 2 +#define AB8500_DENC_CONF2_RST_OSC_BUF_MASK 0x00000004 +#define AB8500_DENC_CONF2_RST_OSC_BUF(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, RST_OSC_BUF, __x) +#define AB8500_DENC_CONF2_VAL_RST_SHIFT 0 +#define AB8500_DENC_CONF2_VAL_RST_MASK 0x00000003 +#define AB8500_DENC_CONF2_VAL_RST_ALL_LINES 0 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_2ND_FIELD 1 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_4TH_FIELD 2 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_8TH_FIELD 3 +#define AB8500_DENC_CONF2_VAL_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, VAL_RST, \ + AB8500_DENC_CONF2_VAL_RST_##__x) +#define AB8500_DENC_CONF6 0x00000606 +#define AB8500_DENC_CONF6_SOFT_RESET_SHIFT 7 +#define AB8500_DENC_CONF6_SOFT_RESET_MASK 0x00000080 +#define AB8500_DENC_CONF6_SOFT_RESET(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, SOFT_RESET, __x) +#define AB8500_DENC_CONF6_JUMP_SHIFT 6 +#define AB8500_DENC_CONF6_JUMP_MASK 0x00000040 +#define AB8500_DENC_CONF6_JUMP(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, JUMP, __x) +#define AB8500_DENC_CONF6_DEC_NINC_SHIFT 5 +#define AB8500_DENC_CONF6_DEC_NINC_MASK 0x00000020 +#define AB8500_DENC_CONF6_DEC_NINC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, DEC_NINC, __x) +#define AB8500_DENC_CONF6_FREE_JUMP_SHIFT 4 +#define AB8500_DENC_CONF6_FREE_JUMP_MASK 0x00000010 +#define AB8500_DENC_CONF6_FREE_JUMP(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, FREE_JUMP, __x) +#define AB8500_DENC_CONF6_MAX_DYN_SHIFT 0 +#define AB8500_DENC_CONF6_MAX_DYN_MASK 0x00000001 +#define AB8500_DENC_CONF6_MAX_DYN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, MAX_DYN, __x) +#define AB8500_DENC_CONF8 0x00000608 +#define AB8500_DENC_CONF8_PH_RST_MODE_SHIFT 6 +#define AB8500_DENC_CONF8_PH_RST_MODE_MASK 0x000000C0 +#define AB8500_DENC_CONF8_PH_RST_MODE_DISABLED 0 +#define AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_PHASE_BUF 1 +#define AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_INC_DFS 2 +#define AB8500_DENC_CONF8_PH_RST_MODE_RESET 3 +#define AB8500_DENC_CONF8_PH_RST_MODE(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, PH_RST_MODE, \ + AB8500_DENC_CONF8_PH_RST_MODE_##__x) +#define AB8500_DENC_CONF8_VAL_422_MUX_SHIFT 4 +#define AB8500_DENC_CONF8_VAL_422_MUX_MASK 0x00000010 +#define AB8500_DENC_CONF8_VAL_422_MUX_TEST 0 +#define AB8500_DENC_CONF8_VAL_422_MUX_ACTIVE 1 +#define AB8500_DENC_CONF8_VAL_422_MUX(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, VAL_422_MUX, \ + AB8500_DENC_CONF8_VAL_422_MUX_##__x) +#define AB8500_DENC_CONF8_BLK_ALL_SHIFT 3 +#define AB8500_DENC_CONF8_BLK_ALL_MASK 0x00000008 +#define AB8500_DENC_CONF8_BLK_ALL(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, BLK_ALL, __x) +#define AB8500_TVOUT_CTRL 0x00000680 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC_SHIFT 6 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC_MASK 0x00000040 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, TV_LOAD_RC, __x) +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_SHIFT 3 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_MASK 0x00000038 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_0_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_1S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_1_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_2S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_2_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_3S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, PLUG_TV_TIME, \ + AB8500_TVOUT_CTRL_PLUG_TV_TIME_##__x) +#define AB8500_TVOUT_CTRL_TV_PLUG_ON_SHIFT 2 +#define AB8500_TVOUT_CTRL_TV_PLUG_ON_MASK 0x00000004 +#define AB8500_TVOUT_CTRL_TV_PLUG_ON(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, TV_PLUG_ON, __x) +#define AB8500_TVOUT_CTRL_DAC_CTRL0_SHIFT 1 +#define AB8500_TVOUT_CTRL_DAC_CTRL0_MASK 0x00000002 +#define AB8500_TVOUT_CTRL_DAC_CTRL0(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, DAC_CTRL0, __x) +#define AB8500_TVOUT_CTRL_DAC_CTRL1_SHIFT 0 +#define AB8500_TVOUT_CTRL_DAC_CTRL1_MASK 0x00000001 +#define AB8500_TVOUT_CTRL_DAC_CTRL1(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, DAC_CTRL1, __x) +#define AB8500_TVOUT_CTRL2 0x00000681 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN_SHIFT 1 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN_MASK 0x00000002 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, SWAP_DDR_DATA_IN, __x) +#define AB8500_TVOUT_CTRL2_DENC_DDR_SHIFT 0 +#define AB8500_TVOUT_CTRL2_DENC_DDR_MASK 0x00000001 +#define AB8500_TVOUT_CTRL2_DENC_DDR(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, DENC_DDR, __x) +#define AB8500_IT_MASK1 0x00000E40 +#define AB8500_IT_MASK1_PON_KEY1_DBR_SHIFT 7 +#define AB8500_IT_MASK1_PON_KEY1_DBR_MASK 0x00000080 +#define AB8500_IT_MASK1_PON_KEY1_DBR(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY1_DBR, __x) +#define AB8500_IT_MASK1_PON_KEY1_DBF_SHIFT 6 +#define AB8500_IT_MASK1_PON_KEY1_DBF_MASK 0x00000040 +#define AB8500_IT_MASK1_PON_KEY1_DBF(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY1_DBF, __x) +#define AB8500_IT_MASK1_PON_KEY2_DBR_SHIFT 5 +#define AB8500_IT_MASK1_PON_KEY2_DBR_MASK 0x00000020 +#define AB8500_IT_MASK1_PON_KEY2_DBR(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY2_DBR, __x) +#define AB8500_IT_MASK1_PON_KEY2_DBF_SHIFT 4 +#define AB8500_IT_MASK1_PON_KEY2_DBF_MASK 0x00000010 +#define AB8500_IT_MASK1_PON_KEY2_DBF(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY2_DBF, __x) +#define AB8500_IT_MASK1_TEMP_WARN_SHIFT 3 +#define AB8500_IT_MASK1_TEMP_WARN_MASK 0x00000008 +#define AB8500_IT_MASK1_TEMP_WARN(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, TEMP_WARN, __x) +#define AB8500_IT_MASK1_PLUG_TV_DET_SHIFT 2 +#define AB8500_IT_MASK1_PLUG_TV_DET_MASK 0x00000004 +#define AB8500_IT_MASK1_PLUG_TV_DET(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PLUG_TV_DET, __x) +#define AB8500_IT_MASK1_UNPLUG_TV_DET_SHIFT 1 +#define AB8500_IT_MASK1_UNPLUG_TV_DET_MASK 0x00000002 +#define AB8500_IT_MASK1_UNPLUG_TV_DET(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, UNPLUG_TV_DET, __x) +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK_SHIFT 0 +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK_MASK 0x00000001 +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, MAIN_EXT_CH_NOK, __x) +#define AB8500_REV 0x00001080 +#define AB8500_REV_FULL_MASK_SHIFT 4 +#define AB8500_REV_FULL_MASK_MASK 0x000000F0 +#define AB8500_REV_FULL_MASK(__x) \ + AB8500_VAL2REG(AB8500_REV, FULL_MASK, __x) +#define AB8500_REV_METAL_FIX_SHIFT 0 +#define AB8500_REV_METAL_FIX_MASK 0x0000000F +#define AB8500_REV_METAL_FIX(__x) \ + AB8500_VAL2REG(AB8500_REV, METAL_FIX, __x) + +#endif /* __AB8500_DENC_H */ diff --git a/include/linux/mfd/ab8500/denc.h b/include/linux/mfd/ab8500/denc.h new file mode 100644 index 00000000000..25a09a2c2bd --- /dev/null +++ b/include/linux/mfd/ab8500/denc.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * AB8500 tvout driver interface + * + * Author: Marcel Tunnissen + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ +#ifndef __AB8500_DENC__H__ +#define __AB8500_DENC__H__ + +#include + +struct ab8500_denc_platform_data { + /* Platform info */ + bool ddr_enable; + bool ddr_little_endian; +}; + +enum ab8500_denc_TV_std { + TV_STD_PAL_BDGHI, + TV_STD_PAL_N, + TV_STD_PAL_M, + TV_STD_NTSC_M, +}; + +enum ab8500_denc_cr_filter_bandwidth { + TV_CR_NTSC_LOW_DEF_FILTER, + TV_CR_PAL_LOW_DEF_FILTER, + TV_CR_NTSC_HIGH_DEF_FILTER, + TV_CR_PAL_HIGH_DEF_FILTER, +}; + +enum ab8500_denc_phase_reset_mode { + TV_PHASE_RST_MOD_DISABLE, + TV_PHASE_RST_MOD_FROM_PHASE_BUF, + TV_PHASE_RST_MOD_FROM_INC_DFS, + TV_PHASE_RST_MOD_RST, +}; + +enum ab8500_denc_plug_time { + TV_PLUG_TIME_0_5S, + TV_PLUG_TIME_1S, + TV_PLUG_TIME_1_5S, + TV_PLUG_TIME_2S, + TV_PLUG_TIME_2_5S, + TV_PLUG_TIME_3S, +}; + +struct ab8500_denc_conf { + /* register settings for DENC_configuration */ + bool act_output; + enum ab8500_denc_TV_std TV_std; + bool progressive; + bool test_pattern; + bool partial_blanking; + bool blank_all; + bool black_level_setup; + enum ab8500_denc_cr_filter_bandwidth cr_filter; + bool suppress_col; + enum ab8500_denc_phase_reset_mode phase_reset_mode; + bool dac_enable; + bool act_dc_output; +}; + +struct platform_device *ab8500_denc_get_device(void); +void ab8500_denc_put_device(struct platform_device *pdev); + +void ab8500_denc_reset(struct platform_device *pdev, bool hard); +void ab8500_denc_power_up(struct platform_device *pdev); +void ab8500_denc_power_down(struct platform_device *pdev); + +void ab8500_denc_conf(struct platform_device *pdev, + struct ab8500_denc_conf *conf); +void ab8500_denc_conf_plug_detect(struct platform_device *pdev, + bool enable, bool load_RC, + enum ab8500_denc_plug_time time); +void ab8500_denc_mask_int_plug_det(struct platform_device *pdev, bool plug, + bool unplug); +#endif /* __AB8500_DENC__H__ */ diff --git a/include/linux/mfd/ab8500/gpadc.h b/include/linux/mfd/ab8500/gpadc.h index 46b954011f1..57c6b59fcaa 100644 --- a/include/linux/mfd/ab8500/gpadc.h +++ b/include/linux/mfd/ab8500/gpadc.h @@ -26,7 +26,7 @@ struct ab8500_gpadc; -struct ab8500_gpadc *ab8500_gpadc_get(char *name); +struct ab8500_gpadc *ab8500_gpadc_get(void); int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); #endif /* _AB8500_GPADC_H */ diff --git a/include/linux/mfd/ab8500/ux500_chargalg.h b/include/linux/mfd/ab8500/ux500_chargalg.h new file mode 100644 index 00000000000..f04e47ff56a --- /dev/null +++ b/include/linux/mfd/ab8500/ux500_chargalg.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Johan Gardsmark for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _UX500_CHARGALG_H +#define _UX500_CHARGALG_H + +#include + +#define psy_to_ux500_charger(x) container_of((x), \ + struct ux500_charger, psy) + +/* Forward declaration */ +struct ux500_charger; + +struct ux500_charger_ops { + int (*enable) (struct ux500_charger *, int, int, int); + int (*kick_wd) (struct ux500_charger *); + int (*update_curr) (struct ux500_charger *, int); +}; + +/** + * struct ux500_charger - power supply ux500 charger sub class + * @psy power supply base class + * @ops ux500 charger operations + * @max_out_volt maximum output charger voltage in mV + * @max_out_curr maximum output charger current in mA + */ +struct ux500_charger { + struct power_supply psy; + struct ux500_charger_ops ops; + int max_out_volt; + int max_out_curr; +}; + +#endif diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 896b5e47f16..78fde125403 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -31,8 +31,15 @@ #define AB3100_R2B 0xc8 #define AB3550_P1A 0x10 #define AB5500_1_0 0x20 -#define AB5500_2_0 0x21 -#define AB5500_2_1 0x22 +#define AB5500_1_1 0x21 +#define AB5500_2_0 0x24 + +/* AB8500 CIDs*/ +#define AB8500_CUTEARLY 0x00 +#define AB8500_CUT1P0 0x10 +#define AB8500_CUT1P1 0x11 +#define AB8500_CUT2P0 0x20 +#define AB8500_CUT3P0 0x30 /* AB8500 CIDs*/ #define AB8500_CUTEARLY 0x00 @@ -198,6 +205,51 @@ struct ab3550_platform_data { unsigned int init_settings_sz; }; +enum ab5500_devid { + AB5500_DEVID_ADC, + AB5500_DEVID_LEDS, + AB5500_DEVID_POWER, + AB5500_DEVID_REGULATORS, + AB5500_DEVID_SIM, + AB5500_DEVID_RTC, + AB5500_DEVID_CHARGER, + AB5500_DEVID_FUELGAUGE, + AB5500_DEVID_VIBRATOR, + AB5500_DEVID_CODEC, + AB5500_DEVID_USB, + AB5500_DEVID_OTP, + AB5500_DEVID_VIDEO, + AB5500_DEVID_DBIECI, + AB5500_NUM_DEVICES, +}; + +enum ab5500_banks { + AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, + AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, + AB5500_BANK_VDENC = 2, + AB5500_BANK_SIM_USBSIM = 3, + AB5500_BANK_LED = 4, + AB5500_BANK_ADC = 5, + AB5500_BANK_RTC = 6, + AB5500_BANK_STARTUP = 7, + AB5500_BANK_DBI_ECI = 8, + AB5500_BANK_CHG = 9, + AB5500_BANK_FG_BATTCOM_ACC = 10, + AB5500_BANK_USB = 11, + AB5500_BANK_IT = 12, + AB5500_BANK_VIBRA = 13, + AB5500_BANK_AUDIO_HEADSETUSB = 14, + AB5500_NUM_BANKS = 15, +}; + +struct ab5500_platform_data { + struct {unsigned int base; unsigned int count; } irq; + void *dev_data[AB5500_NUM_DEVICES]; + size_t dev_data_sz[AB5500_NUM_DEVICES]; + struct abx500_init_settings *init_settings; + unsigned int init_settings_sz; +}; + int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 value); int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, @@ -235,6 +287,6 @@ struct abx500_ops { int (*startup_irq_enabled) (struct device *, unsigned int); }; -int abx500_register_ops(struct device *core_dev, struct abx500_ops *ops); +int abx500_register_ops(struct device *dev, struct abx500_ops *ops); void abx500_remove_ops(struct device *dev); #endif diff --git a/include/linux/regulator/ab8500-debug.h b/include/linux/regulator/ab8500-debug.h new file mode 100644 index 00000000000..01655fc7fc1 --- /dev/null +++ b/include/linux/regulator/ab8500-debug.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Authors: Bengt Jonsson for ST-Ericsson + */ + +#ifndef __LINUX_MFD_AB8500_REGULATOR_DEBUG_H +#define __LINUX_MFD_AB8500_REGULATOR_DEBUG_H + +#ifdef CONFIG_REGULATOR_AB8500_DEBUG +/* AB8500 debug force/restore functions */ +void ab8500_regulator_debug_force(void); +void ab8500_regulator_debug_restore(void); +#else +static inline void ab8500_regulator_debug_force(void) {} +static inline void ab8500_regulator_debug_restore(void) {} +#endif + +#endif -- cgit v1.2.3 From a8de519c55509b715527225304eb8060a6c4c194 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 28 Apr 2011 13:23:01 +0200 Subject: modem: add mloader and trace features Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/Kconfig | 8 + arch/arm/mach-ux500/Makefile | 1 + arch/arm/mach-ux500/include/mach/mloader-dbx500.h | 48 ++++ arch/arm/mach-ux500/mloader-db8500.c | 82 +++++++ drivers/misc/Kconfig | 14 ++ drivers/misc/Makefile | 2 + drivers/misc/db8500-modem-trace.c | 273 ++++++++++++++++++++++ drivers/misc/dbx500-mloader.c | 269 +++++++++++++++++++++ include/linux/db8500-modem-trace.h | 24 ++ include/linux/mloader.h | 25 ++ 10 files changed, 746 insertions(+) create mode 100644 arch/arm/mach-ux500/include/mach/mloader-dbx500.h create mode 100644 arch/arm/mach-ux500/mloader-db8500.c create mode 100644 drivers/misc/db8500-modem-trace.c create mode 100644 drivers/misc/dbx500-mloader.c create mode 100644 include/linux/db8500-modem-trace.h create mode 100644 include/linux/mloader.h diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index 22b0c4903b4..f66dfffb68c 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -83,5 +83,13 @@ config TEE_SVP help Adds TEE support for SVP in ux500 platforms. +config DB8500_MLOADER + bool "Modem firmware upload/download support" + depends on UX500_SOC_DB8500 + select DBX500_MLOADER + default n + help + Adds Modem firmware upload/download support to DB8500. + source "arch/arm/mach-ux500/pm/Kconfig" endif diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 4790d43874f..78e27ceca40 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o obj-$(CONFIG_TEE_UX500) += tee_ux500.o obj-$(CONFIG_TEE_SVP) += tee_service_svp.o obj-$(CONFIG_TEE_SVP) += tee_ta_start_modem_svp.o +obj-$(CONFIG_DB8500_MLOADER) += mloader-db8500.o diff --git a/arch/arm/mach-ux500/include/mach/mloader-dbx500.h b/arch/arm/mach-ux500/include/mach/mloader-dbx500.h new file mode 100644 index 00000000000..68fa55a3f53 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/mloader-dbx500.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Ludovic Barre for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _MLOADER_UX500_H_ +#define _MLOADER_UX500_H_ + +/** + * struct dbx500_ml_area - data structure for modem memory areas description + * @name: name of the area + * @start: start address of the area + * @size: size of the area + */ +struct dbx500_ml_area { + const char *name; + u32 start; + u32 size; +}; + +/** + * struct dbx500_ml_fw - data stucture for modem firmwares description + * @name: firmware name + * @area: area where firmware is uploaded + * @offset: offset in the area where firmware is uploaded + */ +struct dbx500_ml_fw { + const char *name; + struct dbx500_ml_area *area; + u32 offset; +}; + +/** + * struct dbx500_mloader_pdata - data structure for platform specific data + * @fws: pointer on firmwares table + * @nr_fws: number of firmwares + * @areas: pointer on areas table + * @nr_areas: number of areas + */ +struct dbx500_mloader_pdata { + struct dbx500_ml_fw *fws; + int nr_fws; + struct dbx500_ml_area *areas; + int nr_areas; +}; + +#endif /* _MLOADER_UX500_H_ */ diff --git a/arch/arm/mach-ux500/mloader-db8500.c b/arch/arm/mach-ux500/mloader-db8500.c new file mode 100644 index 00000000000..6171a9db82f --- /dev/null +++ b/arch/arm/mach-ux500/mloader-db8500.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011 ST-Ericsson + * + * Author: Maxime Coquelin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ +#include +#include + +#include + +static struct dbx500_ml_area modem_areas[] = { + { .name = "modem_trace", .start = 0x6000000, .size = 0xf00000 }, + { .name = "modem_shared", .start = 0x6f00000, .size = 0x100000 }, + { .name = "modem_priv", .start = 0x7000000, .size = 0x1000000 }, +}; + +static struct dbx500_ml_fw modem_fws[] = { + { .name = "MODEM", .area = &modem_areas[0], .offset = 0x0 }, + { .name = "IPL", .area = &modem_areas[1], .offset = 0x00 }, +}; + +static struct dbx500_mloader_pdata mloader_fw_data = { + .fws = modem_fws, + .nr_fws = ARRAY_SIZE(modem_fws), + .areas = modem_areas, + .nr_areas = ARRAY_SIZE(modem_areas), +}; + +struct platform_device mloader_fw_device = { + .name = "dbx500_mloader_fw", + .id = -1, + .dev = { + .platform_data = &mloader_fw_data, + }, + .num_resources = 0, +}; + +/* Default areas can be overloaded in cmdline */ +static int __init early_modem_priv(char *p) +{ + struct dbx500_ml_area *area = &modem_areas[2]; + + area->size = memparse(p, &p); + + if (*p == '@') + area->start = memparse(p + 1, &p); + + return 0; +} +early_param("mem_modem", early_modem_priv); + +static int __init early_modem_shared(char *p) +{ + struct dbx500_ml_area *area = &modem_areas[1]; + + area->size = memparse(p, &p); + + if (*p == '@') + area->start = memparse(p + 1, &p); + + return 0; +} +early_param("mem_mshared", early_modem_shared); + +static int __init early_modem_trace(char *p) +{ + struct dbx500_ml_area *area = &modem_areas[0]; + + area->size = memparse(p, &p); + + if (*p == '@') + area->start = memparse(p + 1, &p); + + return 0; +} +early_param("mem_mtrace", early_modem_trace); + diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index e3c46657da5..a7e16ec23c3 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -461,6 +461,20 @@ config ARM_CHARLCD line and the Linux version on the second line, but that's still useful. +config STE_TRACE_MODEM + tristate "DB8500 trace Modem" + depends on ARCH_U8500 + default n + help + Select this option to enable modem tracing by APE + +config DBX500_MLOADER + tristate "Modem firmware loader for db8500" + default n + depends on UX500_SOC_DB8500 || UX500_SOC_DB5500 + help + Provides a user interface to load modem firmware on dbx500 SOCs + config BMP085 tristate "BMP085 digital pressure sensor" depends on I2C && SYSFS diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 44487937376..45a59bb8fce 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -47,6 +47,8 @@ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ obj-$(CONFIG_STM_TRACE) += stm.o/ +obj-$(CONFIG_STE_TRACE_MODEM) += db8500-modem-trace.o +obj-$(CONFIG_DBX500_MLOADER) += dbx500-mloader.o obj-$(CONFIG_U8500_SHRM) += shrm/ obj-$(CONFIG_STM_I2S) += i2s/ obj-$(CONFIG_STE_AUDIO_IO_DEV) += audio_io_dev/ diff --git a/drivers/misc/db8500-modem-trace.c b/drivers/misc/db8500-modem-trace.c new file mode 100644 index 00000000000..0d739fb4694 --- /dev/null +++ b/drivers/misc/db8500-modem-trace.c @@ -0,0 +1,273 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: Michel JAOUEN + * Maxime COQUELIN + * for ST-Ericsson + * License terms: GNU General Public License (GPL), version 2 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DEVICE_NAME "db8500-modem-trace" + +/* activation of this flag triggers an initialization of 2 buffers + * 4kbytes , id 0xdeadbeef + * and 16Kbytes id 0xfadafada + * we assume that platform provides minimum 20Kbytes. */ + +struct trace { + u32 start; + u32 end; + u32 mdm_base; + u32 ape_base; + void __iomem *area; + /* this spinlock to forbid concurrent access on the same trace buffer */ + spinlock_t lock; + struct device *dev; + struct miscdevice misc_dev; +}; + +struct trace_modem { + u32 phys_addr; + u8 filler; +}; + +static struct trace *trace_priv; + + +/* all this definition are linked to modem interface */ +#define MODEM_MARKER 0x88 +/* free marker is also written on filler */ +#define FREE_MARKER 0xa5 +#define FREE_MARKER_2 0xa5a5 +#define READ_MARKER 0x5a + +struct buffer_header { + u8 pattern; + u8 filler; + u16 head_size; +}; + + +static int trace_read(unsigned long arg) +{ + struct modem_trace_req req; + struct buffer_header *pt; + char tmp_char; + + if (copy_from_user(&req, (struct modem_trace_req *)arg, + sizeof(struct modem_trace_req))) + return -EFAULT; + + /* compute Modem physical address to APE physical address range */ + if (req.phys_addr < trace_priv->mdm_base) { + dev_err(trace_priv->dev, "MODEM ADDR uncorrect\n"); + return -EINVAL; + } + req.phys_addr += trace_priv->ape_base - trace_priv->mdm_base; + + /* check request is in the range and aligned */ + if ((req.phys_addr % 4 != 0) + || (req.phys_addr < trace_priv->start) + || (req.phys_addr + req.size) >= trace_priv->end) { + dev_err(trace_priv->dev, "req out of range %x %x\n", + req.phys_addr, req.size); + return -EINVAL; + } + + /* perform access to memory area */ + pt = (struct buffer_header *)((u32)trace_priv->area + + req.phys_addr - trace_priv->start); + + /* in case of several request coming on same trace buffer take a + * spinlock */ + spin_lock(&trace_priv->lock); + if (pt->pattern != MODEM_MARKER) { + /* pattern and size not matching */ + dev_err(trace_priv->dev, "req not matching filler %x/%x \ + or/and pattern %x\n", req.filler, pt->filler, + pt->pattern); + spin_unlock(&trace_priv->lock); + return -EINVAL; + } + /* mark pattern as read and unlock spin */ + pt->pattern = READ_MARKER; + spin_unlock(&trace_priv->lock); + + req.size -= copy_to_user(req.buff, pt, req.size); + + pt->pattern = FREE_MARKER; + pt->filler = FREE_MARKER; + tmp_char = MODEM_MARKER; + + /* Update marker for trace tool */ + if (copy_to_user(req.buff, &tmp_char, 1)) + return -EFAULT; + + /* Update effective written size */ + if (copy_to_user((struct modem_trace_req *)arg, &req, + sizeof(struct modem_trace_req))) + return -EFAULT; + + return 0; +} + +static int trace_mmapdump(struct file *file, struct vm_area_struct *vma) +{ + unsigned long vma_start = vma->vm_start; + + if (vma->vm_flags & VM_WRITE) + return -EPERM; + + if ((vma->vm_end - vma->vm_start) < + (trace_priv->end - trace_priv->start)) + return -EINVAL; + if (remap_pfn_range(vma, + vma_start, + trace_priv->start >> PAGE_SHIFT, + trace_priv->end - trace_priv->start, + vma->vm_page_prot)) + return -EAGAIN; + return 0; +} + +static long trace_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + void __user *argp = (void __user *)arg; + unsigned long size = trace_priv->end-trace_priv->start; + + switch (cmd) { + case TM_GET_DUMPINFO: + ret = put_user(size, (unsigned long *)argp); + break; + case TM_TRACE_REQ: + ret = trace_read(arg); + break; + + default: + ret = -EPERM; + break; + } + return ret; +} + +static const struct file_operations trace_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = trace_ioctl, + .mmap = trace_mmapdump +}; + +static int trace_probe(struct platform_device *pdev) +{ + int rv = 0; + struct db8500_trace_platform_data *pdata = pdev->dev.platform_data; + /* retrieve area descriptor from platform device ressource */ + struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if ((mem->start == 0) && (mem->end == 0)) { + rv = -EINVAL; + goto out; + } + + if ((pdata->ape_base == 0) || (pdata->modem_base == 0)) { + rv = -EINVAL; + goto out; + } + + trace_priv = kzalloc(sizeof(*trace_priv), GFP_ATOMIC); + if (!trace_priv) { + rv = -ENOMEM; + goto out; + } + + trace_priv->dev = &pdev->dev; + trace_priv->misc_dev.minor = MISC_DYNAMIC_MINOR; + trace_priv->misc_dev.name = DEVICE_NAME; + trace_priv->misc_dev.fops = &trace_fops; + trace_priv->area = (void __iomem *)ioremap_nocache(mem->start, + mem->end - mem->start); + if (!trace_priv->area) { + rv = -ENOMEM; + goto outfree; + } + + trace_priv->start = mem->start; + trace_priv->end = mem->end; + + trace_priv->mdm_base = pdata->modem_base; + trace_priv->ape_base = pdata->ape_base; + + /* spin allowing smp access for reading/writing trace buffer header */ + spin_lock_init(&trace_priv->lock); + + rv = misc_register(&trace_priv->misc_dev); + if (rv) { + dev_err(&pdev->dev, "can't misc_register\n"); + goto outunmap; + } + + return rv; + +outunmap: + iounmap(trace_priv->area); +outfree: + kfree(trace_priv); +out: + return rv; + +} + +static int trace_remove(struct platform_device *pdev) +{ + int rv = 0; + + if (trace_priv) { + rv = misc_deregister(&trace_priv->misc_dev); + iounmap(trace_priv->area); + kfree(trace_priv); + } + + return rv; +} + +static struct platform_driver trace_driver = { + .probe = trace_probe, + .remove = trace_remove, + .driver = { + .name = "db8500-modem-trace", + .owner = THIS_MODULE, + }, +}; + +static int trace_init(void) +{ + platform_driver_register(&trace_driver); + return 0; +} +static void trace_exit(void) +{ + platform_driver_unregister(&trace_driver); +} +module_init(trace_init); +module_exit(trace_exit); + +MODULE_AUTHOR("ST-Ericsson"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/dbx500-mloader.c b/drivers/misc/dbx500-mloader.c new file mode 100644 index 00000000000..c3ec8b67983 --- /dev/null +++ b/drivers/misc/dbx500-mloader.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Ludovic Barre for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DEVICE_NAME "dbx500_mloader_fw" + +struct mloader_priv { + struct platform_device *pdev; + struct dbx500_mloader_pdata *pdata; + struct miscdevice misc_dev; + u32 aeras_size; +}; + +static struct mloader_priv *mloader_priv; + +static int mloader_fw_send(struct dbx500_ml_fw *fw_info) +{ + const struct firmware *fw; + unsigned long size; + unsigned long phys_start; + void *fw_data; + void *vaddr; + void __iomem *ioaddr; + int ret; + + ret = request_firmware(&fw, fw_info->name, &mloader_priv->pdev->dev); + if (ret) { + dev_err(&mloader_priv->pdev->dev, "request firmware failed\n"); + goto out; + } + + if (fw->size > (fw_info->area->size - fw_info->offset)) { + dev_err(&mloader_priv->pdev->dev, + "fw:%s is too big for:%s\n", + fw_info->name, fw_info->area->name); + ret = -EINVAL; + goto err_fw; + } + + size = PAGE_ALIGN(fw->size); + phys_start = fw_info->area->start + fw_info->offset; + phys_start &= PAGE_MASK; + ioaddr = ioremap(phys_start, size); + if (!ioaddr) { + dev_err(&mloader_priv->pdev->dev, + "failed remap memory region.\n"); + ret = -EINVAL; + goto err_fw; + } + + vaddr = ioaddr + (fw_info->offset & ~PAGE_MASK); + fw_data = (void *)fw->data; + memcpy_toio(vaddr, fw_data, fw->size); + iounmap(ioaddr); + +err_fw: + release_firmware(fw); +out: + return ret; +} + +static int mloader_fw_upload(void) +{ + int i, ret; + struct dbx500_mloader_pdata *pdata = mloader_priv->pdata; + + for (i = 0; i < pdata->nr_fws; i++) { + ret = mloader_fw_send(&pdata->fws[i]); + if (ret) + goto err; + } + + return 0; +err: + dev_err(&mloader_priv->pdev->dev, + "Failed to upload %s firmware", pdata->fws[i].name); + return ret; +} + +static int mloader_fw_mmapdump(struct file *file, struct vm_area_struct *vma) +{ + int i; + unsigned long dump_size = 0; + unsigned long vma_start = vma->vm_start; + + if (vma->vm_flags & VM_WRITE) + return -EPERM; + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) + dump_size += mloader_priv->pdata->areas[i].size; + + if ((vma->vm_end - vma->vm_start) < dump_size) + return -EINVAL; + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) { + if (remap_pfn_range(vma, + vma_start, + mloader_priv->pdata->areas[i].start >> PAGE_SHIFT, + mloader_priv->pdata->areas[i].size, + vma->vm_page_prot)) + return -EAGAIN; + vma_start += mloader_priv->pdata->areas[i].size; + } + return 0; +} + +static void mloader_fw_dumpinfo(struct dump_image *images) +{ + u32 offset = 0; + int i; + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) { + strncpy(images[i].name, + mloader_priv->pdata->areas[i].name, MAX_NAME); + images[i].name[MAX_NAME-1] = 0; + images[i].offset = offset; + images[i].size = mloader_priv->pdata->areas[i].size; + offset += mloader_priv->pdata->areas[i].size; + } +} + +static long mloader_fw_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case ML_UPLOAD: + ret = mloader_fw_upload(); + break; + case ML_GET_NBIMAGES: + ret = put_user(mloader_priv->pdata->nr_areas, + (unsigned long __user *)argp); + break; + case ML_GET_DUMPINFO: { + struct dump_image *dump_images; + dump_images = kzalloc(mloader_priv->pdata->nr_areas + * sizeof(struct dump_image), GFP_ATOMIC); + mloader_fw_dumpinfo(dump_images); + ret = copy_to_user(argp, (void *) dump_images, + mloader_priv->pdata->nr_areas + * sizeof(struct dump_image)) ? -EFAULT : 0; + kfree(dump_images); + break; + } + default: + ret = -EPERM; + break; + } + + return ret; +} + +static const struct file_operations modem_fw_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = mloader_fw_ioctl, + .mmap = mloader_fw_mmapdump, +}; + +static int __devinit mloader_fw_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + + mloader_priv = kzalloc(sizeof(*mloader_priv), GFP_ATOMIC); + if (!mloader_priv) { + ret = -ENOMEM; + goto out; + } + + mloader_priv->pdev = pdev; + mloader_priv->pdata = pdev->dev.platform_data; + + mloader_priv->misc_dev.minor = MISC_DYNAMIC_MINOR; + mloader_priv->misc_dev.name = DEVICE_NAME; + mloader_priv->misc_dev.fops = &modem_fw_fops; + ret = misc_register(&mloader_priv->misc_dev); + if (ret < 0) { + dev_err(&pdev->dev, "can't misc_register\n"); + goto err_free_priv; + } + + dev_info(&mloader_priv->pdev->dev, "mloader device register\n"); + + for (i = 0 ; i < mloader_priv->pdata->nr_areas ; i++) { + dev_dbg(&mloader_priv->pdev->dev, + "Area:%d (name:%s start:%x size:%x)\n", + i, mloader_priv->pdata->areas[i].name, + mloader_priv->pdata->areas[i].start, + mloader_priv->pdata->areas[i].size); + } + + for (i = 0 ; i < mloader_priv->pdata->nr_fws ; i++) { + dev_dbg(&mloader_priv->pdev->dev, + "Firmware:%d (name:%s offset:%x " + "area_name:%s area_start:%x area_size:%x)\n", + i, mloader_priv->pdata->fws[i].name, + mloader_priv->pdata->fws[i].offset, + mloader_priv->pdata->fws[i].area->name, + mloader_priv->pdata->fws[i].area->start, + mloader_priv->pdata->fws[i].area->size); + } + + return ret; + +err_free_priv: + kfree(mloader_priv); +out: + return ret; +} + +static int __devexit mloader_fw_remove(struct platform_device *pdev) +{ + int err; + + err = misc_register(&mloader_priv->misc_dev); + if (err < 0) + dev_err(&pdev->dev, "can't misc_deregister, %d\n", err); + + kfree(mloader_priv); + + return err; +} + +static struct platform_driver mloader_fw_driver = { + .driver.name = DEVICE_NAME, + .driver.owner = THIS_MODULE, + .probe = mloader_fw_probe, + .remove = __devexit_p(mloader_fw_remove), +}; + +static int __init mloader_fw_init(void) +{ + return platform_driver_register(&mloader_fw_driver); +} + +static void __exit mloader_fw_exit(void) +{ + kfree(mloader_priv); + platform_driver_unregister(&mloader_fw_driver); +} + +module_init(mloader_fw_init); +module_exit(mloader_fw_exit); +MODULE_DESCRIPTION("ST-Ericsson modem loader firmware"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ludovic Barre "); diff --git a/include/linux/db8500-modem-trace.h b/include/linux/db8500-modem-trace.h new file mode 100644 index 00000000000..4863e1a0b03 --- /dev/null +++ b/include/linux/db8500-modem-trace.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: Michel JAOUEN + * Maxime COQUELIN + * for ST-Ericsson + * License terms: GNU General Public License (GPL), version 2 + */ +/* macro for requesting a trace read */ + +struct modem_trace_req { + __u32 phys_addr; + __u8 filler; + __u8 *buff; + __u32 size; +}; + +#define TM_IO_NUMBER 0xfc +#define TM_GET_DUMPINFO _IOR(TM_IO_NUMBER, 1, unsigned long) +#define TM_TRACE_REQ _IOWR(TM_IO_NUMBER, 2, unsigned long) + +struct db8500_trace_platform_data { + unsigned long ape_base; + unsigned long modem_base; +}; diff --git a/include/linux/mloader.h b/include/linux/mloader.h new file mode 100644 index 00000000000..ceca3245856 --- /dev/null +++ b/include/linux/mloader.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Ludovic Barre for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _MLOADER_H_ +#define _MLOADER_H_ + +/* not use in ioctl-number.txt */ +#define ML_IO_NUMBER 0xFE + +#define ML_UPLOAD _IO(ML_IO_NUMBER, 1) +#define ML_GET_NBIMAGES _IOR(ML_IO_NUMBER, 2, int) +#define ML_GET_DUMPINFO _IOR(ML_IO_NUMBER, 3, struct dump_image*) + +#define MAX_NAME 16 + +struct dump_image { + char name[MAX_NAME]; + unsigned int offset; + unsigned int size; +}; + +#endif /* _MLOADER_H_ */ -- cgit v1.2.3 From 714a2ff87b21bed8f26e8f9f3dc3aa105057c85a Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 28 Apr 2011 14:16:26 +0200 Subject: mach-ux500: all the necessary for abx500 platform data --- arch/arm/mach-ux500/Makefile | 7 +- arch/arm/mach-ux500/board-mop500-bm.c | 411 +++++++++++++++++++++ arch/arm/mach-ux500/board-mop500-bm.h | 24 ++ arch/arm/mach-ux500/board-mop500-msp.c | 225 +++++++++++ arch/arm/mach-ux500/board-mop500.c | 24 ++ arch/arm/mach-ux500/board-mop500.h | 3 + .../arm/mach-ux500/include/mach/ste-dma40-db8500.h | 147 ++++++++ arch/arm/mach-ux500/include/mach/ste_audio.h | 18 + 8 files changed, 858 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-ux500/board-mop500-bm.c create mode 100644 arch/arm/mach-ux500/board-mop500-bm.h create mode 100644 arch/arm/mach-ux500/board-mop500-msp.c create mode 100644 arch/arm/mach-ux500/include/mach/ste-dma40-db8500.h create mode 100644 arch/arm/mach-ux500/include/mach/ste_audio.h diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 78e27ceca40..03525cc7be9 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -2,6 +2,10 @@ # Makefile for the linux kernel, U8500 machine. # +ifeq ($(CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL), y) + CFLAGS_board-mop500-bm.o += -DAB8500_BATTERY_THERM_ON_BATCTRL +endif + obj-y := clock.o cpu.o devices.o devices-common.o \ id.o usb.o obj-y += pm/ @@ -13,7 +17,8 @@ obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \ board-mop500-regulators.o \ board-mop500-uib.o board-mop500-stuib.o \ board-mop500-u8500uib.o \ - board-mop500-pins.o + board-mop500-pins.o \ + board-mop500-msp.o board-mop500-bm.o obj-$(CONFIG_MACH_U5500) += board-u5500.o board-u5500-sdi.o obj-$(CONFIG_SMP) += platsmp.o headsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c new file mode 100644 index 00000000000..031d8721f4a --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson for ST-Ericsson. + * Author: Johan Gardsmark for ST-Ericsson. + * + */ + +#include +#include +#include "board-mop500-bm.h" + +#ifdef AB8500_BATTERY_THERM_ON_BATCTRL +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +static struct res_to_temp temp_tbl_A[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; +static struct res_to_temp temp_tbl_B[] = { + {-5, 165418}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; +static struct v_to_cap cap_tbl_A[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; +static struct v_to_cap cap_tbl_B[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; +#endif +static struct v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +static const struct battery_type bat_type[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + +#ifdef AB8500_BATTERY_THERM_ON_BATCTRL + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 53407, + .resis_low = 12500, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_A), + .r_to_t_tbl = temp_tbl_A, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_A), + .v_to_cap_tbl = cap_tbl_A, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 165418, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B), + .r_to_t_tbl = temp_tbl_B, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B), + .v_to_cap_tbl = cap_tbl_B, + }, +#else +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, +#endif +}; + +static char *ab8500_charger_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", + "ab8500_btemp", +}; + +static char *ab8500_btemp_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", +}; + +static char *ab8500_fg_supplied_to[] = { + "ab8500_chargalg", +}; + +static char *ab8500_chargalg_supplied_to[] = { + "ab8500_fg", +}; + +struct ab8500_charger_platform_data ab8500_charger_plat_data = { + .supplied_to = ab8500_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_charger_supplied_to), +}; + +struct ab8500_btemp_platform_data ab8500_btemp_plat_data = { + .supplied_to = ab8500_btemp_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_btemp_supplied_to), +}; + +struct ab8500_fg_platform_data ab8500_fg_plat_data = { + .supplied_to = ab8500_fg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_fg_supplied_to), +}; + +struct ab8500_chargalg_platform_data ab8500_chargalg_plat_data = { + .supplied_to = ab8500_chargalg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_chargalg_supplied_to), +}; + +static const struct ab8500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct ab8500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, +}; + +static const struct ab8500_maxim_parameters maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct ab8500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +struct ab8500_bm_data ab8500_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 55, + .temp_over = 60, + .main_safety_tmr_h = 4, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, +#ifdef AB8500_BATTERY_THERM_ON_BATCTRL + .adc_therm = ADC_THERM_BATCTRL, +#else + .adc_therm = ADC_THERM_BATTEMP, +#endif + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 10, + .cap_levels = &cap_levels, + .bat_type = bat_type, + .n_btypes = ARRAY_SIZE(bat_type), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .maxi = &maxi_params, + .chg_params = &chg, + .fg_params = &fg, +}; diff --git a/arch/arm/mach-ux500/board-mop500-bm.h b/arch/arm/mach-ux500/board-mop500-bm.h new file mode 100644 index 00000000000..eb2450f1ab5 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson for ST-Ericsson. + * Author: Johan Gardsmark for ST-Ericsson. + * + */ + +#ifndef __BOARD_MOP500_BM_H +#define __BOARD_MOP500_BM_H + +#include + +extern struct ab8500_charger_platform_data ab8500_charger_plat_data; +extern struct ab8500_btemp_platform_data ab8500_btemp_plat_data; +extern struct ab8500_fg_platform_data ab8500_fg_plat_data; +extern struct ab8500_chargalg_platform_data ab8500_chargalg_plat_data; +extern struct ab8500_bm_data ab8500_bm_data; + +#endif diff --git a/arch/arm/mach-ux500/board-mop500-msp.c b/arch/arm/mach-ux500/board-mop500-msp.c new file mode 100644 index 00000000000..8867dded7aa --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-msp.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "board-mop500.h" +#include "devices-db8500.h" +#include "pins-db8500.h" + +/* MSP1/3 Tx/Rx usage protection */ +static DEFINE_SPINLOCK(msp_rxtx_lock); + +/* Reference Count */ +static int msp_rxtx_ref; + +static pin_cfg_t mop500_msp1_pins[] = { + GPIO33_MSP1_TXD | PIN_OUTPUT_LOW, + GPIO34_MSP1_TFS | PIN_INPUT_NOPULL, + GPIO35_MSP1_TCK | PIN_INPUT_NOPULL, + GPIO36_MSP1_RXD | PIN_INPUT_NOPULL, +}; + +int msp13_i2s_init(void) +{ + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&msp_rxtx_lock, flags); + if (msp_rxtx_ref == 0) + retval = nmk_config_pins( + ARRAY_AND_SIZE(mop500_msp1_pins)); + if (!retval) + msp_rxtx_ref++; + spin_unlock_irqrestore(&msp_rxtx_lock, flags); + + return retval; +} + +int msp13_i2s_exit(void) +{ + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&msp_rxtx_lock, flags); + WARN_ON(!msp_rxtx_ref); + msp_rxtx_ref--; + if (msp_rxtx_ref == 0) + retval = nmk_config_pins_sleep( + ARRAY_AND_SIZE(mop500_msp1_pins)); + spin_unlock_irqrestore(&msp_rxtx_lock, flags); + + return retval; +} + +static struct stedma40_chan_cfg msp0_dma_rx = { + .high_priority = true, + .dir = STEDMA40_PERIPH_TO_MEM, + + .src_dev_type = DB8500_DMA_DEV31_MSP0_RX_SLIM0_CH0_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + + /* data_width is set during configuration */ +}; + +static struct stedma40_chan_cfg msp0_dma_tx = { + .high_priority = true, + .dir = STEDMA40_MEM_TO_PERIPH, + + .src_dev_type = STEDMA40_DEV_DST_MEMORY, + .dst_dev_type = DB8500_DMA_DEV31_MSP0_TX_SLIM0_CH0_TX, + + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + + /* data_width is set during configuration */ +}; + +static struct msp_i2s_platform_data msp0_platform_data = { + .id = MSP_0_I2S_CONTROLLER, + .msp_i2s_dma_rx = &msp0_dma_rx, + .msp_i2s_dma_tx = &msp0_dma_tx, +}; + +static struct stedma40_chan_cfg msp1_dma_rx = { + .high_priority = true, + .dir = STEDMA40_PERIPH_TO_MEM, + + .src_dev_type = DB8500_DMA_DEV30_MSP1_RX, /* v2: MSP3 RX */ + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + + /* data_width is set during configuration */ +}; + +static struct stedma40_chan_cfg msp1_dma_tx = { + .high_priority = true, + .dir = STEDMA40_MEM_TO_PERIPH, + + .src_dev_type = STEDMA40_DEV_DST_MEMORY, + .dst_dev_type = DB8500_DMA_DEV30_MSP1_TX, + + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + + /* data_width is set during configuration */ +}; + +static struct msp_i2s_platform_data msp1_platform_data = { + .id = MSP_1_I2S_CONTROLLER, + .msp_i2s_dma_rx = &msp1_dma_rx, + .msp_i2s_dma_tx = &msp1_dma_tx, + .msp_i2s_init = msp13_i2s_init, + .msp_i2s_exit = msp13_i2s_exit, +}; + +static struct stedma40_chan_cfg msp2_dma_rx = { + .high_priority = true, + .dir = STEDMA40_PERIPH_TO_MEM, + + .src_dev_type = DB8500_DMA_DEV14_MSP2_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + + /* data_width is set during configuration */ +}; + +static struct stedma40_chan_cfg msp2_dma_tx = { + .high_priority = true, + .dir = STEDMA40_MEM_TO_PERIPH, + + .src_dev_type = STEDMA40_DEV_DST_MEMORY, + .dst_dev_type = DB8500_DMA_DEV14_MSP2_TX, + + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + + /* data_width is set during configuration */ +}; + +static struct msp_i2s_platform_data msp2_platform_data = { + .id = MSP_2_I2S_CONTROLLER, + .msp_i2s_dma_rx = &msp2_dma_rx, + .msp_i2s_dma_tx = &msp2_dma_tx, +}; + +static struct msp_i2s_platform_data msp3_platform_data = { + .id = MSP_3_I2S_CONTROLLER, + .msp_i2s_dma_rx = &msp1_dma_rx, + .msp_i2s_dma_tx = NULL, + .msp_i2s_init = msp13_i2s_init, + .msp_i2s_exit = msp13_i2s_exit, +}; + +static struct i2s_board_info stm_i2s_board_info[] __initdata = { + { + .modalias = "i2s_device.0", + .id = 0, + .chip_select = 0, + }, + { + .modalias = "i2s_device.1", + .id = 1, + .chip_select = 1, + }, + { + .modalias = "i2s_device.2", + .id = 2, + .chip_select = 2, + }, + { + .modalias = "i2s_device.3", + .id = 3, + .chip_select = 3, + }, +}; + +static void __init mop500_msp_fixup(void) +{ + if (cpu_is_u8500ed() || cpu_is_u8500v1()) + return; + + /* DMA Rx is moved to MSP3 on DB8500v2 */ + msp1_platform_data.msp_i2s_dma_rx = NULL; + + /* MSP2 DMA doesn't work with PSIZE == 4 on DB8500v2 */ + msp2_dma_rx.src_info.psize = STEDMA40_PSIZE_LOG_1; + msp2_dma_rx.dst_info.psize = STEDMA40_PSIZE_LOG_1; +} + +void __init mop500_msp_init(void) +{ + mop500_msp_fixup(); + + db8500_add_msp0_i2s(&msp0_platform_data); + db8500_add_msp1_i2s(&msp1_platform_data); + db8500_add_msp2_i2s(&msp2_platform_data); + + if (cpu_is_u8500v2()) + db8500_add_msp3_i2s(&msp3_platform_data); + + i2s_register_board_info(stm_i2s_board_info, + ARRAY_SIZE(stm_i2s_board_info)); +} diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index 7fee708888c..6625669ddd3 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -44,12 +45,26 @@ #include #include #include +#include #include "pins-db8500.h" #include "ste-dma40-db8500.h" #include "devices-db8500.h" #include "board-mop500.h" #include "board-mop500-regulators.h" +#include "board-mop500-bm.h" + +#ifdef CONFIG_AB8500_DENC +static struct ab8500_denc_platform_data ab8500_denc_pdata = { + .ddr_enable = true, + .ddr_little_endian = false, +}; +#endif + +static struct ab8500_audio_platform_data ab8500_audio_plat_data = { + .ste_gpio_altf_init = msp13_i2s_init, + .ste_gpio_altf_exit = msp13_i2s_exit, +}; static struct gpio_led snowball_led_array[] = { { @@ -190,6 +205,15 @@ static struct ab8500_platform_data ab8500_platdata = { .num_regulator_reg_init = ARRAY_SIZE(ab8500_regulator_reg_init), .regulator = ab8500_regulators, .num_regulator = ARRAY_SIZE(ab8500_regulators), +#ifdef CONFIG_AB8500_DENC + .denc = &ab8500_denc_pdata, +#endif + .audio = &ab8500_audio_plat_data, + .battery = &ab8500_bm_data, + .charger = &ab8500_charger_plat_data, + .btemp = &ab8500_btemp_plat_data, + .fg = &ab8500_fg_plat_data, + .chargalg = &ab8500_chargalg_plat_data, .gpio = &ab8500_gpio_pdata, }; diff --git a/arch/arm/mach-ux500/board-mop500.h b/arch/arm/mach-ux500/board-mop500.h index c7f1e6fee3b..158d978252e 100644 --- a/arch/arm/mach-ux500/board-mop500.h +++ b/arch/arm/mach-ux500/board-mop500.h @@ -53,4 +53,7 @@ void __init mop500_pins_init(void); void mop500_uib_i2c_add(int busnum, struct i2c_board_info *info, unsigned n); +int msp13_i2s_init(void); +int msp13_i2s_exit(void); + #endif diff --git a/arch/arm/mach-ux500/include/mach/ste-dma40-db8500.h b/arch/arm/mach-ux500/include/mach/ste-dma40-db8500.h new file mode 100644 index 00000000000..64c494f0836 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ste-dma40-db8500.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) ST-Ericsson SA 2007-2010 + * Author: Per Friden for ST-Ericsson + * Author: Jonas Aaberg for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * + * DB8500-SoC-specific configuration for DMA40 + */ +#ifndef STE_DMA40_DB8500_H +#define STE_DMA40_DB8500_H + +#define DB8500_DMA_NR_DEV 64 +/* + * All entries with double names are multiplexed + * and can never be used at the same time. + */ + +enum dma_src_dev_type { + DB8500_DMA_DEV0_SPI0_RX = 0, + DB8500_DMA_DEV1_SD_MMC0_RX = 1, + DB8500_DMA_DEV2_SD_MMC1_RX = 2, + DB8500_DMA_DEV3_SD_MMC2_RX = 3, + DB8500_DMA_DEV4_I2C1_RX = 4, + DB8500_DMA_DEV5_I2C3_RX = 5, + DB8500_DMA_DEV6_I2C2_RX = 6, + DB8500_DMA_DEV7_I2C4_RX = 7, /* Only on V1 and later */ + DB8500_DMA_DEV8_SSP0_RX = 8, + DB8500_DMA_DEV9_SSP1_RX = 9, + DB8500_DMA_DEV10_MCDE_RX = 10, + DB8500_DMA_DEV11_UART2_RX = 11, + DB8500_DMA_DEV12_UART1_RX = 12, + DB8500_DMA_DEV13_UART0_RX = 13, + DB8500_DMA_DEV14_MSP2_RX = 14, + DB8500_DMA_DEV15_I2C0_RX = 15, + DB8500_DMA_DEV16_USB_OTG_IEP_7_15 = 16, + DB8500_DMA_DEV17_USB_OTG_IEP_6_14 = 17, + DB8500_DMA_DEV18_USB_OTG_IEP_5_13 = 18, + DB8500_DMA_DEV19_USB_OTG_IEP_4_12 = 19, + DB8500_DMA_DEV20_SLIM0_CH0_RX_HSI_RX_CH0 = 20, + DB8500_DMA_DEV21_SLIM0_CH1_RX_HSI_RX_CH1 = 21, + DB8500_DMA_DEV22_SLIM0_CH2_RX_HSI_RX_CH2 = 22, + DB8500_DMA_DEV23_SLIM0_CH3_RX_HSI_RX_CH3 = 23, + DB8500_DMA_DEV24_SRC_SXA0_RX_TX = 24, + DB8500_DMA_DEV25_SRC_SXA1_RX_TX = 25, + DB8500_DMA_DEV26_SRC_SXA2_RX_TX = 26, + DB8500_DMA_DEV27_SRC_SXA3_RX_TX = 27, + DB8500_DMA_DEV28_SD_MM2_RX = 28, + DB8500_DMA_DEV29_SD_MM0_RX = 29, + DB8500_DMA_DEV30_MSP1_RX = 30, + /* On DB8500v2, MSP3 RX replaces MSP1 RX */ + DB8500_DMA_DEV30_MSP3_RX = 30, + DB8500_DMA_DEV31_MSP0_RX_SLIM0_CH0_RX = 31, + DB8500_DMA_DEV32_SD_MM1_RX = 32, + DB8500_DMA_DEV33_SPI2_RX = 33, + DB8500_DMA_DEV34_I2C3_RX2 = 34, + DB8500_DMA_DEV35_SPI1_RX = 35, + DB8500_DMA_DEV36_USB_OTG_IEP_3_11 = 36, + DB8500_DMA_DEV37_USB_OTG_IEP_2_10 = 37, + DB8500_DMA_DEV38_USB_OTG_IEP_1_9 = 38, + DB8500_DMA_DEV39_USB_OTG_IEP_8 = 39, + DB8500_DMA_DEV40_SPI3_RX = 40, + DB8500_DMA_DEV41_SD_MM3_RX = 41, + DB8500_DMA_DEV42_SD_MM4_RX = 42, + DB8500_DMA_DEV43_SD_MM5_RX = 43, + DB8500_DMA_DEV44_SRC_SXA4_RX_TX = 44, + DB8500_DMA_DEV45_SRC_SXA5_RX_TX = 45, + DB8500_DMA_DEV46_SLIM0_CH8_RX_SRC_SXA6_RX_TX = 46, + DB8500_DMA_DEV47_SLIM0_CH9_RX_SRC_SXA7_RX_TX = 47, + DB8500_DMA_DEV48_CAC1_RX = 48, + /* 49, 50 and 51 are not used */ + DB8500_DMA_DEV52_SLIM0_CH4_RX_HSI_RX_CH4 = 52, + DB8500_DMA_DEV53_SLIM0_CH5_RX_HSI_RX_CH5 = 53, + DB8500_DMA_DEV54_SLIM0_CH6_RX_HSI_RX_CH6 = 54, + DB8500_DMA_DEV55_SLIM0_CH7_RX_HSI_RX_CH7 = 55, + /* 56, 57, 58, 59 and 60 are not used */ + DB8500_DMA_DEV61_CAC0_RX = 61, + /* 62 and 63 are not used */ +}; + +enum dma_dest_dev_type { + DB8500_DMA_DEV0_SPI0_TX = 0, + DB8500_DMA_DEV1_SD_MMC0_TX = 1, + DB8500_DMA_DEV2_SD_MMC1_TX = 2, + DB8500_DMA_DEV3_SD_MMC2_TX = 3, + DB8500_DMA_DEV4_I2C1_TX = 4, + DB8500_DMA_DEV5_I2C3_TX = 5, + DB8500_DMA_DEV6_I2C2_TX = 6, + DB8500_DMA_DEV7_I2C4_TX = 7, /* Only on V1 and later */ + DB8500_DMA_DEV8_SSP0_TX = 8, + DB8500_DMA_DEV9_SSP1_TX = 9, + /* 10 is not used*/ + DB8500_DMA_DEV11_UART2_TX = 11, + DB8500_DMA_DEV12_UART1_TX = 12, + DB8500_DMA_DEV13_UART0_TX = 13, + DB8500_DMA_DEV14_MSP2_TX = 14, + DB8500_DMA_DEV15_I2C0_TX = 15, + DB8500_DMA_DEV16_USB_OTG_OEP_7_15 = 16, + DB8500_DMA_DEV17_USB_OTG_OEP_6_14 = 17, + DB8500_DMA_DEV18_USB_OTG_OEP_5_13 = 18, + DB8500_DMA_DEV19_USB_OTG_OEP_4_12 = 19, + DB8500_DMA_DEV20_SLIM0_CH0_TX_HSI_TX_CH0 = 20, + DB8500_DMA_DEV21_SLIM0_CH1_TX_HSI_TX_CH1 = 21, + DB8500_DMA_DEV22_SLIM0_CH2_TX_HSI_TX_CH2 = 22, + DB8500_DMA_DEV23_SLIM0_CH3_TX_HSI_TX_CH3 = 23, + DB8500_DMA_DEV24_DST_SXA0_RX_TX = 24, + DB8500_DMA_DEV25_DST_SXA1_RX_TX = 25, + DB8500_DMA_DEV26_DST_SXA2_RX_TX = 26, + DB8500_DMA_DEV27_DST_SXA3_RX_TX = 27, + DB8500_DMA_DEV28_SD_MM2_TX = 28, + DB8500_DMA_DEV29_SD_MM0_TX = 29, + DB8500_DMA_DEV30_MSP1_TX = 30, + DB8500_DMA_DEV31_MSP0_TX_SLIM0_CH0_TX = 31, + DB8500_DMA_DEV32_SD_MM1_TX = 32, + DB8500_DMA_DEV33_SPI2_TX = 33, + DB8500_DMA_DEV34_I2C3_TX2 = 34, + DB8500_DMA_DEV35_SPI1_TX = 35, + DB8500_DMA_DEV36_USB_OTG_OEP_3_11 = 36, + DB8500_DMA_DEV37_USB_OTG_OEP_2_10 = 37, + DB8500_DMA_DEV38_USB_OTG_OEP_1_9 = 38, + DB8500_DMA_DEV39_USB_OTG_OEP_8 = 39, + DB8500_DMA_DEV40_SPI3_TX = 40, + DB8500_DMA_DEV41_SD_MM3_TX = 41, + DB8500_DMA_DEV42_SD_MM4_TX = 42, + DB8500_DMA_DEV43_SD_MM5_TX = 43, + DB8500_DMA_DEV44_DST_SXA4_RX_TX = 44, + DB8500_DMA_DEV45_DST_SXA5_RX_TX = 45, + DB8500_DMA_DEV46_SLIM0_CH8_TX_DST_SXA6_RX_TX = 46, + DB8500_DMA_DEV47_SLIM0_CH9_TX_DST_SXA7_RX_TX = 47, + DB8500_DMA_DEV48_CAC1_TX = 48, + DB8500_DMA_DEV49_CAC1_TX_HAC1_TX = 49, + DB8500_DMA_DEV50_HAC1_TX = 50, + DB8500_DMA_MEMCPY_TX_0 = 51, + DB8500_DMA_DEV52_SLIM1_CH4_TX_HSI_TX_CH4 = 52, + DB8500_DMA_DEV53_SLIM1_CH5_TX_HSI_TX_CH5 = 53, + DB8500_DMA_DEV54_SLIM1_CH6_TX_HSI_TX_CH6 = 54, + DB8500_DMA_DEV55_SLIM1_CH7_TX_HSI_TX_CH7 = 55, + DB8500_DMA_MEMCPY_TX_1 = 56, + DB8500_DMA_MEMCPY_TX_2 = 57, + DB8500_DMA_MEMCPY_TX_3 = 58, + DB8500_DMA_MEMCPY_TX_4 = 59, + DB8500_DMA_MEMCPY_TX_5 = 60, + DB8500_DMA_DEV61_CAC0_TX = 61, + DB8500_DMA_DEV62_CAC0_TX_HAC0_TX = 62, + DB8500_DMA_DEV63_HAC0_TX = 63, +}; + +#endif diff --git a/arch/arm/mach-ux500/include/mach/ste_audio.h b/arch/arm/mach-ux500/include/mach/ste_audio.h new file mode 100644 index 00000000000..c314b463701 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ste_audio.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2009 ST-Ericsson SA + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _UX500_STE_AUDIO_H_ +#define _UX500_STE_AUDIO_H_ + + +struct ab8500_audio_platform_data { + int (*ste_gpio_altf_init) (void); + int (*ste_gpio_altf_exit) (void); +}; + +#endif /* _UX500_STE_AUDIO_H_ */ -- cgit v1.2.3 From 5bdb7c2328f0f3fb424683d6ab4ebf0755a8d8e3 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 25 Mar 2011 12:53:44 +0100 Subject: hwmon db8500: update for new pmu interface --- drivers/hwmon/db8500.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/db8500.c b/drivers/hwmon/db8500.c index 09630f2f707..e28f9938eb4 100755 --- a/drivers/hwmon/db8500.c +++ b/drivers/hwmon/db8500.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include -- cgit v1.2.3 From 43fde9bed4323d5826b9e5ac7416800dcb5ae3ed Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 25 Mar 2011 12:54:23 +0100 Subject: mfd ab5500: Fix for 2.6.38 --- drivers/mfd/ab5500-core.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index a4fa61d3d9b..37bd215538f 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1907,28 +1907,30 @@ static void ab5500_mask_work(struct work_struct *work) } } -static void ab5500_mask(unsigned int irq) +static void ab5500_mask(struct irq_data *data) { unsigned long flags; struct ab5500 *ab; + int irq; + + ab = irq_data_get_irq_chip_data(data); + irq = data->irq - ab->irq.base; - ab = get_irq_chip_data(irq); - irq -= ab->irq_base; - - spin_lock_irqsave(&ab->event_lock, flags); + spin_lock_irqsave(&ab->event_lock, flags); ab->event_mask[irq / 8] |= BIT(irq % 8); spin_unlock_irqrestore(&ab->event_lock, flags); schedule_work(&ab->mask_work); } -static void ab5500_unmask(unsigned int irq) +static void ab5500_unmask(struct irq_data *data) { unsigned long flags; struct ab5500 *ab; - - ab = get_irq_chip_data(irq); - irq -= ab->irq_base; + int irq; + + ab = irq_data_get_irq_chip_data(data); + irq = data->irq - ab->irq.base; spin_lock_irqsave(&ab->event_lock, flags); ab->event_mask[irq / 8] &= ~BIT(irq % 8); -- cgit v1.2.3 From dffa1ea63300a5d83e2be54407c9db31c90486d0 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 9 May 2005 14:10:26 -0700 Subject: ARM: Make low-level printk work Makes low-level printk work. Signed-off-by: Tony Lindgren Signed-off-by: Mian Yousaf Kaukab --- kernel/printk.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/printk.c b/kernel/printk.c index 90ddb222c12..d6aa909848d 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -55,6 +55,10 @@ void asmlinkage __attribute__((weak)) early_printk(const char *fmt, ...) #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) +#ifdef CONFIG_DEBUG_LL +extern void printascii(char *); +#endif + /* printk's without a loglevel use this.. */ #define DEFAULT_MESSAGE_LOGLEVEL CONFIG_DEFAULT_MESSAGE_LOGLEVEL -- cgit v1.2.3 From a932e978848f8213037ece68cf0fcfae70f956e0 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Thu, 11 Mar 2010 08:40:51 +0530 Subject: arm: add printascii in printk option Signed-off-by: Rabin Vincent Change-Id: Ie73cf9782ccbf3a974383fa5d1474613c660c6b9 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/3172 Tested-by: Mian Yousaf KAUKAB Reviewed-by: Srinidhi KASAGAR Signed-off-by: Mian Yousaf Kaukab --- arch/arm/Kconfig.debug | 9 +++++++++ kernel/printk.c | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 81cbe40c159..5816849fa15 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -80,6 +80,15 @@ config EARLY_PRINTK kernel low-level debugging functions. Add earlyprintk to your kernel parameters to enable this console. +config PRINTK_LL + bool "Use printascii in printk" + depends on DEBUG_LL + help + Say Y here if you want to have printk send its output via the + kernel low-level debugging functions. This is useful if you + are debugging code that executes before the earlyprintk console + is initialized. + config DEBUG_ICEDCC bool "Kernel low-level debugging via EmbeddedICE DCC channel" depends on DEBUG_LL diff --git a/kernel/printk.c b/kernel/printk.c index d6aa909848d..2918e7287ab 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -55,7 +55,7 @@ void asmlinkage __attribute__((weak)) early_printk(const char *fmt, ...) #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) -#ifdef CONFIG_DEBUG_LL +#ifdef CONFIG_PRINTK_LL extern void printascii(char *); #endif -- cgit v1.2.3 From 31e55fe0e0573d6acc8eb752e98a765da49619df Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Fri, 3 Dec 2010 13:19:13 +0100 Subject: irq: Allow threaded and nested irqs to be shared This patch will make the threaded nested handler run all the registered shared action handlers and not just the first registered. Signed-off-by: Mattias Wallin Change-Id: Ib484a2fa50186dbc05aedbcb9936518302ab801b Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/10494 Reviewed-by: Rabin VINCENT Reviewed-by: Jonas ABERG --- kernel/irq/chip.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index d5a3009da71..e5495b907e4 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -244,7 +244,7 @@ void handle_nested_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); struct irqaction *action; - irqreturn_t action_ret; + irqreturn_t action_ret = IRQ_NONE; might_sleep(); @@ -259,7 +259,11 @@ void handle_nested_irq(unsigned int irq) irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock_irq(&desc->lock); - action_ret = action->thread_fn(action->irq, action->dev_id); + do { + action_ret |= action->thread_fn(action->irq, action->dev_id); + action = action->next; + } while (action); + if (!noirqdebug) note_interrupt(irq, desc, action_ret); -- cgit v1.2.3 From 5d88368e79615e1e0206ce9d8e8df81e01c870fe Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 14 Feb 2011 09:51:56 +0100 Subject: ftrace: Increase number of named processes The default setting of 128 is not close to enough to save all the names of processes/threads executed during 1s of idle in Android. ST-Ericsson Linux next: - ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Jonas Aaberg Change-Id: I6f9ef8d5f167f4b1f8de7b996130e65ee5fc7598 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/15157 Reviewed-by: Martin PERSSON Reviewed-by: Mattias WALLIN --- kernel/trace/trace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 8b4b6d756d5..2412d59025e 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -907,7 +907,7 @@ void tracing_reset_current_online_cpus(void) tracing_reset_online_cpus(&global_trace); } -#define SAVED_CMDLINES 128 +#define SAVED_CMDLINES 2048 #define NO_CMDLINE_MAP UINT_MAX static unsigned map_pid_to_cmdline[PID_MAX_DEFAULT+1]; static unsigned map_cmdline_to_pid[SAVED_CMDLINES]; -- cgit v1.2.3 From 8b46f63182582dafe825b9159cb1709f7f2d8201 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 13:18:24 +0200 Subject: ux500-ARM : make backup RAM as an executable area On the v1.0/ED boards, during the deep sleep resume, it is required to make the backup RAM area as "executable" in order manage MMU settings for a single core mode. As a result, the rom code requires such a condition for aligning SMP strategy for different per-core MMU configuration. This patch makes the backup RAM configuration area as executable by adding a new memory type. *This change will not be needed for U8500 v2.0 as the rom code would (hopefully as discussions are showing) be updated with a minor API change* This patch is a part of patches for the deep sleep feature for the U8500 and FIDO_IR_ER: 258539 This patch *must* be re-visisted during u8500 v2.0 cut or mainlining the deep sleep to community Signed-off-by: Sundar R Iyer Acked-By: Biju C Das Signed-off-by: Mian Yousaf Kaukab Change-Id: Id4d8215ca1e9aaf8f327ff3f55f3f9ca1e68aef5 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2190 Reviewed-by: Jonas ABERG --- arch/arm/include/asm/io.h | 6 ++++++ arch/arm/mm/mmu.c | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index d66605dea55..ffa4430e106 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -62,6 +62,12 @@ extern void __raw_readsl(const void __iomem *addr, void *data, int longlen); #define MT_DEVICE_NONSHARED 1 #define MT_DEVICE_CACHED 2 #define MT_DEVICE_WC 3 +/* + * NOTE : U8500 v1.0/ED cut specific hack. + * look at the commit message for more details + */ +#define MT_BACKUP_RAM 4 + /* * types 4 onwards can be found in asm/mach/map.h and are undefined * for ioremap diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 594d677b92c..df6ced286ea 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -273,6 +273,20 @@ static struct mem_type mem_types[] = { .prot_l1 = PMD_TYPE_TABLE, .domain = DOMAIN_KERNEL, }, + /* NOTE : this is only a temporary hack!!! + * The U8500 ED/V1.0 cuts require such a + * memory type for deep sleep resume. + * This is expected to be solved in cut v2.0 + * and we clean this up then. for more details + * look @ the commit message please + */ + [MT_BACKUP_RAM] = { + .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED | + L_PTE_SHARED, + .prot_l1 = PMD_TYPE_TABLE, + .prot_sect = PROT_SECT_DEVICE | PMD_SECT_S, + .domain = DOMAIN_IO, + }, }; const struct mem_type *get_mem_type(unsigned int type) -- cgit v1.2.3 From 15c06f07b5a46cdcca9523f029023faf08b14f9b Mon Sep 17 00:00:00 2001 From: Sundar R Iyer Date: Tue, 9 Feb 2010 15:52:26 +0530 Subject: ARM : handle preempt count in cpu_idle Signed-off-by: Sundar R Iyer Acked-by: Linus Walleij Signed-off-by: Mian Yousaf Kaukab --- arch/arm/kernel/process.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 5e1e5419722..99c0d6b2cd4 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c @@ -186,8 +186,17 @@ void cpu_idle(void) leds_event(led_idle_start); while (!need_resched()) { #ifdef CONFIG_HOTPLUG_CPU - if (cpu_is_offline(smp_processor_id())) + if (cpu_is_offline(smp_processor_id())) { + + /* NOTE : preempt_count() should be 0 for dying CPU + * as the CPU will use this very thread when + * it is alive + */ + if (preempt_count()) + preempt_enable_no_resched(); + cpu_die(); + } #endif local_irq_disable(); -- cgit v1.2.3 From 356a38d2ea35fce7c2987f905495354f6379391f Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 10:49:09 +0200 Subject: u8500: enable u8500 configurations Signed-off-by: Mian Yousaf Kaukab --- arch/arm/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index f38e275bea3..e24068d40d4 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -819,7 +819,9 @@ config ARCH_U8500 select GENERIC_CLOCKEVENTS select CLKDEV_LOOKUP select ARCH_REQUIRE_GPIOLIB + select HAVE_CLK select ARCH_HAS_CPUFREQ + select NOMADIK_GPIO help Support for ST-Ericsson's Ux500 architecture -- cgit v1.2.3 From 5936c0ed538ba8a6bd5515b34d5538e62387554a Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 11:08:15 +0200 Subject: Add clean and flush_dcache_all to ARM cache API This patch adds functions to flush and clean the entire data cache. Since the existing flush/clean range functions take more time to flush/clean the entire data cache, they cannot be used. Whenever the range of data to be flushed/cleaned from the data cache is more than some threshold value, it is better to do the entire data flush/clean, this will reduce the time taken and effectively increases the performance of the system. ST-Ericsson ID: IR275682 ER275397 Change-Id: I8d7e6004232301cc2c9922738fa728cdc833cfde Signed-off-by: Johan Mossberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/7161 Reviewed-by: Robert FEKETE Reviewed-by: Srinidhi KASAGAR Conflicts: arch/arm/mm/cache-v7.S --- arch/arm/include/asm/cacheflush.h | 15 +++++++ arch/arm/mm/cache-fa.S | 18 ++++++++ arch/arm/mm/cache-v3.S | 18 ++++++++ arch/arm/mm/cache-v4.S | 18 ++++++++ arch/arm/mm/cache-v4wb.S | 18 ++++++++ arch/arm/mm/cache-v4wt.S | 18 ++++++++ arch/arm/mm/cache-v6.S | 18 ++++++++ arch/arm/mm/cache-v7.S | 94 ++++++++++++++++++++++++++++++++++++--- arch/arm/mm/proc-macros.S | 2 + arch/arm/mm/proc-v7.S | 2 +- 10 files changed, 214 insertions(+), 7 deletions(-) diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index d5d8d5c7268..a4bf3199819 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -84,6 +84,14 @@ * - kaddr - page address * - size - region size * + * clean_dcache_all() + * + * Cleans the entire d-cache. + * + * flush_dcache_all() + * + * Flushes the entire d-cache. + * * DMA Cache Coherency * =================== * @@ -104,6 +112,9 @@ struct cpu_cache_fns { void (*coherent_user_range)(unsigned long, unsigned long); void (*flush_kern_dcache_area)(void *, size_t); + void (*clean_dcache_all)(void); + void (*flush_dcache_all)(void); + void (*dma_map_area)(const void *, size_t, int); void (*dma_unmap_area)(const void *, size_t, int); @@ -124,6 +135,8 @@ extern struct cpu_cache_fns cpu_cache; #define __cpuc_coherent_kern_range cpu_cache.coherent_kern_range #define __cpuc_coherent_user_range cpu_cache.coherent_user_range #define __cpuc_flush_dcache_area cpu_cache.flush_kern_dcache_area +#define __cpuc_clean_dcache_all cpu_cache.clean_dcache_all +#define __cpuc_flush_dcache_all cpu_cache.flush_dcache_all /* * These are private to the dma-mapping API. Do not use directly. @@ -144,6 +157,8 @@ extern void __cpuc_flush_user_range(unsigned long, unsigned long, unsigned int); extern void __cpuc_coherent_kern_range(unsigned long, unsigned long); extern void __cpuc_coherent_user_range(unsigned long, unsigned long); extern void __cpuc_flush_dcache_area(void *, size_t); +extern void __cpuc_clean_dcache_all(void); +extern void __cpuc_flush_dcache_all(void); /* * These are private to the dma-mapping API. Do not use directly. diff --git a/arch/arm/mm/cache-fa.S b/arch/arm/mm/cache-fa.S index 07201637109..226f8736152 100644 --- a/arch/arm/mm/cache-fa.S +++ b/arch/arm/mm/cache-fa.S @@ -240,6 +240,24 @@ ENTRY(fa_dma_unmap_area) mov pc, lr ENDPROC(fa_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(fa_clean_dcache_all) + mov pc, lr +ENDPROC(fa_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(fa_flush_dcache_all) + mov pc, lr +ENDPROC(fa_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v3.S b/arch/arm/mm/cache-v3.S index c2301f22610..ab5bf508a2a 100644 --- a/arch/arm/mm/cache-v3.S +++ b/arch/arm/mm/cache-v3.S @@ -127,6 +127,24 @@ ENTRY(v3_dma_map_area) ENDPROC(v3_dma_unmap_area) ENDPROC(v3_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v3_clean_dcache_all) + mov pc, lr +ENDPROC(v3_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v3_flush_dcache_all) + mov pc, lr +ENDPROC(v3_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v4.S b/arch/arm/mm/cache-v4.S index fd9bb7addc8..9d3a055127e 100644 --- a/arch/arm/mm/cache-v4.S +++ b/arch/arm/mm/cache-v4.S @@ -139,6 +139,24 @@ ENTRY(v4_dma_map_area) ENDPROC(v4_dma_unmap_area) ENDPROC(v4_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4_clean_dcache_all) + mov pc, lr +ENDPROC(v4_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4_flush_dcache_all) + mov pc, lr +ENDPROC(v4_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v4wb.S b/arch/arm/mm/cache-v4wb.S index 4f2c14151cc..54d3cda4a89 100644 --- a/arch/arm/mm/cache-v4wb.S +++ b/arch/arm/mm/cache-v4wb.S @@ -251,6 +251,24 @@ ENTRY(v4wb_dma_unmap_area) mov pc, lr ENDPROC(v4wb_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4wb_clean_dcache_all) + mov pc, lr +ENDPROC(v4wb_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4wb_flush_dcache_all) + mov pc, lr +ENDPROC(v4wb_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v4wt.S b/arch/arm/mm/cache-v4wt.S index 4d7b467631c..40f7dba11f5 100644 --- a/arch/arm/mm/cache-v4wt.S +++ b/arch/arm/mm/cache-v4wt.S @@ -195,6 +195,24 @@ ENTRY(v4wt_dma_map_area) ENDPROC(v4wt_dma_unmap_area) ENDPROC(v4wt_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4wt_clean_dcache_all) + mov pc, lr +ENDPROC(v4wt_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4wt_flush_dcache_all) + mov pc, lr +ENDPROC(v4wt_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S index 74c2e5a33a4..b88dd4ab038 100644 --- a/arch/arm/mm/cache-v6.S +++ b/arch/arm/mm/cache-v6.S @@ -328,6 +328,24 @@ ENTRY(v6_dma_unmap_area) mov pc, lr ENDPROC(v6_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v6_clean_dcache_all) + mov pc, lr +ENDPROC(v6_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v6_flush_dcache_all) + mov pc, lr +ENDPROC(v6_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index 3b24bfa3b82..28af05c0bd2 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -33,7 +33,7 @@ ENTRY(v7_flush_icache_all) ENDPROC(v7_flush_icache_all) /* - * v7_flush_dcache_all() + * __v7_flush_dcache_all() * * Flush the whole D-cache. * @@ -41,7 +41,7 @@ ENDPROC(v7_flush_icache_all) * * - mm - mm_struct describing address space */ -ENTRY(v7_flush_dcache_all) +ENTRY(__v7_flush_dcache_all) dmb @ ensure ordering with previous memory accesses mrc p15, 1, r0, c0, c0, 1 @ read clidr ands r3, r0, #0x7000000 @ extract loc from clidr @@ -88,8 +88,92 @@ finished: dsb isb mov pc, lr +ENDPROC(__v7_flush_dcache_all) + +/* + * __v7_clean_dcache_all() + * + * Clean the whole D-cache. + * + * Corrupted registers: r0-r7, r9-r11 (r6 only in Thumb mode) + */ +ENTRY(__v7_clean_dcache_all) + dmb @ ensure ordering with previous memory accesses + mrc p15, 1, r0, c0, c0, 1 @ read clidr + ands r3, r0, #0x7000000 @ extract loc from clidr + mov r3, r3, lsr #23 @ left align loc bit field + beq finished1 @ if loc is 0, then no need to clean + mov r10, #0 @ start clean at cache level 0 +loop21: + add r2, r10, r10, lsr #1 @ work out 3x current cache level + mov r1, r0, lsr r2 @ extract cache type bits from clidr + and r1, r1, #7 @ mask of the bits for current cache only + cmp r1, #2 @ see what cache we have at this level + blt skip1 @ skip if no cache, or just i-cache + mcr p15, 2, r10, c0, c0, 0 @ select current cache level in cssr + isb @ isb to sych the new cssr&csidr + mrc p15, 1, r1, c0, c0, 0 @ read the new csidr + and r2, r1, #7 @ extract the length of the cache lines + add r2, r2, #4 @ add 4 (line length offset) + ldr r4, =0x3ff + ands r4, r4, r1, lsr #3 @ find maximum number on the way size + clz r5, r4 @ find bit position of way size increment + ldr r7, =0x7fff + ands r7, r7, r1, lsr #13 @ extract max number of the index size +loop22: + mov r9, r4 @ create working copy of max way size +loop23: + ARM( orr r11, r10, r9, lsl r5 ) @ factor way and cache number into r11 + THUMB( lsl r6, r9, r5 ) + THUMB( orr r11, r10, r6 ) @ factor way and cache number into r11 + ARM( orr r11, r11, r7, lsl r2 ) @ factor index number into r11 + THUMB( lsl r6, r7, r2 ) + THUMB( orr r11, r11, r6 ) @ factor index number into r11 + mcr p15, 0, r11, c7, c10, 2 @ clean by set/way + subs r9, r9, #1 @ decrement the way + bge loop23 + subs r7, r7, #1 @ decrement the index + bge loop22 +skip1: + add r10, r10, #2 @ increment cache number + cmp r3, r10 + bgt loop21 +finished1: + mov r10, #0 @ swith back to cache level 0 + mcr p15, 2, r10, c0, c0, 0 @ select current cache level in cssr + dsb + isb + mov pc, lr +ENDPROC(__v7_clean_dcache_all) + +/* + * v7_flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v7_flush_dcache_all) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + bl __v7_flush_dcache_all + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr ENDPROC(v7_flush_dcache_all) +/* + * v7_clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v7_clean_dcache_all) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + bl __v7_clean_dcache_all + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr +ENDPROC(v7_clean_dcache_all) + /* * v7_flush_cache_all() * @@ -102,14 +186,12 @@ ENDPROC(v7_flush_dcache_all) * */ ENTRY(v7_flush_kern_cache_all) - ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + stmfd sp!, {lr} bl v7_flush_dcache_all mov r0, #0 ALT_SMP(mcr p15, 0, r0, c7, c1, 0) @ invalidate I-cache inner shareable ALT_UP(mcr p15, 0, r0, c7, c5, 0) @ I+BTB cache invalidate - ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + ldmfd sp!, {lr} mov pc, lr ENDPROC(v7_flush_kern_cache_all) diff --git a/arch/arm/mm/proc-macros.S b/arch/arm/mm/proc-macros.S index 4ae9b440707..51d09628b2f 100644 --- a/arch/arm/mm/proc-macros.S +++ b/arch/arm/mm/proc-macros.S @@ -298,6 +298,8 @@ ENTRY(\name\()_cache_fns) .long \name\()_coherent_kern_range .long \name\()_coherent_user_range .long \name\()_flush_kern_dcache_area + .long \name\()_clean_dcache_all + .long \name\()_flush_dcache_all .long \name\()_dma_map_area .long \name\()_dma_unmap_area .long \name\()_dma_flush_range diff --git a/arch/arm/mm/proc-v7.S b/arch/arm/mm/proc-v7.S index 54d1a63517c..daa98f6e428 100644 --- a/arch/arm/mm/proc-v7.S +++ b/arch/arm/mm/proc-v7.S @@ -303,7 +303,7 @@ __v7_ca15mp_setup: __v7_setup: adr r12, __v7_setup_stack @ the local stack stmia r12, {r0-r5, r7, r9, r11, lr} - bl v7_flush_dcache_all + bl __v7_flush_dcache_all ldmia r12, {r0-r5, r7, r9, r11, lr} mrc p15, 0, r0, c0, c0, 0 @ read main ID register -- cgit v1.2.3 From 7dd37464b68e918f22a7971c94137d3fcb6b30a6 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 11:24:09 +0200 Subject: ARM: Translate delay.S into (mostly) C We want to allow machines to override the __delay() implementation at runtime so they can use a timer based __delay() routine. It's easier to do this using C, so let's write udelay and friends in C. We lose the #if 0 code, which according to Russell is used "to make the delay loop more stable and predictable on older CPUs" (see http://article.gmane.org/gmane.linux.kernel/888867 for more info). We shouldn't be too worried though, since we'll soon add functionality allowing a machine to set the __delay() loop themselves, thus allowing machines to resurrect the commented out code should they need it. Nico expressed concern that fixed lpj cmdlines will break due to compiler optimizations. That doesn't seem to be the case since before and after this patch I get the same lpj value when running my CPU at 19.2 MHz. That should be sufficiently slow enough to cover any machine running Linux. Reviewed-by: Saravana Kannan Acked-by: Nicolas Pitre Signed-off-by: Stephen Boyd Change-Id: I84311dc3955250960ffa8dc56d45a4833b3ad0f2 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13562 Tested-by: Mattias WALLIN Reviewed-by: Jonas ABERG Conflicts: arch/arm/lib/delay.S --- arch/arm/include/asm/delay.h | 2 +- arch/arm/kernel/armksyms.c | 4 --- arch/arm/lib/delay.S | 69 -------------------------------------------- arch/arm/lib/delay.c | 56 +++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 74 deletions(-) delete mode 100644 arch/arm/lib/delay.S create mode 100644 arch/arm/lib/delay.c diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index b2deda18154..ccc5ed573fa 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -8,7 +8,7 @@ #include /* HZ */ -extern void __delay(int loops); +extern void __delay(unsigned long loops); /* * This function intentionally does not exist; if you see references to diff --git a/arch/arm/kernel/armksyms.c b/arch/arm/kernel/armksyms.c index acca35aebe2..263eaaf3f26 100644 --- a/arch/arm/kernel/armksyms.c +++ b/arch/arm/kernel/armksyms.c @@ -52,10 +52,6 @@ extern void fpundefinstr(void); EXPORT_SYMBOL(__backtrace); - /* platform dependent support */ -EXPORT_SYMBOL(__udelay); -EXPORT_SYMBOL(__const_udelay); - /* networking */ EXPORT_SYMBOL(csum_partial); EXPORT_SYMBOL(csum_partial_copy_from_user); diff --git a/arch/arm/lib/delay.S b/arch/arm/lib/delay.S deleted file mode 100644 index 3c9a05c8d20..00000000000 --- a/arch/arm/lib/delay.S +++ /dev/null @@ -1,69 +0,0 @@ -/* - * linux/arch/arm/lib/delay.S - * - * Copyright (C) 1995, 1996 Russell King - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ -#include -#include -#include - .text - -.LC0: .word loops_per_jiffy -.LC1: .word (2199023*HZ)>>11 - -/* - * r0 <= 2000 - * lpj <= 0x01ffffff (max. 3355 bogomips) - * HZ <= 1000 - */ - -ENTRY(__udelay) - ldr r2, .LC1 - mul r0, r2, r0 -ENTRY(__const_udelay) @ 0 <= r0 <= 0x7fffff06 - mov r1, #-1 - ldr r2, .LC0 - ldr r2, [r2] @ max = 0x01ffffff - add r0, r0, r1, lsr #32-14 - mov r0, r0, lsr #14 @ max = 0x0001ffff - add r2, r2, r1, lsr #32-10 - mov r2, r2, lsr #10 @ max = 0x00007fff - mul r0, r2, r0 @ max = 2^32-1 - add r0, r0, r1, lsr #32-6 - movs r0, r0, lsr #6 - moveq pc, lr - -/* - * loops = r0 * HZ * loops_per_jiffy / 1000000 - * - * Oh, if only we had a cycle counter... - */ - -@ Delay routine -ENTRY(__delay) - subs r0, r0, #1 -#if 0 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 -#endif - bhi __delay - mov pc, lr -ENDPROC(__udelay) -ENDPROC(__const_udelay) -ENDPROC(__delay) diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c new file mode 100644 index 00000000000..f92aca011ba --- /dev/null +++ b/arch/arm/lib/delay.c @@ -0,0 +1,56 @@ +/* + * Originally from linux/arch/arm/lib/delay.S + * + * Copyright (C) 1995, 1996 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include + +/* + * loops = usecs * HZ * loops_per_jiffy / 1000000 + * + * Oh, if only we had a cycle counter... + */ +void __delay(unsigned long loops) +{ + asm volatile( + "1: subs %0, %0, #1 \n" + " bhi 1b \n" + : /* No output */ + : "r" (loops) + ); +} +EXPORT_SYMBOL(__delay); + +/* + * 0 <= xloops <= 0x7fffff06 + * loops_per_jiffy <= 0x01ffffff (max. 3355 bogomips) + */ +void __const_udelay(unsigned long xloops) +{ + unsigned long lpj; + unsigned long loops; + + xloops >>= 14; /* max = 0x01ffffff */ + lpj = loops_per_jiffy >> 10; /* max = 0x0001ffff */ + loops = lpj * xloops; /* max = 0x00007fff */ + loops >>= 6; /* max = 2^32-1 */ + + if (likely(loops)) + __delay(loops); +} +EXPORT_SYMBOL(__const_udelay); + +/* + * usecs <= 2000 + * HZ <= 1000 + */ +void __udelay(unsigned long usecs) +{ + __const_udelay(usecs * ((2199023*HZ)>>11)); +} +EXPORT_SYMBOL(__udelay); -- cgit v1.2.3 From 9007e7d007674c1b736c3e22fac70e1da7985125 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Mon, 20 Dec 2010 14:20:31 -0800 Subject: ARM: Allow machines to override __delay() Some machines want to implement their own __delay() routine based on fixed rate timers. Expose functionality to set the __delay() routine at runtime. This should allow two machines with different __delay() routines to happily co-exist within the same kernel with minimal overhead. Russell expressed concern that using a timer based __delay() would cause problems when an iomapped device isn't mapped in prior to a delay call being made (see http://article.gmane.org/gmane.linux.ports.arm.kernel/78543 for more info). We can sidestep that issue with this approach since the __delay() routine _should_ only be pointed to a timer based delay once the timer has been properly mapped. Up until that point __delay() and udelay() will use delay_loop() which is always safe to call. This patch is inspired by x86's delay.c Reviewed-by: Saravana Kannan Signed-off-by: Stephen Boyd Change-Id: I269f101b40ba50c2b635dc92d50f6e82bb934b32 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13563 Tested-by: Mattias WALLIN Reviewed-by: Jonas ABERG --- arch/arm/include/asm/delay.h | 7 +++++++ arch/arm/lib/delay.c | 14 +++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index ccc5ed573fa..82ef82a417b 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -40,5 +40,12 @@ extern void __const_udelay(unsigned long); __const_udelay((n) * ((2199023U*HZ)>>11))) : \ __udelay(n)) +extern void (*delay_fn)(unsigned long); + +static inline void set_delay_fn(void (*fn)(unsigned long)) +{ + delay_fn = fn; +} + #endif /* defined(_ARM_DELAY_H) */ diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c index f92aca011ba..7468bc64b35 100644 --- a/arch/arm/lib/delay.c +++ b/arch/arm/lib/delay.c @@ -11,11 +11,9 @@ #include /* - * loops = usecs * HZ * loops_per_jiffy / 1000000 - * * Oh, if only we had a cycle counter... */ -void __delay(unsigned long loops) +static void delay_loop(unsigned long loops) { asm volatile( "1: subs %0, %0, #1 \n" @@ -24,6 +22,16 @@ void __delay(unsigned long loops) : "r" (loops) ); } + +void (*delay_fn)(unsigned long) = delay_loop; + +/* + * loops = usecs * HZ * loops_per_jiffy / 1000000 + */ +void __delay(unsigned long loops) +{ + delay_fn(loops); +} EXPORT_SYMBOL(__delay); /* -- cgit v1.2.3 From d0e5662f2f29e4f56663e68760cc2e1925ea0d31 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Mon, 20 Dec 2010 14:20:32 -0800 Subject: ARM: Implement a timer based __delay() loop udelay() can be incorrect on SMP machines that scale their CPU frequencies independently of one another (as pointed out here http://article.gmane.org/gmane.linux.kernel/977567). The delay loop can either be too fast or too slow depending on which CPU the loops_per_jiffy counter is calibrated on and which CPU the delay loop is running on. udelay() can also be incorrect if the CPU frequency switches during the __delay() loop, causing the loop to either terminate too early, or too late. Forcing udelay() to run on one CPU is unreasonable and taking the penalty of a rather large loops_per_jiffy in udelay() when the CPU is actually running slower is bad for performance. Solve the problem by adding a timer based__delay() loop unaffected by CPU frequency scaling. Machines should set this loop as their __delay() implementation by calling set_timer_fn() during their timer initialization. The kernel is already prepared for a timer based approach (evident by the read_current_timer() function). If an arch implements read_current_timer(), calibrate_delay() will use calibrate_delay_direct() to calculate loops_per_jiffy (in which case loops_per_jiffy should really be renamed to timer_ticks_per_jiffy). Since the loops_per_jiffy will be based on timer ticks, __delay() should be implemented as a loop around read_current_timer(). Doing this makes the expensive loops_per_jiffy calculation go away (saving ~150ms on boot time on my machine) and fixes udelay() by making it safe in the face of independently scaling CPUs. The only prerequisite is that read_current_timer() is monotonically increasing across calls (and doesn't overflow within ~2000us). There is a downside to this approach though. BogoMIPS is no longer "accurate" in that it reflects the BogoMIPS of the timer and not the CPU. On most SoC's the timer isn't running anywhere near as fast as the CPU so BogoMIPS will be ridiculously low (my timer runs at 4.8 MHz and thus my BogoMIPS is 9.6 compared to my CPU's 800). This shouldn't be too much of a concern though since BogoMIPS are bogus anyway (hence the name). This loop is pretty much a copy of AVR's version. Reported-and-reviewed-by: Saravana Kannan Signed-off-by: Stephen Boyd Change-Id: I9a4bee236ff1f26e1f2ae7e15e92b9ba14b46952 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13564 Tested-by: Mattias WALLIN Reviewed-by: Jonas ABERG --- arch/arm/include/asm/delay.h | 2 ++ arch/arm/lib/delay.c | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index 82ef82a417b..91063a3976f 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -47,5 +47,7 @@ static inline void set_delay_fn(void (*fn)(unsigned long)) delay_fn = fn; } +extern void read_current_timer_delay_loop(unsigned long loops); + #endif /* defined(_ARM_DELAY_H) */ diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c index 7468bc64b35..f24c956e4fd 100644 --- a/arch/arm/lib/delay.c +++ b/arch/arm/lib/delay.c @@ -9,6 +9,7 @@ */ #include #include +#include /* * Oh, if only we had a cycle counter... @@ -23,6 +24,22 @@ static void delay_loop(unsigned long loops) ); } +#ifdef ARCH_HAS_READ_CURRENT_TIMER +/* + * Assumes read_current_timer() is monotonically increasing + * across calls and wraps at most once within MAX_UDELAY_MS. + */ +void read_current_timer_delay_loop(unsigned long loops) +{ + unsigned long bclock, now; + + read_current_timer(&bclock); + do { + read_current_timer(&now); + } while ((now - bclock) < loops); +} +#endif + void (*delay_fn)(unsigned long) = delay_loop; /* -- cgit v1.2.3 From 95a638ef517396cb6f4e8ab28e2a9f34404bddf2 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 28 Feb 2011 16:43:44 +0100 Subject: ARM: kernel: Remove warnings This patch removes a warning in order to reach a point where we have zero warnings and Hudson can start to give -1 results on any added ones. This patch shall never reach mainline. ST-Ericsson Linux next: Never ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Jonas Aaberg Change-Id: Ic80fb9b7280a52c1e03948de91b602d148b1a760 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17154 Reviewed-by: Mattias WALLIN --- arch/arm/kernel/return_address.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/arm/kernel/return_address.c b/arch/arm/kernel/return_address.c index 0b13a72f855..b7fe66d270a 100644 --- a/arch/arm/kernel/return_address.c +++ b/arch/arm/kernel/return_address.c @@ -58,10 +58,6 @@ void *return_address(unsigned int level) #else /* if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND) */ -#if defined(CONFIG_ARM_UNWIND) -#warning "TODO: return_address should use unwind tables" -#endif - void *return_address(unsigned int level) { return NULL; -- cgit v1.2.3 From 63370397029ce64d1e03c42695f29ca4071e4586 Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Fri, 11 Mar 2011 17:18:07 +0000 Subject: mmc: mmci: Add ARM variant with extended FIFO New IO FPGA implementation for Versatile Express boards contain MMCI (PL180) cell with FIFO extended to 128 words (512 bytes). Matt Waddel reports that this patch improves MMC performance on his vexpress system, and also fixes "mmcblk0: error -5 transferring data" errors. Signed-off-by: Pawel Moll Tested-by: Matt Waddel Signed-off-by: Chris Ball (cherry picked from commit 768fbc1876b3239f4c463c00ea1e78725554cf21) --- drivers/mmc/host/mmci.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index ba6f324c093..064d3cab223 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -82,6 +82,12 @@ static struct variant_data variant_arm_extended_fifo = { .datalength_bits = 16, }; +static struct variant_data variant_arm_extended_fifo = { + .fifosize = 128 * 4, + .fifohalfsize = 64 * 4, + .datalength_bits = 16, +}; + static struct variant_data variant_u300 = { .fifosize = 16 * 4, .fifohalfsize = 8 * 4, -- cgit v1.2.3 From f5cdfb330263ae32bceb2c7e4d87bfe36e5f84b6 Mon Sep 17 00:00:00 2001 From: David Henningsson Date: Fri, 29 Apr 2011 14:10:55 +0200 Subject: ALSA: HDA: Fix automute for Gateway NV79 commit 94024cd1aefa0f8bcc9dfe46c05bd7ce3f471a1c upstream. The PCI SSID is 1025:031c and the codec SSID is 1025:031d, so the driver mistakes this for a SKU value, but looking at the numbers, this is obviously wrong. BugLink: http://bugs.launchpad.net/bugs/761861 Signed-off-by: David Henningsson Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- sound/pci/hda/patch_realtek.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index b48fb43b544..d2c82caa6cc 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -19491,6 +19491,10 @@ static const struct alc_fixup alc662_fixups[] = { .type = ALC_FIXUP_SKU, .v.sku = ALC_FIXUP_SKU_IGNORE, }, + [ALC662_FIXUP_SKU_IGNORE] = { + .type = ALC_FIXUP_SKU, + .v.sku = ALC_FIXUP_SKU_IGNORE, + }, }; static const struct snd_pci_quirk alc662_fixup_tbl[] = { -- cgit v1.2.3 From d884e622faad4b3da36bdf2ec6f1fb005273fbe1 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 15 Apr 2011 20:08:19 +0200 Subject: mmc: fix a race between card-detect rescan and clock-gate work instances commit 26fc8775b51484d8c0a671198639c6d5ae60533e upstream. Currently there is a race in the MMC core between a card-detect rescan work and the clock-gating work, scheduled from a command completion. Fix it by removing the dedicated clock-gating mutex and using the MMC standard locking mechanism instead. Signed-off-by: Guennadi Liakhovetski Cc: Simon Horman Cc: Magnus Damm Acked-by: Linus Walleij Signed-off-by: Chris Ball Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/core/host.c | 9 ++++----- include/linux/mmc/host.h | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b29d3e8fd3a..6714ea4e46e 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -94,7 +94,7 @@ static void mmc_host_clk_gate_delayed(struct mmc_host *host) spin_unlock_irqrestore(&host->clk_lock, flags); return; } - mutex_lock(&host->clk_gate_mutex); + mmc_claim_host(host); spin_lock_irqsave(&host->clk_lock, flags); if (!host->clk_requests) { spin_unlock_irqrestore(&host->clk_lock, flags); @@ -104,7 +104,7 @@ static void mmc_host_clk_gate_delayed(struct mmc_host *host) pr_debug("%s: gated MCI clock\n", mmc_hostname(host)); } spin_unlock_irqrestore(&host->clk_lock, flags); - mutex_unlock(&host->clk_gate_mutex); + mmc_release_host(host); } /* @@ -130,7 +130,7 @@ void mmc_host_clk_ungate(struct mmc_host *host) { unsigned long flags; - mutex_lock(&host->clk_gate_mutex); + mmc_claim_host(host); spin_lock_irqsave(&host->clk_lock, flags); if (host->clk_gated) { spin_unlock_irqrestore(&host->clk_lock, flags); @@ -140,7 +140,7 @@ void mmc_host_clk_ungate(struct mmc_host *host) } host->clk_requests++; spin_unlock_irqrestore(&host->clk_lock, flags); - mutex_unlock(&host->clk_gate_mutex); + mmc_release_host(host); } /** @@ -215,7 +215,6 @@ static inline void mmc_host_clk_init(struct mmc_host *host) host->clk_gated = false; INIT_WORK(&host->clk_gate_work, mmc_host_clk_gate_work); spin_lock_init(&host->clk_lock); - mutex_init(&host->clk_gate_mutex); } /** diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 5ce91778d6d..303f6f1bba0 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -222,7 +222,6 @@ struct mmc_host { struct work_struct clk_gate_work; /* delayed clock gate */ unsigned int clk_old; /* old clock value cache */ spinlock_t clk_lock; /* lock for clk fields */ - struct mutex clk_gate_mutex; /* mutex for clock gating */ #endif /* host specific block data */ -- cgit v1.2.3 From 11b43813ee4dc761a0f76dccac589582f0a1f552 Mon Sep 17 00:00:00 2001 From: Boris Ostrovsky Date: Fri, 29 Apr 2011 17:47:43 -0400 Subject: x86, AMD: Fix APIC timer erratum 400 affecting K8 Rev.A-E processors commit e20a2d205c05cef6b5783df339a7d54adeb50962 upstream. Older AMD K8 processors (Revisions A-E) are affected by erratum 400 (APIC timer interrupts don't occur in C states greater than C1). This, for example, means that X86_FEATURE_ARAT flag should not be set for these parts. This addresses regression introduced by commit b87cf80af3ba4b4c008b4face3c68d604e1715c6 ("x86, AMD: Set ARAT feature on AMD processors") where the system may become unresponsive until external interrupt (such as keyboard input) occurs. This results, for example, in time not being reported correctly, lack of progress on the system and other lockups. Reported-by: Joerg-Volker Peetz Tested-by: Joerg-Volker Peetz Acked-by: Borislav Petkov Signed-off-by: Boris Ostrovsky Link: http://lkml.kernel.org/r/1304113663-6586-1-git-send-email-ostr@amd64.org Signed-off-by: Ingo Molnar Signed-off-by: Greg Kroah-Hartman --- arch/x86/kernel/cpu/amd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index b13ed393dfc..ea00b48274e 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -704,7 +704,7 @@ cpu_dev_register(amd_cpu_dev); */ const int amd_erratum_400[] = - AMD_OSVW_ERRATUM(1, AMD_MODEL_RANGE(0xf, 0x41, 0x2, 0xff, 0xf), + AMD_OSVW_ERRATUM(1, AMD_MODEL_RANGE(0x0f, 0x4, 0x2, 0xff, 0xf), AMD_MODEL_RANGE(0x10, 0x2, 0x1, 0xff, 0xf)); EXPORT_SYMBOL_GPL(amd_erratum_400); -- cgit v1.2.3 From e6d1ef2a13581310c851c32dc8d7e16728d281d9 Mon Sep 17 00:00:00 2001 From: Par-Gunnar Hjalmdahl Date: Tue, 29 Mar 2011 13:49:18 +0200 Subject: staging: Add ST-Ericsson CG2900 driver This patch adds support for the ST-Ericsson CG2900 Connectivity Combo controller (Bluetooth, FM, GPS). Signed-off-by: Par-Gunnar Hjalmdahl Acked-by: Linus Walleij Signed-off-by: Philippe Langlais Signed-off-by: Robert Marklund --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 2 +- drivers/staging/cg2900/Kconfig | 70 + drivers/staging/cg2900/Makefile | 12 + drivers/staging/cg2900/TODO | 23 + drivers/staging/cg2900/bluetooth/Makefile | 9 + drivers/staging/cg2900/bluetooth/btcg2900.c | 1208 ++++++++ drivers/staging/cg2900/bluetooth/cg2900_uart.c | 2074 +++++++++++++ drivers/staging/cg2900/bluetooth/hci_ldisc.c | 657 ++++ drivers/staging/cg2900/bluetooth/hci_uart.h | 105 + drivers/staging/cg2900/board-mop500-cg2900.c | 205 ++ drivers/staging/cg2900/devices-cg2900.c | 353 +++ drivers/staging/cg2900/devices-cg2900.h | 31 + drivers/staging/cg2900/include/cg2900.h | 278 ++ drivers/staging/cg2900/include/cg2900_audio.h | 473 +++ drivers/staging/cg2900/include/cg2900_hci.h | 19 + drivers/staging/cg2900/mfd/Makefile | 18 + drivers/staging/cg2900/mfd/cg2900_audio.c | 3462 ++++++++++++++++++++++ drivers/staging/cg2900/mfd/cg2900_char_devices.c | 706 +++++ drivers/staging/cg2900/mfd/cg2900_chip.c | 3393 +++++++++++++++++++++ drivers/staging/cg2900/mfd/cg2900_chip.h | 613 ++++ drivers/staging/cg2900/mfd/cg2900_core.c | 713 +++++ drivers/staging/cg2900/mfd/cg2900_core.h | 51 + drivers/staging/cg2900/mfd/cg2900_lib.c | 281 ++ drivers/staging/cg2900/mfd/cg2900_lib.h | 61 + drivers/staging/cg2900/mfd/cg2900_test.c | 402 +++ drivers/staging/cg2900/mfd/stlc2690_chip.c | 1643 ++++++++++ drivers/staging/cg2900/mfd/stlc2690_chip.h | 47 + 28 files changed, 16910 insertions(+), 1 deletion(-) create mode 100644 drivers/staging/cg2900/Kconfig create mode 100644 drivers/staging/cg2900/Makefile create mode 100644 drivers/staging/cg2900/TODO create mode 100644 drivers/staging/cg2900/bluetooth/Makefile create mode 100644 drivers/staging/cg2900/bluetooth/btcg2900.c create mode 100644 drivers/staging/cg2900/bluetooth/cg2900_uart.c create mode 100644 drivers/staging/cg2900/bluetooth/hci_ldisc.c create mode 100644 drivers/staging/cg2900/bluetooth/hci_uart.h create mode 100644 drivers/staging/cg2900/board-mop500-cg2900.c create mode 100644 drivers/staging/cg2900/devices-cg2900.c create mode 100644 drivers/staging/cg2900/devices-cg2900.h create mode 100644 drivers/staging/cg2900/include/cg2900.h create mode 100644 drivers/staging/cg2900/include/cg2900_audio.h create mode 100644 drivers/staging/cg2900/include/cg2900_hci.h create mode 100644 drivers/staging/cg2900/mfd/Makefile create mode 100644 drivers/staging/cg2900/mfd/cg2900_audio.c create mode 100644 drivers/staging/cg2900/mfd/cg2900_char_devices.c create mode 100644 drivers/staging/cg2900/mfd/cg2900_chip.c create mode 100644 drivers/staging/cg2900/mfd/cg2900_chip.h create mode 100644 drivers/staging/cg2900/mfd/cg2900_core.c create mode 100644 drivers/staging/cg2900/mfd/cg2900_core.h create mode 100644 drivers/staging/cg2900/mfd/cg2900_lib.c create mode 100644 drivers/staging/cg2900/mfd/cg2900_lib.h create mode 100644 drivers/staging/cg2900/mfd/cg2900_test.c create mode 100644 drivers/staging/cg2900/mfd/stlc2690_chip.c create mode 100644 drivers/staging/cg2900/mfd/stlc2690_chip.h diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 196284dc2f3..6c4456f8b13 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -160,4 +160,6 @@ source "drivers/staging/mei/Kconfig" source "drivers/staging/nvec/Kconfig" +source "drivers/staging/cg2900/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index fa41b9c2378..44b49294101 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -16,8 +16,8 @@ obj-$(CONFIG_USBIP_CORE) += usbip/ obj-$(CONFIG_W35UND) += winbond/ obj-$(CONFIG_PRISM2_USB) += wlan-ng/ obj-$(CONFIG_ECHO) += echo/ -obj-$(CONFIG_BRCMSMAC) += brcm80211/ obj-$(CONFIG_BRCMFMAC) += brcm80211/ +obj-$(CONFIG_CG2900) += cg2900/ obj-$(CONFIG_COMEDI) += comedi/ obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/ obj-$(CONFIG_ASUS_OLED) += asus_oled/ diff --git a/drivers/staging/cg2900/Kconfig b/drivers/staging/cg2900/Kconfig new file mode 100644 index 00000000000..0d1ad7ca8d8 --- /dev/null +++ b/drivers/staging/cg2900/Kconfig @@ -0,0 +1,70 @@ +# +# CG2900 +# + +config CG2900 + tristate "Support ST-Ericsson CG2900 main structure" + depends on NET && MFD_SUPPORT + help + ST-Ericsson CG2900 Connectivity Combo controller main + structure. + Supports multiple functionalities muxed over a Bluetooth HCI H:4 + interface. + CG2900 support Bluetooth, FM radio, and GPS. + +config CG2900_CHIP + tristate "Support CG2900 Connectivity controller" + depends on CG2900 + help + ST-Ericsson CG2900 Connectivity Controller chip handler. + Contains chip handler performing driver initialization + such as patchdownload and also instantiates the supported + MFD devices. + +config STLC2690_CHIP + tristate "Support STLC2690 Connectivity controller" + depends on CG2900 + help + ST-Ericsson STLC2690 Connectivity Controller chip handler. + Contains chip handler performing driver initialization + such as patchdownload and also instantiates the supported + MFD devices. + +config CG2900_UART + tristate "Support CG2900 UART transport" + depends on CG2900 && BT + help + UART driver for ST-Ericsson CG2900 Connectivity Controller. + Contains functions for setting baud rate and to transport + data to and from the CG2900 controller over UART. + Also handles low power handling for the CG2900 when using UART as + transport. + +config CG2900_AUDIO + tristate "Support CG2900 audio interface" + depends on CG2900 + help + ST-Ericsson CG2900 Connectivity audio interface driver. + Gives a module the ability to setup audio paths + within the CG2900 controller. + Supports both a normal function API and using character device + from user space. + +config CG2900_TEST + tristate "Support CG2900 Test Char Device" + depends on CG2900 + help + ST-Ericsson CG2900 Test Character Device driver. + Creates a character device which can be used by + a test framework in user space to emulate a connected chip. + Note that this is used to test the chip handler driver, + not to test the connected chip. + +config BT_CG2900 + tristate "ST-Ericsson CG2900 Bluetooth driver" + depends on CG2900 && BT + help + Select if ST-Ericsson CG2900 Connectivity controller shall be used as + Bluetooth controller for BlueZ. + This driver registers to the Bluetooth stack and when opened, + enables the CG2900 controller in a proper way. diff --git a/drivers/staging/cg2900/Makefile b/drivers/staging/cg2900/Makefile new file mode 100644 index 00000000000..c6414c21c5d --- /dev/null +++ b/drivers/staging/cg2900/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +ccflags-y := \ + -Idrivers/staging/cg2900/include \ + -Iarch/arm/mach-ux500 + +obj-$(CONFIG_CG2900) += board-mop500-cg2900.o devices-cg2900.o + +obj-y += mfd/ +obj-y += bluetooth/ diff --git a/drivers/staging/cg2900/TODO b/drivers/staging/cg2900/TODO new file mode 100644 index 00000000000..e122eba6d53 --- /dev/null +++ b/drivers/staging/cg2900/TODO @@ -0,0 +1,23 @@ +TODO +---- + + - Decide upon main driver architecture. + + - Decide if the CG2900 driver should be a separate driver as today or if it + should be a sub-driver using the TI-ST (Shared Transport) driver that is also + written for a combo connectivity controller. + + - Decide if cg2900_uart should register on top of hci_ldisc.c (as now) or if it + should instead register on top of hci_h4.c thereby reusing hci_h4 + implementation. + + - Update the hci_ldisc.c so that it will allow drivers to be registered without + registering them directly to the Bluetooth stack. Also extend the hci_ldisc.c + with more functions to abstract the tty API in a conformative way (currently + sometimes the tty API used, sometimes the hci_ldisc interface). + + - Decide if the CG2900 driver should use imported structs and defines to create + Bluetooth packets as today or if the Bluetooth stack in the Kernel should be + extended so it is possible to use generic functions to send and receive + commands and events both from the Bluetooth stack itself and from external + drivers such as the CG2900 driver. diff --git a/drivers/staging/cg2900/bluetooth/Makefile b/drivers/staging/cg2900/bluetooth/Makefile new file mode 100644 index 00000000000..936a4a257da --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +ccflags-y := \ + -Idrivers/staging/cg2900/include + +obj-$(CONFIG_BT_CG2900) += btcg2900.o +obj-$(CONFIG_CG2900_UART) += cg2900_uart.o hci_ldisc.o diff --git a/drivers/staging/cg2900/bluetooth/btcg2900.c b/drivers/staging/cg2900/bluetooth/btcg2900.c new file mode 100644 index 00000000000..9c1c4413087 --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/btcg2900.c @@ -0,0 +1,1208 @@ +/* + * Bluetooth driver for ST-Ericsson CG2900 connectivity controller. + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) + * Henrik Possung (henrik.possung@stericsson.com) + * Josef Kindberg (josef.kindberg@stericsson.com) + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) + * Kjell Andersson (kjell.k.andersson@stericsson.com) + * + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" + +#define BT_HEADER_LENGTH 0x03 + +#define STLC2690_HCI_REV 0x0600 +#define CG2900_PG1_HCI_REV 0x0101 +#define CG2900_PG2_HCI_REV 0x0200 +#define CG2900_PG1_SPECIAL_HCI_REV 0x0700 + +#define NAME "BTCG2900 " + +/* Wait for 5 seconds for a response to our requests */ +#define RESP_TIMEOUT 5000 + +/* Bluetooth error codes */ +#define HCI_ERR_NO_ERROR 0x00 +#define HCI_ERR_CMD_DISALLOWED 0x0C + +/** + * enum reset_state - RESET-states of the HCI driver. + * + * @RESET_IDLE: No reset in progress. + * @RESET_ACTIVATED: Reset in progress. + * @RESET_UNREGISTERED: hdev is unregistered. + */ + +enum reset_state { + RESET_IDLE, + RESET_ACTIVATED, + RESET_UNREGISTERED +}; + +/** + * enum enable_state - ENABLE-states of the HCI driver. + * + * @ENABLE_IDLE: The HCI driver is loaded but not opened. + * @ENABLE_WAITING_BT_ENABLED_CC: The HCI driver is waiting for a command + * complete event from the BT chip as a + * response to a BT Enable (true) command. + * @ENABLE_BT_ENABLED: The BT chip is enabled. + * @ENABLE_WAITING_BT_DISABLED_CC: The HCI driver is waiting for a command + * complete event from the BT chip as a + * response to a BT Enable (false) command. + * @ENABLE_BT_DISABLED: The BT chip is disabled. + * @ENABLE_BT_ERROR: The HCI driver is in a bad state, some + * thing has failed and is not expected to + * work properly. + */ +enum enable_state { + ENABLE_IDLE, + ENABLE_WAITING_BT_ENABLED_CC, + ENABLE_BT_ENABLED, + ENABLE_WAITING_BT_DISABLED_CC, + ENABLE_BT_DISABLED, + ENABLE_BT_ERROR +}; + +/* Defines which state the driver has when BT is active */ +#define BTCG2900_ACTIVE_STATE ENABLE_BT_ENABLED + +/** + * struct btcg2900_info - Specifies HCI driver private data. + * + * This type specifies CG2900 HCI driver private data. + * + * @list: list_head struct. + * @parent: Parent to this BT device. All BT channels will have + * common parent. + * @cmd: Device structure for BT command channel. + * @evt: Device structure for BT event channel. + * @acl: Device structure for BT ACL channel. + * @pdev: Device structure for platform device. + * @hdev: Device structure for HCI device. + * @reset_state: Device enum for HCI driver reset state. + * @enable_state: Device enum for HCI driver BT enable state. + */ +struct btcg2900_info { + struct list_head list; + struct device *parent; + struct device *cmd; + struct device *evt; + struct device *acl; + struct hci_dev *hdev; + enum reset_state reset_state; + enum enable_state enable_state; +}; + +/** + * struct enable_info - Specifies data for sending enable commands. + * + * @enable: True if command should enable the functionality. + * @name: Name of the command, only informative. + * @get_cmd: Function for retrieving command. + * @success: State to set upon success. + * @awaiting_cc: State to set while waiting for response. + * @failed: State to set upon failure. + */ +struct enable_info { + bool enable; + char *name; + struct sk_buff* (*get_cmd)(struct btcg2900_info *info, bool enable); + enum enable_state success; + enum enable_state awaiting_cc; + enum enable_state failed; +}; + +/** + * struct dev_info - Specifies private data used when receiving callbacks from CG2900 driver. + * + * @hci_data_type: Type of data according to BlueZ. + */ +struct dev_info { + u8 hci_data_type; +}; + +/* Defines for vs_bt_enable_cmd */ +#define BT_VS_BT_ENABLE 0xFF10 +#define VS_BT_DISABLE 0x00 +#define VS_BT_ENABLE 0x01 + +/** + * struct vs_bt_enable_cmd - Specifies HCI VS Bluetooth_Enable command. + * + * @op_code: HCI command op code. + * @len: Parameter length of command. + * @enable: 0 for disable BT, 1 for enable BT. + */ +struct vs_bt_enable_cmd { + __le16 op_code; + u8 len; + u8 enable; +} __packed; + +/* + * hci_wait_queue - Main Wait Queue in HCI driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(hci_wait_queue); + +/* + * btcg2900_devices - List of active CG2900 BT devices. + */ +static LIST_HEAD(btcg2900_devices); + +/* Internal function declarations */ +static int register_bluetooth(struct btcg2900_info *info); + +/* Internal functions */ + +/** + * get_bt_enable_cmd() - Get HCI BT enable command. + * @info: Device info structure. + * @bt_enable: true if Bluetooth IP shall be enabled, false otherwise. + * + * Returns: + * NULL if no command shall be sent, + * sk_buffer with command otherwise. + */ +static struct sk_buff *get_bt_enable_cmd(struct btcg2900_info *info, + bool bt_enable) +{ + struct sk_buff *skb; + struct vs_bt_enable_cmd *cmd; + struct cg2900_rev_data rev_data; + struct cg2900_user_data *pf_data; + + pf_data = dev_get_platdata(info->cmd); + + if (!pf_data->get_local_revision(pf_data, &rev_data)) { + BT_ERR(NAME "Couldn't get revision"); + return NULL; + } + + /* If connected chip does not support the command return NULL */ + if (CG2900_PG1_SPECIAL_HCI_REV != rev_data.revision && + CG2900_PG1_HCI_REV != rev_data.revision && + CG2900_PG2_HCI_REV != rev_data.revision) + return NULL; + + /* CG2900 used */ + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + BT_ERR(NAME "Could not allocate skb"); + return NULL; + } + + cmd = (struct vs_bt_enable_cmd *)skb_put(skb, sizeof(*cmd)); + cmd->op_code = cpu_to_le16(BT_VS_BT_ENABLE); + cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH; + if (bt_enable) + cmd->enable = VS_BT_ENABLE; + else + cmd->enable = VS_BT_DISABLE; + + return skb; +} + +/** + * close_bt_users() - Close all BT channels. + * @info: HCI driver info structure. + */ +static void close_bt_users(struct btcg2900_info *info) +{ + struct cg2900_user_data *pf_data; + + pf_data = dev_get_platdata(info->cmd); + if (pf_data->opened) + pf_data->close(pf_data); + + pf_data = dev_get_platdata(info->acl); + if (pf_data->opened) + pf_data->close(pf_data); + + pf_data = dev_get_platdata(info->evt); + if (pf_data->opened) + pf_data->close(pf_data); +} + +/** + * handle_bt_enable_comp() - Handle received BtEnable Complete event. + * @info: Info structure. + * @skb: Buffer with data coming from device. + * + * Returns: + * true if data has been handled internally, + * false otherwise. + */ +static bool handle_bt_enable_comp(struct btcg2900_info *info, u8 status) +{ + if (info->enable_state != ENABLE_WAITING_BT_ENABLED_CC && + info->enable_state != ENABLE_WAITING_BT_DISABLED_CC) + return false; + /* + * This is the command complete event for + * the HCI_Cmd_VS_Bluetooth_Enable. + * Check result and update state. + * + * The BT chip is enabled/disabled. Either it was enabled/ + * disabled now (status NO_ERROR) or it was already enabled/ + * disabled (assuming status CMD_DISALLOWED is already enabled/ + * disabled). + */ + if (status != HCI_ERR_NO_ERROR && status != HCI_ERR_CMD_DISALLOWED) { + BT_ERR(NAME "Could not enable/disable BT core (0x%X)", + status); + BT_DBG("New enable_state: ENABLE_BT_ERROR"); + info->enable_state = ENABLE_BT_ERROR; + goto finished; + } + + if (info->enable_state == ENABLE_WAITING_BT_ENABLED_CC) { + BT_DBG("New enable_state: ENABLE_BT_ENABLED"); + info->enable_state = ENABLE_BT_ENABLED; + BT_INFO("CG2900 BT core is enabled"); + } else { + BT_DBG("New enable_state: ENABLE_BT_DISABLED"); + info->enable_state = ENABLE_BT_DISABLED; + BT_INFO("CG2900 BT core is disabled"); + } + +finished: + /* Wake up whoever is waiting for this result. */ + wake_up_all(&hci_wait_queue); + return true; +} + +/** + * handle_bt_enable_stat() - Handle received BtEnable Status event. + * @info: Info structure. + * @skb: Buffer with data coming from device. + * + * Returns: + * true if data has been handled internally, + * false otherwise. + */ +static bool handle_bt_enable_stat(struct btcg2900_info *info, u8 status) +{ + if (info->enable_state != ENABLE_WAITING_BT_DISABLED_CC && + info->enable_state != ENABLE_WAITING_BT_ENABLED_CC) + return false; + + BT_DBG("HCI Driver received Command Status (BT enable): 0x%X", status); + /* + * This is the command status event for the HCI_Cmd_VS_Bluetooth_Enable. + * Just free the packet. + */ + return true; +} + +/** + * handle_rx_evt() - Check if received data is response to internal command. + * @info: Info structure. + * @skb: Buffer with data coming from device. + * + * Returns: + * true if data has been handled internally, + * false otherwise. + */ +static bool handle_rx_evt(struct btcg2900_info *info, struct sk_buff *skb) +{ + struct hci_event_hdr *evt = (struct hci_event_hdr *)skb->data; + struct hci_ev_cmd_complete *cmd_complete; + struct hci_ev_cmd_status *cmd_status; + u16 op_code; + u8 status; + bool pkt_handled = false; + + /* If BT is active no internal packets shall be generated */ + if (info->enable_state == BTCG2900_ACTIVE_STATE) + return false; + + if (evt->evt == HCI_EV_CMD_COMPLETE) { + cmd_complete = (struct hci_ev_cmd_complete *)(evt + 1); + status = *((u8 *)(cmd_complete + 1)); + op_code = le16_to_cpu(cmd_complete->opcode); + + if (op_code == BT_VS_BT_ENABLE) + pkt_handled = handle_bt_enable_comp(info, status); + } else if (evt->evt == HCI_EV_CMD_STATUS) { + cmd_status = (struct hci_ev_cmd_status *)(evt + 1); + op_code = le16_to_cpu(cmd_status->opcode); + status = cmd_status->status; + + if (op_code == BT_VS_BT_ENABLE) + pkt_handled = handle_bt_enable_stat(info, status); + } + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * hci_read_cb() - Callback for handling data received from CG2900 driver. + * @dev: Device receiving data. + * @skb: Buffer with data coming from device. + */ +static void hci_read_cb(struct cg2900_user_data *user, struct sk_buff *skb) +{ + int err = 0; + struct dev_info *dev_info; + struct btcg2900_info *info; + + dev_info = cg2900_get_usr(user); + info = dev_get_drvdata(user->dev); + + if (user->dev != info->evt || !handle_rx_evt(info, skb)) { + bt_cb(skb)->pkt_type = dev_info->hci_data_type; + skb->dev = (struct net_device *)info->hdev; + /* Update BlueZ stats */ + info->hdev->stat.byte_rx += skb->len; + if (bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) + info->hdev->stat.acl_rx++; + else + info->hdev->stat.evt_rx++; + + BT_DBG("Data receive %d bytes", skb->len); + + /* Provide BlueZ with received frame*/ + err = hci_recv_frame(skb); + /* If err, skb have been freed in hci_recv_frame() */ + if (err) + BT_ERR(NAME "Failed in supplying packet to Bluetooth" + " stack (%d)", err); + } +} + +/** + * hci_reset_cb() - Callback for handling reset from CG2900 driver. + * @dev: CPD device resetting. + */ +static void hci_reset_cb(struct cg2900_user_data *dev) +{ + int err; + struct btcg2900_info *info; + struct cg2900_user_data *pf_data; + + BT_INFO(NAME "hci_reset_cb"); + + info = dev_get_drvdata(dev->dev); + + BT_DBG("New reset_state: RESET_ACTIVATED"); + info->reset_state = RESET_ACTIVATED; + + /* + * Continue to deregister hdev if all channels has been reset else + * return. + */ + pf_data = dev_get_platdata(info->acl); + if (pf_data->opened) + return; + pf_data = dev_get_platdata(info->cmd); + if (pf_data->opened) + return; + pf_data = dev_get_platdata(info->evt); + if (pf_data->opened) + return; + + /* + * Deregister HCI device. Close and Destruct functions should + * in turn be called by BlueZ. + */ + BT_DBG("Deregister HCI device"); + err = hci_unregister_dev(info->hdev); + if (err) + BT_ERR(NAME "Can not deregister HCI device! (%d)", err); + /* + * Now we are in trouble. Try to register a new hdev + * anyway even though this will cost some memory. + */ + + wait_event_timeout(hci_wait_queue, + (RESET_UNREGISTERED == info->reset_state), + msecs_to_jiffies(RESP_TIMEOUT)); + if (RESET_UNREGISTERED != info->reset_state) + /* + * Now we are in trouble. Try to register a new hdev + * anyway even though this will cost some memory. + */ + BT_ERR(NAME "Timeout expired. Could not deregister HCI device"); + + /* Init and register hdev */ + BT_DBG("Register HCI device"); + err = register_bluetooth(info); + if (err) + BT_ERR(NAME "HCI Device registration error (%d)", err); +} + +/** + * send_enable_cmd() - Send a command with only enable/disable functionality. + * @info: Info structure. + * @en_info: Enable info structure. + * + * Returns: + * 0 if successful, + * -EACCES if correct response to command is not received, + * Error codes from CG2900 write. + */ +static int send_enable_cmd(struct btcg2900_info *info, + struct enable_info *en_info) +{ + struct sk_buff *enable_cmd; + int err; + struct cg2900_user_data *pf_data; + + /* + * Call function that returns the chip dependent enable HCI command. + * If NULL is returned, then no bt_enable command should be sent to the + * chip. + */ + enable_cmd = en_info->get_cmd(info, en_info->enable); + if (!enable_cmd) { + BT_DBG("%s New enable_state: %d", en_info->name, + en_info->success); + info->enable_state = en_info->success; + return 0; + } + + /* Set the HCI state before sending command to chip. */ + BT_DBG("%s New enable_state: %d", en_info->name, en_info->awaiting_cc); + info->enable_state = en_info->awaiting_cc; + + /* Send command to chip */ + pf_data = dev_get_platdata(info->cmd); + err = pf_data->write(pf_data, enable_cmd); + if (err) { + BT_ERR("Couldn't send %s command (%d)", en_info->name, err); + kfree_skb(enable_cmd); + info->enable_state = en_info->failed; + return err; + } + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + wait_event_timeout(hci_wait_queue, + info->enable_state == en_info->success, + msecs_to_jiffies(RESP_TIMEOUT)); + /* Check the current state to see if it worked */ + if (info->enable_state != en_info->success) { + BT_ERR("Could not change %s state (%d)", + en_info->name, info->enable_state); + BT_DBG("%s New enable_state: %d", en_info->name, + en_info->failed); + info->enable_state = en_info->failed; + return -EACCES; + } + + return 0; +} + +/** + * btcg2900_open() - Open HCI interface. + * @hdev: HCI device being opened. + * + * BlueZ callback function for opening HCI interface to device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * -EBUSY if device is already opened. + * -EACCES if opening of channels failed. + */ +static int btcg2900_open(struct hci_dev *hdev) +{ + struct btcg2900_info *info; + struct cg2900_user_data *pf_data; + int err; + struct enable_info en_info; + + BT_INFO("Open ST-Ericsson CG2900 driver"); + + if (!hdev) { + BT_ERR(NAME "NULL supplied for hdev"); + return -EINVAL; + } + + info = (struct btcg2900_info *)hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for driver_data"); + return -EINVAL; + } + + if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) { + BT_ERR(NAME "Device already opened!"); + return -EBUSY; + } + + pf_data = dev_get_platdata(info->acl); + err = pf_data->open(pf_data); + if (err) { + BT_ERR("Couldn't open BT ACL channel (%d)", err); + goto handle_error; + } + + pf_data = dev_get_platdata(info->cmd); + err = pf_data->open(pf_data); + if (err) { + BT_ERR("Couldn't open BT CMD channel (%d)", err); + goto handle_error; + } + + pf_data = dev_get_platdata(info->evt); + err = pf_data->open(pf_data); + if (err) { + BT_ERR("Couldn't open BT EVT channel (%d)", err); + goto handle_error; + } + + if (info->reset_state == RESET_ACTIVATED) { + BT_DBG("New reset_state: RESET_IDLE"); + info->reset_state = RESET_IDLE; + } + + /* First enable the BT core */ + en_info.enable = true; + en_info.get_cmd = get_bt_enable_cmd; + en_info.name = "VS BT Enable (true)"; + en_info.success = ENABLE_BT_ENABLED; + en_info.awaiting_cc = ENABLE_WAITING_BT_ENABLED_CC; + en_info.failed = ENABLE_BT_DISABLED; + + err = send_enable_cmd(info, &en_info); + if (err) { + BT_ERR("Couldn't enable BT core (%d)", err); + goto handle_error; + } + + return 0; + +handle_error: + close_bt_users(info); + clear_bit(HCI_RUNNING, &(hdev->flags)); + return err; + +} + +/** + * btcg2900_close() - Close HCI interface. + * @hdev: HCI device being closed. + * + * BlueZ callback function for closing HCI interface. + * It flushes the interface first. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * -EBUSY if device is not opened. + */ +static int btcg2900_close(struct hci_dev *hdev) +{ + struct btcg2900_info *info = NULL; + int err; + struct enable_info en_info; + + BT_DBG("btcg2900_close"); + + if (!hdev) { + BT_ERR(NAME "NULL supplied for hdev"); + return -EINVAL; + } + + info = (struct btcg2900_info *)hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for driver_data"); + return -EINVAL; + } + + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) { + BT_ERR(NAME "Device already closed!"); + return -EBUSY; + } + + /* Do not do this if there is an reset ongoing */ + if (info->reset_state == RESET_ACTIVATED) + goto remove_users; + + /* Now disable the BT core */ + en_info.enable = false; + en_info.get_cmd = get_bt_enable_cmd; + en_info.name = "VS BT Enable (false)"; + en_info.success = ENABLE_BT_DISABLED; + en_info.awaiting_cc = ENABLE_WAITING_BT_DISABLED_CC; + en_info.failed = ENABLE_BT_ENABLED; + + err = send_enable_cmd(info, &en_info); + if (err) + BT_ERR("Couldn't disable BT core (%d)", err); + +remove_users: + /* Finally deregister all users and free allocated data */ + close_bt_users(info); + return 0; +} + +/** + * btcg2900_send() - Send packet to device. + * @skb: sk buffer to be sent. + * + * BlueZ callback function for sending sk buffer. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * Error codes from cg2900_write. + */ +static int btcg2900_send(struct sk_buff *skb) +{ + struct hci_dev *hdev; + struct btcg2900_info *info; + struct cg2900_user_data *pf_data; + int err = 0; + + if (!skb) { + BT_ERR(NAME "NULL supplied for skb"); + return -EINVAL; + } + + hdev = (struct hci_dev *)(skb->dev); + if (!hdev) { + BT_ERR(NAME "NULL supplied for hdev"); + return -EINVAL; + } + + info = (struct btcg2900_info *)hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for info"); + return -EINVAL; + } + + /* Update BlueZ stats */ + hdev->stat.byte_tx += skb->len; + + BT_DBG("Data transmit %d bytes", skb->len); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + BT_DBG("Sending HCI_COMMAND_PKT"); + pf_data = dev_get_platdata(info->cmd); + err = pf_data->write(pf_data, skb); + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + BT_DBG("Sending HCI_ACLDATA_PKT"); + pf_data = dev_get_platdata(info->acl); + err = pf_data->write(pf_data, skb); + hdev->stat.acl_tx++; + break; + default: + BT_ERR(NAME "Trying to transmit unsupported packet type" + " (0x%.2X)", bt_cb(skb)->pkt_type); + err = -EOPNOTSUPP; + break; + }; + + return err; +} + +/** + * btcg2900_destruct() - Destruct HCI interface. + * @hdev: HCI device being destructed. + */ +static void btcg2900_destruct(struct hci_dev *hdev) +{ + struct btcg2900_info *info; + + BT_DBG("btcg2900_destruct"); + + info = hdev->driver_data; + if (!info) { + BT_ERR(NAME "NULL supplied for info"); + return; + } + + /* + * When destruct is called it means that the Bluetooth stack is done + * with the HCI device and we can now free it. + * Normally we do this only when removing the whole module through + * btcg2900_remove(), but when being reset we free the device here and + * we then set the reset state so that the reset handler can allocate a + * new HCI device and then register it to the Bluetooth stack. + */ + if (info->reset_state == RESET_ACTIVATED) { + if (info->hdev) { + hci_free_dev(info->hdev); + info->hdev = NULL; + } + BT_DBG("New reset_state: RESET_UNREGISTERED"); + info->reset_state = RESET_UNREGISTERED; + wake_up_all(&hci_wait_queue); + } +} + +/** + * get_info() - Return info structure for this device. + * @dev: Current device. + * + * Returns: + * Pointer to info struct if there is no error. + * ERR_PTR(-ENOMEM) if allocation fails. + */ +static struct btcg2900_info *get_info(struct device *dev) +{ + struct list_head *cursor; + struct btcg2900_info *tmp; + struct btcg2900_info *info = NULL; + + /* Find the info structure */ + list_for_each(cursor, &btcg2900_devices) { + tmp = list_entry(cursor, struct btcg2900_info, list); + if (tmp->parent == dev->parent) { + info = tmp; + break; + } + } + + if (info) + return info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + BT_ERR("Could not allocate info struct"); + return ERR_PTR(-ENOMEM); + } + info->parent = dev->parent; + list_add_tail(&info->list, &btcg2900_devices); + BT_DBG("CG2900 device added"); + return info; +} + +/** + * device_removed() - Remove device from list if there are no channels left. + * @info: BTCG2900 info structure. + */ +static void device_removed(struct btcg2900_info *info) +{ + struct list_head *cursor; + struct btcg2900_info *tmp; + + if (info->acl || info->cmd || info->evt) + /* There are still devices active */ + return; + + /* Find the info structure and delete it */ + list_for_each(cursor, &btcg2900_devices) { + tmp = list_entry(cursor, struct btcg2900_info, list); + if (tmp == info) { + list_del(cursor); + break; + } + } + kfree(info); + BT_DBG("CG2900 device removed"); +} + +/** + * register_bluetooth() - Initialize module. + * + * Alloc, init, and register HCI device to BlueZ. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from hci_register_dev. + */ +static int register_bluetooth(struct btcg2900_info *info) +{ + int err; + struct cg2900_user_data *pf_data; + + /* Check if all channels have been probed */ + if (!info->acl || !info->cmd || !info->evt) + return 0; + + pf_data = dev_get_platdata(info->cmd); + + info->hdev = hci_alloc_dev(); + if (!info->hdev) { + BT_ERR("Could not allocate mem for CG2900 BT driver"); + return -ENOMEM; + } + + SET_HCIDEV_DEV(info->hdev, info->parent); + info->hdev->bus = pf_data->channel_data.bt_bus; + info->hdev->driver_data = info; + info->hdev->owner = THIS_MODULE; + info->hdev->open = btcg2900_open; + info->hdev->close = btcg2900_close; + info->hdev->send = btcg2900_send; + info->hdev->destruct = btcg2900_destruct; + + err = hci_register_dev(info->hdev); + if (err) { + BT_ERR("Can not register BTCG2900 HCI device (%d)", err); + hci_free_dev(info->hdev); + info->hdev = NULL; + } + + BT_INFO("CG2900 registered"); + + BT_DBG("New enable_state: ENABLE_IDLE"); + info->enable_state = ENABLE_IDLE; + BT_DBG("New reset_state: RESET_IDLE"); + info->reset_state = RESET_IDLE; + + return err; +} + +/** + * probe_common() - Initialize channel and register to BT stack. + * @dev: Current device. + * @info: BTCG2900 info structure. + * @hci_data_type: Data type of this channel, e.g. ACL. + * + * Allocate and initialize private data. Register to Bluetooth stack. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from register_bluetooth. + */ +static int probe_common(struct platform_device *pdev, + struct btcg2900_info *info, + u8 hci_data_type) +{ + int err; + struct cg2900_user_data *pf_data; + struct dev_info *dev_info; + struct device *dev = &pdev->dev; + + dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL); + if (!dev_info) { + BT_ERR("Could not allocate dev_info"); + return -ENOMEM; + } + + dev_set_drvdata(dev, info); + + pf_data = mfd_get_data(pdev); + pf_data->dev = dev; + pf_data->read_cb = hci_read_cb; + pf_data->reset_cb = hci_reset_cb; + + /* Set platform data */ + dev->platform_data = pf_data; + + /* Init and register hdev */ + err = register_bluetooth(info); + if (err) { + BT_ERR("HCI Device registration error (%d)", err); + kfree(dev_info); + return err; + } + dev_info->hci_data_type = hci_data_type; + cg2900_set_usr(pf_data, dev_info); + + return 0; +} + +/** + * btcg2900_cmd_probe() - Initialize command channel. + * @pdev: Platform device. + * + * Allocate and initialize private data. + * + * Returns: + * 0 if there is no error. + * Error codes from get_info and probe_common. + */ +static int __devinit btcg2900_cmd_probe(struct platform_device *pdev) +{ + int err; + struct btcg2900_info *info; + + BT_DBG("Starting CG2900 Command channel"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->cmd = &pdev->dev; + + err = probe_common(pdev, info, HCI_COMMAND_PKT); + if (err) { + BT_ERR("Failed to initialize channel"); + info->cmd = NULL; + device_removed(info); + return err; + } + + return 0; +} + +/** + * btcg2900_acl_probe() - Initialize command channel. + * @pdev: Platform device. + * + * Allocate and initialize private data. + * + * Returns: + * 0 if there is no error. + * Error codes from get_info and probe_common. + */ +static int __devinit btcg2900_acl_probe(struct platform_device *pdev) +{ + int err; + struct btcg2900_info *info; + + BT_DBG("Starting CG2900 ACL channel"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->acl = &pdev->dev; + + err = probe_common(pdev, info, HCI_ACLDATA_PKT); + if (err) { + BT_ERR("Failed to initialize channel"); + info->acl = NULL; + device_removed(info); + return err; + } + + return 0; +} + +/** + * btcg2900_evt_probe() - Initialize event channel. + * @pdev: Platform device. + * + * Allocate and initialize private data. + * + * Returns: + * 0 if there is no error. + * Error codes from get_info and probe_common. + */ +static int __devinit btcg2900_evt_probe(struct platform_device *pdev) +{ + int err; + struct btcg2900_info *info; + + BT_DBG("Starting CG2900 Event channel"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->evt = &pdev->dev; + + err = probe_common(pdev, info, HCI_EVENT_PKT); + if (err) { + BT_ERR("Failed to initialize channel"); + info->evt = NULL; + device_removed(info); + return err; + } + + return 0; +} + +/** + * remove_common() - Remove channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from hci_unregister_dev. + */ +static int remove_common(struct platform_device *pdev, + struct btcg2900_info *info) +{ + int err = 0; + struct cg2900_user_data *pf_data; + struct dev_info *dev_info; + + pf_data = mfd_get_data(pdev); + dev_info = cg2900_get_usr(pf_data); + + kfree(dev_info); + cg2900_set_usr(pf_data, NULL); + + if (!info->hdev) + goto finished; + + BT_INFO("Unregistering CG2900"); + err = hci_unregister_dev(info->hdev); + if (err) + BT_ERR("Can not unregister HCI device (%d)", err); + hci_free_dev(info->hdev); + info->hdev = NULL; + +finished: + device_removed(info); + return err; +} + +/** + * btcg2900_cmd_remove() - Remove command channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from remove_common. + */ +static int __devexit btcg2900_cmd_remove(struct platform_device *pdev) +{ + struct btcg2900_info *info; + + BT_DBG("Removing CG2900 Command channel"); + + info = dev_get_drvdata(&pdev->dev); + info->cmd = NULL; + return remove_common(pdev, info); +} + +/** + * btcg2900_acl_remove() - Remove ACL channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from remove_common. + */ +static int __devexit btcg2900_acl_remove(struct platform_device *pdev) +{ + struct btcg2900_info *info; + + BT_DBG("Removing CG2900 ACL channel"); + + info = dev_get_drvdata(&pdev->dev); + info->acl = NULL; + return remove_common(pdev, info); +} + +/** + * btcg2900_evt_remove() - Remove event channel. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * Error codes from remove_common. + */ +static int __devexit btcg2900_evt_remove(struct platform_device *pdev) +{ + struct btcg2900_info *info; + + BT_DBG("Removing CG2900 Event channel"); + + info = dev_get_drvdata(&pdev->dev); + info->evt = NULL; + return remove_common(pdev, info); +} + +static struct platform_driver btcg2900_cmd_driver = { + .driver = { + .name = "cg2900-btcmd", + .owner = THIS_MODULE, + }, + .probe = btcg2900_cmd_probe, + .remove = __devexit_p(btcg2900_cmd_remove), +}; + +static struct platform_driver btcg2900_acl_driver = { + .driver = { + .name = "cg2900-btacl", + .owner = THIS_MODULE, + }, + .probe = btcg2900_acl_probe, + .remove = __devexit_p(btcg2900_acl_remove), +}; + +static struct platform_driver btcg2900_evt_driver = { + .driver = { + .name = "cg2900-btevt", + .owner = THIS_MODULE, + }, + .probe = btcg2900_evt_probe, + .remove = __devexit_p(btcg2900_evt_remove), +}; + +/** + * btcg2900_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init btcg2900_init(void) +{ + int err; + + BT_DBG("btcg2900_init"); + + err = platform_driver_register(&btcg2900_cmd_driver); + if (err) { + BT_ERR("Failed to register cmd (%d)", err); + return err; + } + err = platform_driver_register(&btcg2900_acl_driver); + if (err) { + BT_ERR("Failed to register acl (%d)", err); + return err; + } + err = platform_driver_register(&btcg2900_evt_driver); + if (err) { + BT_ERR("Failed to register evt (%d)", err); + return err; + } + return err; +} + +/** + * btcg2900_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit btcg2900_exit(void) +{ + BT_DBG("btcg2900_exit"); + platform_driver_unregister(&btcg2900_cmd_driver); + platform_driver_unregister(&btcg2900_acl_driver); + platform_driver_unregister(&btcg2900_evt_driver); +} + +module_init(btcg2900_init); +module_exit(btcg2900_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_AUTHOR("Henrik Possung ST-Ericsson"); +MODULE_AUTHOR("Josef Kindberg ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 Driver for ST-Ericsson controller"); diff --git a/drivers/staging/cg2900/bluetooth/cg2900_uart.c b/drivers/staging/cg2900/bluetooth/cg2900_uart.c new file mode 100644 index 00000000000..4417525306a --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/cg2900_uart.c @@ -0,0 +1,2074 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * Lukasz Rymanowski (lukasz.rymanowski@tieto.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth UART Driver for ST-Ericsson CG2900 connectivity controller. + */ +#define NAME "cg2900_uart" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" + +#include "hci_uart.h" + +#define MAIN_DEV (uart_info->dev) + +/* Workqueues' names */ +#define UART_WQ_NAME "cg2900_uart_wq" +#define UART_NAME "cg2900_uart" + +/* + * A BT command complete event without any parameters is the defined size plus + * 1 byte extra for the status field which is always present in a + * command complete event. + */ +#define HCI_BT_CMD_COMPLETE_LEN (sizeof(struct hci_ev_cmd_complete) + 1) + +/* Timers used in milliseconds */ +#define UART_TX_TIMEOUT 100 +#define UART_RX_TIMEOUT 20 +#define UART_RESP_TIMEOUT 1000 +#define UART_RESUME_TIMEOUT 20 + +/* Number of bytes to reserve at start of sk_buffer when receiving packet */ +#define RX_SKB_RESERVE 8 +/* Max size of received packet (not including reserved bytes) */ +#define RX_SKB_MAX_SIZE 1024 + +/* Size of the header in the different packets */ +#define HCI_BT_EVT_HDR_SIZE 2 +#define HCI_BT_ACL_HDR_SIZE 4 +#define HCI_FM_RADIO_HDR_SIZE 1 +#define HCI_GNSS_HDR_SIZE 3 + +/* Position of length field in the different packets */ +#define HCI_EVT_LEN_POS 2 +#define HCI_ACL_LEN_POS 3 +#define FM_RADIO_LEN_POS 1 +#define GNSS_LEN_POS 2 + +/* Baud rate defines */ +#define ZERO_BAUD_RATE 0 +#define DEFAULT_BAUD_RATE 115200 +#define HIGH_BAUD_RATE 3000000 + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +/* Standardized Bluetooth H:4 channels */ +#define HCI_BT_CMD_H4_CHANNEL 0x01 +#define HCI_BT_ACL_H4_CHANNEL 0x02 +#define HCI_BT_SCO_H4_CHANNEL 0x03 +#define HCI_BT_EVT_H4_CHANNEL 0x04 + +#define BT_BDADDR_SIZE 6 + +/* Reserve 1 byte for the HCI H:4 header */ +#define HCI_H4_SIZE 1 +#define CG2900_SKB_RESERVE HCI_H4_SIZE + +/* Default H4 channels which may change depending on connected controller */ +#define HCI_FM_RADIO_H4_CHANNEL 0x08 +#define HCI_GNSS_H4_CHANNEL 0x09 + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */ +#define CG2900_BAUD_RATE_57600 0x03 +#define CG2900_BAUD_RATE_115200 0x02 +#define CG2900_BAUD_RATE_230400 0x01 +#define CG2900_BAUD_RATE_460800 0x00 +#define CG2900_BAUD_RATE_921600 0x20 +#define CG2900_BAUD_RATE_2000000 0x25 +#define CG2900_BAUD_RATE_3000000 0x27 +#define CG2900_BAUD_RATE_4000000 0x2B + +/* GNSS */ +struct gnss_hci_hdr { + __u8 op_code; + __le16 plen; +} __packed; + +/* FM legacy command packet */ +struct fm_leg_cmd { + __u8 length; + __u8 opcode; + __u8 read_write; + __u8 fm_function; + union { /* Payload varies with function */ + __le16 irqmask; + struct fm_leg_fm_cmd { + __le16 head; + __le16 data[]; + } fm_cmd; + }; +} __packed; + +/* FM legacy command complete packet */ +struct fm_leg_cmd_cmpl { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 read_write; + __u8 cmd_status; + __u8 fm_function; + __le16 response_head; + __le16 data[]; +} __packed; + +/* FM legacy interrupt packet, PG2 style */ +struct fm_leg_irq_v2 { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 event_type; + __u8 event_id; + __le16 irq; +} __packed; + +/* FM legacy interrupt packet, PG1 style */ +struct fm_leg_irq_v1 { + __u8 param_length; + __u8 opcode; + __u8 event_id; + __le16 irq; +} __packed; + +union fm_leg_evt_or_irq { + __u8 param_length; + struct fm_leg_cmd_cmpl evt; + struct fm_leg_irq_v2 irq_v2; + struct fm_leg_irq_v1 irq_v1; +} __packed; + +/* BT VS SetBaudRate command */ +#define CG2900_BT_OP_VS_SET_BAUD_RATE 0xFC09 +struct bt_vs_set_baud_rate_cmd { + __le16 opcode; + __u8 plen; + __u8 baud_rate; +} __packed; + +/** + * enum uart_rx_state - UART RX-state for UART. + * @W4_PACKET_TYPE: Waiting for packet type. + * @W4_EVENT_HDR: Waiting for BT event header. + * @W4_ACL_HDR: Waiting for BT ACL header. + * @W4_FM_RADIO_HDR: Waiting for FM header. + * @W4_GNSS_HDR: Waiting for GNSS header. + * @W4_DATA: Waiting for data in rest of the packet (after header). + */ +enum uart_rx_state { + W4_PACKET_TYPE, + W4_EVENT_HDR, + W4_ACL_HDR, + W4_FM_RADIO_HDR, + W4_GNSS_HDR, + W4_DATA +}; + +/** + * enum sleep_state - Sleep-state for UART. + * @CHIP_AWAKE: Chip is awake. + * @CHIP_FALLING_ASLEEP: Chip is falling asleep. + * @CHIP_ASLEEP: Chip is asleep. + * @CHIP_SUSPENDED: Chip in suspend state. + * @CHIP_RESUMING: Chip is going back from suspend state. + * @CHIP_POWERED_DOWN: Chip is off. + */ +enum sleep_state { + CHIP_AWAKE, + CHIP_FALLING_ASLEEP, + CHIP_ASLEEP, + CHIP_SUSPENDED, + CHIP_RESUMING, + CHIP_POWERED_DOWN +}; + +/** + * enum baud_rate_change_state - Baud rate-state for UART. + * @BAUD_IDLE: No baud rate change is ongoing. + * @BAUD_SENDING_RESET: HCI reset has been sent. Waiting for command complete + * event. + * @BAUD_START: Set baud rate cmd scheduled for sending. + * @BAUD_SENDING: Set baud rate cmd sending in progress. + * @BAUD_WAITING: Set baud rate cmd sent, waiting for command complete + * event. + * @BAUD_SUCCESS: Baud rate change has succeeded. + * @BAUD_FAIL: Baud rate change has failed. + */ +enum baud_rate_change_state { + BAUD_IDLE, + BAUD_SENDING_RESET, + BAUD_START, + BAUD_SENDING, + BAUD_WAITING, + BAUD_SUCCESS, + BAUD_FAIL +}; + +/** + * struct uart_work_struct - Work structure for UART module. + * @work: Work structure. + * @data: Pointer to private data. + * + * This structure is used to pack work for work queue. + */ +struct uart_work_struct { + struct work_struct work; + void *data; +}; + +/** + * struct uart_delayed_work_struct - Work structure for UART module. + * @delayed_work: Work structure. + * @data: Pointer to private data. + * + * This structure is used to pack work for work queue. + */ +struct uart_delayed_work_struct { + struct delayed_work work; + void *data; +}; + +/** + * struct uart_info - Main UART info structure. + * @rx_state: Current RX state. + * @rx_count: Number of bytes left to receive. + * @rx_skb: SK_buffer to store the received data into. + * @tx_queue: TX queue for sending data to chip. + * @hu: Hci uart structure. + * @wq: UART work queue. + * @baud_rate_state: UART baud rate change state. + * @baud_rate: Current baud rate setting. + * @sleep_state: UART sleep state. + * @sleep_work: Delayed sleep work struct. + * @wakeup_work: Wake-up work struct. + * @restart_sleep_work: Reschedule sleep_work and wake-up work struct. + * @sleep_state_lock: Used to protect chip state. + * @sleep_allowed: Indicates if tty has functions needed for sleep mode. + * @tx_in_progress: Indicates data sending in progress. + * @rx_in_progress: Indicates data receiving in progress. + * @transmission_lock: Spin_lock to protect tx/rx_in_progress. + * @regulator: Regulator. + * @regulator_enabled: True if regulator is enabled. + * @dev: Pointer to CG2900 uart device. + * @chip_dev: Chip device for current UART transport. + * @cts_irq: CTS interrupt for this UART. + * @cts_gpio: CTS GPIO for this UART. + */ +struct uart_info { + enum uart_rx_state rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + struct sk_buff_head tx_queue; + + struct hci_uart *hu; + + struct workqueue_struct *wq; + enum baud_rate_change_state baud_rate_state; + int baud_rate; + enum sleep_state sleep_state; + struct uart_delayed_work_struct sleep_work; + struct uart_work_struct wakeup_work; + struct uart_work_struct restart_sleep_work; + struct mutex sleep_state_lock; + bool sleep_allowed; + bool tx_in_progress; + bool rx_in_progress; + spinlock_t transmission_lock; + struct regulator *regulator; + bool regulator_enabled; + struct device *dev; + struct cg2900_chip_dev chip_dev; + int cts_irq; + int cts_gpio; +}; + +/* Module parameters */ +static int uart_default_baud = DEFAULT_BAUD_RATE; +static int uart_high_baud = HIGH_BAUD_RATE; +static int uart_debug; + +static DECLARE_WAIT_QUEUE_HEAD(uart_wait_queue); + +static void wake_up_chip(struct uart_info *uart_info); + +/** + * is_chip_flow_off() - Check if chip has set flow off. + * @tty: Pointer to tty. + * + * Returns: + * true - chip flows off. + * false - chip flows on. + */ +static bool is_chip_flow_off(struct uart_info *uart_info) +{ + int lines; + + lines = hci_uart_tiocmget(uart_info->hu); + + if (lines & TIOCM_CTS) + return false; + else + return true; +} + +/** + * create_work_item() - Create work item and add it to the work queue. + * @uart_info: Main Uart structure. + * @work_func: Work function. + * + * Returns: + * 0 if there is no error. + * -EBUSY if not possible to queue work. + * -ENOMEM if allocation fails. + */ +static int create_work_item(struct uart_info *uart_info, + work_func_t work_func) +{ + struct uart_work_struct *new_work; + int res; + + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC); + if (!new_work) { + dev_err(MAIN_DEV, + "Failed to alloc memory for uart_work_struct\n"); + return -ENOMEM; + } + + new_work->data = uart_info; + INIT_WORK(&new_work->work, work_func); + + res = queue_work(uart_info->wq, &new_work->work); + if (!res) { + dev_err(MAIN_DEV, + "Failed to queue work_struct because it's already " + "in the queue\n"); + kfree(new_work); + return -EBUSY; + } + + return 0; +} + +/** + * handle_cts_irq() - Called to handle CTS interrupt in work context. + * @work: work which needs to be done. + * + * The handle_cts_irq() function is a work handler called if interrupt on CTS + * occurred. It wakes up the transport. + */ +static void handle_cts_irq(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + + spin_lock_bh(&(uart_info->transmission_lock)); + /* Mark that there is an ongoing transfer. */ + uart_info->rx_in_progress = true; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + mutex_lock(&(uart_info->sleep_state_lock)); + + if (uart_info->sleep_state == CHIP_SUSPENDED) { + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_RESUMING\n"); + uart_info->sleep_state = CHIP_RESUMING; + mutex_unlock(&(uart_info->sleep_state_lock)); + } else { + mutex_unlock(&(uart_info->sleep_state_lock)); + wake_up_chip(uart_info); + } + + kfree(current_work); +} + +/** + * cts_interrupt() - Called to handle CTS interrupt. + * @irq: Interrupt that occurred. + * @dev_id: Device ID where interrupt occurred. + * + * The cts_interrupt() function is called if interrupt on CTS occurred. + * It disables the interrupt and starts a new work thread to handle + * the interrupt. + */ +static irqreturn_t cts_interrupt(int irq, void *dev_id) +{ + struct uart_info *uart_info = dev_get_drvdata(dev_id); +#ifdef CONFIG_PM + disable_irq_wake(irq); +#endif + disable_irq_nosync(irq); + + /* Create work and leave IRQ context. */ + (void)create_work_item(uart_info, handle_cts_irq); + + return IRQ_HANDLED; +} + +/** + * set_cts_irq() - Enable interrupt on CTS. + * @uart_info: Main Uart structure. + * + * Returns: + * 0 if there is no error. + * Error codes from request_irq and disable_uart. + */ +static int set_cts_irq(struct uart_info *uart_info) +{ + int err; + int cts_val = 0; + + /* Set IRQ on CTS. */ + err = request_irq(uart_info->cts_irq, + cts_interrupt, + IRQF_TRIGGER_FALLING, + UART_NAME, + uart_info->dev); + if (err) { + dev_err(MAIN_DEV, "Could not request CTS IRQ (%d)\n", err); + return err; + } + + /* + * It may happen that there was already an interrupt on CTS just before + * the enable_irq() call above. If the CTS line is low now it means that + * it's happened, so disable the CTS interrupt and return -ECANCELED. + */ + cts_val = gpio_get_value(uart_info->cts_gpio); + if (!cts_val) { + dev_dbg(MAIN_DEV, "Missed interrupt, going back to " + "awake state\n"); + free_irq(uart_info->cts_irq, uart_info->dev); + return -ECANCELED; + } + +#ifdef CONFIG_PM + enable_irq_wake(uart_info->cts_irq); +#endif + return 0; +} + +/** + * disable_uart_pins() - Disable the UART pins. + * @uart_info: Main Uart structure. + */ +static void disable_uart_pins(struct uart_info *uart_info) +{ + struct cg2900_platform_data *pf_data; + + pf_data = dev_get_platdata(uart_info->dev); + + if (pf_data->uart.disable_uart) { + int err = pf_data->uart.disable_uart(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, + "Unable to disable UART Hardware (%d)\n", err); + } +} + +/** + * enable_uart_pins() - Enable the UART pins. + * @uart_info: Main Uart structure. + */ +static void enable_uart_pins(struct uart_info *uart_info) +{ + struct cg2900_platform_data *pf_data; + + pf_data = dev_get_platdata(uart_info->dev); + + if (pf_data->uart.enable_uart) { + int err = pf_data->uart.enable_uart(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, + "Unable to enable UART Hardware (%d)\n", err); + } +} + +/** + * unset_cts_irq() - Disable interrupt on CTS. + * @uart_info: Main Uart structure. + */ +static void unset_cts_irq(struct uart_info *uart_info) +{ + /* Free CTS interrupt */ + free_irq(uart_info->cts_irq, uart_info->dev); +} + +/** + * get_sleep_timeout() - Get sleep timeout. + * @uart_info: Main Uart structure. + * + * Check all conditions for sleep and return sleep timeout. + * Return: + * 0: sleep not allowed. + * other: Timeout value in ms. + */ +static unsigned long get_sleep_timeout(struct uart_info *uart_info) +{ + unsigned long timeout_jiffies = cg2900_get_sleep_timeout(); + + if (timeout_jiffies && + uart_info->hu && + uart_info->hu->fd && + uart_info->sleep_allowed) + return timeout_jiffies; + + return 0; +} + +/** + * work_wake_up_chip() - Called to wake up of the transport in work context. + * @work: work which needs to be done. + */ +static void work_wake_up_chip(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + + wake_up_chip(uart_info); +} + +/** + * wake_up_chip() - Wakes up the chip and transport. + * @work: pointer to a work struct if the function was called that way. + * + * Depending on the current sleep state it may wake up the transport. + */ +static void wake_up_chip(struct uart_info *uart_info) +{ + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + /* Resuming state is special. Need to get back chip to awake state. */ + if (!timeout_jiffies && uart_info->sleep_state != CHIP_RESUMING) + return; + + mutex_lock(&(uart_info->sleep_state_lock)); + + /* + * If chip is powered down we cannot wake it up here. It has to be woken + * up through a call to uart_set_chip_power() + */ + if (CHIP_POWERED_DOWN == uart_info->sleep_state) + goto finished; + + /* + * This function indicates data is transmitted. + * Therefore see to that the chip is awake. + */ + if (CHIP_AWAKE == uart_info->sleep_state) + goto finished; + + if (CHIP_ASLEEP == uart_info->sleep_state || + CHIP_RESUMING == uart_info->sleep_state) { + /* Wait before disabling IRQ */ + schedule_timeout_killable( + msecs_to_jiffies(UART_RESUME_TIMEOUT)); + + /* Disable IRQ only when it was enabled. */ + unset_cts_irq(uart_info); + (void)hci_uart_set_baudrate(uart_info->hu, + uart_info->baud_rate); + + enable_uart_pins(uart_info); + + /* + * Wait before flowing on. Otherwise UART might not be ready in + * time + */ + schedule_timeout_killable( + msecs_to_jiffies(UART_RESUME_TIMEOUT)); + + /* Set FLOW on. */ + hci_uart_flow_ctrl(uart_info->hu, FLOW_ON); + } + + /* Unset BREAK. */ + dev_dbg(MAIN_DEV, "wake_up_chip: Clear break\n"); + hci_uart_set_break(uart_info->hu, BREAK_OFF); + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_AWAKE\n"); + uart_info->sleep_state = CHIP_AWAKE; + +finished: + mutex_unlock(&(uart_info->sleep_state_lock)); +} + +/** + * set_chip_sleep_mode() - Put the chip and transport to sleep mode. + * @work: pointer to work_struct. + * + * The set_chip_sleep_mode() function is called if there are no ongoing data + * transmissions. It tries to put the chip in sleep mode. + * + */ +static void set_chip_sleep_mode(struct work_struct *work) +{ + int err = 0; + struct delayed_work *delayed_work = + container_of(work, struct delayed_work, work); + struct uart_delayed_work_struct *current_work = container_of( + delayed_work, struct uart_delayed_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + int chars_in_buffer; + + if (!timeout_jiffies) + return; + + if (uart_info->tx_in_progress || uart_info->rx_in_progress) { + dev_dbg(MAIN_DEV, "Not going to sleep, TX/RX in progress\n"); + return; + } + + mutex_lock(&(uart_info->sleep_state_lock)); + + switch (uart_info->sleep_state) { + case CHIP_FALLING_ASLEEP: + if (!is_chip_flow_off(uart_info)) { + dev_dbg(MAIN_DEV, "Chip flow is on, it's not ready to" + "sleep yet\n"); + goto schedule_sleep_work; + } + + /* Flow OFF. */ + hci_uart_flow_ctrl(uart_info->hu, FLOW_OFF); + + disable_uart_pins(uart_info); + + /* + * Set baud zero. + * This cause shut off UART clock as well. + */ + (void)hci_uart_set_baudrate(uart_info->hu, + ZERO_BAUD_RATE); + err = set_cts_irq(uart_info); + if (err < 0) { + enable_uart_pins(uart_info); + (void)hci_uart_set_baudrate(uart_info->hu, + uart_info->baud_rate); + hci_uart_flow_ctrl(uart_info->hu, FLOW_ON); + hci_uart_set_break(uart_info->hu, BREAK_OFF); + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_AWAKE\n"); + uart_info->sleep_state = CHIP_AWAKE; + + if (err == -ECANCELED) + goto finished; + else { + dev_err(MAIN_DEV, "Can not set interrupt on " + "CTS, err:%d\n", err); + goto error; + } + } + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_ASLEEP\n"); + uart_info->sleep_state = CHIP_ASLEEP; + break; + case CHIP_AWAKE: + chars_in_buffer = hci_uart_chars_in_buffer(uart_info->hu); + if (chars_in_buffer) { + dev_dbg(MAIN_DEV, "sleep_timer_expired: " + "tx not finished, stay awake and " + "restart the sleep timer\n"); + goto schedule_sleep_work; + } + + dev_dbg(MAIN_DEV, "sleep_timer_expired: Set break\n"); + hci_uart_set_break(uart_info->hu, BREAK_ON); + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_FALLING_ASLEEP\n"); + uart_info->sleep_state = CHIP_FALLING_ASLEEP; + goto schedule_sleep_work; + + case CHIP_POWERED_DOWN: + case CHIP_SUSPENDED: + case CHIP_ASLEEP: /* Fallthrough. */ + default: + dev_dbg(MAIN_DEV, + "Chip sleeps, is suspended or powered down\n"); + break; + } + + mutex_unlock(&(uart_info->sleep_state_lock)); + + return; + +finished: + mutex_unlock(&(uart_info->sleep_state_lock)); + return; +schedule_sleep_work: + mutex_unlock(&(uart_info->sleep_state_lock)); + if (timeout_jiffies) + queue_delayed_work(uart_info->wq, &uart_info->sleep_work.work, + timeout_jiffies); + return; +error: + /* Disable sleep mode.*/ + dev_err(MAIN_DEV, "Disable sleep mode\n"); + uart_info->sleep_allowed = false; + mutex_unlock(&(uart_info->sleep_state_lock)); +} + +#ifdef CONFIG_PM +/** + * cg2900_uart_suspend() - Called by Linux PM to put the device in a low power mode. + * @pdev: Pointer to platform device. + * @state: New state. + * + * In UART case, CG2900 driver does nothing on suspend. + * + * Returns: + * 0 - Success. + */ +static int cg2900_uart_suspend(struct platform_device *pdev, pm_message_t state) +{ + int err = 0; + struct uart_info *uart_info = dev_get_drvdata(&pdev->dev); + + mutex_lock(&(uart_info->sleep_state_lock)); + + if (uart_info->sleep_state == CHIP_POWERED_DOWN) + goto finished; + + if (uart_info->sleep_state != CHIP_ASLEEP) { + err = -EBUSY; + goto finished; + } + + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_SUSPENDED\n"); + uart_info->sleep_state = CHIP_SUSPENDED; + +finished: + mutex_unlock(&(uart_info->sleep_state_lock)); + return err; +} + +/** + * cg2900_uart_resume() - Called to bring a device back from a low power state. + * @pdev: Pointer to platform device. + * + * In UART case, CG2900 driver does nothing on resume. + * + * Returns: + * 0 - Success. + */ +static int cg2900_uart_resume(struct platform_device *pdev) +{ + struct uart_info *uart_info = dev_get_drvdata(&pdev->dev); + + mutex_lock(&(uart_info->sleep_state_lock)); + + if (uart_info->sleep_state == CHIP_RESUMING) + /* System resume because of trafic on UART. Lets wakeup.*/ + (void)queue_work(uart_info->wq, &uart_info->wakeup_work.work); + else if (uart_info->sleep_state != CHIP_POWERED_DOWN) { + /* No need to wakeup chip. Go back to Asleep state.*/ + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_ASLEEP\n"); + uart_info->sleep_state = CHIP_ASLEEP; + } + + mutex_unlock(&(uart_info->sleep_state_lock)); + return 0; +} +#endif /* CONFIG_PM */ + +/** + * cg2900_enable_regulator() - Enable regulator. + * @uart_info: Main Uart structure. + * + * Returns: + * 0 - Success. + * Error from regulator_get, regulator_enable. + */ +static int cg2900_enable_regulator(struct uart_info *uart_info) +{ +#ifdef CONFIG_REGULATOR + int err; + + /* Get and enable regulator. */ + uart_info->regulator = regulator_get(uart_info->dev, "gbf_1v8"); + if (IS_ERR(uart_info->regulator)) { + dev_err(MAIN_DEV, "Not able to find regulator\n"); + err = PTR_ERR(uart_info->regulator); + } else { + err = regulator_enable(uart_info->regulator); + if (err) + dev_err(MAIN_DEV, "Not able to enable regulator\n"); + else + uart_info->regulator_enabled = true; + } + return err; +#else + return 0; +#endif +} + +/** + * cg2900_disable_regulator() - Disable regulator. + * @uart_info: Main Uart structure. + * + */ +static void cg2900_disable_regulator(struct uart_info *uart_info) +{ +#ifdef CONFIG_REGULATOR + /* Disable and put regulator. */ + if (uart_info->regulator && uart_info->regulator_enabled) { + regulator_disable(uart_info->regulator); + uart_info->regulator_enabled = false; + } + regulator_put(uart_info->regulator); + uart_info->regulator = NULL; +#endif +} + +/** + * is_set_baud_rate_cmd() - Checks if data contains set baud rate hci cmd. + * @data: Pointer to data array to check. + * + * Returns: + * true - if cmd found; + * false - otherwise. + */ +static bool is_set_baud_rate_cmd(const char *data) +{ + struct hci_command_hdr *cmd; + + if (data[0] != HCI_BT_CMD_H4_CHANNEL) + return false; + + cmd = (struct hci_command_hdr *)&data[1]; + if (le16_to_cpu(cmd->opcode) == CG2900_BT_OP_VS_SET_BAUD_RATE && + cmd->plen == BT_PARAM_LEN(sizeof(struct bt_vs_set_baud_rate_cmd))) + return true; + + return false; +} + +/** + * is_bt_cmd_complete_no_param() - Checks if data contains command complete event for a certain command. + * @skb: sk_buffer containing the data including H:4 header. + * @opcode: Command op code. + * @status: Command status. + * + * Returns: + * true - If this is the command complete we were looking for; + * false - otherwise. + */ +static bool is_bt_cmd_complete_no_param(struct sk_buff *skb, u16 opcode, + u8 *status) +{ + struct hci_event_hdr *event; + struct hci_ev_cmd_complete *complete; + u8 *data = &(skb->data[0]); + + if (HCI_BT_EVT_H4_CHANNEL != *data) + return false; + + data += HCI_H4_SIZE; + event = (struct hci_event_hdr *)data; + if (HCI_EV_CMD_COMPLETE != event->evt || + HCI_BT_CMD_COMPLETE_LEN != event->plen) + return false; + + data += sizeof(*event); + complete = (struct hci_ev_cmd_complete *)data; + if (opcode != le16_to_cpu(complete->opcode)) + return false; + + if (status) { + /* + * All command complete have the status field at first byte of + * packet data. + */ + data += sizeof(*complete); + *status = *data; + } + return true; +} + +/** + * alloc_rx_skb() - Alloc an sk_buff structure for receiving data from controller. + * @size: Size in number of octets. + * @priority: Allocation priority, e.g. GFP_KERNEL. + * + * Returns: + * Pointer to sk_buff structure. + */ +static struct sk_buff *alloc_rx_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + RX_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, RX_SKB_RESERVE); + + return skb; +} + +/** + * finish_setting_baud_rate() - Handles sending the ste baud rate hci cmd. + * @hu: Pointer to associated Hci uart structure. + * + * finish_setting_baud_rate() makes sure that the set baud rate cmd has + * been really sent out on the wire and then switches the tty driver to new + * baud rate. + */ +static void finish_setting_baud_rate(struct hci_uart *hu) +{ + struct uart_info *uart_info = + (struct uart_info *)dev_get_drvdata(hu->proto->dev); + /* + * Give the tty driver time to send data and proceed. If it hasn't + * been sent we can't do much about it anyway. + */ + schedule_timeout_killable(msecs_to_jiffies(UART_TX_TIMEOUT)); + + /* + * Now set the termios struct to the new baudrate. Start by storing + * the old termios. + */ + if (hci_uart_set_baudrate(hu, uart_info->baud_rate) < 0) { + /* Something went wrong.*/ + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + } else { + dev_dbg(MAIN_DEV, "Setting termios to new baud rate\n"); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_WAITING\n"); + uart_info->baud_rate_state = BAUD_WAITING; + } + + hci_uart_flow_ctrl(hu, FLOW_ON); +} + +/** + * alloc_set_baud_rate_cmd() - Allocates new sk_buff and fills in the change baud rate hci cmd. + * @uart_info: Main Uart structure. + * @baud: (in/out) Requested new baud rate. Updated to default baud rate + * upon invalid value. + * + * Returns: + * Pointer to allocated sk_buff if successful; + * NULL otherwise. + */ +static struct sk_buff *alloc_set_baud_rate_cmd(struct uart_info *uart_info, + int *baud) +{ + struct sk_buff *skb; + u8 *h4; + struct bt_vs_set_baud_rate_cmd *cmd; + + skb = alloc_skb(sizeof(*cmd) + CG2900_SKB_RESERVE, GFP_ATOMIC); + if (!skb) { + dev_err(MAIN_DEV, + "alloc_set_baud_rate_cmd: Failed to alloc skb\n"); + return NULL; + } + skb_reserve(skb, CG2900_SKB_RESERVE); + + cmd = (struct bt_vs_set_baud_rate_cmd *)skb_put(skb, sizeof(cmd)); + + /* Create the Hci_Cmd_ST_Set_Uart_Baud_Rate packet */ + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_SET_BAUD_RATE); + cmd->plen = BT_PARAM_LEN(sizeof(cmd)); + + switch (*baud) { + case 57600: + cmd->baud_rate = CG2900_BAUD_RATE_57600; + break; + case 115200: + cmd->baud_rate = CG2900_BAUD_RATE_115200; + break; + case 230400: + cmd->baud_rate = CG2900_BAUD_RATE_230400; + break; + case 460800: + cmd->baud_rate = CG2900_BAUD_RATE_460800; + break; + case 921600: + cmd->baud_rate = CG2900_BAUD_RATE_921600; + break; + case 2000000: + cmd->baud_rate = CG2900_BAUD_RATE_2000000; + break; + case 3000000: + cmd->baud_rate = CG2900_BAUD_RATE_3000000; + break; + case 4000000: + cmd->baud_rate = CG2900_BAUD_RATE_4000000; + break; + default: + dev_err(MAIN_DEV, + "Invalid speed requested (%d), using 115200 bps " + "instead\n", *baud); + cmd->baud_rate = CG2900_BAUD_RATE_115200; + *baud = 115200; + break; + }; + + h4 = skb_push(skb, HCI_H4_SIZE); + *h4 = HCI_BT_CMD_H4_CHANNEL; + + return skb; +} + +/** + * work_do_transmit() - Transmit data packet to connectivity controller over UART. + * @work: Pointer to work info structure. Contains uart_info structure + * pointer. + */ +static void work_do_transmit(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + + kfree(current_work); + + spin_lock_bh(&(uart_info->transmission_lock)); + /* Mark that there is an ongoing transfer. */ + uart_info->tx_in_progress = true; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + /* Wake up the chip and transport. */ + wake_up_chip(uart_info); + + (void)hci_uart_tx_wakeup(uart_info->hu); +} + +/** + * work_hw_deregistered() - Handle HW deregistered. + * @work: Reference to work data. + */ +static void work_hw_deregistered(struct work_struct *work) +{ + struct uart_work_struct *current_work; + struct uart_info *uart_info; + int err; + current_work = container_of(work, struct uart_work_struct, work); + uart_info = (struct uart_info *)current_work->data; + + err = cg2900_deregister_trans_driver(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, "Could not deregister UART from Core (%d)\n", + err); + + kfree(current_work); +} + +/** + * set_baud_rate() - Sets new baud rate for the UART. + * @hu: Pointer to hci_uart structure. + * @baud: New baud rate. + * + * This function first sends the HCI command + * Hci_Cmd_ST_Set_Uart_Baud_Rate. It then changes the baud rate in HW, and + * finally it waits for the Command Complete event for the + * Hci_Cmd_ST_Set_Uart_Baud_Rate command. + * + * Returns: + * 0 if there is no error. + * -EALREADY if baud rate change is already in progress. + * -EFAULT if one or more of the UART related structs is not allocated. + * -ENOMEM if skb allocation has failed. + * -EPERM if setting the new baud rate has failed. + * Errors from create_work_item. + */ +static int set_baud_rate(struct hci_uart *hu, int baud) +{ + int err = 0; + struct sk_buff *skb; + int old_baud_rate; + struct uart_info *uart_info = + (struct uart_info *)dev_get_drvdata(hu->proto->dev); + + dev_dbg(MAIN_DEV, "set_baud_rate (%d baud)\n", baud); + + if (uart_info->baud_rate_state != BAUD_IDLE) { + dev_err(MAIN_DEV, + "Trying to set new baud rate before old setting " + "is finished\n"); + return -EALREADY; + } + + /* + * Wait some time to be sure that any RX process has finished (which + * flows on RTS in the end) before flowing off the RTS. + */ + schedule_timeout_killable(msecs_to_jiffies(UART_RX_TIMEOUT)); + hci_uart_flow_ctrl(uart_info->hu, FLOW_OFF); + + /* + * Store old baud rate so that we can restore it if something goes + * wrong. + */ + old_baud_rate = uart_info->baud_rate; + + skb = alloc_set_baud_rate_cmd(uart_info, &baud); + if (!skb) { + dev_err(MAIN_DEV, "alloc_set_baud_rate_cmd failed\n"); + return -ENOMEM; + } + + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_START\n"); + uart_info->baud_rate_state = BAUD_START; + uart_info->baud_rate = baud; + + /* Queue the sk_buffer... */ + skb_queue_tail(&uart_info->tx_queue, skb); + + /* ... and call the common UART TX function */ + err = create_work_item(uart_info, work_do_transmit); + if (err) { + dev_err(MAIN_DEV, + "Failed to send change baud rate cmd, freeing skb\n"); + skb = skb_dequeue_tail(&uart_info->tx_queue); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + uart_info->baud_rate = old_baud_rate; + kfree_skb(skb); + return err; + } + + dev_dbg(MAIN_DEV, "Set baud rate cmd scheduled for sending\n"); + + /* + * Now wait for the command complete. + * It will come at the new baudrate. + */ + wait_event_timeout(uart_wait_queue, + ((BAUD_SUCCESS == uart_info->baud_rate_state) || + (BAUD_FAIL == uart_info->baud_rate_state)), + msecs_to_jiffies(UART_RESP_TIMEOUT)); + if (BAUD_SUCCESS == uart_info->baud_rate_state) + dev_info(MAIN_DEV, "Baud rate changed to %d baud\n", baud); + else { + dev_err(MAIN_DEV, "Failed to set new baud rate (%d)\n", + uart_info->baud_rate_state); + err = -EPERM; + } + + /* Finally flush the TTY so we are sure that is no bad data there */ + hci_uart_flush_buffer(hu); + dev_dbg(MAIN_DEV, "Flushing TTY after baud rate change\n"); + /* Finished. Set state to IDLE */ + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + + return err; +} + +/** + * uart_write() - Transmit data to CG2900 over UART. + * @dev: Transport device information. + * @skb: SK buffer to transmit. + * + * Returns: + * 0 if there is no error. + * Errors from create_work_item. + */ +static int uart_write(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + int err; + struct uart_info *uart_info = dev_get_drvdata(dev->dev); + + if (uart_debug) + dev_dbg(MAIN_DEV, "uart_write: data len = %d\n", skb->len); + + /* Queue the sk_buffer... */ + skb_queue_tail(&uart_info->tx_queue, skb); + + /* ...and start TX operation */ + + err = create_work_item(uart_info, work_do_transmit); + if (err) + dev_err(MAIN_DEV, + "Failed to create work item (%d) uart_tty_wakeup\n", + err); + + return err; +} + +/** + * uart_open() - Open the CG2900 UART for data transfers. + * @dev: Transport device information. + * + * Returns: + * 0 if there is no error, + * -EACCES if write to transport failed, + * -EIO if chip did not answer to commands. + * Errors from set_baud_rate. + */ +static int uart_open(struct cg2900_chip_dev *dev) +{ + u8 *h4; + struct sk_buff *skb; + struct hci_command_hdr *cmd; + struct uart_info *uart_info = dev_get_drvdata(dev->dev); + + /* + * Chip has just been started up. It has a system to autodetect + * exact baud rate and transport to use. There are only a few commands + * it will recognize and HCI Reset is one of them. + * We therefore start with sending that before actually changing + * baud rate. + * + * Create the Hci_Reset packet + */ + + skb = alloc_skb(sizeof(*cmd) + HCI_H4_SIZE, GFP_ATOMIC); + if (!skb) { + dev_err(MAIN_DEV, "Couldn't allocate sk_buff with length %d\n", + sizeof(*cmd)); + return -EACCES; + } + skb_reserve(skb, HCI_H4_SIZE); + cmd = (struct hci_command_hdr *)skb_put(skb, sizeof(*cmd)); + cmd->opcode = cpu_to_le16(HCI_OP_RESET); + cmd->plen = 0; /* No parameters for HCI reset */ + + h4 = skb_push(skb, HCI_H4_SIZE); + *h4 = HCI_BT_CMD_H4_CHANNEL; + + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_SENDING_RESET\n"); + uart_info->baud_rate_state = BAUD_SENDING_RESET; + dev_dbg(MAIN_DEV, "Sending HCI reset before baud rate change\n"); + + + /* Queue the sk_buffer... */ + skb_queue_tail(&uart_info->tx_queue, skb); + + (void)hci_uart_tx_wakeup(uart_info->hu); + + /* + * Wait for command complete. If error, exit without changing + * baud rate. + */ + wait_event_timeout(uart_wait_queue, + BAUD_IDLE == uart_info->baud_rate_state, + msecs_to_jiffies(UART_RESP_TIMEOUT)); + if (BAUD_IDLE != uart_info->baud_rate_state) { + dev_err(MAIN_DEV, "Failed to send HCI Reset\n"); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + return -EIO; + } + + /* Just return if there will be no change of baud rate */ + if (uart_default_baud != uart_high_baud) + return set_baud_rate(uart_info->hu, uart_high_baud); + else + return 0; +} + +/** + * uart_set_chip_power() - Enable or disable the CG2900. + * @chip_on: true if chip shall be enabled, false otherwise. + */ +static void uart_set_chip_power(struct cg2900_chip_dev *dev, bool chip_on) +{ + int uart_baudrate = uart_default_baud; + struct cg2900_platform_data *pf_data; + struct uart_info *uart_info; + + pf_data = dev_get_platdata(dev->dev); + uart_info = dev_get_drvdata(dev->dev); + + dev_info(MAIN_DEV, "Set chip power: %s\n", + (chip_on ? "ENABLE" : "DISABLE")); + + /* Cancel any ongoing works.*/ + cancel_work_sync(&uart_info->wakeup_work.work); + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + mutex_lock(&uart_info->sleep_state_lock); + + if (!uart_info->hu) { + dev_err(MAIN_DEV, "Hci uart struct is not allocated\n"); + goto unlock; + } + + if (chip_on) { + if (uart_info->sleep_state != CHIP_POWERED_DOWN) { + dev_err(MAIN_DEV, "Chip is already powered up (%d)\n", + uart_info->sleep_state); + goto unlock; + } + + if (cg2900_enable_regulator(uart_info)) + goto unlock; + + if (pf_data->enable_chip) { + pf_data->enable_chip(dev); + dev_dbg(MAIN_DEV, "New sleep_state: CHIP_AWAKE\n"); + uart_info->sleep_state = CHIP_AWAKE; + } + + (void)hci_uart_set_baudrate(uart_info->hu, uart_baudrate); + + hci_uart_flow_ctrl(uart_info->hu, FLOW_ON); + hci_uart_set_break(uart_info->hu, BREAK_OFF); + } else { + /* Turn off the chip.*/ + switch (uart_info->sleep_state) { + case CHIP_AWAKE: + break; + case CHIP_FALLING_ASLEEP: + hci_uart_set_break(uart_info->hu, BREAK_OFF); + break; + case CHIP_SUSPENDED: + case CHIP_ASLEEP: + unset_cts_irq(uart_info); + enable_uart_pins(uart_info); + break; + default: + break; + } + + if (pf_data->disable_chip) { + pf_data->disable_chip(dev); + dev_dbg(MAIN_DEV, + "New sleep_state: CHIP_POWERED_DOWN\n"); + uart_info->sleep_state = CHIP_POWERED_DOWN; + } + cg2900_disable_regulator(uart_info); + /* + * Setting baud rate to 0 will tell UART driver to shut off its + * clocks. + */ + (void)hci_uart_set_baudrate(uart_info->hu, ZERO_BAUD_RATE); + } + +unlock: + mutex_unlock(&(uart_info->sleep_state_lock)); +} + +/** + * uart_chip_startup_finished() - CG2900 startup finished. + * @dev: Transport device information. + */ +static void uart_chip_startup_finished(struct cg2900_chip_dev *dev) +{ + struct uart_info *uart_info = dev_get_drvdata(dev->dev); + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + /* Schedule work to put the chip and transport to sleep. */ + if (timeout_jiffies) + queue_delayed_work(uart_info->wq, &uart_info->sleep_work.work, + timeout_jiffies); +} +/** + * uart_close() - Close the CG2900 UART for data transfers. + * @dev: Transport device information. + * + * Returns: + * 0 if there is no error. + */ +static int uart_close(struct cg2900_chip_dev *dev) +{ + /* The chip is already shut down. Power off the chip. */ + uart_set_chip_power(dev, false); + return 0; +} + +/** + * send_skb_to_core() - Sends packet received from UART to CG2900 Core. + * @skb: Received data packet. + * + * This function checks if UART is waiting for Command complete event, + * see set_baud_rate. + * If it is waiting it checks if it is the expected packet and the status. + * If not is passes the packet to CG2900 Core. + */ +static void send_skb_to_core(struct uart_info *uart_info, struct sk_buff *skb) +{ + u8 status; + + if (!skb) { + dev_err(MAIN_DEV, "send_skb_to_core: Received NULL as skb\n"); + return; + } + + if (BAUD_WAITING == uart_info->baud_rate_state) { + /* + * Should only really be one packet received now: + * the CmdComplete for the SetBaudrate command + * Let's see if this is the packet we are waiting for. + */ + if (!is_bt_cmd_complete_no_param(skb, + CG2900_BT_OP_VS_SET_BAUD_RATE, &status)) { + /* + * Received other event. Should not really happen, + * but pass the data to CG2900 Core anyway. + */ + dev_dbg(MAIN_DEV, "Sending packet to CG2900 Core while " + "waiting for BaudRate CmdComplete\n"); + uart_info->chip_dev.c_cb.data_from_chip + (&uart_info->chip_dev, skb); + return; + } + + /* + * We have received complete event for our baud rate + * change command + */ + if (HCI_BT_ERROR_NO_ERROR == status) { + dev_dbg(MAIN_DEV, "Received baud rate change complete " + "event OK\n"); + dev_dbg(MAIN_DEV, + "New baud_rate_state: BAUD_SUCCESS\n"); + uart_info->baud_rate_state = BAUD_SUCCESS; + } else { + dev_err(MAIN_DEV, + "Received baud rate change complete event " + "with status 0x%X\n", status); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_FAIL\n"); + uart_info->baud_rate_state = BAUD_FAIL; + } + wake_up_all(&uart_wait_queue); + kfree_skb(skb); + } else if (BAUD_SENDING_RESET == uart_info->baud_rate_state) { + /* + * Should only really be one packet received now: + * the CmdComplete for the Reset command + * Let's see if this is the packet we are waiting for. + */ + if (!is_bt_cmd_complete_no_param(skb, HCI_OP_RESET, &status)) { + /* + * Received other event. Should not really happen, + * but pass the data to CG2900 Core anyway. + */ + dev_dbg(MAIN_DEV, "Sending packet to CG2900 Core while " + "waiting for Reset CmdComplete\n"); + uart_info->chip_dev.c_cb.data_from_chip + (&uart_info->chip_dev, skb); + return; + } + + /* + * We have received complete event for our baud rate + * change command + */ + if (HCI_BT_ERROR_NO_ERROR == status) { + dev_dbg(MAIN_DEV, + "Received HCI reset complete event OK\n"); + /* + * Go back to BAUD_IDLE since this was not really + * baud rate change but just a preparation of the chip + * to be ready to receive commands. + */ + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_IDLE\n"); + uart_info->baud_rate_state = BAUD_IDLE; + } else { + dev_err(MAIN_DEV, + "Received HCI reset complete event with " + "status 0x%X", status); + dev_dbg(MAIN_DEV, "New baud_rate_state: BAUD_FAIL\n"); + uart_info->baud_rate_state = BAUD_FAIL; + } + wake_up_all(&uart_wait_queue); + kfree_skb(skb); + } else { + /* Just pass data to CG2900 Core */ + uart_info->chip_dev.c_cb.data_from_chip + (&uart_info->chip_dev, skb); + } +} + +/** + * check_data_len() - Check number of bytes to receive. + * @len: Number of bytes left to receive. + */ +static void check_data_len(struct uart_info *uart_info, int len) +{ + /* First get number of bytes left in the sk_buffer */ + register int room = skb_tailroom(uart_info->rx_skb); + + if (!len) { + /* No data left to receive. Transmit to CG2900 Core */ + send_skb_to_core(uart_info, uart_info->rx_skb); + } else if (len > room) { + dev_err(MAIN_DEV, "Data length is too large (%d > %d)\n", + len, room); + kfree_skb(uart_info->rx_skb); + } else { + /* + * "Normal" case. Switch to data receiving state and store + * data length. + */ + uart_info->rx_state = W4_DATA; + uart_info->rx_count = len; + return; + } + + uart_info->rx_state = W4_PACKET_TYPE; + uart_info->rx_skb = NULL; + uart_info->rx_count = 0; +} + +/** + * work_restart_sleep() - Cancel pending sleep_work, wake-up driver and + * schedule new sleep_work in a work context. + * @work: work which needs to be done. + */ +static void work_restart_sleep(struct work_struct *work) +{ + struct uart_work_struct *current_work = + container_of(work, struct uart_work_struct, work); + struct uart_info *uart_info = (struct uart_info *)current_work->data; + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + spin_lock_bh(&(uart_info->transmission_lock)); + uart_info->rx_in_progress = false; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work_sync(&uart_info->sleep_work.work); + + wake_up_chip(uart_info); + + spin_lock_bh(&(uart_info->transmission_lock)); + /* + * If there are no ongoing transfers schedule the sleep work. + */ + if (!(uart_info->tx_in_progress) && timeout_jiffies) + queue_delayed_work(uart_info->wq, + &uart_info->sleep_work.work, + timeout_jiffies); + spin_unlock_bh(&(uart_info->transmission_lock)); +} + +/** + * cg2900_hu_receive() - Handles received UART data. + * @data: Data received + * @count: Number of bytes received + * + * The cg2900_hu_receive() function handles received UART data and puts it + * together to one complete packet. + * + * Returns: + * Number of bytes not handled, i.e. 0 = no error. + */ +static int cg2900_hu_receive(struct hci_uart *hu, + void *data, int count) +{ + const u8 *r_ptr; + u8 *w_ptr; + int len; + struct hci_event_hdr *evt; + struct hci_acl_hdr *acl; + union fm_leg_evt_or_irq *fm; + struct gnss_hci_hdr *gnss; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + u8 *tmp; + + r_ptr = (const u8 *)data; + + spin_lock_bh(&(uart_info->transmission_lock)); + /* Mark that there is an ongoing transfer. */ + uart_info->rx_in_progress = true; + spin_unlock_bh(&(uart_info->transmission_lock)); + + /* Cancel pending sleep work if there is any. */ + cancel_delayed_work(&uart_info->sleep_work.work); + + if (uart_debug) + print_hex_dump_bytes(NAME " RX:\t", DUMP_PREFIX_NONE, + data, count); + + /* Continue while there is data left to handle */ + while (count) { + /* + * If we have already received a packet we know how many bytes + * there are left. + */ + if (!uart_info->rx_count) + goto check_h4_header; + + /* First copy received data into the skb_rx */ + len = min_t(unsigned int, uart_info->rx_count, count); + memcpy(skb_put(uart_info->rx_skb, len), r_ptr, len); + /* Update counters from the length and step the data pointer */ + uart_info->rx_count -= len; + count -= len; + r_ptr += len; + + if (uart_info->rx_count) + /* + * More data to receive to current packet. Break and + * wait for next data on the UART. + */ + break; + + /* Handle the different states */ + tmp = uart_info->rx_skb->data + CG2900_SKB_RESERVE; + switch (uart_info->rx_state) { + case W4_DATA: + /* + * Whole data packet has been received. + * Transmit it to CG2900 Core. + */ + send_skb_to_core(uart_info, uart_info->rx_skb); + + uart_info->rx_state = W4_PACKET_TYPE; + uart_info->rx_skb = NULL; + continue; + + case W4_EVENT_HDR: + evt = (struct hci_event_hdr *)tmp; + check_data_len(uart_info, evt->plen); + /* Header read. Continue with next bytes */ + continue; + + case W4_ACL_HDR: + acl = (struct hci_acl_hdr *)tmp; + check_data_len(uart_info, le16_to_cpu(acl->dlen)); + /* Header read. Continue with next bytes */ + continue; + + case W4_FM_RADIO_HDR: + fm = (union fm_leg_evt_or_irq *)tmp; + check_data_len(uart_info, fm->param_length); + /* Header read. Continue with next bytes */ + continue; + + case W4_GNSS_HDR: + gnss = (struct gnss_hci_hdr *)tmp; + check_data_len(uart_info, le16_to_cpu(gnss->plen)); + /* Header read. Continue with next bytes */ + continue; + + default: + dev_err(MAIN_DEV, + "Bad state indicating memory overwrite " + "(0x%X)\n", (u8)(uart_info->rx_state)); + break; + } + +check_h4_header: + /* Check which H:4 packet this is and update RX states */ + if (*r_ptr == HCI_BT_EVT_H4_CHANNEL) { + uart_info->rx_state = W4_EVENT_HDR; + uart_info->rx_count = HCI_BT_EVT_HDR_SIZE; + } else if (*r_ptr == HCI_BT_ACL_H4_CHANNEL) { + uart_info->rx_state = W4_ACL_HDR; + uart_info->rx_count = HCI_BT_ACL_HDR_SIZE; + } else if (*r_ptr == HCI_FM_RADIO_H4_CHANNEL) { + uart_info->rx_state = W4_FM_RADIO_HDR; + uart_info->rx_count = HCI_FM_RADIO_HDR_SIZE; + } else if (*r_ptr == HCI_GNSS_H4_CHANNEL) { + uart_info->rx_state = W4_GNSS_HDR; + uart_info->rx_count = HCI_GNSS_HDR_SIZE; + } else { + dev_err(MAIN_DEV, "Unknown HCI packet type 0x%X\n", + (u8)*r_ptr); + r_ptr++; + count--; + continue; + } + + /* + * Allocate packet. We do not yet know the size and therefore + * allocate max size. + */ + uart_info->rx_skb = alloc_rx_skb(RX_SKB_MAX_SIZE, GFP_ATOMIC); + if (!uart_info->rx_skb) { + dev_err(MAIN_DEV, + "Can't allocate memory for new packet\n"); + uart_info->rx_state = W4_PACKET_TYPE; + uart_info->rx_count = 0; + + spin_lock_bh(&(uart_info->transmission_lock)); + uart_info->rx_in_progress = false; + spin_unlock_bh(&(uart_info->transmission_lock)); + return 0; + } + + /* Write the H:4 header first in the sk_buffer */ + w_ptr = skb_put(uart_info->rx_skb, 1); + *w_ptr = *r_ptr; + + /* First byte (H4 header) read. Goto next byte */ + r_ptr++; + count--; + } + + (void)queue_work(uart_info->wq, &uart_info->restart_sleep_work.work); + + return count; +} + +/** + * cg2900_hu_open() - Called when UART line discipline changed to N_HCI. + * @hu: Pointer to associated Hci uart structure. + * + * Returns: + * 0 if there is no error. + * Errors from cg2900_register_trans_driver. + */ +static int cg2900_hu_open(struct hci_uart *hu) +{ + int err; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + + if (!uart_info) + return -EACCES; + + dev_info(MAIN_DEV, "UART opened\n"); + + skb_queue_head_init(&uart_info->tx_queue); + + uart_info->hu = hu; + + /* Tell CG2900 Core that UART is connected */ + err = cg2900_register_trans_driver(&uart_info->chip_dev); + if (err) + dev_err(MAIN_DEV, "Could not register transport driver (%d)\n", + err); + + if (hu->tty->ops->tiocmget && hu->tty->ops->break_ctl) + uart_info->sleep_allowed = true; + else { + dev_err(MAIN_DEV, "Sleep mode not available\n"); + uart_info->sleep_allowed = false; + } + + return err; + +} + +/** + * cg2900_hu_close() - Close UART tty. + * @hu: Pointer to associated hci_uart structure. + * + * The uart_tty_close() function is called when the line discipline is changed + * to something else, the TTY is closed, or the TTY detects a hangup. + */ +static int cg2900_hu_close(struct hci_uart *hu) +{ + int err; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + + + BUG_ON(!uart_info); + BUG_ON(!uart_info->wq); + + /* Purge any stored sk_buffers */ + skb_queue_purge(&uart_info->tx_queue); + if (uart_info->rx_skb) { + kfree_skb(uart_info->rx_skb); + uart_info->rx_skb = NULL; + } + + dev_info(MAIN_DEV, "UART closed\n"); + err = create_work_item(uart_info, work_hw_deregistered); + if (err) + dev_err(MAIN_DEV, "Failed to create work item (%d) " + "work_hw_deregistered\n", err); + + uart_info->hu = NULL; + + return 0; +} + +/** + * cg2900_hu_dequeue() - Get new skbuff. + * @hu: Pointer to associated hci_uart structure. + * + * The uart_tty_close() function is called when the line discipline is changed + * to something else, the TTY is closed, or the TTY detects a hangup. + */ +static struct sk_buff *cg2900_hu_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb; + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + unsigned long timeout_jiffies = get_sleep_timeout(uart_info); + + spin_lock_bh(&(uart_info->transmission_lock)); + + skb = skb_dequeue(&uart_info->tx_queue); + + if (!skb) + uart_info->tx_in_progress = false; + + /* + * If there are no ongoing transfers schedule the sleep work. + */ + if (!(uart_info->rx_in_progress) && timeout_jiffies && !skb) + queue_delayed_work(uart_info->wq, + &uart_info->sleep_work.work, + timeout_jiffies); + + spin_unlock_bh(&(uart_info->transmission_lock)); + + if (BAUD_SENDING == uart_info->baud_rate_state && !skb) + finish_setting_baud_rate(hu); + /* + * If it's set baud rate cmd set correct baud state and after + * sending is finished inform the tty driver about the new + * baud rate. + */ + if ((BAUD_START == uart_info->baud_rate_state) && + skb && (is_set_baud_rate_cmd(skb->data))) { + dev_dbg(MAIN_DEV, "UART set baud rate cmd found\n"); + uart_info->baud_rate_state = BAUD_SENDING; + } + + if (uart_debug && skb) + print_hex_dump_bytes(NAME " TX:\t", DUMP_PREFIX_NONE, + skb->data, skb->len); + + return skb; +} + +/** + * cg2900_hu_flush() - Flush buffers. + * @hu: Pointer to associated hci_uart structure. + * + */ +static int cg2900_hu_flush(struct hci_uart *hu) +{ + struct uart_info *uart_info = dev_get_drvdata(hu->proto->dev); + + dev_dbg(MAIN_DEV, "ui %p", uart_info); + skb_queue_purge(&uart_info->tx_queue); + return 0; +} + +/** + * cg2900_uart_probe() - Initialize CG2900 UART resources. + * @pdev: Platform device. + * + * This function initializes the module and registers to the UART framework. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * -ECHILD for failed work queue creation. + * Error codes generated by tty_register_ldisc. + */ +static int __devinit cg2900_uart_probe(struct platform_device *pdev) +{ + int err = 0; + struct uart_info *uart_info; + struct hci_uart_proto *p; + struct resource *resource; + + pr_debug("cg2900_uart_probe"); + + uart_info = kzalloc(sizeof(*uart_info), GFP_KERNEL); + if (!uart_info) { + pr_err("Couldn't allocate uart_info"); + return -ENOMEM; + } + + uart_info->sleep_state = CHIP_POWERED_DOWN; + mutex_init(&(uart_info->sleep_state_lock)); + + spin_lock_init(&(uart_info->transmission_lock)); + + uart_info->chip_dev.t_cb.open = uart_open; + uart_info->chip_dev.t_cb.close = uart_close; + uart_info->chip_dev.t_cb.write = uart_write; + uart_info->chip_dev.t_cb.set_chip_power = uart_set_chip_power; + uart_info->chip_dev.t_cb.chip_startup_finished = + uart_chip_startup_finished; + uart_info->chip_dev.pdev = pdev; + uart_info->chip_dev.dev = &pdev->dev; + uart_info->chip_dev.t_data = uart_info; + + resource = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "cts_irq"); + if (!resource) { + dev_err(&pdev->dev, "CTS IRQ does not exist\n"); + err = -EINVAL; + goto error_handling_free; + } + uart_info->cts_irq = resource->start; + + resource = platform_get_resource_byname(pdev, IORESOURCE_IO, + "cts_gpio"); + if (!resource) { + dev_err(&pdev->dev, "CTS GPIO does not exist\n"); + err = -EINVAL; + goto error_handling_free; + } + uart_info->cts_gpio = resource->start; + + /* Init UART TX work queue */ + uart_info->wq = create_singlethread_workqueue(UART_WQ_NAME); + if (!uart_info->wq) { + dev_err(MAIN_DEV, "Could not create workqueue\n"); + err = -ECHILD; /* No child processes */ + goto error_handling_free; + } + + /* Initialize sleep work data */ + uart_info->sleep_work.data = uart_info; + INIT_DELAYED_WORK(&uart_info->sleep_work.work, set_chip_sleep_mode); + + /* Initialize wake-up work data */ + uart_info->wakeup_work.data = uart_info; + INIT_WORK(&uart_info->wakeup_work.work, work_wake_up_chip); + + /* Initialize after_receive work data */ + uart_info->restart_sleep_work.data = uart_info; + INIT_WORK(&uart_info->restart_sleep_work.work, work_restart_sleep); + + uart_info->dev = &pdev->dev; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + dev_err(MAIN_DEV, "cg2900_uart_probe: Could not allocate p\n"); + goto error_handling_wq; + } + + p->dev = uart_info->dev; + p->id = HCI_UART_STE; + p->open = &cg2900_hu_open; + p->close = &cg2900_hu_close; + p->recv = &cg2900_hu_receive; + p->dequeue = &cg2900_hu_dequeue; + p->flush = &cg2900_hu_flush; + + dev_set_drvdata(uart_info->dev, (void *)uart_info); + + err = hci_uart_register_proto(p); + if (err) { + dev_err(MAIN_DEV, "cg2900_uart_probe: Can not register " + "protocol\n"); + kfree(p); + goto error_handling_wq; + } + + goto finished; + +error_handling_wq: + destroy_workqueue(uart_info->wq); +error_handling_free: + kfree(uart_info); + uart_info = NULL; +finished: + return err; +} + +/** + * cg2900_uart_remove() - Release CG2900 UART resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes generated by tty_unregister_ldisc. + */ +static int __devexit cg2900_uart_remove(struct platform_device *pdev) +{ + struct uart_info *uart_info = dev_get_drvdata(&pdev->dev); + + pr_debug("cg2900_uart_remove"); + + if (!uart_info) + return -ECHILD; + + if (uart_info->hu) + hci_uart_unregister_proto(uart_info->hu->proto); + + destroy_workqueue(uart_info->wq); + + dev_info(MAIN_DEV, "CG2900 UART removed\n"); + kfree(uart_info); + uart_info = NULL; + return 0; +} + +static struct platform_driver cg2900_uart_driver = { + .driver = { + .name = "cg2900-uart", + .owner = THIS_MODULE, + }, + .probe = cg2900_uart_probe, + .remove = __devexit_p(cg2900_uart_remove), +#ifdef CONFIG_PM + .suspend = cg2900_uart_suspend, + .resume = cg2900_uart_resume +#endif +}; + + +/** + * cg2900_uart_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_uart_init(void) +{ + pr_debug("cg2900_uart_init"); + return platform_driver_register(&cg2900_uart_driver); +} + +/** + * cg2900_uart_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_uart_exit(void) +{ + pr_debug("cg2900_uart_exit"); + platform_driver_unregister(&cg2900_uart_driver); +} + +module_init(cg2900_uart_init); +module_exit(cg2900_uart_exit); + +module_param(uart_default_baud, int, S_IRUGO); +MODULE_PARM_DESC(uart_default_baud, + "Default UART baud rate, e.g. 115200. If not set 115200 will " + "be used."); + +module_param(uart_high_baud, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(uart_high_baud, + "High speed UART baud rate, e.g. 4000000. If not set 3000000 " + "will be used."); + +module_param(uart_debug, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(uart_debug, "Enable/Disable debug. 0 means Debug disabled."); +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ST-Ericsson CG2900 UART Driver"); diff --git a/drivers/staging/cg2900/bluetooth/hci_ldisc.c b/drivers/staging/cg2900/bluetooth/hci_ldisc.c new file mode 100644 index 00000000000..d7ff9d419a1 --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/hci_ldisc.c @@ -0,0 +1,657 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * This file is a staging solution and shall be integrated into + * /drivers/bluetooth/hci_ldisc.c. + * + * Original hci_ldisc.c file: + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "hci_uart.h" + +#define VERSION "2.3" + +#define TTY_BREAK_ON (-1) +#define TTY_BREAK_OFF (0) + +static int reset; + +static struct hci_uart_proto *hup[HCI_UART_MAX_PROTO]; + +int cg2900_hci_uart_register_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (hup[p->id]) + return -EEXIST; + + hup[p->id] = p; + + return 0; +} + +int cg2900_hci_uart_unregister_proto(struct hci_uart_proto *p) +{ + if (p->id >= HCI_UART_MAX_PROTO) + return -EINVAL; + + if (!hup[p->id]) + return -EINVAL; + + hup[p->id] = NULL; + + return 0; +} + +static struct hci_uart_proto *hci_uart_get_proto(unsigned int id) +{ + if (id >= HCI_UART_MAX_PROTO) + return NULL; + + return hup[id]; +} + +static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type) +{ + struct hci_dev *hdev = hu->hdev; + + if (!hdev) + return; + + /* Update HCI stat counters */ + switch (pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } +} + +static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb = hu->tx_skb; + + if (!skb) + skb = hu->proto->dequeue(hu); + else + hu->tx_skb = NULL; + + return skb; +} + +int cg2900_hci_uart_tx_wakeup(struct hci_uart *hu) +{ + struct tty_struct *tty = hu->tty; + struct hci_dev *hdev = hu->hdev; + struct sk_buff *skb; + + if (test_and_set_bit(HCI_UART_SENDING, &hu->tx_state)) { + set_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + return 0; + } + + BT_DBG(""); + +restart: + clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + + while ((skb = hci_uart_dequeue(hu))) { + int len; + + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + len = tty->ops->write(tty, skb->data, skb->len); + if (hdev) + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len) { + hu->tx_skb = skb; + break; + } + + hci_uart_tx_complete(hu, bt_cb(skb)->pkt_type); + kfree_skb(skb); + } + + if (test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state)) + goto restart; + + clear_bit(HCI_UART_SENDING, &hu->tx_state); + return 0; +} + +int cg2900_hci_uart_set_break(struct hci_uart *hu, bool break_on) +{ + struct tty_struct *tty = hu->tty; + int state = TTY_BREAK_OFF; + + if (break_on) + state = TTY_BREAK_ON; + + if (tty->ops->break_ctl) + return tty->ops->break_ctl(tty, state); + else + return -EOPNOTSUPP; +} + +void cg2900_hci_uart_flow_ctrl(struct hci_uart *hu, bool flow_on) +{ + if (flow_on) + tty_unthrottle(hu->tty); + else + tty_throttle(hu->tty); +} + +int cg2900_hci_uart_set_baudrate(struct hci_uart *hu, int baud) +{ + struct ktermios old_termios; + struct tty_struct *tty = hu->tty; + + if (!tty->ops->set_termios) + return -EOPNOTSUPP; + + mutex_lock(&(tty->termios_mutex)); + /* Start by storing the old termios. */ + memcpy(&old_termios, tty->termios, sizeof(old_termios)); + + tty_encode_baud_rate(tty, baud, baud); + + /* Finally inform the driver */ + tty->ops->set_termios(tty, &old_termios); + + mutex_unlock(&(tty->termios_mutex)); + + return 0; +} + +int cg2900_hci_uart_tiocmget(struct hci_uart *hu) +{ + struct tty_struct *tty = hu->tty; + + if (!tty->ops->tiocmget || !hu->fd) + return -EOPNOTSUPP; + + return tty->ops->tiocmget(tty, hu->fd); +} + +void cg2900_hci_uart_flush_buffer(struct hci_uart *hu) +{ + tty_driver_flush_buffer(hu->tty); +} + +int cg2900_hci_uart_chars_in_buffer(struct hci_uart *hu) +{ + return tty_chars_in_buffer(hu->tty); +} + +/* ------- Interface to HCI layer ------ */ +/* Initialize device */ +static int hci_uart_open(struct hci_dev *hdev) +{ + BT_DBG("%s %p", hdev->name, hdev); + + /* Nothing to do for UART driver */ + + set_bit(HCI_RUNNING, &hdev->flags); + + return 0; +} + +/* Reset device */ +static int hci_uart_flush(struct hci_dev *hdev) +{ + struct hci_uart *hu = (struct hci_uart *) hdev->driver_data; + struct tty_struct *tty = hu->tty; + + BT_DBG("hdev %p tty %p", hdev, tty); + + if (hu->tx_skb) { + kfree_skb(hu->tx_skb); hu->tx_skb = NULL; + } + + /* Flush any pending characters in the driver and discipline. */ + tty_ldisc_flush(tty); + tty_driver_flush_buffer(tty); + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hu->proto->flush(hu); + + return 0; +} + +/* Close device */ +static int hci_uart_close(struct hci_dev *hdev) +{ + BT_DBG("hdev %p", hdev); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + hci_uart_flush(hdev); + hdev->flush = NULL; + return 0; +} + +/* Send frames from HCI layer */ +static int hci_uart_send_frame(struct sk_buff *skb) +{ + struct hci_dev* hdev = (struct hci_dev *) skb->dev; + struct hci_uart *hu; + + if (!hdev) { + BT_ERR("Frame for unknown device (hdev=NULL)"); + return -ENODEV; + } + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; + + hu = (struct hci_uart *) hdev->driver_data; + + BT_DBG("%s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type, + skb->len); + + hu->proto->enqueue(hu, skb); + + hci_uart_tx_wakeup(hu); + + return 0; +} + +static void hci_uart_destruct(struct hci_dev *hdev) +{ + if (!hdev) + return; + + BT_DBG("%s", hdev->name); + kfree(hdev->driver_data); +} + +/* ------ LDISC part ------ */ +/* hci_uart_tty_open + * + * Called when line discipline changed to HCI_UART. + * + * Arguments: + * tty pointer to tty info structure + * Return Value: + * 0 if success, otherwise error code + */ +static int hci_uart_tty_open(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *) tty->disc_data; + + BT_DBG("tty %p", tty); + + /* FIXME: This btw is bogus, nothing requires the old ldisc to clear + the pointer */ + if (hu) + return -EEXIST; + + /* Error if the tty has no write op instead of leaving an exploitable + hole */ + if (tty->ops->write == NULL) + return -EOPNOTSUPP; + + hu = kzalloc(sizeof(struct hci_uart), GFP_KERNEL); + if (!hu) { + BT_ERR("Can't allocate control structure"); + return -ENFILE; + } + + tty->disc_data = hu; + hu->tty = tty; + tty->receive_room = 65536; + + spin_lock_init(&hu->rx_lock); + + /* Flush any pending characters in the driver and line discipline. */ + + /* FIXME: why is this needed. Note don't use ldisc_ref here as the + open path is before the ldisc is referencable */ + + if (tty->ldisc->ops->flush_buffer) + tty->ldisc->ops->flush_buffer(tty); + tty_driver_flush_buffer(tty); + + return 0; +} + +/* hci_uart_tty_close() + * + * Called when the line discipline is changed to something + * else, the tty is closed, or the tty detects a hangup. + */ +static void hci_uart_tty_close(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG("tty %p", tty); + + /* Detach from the tty */ + tty->disc_data = NULL; + + if (hu) { + struct hci_dev *hdev = hu->hdev; + + if (hdev) + hci_uart_close(hdev); + + if (test_and_clear_bit(HCI_UART_PROTO_SET, &hu->flags)) { + hu->proto->close(hu); + if (hdev) { + hci_unregister_dev(hdev); + hci_free_dev(hdev); + } + } + } +} + +/* hci_uart_tty_wakeup() + * + * Callback for transmit wakeup. Called when low level + * device driver can accept more send data. + * + * Arguments: tty pointer to associated tty instance data + * Return Value: None + */ +static void hci_uart_tty_wakeup(struct tty_struct *tty) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + BT_DBG(""); + + if (!hu) + return; + + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + if (tty != hu->tty) + return; + + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + hci_uart_tx_wakeup(hu); +} + +/* hci_uart_tty_receive() + * + * Called by tty low level driver when receive data is + * available. + * + * Arguments: tty pointer to tty isntance data + * data pointer to received data + * flags pointer to flags for data + * count count of received data in bytes + * + * Return Value: None + */ +static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, + char *flags, int count) +{ + struct hci_uart *hu = (void *)tty->disc_data; + + if (!hu || tty != hu->tty) + return; + + if (!test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return; + + spin_lock(&hu->rx_lock); + hu->proto->recv(hu, (void *) data, count); + if (hu->hdev) + hu->hdev->stat.byte_rx += count; + spin_unlock(&hu->rx_lock); + + tty_unthrottle(tty); +} + +static int hci_uart_register_dev(struct hci_uart *hu) +{ + struct hci_dev *hdev; + + BT_DBG(""); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + hu->hdev = hdev; + + hdev->bus = HCI_UART; + hdev->driver_data = hu; + + hdev->open = hci_uart_open; + hdev->close = hci_uart_close; + hdev->flush = hci_uart_flush; + hdev->send = hci_uart_send_frame; + hdev->destruct = hci_uart_destruct; + + hdev->owner = THIS_MODULE; + + if (!reset) + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + + if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags)) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + +static int hci_uart_set_proto(struct hci_uart *hu, int id) +{ + struct hci_uart_proto *p; + int err; + + p = hci_uart_get_proto(id); + if (!p) + return -EPROTONOSUPPORT; + + hu->proto = p; + + err = p->open(hu); + if (err) + return err; + + /* + * Protocol might register hdev by itself. + * In that case, there is no need to register it here. + */ + if (!hu->proto->register_hci_dev) + return 0; + + err = hci_uart_register_dev(hu); + if (err) { + p->close(hu); + return err; + } + + return 0; +} + +/* hci_uart_tty_ioctl() + * + * Process IOCTL system call for the tty device. + * + * Arguments: + * + * tty pointer to tty instance data + * file pointer to open file object for device + * cmd IOCTL command code + * arg argument for IOCTL call (cmd dependent) + * + * Return Value: Command dependent + */ +static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + struct hci_uart *hu = (void *)tty->disc_data; + int err = 0; + + BT_DBG(""); + + /* Verify the status of the device */ + if (!hu) + return -EBADF; + + switch (cmd) { + case HCIUARTSETPROTO: + if (!test_and_set_bit(HCI_UART_PROTO_SET, &hu->flags)) { + err = hci_uart_set_proto(hu, arg); + if (err) { + clear_bit(HCI_UART_PROTO_SET, &hu->flags); + return err; + } + /* Keep file descriptor.*/ + hu->fd = file; + } else + return -EBUSY; + break; + + case HCIUARTGETPROTO: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return hu->proto->id; + return -EUNATCH; + + case HCIUARTGETDEVICE: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) { + if (hu->hdev) + return hu->hdev->id; + else + return -ENOMSG; + } + return -EUNATCH; + + case HCIUARTSETFLAGS: + if (test_bit(HCI_UART_PROTO_SET, &hu->flags)) + return -EBUSY; + hu->hdev_flags = arg; + break; + + case HCIUARTGETFLAGS: + return hu->hdev_flags; + + default: + err = n_tty_ioctl_helper(tty, file, cmd, arg); + break; + }; + + return err; +} + +/* + * We don't provide read/write/poll interface for user space. + */ +static ssize_t hci_uart_tty_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + return 0; +} + +static ssize_t hci_uart_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *data, size_t count) +{ + return 0; +} + +static unsigned int hci_uart_tty_poll(struct tty_struct *tty, + struct file *filp, poll_table *wait) +{ + return 0; +} + +static int __init cg2900_hci_uart_init(void) +{ + static struct tty_ldisc_ops hci_uart_ldisc; + int err; + + BT_INFO("HCI UART driver ver %s", VERSION); + + /* Register the tty discipline */ + + memset(&hci_uart_ldisc, 0, sizeof(hci_uart_ldisc)); + hci_uart_ldisc.magic = TTY_LDISC_MAGIC; + hci_uart_ldisc.name = "n_cg2900_hci"; + hci_uart_ldisc.open = hci_uart_tty_open; + hci_uart_ldisc.close = hci_uart_tty_close; + hci_uart_ldisc.read = hci_uart_tty_read; + hci_uart_ldisc.write = hci_uart_tty_write; + hci_uart_ldisc.ioctl = hci_uart_tty_ioctl; + hci_uart_ldisc.poll = hci_uart_tty_poll; + hci_uart_ldisc.receive_buf = hci_uart_tty_receive; + hci_uart_ldisc.write_wakeup = hci_uart_tty_wakeup; + hci_uart_ldisc.owner = THIS_MODULE; + + err = tty_register_ldisc(N_CG2900_HCI, &hci_uart_ldisc); + if (err) { + BT_ERR("HCI line discipline registration failed. (%d)", err); + return err; + } + + return 0; +} + +static void __exit cg2900_hci_uart_exit(void) +{ + int err; + + /* Release tty registration of line discipline */ + err = tty_unregister_ldisc(N_CG2900_HCI); + if (err) + BT_ERR("Can't unregister HCI line discipline (%d)", err); +} + +module_init(cg2900_hci_uart_init); +module_exit(cg2900_hci_uart_exit); + +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl "); +MODULE_DESCRIPTION("CG2900 Staging Bluetooth HCI UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_CG2900_HCI); diff --git a/drivers/staging/cg2900/bluetooth/hci_uart.h b/drivers/staging/cg2900/bluetooth/hci_uart.h new file mode 100644 index 00000000000..23a69519ccd --- /dev/null +++ b/drivers/staging/cg2900/bluetooth/hci_uart.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * This file is a staging solution and shall be integrated into + * /drivers/bluetooth/hci_uart.h. + * + * Original hci_uart.h file: + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + */ + +/* + * Staging CG2900 Bluetooth HCI UART. Will be replaced by normal N_HCI when + * moved to normal driver folder. + */ +#ifndef N_CG2900_HCI +#define N_CG2900_HCI 23 +#endif /* N_CG2900_HCI */ + +/* Ioctls */ +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) +#define HCIUARTGETDEVICE _IOR('U', 202, int) +#define HCIUARTSETFLAGS _IOW('U', 203, int) +#define HCIUARTGETFLAGS _IOR('U', 204, int) + +/* UART protocols */ +#define HCI_UART_MAX_PROTO 7 + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_3WIRE 2 +#define HCI_UART_H4DS 3 +#define HCI_UART_LL 4 +#define HCI_UART_ATH3K 5 +#define HCI_UART_STE 6 + +#define HCI_UART_RAW_DEVICE 0 + +/* UART break and flow control parameters */ +#define BREAK_ON true +#define BREAK_OFF false +#define FLOW_ON true +#define FLOW_OFF false + +struct hci_uart; + +struct hci_uart_proto { + unsigned int id; + int (*open)(struct hci_uart *hu); + int (*close)(struct hci_uart *hu); + int (*flush)(struct hci_uart *hu); + int (*recv)(struct hci_uart *hu, void *data, int len); + int (*enqueue)(struct hci_uart *hu, struct sk_buff *skb); + struct sk_buff *(*dequeue)(struct hci_uart *hu); + bool register_hci_dev; + struct device *dev; +}; + +struct hci_uart { + struct tty_struct *tty; + struct hci_dev *hdev; + unsigned long flags; + unsigned long hdev_flags; + + struct hci_uart_proto *proto; + void *priv; + + struct sk_buff *tx_skb; + unsigned long tx_state; + spinlock_t rx_lock; + + struct file *fd; +}; + +/* HCI_UART proto flag bits */ +#define HCI_UART_PROTO_SET 0 + +/* TX states */ +#define HCI_UART_SENDING 1 +#define HCI_UART_TX_WAKEUP 2 + +int cg2900_hci_uart_register_proto(struct hci_uart_proto *p); +int cg2900_hci_uart_unregister_proto(struct hci_uart_proto *p); +int cg2900_hci_uart_tx_wakeup(struct hci_uart *hu); +int cg2900_hci_uart_set_baudrate(struct hci_uart *hu, int baud); +int cg2900_hci_uart_set_break(struct hci_uart *hu, bool break_on); +int cg2900_hci_uart_tiocmget(struct hci_uart *hu); +void cg2900_hci_uart_flush_buffer(struct hci_uart *hu); +void cg2900_hci_uart_flow_ctrl(struct hci_uart *hu, bool flow_on); +int cg2900_hci_uart_chars_in_buffer(struct hci_uart *hu); + +#define hci_uart_register_proto cg2900_hci_uart_register_proto +#define hci_uart_unregister_proto cg2900_hci_uart_unregister_proto +#define hci_uart_tx_wakeup cg2900_hci_uart_tx_wakeup +#define hci_uart_set_baudrate cg2900_hci_uart_set_baudrate +#define hci_uart_set_break cg2900_hci_uart_set_break +#define hci_uart_tiocmget cg2900_hci_uart_tiocmget +#define hci_uart_flush_buffer cg2900_hci_uart_flush_buffer +#define hci_uart_flow_ctrl cg2900_hci_uart_flow_ctrl +#define hci_uart_chars_in_buffer cg2900_hci_uart_chars_in_buffer diff --git a/drivers/staging/cg2900/board-mop500-cg2900.c b/drivers/staging/cg2900/board-mop500-cg2900.c new file mode 100644 index 00000000000..d112b36cc9f --- /dev/null +++ b/drivers/staging/cg2900/board-mop500-cg2900.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008-2011 ST-Ericsson + * + * Author: Par-Gunnar Hjalmdahl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include + +#include +#include +#include + +#include +#include + +#include + +#include "cg2900.h" +#include "devices-cg2900.h" +#include "pins-db8500.h" + + +#define CG2900_BT_ENABLE_GPIO 170 +#define CG2900_GBF_ENA_RESET_GPIO 171 +#define CG2900_BT_CTS_GPIO 0 + +enum cg2900_gpio_pull_sleep cg2900_sleep_gpio[21] = { + CG2900_NO_PULL, /* GPIO 0: PTA_CONFX */ + CG2900_PULL_DN, /* GPIO 1: PTA_STATUS */ + CG2900_NO_PULL, /* GPIO 2: UART_CTSN */ + CG2900_PULL_UP, /* GPIO 3: UART_RTSN */ + CG2900_PULL_UP, /* GPIO 4: UART_TXD */ + CG2900_NO_PULL, /* GPIO 5: UART_RXD */ + CG2900_PULL_DN, /* GPIO 6: IOM_DOUT */ + CG2900_NO_PULL, /* GPIO 7: IOM_FSC */ + CG2900_NO_PULL, /* GPIO 8: IOM_CLK */ + CG2900_NO_PULL, /* GPIO 9: IOM_DIN */ + CG2900_PULL_DN, /* GPIO 10: PWR_REQ */ + CG2900_PULL_DN, /* GPIO 11: HOST_WAKEUP */ + CG2900_PULL_DN, /* GPIO 12: IIS_DOUT */ + CG2900_NO_PULL, /* GPIO 13: IIS_WS */ + CG2900_NO_PULL, /* GPIO 14: IIS_CLK */ + CG2900_NO_PULL, /* GPIO 15: IIS_DIN */ + CG2900_PULL_DN, /* GPIO 16: PTA_FREQ */ + CG2900_PULL_DN, /* GPIO 17: PTA_RF_ACTIVE */ + CG2900_NO_PULL, /* GPIO 18: NotConnected (J6428) */ + CG2900_NO_PULL, /* GPIO 19: EXT_DUTY_CYCLE */ + CG2900_NO_PULL, /* GPIO 20: EXT_FRM_SYNCH */ +}; + +static struct platform_device ux500_cg2900_device = { + .name = "cg2900", +}; + +static struct platform_device ux500_cg2900_chip_device = { + .name = "cg2900-chip", + .dev = { + .parent = &ux500_cg2900_device.dev, + }, +}; + +static struct platform_device ux500_stlc2690_chip_device = { + .name = "stlc2690-chip", + .dev = { + .parent = &ux500_cg2900_device.dev, + }, +}; + +static struct cg2900_platform_data cg2900_test_platform_data = { + .bus = HCI_VIRTUAL, + .gpio_sleep = cg2900_sleep_gpio, +}; + +static struct platform_device ux500_cg2900_test_device = { + .name = "cg2900-test", + .dev = { + .parent = &ux500_cg2900_device.dev, + .platform_data = &cg2900_test_platform_data, + }, +}; + +static struct resource cg2900_uart_resources[] = { + { + .start = CG2900_GBF_ENA_RESET_GPIO, + .end = CG2900_GBF_ENA_RESET_GPIO, + .flags = IORESOURCE_IO, + .name = "gbf_ena_reset", + }, + { + .start = CG2900_BT_ENABLE_GPIO, + .end = CG2900_BT_ENABLE_GPIO, + .flags = IORESOURCE_IO, + .name = "bt_enable", + }, + { + .start = CG2900_BT_CTS_GPIO, + .end = CG2900_BT_CTS_GPIO, + .flags = IORESOURCE_IO, + .name = "cts_gpio", + }, + { + .start = NOMADIK_GPIO_TO_IRQ(CG2900_BT_CTS_GPIO), + .end = NOMADIK_GPIO_TO_IRQ(CG2900_BT_CTS_GPIO), + .flags = IORESOURCE_IRQ, + .name = "cts_irq", + }, +}; + +static pin_cfg_t cg2900_uart_enabled[] = { + GPIO0_U0_CTSn | PIN_INPUT_PULLUP, + GPIO1_U0_RTSn | PIN_OUTPUT_HIGH, + GPIO2_U0_RXD | PIN_INPUT_PULLUP, + GPIO3_U0_TXD | PIN_OUTPUT_HIGH +}; + +static pin_cfg_t cg2900_uart_disabled[] = { + GPIO0_GPIO | PIN_INPUT_PULLUP, /* CTS pull up. */ + GPIO1_GPIO | PIN_OUTPUT_HIGH, /* RTS high-flow off. */ + GPIO2_GPIO | PIN_INPUT_PULLUP, /* RX pull down. */ + GPIO3_GPIO | PIN_OUTPUT_LOW /* TX low - break on. */ +}; + +static struct cg2900_platform_data cg2900_uart_platform_data = { + .bus = HCI_UART, + .gpio_sleep = cg2900_sleep_gpio, + .uart = { + .n_uart_gpios = 4, + .uart_enabled = cg2900_uart_enabled, + .uart_disabled = cg2900_uart_disabled, + }, +}; + +static struct platform_device ux500_cg2900_uart_device = { + .name = "cg2900-uart", + .dev = { + .platform_data = &cg2900_uart_platform_data, + .parent = &ux500_cg2900_device.dev, + }, + .num_resources = ARRAY_SIZE(cg2900_uart_resources), + .resource = cg2900_uart_resources, +}; + +static bool mach_supported(void) +{ + if (machine_is_u8500() || + machine_is_u5500() || + machine_is_hrefv60() || + machine_is_nomadik() || + machine_is_snowball()) + return true; + + return false; +} + +static int __init board_cg2900_init(void) +{ + int err; + + if (!mach_supported()) + return 0; + + dcg2900_init_platdata(&cg2900_test_platform_data); + dcg2900_init_platdata(&cg2900_uart_platform_data); + + err = platform_device_register(&ux500_cg2900_device); + if (err) + return err; + err = platform_device_register(&ux500_cg2900_uart_device); + if (err) + return err; + err = platform_device_register(&ux500_cg2900_test_device); + if (err) + return err; + err = platform_device_register(&ux500_cg2900_chip_device); + if (err) + return err; + err = platform_device_register(&ux500_stlc2690_chip_device); + if (err) + return err; + + dev_info(&ux500_cg2900_device.dev, "CG2900 initialized\n"); + return 0; +} + +static void __exit board_cg2900_exit(void) +{ + if (!mach_supported()) + return; + + platform_device_unregister(&ux500_stlc2690_chip_device); + platform_device_unregister(&ux500_cg2900_chip_device); + platform_device_unregister(&ux500_cg2900_test_device); + platform_device_unregister(&ux500_cg2900_uart_device); + platform_device_unregister(&ux500_cg2900_device); + + dev_info(&ux500_cg2900_device.dev, "CG2900 removed\n"); +} + +module_init(board_cg2900_init); +module_exit(board_cg2900_exit); diff --git a/drivers/staging/cg2900/devices-cg2900.c b/drivers/staging/cg2900/devices-cg2900.c new file mode 100644 index 00000000000..9917e6458c2 --- /dev/null +++ b/drivers/staging/cg2900/devices-cg2900.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Board specific device support for the Linux Bluetooth HCI H:4 Driver + * for ST-Ericsson connectivity controller. + */ +#define NAME "devices-cg2900" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" +#include "devices-cg2900.h" + +#define BT_VS_POWER_SWITCH_OFF 0xFD40 + +#define H4_HEADER_LENGTH 0x01 +#define BT_HEADER_LENGTH 0x03 + +#define STLC2690_HCI_REV 0x0600 +#define CG2900_PG1_HCI_REV 0x0101 +#define CG2900_PG2_HCI_REV 0x0200 +#define CG2900_PG1_SPECIAL_HCI_REV 0x0700 + +#define CHIP_INITIAL_HIGH_TIMEOUT 5 /* ms */ +#define CHIP_INITIAL_LOW_TIMEOUT 2 /* us */ + +struct vs_power_sw_off_cmd { + __le16 op_code; + u8 len; + u8 gpio_0_7_pull_up; + u8 gpio_8_15_pull_up; + u8 gpio_16_20_pull_up; + u8 gpio_0_7_pull_down; + u8 gpio_8_15_pull_down; + u8 gpio_16_20_pull_down; +} __packed; + +struct dcg2900_info { + int gbf_gpio; + int bt_gpio; + bool sleep_gpio_set; + u8 gpio_0_7_pull_up; + u8 gpio_8_15_pull_up; + u8 gpio_16_20_pull_up; + u8 gpio_0_7_pull_down; + u8 gpio_8_15_pull_down; + u8 gpio_16_20_pull_down; + spinlock_t pdb_toggle_lock; +}; + +static void dcg2900_enable_chip(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + unsigned long flags; + + if (info->gbf_gpio == -1) + return; + + /* + * Due to a bug in some CG2900 we cannot just set GPIO high to enable + * the chip. We must do the following: + * 1: Set PDB high + * 2: Wait a few milliseconds + * 3: Set PDB low + * 4: Wait 2 microseconds + * 5: Set PDB high + * We disable interrupts step 3-5 to assure that step 4 does not take + * too long time (which would invalidate the fix). + */ + gpio_set_value(info->gbf_gpio, 1); + + schedule_timeout_uninterruptible( + msecs_to_jiffies(CHIP_INITIAL_HIGH_TIMEOUT)); + + spin_lock_irqsave(&info->pdb_toggle_lock, flags); + gpio_set_value(info->gbf_gpio, 0); + udelay(CHIP_INITIAL_LOW_TIMEOUT); + gpio_set_value(info->gbf_gpio, 1); + spin_unlock_irqrestore(&info->pdb_toggle_lock, flags); +} + +static void dcg2900_disable_chip(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + + if (info->gbf_gpio != -1) + gpio_set_value(info->gbf_gpio, 0); +} + +static struct sk_buff *dcg2900_get_power_switch_off_cmd + (struct cg2900_chip_dev *dev, u16 *op_code) +{ + struct sk_buff *skb; + struct vs_power_sw_off_cmd *cmd; + struct dcg2900_info *info; + int i; + + /* If connected chip does not support the command return NULL */ + if (CG2900_PG1_SPECIAL_HCI_REV != dev->chip.hci_revision && + CG2900_PG1_HCI_REV != dev->chip.hci_revision && + CG2900_PG2_HCI_REV != dev->chip.hci_revision) + return NULL; + + dev_dbg(dev->dev, "Generating PowerSwitchOff command\n"); + + info = dev->b_data; + + skb = alloc_skb(sizeof(*cmd) + H4_HEADER_LENGTH, GFP_KERNEL); + if (!skb) { + dev_err(dev->dev, "Could not allocate skb\n"); + return NULL; + } + + skb_reserve(skb, H4_HEADER_LENGTH); + cmd = (struct vs_power_sw_off_cmd *)skb_put(skb, sizeof(*cmd)); + cmd->op_code = cpu_to_le16(BT_VS_POWER_SWITCH_OFF); + cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH; + /* + * Enter system specific GPIO settings here: + * Section data[3-5] is GPIO pull-up selection + * Section data[6-8] is GPIO pull-down selection + * Each section is a bitfield where + * - byte 0 bit 0 is GPIO 0 + * - byte 0 bit 1 is GPIO 1 + * - up to + * - byte 2 bit 4 which is GPIO 20 + * where each bit means: + * - 0: No pull-up / no pull-down + * - 1: Pull-up / pull-down + * All GPIOs are set as input. + */ + if (!info->sleep_gpio_set) { + struct cg2900_platform_data *pf_data; + + pf_data = dev_get_platdata(dev->dev); + for (i = 0; i < 8; i++) { + if (pf_data->gpio_sleep[i] == CG2900_PULL_UP) + info->gpio_0_7_pull_up |= (1 << i); + else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN) + info->gpio_0_7_pull_down |= (1 << i); + } + for (i = 8; i < 16; i++) { + if (pf_data->gpio_sleep[i] == CG2900_PULL_UP) + info->gpio_8_15_pull_up |= (1 << (i - 8)); + else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN) + info->gpio_8_15_pull_down |= (1 << (i - 8)); + } + for (i = 16; i < 21; i++) { + if (pf_data->gpio_sleep[i] == CG2900_PULL_UP) + info->gpio_16_20_pull_up |= (1 << (i - 16)); + else if (pf_data->gpio_sleep[i] == CG2900_PULL_DN) + info->gpio_16_20_pull_down |= (1 << (i - 16)); + } + info->sleep_gpio_set = true; + } + cmd->gpio_0_7_pull_up = info->gpio_0_7_pull_up; + cmd->gpio_8_15_pull_up = info->gpio_8_15_pull_up; + cmd->gpio_16_20_pull_up = info->gpio_16_20_pull_up; + cmd->gpio_0_7_pull_down = info->gpio_0_7_pull_down; + cmd->gpio_8_15_pull_down = info->gpio_8_15_pull_down; + cmd->gpio_16_20_pull_down = info->gpio_16_20_pull_down; + + + if (op_code) + *op_code = BT_VS_POWER_SWITCH_OFF; + + return skb; +} + +static int dcg2900_init(struct cg2900_chip_dev *dev) +{ + int err = 0; + struct dcg2900_info *info; + struct resource *resource; + const char *gbf_name; + const char *bt_name = NULL; + + /* First retrieve and save the resources */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Could not allocate dcg2900_info\n"); + return -ENOMEM; + } + + spin_lock_init(&info->pdb_toggle_lock); + + if (!dev->pdev->num_resources) { + dev_dbg(dev->dev, "No resources available\n"); + info->gbf_gpio = -1; + info->bt_gpio = -1; + goto finished; + } + + resource = platform_get_resource_byname(dev->pdev, IORESOURCE_IO, + "gbf_ena_reset"); + if (!resource) { + dev_err(dev->dev, "GBF GPIO does not exist\n"); + err = -EINVAL; + goto err_handling; + } + info->gbf_gpio = resource->start; + gbf_name = resource->name; + + resource = platform_get_resource_byname(dev->pdev, IORESOURCE_IO, + "bt_enable"); + /* BT Enable GPIO may not exist */ + if (resource) { + info->bt_gpio = resource->start; + bt_name = resource->name; + } + + /* Now setup the GPIOs */ + err = gpio_request(info->gbf_gpio, gbf_name); + if (err < 0) { + dev_err(dev->dev, "gpio_request failed with err: %d\n", err); + goto err_handling; + } + + err = gpio_direction_output(info->gbf_gpio, 0); + if (err < 0) { + dev_err(dev->dev, "gpio_direction_output failed with err: %d\n", + err); + goto err_handling_free_gpio_gbf; + } + + if (!bt_name) { + info->bt_gpio = -1; + goto finished; + } + + err = gpio_request(info->bt_gpio, bt_name); + if (err < 0) { + dev_err(dev->dev, "gpio_request failed with err: %d\n", err); + goto err_handling_free_gpio_gbf; + } + + err = gpio_direction_output(info->bt_gpio, 1); + if (err < 0) { + dev_err(dev->dev, "gpio_direction_output failed with err: %d\n", + err); + goto err_handling_free_gpio_bt; + } + + /* + * Enable the power on snowball + */ + if (machine_is_snowball()) { + /* Take the regulator */ + } + +finished: + dev->b_data = info; + return 0; +err_handling_free_gpio_bt: + gpio_free(info->bt_gpio); +err_handling_free_gpio_gbf: + gpio_free(info->gbf_gpio); +err_handling: + kfree(info); + return err; +} + +static void dcg2900_exit(struct cg2900_chip_dev *dev) +{ + struct dcg2900_info *info = dev->b_data; + + if (machine_is_snowball()) { + /* Put the regulator */ + } + dcg2900_disable_chip(dev); + if (info->bt_gpio != -1) + gpio_free(info->bt_gpio); + if (info->gbf_gpio != -1) + gpio_free(info->gbf_gpio); + kfree(info); + dev->b_data = NULL; +} + +static int dcg2900_disable_uart(struct cg2900_chip_dev *dev) +{ + int err; + struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev); + + /* + * Without this delay we get interrupt on CTS immediately + * due to some turbulences on this line. + */ + mdelay(4); + + /* Disable UART functions. */ + err = nmk_config_pins(pdata->uart.uart_disabled, + pdata->uart.n_uart_gpios); + if (err) + goto error; + + return 0; + +error: + (void)nmk_config_pins(pdata->uart.uart_enabled, + pdata->uart.n_uart_gpios); + dev_err(dev->dev, "Cannot set interrupt (%d)\n", err); + return err; +} + +static int dcg2900_enable_uart(struct cg2900_chip_dev *dev) +{ + int err; + struct cg2900_platform_data *pdata = dev_get_platdata(dev->dev); + + /* Restore UART settings. */ + err = nmk_config_pins(pdata->uart.uart_enabled, + pdata->uart.n_uart_gpios); + if (err) + dev_err(dev->dev, "Unable to enable UART (%d)\n", err); + + return err; +} + +void dcg2900_init_platdata(struct cg2900_platform_data *data) +{ + data->init = dcg2900_init; + data->exit = dcg2900_exit; + data->enable_chip = dcg2900_enable_chip; + data->disable_chip = dcg2900_disable_chip; + data->get_power_switch_off_cmd = dcg2900_get_power_switch_off_cmd; + + data->uart.enable_uart = dcg2900_enable_uart; + data->uart.disable_uart = dcg2900_disable_uart; +} diff --git a/drivers/staging/cg2900/devices-cg2900.h b/drivers/staging/cg2900/devices-cg2900.h new file mode 100644 index 00000000000..e365a4fae1e --- /dev/null +++ b/drivers/staging/cg2900/devices-cg2900.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Par-Gunnar Hjalmdahl + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef __DEVICES_CG2900_H +#define __DEVICES_CG2900_H + +#include "cg2900.h" + +/** + * enum cg2900_gpio_pull_sleep - GPIO pull setting in sleep. + * @CG2900_NO_PULL: Normal input in sleep (no pull up or down). + * @CG2900_PULL_UP: Pull up in sleep. + * @CG2900_PULL_DN: Pull down in sleep. + */ +enum cg2900_gpio_pull_sleep { + CG2900_NO_PULL, + CG2900_PULL_UP, + CG2900_PULL_DN +}; + +/** + * dcg2900_init_platdata() - Initializes platform data with callback functions. + * @data: Platform data. + */ +extern void dcg2900_init_platdata(struct cg2900_platform_data *data); + +#endif /* __DEVICES_CG2900_H */ diff --git a/drivers/staging/cg2900/include/cg2900.h b/drivers/staging/cg2900/include/cg2900.h new file mode 100644 index 00000000000..476ce158e4d --- /dev/null +++ b/drivers/staging/cg2900/include/cg2900.h @@ -0,0 +1,278 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 connectivity + * controller. + */ + +#ifndef _CG2900_H_ +#define _CG2900_H_ + +#include + +/* Perform reset. No parameters used */ +#define CG2900_CHAR_DEV_IOCTL_RESET _IOW('U', 210, int) +/* Check for reset */ +#define CG2900_CHAR_DEV_IOCTL_CHECK4RESET _IOR('U', 212, int) +/* Retrieve revision info */ +#define CG2900_CHAR_DEV_IOCTL_GET_REVISION _IOR('U', 213, \ + struct cg2900_rev_data) + +#define CG2900_CHAR_DEV_IOCTL_EVENT_IDLE 0 +#define CG2900_CHAR_DEV_IOCTL_EVENT_RESET 1 + +/** + * struct cg2900_rev_data - Contains revision data for the local controller. + * @revision: Revision of the controller, e.g. to indicate that it is + * a CG2900 controller. + * @sub_version: Subversion of the controller, e.g. to indicate a certain + * tape-out of the controller. + * + * The values to match retrieved values to each controller may be retrieved from + * the manufacturer. + */ +struct cg2900_rev_data { + int revision; + int sub_version; +}; + +#ifdef __KERNEL__ +#include +#include +#include + +/* Temporary solution while in staging directory */ +#include "cg2900_hci.h" + +/** + * struct cg2900_chip_rev_info - Chip info structure. + * @manufacturer: Chip manufacturer. + * @hci_version: Bluetooth version supported over HCI. + * @hci_revision: Chip revision, i.e. which chip is this. + * @lmp_pal_version: Bluetooth version supported over air. + * @hci_sub_version: Chip sub-version, i.e. which tape-out is this. + * + * Note that these values match the Bluetooth Assigned Numbers, + * see http://www.bluetooth.org/ + */ +struct cg2900_chip_rev_info { + u16 manufacturer; + u8 hci_version; + u16 hci_revision; + u8 lmp_pal_version; + u16 hci_sub_version; +}; + +struct cg2900_chip_dev; + +/** + * struct cg2900_id_callbacks - Chip handler identification callbacks. + * @check_chip_support: Called when chip is connected. If chip is supported by + * driver, return true and fill in @callbacks in @dev. + * + * Note that the callback may be NULL. It must always be NULL checked before + * calling. + */ +struct cg2900_id_callbacks { + bool (*check_chip_support)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_chip_callbacks - Callback functions registered by chip handler. + * @data_from_chip: Called when data shall be transmitted to user. + * @chip_removed: Called when chip is removed. + * + * Note that some callbacks may be NULL. They must always be NULL checked before + * calling. + */ +struct cg2900_chip_callbacks { + void (*data_from_chip)(struct cg2900_chip_dev *dev, + struct sk_buff *skb); + void (*chip_removed)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_trans_callbacks - Callback functions registered by transport. + * @open: CG2900 Core needs a transport. + * @close: CG2900 Core does not need a transport. + * @write: CG2900 Core transmits to the chip. + * @set_chip_power: CG2900 Core enables or disables the chip. + * @chip_startup_finished: CG2900 Chip startup finished notification. + * + * Note that some callbacks may be NULL. They must always be NULL checked before + * calling. + */ +struct cg2900_trans_callbacks { + int (*open)(struct cg2900_chip_dev *dev); + int (*close)(struct cg2900_chip_dev *dev); + int (*write)(struct cg2900_chip_dev *dev, struct sk_buff *skb); + void (*set_chip_power)(struct cg2900_chip_dev *dev, bool chip_on); + void (*chip_startup_finished)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_chip_dev - Chip handler info structure. + * @dev: Device associated with this chip. + * @pdev: Platform device associated with this chip. + * @chip: Chip info such as manufacturer. + * @c_cb: Callback structure for the chip handler. + * @t_cb: Callback structure for the transport. + * @c_data: Arbitrary data set by chip handler. + * @t_data: Arbitrary data set by transport. + * @b_data: Arbitrary data set by board handler. + * @prv_data: Arbitrary data set by CG2900 Core. + */ +struct cg2900_chip_dev { + struct device *dev; + struct platform_device *pdev; + struct cg2900_chip_rev_info chip; + struct cg2900_chip_callbacks c_cb; + struct cg2900_trans_callbacks t_cb; + void *c_data; + void *t_data; + void *b_data; + void *prv_data; +}; + +/** + * struct cg2900_platform_data - Contains platform data for CG2900. + * @init: Callback called upon system start. + * @exit: Callback called upon system shutdown. + * @enable_chip: Callback called for enabling CG2900 chip. + * @disable_chip: Callback called for disabling CG2900 chip. + * @get_power_switch_off_cmd: Callback called to retrieve + * HCI VS_Power_Switch_Off command (command + * HCI requires platform specific GPIO data). + * @bus: Transport used, see @include/net/bluetooth/hci.h. + * @gpio_sleep: Array of GPIO sleep settings. + * @enable_uart: Callback called when switching from UART GPIO to + * UART HW. + * @disable_uart: Callback called when switching from UART HW to + * UART GPIO. + * @n_uart_gpios: Number of UART GPIOs. + * @uart_enabled: Array of size @n_uart_gpios with GPIO setting for + * enabling UART HW (switching from GPIO mode). + * @uart_disabled: Array of size @n_uart_gpios with GPIO setting for + * disabling UART HW (switching to GPIO mode). + * @uart: Platform data structure for UART transport. + * + * Any callback may be NULL if not needed. + */ +struct cg2900_platform_data { + int (*init)(struct cg2900_chip_dev *dev); + void (*exit)(struct cg2900_chip_dev *dev); + void (*enable_chip)(struct cg2900_chip_dev *dev); + void (*disable_chip)(struct cg2900_chip_dev *dev); + struct sk_buff* (*get_power_switch_off_cmd)(struct cg2900_chip_dev *dev, + u16 *op_code); + + __u8 bus; + enum cg2900_gpio_pull_sleep *gpio_sleep; + + struct { + int (*enable_uart)(struct cg2900_chip_dev *dev); + int (*disable_uart)(struct cg2900_chip_dev *dev); + int n_uart_gpios; + unsigned long *uart_enabled; + unsigned long *uart_disabled; + } uart; +}; + +/** + * struct cg2900_user_data - Contains platform data for CG2900 user. + * @dev: Current device. Set by CG2900 user upon probe. + * @opened: True if channel is opened. + * @user_data: Data set and used by CG2900 user. + * @private_data: Data set and used by CG2900 driver. + * @h4_channel: H4 channel. Set by CG2900 driver. + * @is_audio: True if this channel is an audio channel. Set by CG2900 + * driver. + * @chip_independent: True if this channel does not require chip to be + * powered. Set by CG2900 driver. + * @bt_bus: Transport used, see @include/net/bluetooth/hci.h. + * @char_dev_name: Name to be used for character device. + * @channel_data: Input data specific to current device. + * @open: Open device channel. Set by CG2900 driver. + * @close: Close device channel. Set by CG2900 driver. + * @reset: Reset connectivity controller. Set by CG2900 driver. + * @alloc_skb: Alloc sk_buffer. Set by CG2900 driver. + * @write: Write to device channel. Set by CG2900 driver. + * @get_local_revision: Get revision data of conncected chip. Set by CG2900 + * driver. + * @read_cb: Callback function called when data is received on the + * device channel. Set by CG2900 user. Mandatory. + * @reset_cb: Callback function called when the connectivity + * controller has been reset. Set by CG2900 user. + * + * Any callback may be NULL if not needed. + */ +struct cg2900_user_data { + struct device *dev; + bool opened; + + void *user_data; + void *private_data; + + int h4_channel; + bool is_audio; + bool chip_independent; + + union { + __u8 bt_bus; + char *char_dev_name; + } channel_data; + + int (*open)(struct cg2900_user_data *user_data); + void (*close)(struct cg2900_user_data *user_data); + int (*reset)(struct cg2900_user_data *user_data); + struct sk_buff * (*alloc_skb)(unsigned int size, gfp_t priority); + int (*write)(struct cg2900_user_data *user_data, struct sk_buff *skb); + bool (*get_local_revision)(struct cg2900_user_data *user_data, + struct cg2900_rev_data *rev_data); + + void (*read_cb)(struct cg2900_user_data *user_data, + struct sk_buff *skb); + void (*reset_cb)(struct cg2900_user_data *user_data); +}; + +static inline void *cg2900_get_usr(struct cg2900_user_data *dev) +{ + if (dev) + return dev->user_data; + return NULL; +} + +static inline void cg2900_set_usr(struct cg2900_user_data *dev, void *data) +{ + if (dev) + dev->user_data = data; +} + +static inline void *cg2900_get_prv(struct cg2900_user_data *dev) +{ + if (dev) + return dev->private_data; + return NULL; +} + +static inline void cg2900_set_prv(struct cg2900_user_data *dev, void *data) +{ + if (dev) + dev->private_data = data; +} + +extern int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb); +extern void cg2900_deregister_chip_driver(struct cg2900_id_callbacks *cb); +extern int cg2900_register_trans_driver(struct cg2900_chip_dev *dev); +extern int cg2900_deregister_trans_driver(struct cg2900_chip_dev *dev); +extern unsigned long cg2900_get_sleep_timeout(void); + +#endif /* __KERNEL__ */ +#endif /* _CG2900_H_ */ diff --git a/drivers/staging/cg2900/include/cg2900_audio.h b/drivers/staging/cg2900/include/cg2900_audio.h new file mode 100644 index 00000000000..ff0f053fa53 --- /dev/null +++ b/drivers/staging/cg2900/include/cg2900_audio.h @@ -0,0 +1,473 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth Audio Driver for ST-Ericsson controller. + */ + +#ifndef _CG2900_AUDIO_H_ +#define _CG2900_AUDIO_H_ + +#include + +/* + * Digital Audio Interface configuration types + */ + +/** CG2900_A2DP_MAX_AVDTP_HDR_LEN - Max length of a AVDTP header. + * Max length of a AVDTP header for an A2DP packet. + */ +#define CG2900_A2DP_MAX_AVDTP_HDR_LEN 25 + +/* + * Op codes used when writing commands to the audio interface from user space + * using the char device. + */ +#define CG2900_OPCODE_SET_DAI_CONF 0x01 +#define CG2900_OPCODE_GET_DAI_CONF 0x02 +#define CG2900_OPCODE_CONFIGURE_ENDPOINT 0x03 +#define CG2900_OPCODE_START_STREAM 0x04 +#define CG2900_OPCODE_STOP_STREAM 0x05 + +/** + * enum cg2900_dai_dir - Contains the DAI port directions alternatives. + * @DAI_DIR_B_RX_A_TX: Port B as Rx and port A as Tx. + * @DAI_DIR_B_TX_A_RX: Port B as Tx and port A as Rx. + */ +enum cg2900_dai_dir { + DAI_DIR_B_RX_A_TX = 0x00, + DAI_DIR_B_TX_A_RX = 0x01 +}; + +/** + * enum cg2900_dai_mode - DAI mode alternatives. + * @DAI_MODE_SLAVE: Slave. + * @DAI_MODE_MASTER: Master. + */ +enum cg2900_dai_mode { + DAI_MODE_SLAVE = 0x00, + DAI_MODE_MASTER = 0x01 +}; + +/** + * enum cg2900_dai_stream_ratio - Voice stream ratio alternatives. + * @STREAM_RATIO_FM16_VOICE16: FM 16kHz, Voice 16kHz. + * @STREAM_RATIO_FM16_VOICE8: FM 16kHz, Voice 8kHz. + * @STREAM_RATIO_FM48_VOICE16: FM 48kHz, Voice 16Khz. + * @STREAM_RATIO_FM48_VOICE8: FM 48kHz, Voice 8kHz. + * + * Contains the alternatives for the voice stream ratio between the Audio stream + * sample rate and the Voice stream sample rate. + */ +enum cg2900_dai_stream_ratio { + STREAM_RATIO_FM16_VOICE16 = 0x01, + STREAM_RATIO_FM16_VOICE8 = 0x02, + STREAM_RATIO_FM48_VOICE16 = 0x03, + STREAM_RATIO_FM48_VOICE8 = 0x06 +}; + +/** + * enum cg2900_dai_fs_duration - Frame sync duration alternatives. + * @SYNC_DURATION_8: 8 frames sync duration. + * @SYNC_DURATION_16: 16 frames sync duration. + * @SYNC_DURATION_24: 24 frames sync duration. + * @SYNC_DURATION_32: 32 frames sync duration. + * @SYNC_DURATION_48: 48 frames sync duration. + * @SYNC_DURATION_50: 50 frames sync duration. + * @SYNC_DURATION_64: 64 frames sync duration. + * @SYNC_DURATION_75: 75 frames sync duration. + * @SYNC_DURATION_96: 96 frames sync duration. + * @SYNC_DURATION_125: 125 frames sync duration. + * @SYNC_DURATION_128: 128 frames sync duration. + * @SYNC_DURATION_150: 150 frames sync duration. + * @SYNC_DURATION_192: 192 frames sync duration. + * @SYNC_DURATION_250: 250 frames sync duration. + * @SYNC_DURATION_256: 256 frames sync duration. + * @SYNC_DURATION_300: 300 frames sync duration. + * @SYNC_DURATION_384: 384 frames sync duration. + * @SYNC_DURATION_500: 500 frames sync duration. + * @SYNC_DURATION_512: 512 frames sync duration. + * @SYNC_DURATION_600: 600 frames sync duration. + * @SYNC_DURATION_768: 768 frames sync duration. + * + * This parameter sets the PCM frame sync duration. It is calculated as the + * ratio between the bit clock and the frame rate. For example, if the bit + * clock is 512 kHz and the stream sample rate is 8 kHz, the PCM frame sync + * duration is 512 / 8 = 64. + */ +enum cg2900_dai_fs_duration { + SYNC_DURATION_8 = 0, + SYNC_DURATION_16 = 1, + SYNC_DURATION_24 = 2, + SYNC_DURATION_32 = 3, + SYNC_DURATION_48 = 4, + SYNC_DURATION_50 = 5, + SYNC_DURATION_64 = 6, + SYNC_DURATION_75 = 7, + SYNC_DURATION_96 = 8, + SYNC_DURATION_125 = 9, + SYNC_DURATION_128 = 10, + SYNC_DURATION_150 = 11, + SYNC_DURATION_192 = 12, + SYNC_DURATION_250 = 13, + SYNC_DURATION_256 = 14, + SYNC_DURATION_300 = 15, + SYNC_DURATION_384 = 16, + SYNC_DURATION_500 = 17, + SYNC_DURATION_512 = 18, + SYNC_DURATION_600 = 19, + SYNC_DURATION_768 = 20 +}; + +/** + * enum cg2900_dai_bit_clk - Bit Clock alternatives. + * @BIT_CLK_128: 128 Kbits clock. + * @BIT_CLK_256: 256 Kbits clock. + * @BIT_CLK_512: 512 Kbits clock. + * @BIT_CLK_768: 768 Kbits clock. + * @BIT_CLK_1024: 1024 Kbits clock. + * @BIT_CLK_1411_76: 1411.76 Kbits clock. + * @BIT_CLK_1536: 1536 Kbits clock. + * @BIT_CLK_2000: 2000 Kbits clock. + * @BIT_CLK_2048: 2048 Kbits clock. + * @BIT_CLK_2400: 2400 Kbits clock. + * @BIT_CLK_2823_52: 2823.52 Kbits clock. + * @BIT_CLK_3072: 3072 Kbits clock. + * + * This parameter sets the bit clock speed. This is the clocking of the actual + * data. A usual parameter for eSCO voice is 512 kHz. + */ +enum cg2900_dai_bit_clk { + BIT_CLK_128 = 0x00, + BIT_CLK_256 = 0x01, + BIT_CLK_512 = 0x02, + BIT_CLK_768 = 0x03, + BIT_CLK_1024 = 0x04, + BIT_CLK_1411_76 = 0x05, + BIT_CLK_1536 = 0x06, + BIT_CLK_2000 = 0x07, + BIT_CLK_2048 = 0x08, + BIT_CLK_2400 = 0x09, + BIT_CLK_2823_52 = 0x0A, + BIT_CLK_3072 = 0x0B +}; + +/** + * enum cg2900_dai_sample_rate - Sample rates alternatives. + * @SAMPLE_RATE_8: 8 kHz sample rate. + * @SAMPLE_RATE_16: 16 kHz sample rate. + * @SAMPLE_RATE_44_1: 44.1 kHz sample rate. + * @SAMPLE_RATE_48: 48 kHz sample rate. + */ +enum cg2900_dai_sample_rate { + SAMPLE_RATE_8 = 0, + SAMPLE_RATE_16 = 1, + SAMPLE_RATE_44_1 = 2, + SAMPLE_RATE_48 = 3 +}; + +/** + * enum cg2900_dai_port_protocol - Port protocol alternatives. + * @PORT_PROTOCOL_PCM: Protocol PCM. + * @PORT_PROTOCOL_I2S: Protocol I2S. + */ +enum cg2900_dai_port_protocol { + PORT_PROTOCOL_PCM = 0x00, + PORT_PROTOCOL_I2S = 0x01 +}; + +/** + * enum cg2900_dai_channel_sel - The channel selection alternatives. + * @CHANNEL_SELECTION_RIGHT: Right channel used. + * @CHANNEL_SELECTION_LEFT: Left channel used. + * @CHANNEL_SELECTION_BOTH: Both channels used. + */ +enum cg2900_dai_channel_sel { + CHANNEL_SELECTION_RIGHT = 0x00, + CHANNEL_SELECTION_LEFT = 0x01, + CHANNEL_SELECTION_BOTH = 0x02 +}; + +/** + * struct cg2900_dai_conf_i2s_pcm - Port configuration structure. + * @mode: Operational mode of the port configured. + * @i2s_channel_sel: I2S channels used. Only valid if used in I2S mode. + * @slot_0_used: True if SCO slot 0 is used. + * @slot_1_used: True if SCO slot 1 is used. + * @slot_2_used: True if SCO slot 2 is used. + * @slot_3_used: True if SCO slot 3 is used. + * @slot_0_dir: Direction of slot 0. + * @slot_1_dir: Direction of slot 1. + * @slot_2_dir: Direction of slot 2. + * @slot_3_dir: Direction of slot 3. + * @slot_0_start: Slot 0 start (relative to the PCM frame sync). + * @slot_1_start: Slot 1 start (relative to the PCM frame sync) + * @slot_2_start: Slot 2 start (relative to the PCM frame sync) + * @slot_3_start: Slot 3 start (relative to the PCM frame sync) + * @ratio: Voice stream ratio between the Audio stream sample rate + * and the Voice stream sample rate. + * @protocol: Protocol used on port. + * @duration: Frame sync duration. + * @clk: Bit clock. + * @sample_rate: Sample rate. + */ +struct cg2900_dai_conf_i2s_pcm { + enum cg2900_dai_mode mode; + enum cg2900_dai_channel_sel i2s_channel_sel; + bool slot_0_used; + bool slot_1_used; + bool slot_2_used; + bool slot_3_used; + enum cg2900_dai_dir slot_0_dir; + enum cg2900_dai_dir slot_1_dir; + enum cg2900_dai_dir slot_2_dir; + enum cg2900_dai_dir slot_3_dir; + __u8 slot_0_start; + __u8 slot_1_start; + __u8 slot_2_start; + __u8 slot_3_start; + enum cg2900_dai_stream_ratio ratio; + enum cg2900_dai_port_protocol protocol; + enum cg2900_dai_fs_duration duration; + enum cg2900_dai_bit_clk clk; + enum cg2900_dai_sample_rate sample_rate; +}; + +/** + * enum cg2900_dai_half_period - Half period duration alternatives. + * @HALF_PER_DUR_8: 8 Bits. + * @HALF_PER_DUR_16: 16 Bits. + * @HALF_PER_DUR_24: 24 Bits. + * @HALF_PER_DUR_25: 25 Bits. + * @HALF_PER_DUR_32: 32 Bits. + * @HALF_PER_DUR_48: 48 Bits. + * @HALF_PER_DUR_64: 64 Bits. + * @HALF_PER_DUR_75: 75 Bits. + * @HALF_PER_DUR_96: 96 Bits. + * @HALF_PER_DUR_128: 128 Bits. + * @HALF_PER_DUR_150: 150 Bits. + * @HALF_PER_DUR_192: 192 Bits. + * + * This parameter sets the number of bits contained in each I2S half period, + * i.e. each channel slot. A usual value is 16 bits. + */ +enum cg2900_dai_half_period { + HALF_PER_DUR_8 = 0x00, + HALF_PER_DUR_16 = 0x01, + HALF_PER_DUR_24 = 0x02, + HALF_PER_DUR_25 = 0x03, + HALF_PER_DUR_32 = 0x04, + HALF_PER_DUR_48 = 0x05, + HALF_PER_DUR_64 = 0x06, + HALF_PER_DUR_75 = 0x07, + HALF_PER_DUR_96 = 0x08, + HALF_PER_DUR_128 = 0x09, + HALF_PER_DUR_150 = 0x0A, + HALF_PER_DUR_192 = 0x0B +}; + +/** + * enum cg2900_dai_word_width - Word width alternatives. + * @WORD_WIDTH_16: 16 bits words. + * @WORD_WIDTH_32: 32 bits words. + */ +enum cg2900_dai_word_width { + WORD_WIDTH_16 = 0x00, + WORD_WIDTH_32 = 0x01 +}; + +/** + * struct cg2900_dai_conf_i2s - Port configuration struct for I2S. + * @mode: Operational mode of the port. + * @half_period: Half period duration. + * @channel_sel: Channel selection. + * @sample_rate: Sample rate. + * @word_width: Word width. + */ +struct cg2900_dai_conf_i2s { + enum cg2900_dai_mode mode; + enum cg2900_dai_half_period half_period; + enum cg2900_dai_channel_sel channel_sel; + enum cg2900_dai_sample_rate sample_rate; + enum cg2900_dai_word_width word_width; +}; + +/** + * union cg2900_dai_port_conf - DAI port configuration union. + * @i2s: The configuration struct for a port supporting only I2S. + * @i2s_pcm: The configuration struct for a port supporting both PCM and I2S. + */ +union cg2900_dai_port_conf { + struct cg2900_dai_conf_i2s i2s; + struct cg2900_dai_conf_i2s_pcm i2s_pcm; +}; + +/** + * enum cg2900_dai_ext_port_id - DAI external port id alternatives. + * @PORT_0_I2S: Port id is 0 and it supports only I2S. + * @PORT_1_I2S_PCM: Port id is 1 and it supports both I2S and PCM. + */ +enum cg2900_dai_ext_port_id { + PORT_0_I2S, + PORT_1_I2S_PCM +}; + +/** + * enum cg2900_audio_endpoint_id - Audio endpoint id alternatives. + * @ENDPOINT_PORT_0_I2S: Internal audio endpoint of the external I2S + * interface. + * @ENDPOINT_PORT_1_I2S_PCM: Internal audio endpoint of the external I2S/PCM + * interface. + * @ENDPOINT_SLIMBUS_VOICE: Internal audio endpoint of the external Slimbus + * voice interface. (Currently not supported) + * @ENDPOINT_SLIMBUS_AUDIO: Internal audio endpoint of the external Slimbus + * audio interface. (Currently not supported) + * @ENDPOINT_BT_SCO_INOUT: Bluetooth SCO bidirectional. + * @ENDPOINT_BT_A2DP_SRC: Bluetooth A2DP source. + * @ENDPOINT_BT_A2DP_SNK: Bluetooth A2DP sink. + * @ENDPOINT_FM_RX: FM receive. + * @ENDPOINT_FM_TX: FM transmit. + * @ENDPOINT_ANALOG_OUT: Analog out. + * @ENDPOINT_DSP_AUDIO_IN: DSP audio in. + * @ENDPOINT_DSP_AUDIO_OUT: DSP audio out. + * @ENDPOINT_DSP_VOICE_IN: DSP voice in. + * @ENDPOINT_DSP_VOICE_OUT: DSP voice out. + * @ENDPOINT_DSP_TONE_IN: DSP tone in. + * @ENDPOINT_BURST_BUFFER_IN: Burst buffer in. + * @ENDPOINT_BURST_BUFFER_OUT: Burst buffer out. + * @ENDPOINT_MUSIC_DECODER: Music decoder. + * @ENDPOINT_HCI_AUDIO_IN: HCI audio in. + */ +enum cg2900_audio_endpoint_id { + ENDPOINT_PORT_0_I2S, + ENDPOINT_PORT_1_I2S_PCM, + ENDPOINT_SLIMBUS_VOICE, + ENDPOINT_SLIMBUS_AUDIO, + ENDPOINT_BT_SCO_INOUT, + ENDPOINT_BT_A2DP_SRC, + ENDPOINT_BT_A2DP_SNK, + ENDPOINT_FM_RX, + ENDPOINT_FM_TX, + ENDPOINT_ANALOG_OUT, + ENDPOINT_DSP_AUDIO_IN, + ENDPOINT_DSP_AUDIO_OUT, + ENDPOINT_DSP_VOICE_IN, + ENDPOINT_DSP_VOICE_OUT, + ENDPOINT_DSP_TONE_IN, + ENDPOINT_BURST_BUFFER_IN, + ENDPOINT_BURST_BUFFER_OUT, + ENDPOINT_MUSIC_DECODER, + ENDPOINT_HCI_AUDIO_IN +}; + +/** + * struct cg2900_dai_config - Configuration struct for Digital Audio Interface. + * @port: The port id to configure. Acts as a discriminator for @conf parameter + * which is a union. + * @conf: The configuration union that contains the parameters for the port. + */ +struct cg2900_dai_config { + enum cg2900_dai_ext_port_id port; + union cg2900_dai_port_conf conf; +}; + +/* + * Endpoint configuration types + */ + +/** + * enum cg2900_endpoint_sample_rate - Audio endpoint configuration sample rate alternatives. + * + * This enum defines the same values as @cg2900_dai_sample_rate, but + * is kept to preserve the API. + * + * @ENDPOINT_SAMPLE_RATE_8_KHZ: 8 kHz sample rate. + * @ENDPOINT_SAMPLE_RATE_16_KHZ: 16 kHz sample rate. + * @ENDPOINT_SAMPLE_RATE_44_1_KHZ: 44.1 kHz sample rate. + * @ENDPOINT_SAMPLE_RATE_48_KHZ: 48 kHz sample rate. + */ +enum cg2900_endpoint_sample_rate { + ENDPOINT_SAMPLE_RATE_8_KHZ = SAMPLE_RATE_8, + ENDPOINT_SAMPLE_RATE_16_KHZ = SAMPLE_RATE_16, + ENDPOINT_SAMPLE_RATE_44_1_KHZ = SAMPLE_RATE_44_1, + ENDPOINT_SAMPLE_RATE_48_KHZ = SAMPLE_RATE_48 +}; + + +/** + * struct cg2900_endpoint_config_a2dp_src - A2DP source audio endpoint configurations. + * @sample_rate: Sample rate. + * @channel_count: Number of channels. + */ +struct cg2900_endpoint_config_a2dp_src { + enum cg2900_endpoint_sample_rate sample_rate; + unsigned int channel_count; +}; + +/** + * struct cg2900_endpoint_config_fm - Configuration parameters for an FM endpoint. + * @sample_rate: The sample rate alternatives for the FM audio endpoints. + */ +struct cg2900_endpoint_config_fm { + enum cg2900_endpoint_sample_rate sample_rate; +}; + + +/** + * struct cg2900_endpoint_config_sco_in_out - SCO audio endpoint configuration structure. + * @sample_rate: Sample rate, valid values are + * * ENDPOINT_SAMPLE_RATE_8_KHZ + * * ENDPOINT_SAMPLE_RATE_16_KHZ. + */ +struct cg2900_endpoint_config_sco_in_out { + enum cg2900_endpoint_sample_rate sample_rate; +}; + +/** + * union cg2900_endpoint_config - Different audio endpoint configurations. + * @sco: SCO audio endpoint configuration structure. + * @a2dp_src: A2DP source audio endpoint configuration structure. + * @fm: FM audio endpoint configuration structure. + */ +union cg2900_endpoint_config_union { + struct cg2900_endpoint_config_sco_in_out sco; + struct cg2900_endpoint_config_a2dp_src a2dp_src; + struct cg2900_endpoint_config_fm fm; +}; + +/** + * struct cg2900_endpoint_config - Audio endpoint configuration. + * @endpoint_id: Identifies the audio endpoint. Works as a discriminator + * for the config union. + * @config: Union holding the configuration parameters for + * the endpoint. + */ +struct cg2900_endpoint_config { + enum cg2900_audio_endpoint_id endpoint_id; + union cg2900_endpoint_config_union config; +}; + +#ifdef __KERNEL__ +#include + +int cg2900_audio_get_devices(struct device *devices[], __u8 size); +int cg2900_audio_open(unsigned int *session, struct device *parent); +int cg2900_audio_close(unsigned int *session); +int cg2900_audio_set_dai_config(unsigned int session, + struct cg2900_dai_config *config); +int cg2900_audio_get_dai_config(unsigned int session, + struct cg2900_dai_config *config); +int cg2900_audio_config_endpoint(unsigned int session, + struct cg2900_endpoint_config *config); +int cg2900_audio_start_stream(unsigned int session, + enum cg2900_audio_endpoint_id ep_1, + enum cg2900_audio_endpoint_id ep_2, + unsigned int *stream_handle); +int cg2900_audio_stop_stream(unsigned int session, + unsigned int stream_handle); + +#endif /* __KERNEL__ */ +#endif /* _CG2900_AUDIO_H_ */ diff --git a/drivers/staging/cg2900/include/cg2900_hci.h b/drivers/staging/cg2900/include/cg2900_hci.h new file mode 100644 index 00000000000..e094a9dddbd --- /dev/null +++ b/drivers/staging/cg2900/include/cg2900_hci.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * This file is a staging solution and shall be integrated into + * /include/net/bluetooth/hci.h. + */ + +#ifndef __CG2900_HCI_H +#define __CG2900_HCI_H + +#define HCI_EV_HW_ERROR 0x10 +struct hci_ev_hw_error { + __u8 hw_code; +} __packed; + +#endif /* __CG2900_HCI_H */ diff --git a/drivers/staging/cg2900/mfd/Makefile b/drivers/staging/cg2900/mfd/Makefile new file mode 100644 index 00000000000..bdbd8de90ee --- /dev/null +++ b/drivers/staging/cg2900/mfd/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +ccflags-y := \ + -Idrivers/staging/cg2900/include + +obj-$(CONFIG_CG2900) += cg2900_core.o cg2900_lib.o +export-objs := cg2900_core.o cg2900_lib.o + +obj-$(CONFIG_CG2900) += cg2900_char_devices.o + +obj-$(CONFIG_CG2900_TEST) += cg2900_test.o + +obj-$(CONFIG_CG2900_CHIP) += cg2900_chip.o +obj-$(CONFIG_STLC2690_CHIP) += stlc2690_chip.o + +obj-$(CONFIG_CG2900_AUDIO) += cg2900_audio.o diff --git a/drivers/staging/cg2900/mfd/cg2900_audio.c b/drivers/staging/cg2900/mfd/cg2900_audio.c new file mode 100644 index 00000000000..6eadd96b2de --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_audio.c @@ -0,0 +1,3462 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth Audio Driver for ST-Ericsson CG2900 controller. + */ +#define NAME "cg2900_audio" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" +#include "cg2900_audio.h" +#include "cg2900_chip.h" + +#define MAX_NBR_OF_USERS 10 +#define FIRST_USER 1 + +#define DEFAULT_SCO_HANDLE 0x0008 + +/* Use a timeout of 5 seconds when waiting for a command response */ +#define RESP_TIMEOUT 5000 + +#define BT_DEV (info->dev_bt) +#define FM_DEV (info->dev_fm) + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* Used to select proper API, ignoring subrevisions etc */ +enum chip_revision { + CHIP_REV_PG1, + CHIP_REV_PG2 +}; + +/** + * enum chip_resp_state - State when communicating with the CG2900 controller. + * @IDLE: No outstanding packets to the controller. + * @WAITING: Packet has been sent to the controller. Waiting for + * response. + * @RESP_RECEIVED: Response from controller has been received but not yet + * handled. + */ +enum chip_resp_state { + IDLE, + WAITING, + RESP_RECEIVED +}; + +/** + * enum main_state - Main state for the CG2900 Audio driver. + * @OPENED: Audio driver has registered to CG2900 Core. + * @CLOSED: Audio driver is not registered to CG2900 Core. + * @RESET: A reset of CG2900 Core has occurred and no user has re-opened + * the audio driver. + */ +enum main_state { + OPENED, + CLOSED, + RESET +}; + +/** + * struct endpoint_list - List for storing endpoint configuration nodes. + * @ep_list: Pointer to first node in list. + * @management_mutex: Mutex for handling access to list. + */ +struct endpoint_list { + struct list_head ep_list; + struct mutex management_mutex; +}; + +/** + * struct endpoint_config_node - Node for storing endpoint configuration. + * @list: list_head struct. + * @endpoint_id: Endpoint ID. + * @config: Stored configuration for this endpoint. + */ +struct endpoint_config_node { + struct list_head list; + enum cg2900_audio_endpoint_id endpoint_id; + union cg2900_endpoint_config_union config; +}; + +/** + * struct audio_info - Main CG2900 Audio driver info structure. + * @list: list_head struct. + * @state: Current state of the CG2900 Audio driver. + * @revision: Chip revision, used to select API. + * @misc_dev: The misc device created by this driver. + * @misc_registered: True if misc device is registered. + * @parent: Parent device. + * @dev_bt: Device registered by this driver for the BT + * audio channel. + * @dev_fm: Device registered by this driver for the FM + * audio channel. + * @management_mutex: Mutex for handling access to CG2900 Audio driver + * management. + * @bt_mutex: Mutex for handling access to BT audio channel. + * @fm_mutex: Mutex for handling access to FM audio channel. + * @nbr_of_users_active: Number of sessions open in the CG2900 Audio + * driver. + * @i2s_config: DAI I2S configuration. + * @i2s_pcm_config: DAI PCM_I2S configuration. + * @i2s_config_known: @true if @i2s_config has been set, + * @false otherwise. + * @i2s_pcm_config_known: @true if @i2s_pcm_config has been set, + * @false otherwise. + * @endpoints: List containing the endpoint configurations. + * @stream_ids: Bitmask for in-use stream ids (only used with + * PG2 chip API). + */ +struct audio_info { + struct list_head list; + enum main_state state; + enum chip_revision revision; + struct miscdevice misc_dev; + bool misc_registered; + struct device *parent; + struct device *dev_bt; + struct device *dev_fm; + struct mutex management_mutex; + struct mutex bt_mutex; + struct mutex fm_mutex; + int nbr_of_users_active; + struct cg2900_dai_conf_i2s i2s_config; + struct cg2900_dai_conf_i2s_pcm i2s_pcm_config; + bool i2s_config_known; + bool i2s_pcm_config_known; + struct endpoint_list endpoints; + u8 stream_ids[16]; +}; + +/** + * struct audio_user - CG2900 audio user info structure. + * @session: Stored session for the char device. + * @resp_state: State for controller communications. + * @info: CG2900 audio info structure. + */ +struct audio_user { + int session; + enum chip_resp_state resp_state; + struct audio_info *info; +}; + +/** + * struct audio_cb_info - Callback info structure registered in @user_data. + * @user: Audio user currently awaiting data on the channel. + * @wq: Wait queue for this channel. + * @skb_queue: Sk buffer queue. + */ +struct audio_cb_info { + struct audio_user *user; + wait_queue_head_t wq; + struct sk_buff_head skb_queue; +}; + +/** + * struct char_dev_info - CG2900 character device info structure. + * @session: Stored session for the char device. + * @stored_data: Data returned when executing last command, if any. + * @stored_data_len: Length of @stored_data in bytes. + * @management_mutex: Mutex for handling access to char dev management. + * @rw_mutex: Mutex for handling access to char dev writes and reads. + * @info: CG2900 audio info struct. + * @rx_queue: Data queue. + */ +struct char_dev_info { + int session; + u8 *stored_data; + int stored_data_len; + struct mutex management_mutex; + struct mutex rw_mutex; + struct audio_info *info; + struct sk_buff_head rx_queue; +}; + +/* + * cg2900_audio_devices - List of active CG2900 audio devices. + */ +LIST_HEAD(cg2900_audio_devices); + +/* + * cg2900_audio_sessions - Pointers to currently opened sessions (maps + * session ID to user info). + */ +static struct audio_user *cg2900_audio_sessions[MAX_NBR_OF_USERS]; + +/* + * Internal conversion functions + * + * Since the CG2900 APIs uses several different ways to encode the + * same parameter in different cases, we have to use translator + * functions. + */ + +/** + * session_config_sample_rate() - Convert sample rate to format used in VS_Set_SessionConfiguration. + * @rate: Sample rate in API encoding. + */ +static u8 session_config_sample_rate(enum cg2900_endpoint_sample_rate rate) +{ + static const u8 codes[] = { + [ENDPOINT_SAMPLE_RATE_8_KHZ] = CG2900_BT_SESSION_RATE_8K, + [ENDPOINT_SAMPLE_RATE_16_KHZ] = CG2900_BT_SESSION_RATE_16K, + [ENDPOINT_SAMPLE_RATE_44_1_KHZ] = CG2900_BT_SESSION_RATE_44_1K, + [ENDPOINT_SAMPLE_RATE_48_KHZ] = CG2900_BT_SESSION_RATE_48K + }; + + return codes[rate]; +} + +/** + * mc_i2s_sample_rate() - Convert sample rate to format used in VS_Port_Config for I2S. + * @rate: Sample rate in API encoding. + */ +static u8 mc_i2s_sample_rate(enum cg2900_dai_sample_rate rate) +{ + static const u8 codes[] = { + [SAMPLE_RATE_8] = CG2900_MC_I2S_SAMPLE_RATE_8, + [SAMPLE_RATE_16] = CG2900_MC_I2S_SAMPLE_RATE_16, + [SAMPLE_RATE_44_1] = CG2900_MC_I2S_SAMPLE_RATE_44_1, + [SAMPLE_RATE_48] = CG2900_MC_I2S_SAMPLE_RATE_48 + }; + + return codes[rate]; +} + +/** + * mc_pcm_sample_rate() - Convert sample rate to format used in VS_Port_Config for PCM/I2S. + * @rate: Sample rate in API encoding. + */ +static u8 mc_pcm_sample_rate(enum cg2900_dai_sample_rate rate) +{ + static const u8 codes[] = { + [SAMPLE_RATE_8] = CG2900_MC_PCM_SAMPLE_RATE_8, + [SAMPLE_RATE_16] = CG2900_MC_PCM_SAMPLE_RATE_16, + [SAMPLE_RATE_44_1] = CG2900_MC_PCM_SAMPLE_RATE_44_1, + [SAMPLE_RATE_48] = CG2900_MC_PCM_SAMPLE_RATE_48 + }; + + return codes[rate]; +} + +/** + * mc_i2s_channel_select() - Convert channel selection to format used in VS_Port_Config. + * @sel: Channel selection in API encoding. + */ +static u8 mc_i2s_channel_select(enum cg2900_dai_channel_sel sel) +{ + static const u8 codes[] = { + [CHANNEL_SELECTION_RIGHT] = CG2900_MC_I2S_RIGHT_CHANNEL, + [CHANNEL_SELECTION_LEFT] = CG2900_MC_I2S_LEFT_CHANNEL, + [CHANNEL_SELECTION_BOTH] = CG2900_MC_I2S_BOTH_CHANNELS + }; + return codes[sel]; +} + +/** + * get_fs_duration() - Convert framesync-enumeration to real value. + * @duration: Framsync duration (API encoding). + * + * Returns: + * Duration in bits. + */ +static u16 get_fs_duration(enum cg2900_dai_fs_duration duration) +{ + static const u16 values[] = { + [SYNC_DURATION_8] = 8, + [SYNC_DURATION_16] = 16, + [SYNC_DURATION_24] = 24, + [SYNC_DURATION_32] = 32, + [SYNC_DURATION_48] = 48, + [SYNC_DURATION_50] = 50, + [SYNC_DURATION_64] = 64, + [SYNC_DURATION_75] = 75, + [SYNC_DURATION_96] = 96, + [SYNC_DURATION_125] = 125, + [SYNC_DURATION_128] = 128, + [SYNC_DURATION_150] = 150, + [SYNC_DURATION_192] = 192, + [SYNC_DURATION_250] = 250, + [SYNC_DURATION_256] = 256, + [SYNC_DURATION_300] = 300, + [SYNC_DURATION_384] = 384, + [SYNC_DURATION_500] = 500, + [SYNC_DURATION_512] = 512, + [SYNC_DURATION_600] = 600, + [SYNC_DURATION_768] = 768 + }; + return values[duration]; +} + +/** + * mc_i2s_role() - Convert master/slave encoding to format for I2S-ports. + * @mode: Master/slave in API encoding. + */ +static u8 mc_i2s_role(enum cg2900_dai_mode mode) +{ + if (mode == DAI_MODE_SLAVE) + return CG2900_I2S_MODE_SLAVE; + else + return CG2900_I2S_MODE_MASTER; +} + +/** + * mc_pcm_role() - Convert master/slave encoding to format for PCM/I2S-port. + * @mode: Master/slave in API encoding. + */ +static u8 mc_pcm_role(enum cg2900_dai_mode mode) +{ + if (mode == DAI_MODE_SLAVE) + return CG2900_PCM_MODE_SLAVE; + else + return CG2900_PCM_MODE_MASTER; +} + +/** + * fm_get_conversion() - Convert sample rate to convert up/down used in X_Set_Control FM commands. + * @srate: Sample rate. + */ +static u16 fm_get_conversion(enum cg2900_endpoint_sample_rate srate) +{ + if (srate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ) + return CG2900_FM_CMD_SET_CTRL_CONV_UP; + else + return CG2900_FM_CMD_SET_CTRL_CONV_DOWN; +} + +/** + * get_info() - Return info structure for this device. + * @dev: Current device. + * + * This function returns the info structure on the following basis: + * * If dev is NULL return first info struct found. If none is found return + * NULL. + * * If dev is valid we will return corresponding info struct if dev is the + * parent of the info struct or if dev's parent is the parent of the info + * struct. + * * If dev is valid and no info structure is found, a new info struct is + * allocated, initialized, and returned. + * + * Returns: + * Pointer to info struct if there is no error. + * NULL if NULL was supplied and no info structure exist. + * ERR_PTR(-ENOMEM) if allocation fails. + */ +static struct audio_info *get_info(struct device *dev) +{ + struct list_head *cursor; + struct audio_info *tmp; + struct audio_info *info = NULL; + + /* + * Find the info structure for dev. If NULL is supplied for dev + * just return first device found. + */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (!dev || tmp->parent == dev->parent || tmp->parent == dev) { + info = tmp; + break; + } + } + + if (!dev || info) + return info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev, "Could not allocate info struct\n"); + return ERR_PTR(-ENOMEM); + } + info->parent = dev->parent; + + /* Initiate the mutexes */ + mutex_init(&(info->management_mutex)); + mutex_init(&(info->bt_mutex)); + mutex_init(&(info->fm_mutex)); + mutex_init(&(info->endpoints.management_mutex)); + + /* Initiate the endpoint list */ + INIT_LIST_HEAD(&info->endpoints.ep_list); + + list_add_tail(&info->list, &cg2900_audio_devices); + + dev_info(dev, "CG2900 device added\n"); + return info; +} + +/** + * flush_endpoint_list() - Deletes all stored endpoints in @list. + * @list: List of endpoints. + */ +static void flush_endpoint_list(struct endpoint_list *list) +{ + struct list_head *cursor, *next; + struct endpoint_config_node *tmp; + + mutex_lock(&list->management_mutex); + list_for_each_safe(cursor, next, &(list->ep_list)) { + tmp = list_entry(cursor, struct endpoint_config_node, list); + list_del(cursor); + kfree(tmp); + } + mutex_unlock(&list->management_mutex); +} + +/** + * device_removed() - Remove device from list if there are no channels left. + * @info: CG2900 audio info structure. + */ +static void device_removed(struct audio_info *info) +{ + struct list_head *cursor; + struct audio_info *tmp; + + if (info->dev_bt || info->dev_fm) + /* There are still devices active */ + return; + + /* Find the stored info structure */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (tmp == info) { + list_del(cursor); + break; + } + } + + flush_endpoint_list(&info->endpoints); + + mutex_destroy(&info->management_mutex); + mutex_destroy(&info->bt_mutex); + mutex_destroy(&info->fm_mutex); + mutex_destroy(&info->endpoints.management_mutex); + + kfree(info); + pr_info("CG2900 Audio device removed"); +} + +/** + * read_cb() - Handle data received from STE connectivity driver. + * @dev: Device receiving data. + * @skb: Buffer with data coming form device. + */ +static void read_cb(struct cg2900_user_data *dev, struct sk_buff *skb) +{ + struct audio_cb_info *cb_info; + + cb_info = cg2900_get_usr(dev); + + if (!(cb_info->user)) { + dev_err(dev->dev, "NULL supplied as cb_info->user\n"); + return; + } + + /* Mark that packet has been received */ + dev_dbg(dev->dev, "New resp_state: RESP_RECEIVED"); + cb_info->user->resp_state = RESP_RECEIVED; + skb_queue_tail(&cb_info->skb_queue, skb); + wake_up_all(&cb_info->wq); +} + +/** + * reset_cb() - Reset callback function. + * @dev: CG2900_Core device resetting. + */ +static void reset_cb(struct cg2900_user_data *dev) +{ + struct audio_info *info; + + dev_dbg(dev->dev, "reset_cb\n"); + + info = dev_get_drvdata(dev->dev); + mutex_lock(&info->management_mutex); + info->nbr_of_users_active = 0; + info->state = RESET; + mutex_unlock(&info->management_mutex); +} + +/** + * get_session_user() - Check that supplied session is within valid range. + * @session: Session ID. + * + * Returns: + * Audio_user if there is no error. + * NULL for bad session ID. + */ +static struct audio_user *get_session_user(int session) +{ + struct audio_user *audio_user; + + if (session < FIRST_USER || session >= MAX_NBR_OF_USERS) { + pr_err("Calling with invalid session %d", session); + return NULL; + } + + audio_user = cg2900_audio_sessions[session]; + if (!audio_user) + pr_err("Calling with non-opened session %d", session); + return audio_user; +} + +/** + * del_endpoint_private() - Deletes an endpoint from @list. + * @endpoint_id: Endpoint ID. + * @list: List of endpoints. + * + * Deletes an endpoint from the supplied endpoint list. + * This function is not protected by any semaphore. + */ +static void del_endpoint_private(enum cg2900_audio_endpoint_id endpoint_id, + struct endpoint_list *list) +{ + struct list_head *cursor, *next; + struct endpoint_config_node *tmp; + + list_for_each_safe(cursor, next, &(list->ep_list)) { + tmp = list_entry(cursor, struct endpoint_config_node, list); + if (tmp->endpoint_id == endpoint_id) { + list_del(cursor); + kfree(tmp); + } + } +} + +/** + * add_endpoint() - Add endpoint node to @list. + * @ep_config: Endpoint configuration. + * @list: List of endpoints. + * + * Add endpoint node to the supplied list and copies supplied config to node. + * If a node already exists for the supplied endpoint, the old node is removed + * and replaced by the new node. + */ +static void add_endpoint(struct cg2900_endpoint_config *ep_config, + struct endpoint_list *list) +{ + struct endpoint_config_node *item; + + item = kzalloc(sizeof(*item), GFP_KERNEL); + if (!item) { + pr_err("add_endpoint: Failed to alloc memory"); + return; + } + + /* Store values */ + item->endpoint_id = ep_config->endpoint_id; + memcpy(&(item->config), &(ep_config->config), sizeof(item->config)); + + mutex_lock(&(list->management_mutex)); + + /* + * Check if endpoint ID already exist in list. + * If that is the case, remove it. + */ + if (!list_empty(&(list->ep_list))) + del_endpoint_private(ep_config->endpoint_id, list); + + list_add_tail(&(item->list), &(list->ep_list)); + + mutex_unlock(&(list->management_mutex)); +} + +/** + * find_endpoint() - Finds endpoint identified by @endpoint_id in @list. + * @endpoint_id: Endpoint ID. + * @list: List of endpoints. + * + * Returns: + * Endpoint configuration if there is no error. + * NULL if no configuration can be found for @endpoint_id. + */ +static union cg2900_endpoint_config_union * +find_endpoint(enum cg2900_audio_endpoint_id endpoint_id, + struct endpoint_list *list) +{ + struct list_head *cursor, *next; + struct endpoint_config_node *tmp; + struct endpoint_config_node *ret_ep = NULL; + + mutex_lock(&list->management_mutex); + list_for_each_safe(cursor, next, &(list->ep_list)) { + tmp = list_entry(cursor, struct endpoint_config_node, list); + if (tmp->endpoint_id == endpoint_id) { + ret_ep = tmp; + break; + } + } + mutex_unlock(&list->management_mutex); + + if (ret_ep) + return &(ret_ep->config); + else + return NULL; +} + +/** + * new_stream_id() - Allocate a new stream id. + * @info: Current audio info struct. + * + * Returns: + * 0-127 new valid id. + * -ENOMEM if no id is available. + */ +static s8 new_stream_id(struct audio_info *info) +{ + int r; + + mutex_lock(&info->management_mutex); + + r = find_first_zero_bit(info->stream_ids, + 8 * sizeof(info->stream_ids)); + + if (r >= 8 * sizeof(info->stream_ids)) { + r = -ENOMEM; + goto out; + } + + set_bit(r, (unsigned long int *)info->stream_ids); + +out: + mutex_unlock(&info->management_mutex); + return r; +} + +/** + * release_stream_id() - Release a stream id. + * @info: Current audio info struct. + * @id: Stream to release. + */ +static void release_stream_id(struct audio_info *info, u8 id) +{ + if (id >= 8 * sizeof(info->stream_ids)) + return; + + mutex_lock(&info->management_mutex); + clear_bit(id, (unsigned long int *)info->stream_ids); + mutex_unlock(&info->management_mutex); +} + +/** + * receive_fm_write_response() - Wait for and handle the response to an FM Legacy WriteCommand request. + * @audio_user: Audio user to check for. + * @command: FM command to wait for. + * + * This function first waits (up to 5 seconds) for a response to an FM + * write command and when one arrives, it checks that it is the one we + * are waiting for and also that no error has occurred. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int receive_fm_write_response(struct audio_user *audio_user, + u16 command) +{ + int err = 0; + int res; + struct sk_buff *skb; + struct fm_leg_cmd_cmpl *pkt; + u16 rsp_cmd; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = audio_user->info; + pf_data = dev_get_platdata(info->dev_fm); + cb_info = cg2900_get_usr(pf_data); + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + res = wait_event_timeout(cb_info->wq, + audio_user->resp_state == RESP_RECEIVED, + msecs_to_jiffies(RESP_TIMEOUT)); + if (!res) { + dev_err(FM_DEV, "Timeout while waiting for return packet\n"); + return -ECOMM; + } else if (res < 0) { + dev_err(FM_DEV, + "Error %d occurred while waiting for return packet\n", + res); + return -ECOMM; + } + + /* OK, now we should have received answer. Let's check it. */ + skb = skb_dequeue_tail(&cb_info->skb_queue); + if (!skb) { + dev_err(FM_DEV, "No skb in queue when it should be there\n"); + return -EIO; + } + + pkt = (struct fm_leg_cmd_cmpl *)skb->data; + + /* Check if we received the correct event */ + if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) { + dev_err(FM_DEV, + "Received unknown FM packet. 0x%X %X %X %X %X\n", + skb->data[0], skb->data[1], skb->data[2], + skb->data[3], skb->data[4]); + err = -EIO; + goto error_handling_free_skb; + } + + /* FM Legacy Command complete event */ + rsp_cmd = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->response_head)); + + if (pkt->fm_function != CG2900_FM_CMD_PARAM_WRITECOMMAND || + rsp_cmd != command) { + dev_err(FM_DEV, + "Received unexpected packet func 0x%X cmd 0x%04X\n", + pkt->fm_function, rsp_cmd); + err = -EIO; + goto error_handling_free_skb; + } + + if (pkt->cmd_status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) { + dev_err(FM_DEV, "FM Command failed (%d)\n", pkt->cmd_status); + err = -EIO; + goto error_handling_free_skb; + } + /* Operation succeeded. We are now done */ + +error_handling_free_skb: + kfree_skb(skb); + return err; +} + +/** + * receive_bt_cmd_complete() - Wait for and handle an BT Command Complete event. + * @audio_user: Audio user to check for. + * @rsp: Opcode of BT command to wait for. + * @data: Pointer to buffer if any received data should be stored (except + * status). + * @data_len: Length of @data in bytes. + * + * This function first waits for BT Command Complete event (up to 5 seconds) + * and when one arrives, it checks that it is the one we are waiting for and + * also that no error has occurred. + * If @data is supplied it also copies received data into @data. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int receive_bt_cmd_complete(struct audio_user *audio_user, u16 rsp, + void *data, int data_len) +{ + int err = 0; + int res; + struct sk_buff *skb; + struct bt_cmd_cmpl_event *evt; + u16 opcode; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = audio_user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + res = wait_event_timeout(cb_info->wq, + audio_user->resp_state == RESP_RECEIVED, + msecs_to_jiffies(RESP_TIMEOUT)); + if (!res) { + dev_err(BT_DEV, "Timeout while waiting for return packet\n"); + return -ECOMM; + } else if (res < 0) { + /* We timed out or an error occurred */ + dev_err(BT_DEV, + "Error %d occurred while waiting for return packet\n", + res); + return -ECOMM; + } + + /* OK, now we should have received answer. Let's check it. */ + skb = skb_dequeue_tail(&cb_info->skb_queue); + if (!skb) { + dev_err(BT_DEV, "No skb in queue when it should be there\n"); + return -EIO; + } + + evt = (struct bt_cmd_cmpl_event *)skb->data; + if (evt->eventcode != HCI_EV_CMD_COMPLETE) { + dev_err(BT_DEV, + "We did not receive the event we expected (0x%X)\n", + evt->eventcode); + err = -EIO; + goto error_handling_free_skb; + } + + opcode = le16_to_cpu(evt->opcode); + if (opcode != rsp) { + dev_err(BT_DEV, + "Received cmd complete for unexpected command: " + "0x%04X\n", opcode); + err = -EIO; + goto error_handling_free_skb; + } + + if (evt->status != HCI_BT_ERROR_NO_ERROR) { + dev_err(BT_DEV, "Received command complete with err %d\n", + evt->status); + err = -EIO; + /* + * In data there might be more detailed error code. + * Let's copy it. + */ + } + + /* + * Copy the rest of the parameters if a buffer has been supplied. + * The caller must have set the length correctly. + */ + if (data) + memcpy(data, evt->data, data_len); + + /* Operation succeeded. We are now done */ + +error_handling_free_skb: + kfree_skb(skb); + return err; +} + +/** + * send_vs_delete_stream() - Delete an audio stream defined by @stream_handle. + * @audio_user: Audio user to check for. + * @stream_handle: Handle of the audio stream. + * + * This function is used to delete an audio stream defined by a stream + * handle. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * Errors from @cg2900_write. + * -EIO for other errors. + */ +static int send_vs_delete_stream(struct audio_user *audio_user, + unsigned int stream_handle) +{ + int err = 0; + struct sk_buff *skb; + u16 opcode; + struct audio_info *info = audio_user->info; + struct cg2900_user_data *pf_data = dev_get_platdata(info->dev_bt); + struct audio_cb_info *cb_info = cg2900_get_usr(pf_data); + + /* Now delete the stream - format command... */ + if (info->revision == CHIP_REV_PG1) { + struct bt_vs_reset_session_cfg_cmd *cmd; + + dev_dbg(BT_DEV, "BT: HCI_VS_Reset_Session_Configuration\n"); + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "Could not allocate skb\n"); + err = -ENOMEM; + return err; + } + + cmd = (struct bt_vs_reset_session_cfg_cmd *) + skb_put(skb, sizeof(*cmd)); + + opcode = CG2900_BT_VS_RESET_SESSION_CONFIG; + cmd->opcode = cpu_to_le16(opcode); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->id = (u8)stream_handle; + } else { + struct mc_vs_delete_stream_cmd *cmd; + + dev_dbg(BT_DEV, "BT: HCI_VS_Delete_Stream\n"); + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "Could not allocate skb\n"); + err = -ENOMEM; + return err; + } + + cmd = (struct mc_vs_delete_stream_cmd *) + skb_put(skb, sizeof(*cmd)); + + opcode = CG2900_MC_VS_DELETE_STREAM; + cmd->opcode = cpu_to_le16(opcode); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->stream = (u8)stream_handle; + } + + /* ...and send it */ + cb_info->user = audio_user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + audio_user->resp_state = WAITING; + + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + goto error_handling_free_skb; + } + + /* wait for response */ + if (info->revision == CHIP_REV_PG1) { + err = receive_bt_cmd_complete(audio_user, opcode, NULL, 0); + } else { + u8 vs_err; + + /* All commands in PG2 API returns one byte extra status */ + err = receive_bt_cmd_complete(audio_user, opcode, + &vs_err, sizeof(vs_err)); + + if (err) + dev_err(BT_DEV, + "VS_DELETE_STREAM - failed with error 0x%02X\n", + vs_err); + else + release_stream_id(info, stream_handle); + } + + return err; + +error_handling_free_skb: + kfree_skb(skb); + return err; +} + +/** + * send_vs_session_ctrl() - Formats an sends a CG2900_BT_VS_SESSION_CTRL command. + * @user: Audio user this command belongs to. + * @stream_handle: Handle to stream. + * @command: Command to execute on stream, should be one of + * CG2900_BT_SESSION_START, CG2900_BT_SESSION_STOP, + * CG2900_BT_SESSION_PAUSE, CG2900_BT_SESSION_RESUME. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_session_ctrl(struct audio_user *user, + u8 stream_handle, u8 command) +{ + int err = 0; + struct bt_vs_session_ctrl_cmd *pkt; + struct sk_buff *skb; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "BT: HCI_VS_Session_Control handle: %d cmd: %d\n", + stream_handle, command); + + skb = pf_data->alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_session_ctrl: Could not allocate skb\n"); + return -ENOMEM; + } + + /* Enter data into the skb */ + pkt = (struct bt_vs_session_ctrl_cmd *) skb_put(skb, sizeof(*pkt)); + + pkt->opcode = cpu_to_le16(CG2900_BT_VS_SESSION_CTRL); + pkt->plen = BT_PARAM_LEN(sizeof(*pkt)); + pkt->id = stream_handle; + pkt->control = command; /* Start/stop etc */ + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_bt_cmd_complete(user, CG2900_BT_VS_SESSION_CTRL, + NULL, 0); +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_session_config() - Formats an sends a CG2900_BT_VS_SESSION_CONFIG command. + * @user: Audio user this command belongs to. + * @config_stream: Custom function for configuring the stream. + * @priv_data: Private data passed to @config_stream untouched. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Space is allocated for one stream and a custom function is used to + * fill in the stream configuration. + * + * Returns: + * 0-255 stream handle if no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_session_config(struct audio_user *user, + void(*config_stream)(struct audio_info *, void *, + struct session_config_stream *), + void *priv_data) +{ + int err = 0; + struct sk_buff *skb; + struct bt_vs_session_config_cmd *pkt; + u8 session_id; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "BT: HCI_VS_Set_Session_Configuration\n"); + + skb = pf_data->alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_session_config: Could not allocate skb\n"); + return -ENOMEM; + } + + pkt = (struct bt_vs_session_config_cmd *)skb_put(skb, sizeof(*pkt)); + /* zero the packet so we don't have to set all reserved fields */ + memset(pkt, 0, sizeof(*pkt)); + + /* Common parameters */ + pkt->opcode = cpu_to_le16(CG2900_BT_VS_SET_SESSION_CONFIG); + pkt->plen = BT_PARAM_LEN(sizeof(*pkt)); + pkt->n_streams = 1; /* 1 stream configuration supplied */ + + /* Let the custom-function fill in the rest */ + config_stream(info, priv_data, &pkt->stream); + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_bt_cmd_complete(user, + CG2900_BT_VS_SET_SESSION_CONFIG, + &session_id, sizeof(session_id)); + /* Return session id/stream handle if success */ + if (!err) + err = session_id; + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_fm_write_1_param() - Formats and sends an FM legacy write command with one parameter. + * @user: Audio user this command belongs to. + * @command: Command. + * @param: Parameter for command. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the fm_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_fm_write_1_param(struct audio_user *user, + u16 command, u16 param) +{ + int err = 0; + struct sk_buff *skb; + struct fm_leg_cmd *cmd; + size_t len; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_fm); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(FM_DEV, "send_fm_write_1_param cmd 0x%X param 0x%X\n", + command, param); + + /* base package + one parameter */ + len = sizeof(*cmd) + sizeof(cmd->fm_cmd.data[0]); + + skb = pf_data->alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(FM_DEV, + "send_fm_write_1_param: Could not allocate skb\n"); + return -ENOMEM; + } + + cmd = (struct fm_leg_cmd *)skb_put(skb, len); + + cmd->length = CG2900_FM_CMD_PARAM_LEN(len); + cmd->opcode = CG2900_FM_GEN_ID_LEGACY; + cmd->read_write = CG2900_FM_CMD_LEG_PARAM_WRITE; + cmd->fm_function = CG2900_FM_CMD_PARAM_WRITECOMMAND; + /* one parameter - builtin assumption for this function */ + cmd->fm_cmd.head = cpu_to_le16(cg2900_make_fm_cmd_id(command, 1)); + cmd->fm_cmd.data[0] = cpu_to_le16(param); + + cb_info->user = user; + dev_dbg(FM_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(FM_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_fm_write_response(user, command); +finished: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_stream_ctrl() - Formats an sends a CG2900_MC_VS_STREAM_CONTROL command. + * @user: Audio user this command belongs to. + * @stream: Stream id. + * @command: Start/stop etc. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * While the HCI command allows for multiple streams in one command, + * this function only handles one. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_stream_ctrl(struct audio_user *user, u8 stream, u8 command) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_stream_ctrl_cmd *cmd; + size_t len; + u8 vs_err; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "send_vs_stream_ctrl stream %d command %d\n", stream, + command); + + /* basic length + one stream */ + len = sizeof(*cmd) + sizeof(cmd->stream[0]); + + skb = pf_data->alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "send_vs_stream_ctrl:Could not allocate skb\n"); + return -ENOMEM; + } + + cmd = (struct mc_vs_stream_ctrl_cmd *)skb_put(skb, len); + + cmd->opcode = cpu_to_le16(CG2900_MC_VS_STREAM_CONTROL); + cmd->plen = BT_PARAM_LEN(len); + cmd->command = command; + + /* one stream */ + cmd->n_streams = 1; + cmd->stream[0] = stream; + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_STREAM_CONTROL, + &vs_err, sizeof(vs_err)); + if (err) + dev_err(BT_DEV, + "VS_STREAM_CONTROL - failed with error 0x%02x\n", + vs_err); + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_create_stream() - Formats an sends a CG2900_MC_VS_CREATE_STREAM command. + * @user: Audio user this command belongs to. + * @inport: Stream id. + * @outport: Start/stop etc. + * @order: Activation order. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_create_stream(struct audio_user *user, u8 inport, + u8 outport, u8 order) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_create_stream_cmd *cmd; + s8 id; + u8 vs_err; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, + "send_vs_create_stream inport %d outport %d order %d\n", + inport, outport, order); + + id = new_stream_id(info); + if (id < 0) { + dev_err(BT_DEV, "No free stream id\n"); + err = -EIO; + goto finished; + } + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_create_stream: Could not allocate skb\n"); + err = -ENOMEM; + goto finished_release_id; + } + + cmd = (struct mc_vs_create_stream_cmd *)skb_put(skb, sizeof(*cmd)); + + cmd->opcode = cpu_to_le16(CG2900_MC_VS_CREATE_STREAM); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->id = (u8)id; + cmd->inport = inport; + cmd->outport = outport; + cmd->order = order; + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished_release_id; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_CREATE_STREAM, + &vs_err, sizeof(vs_err)); + if (err) { + dev_err(BT_DEV, + "VS_CREATE_STREAM - failed with error 0x%02x\n", + vs_err); + goto finished_release_id; + } + + err = id; + goto finished; + +finished_release_id: + release_stream_id(info, id); +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * send_vs_port_cfg() - Formats an sends a CG2900_MC_VS_PORT_CONFIG command. + * @user: Audio user this command belongs to. + * @port: Port id to configure. + * @cfg: Pointer to specific configuration. + * @cfglen: Length of configuration. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_port_cfg(struct audio_user *user, u8 port, + const void *cfg, size_t cfglen) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_port_cfg_cmd *cmd; + void *ptr; + u8 vs_err; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "send_vs_port_cfg len %d\n", cfglen); + + skb = pf_data->alloc_skb(sizeof(*cmd) + cfglen, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "send_vs_port_cfg: Could not allocate skb\n"); + return -ENOMEM; + } + + /* Fill in common part */ + cmd = (struct mc_vs_port_cfg_cmd *) skb_put(skb, sizeof(*cmd)); + cmd->opcode = cpu_to_le16(CG2900_MC_VS_PORT_CONFIG); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd) + cfglen); + cmd->type = port; + + /* Copy specific configuration */ + ptr = skb_put(skb, cfglen); + memcpy(ptr, cfg, cfglen); + + /* Send */ + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, CG2900_MC_VS_PORT_CONFIG, + &vs_err, sizeof(vs_err)); + if (err) + dev_err(BT_DEV, "VS_PORT_CONFIG - failed with error 0x%02x\n", + vs_err); + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + user->resp_state = IDLE; + return err; +} + +/** + * set_dai_config_pg1() - Internal implementation of @cg2900_audio_set_dai_config for PG1 hardware. + * @audio_user: Pointer to audio user struct. + * @config: Pointer to the configuration to set. + * + * Sets the Digital Audio Interface (DAI) configuration for PG1 + * hardware. This is and internal function and basic + * argument-verification should have been done by the caller. + * + * Returns: + * 0 if there is no error. + * -EACCESS if port is not supported. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int set_dai_config_pg1(struct audio_user *audio_user, + struct cg2900_dai_config *config) +{ + int err = 0; + struct cg2900_dai_conf_i2s_pcm *i2s_pcm; + struct sk_buff *skb = NULL; + struct bt_vs_set_hw_cfg_cmd_i2s *i2s_cmd; + struct bt_vs_set_hw_cfg_cmd_pcm *pcm_cmd; + struct audio_info *info = audio_user->info; + struct cg2900_user_data *pf_data = dev_get_platdata(info->dev_bt); + struct audio_cb_info *cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "set_dai_config_pg1 port %d\n", config->port); + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&info->bt_mutex); + + /* Allocate the sk_buffer. The length is actually a max length since + * length varies depending on logical transport. + */ + skb = pf_data->alloc_skb(CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG, + GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "set_dai_config_pg1: Could not allocate skb\n"); + err = -ENOMEM; + goto finished_unlock_mutex; + } + + /* Fill in hci-command according to received configuration */ + switch (config->port) { + case PORT_0_I2S: + i2s_cmd = (struct bt_vs_set_hw_cfg_cmd_i2s *) + skb_put(skb, sizeof(*i2s_cmd)); + + i2s_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG); + i2s_cmd->plen = BT_PARAM_LEN(sizeof(*i2s_cmd)); + + i2s_cmd->vp_type = PORT_PROTOCOL_I2S; + i2s_cmd->port_id = 0x00; /* First/only I2S port */ + i2s_cmd->half_period = config->conf.i2s.half_period; + + i2s_cmd->master_slave = mc_i2s_role(config->conf.i2s.mode); + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&info->i2s_config, &config->conf.i2s, + sizeof(config->conf.i2s)); + info->i2s_config_known = true; + mutex_unlock(&info->management_mutex); + break; + + case PORT_1_I2S_PCM: + pcm_cmd = (struct bt_vs_set_hw_cfg_cmd_pcm *) + skb_put(skb, sizeof(*pcm_cmd)); + + pcm_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG); + pcm_cmd->plen = BT_PARAM_LEN(sizeof(*pcm_cmd)); + + i2s_pcm = &config->conf.i2s_pcm; + + /* + * PG1 chips don't support I2S over the PCM/I2S bus, + * and PG2 chips don't use this command + */ + if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) { + dev_err(BT_DEV, + "I2S not supported over the PCM/I2S bus\n"); + err = -EACCES; + goto error_handling_free_skb; + } + + pcm_cmd->vp_type = PORT_PROTOCOL_PCM; + pcm_cmd->port_id = 0x00; /* First/only PCM port */ + + HWCONFIG_PCM_SET_MODE(pcm_cmd, mc_pcm_role(i2s_pcm->mode)); + + HWCONFIG_PCM_SET_DIR(pcm_cmd, 0, i2s_pcm->slot_0_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 1, i2s_pcm->slot_1_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 2, i2s_pcm->slot_2_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 3, i2s_pcm->slot_3_dir); + + pcm_cmd->bit_clock = i2s_pcm->clk; + pcm_cmd->frame_len = + cpu_to_le16(get_fs_duration(i2s_pcm->duration)); + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&info->i2s_pcm_config, &config->conf.i2s_pcm, + sizeof(config->conf.i2s_pcm)); + info->i2s_pcm_config_known = true; + mutex_unlock(&info->management_mutex); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EACCES; + goto error_handling_free_skb; + }; + + cb_info->user = audio_user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + audio_user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + goto error_handling_free_skb; + } + + err = receive_bt_cmd_complete(audio_user, + CG2900_BT_VS_SET_HARDWARE_CONFIG, + NULL, 0); + + goto finished_unlock_mutex; + +error_handling_free_skb: + kfree_skb(skb); +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * set_dai_config_pg2() - Internal implementation of @cg2900_audio_set_dai_config for PG2 hardware. + * @audio_user: Pointer to audio user struct. + * @config: Pointer to the configuration to set. + * + * Sets the Digital Audio Interface (DAI) configuration for PG2 + * hardware. This is an internal function and basic + * argument-verification should have been done by the caller. + * + * Returns: + * 0 if there is no error. + * -EACCESS if port is not supported. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int set_dai_config_pg2(struct audio_user *audio_user, + struct cg2900_dai_config *config) +{ + int err = 0; + struct cg2900_dai_conf_i2s *i2s; + struct cg2900_dai_conf_i2s_pcm *i2s_pcm; + + struct mc_vs_port_cfg_i2s i2s_cfg; + struct mc_vs_port_cfg_pcm_i2s pcm_cfg; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "set_dai_config_pg2 port %d\n", config->port); + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&info->bt_mutex); + + switch (config->port) { + case PORT_0_I2S: + i2s = &config->conf.i2s; + + memset(&i2s_cfg, 0, sizeof(i2s_cfg)); /* just to be safe */ + + /* master/slave */ + PORTCFG_I2S_SET_ROLE(i2s_cfg, mc_i2s_role(i2s->mode)); + + PORTCFG_I2S_SET_HALFPERIOD(i2s_cfg, i2s->half_period); + PORTCFG_I2S_SET_CHANNELS(i2s_cfg, + mc_i2s_channel_select(i2s->channel_sel)); + PORTCFG_I2S_SET_SRATE(i2s_cfg, + mc_i2s_sample_rate(i2s->sample_rate)); + switch (i2s->word_width) { + case WORD_WIDTH_16: + PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_16); + break; + case WORD_WIDTH_32: + PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_32); + break; + } + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&(info->i2s_config), &(config->conf.i2s), + sizeof(config->conf.i2s)); + info->i2s_config_known = true; + mutex_unlock(&info->management_mutex); + + /* Send */ + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_I2S, + &i2s_cfg, sizeof(i2s_cfg)); + break; + + case PORT_1_I2S_PCM: + i2s_pcm = &config->conf.i2s_pcm; + + memset(&pcm_cfg, 0, sizeof(pcm_cfg)); /* just to be safe */ + + /* master/slave */ + PORTCFG_PCM_SET_ROLE(pcm_cfg, mc_pcm_role(i2s_pcm->mode)); + + /* set direction for all 4 slots */ + PORTCFG_PCM_SET_DIR(pcm_cfg, 0, i2s_pcm->slot_0_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 1, i2s_pcm->slot_1_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 2, i2s_pcm->slot_2_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 3, i2s_pcm->slot_3_dir); + + /* set used SCO slots, other use cases not supported atm */ + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 0, i2s_pcm->slot_0_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 1, i2s_pcm->slot_1_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 2, i2s_pcm->slot_2_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 3, i2s_pcm->slot_3_used); + + /* slot starts */ + pcm_cfg.slot_start[0] = i2s_pcm->slot_0_start; + pcm_cfg.slot_start[1] = i2s_pcm->slot_1_start; + pcm_cfg.slot_start[2] = i2s_pcm->slot_2_start; + pcm_cfg.slot_start[3] = i2s_pcm->slot_3_start; + + /* audio/voice sample-rate ratio */ + PORTCFG_PCM_SET_RATIO(pcm_cfg, i2s_pcm->ratio); + + /* PCM or I2S mode */ + PORTCFG_PCM_SET_MODE(pcm_cfg, i2s_pcm->protocol); + + pcm_cfg.frame_len = i2s_pcm->duration; + + PORTCFG_PCM_SET_BITCLK(pcm_cfg, i2s_pcm->clk); + PORTCFG_PCM_SET_SRATE(pcm_cfg, + mc_pcm_sample_rate(i2s_pcm->sample_rate)); + + /* Store the new configuration */ + mutex_lock(&info->management_mutex); + memcpy(&(info->i2s_pcm_config), &(config->conf.i2s_pcm), + sizeof(config->conf.i2s_pcm)); + info->i2s_pcm_config_known = true; + mutex_unlock(&info->management_mutex); + + /* Send */ + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_PCM_I2S, + &pcm_cfg, sizeof(pcm_cfg)); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EACCES; + }; + + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * struct i2s_fm_stream_config_priv - Helper struct for stream i2s-fm streams. + * @fm_config: FM endpoint configuration. + * @rx: true for FM-RX, false for FM-TX. + */ +struct i2s_fm_stream_config_priv { + struct cg2900_endpoint_config_fm *fm_config; + bool rx; + +}; + +/** + * config_i2s_fm_stream() - Callback for @send_vs_session_config. + * @info: Audio info structure. + * @_priv: Pointer to a @i2s_fm_stream_config_priv struct. + * @cfg: Pointer to stream config block in command packet. + * + * Fills in stream configuration for I2S-FM RX/TX. + */ + +static void config_i2s_fm_stream(struct audio_info *info, void *_priv, + struct session_config_stream *cfg) +{ + struct i2s_fm_stream_config_priv *priv = _priv; + struct session_config_vport *fm; + struct session_config_vport *i2s; + + cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO; + + if (info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH) + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_STEREO); + else + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO); + + SESSIONCFG_I2S_SET_SRATE(cfg, + session_config_sample_rate(priv->fm_config->sample_rate)); + + cfg->codec_type = CG2900_CODEC_TYPE_NONE; + /* codec mode and parameters not used */ + + if (priv->rx) { + fm = &cfg->inport; /* FM is input */ + i2s = &cfg->outport; /* I2S is output */ + } else { + i2s = &cfg->inport; /* I2S is input */ + fm = &cfg->outport; /* FM is output */ + } + + fm->type = CG2900_BT_VP_TYPE_FM; + + i2s->type = CG2900_BT_VP_TYPE_I2S; + i2s->i2s.index = CG2900_BT_SESSION_I2S_INDEX_I2S; + i2s->i2s.channel = info->i2s_config.channel_sel; +} + +/** + * conn_start_i2s_to_fm_rx() - Start an audio stream connecting FM RX to I2S. + * @audio_user: Audio user to check for. + * @stream_handle: [out] Pointer where to store the stream handle. + * + * This function sets up an FM RX to I2S stream. + * It does this by first setting the output mode and then the configuration of + * the External Sample Rate Converter. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * -EIO for other errors. + */ +static int conn_start_i2s_to_fm_rx(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *fm_config; + struct audio_info *info = audio_user->info; + + dev_dbg(FM_DEV, "conn_start_i2s_to_fm_rx\n"); + + fm_config = find_endpoint(ENDPOINT_FM_RX, &info->endpoints); + if (!fm_config) { + dev_err(FM_DEV, "FM RX not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_config_known)) { + dev_err(FM_DEV, + "I2S DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any + * time on each channel. + */ + mutex_lock(&info->fm_mutex); + mutex_lock(&info->bt_mutex); + + /* + * Now set the output mode of the External Sample Rate Converter by + * sending HCI_Write command with AUP_EXT_SetMode. + */ + err = send_fm_write_1_param(audio_user, + CG2900_FM_CMD_ID_AUP_EXT_SET_MODE, + CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL); + if (err) + goto finished_unlock_mutex; + + /* + * Now configure the External Sample Rate Converter by sending + * HCI_Write command with AUP_EXT_SetControl. + */ + err = send_fm_write_1_param( + audio_user, CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL, + fm_get_conversion(fm_config->fm.sample_rate)); + if (err) + goto finished_unlock_mutex; + + /* Set up the stream */ + if (info->revision == CHIP_REV_PG1) { + struct i2s_fm_stream_config_priv stream_priv; + + /* Now send HCI_VS_Set_Session_Configuration command */ + stream_priv.fm_config = &fm_config->fm; + stream_priv.rx = true; + err = send_vs_session_config(audio_user, config_i2s_fm_stream, + &stream_priv); + } else { + struct mc_vs_port_cfg_fm fm_cfg; + + memset(&fm_cfg, 0, sizeof(fm_cfg)); + + /* Configure port FM RX */ + /* Expects 0-3 - same as user API - so no conversion needed */ + PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_RX_1, + &fm_cfg, sizeof(fm_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_FM_RX_1, + CG2900_MC_PORT_I2S, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + dev_dbg(FM_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + /*Let's delete a stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + mutex_unlock(&info->fm_mutex); + return err; +} + +/** + * conn_start_i2s_to_fm_tx() - Start an audio stream connecting FM TX to I2S. + * @audio_user: Audio user to check for. + * @stream_handle: [out] Pointer where to store the stream handle. + * + * This function sets up an I2S to FM TX stream. + * It does this by first setting the Audio Input source and then setting the + * configuration and input source of BT sample rate converter. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * -EIO for other errors. + */ +static int conn_start_i2s_to_fm_tx(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *fm_config; + struct audio_info *info = audio_user->info; + + dev_dbg(FM_DEV, "conn_start_i2s_to_fm_tx\n"); + + fm_config = find_endpoint(ENDPOINT_FM_TX, &info->endpoints); + if (!fm_config) { + dev_err(FM_DEV, "FM TX not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_config_known)) { + dev_err(FM_DEV, + "I2S DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any time + * on each channel. + */ + mutex_lock(&info->fm_mutex); + mutex_lock(&info->bt_mutex); + + /* + * Select Audio Input Source by sending HCI_Write command with + * AIP_SetMode. + */ + dev_dbg(FM_DEV, "FM: AIP_SetMode\n"); + err = send_fm_write_1_param(audio_user, CG2900_FM_CMD_ID_AIP_SET_MODE, + CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG); + if (err) + goto finished_unlock_mutex; + + /* + * Now configure the BT sample rate converter by sending HCI_Write + * command with AIP_BT_SetControl. + */ + dev_dbg(FM_DEV, "FM: AIP_BT_SetControl\n"); + err = send_fm_write_1_param( + audio_user, CG2900_FM_CMD_ID_AIP_BT_SET_CTRL, + fm_get_conversion(fm_config->fm.sample_rate)); + if (err) + goto finished_unlock_mutex; + + /* + * Now set input of the BT sample rate converter by sending HCI_Write + * command with AIP_BT_SetMode. + */ + dev_dbg(FM_DEV, "FM: AIP_BT_SetMode\n"); + err = send_fm_write_1_param(audio_user, + CG2900_FM_CMD_ID_AIP_BT_SET_MODE, + CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR); + if (err) + goto finished_unlock_mutex; + + /* Set up the stream */ + if (info->revision == CHIP_REV_PG1) { + struct i2s_fm_stream_config_priv stream_priv; + + /* Now send HCI_VS_Set_Session_Configuration command */ + stream_priv.fm_config = &fm_config->fm; + stream_priv.rx = false; + err = send_vs_session_config(audio_user, config_i2s_fm_stream, + &stream_priv); + } else { + struct mc_vs_port_cfg_fm fm_cfg; + + memset(&fm_cfg, 0, sizeof(fm_cfg)); + + /* Configure port FM TX */ + /* Expects 0-3 - same as user API - so no conversion needed */ + PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_TX, + &fm_cfg, sizeof(fm_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_I2S, + CG2900_MC_PORT_FM_TX, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + dev_dbg(FM_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + /* Let's delete and release stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + mutex_unlock(&info->fm_mutex); + return err; +} + +/** + * config_pcm_sco_stream() - Callback for @send_vs_session_config. + * @info: Audio info structure. + * @_priv: Pointer to a @cg2900_endpoint_config_sco_in_out struct. + * @cfg: Pointer to stream config block in command packet. + * + * Fills in stream configuration for PCM-SCO. + */ +static void config_pcm_sco_stream(struct audio_info *info, void *_priv, + struct session_config_stream *cfg) +{ + struct cg2900_endpoint_config_sco_in_out *sco_ep = _priv; + + cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO; + + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO); + SESSIONCFG_I2S_SET_SRATE(cfg, + session_config_sample_rate(sco_ep->sample_rate)); + + cfg->codec_type = CG2900_CODEC_TYPE_NONE; + /* codec mode and parameters not used */ + + cfg->inport.type = CG2900_BT_VP_TYPE_BT_SCO; + cfg->inport.sco.acl_handle = cpu_to_le16(DEFAULT_SCO_HANDLE); + + cfg->outport.type = CG2900_BT_VP_TYPE_PCM; + cfg->outport.pcm.index = CG2900_BT_SESSION_PCM_INDEX_PCM_I2S; + + SESSIONCFG_PCM_SET_USED(cfg->outport, 0, + info->i2s_pcm_config.slot_0_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 1, + info->i2s_pcm_config.slot_1_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 2, + info->i2s_pcm_config.slot_2_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 3, + info->i2s_pcm_config.slot_3_used); + + cfg->outport.pcm.slot_start[0] = + info->i2s_pcm_config.slot_0_start; + cfg->outport.pcm.slot_start[1] = + info->i2s_pcm_config.slot_1_start; + cfg->outport.pcm.slot_start[2] = + info->i2s_pcm_config.slot_2_start; + cfg->outport.pcm.slot_start[3] = + info->i2s_pcm_config.slot_3_start; +} + +/** + * conn_start_pcm_to_sco() - Start an audio stream connecting Bluetooth (e)SCO to PCM_I2S. + * @audio_user: Audio user to check for. + * @stream_handle: [out] Pointer where to store the stream handle. + * + * This function sets up a BT to_from PCM_I2S stream. It does this by + * first setting the Session configuration and then starting the Audio + * Stream. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * Errors from @cg2900_write + * -EIO for other errors. + */ +static int conn_start_pcm_to_sco(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *bt_config; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "conn_start_pcm_to_sco\n"); + + bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT, &info->endpoints); + if (!bt_config) { + dev_err(BT_DEV, "BT not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_pcm_config_known)) { + dev_err(BT_DEV, + "I2S_PCM DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any time on each + * channel. + */ + mutex_lock(&info->bt_mutex); + + /* Set up the stream */ + if (info->revision == CHIP_REV_PG1) { + err = send_vs_session_config(audio_user, config_pcm_sco_stream, + &bt_config->sco); + } else { + struct mc_vs_port_cfg_sco sco_cfg; + + /* zero codec params etc */ + memset(&sco_cfg, 0, sizeof(sco_cfg)); + sco_cfg.acl_id = DEFAULT_SCO_HANDLE; + PORTCFG_SCO_SET_WBS(sco_cfg, 0); /* No WBS yet */ + PORTCFG_SCO_SET_CODEC(sco_cfg, CG2900_CODEC_TYPE_NONE); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_BT_SCO, + &sco_cfg, sizeof(sco_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_PCM_I2S, + CG2900_MC_PORT_BT_SCO, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + dev_dbg(BT_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + /* Let's delete and release stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * conn_stop_stream() - Stops an audio stream defined by @stream_handle. + * @audio_user: Audio user to check for. + * @stream_handle: Handle of the audio stream. + * + * This function is used to stop an audio stream defined by a stream + * handle. It does this by first stopping the stream and then + * resetting the session/stream. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * Errors from @cg2900_write. + * -EIO for other errors. + */ +static int conn_stop_stream(struct audio_user *audio_user, + unsigned int stream_handle) +{ + int err = 0; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "conn_stop_stream handle %d\n", stream_handle); + + /* + * Use mutex to assure that only ONE command is sent at any + * time on each channel. + */ + mutex_lock(&info->bt_mutex); + + /* Now stop the stream */ + if (info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, stream_handle, + CG2900_BT_SESSION_STOP); + else + err = send_vs_stream_ctrl(audio_user, stream_handle, + CG2900_MC_STREAM_STOP); + if (err) + goto finished_unlock_mutex; + + err = send_vs_delete_stream(audio_user, stream_handle); + +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * cg2900_audio_get_devices() - Returns connected CG2900 Audio devices. + * @devices: Array of CG2900 Audio devices. + * @size: Max number of devices in array. + * + * Returns: + * 0 if no devices exist. + * > 0 is the number of devices inserted in the list. + * -EINVAL upon bad input parameter. + */ +int cg2900_audio_get_devices(struct device *devices[], __u8 size) +{ + struct list_head *cursor; + struct audio_info *tmp; + int i = 0; + + if (!size) { + pr_err("No space to insert devices into list\n"); + return 0; + } + + if (!devices) { + pr_err("NULL submitted as devices array\n"); + return -EINVAL; + } + + /* + * Go through and store the devices. If NULL is supplied for dev + * just return first device found. + */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + devices[i] = tmp->parent; + i++; + if (i == size) + break; + } + return i; +} +EXPORT_SYMBOL_GPL(cg2900_audio_get_devices); + +/** + * cg2900_audio_open() - Opens a session to the ST-Ericsson CG2900 Audio control interface. + * @session: [out] Address where to store the session identifier. + * Allocated by caller, must not be NULL. + * @parent: Parent device representing the CG2900 controller connected. + * If NULL is supplied the first available device is used. + * + * Returns: + * 0 if there is no error. + * -EACCES if no info structure can be found. + * -EINVAL upon bad input parameter. + * -ENOMEM upon allocation failure. + * -EMFILE if no more user session could be opened. + * -EIO upon failure to register to CG2900. + * Error codes from get_info. + */ +int cg2900_audio_open(unsigned int *session, struct device *parent) +{ + int err = 0; + int i; + struct audio_info *info; + struct cg2900_user_data *pf_data_bt; + struct cg2900_user_data *pf_data_fm; + + pr_debug("cg2900_audio_open"); + + info = get_info(parent); + if (!info) { + pr_err("No audio info exist"); + return -EACCES; + } else if (IS_ERR(info)) + return PTR_ERR(info); + + if (!session) { + pr_err("NULL supplied as session"); + return -EINVAL; + } + + mutex_lock(&info->management_mutex); + + *session = 0; + + /* + * First find a free session to use and allocate the session structure. + */ + for (i = FIRST_USER; + i < MAX_NBR_OF_USERS && cg2900_audio_sessions[i]; + i++) + ; /* Just loop until found or end reached */ + + if (i >= MAX_NBR_OF_USERS) { + pr_err("Couldn't find free user"); + err = -EMFILE; + goto finished; + } + + cg2900_audio_sessions[i] = + kzalloc(sizeof(*(cg2900_audio_sessions[0])), GFP_KERNEL); + if (!cg2900_audio_sessions[i]) { + pr_err("Could not allocate user"); + err = -ENOMEM; + goto finished; + } + pr_debug("Found free session %d", i); + *session = i; + info->nbr_of_users_active++; + + cg2900_audio_sessions[*session]->resp_state = IDLE; + cg2900_audio_sessions[*session]->session = *session; + cg2900_audio_sessions[*session]->info = info; + + pf_data_bt = dev_get_platdata(info->dev_bt); + pf_data_fm = dev_get_platdata(info->dev_fm); + + if (info->nbr_of_users_active == 1) { + struct cg2900_rev_data rev_data; + + /* + * First user so register to CG2900 Core. + * First the BT audio device. + */ + err = pf_data_bt->open(pf_data_bt); + if (err) { + dev_err(BT_DEV, "Failed to open BT audio channel\n"); + goto error_handling; + } + + /* Then the FM audio device */ + err = pf_data_fm->open(pf_data_fm); + if (err) { + dev_err(FM_DEV, "Failed to open FM audio channel\n"); + goto error_handling; + } + + /* Read chip revision data */ + if (!pf_data_bt->get_local_revision(pf_data_bt, &rev_data)) { + pr_err("Couldn't retrieve revision data"); + err = -EIO; + goto error_handling; + } + + /* Decode revision data */ + switch (rev_data.revision) { + case CG2900_PG1_REV: + case CG2900_PG1_SPECIAL_REV: + info->revision = CHIP_REV_PG1; + break; + + case CG2900_PG2_REV: + info->revision = CHIP_REV_PG2; + break; + + default: + pr_err("Chip rev 0x%04X sub 0x%04X not supported", + rev_data.revision, rev_data.sub_version); + err = -EIO; + goto error_handling; + } + + info->state = OPENED; + } + + pr_info("Session %d opened", *session); + + goto finished; + +error_handling: + if (pf_data_fm->opened) + pf_data_fm->close(pf_data_fm); + if (pf_data_bt->opened) + pf_data_bt->close(pf_data_bt); + info->nbr_of_users_active--; + kfree(cg2900_audio_sessions[*session]); + cg2900_audio_sessions[*session] = NULL; +finished: + mutex_unlock(&info->management_mutex); + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_open); + +/** + * cg2900_audio_close() - Closes an opened session to the ST-Ericsson CG2900 audio control interface. + * @session: [in_out] Pointer to session identifier to close. + * Will be 0 after this call. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + * -EACCES if session has not opened. + */ +int cg2900_audio_close(unsigned int *session) +{ + int err = 0; + struct audio_user *audio_user; + struct audio_info *info; + struct cg2900_user_data *pf_data_bt; + struct cg2900_user_data *pf_data_fm; + + pr_debug("cg2900_audio_close"); + + if (!session) { + pr_err("NULL pointer supplied"); + return -EINVAL; + } + + audio_user = get_session_user(*session); + if (!audio_user) { + pr_err("Invalid session ID"); + return -EINVAL; + } + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + mutex_lock(&info->management_mutex); + + pf_data_bt = dev_get_platdata(info->dev_bt); + pf_data_fm = dev_get_platdata(info->dev_fm); + + if (!cg2900_audio_sessions[*session]) { + dev_err(BT_DEV, "Session %d not opened\n", *session); + err = -EACCES; + goto err_unlock_mutex; + } + + kfree(cg2900_audio_sessions[*session]); + cg2900_audio_sessions[*session] = NULL; + + info->nbr_of_users_active--; + if (info->nbr_of_users_active == 0) { + /* No more sessions open. Close channels */ + pf_data_fm->close(pf_data_fm); + pf_data_bt->close(pf_data_bt); + info->state = CLOSED; + } + + dev_info(BT_DEV, "Session %d closed\n", *session); + + *session = 0; + +err_unlock_mutex: + mutex_unlock(&info->management_mutex); + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_close); + +/** + * cg2900_audio_set_dai_config() - Sets the Digital Audio Interface configuration. + * @session: Session identifier this call is related to. + * @config: Pointer to the configuration to set. + * Allocated by caller, must not be NULL. + * + * Sets the Digital Audio Interface (DAI) configuration. The DAI is the external + * interface between the combo chip and the platform. + * For example the PCM or I2S interface. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + * -ENOMEM upon allocation failure. + * -EACCES if trying to set unsupported configuration. + * Errors from @receive_bt_cmd_complete. + */ +int cg2900_audio_set_dai_config(unsigned int session, + struct cg2900_dai_config *config) +{ + int err = 0; + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_set_dai_config session %d", session); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* Different commands are used for PG1 and PG2 */ + if (info->revision == CHIP_REV_PG1) + err = set_dai_config_pg1(audio_user, config); + else if (info->revision == CHIP_REV_PG2) + err = set_dai_config_pg2(audio_user, config); + + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_set_dai_config); + +/** + * cg2900_audio_get_dai_config() - Gets the current Digital Audio Interface configuration. + * @session: Session identifier this call is related to. + * @config: [out] Pointer to the configuration to get. + * Allocated by caller, must not be NULL. + * + * Gets the current Digital Audio Interface configuration. Currently this method + * can only be called after some one has called + * cg2900_audio_set_dai_config(), there is today no way of getting + * the static settings file parameters from this method. + * Note that the @port parameter within @config must be set when calling this + * function so that the ST-Ericsson CG2900 Audio driver will know which + * configuration to return. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened or configuration has not been set. + */ +int cg2900_audio_get_dai_config(unsigned int session, + struct cg2900_dai_config *config) +{ + int err = 0; + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_get_dai_config session %d", session); + + if (!config) { + pr_err("NULL supplied as config structure"); + return -EINVAL; + } + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* + * Return DAI configuration based on the received port. + * If port has not been configured return error. + */ + switch (config->port) { + case PORT_0_I2S: + mutex_lock(&info->management_mutex); + if (info->i2s_config_known) + memcpy(&config->conf.i2s, + &info->i2s_config, + sizeof(config->conf.i2s)); + else + err = -EIO; + mutex_unlock(&info->management_mutex); + break; + + case PORT_1_I2S_PCM: + mutex_lock(&info->management_mutex); + if (info->i2s_pcm_config_known) + memcpy(&config->conf.i2s_pcm, + &info->i2s_pcm_config, + sizeof(config->conf.i2s_pcm)); + else + err = -EIO; + mutex_unlock(&info->management_mutex); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EIO; + break; + }; + + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_get_dai_config); + +/** + * cg2900_audio_config_endpoint() - Configures one endpoint in the combo chip's audio system. + * @session: Session identifier this call is related to. + * @config: Pointer to the endpoint's configuration structure. + * + * Configures one endpoint in the combo chip's audio system. + * Supported @endpoint_id values are: + * * ENDPOINT_BT_SCO_INOUT + * * ENDPOINT_BT_A2DP_SRC + * * ENDPOINT_FM_RX + * * ENDPOINT_FM_TX + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + * -EACCES if supplied cg2900_dai_config struct contains not supported + * endpoint_id. + */ +int cg2900_audio_config_endpoint(unsigned int session, + struct cg2900_endpoint_config *config) +{ + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_config_endpoint\n"); + + if (!config) { + pr_err("NULL supplied as configuration structure"); + return -EINVAL; + } + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + switch (config->endpoint_id) { + case ENDPOINT_BT_SCO_INOUT: + case ENDPOINT_BT_A2DP_SRC: + case ENDPOINT_FM_RX: + case ENDPOINT_FM_TX: + add_endpoint(config, &info->endpoints); + break; + + case ENDPOINT_PORT_0_I2S: + case ENDPOINT_PORT_1_I2S_PCM: + case ENDPOINT_SLIMBUS_VOICE: + case ENDPOINT_SLIMBUS_AUDIO: + case ENDPOINT_BT_A2DP_SNK: + case ENDPOINT_ANALOG_OUT: + case ENDPOINT_DSP_AUDIO_IN: + case ENDPOINT_DSP_AUDIO_OUT: + case ENDPOINT_DSP_VOICE_IN: + case ENDPOINT_DSP_VOICE_OUT: + case ENDPOINT_DSP_TONE_IN: + case ENDPOINT_BURST_BUFFER_IN: + case ENDPOINT_BURST_BUFFER_OUT: + case ENDPOINT_MUSIC_DECODER: + case ENDPOINT_HCI_AUDIO_IN: + default: + dev_err(BT_DEV, "Unsupported endpoint_id %d\n", + config->endpoint_id); + return -EACCES; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_audio_config_endpoint); + +static bool is_dai_port(enum cg2900_audio_endpoint_id ep) +{ + /* These are the only supported ones */ + return (ep == ENDPOINT_PORT_0_I2S) || (ep == ENDPOINT_PORT_1_I2S_PCM); +} + +/** + * cg2900_audio_start_stream() - Connects two endpoints and starts the audio stream. + * @session: Session identifier this call is related to. + * @ep_1: One of the endpoints, no relation to direction or role. + * @ep_2: The other endpoint, no relation to direction or role. + * @stream_handle: Pointer where to store the stream handle. + * Allocated by caller, must not be NULL. + * + * Connects two endpoints and starts the audio stream. + * Note that the endpoints need to be configured before the stream is started; + * DAI endpoints, such as ENDPOINT_PORT_0_I2S, are + * configured through @cg2900_audio_set_dai_config() while other + * endpoints are configured through @cg2900_audio_config_endpoint(). + * + * Supported @endpoint_id values are: + * * ENDPOINT_PORT_0_I2S + * * ENDPOINT_PORT_1_I2S_PCM + * * ENDPOINT_BT_SCO_INOUT + * * ENDPOINT_FM_RX + * * ENDPOINT_FM_TX + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter or unsupported configuration. + * -EIO if driver has not been opened. + * Errors from @conn_start_i2s_to_fm_rx, @conn_start_i2s_to_fm_tx, and + * @conn_start_pcm_to_sco. + */ +int cg2900_audio_start_stream(unsigned int session, + enum cg2900_audio_endpoint_id ep_1, + enum cg2900_audio_endpoint_id ep_2, + unsigned int *stream_handle) +{ + int err; + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_start_stream session %d ep_1 %d ep_2 %d", + session, ep_1, ep_2); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* Put digital interface in ep_1 to simplify comparison below */ + if (!is_dai_port(ep_1)) { + /* Swap endpoints */ + enum cg2900_audio_endpoint_id t = ep_1; + ep_1 = ep_2; + ep_2 = t; + } + + if (ep_1 == ENDPOINT_PORT_1_I2S_PCM && ep_2 == ENDPOINT_BT_SCO_INOUT) { + err = conn_start_pcm_to_sco(audio_user, stream_handle); + } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_RX) { + err = conn_start_i2s_to_fm_rx(audio_user, stream_handle); + } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_TX) { + err = conn_start_i2s_to_fm_tx(audio_user, stream_handle); + } else { + dev_err(BT_DEV, "Endpoint config not handled: ep1: %d, " + "ep2: %d\n", ep_1, ep_2); + err = -EINVAL; + } + + return err; +} +EXPORT_SYMBOL_GPL(cg2900_audio_start_stream); + +/** + * cg2900_audio_stop_stream() - Stops a stream and disconnects the endpoints. + * @session: Session identifier this call is related to. + * @stream_handle: Handle to the stream to stop. + * + * Returns: + * 0 if there is no error. + * -EINVAL upon bad input parameter. + * -EIO if driver has not been opened. + */ +int cg2900_audio_stop_stream(unsigned int session, unsigned int stream_handle) +{ + struct audio_user *audio_user; + struct audio_info *info; + + pr_debug("cg2900_audio_stop_stream handle %d", stream_handle); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + return conn_stop_stream(audio_user, stream_handle); +} +EXPORT_SYMBOL_GPL(cg2900_audio_stop_stream); + +/** + * audio_dev_open() - Open char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation failed. + * Errors from @cg2900_audio_open. + */ +static int audio_dev_open(struct inode *inode, struct file *filp) +{ + int err; + struct char_dev_info *char_dev_info; + int minor; + struct audio_info *info = NULL; + struct audio_info *tmp; + struct list_head *cursor; + + pr_debug("audio_dev_open"); + + minor = iminor(inode); + + /* Find the info struct for this file */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (tmp->misc_dev.minor == minor) { + info = tmp; + break; + } + } + if (!info) { + pr_err("Could not identify device in inode"); + return -EINVAL; + } + + /* + * Allocate the char dev info structure. It will be stored inside + * the file pointer and supplied when file_ops are called. + * It's free'd in audio_dev_release. + */ + char_dev_info = kzalloc(sizeof(*char_dev_info), GFP_KERNEL); + if (!char_dev_info) { + dev_err(BT_DEV, "Couldn't allocate char_dev_info\n"); + return -ENOMEM; + } + filp->private_data = char_dev_info; + char_dev_info->info = info; + + mutex_init(&char_dev_info->management_mutex); + mutex_init(&char_dev_info->rw_mutex); + skb_queue_head_init(&char_dev_info->rx_queue); + + mutex_lock(&char_dev_info->management_mutex); + err = cg2900_audio_open(&char_dev_info->session, info->dev_bt->parent); + mutex_unlock(&char_dev_info->management_mutex); + if (err) { + dev_err(BT_DEV, "Failed to open CG2900 Audio driver (%d)\n", + err); + goto error_handling_free_mem; + } + + return 0; + +error_handling_free_mem: + kfree(char_dev_info); + filp->private_data = NULL; + return err; +} + +/** + * audio_dev_release() - Release char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -EBADF if NULL pointer was supplied in private data. + * Errors from @cg2900_audio_close. + */ +static int audio_dev_release(struct inode *inode, struct file *filp) +{ + int err = 0; + struct char_dev_info *dev = filp->private_data; + struct audio_info *info = dev->info; + + dev_dbg(BT_DEV, "audio_dev_release\n"); + + mutex_lock(&dev->management_mutex); + err = cg2900_audio_close(&dev->session); + if (err) + /* + * Just print the error. Still free the char_dev_info since we + * don't know the filp structure is valid after this call + */ + dev_err(BT_DEV, "Error %d when closing CG2900 audio driver\n", + err); + + mutex_unlock(&dev->management_mutex); + + kfree(dev); + filp->private_data = NULL; + + return err; +} + +/** + * audio_dev_read() - Return information to the user from last @write call. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Size of buffer. + * @f_pos: Position in buffer. + * + * The audio_dev_read() function returns information from + * the last @write call to same char device. + * The data is in the following format: + * * OpCode of command for this data + * * Data content (Length of data is determined by the command OpCode, i.e. + * fixed for each command) + * + * Returns: + * Bytes successfully read (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_to_user fails. + * -ENOMEM upon allocation failure. + */ +static ssize_t audio_dev_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct char_dev_info *dev = filp->private_data; + struct audio_info *info = dev->info; + unsigned int bytes_to_copy; + int err = 0; + struct sk_buff *skb; + + dev_dbg(BT_DEV, "audio_dev_read count %d\n", count); + + mutex_lock(&dev->rw_mutex); + + skb = skb_dequeue(&dev->rx_queue); + if (!skb) { + /* No data to read */ + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, (unsigned int)(skb->len)); + + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + dev_err(BT_DEV, "copy_to_user error %d\n", err); + skb_queue_head(&dev->rx_queue, skb); + err = -EFAULT; + goto error_handling; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(&dev->rx_queue, skb); + else + kfree_skb(skb); + + goto finished; + +error_handling: + mutex_unlock(&dev->rw_mutex); + return (ssize_t)err; +finished: + mutex_unlock(&dev->rw_mutex); + return bytes_to_copy; +} + +/** + * audio_dev_write() - Call CG2900 Audio API function. + * @filp: Pointer to the file struct. + * @buf: Write buffer. + * @count: Size of the buffer write. + * @f_pos: Position of buffer. + * + * audio_dev_write() function executes supplied data and + * interprets it as if it was a function call to the CG2900 Audio API. + * The data is according to: + * * OpCode (4 bytes, see API). + * * Data according to OpCode (see API). No padding between parameters. + * + * Returns: + * Bytes successfully written (could be 0). Equals input @count if successful. + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_from_user fails. + * Error codes from all CG2900 Audio API functions. + */ +static ssize_t audio_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + u8 *rec_data; + struct char_dev_info *dev = filp->private_data; + struct audio_info *info; + int err = 0; + int op_code = 0; + u8 *curr_data; + unsigned int stream_handle; + struct cg2900_dai_config dai_config; + struct cg2900_endpoint_config ep_config; + enum cg2900_audio_endpoint_id ep_1; + enum cg2900_audio_endpoint_id ep_2; + int bytes_left = count; + + pr_debug("audio_dev_write count %d", count); + + if (!dev) { + pr_err("No dev supplied in private data"); + return -EBADF; + } + info = dev->info; + + rec_data = kmalloc(count, GFP_KERNEL); + if (!rec_data) { + dev_err(BT_DEV, "kmalloc failed (%d bytes)\n", count); + return -ENOMEM; + } + + mutex_lock(&dev->rw_mutex); + + err = copy_from_user(rec_data, buf, count); + if (err) { + dev_err(BT_DEV, "copy_from_user failed (%d)\n", err); + err = -EFAULT; + goto finished_mutex_unlock; + } + + /* Initialize temporary data pointer used to traverse the packet */ + curr_data = rec_data; + + op_code = curr_data[0]; + /* OpCode is int size to keep data int aligned */ + curr_data += sizeof(unsigned int); + bytes_left -= sizeof(unsigned int); + + switch (op_code) { + case CG2900_OPCODE_SET_DAI_CONF: + if (bytes_left < sizeof(dai_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_SET_DAI_CONF\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&dai_config, curr_data, sizeof(dai_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_SET_DAI_CONF port %d\n", + dai_config.port); + err = cg2900_audio_set_dai_config(dev->session, &dai_config); + break; + + case CG2900_OPCODE_GET_DAI_CONF: + if (bytes_left < sizeof(dai_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_GET_DAI_CONF\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + /* + * Only need to copy the port really, but let's copy + * like this for simplicity. It's only test functionality + * after all. + */ + memcpy(&dai_config, curr_data, sizeof(dai_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_GET_DAI_CONF port %d\n", + dai_config.port); + err = cg2900_audio_get_dai_config(dev->session, &dai_config); + if (!err) { + int len; + struct sk_buff *skb; + + /* + * Command succeeded. Store data so it can be returned + * when calling read. + */ + len = sizeof(op_code) + sizeof(dai_config); + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "CG2900_OPCODE_GET_DAI_CONF: " + "Could not allocate skb\n"); + err = -ENOMEM; + goto finished_mutex_unlock; + } + memcpy(skb_put(skb, sizeof(op_code)), &op_code, + sizeof(op_code)); + memcpy(skb_put(skb, sizeof(dai_config)), + &dai_config, sizeof(dai_config)); + skb_queue_tail(&dev->rx_queue, skb); + } + break; + + case CG2900_OPCODE_CONFIGURE_ENDPOINT: + if (bytes_left < sizeof(ep_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_CONFIGURE_ENDPOINT\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&ep_config, curr_data, sizeof(ep_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_CONFIGURE_ENDPOINT ep_id %d\n", + ep_config.endpoint_id); + err = cg2900_audio_config_endpoint(dev->session, &ep_config); + break; + + case CG2900_OPCODE_START_STREAM: + if (bytes_left < (sizeof(ep_1) + sizeof(ep_2))) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_START_STREAM\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&ep_1, curr_data, sizeof(ep_1)); + curr_data += sizeof(ep_1); + memcpy(&ep_2, curr_data, sizeof(ep_2)); + dev_dbg(BT_DEV, "CG2900_OPCODE_START_STREAM ep_1 %d ep_2 %d\n", + ep_1, ep_2); + + err = cg2900_audio_start_stream(dev->session, + ep_1, ep_2, &stream_handle); + if (!err) { + int len; + struct sk_buff *skb; + + /* + * Command succeeded. Store data so it can be returned + * when calling read. + */ + len = sizeof(op_code) + sizeof(stream_handle); + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "CG2900_OPCODE_START_STREAM: " + "Could not allocate skb\n"); + err = -ENOMEM; + goto finished_mutex_unlock; + } + memcpy(skb_put(skb, sizeof(op_code)), &op_code, + sizeof(op_code)); + memcpy(skb_put(skb, sizeof(stream_handle)), + &stream_handle, sizeof(stream_handle)); + skb_queue_tail(&dev->rx_queue, skb); + + dev_dbg(BT_DEV, "stream_handle %d\n", stream_handle); + } + break; + + case CG2900_OPCODE_STOP_STREAM: + if (bytes_left < sizeof(stream_handle)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_STOP_STREAM\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&stream_handle, curr_data, sizeof(stream_handle)); + dev_dbg(BT_DEV, "CG2900_OPCODE_STOP_STREAM stream_handle %d\n", + stream_handle); + err = cg2900_audio_stop_stream(dev->session, stream_handle); + break; + + default: + dev_err(BT_DEV, "Received bad op_code %d\n", op_code); + break; + }; + +finished_mutex_unlock: + kfree(rec_data); + mutex_unlock(&dev->rw_mutex); + + if (err) + return err; + else + return count; +} + +/** + * audio_dev_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * This function is used by the User Space application to see if the device is + * still open and if there is any data available for reading. + * + * Returns: + * Mask of current set POLL values. + */ +static unsigned int audio_dev_poll(struct file *filp, poll_table *wait) +{ + struct char_dev_info *dev = filp->private_data; + struct audio_info *info; + unsigned int mask = 0; + + if (!dev) { + pr_err("No dev supplied in private data"); + return POLLERR | POLLRDHUP; + } + info = dev->info; + + if (RESET == info->state) + mask |= POLLERR | POLLRDHUP | POLLPRI; + else + /* Unless RESET we can transmit */ + mask |= POLLOUT; + + if (!skb_queue_empty(&dev->rx_queue)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static const struct file_operations char_dev_fops = { + .open = audio_dev_open, + .release = audio_dev_release, + .read = audio_dev_read, + .write = audio_dev_write, + .poll = audio_dev_poll +}; + +/** + * probe_common() - Register misc device. + * @info: Audio info structure. + * @dev: Current device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from misc_register. + */ +static int probe_common(struct audio_info *info, struct device *dev) +{ + struct audio_cb_info *cb_info; + struct cg2900_user_data *pf_data; + int err; + + cb_info = kzalloc(sizeof(*cb_info), GFP_KERNEL); + if (!cb_info) { + dev_err(dev, "Failed to allocate cb_info\n"); + return -ENOMEM; + } + init_waitqueue_head(&cb_info->wq); + skb_queue_head_init(&cb_info->skb_queue); + + pf_data = dev_get_platdata(dev); + cg2900_set_usr(pf_data, cb_info); + pf_data->dev = dev; + pf_data->read_cb = read_cb; + pf_data->reset_cb = reset_cb; + + /* Only register misc device when both devices (BT and FM) are probed */ + if (!info->dev_bt || !info->dev_fm) + return 0; + + /* Prepare and register MISC device */ + info->misc_dev.minor = MISC_DYNAMIC_MINOR; + info->misc_dev.name = NAME; + info->misc_dev.fops = &char_dev_fops; + info->misc_dev.parent = dev; + info->misc_dev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&info->misc_dev); + if (err) { + dev_err(dev, "Error %d registering misc dev\n", err); + return err; + } + info->misc_registered = true; + + dev_info(dev, "CG2900 Audio driver started\n"); + return 0; +} + +/** + * cg2900_audio_bt_probe() - Initialize CG2900 BT audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * -EEXIST if device has already been started. + * Error codes from probe_common. + */ +static int __devinit cg2900_audio_bt_probe(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_bt_probe\n"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->dev_bt = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + err = probe_common(info, &pdev->dev); + if (err) { + dev_err(&pdev->dev, "Could not probe audio BT (%d)\n", err); + dev_set_drvdata(&pdev->dev, NULL); + device_removed(info); + } + + return err; +} + +/** + * cg2900_audio_bt_probe() - Initialize CG2900 FM audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * -EEXIST if device has already been started. + * Error codes from probe_common. + */ +static int __devinit cg2900_audio_fm_probe(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_fm_probe\n"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->dev_fm = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + err = probe_common(info, &pdev->dev); + if (err) { + dev_err(&pdev->dev, "Could not probe audio FM (%d)\n", err); + dev_set_drvdata(&pdev->dev, NULL); + device_removed(info); + } + + return err; +} + +/** + * common_remove() - Dergister misc device. + * @info: Audio info structure. + * @dev: Current device. + * + * Returns: + * 0 if success. + * Error codes from misc_deregister. + */ +static int common_remove(struct audio_info *info, struct device *dev) +{ + int err; + struct audio_cb_info *cb_info; + struct cg2900_user_data *pf_data; + + pf_data = dev_get_platdata(dev); + cb_info = cg2900_get_usr(pf_data); + skb_queue_purge(&cb_info->skb_queue); + wake_up_all(&cb_info->wq); + kfree(cb_info); + + if (!info->misc_registered) + return 0; + + err = misc_deregister(&info->misc_dev); + if (err) + dev_err(dev, "Error %d deregistering misc dev\n", err); + info->misc_registered = false; + + dev_info(dev, "CG2900 Audio driver removed\n"); + return err; +} + +/** + * cg2900_audio_bt_remove() - Release CG2900 audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes from common_remove. + */ +static int __devexit cg2900_audio_bt_remove(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_bt_remove\n"); + + info = dev_get_drvdata(&pdev->dev); + + info->dev_bt = NULL; + + err = common_remove(info, &pdev->dev); + if (err) + dev_err(&pdev->dev, + "cg2900_audio_bt_remove:common_remove failed\n"); + + device_removed(info); + + return 0; +} + +/** + * cg2900_audio_fm_remove() - Release CG2900 audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes from common_remove. + */ +static int __devexit cg2900_audio_fm_remove(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_fm_remove\n"); + + info = dev_get_drvdata(&pdev->dev); + + info->dev_fm = NULL; + + err = common_remove(info, &pdev->dev); + if (err) + dev_err(&pdev->dev, + "cg2900_audio_fm_remove:common_remove failed\n"); + + device_removed(info); + + return 0; +} + +static struct platform_driver cg2900_audio_bt_driver = { + .driver = { + .name = "cg2900-audiobt", + .owner = THIS_MODULE, + }, + .probe = cg2900_audio_bt_probe, + .remove = __devexit_p(cg2900_audio_bt_remove), +}; + +static struct platform_driver cg2900_audio_fm_driver = { + .driver = { + .name = "cg2900-audiofm", + .owner = THIS_MODULE, + }, + .probe = cg2900_audio_fm_probe, + .remove = __devexit_p(cg2900_audio_fm_remove), +}; + +/** + * cg2900_audio_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_audio_init(void) +{ + int err; + + pr_debug("cg2900_audio_init"); + + err = platform_driver_register(&cg2900_audio_bt_driver); + if (err) + return err; + return platform_driver_register(&cg2900_audio_fm_driver); +} + +/** + * cg2900_audio_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_audio_exit(void) +{ + pr_debug("cg2900_audio_exit"); + platform_driver_unregister(&cg2900_audio_fm_driver); + platform_driver_unregister(&cg2900_audio_bt_driver); +} + +module_init(cg2900_audio_init); +module_exit(cg2900_audio_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_AUTHOR("Kjell Andersson ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth Audio ST-Ericsson controller"); diff --git a/drivers/staging/cg2900/mfd/cg2900_char_devices.c b/drivers/staging/cg2900/mfd/cg2900_char_devices.c new file mode 100644 index 00000000000..c7bacdf8889 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_char_devices.c @@ -0,0 +1,706 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + */ +#define NAME "cg2900_char_dev" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" +#include "cg2900_core.h" + +#define MAIN_DEV (dev->dev) + +/** + * struct char_dev_user - Stores device information. + * @dev: Current device. + * @miscdev: Registered device struct. + * @name: Name of device. + * @rx_queue: Data queue. + * @rx_wait_queue: Wait queue. + * @reset_wait_queue: Reset Wait queue. + * @read_mutex: Read mutex. + * @write_mutex: Write mutex. + * @list: List header for inserting into device list. + */ +struct char_dev_user { + struct device *dev; + struct miscdevice miscdev; + char *name; + struct sk_buff_head rx_queue; + wait_queue_head_t rx_wait_queue; + wait_queue_head_t reset_wait_queue; + struct mutex read_mutex; + struct mutex write_mutex; + struct list_head list; +}; + +/** + * struct char_info - Stores all current users. + * @open_mutex: Open mutex (used for both open and release). + * @man_mutex: Management mutex. + * @dev_users: List of char dev users. + */ +struct char_info { + struct mutex open_mutex; + struct mutex man_mutex; + struct list_head dev_users; +}; + +static struct char_info *char_info; + +/** + * char_dev_read_cb() - Handle data received from controller. + * @dev: Device receiving data. + * @skb: Buffer with data coming from controller. + * + * The char_dev_read_cb() function handles data received from the CG2900 driver. + */ +static void char_dev_read_cb(struct cg2900_user_data *dev, struct sk_buff *skb) +{ + struct char_dev_user *char_dev = dev_get_drvdata(dev->dev); + + dev_dbg(dev->dev, "char_dev_read_cb len %d\n", skb->len); + + skb_queue_tail(&char_dev->rx_queue, skb); + + wake_up_interruptible(&char_dev->rx_wait_queue); +} + +/** + * char_dev_reset_cb() - Handle reset from controller. + * @dev: Device resetting. + * + * The char_dev_reset_cb() function handles reset from the CG2900 driver. + */ +static void char_dev_reset_cb(struct cg2900_user_data *dev) +{ + struct char_dev_user *char_dev = dev_get_drvdata(dev->dev); + + dev_dbg(dev->dev, "char_dev_reset_cb\n"); + + wake_up_interruptible(&char_dev->rx_wait_queue); + wake_up_interruptible(&char_dev->reset_wait_queue); +} + +/** + * char_dev_open() - Open char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The char_dev_open() function opens the char device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if device cannot be found in device list. + * Error codes from cg2900->open. + */ +static int char_dev_open(struct inode *inode, struct file *filp) +{ + int err; + int minor; + struct char_dev_user *dev = NULL; + struct char_dev_user *tmp; + struct list_head *cursor; + struct cg2900_user_data *user; + + mutex_lock(&char_info->open_mutex); + + minor = iminor(inode); + + /* Find the device for this file */ + mutex_lock(&char_info->man_mutex); + list_for_each(cursor, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + if (tmp->miscdev.minor == minor) { + dev = tmp; + break; + } + } + mutex_unlock(&char_info->man_mutex); + if (!dev) { + pr_err("Could not identify device in inode"); + err = -EINVAL; + goto error_handling; + } + + filp->private_data = dev; + user = dev_get_platdata(dev->dev); + + /* First initiate wait queues for this device. */ + init_waitqueue_head(&dev->rx_wait_queue); + init_waitqueue_head(&dev->reset_wait_queue); + + /* Register to CG2900 Driver */ + err = user->open(user); + if (err) { + dev_err(MAIN_DEV, + "Couldn't register to CG2900 for H:4 channel %s\n", + dev->name); + goto error_handling; + } + dev_info(MAIN_DEV, "char_dev %s opened\n", dev->name); + +error_handling: + mutex_unlock(&char_info->open_mutex); + return err; +} + +/** + * char_dev_release() - Release char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The char_dev_release() function release the char device. + * + * Returns: + * 0 if there is no error. + * -EBADF if NULL pointer was supplied in private data. + */ +static int char_dev_release(struct inode *inode, struct file *filp) +{ + int err = 0; + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + + pr_debug("char_dev_release"); + + if (!dev) { + pr_err("Calling with NULL pointer"); + return -EBADF; + } + + mutex_lock(&char_info->open_mutex); + mutex_lock(&dev->read_mutex); + mutex_lock(&dev->write_mutex); + + user = dev_get_platdata(dev->dev); + if (user->opened) + user->close(user); + + dev_info(MAIN_DEV, "char_dev %s closed\n", dev->name); + + filp->private_data = NULL; + wake_up_interruptible(&dev->rx_wait_queue); + wake_up_interruptible(&dev->reset_wait_queue); + + mutex_unlock(&dev->write_mutex); + mutex_unlock(&dev->read_mutex); + mutex_unlock(&char_info->open_mutex); + + return err; +} + +/** + * char_dev_read() - Queue and copy buffer to user. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Size of buffer. + * @f_pos: Position in buffer. + * + * The char_dev_read() function queues and copy the received buffer to + * the user space char device. If no data is available this function will block. + * + * Returns: + * Bytes successfully read (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_to_user fails. + * Error codes from wait_event_interruptible. + */ +static ssize_t char_dev_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + struct sk_buff *skb; + int bytes_to_copy; + int err = 0; + + pr_debug("char_dev_read"); + + if (!dev) { + pr_err("Calling with NULL pointer"); + return -EBADF; + } + mutex_lock(&dev->read_mutex); + + user = dev_get_platdata(dev->dev); + + if (user->opened && skb_queue_empty(&dev->rx_queue)) { + err = wait_event_interruptible(dev->rx_wait_queue, + (!(skb_queue_empty(&dev->rx_queue))) || + !user->opened); + if (err) { + dev_err(MAIN_DEV, "Failed to wait for event\n"); + goto error_handling; + } + } + + if (!user->opened) { + dev_err(MAIN_DEV, "Channel has been closed\n"); + err = -EBADF; + goto error_handling; + } + + skb = skb_dequeue(&dev->rx_queue); + if (!skb) { + dev_dbg(MAIN_DEV, + "skb queue is empty - return with zero bytes\n"); + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, skb->len); + + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + dev_err(MAIN_DEV, "Error %d from copy_to_user\n", err); + skb_queue_head(&dev->rx_queue, skb); + err = -EFAULT; + goto error_handling; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(&dev->rx_queue, skb); + else + kfree_skb(skb); + + goto finished; + +error_handling: + mutex_unlock(&dev->read_mutex); + return (ssize_t)err; +finished: + mutex_unlock(&dev->read_mutex); + return bytes_to_copy; +} + +/** + * char_dev_write() - Copy buffer from user and write to CG2900 driver. + * @filp: Pointer to the file struct. + * @buf: Write buffer. + * @count: Size of the buffer write. + * @f_pos: Position of buffer. + * + * Returns: + * Bytes successfully written (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_from_user fails. + */ +static ssize_t char_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + int err = 0; + + pr_debug("char_dev_write"); + + if (!dev) { + pr_err("Calling with NULL pointer"); + return -EBADF; + } + + user = dev_get_platdata(dev->dev); + if (!user->opened) { + dev_err(MAIN_DEV, "char_dev_write: Channel not opened\n"); + return -EACCES; + } + + mutex_lock(&dev->write_mutex); + + skb = user->alloc_skb(count, GFP_ATOMIC); + if (!skb) { + dev_err(MAIN_DEV, "Couldn't allocate sk_buff with length %d\n", + count); + goto error_handling; + } + + err = copy_from_user(skb_put(skb, count), buf, count); + if (err) { + dev_err(MAIN_DEV, "Error %d from copy_from_user\n", err); + kfree_skb(skb); + err = -EFAULT; + goto error_handling; + } + + err = user->write(user, skb); + if (err) { + dev_err(MAIN_DEV, "cg2900_write failed (%d)\n", err); + kfree_skb(skb); + goto error_handling; + } + + mutex_unlock(&dev->write_mutex); + return count; + +error_handling: + mutex_unlock(&dev->write_mutex); + return err; +} + +/** + * char_dev_unlocked_ioctl() - Handle IOCTL call to the interface. + * @filp: Pointer to the file struct. + * @cmd: IOCTL command. + * @arg: IOCTL argument. + * + * Returns: + * 0 if there is no error. + * -EINVAL if supplied cmd is not supported. + * For cmd CG2900_CHAR_DEV_IOCTL_CHECK4RESET 0x01 is returned if device is + * reset and 0x02 is returned if device is closed. + */ +static long char_dev_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + struct cg2900_rev_data rev_data; + int err = 0; + int ret_val; + void __user *user_arg = (void __user *)arg; + + dev_dbg(dev->dev, "char_dev_unlocked_ioctl for %s\n" + "\tDIR: %d\n" + "\tTYPE: %d\n" + "\tNR: %d\n" + "\tSIZE: %d", + dev->name, _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), + _IOC_SIZE(cmd)); + + user = dev_get_platdata(dev->dev); + + switch (cmd) { + case CG2900_CHAR_DEV_IOCTL_RESET: + if (!user->opened) + return -EACCES; + dev_dbg(MAIN_DEV, "ioctl reset command for device %s\n", + dev->name); + err = user->reset(user); + break; + + case CG2900_CHAR_DEV_IOCTL_CHECK4RESET: + if (user->opened) + ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_IDLE; + else + ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_RESET; + + dev_dbg(MAIN_DEV, "ioctl check for reset command for device %s", + dev->name); + + err = copy_to_user(user_arg, &ret_val, sizeof(ret_val)); + if (err) { + dev_err(MAIN_DEV, + "Error %d from copy_to_user for reset\n", err); + return -EFAULT; + } + break; + + case CG2900_CHAR_DEV_IOCTL_GET_REVISION: + if (!user->get_local_revision(user, &rev_data)) { + dev_err(MAIN_DEV, "No revision data available\n"); + return -EIO; + } + dev_dbg(MAIN_DEV, "ioctl check for local revision info\n" + "\trevision 0x%04X\n" + "\tsub_version 0x%04X\n", + rev_data.revision, rev_data.sub_version); + err = copy_to_user(user_arg, &rev_data, sizeof(rev_data)); + if (err) { + dev_err(MAIN_DEV, + "Error %d from copy_to_user for " + "revision\n", err); + return -EFAULT; + } + break; + + default: + dev_err(MAIN_DEV, "Unknown ioctl command %08X\n", cmd); + err = -EINVAL; + break; + }; + + return err; +} + +/** + * char_dev_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * Returns: + * Mask of current set POLL values + */ +static unsigned int char_dev_poll(struct file *filp, poll_table *wait) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + unsigned int mask = 0; + + if (!dev) { + pr_debug("Device not open"); + return POLLERR | POLLRDHUP; + } + + user = dev_get_platdata(dev->dev); + + poll_wait(filp, &dev->reset_wait_queue, wait); + poll_wait(filp, &dev->rx_wait_queue, wait); + + if (!user->opened) + mask |= POLLERR | POLLRDHUP | POLLPRI; + else + mask |= POLLOUT; /* We can TX unless there is an error */ + + if (!(skb_queue_empty(&dev->rx_queue))) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +/* + * struct char_dev_fops - Char devices file operations. + * @read: Function that reads from the char device. + * @write: Function that writes to the char device. + * @unlocked_ioctl: Function that performs IO operations with + * the char device. + * @poll: Function that checks if there are possible operations + * with the char device. + * @open: Function that opens the char device. + * @release: Function that release the char device. + */ +static const struct file_operations char_dev_fops = { + .read = char_dev_read, + .write = char_dev_write, + .unlocked_ioctl = char_dev_unlocked_ioctl, + .poll = char_dev_poll, + .open = char_dev_open, + .release = char_dev_release +}; + +/** + * remove_dev() - Remove char device structure for device. + * @dev_usr: Char device user. + * + * The remove_dev() function releases the char_dev structure for this device. + */ +static void remove_dev(struct char_dev_user *dev_usr) +{ + if (!dev_usr) + return; + + dev_dbg(dev_usr->dev, + "Removing char device %s with major %d and minor %d\n", + dev_usr->name, + MAJOR(dev_usr->miscdev.this_device->devt), + MINOR(dev_usr->miscdev.this_device->devt)); + + skb_queue_purge(&dev_usr->rx_queue); + + mutex_destroy(&dev_usr->read_mutex); + mutex_destroy(&dev_usr->write_mutex); + + /* Remove device node in file system. */ + misc_deregister(&dev_usr->miscdev); + kfree(dev_usr); +} + +/** + * cg2900_char_probe() - Initialize char device module. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if allocation fails. + * -EACCES if device already have been initiated. + */ +static int __devinit cg2900_char_probe(struct platform_device *pdev) +{ + int err = 0; + struct char_dev_user *dev_usr; + struct cg2900_user_data *user; + struct device *dev = &pdev->dev; + + dev_dbg(&pdev->dev, "cg2900_char_probe\n"); + + user = mfd_get_data(pdev); + user->dev = dev; + user->read_cb = char_dev_read_cb; + user->reset_cb = char_dev_reset_cb; + + /* Set platform data */ + dev->platform_data = user; + + dev_usr = kzalloc(sizeof(*dev_usr), GFP_KERNEL); + if (!dev_usr) { + dev_err(&pdev->dev, "Couldn't allocate dev_usr\n"); + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, dev_usr); + dev_usr->dev = &pdev->dev; + + /* Store device name */ + dev_usr->name = user->channel_data.char_dev_name; + + /* Prepare miscdevice struct before registering the device */ + dev_usr->miscdev.minor = MISC_DYNAMIC_MINOR; + dev_usr->miscdev.name = dev_usr->name; + dev_usr->miscdev.nodename = dev_usr->name; + dev_usr->miscdev.fops = &char_dev_fops; + dev_usr->miscdev.parent = &pdev->dev; + dev_usr->miscdev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&dev_usr->miscdev); + if (err) { + dev_err(&pdev->dev, "Error %d registering misc dev\n", err); + goto err_free_usr; + } + + dev_dbg(&pdev->dev, "Added char device %s with major %d and minor %d\n", + dev_usr->name, MAJOR(dev_usr->miscdev.this_device->devt), + MINOR(dev_usr->miscdev.this_device->devt)); + + mutex_init(&dev_usr->read_mutex); + mutex_init(&dev_usr->write_mutex); + + skb_queue_head_init(&dev_usr->rx_queue); + + mutex_lock(&char_info->man_mutex); + list_add_tail(&dev_usr->list, &char_info->dev_users); + mutex_unlock(&char_info->man_mutex); + + return 0; + +err_free_usr: + kfree(dev_usr); + dev_set_drvdata(&pdev->dev, NULL); + return err; +} + +/** + * cg2900_char_remove() - Release the char device module. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit cg2900_char_remove(struct platform_device *pdev) +{ + struct list_head *cursor, *next; + struct char_dev_user *tmp; + struct char_dev_user *user; + + dev_dbg(&pdev->dev, "cg2900_char_remove\n"); + + user = dev_get_drvdata(&pdev->dev); + + mutex_lock(&char_info->man_mutex); + list_for_each_safe(cursor, next, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + if (tmp == user) { + list_del(cursor); + remove_dev(tmp); + dev_set_drvdata(&pdev->dev, NULL); + break; + } + } + mutex_unlock(&char_info->man_mutex); + return 0; +} + +static struct platform_driver cg2900_char_driver = { + .driver = { + .name = "cg2900-chardev", + .owner = THIS_MODULE, + }, + .probe = cg2900_char_probe, + .remove = __devexit_p(cg2900_char_remove), +}; + +/** + * cg2900_char_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_char_init(void) +{ + pr_debug("cg2900_char_init"); + + /* Initialize private data. */ + char_info = kzalloc(sizeof(*char_info), GFP_ATOMIC); + if (!char_info) { + pr_err("Could not alloc char_info struct"); + return -ENOMEM; + } + + mutex_init(&char_info->open_mutex); + mutex_init(&char_info->man_mutex); + INIT_LIST_HEAD(&char_info->dev_users); + + return platform_driver_register(&cg2900_char_driver); +} + +/** + * cg2900_char_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_char_exit(void) +{ + struct list_head *cursor, *next; + struct char_dev_user *tmp; + + pr_debug("cg2900_char_exit"); + + platform_driver_unregister(&cg2900_char_driver); + + if (!char_info) + return; + + list_for_each_safe(cursor, next, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + list_del(cursor); + remove_dev(tmp); + } + + mutex_destroy(&char_info->open_mutex); + mutex_destroy(&char_info->man_mutex); + + kfree(char_info); + char_info = NULL; +} + +module_init(cg2900_char_init); +module_exit(cg2900_char_exit); + +MODULE_AUTHOR("Henrik Possung ST-Ericsson"); +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ST-Ericsson CG2900 Char Devices Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_chip.c b/drivers/staging/cg2900/mfd/cg2900_chip.c new file mode 100644 index 00000000000..ad42429d5f0 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_chip.c @@ -0,0 +1,3393 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_chip" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" +#include "cg2900_chip.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" + +#define MAIN_DEV (main_info->dev) +#define BOOT_DEV (info->user_in_charge->dev) + +#define WQ_NAME "cg2900_chip_wq" + +/* + * After waiting the first 500 ms we should just try to get the selftest results + * for another number of poll attempts + */ +#define MAX_NBR_OF_POLLS 50 + +#define LINE_TOGGLE_DETECT_TIMEOUT 50 /* ms */ +#define CHIP_READY_TIMEOUT 100 /* ms */ +#define CHIP_STARTUP_TIMEOUT 15000 /* ms */ +#define CHIP_SHUTDOWN_TIMEOUT 15000 /* ms */ +#define POWER_SW_OFF_WAIT 500 /* ms */ +#define SELFTEST_INITIAL 500 /* ms */ +#define SELFTEST_POLLING 20 /* ms */ + +/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel + * for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_CMD 0x01 + +/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel + * for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_ACL 0x02 + +/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel + * for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_EVT 0x04 + +/** CHANNEL_FM_RADIO - Bluetooth HCI H:4 channel + * for FM radio in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_FM_RADIO 0x08 + +/** CHANNEL_GNSS - Bluetooth HCI H:4 channel + * for GNSS in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_GNSS 0x09 + +/** CHANNEL_DEBUG - Bluetooth HCI H:4 channel + * for internal debug data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_DEBUG 0x0B + +/** CHANNEL_STE_TOOLS - Bluetooth HCI H:4 channel + * for development tools data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_STE_TOOLS 0x0D + +/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel + * for logging all transmitted H4 packets (on all channels). + */ +#define CHANNEL_HCI_LOGGER 0xFA + +/** CHANNEL_CORE - Bluetooth HCI H:4 channel + * for user space control of the ST-Ericsson connectivity controller. + */ +#define CHANNEL_CORE 0xFD + +/** CG2900_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands. + */ +#define CG2900_BT_CMD "cg2900_bt_cmd" + +/** CG2900_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data. + */ +#define CG2900_BT_ACL "cg2900_bt_acl" + +/** CG2900_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events. + */ +#define CG2900_BT_EVT "cg2900_bt_evt" + +/** CG2900_FM_RADIO - Bluetooth HCI H4 channel for FM radio. + */ +#define CG2900_FM_RADIO "cg2900_fm_radio" + +/** CG2900_GNSS - Bluetooth HCI H4 channel for GNSS. + */ +#define CG2900_GNSS "cg2900_gnss" + +/** CG2900_DEBUG - Bluetooth HCI H4 channel for internal debug data. + */ +#define CG2900_DEBUG "cg2900_debug" + +/** CG2900_STE_TOOLS - Bluetooth HCI H4 channel for development tools data. + */ +#define CG2900_STE_TOOLS "cg2900_ste_tools" + +/** CG2900_HCI_LOGGER - BT channel for logging all transmitted H4 packets. + * Data read is copy of all data transferred on the other channels. + * Only write allowed is configuration of the HCI Logger. + */ +#define CG2900_HCI_LOGGER "cg2900_hci_logger" + +/** CG2900_BT_AUDIO - HCI Channel for BT audio configuration commands. + * Maps to Bluetooth command and event channels. + */ +#define CG2900_BT_AUDIO "cg2900_bt_audio" + +/** CG2900_FM_AUDIO - HCI channel for FM audio configuration commands. + * Maps to FM Radio channel. + */ +#define CG2900_FM_AUDIO "cg2900_fm_audio" + +/** CG2900_CORE- Channel for keeping ST-Ericsson CG2900 enabled. + * Opening this channel forces the chip to stay powered. + * No data can be written to or read from this channel. + */ +#define CG2900_CORE "cg2900_core" + +/** + * enum main_state - Main-state for CG2900 driver. + * @CG2900_INIT: CG2900 initializing. + * @CG2900_IDLE: No user registered to CG2900 driver. + * @CG2900_BOOTING: CG2900 booting after first user is registered. + * @CG2900_CLOSING: CG2900 closing after last user has deregistered. + * @CG2900_RESETING: CG2900 reset requested. + * @CG2900_ACTIVE: CG2900 up and running with at least one user. + */ +enum main_state { + CG2900_INIT, + CG2900_IDLE, + CG2900_BOOTING, + CG2900_CLOSING, + CG2900_RESETING, + CG2900_ACTIVE +}; + +/** + * enum boot_state - BOOT-state for CG2900 chip driver. + * @BOOT_NOT_STARTED: Boot has not yet started. + * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address + * has been sent. + * @BOOT_GET_FILES_TO_LOAD: CG2900 chip driver is retrieving file to + * load. + * @BOOT_DOWNLOAD_PATCH: CG2900 chip driver is downloading + * patches. + * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: CG2900 chip driver is activating patches + * and settings. + * @BOOT_READ_SELFTEST_RESULT: CG2900 is performing selftests that + * shall be read out. + * @BOOT_DISABLE_BT: Disable BT Core. + * @BOOT_READY: CG2900 chip driver boot is ready. + * @BOOT_FAILED: CG2900 chip driver boot failed. + */ +enum boot_state { + BOOT_NOT_STARTED, + BOOT_SEND_BD_ADDRESS, + BOOT_GET_FILES_TO_LOAD, + BOOT_DOWNLOAD_PATCH, + BOOT_ACTIVATE_PATCHES_AND_SETTINGS, + BOOT_READ_SELFTEST_RESULT, + BOOT_DISABLE_BT, + BOOT_READY, + BOOT_FAILED +}; + +/** + * enum closing_state - CLOSING-state for CG2900 chip driver. + * @CLOSING_RESET: HCI RESET_CMD has been sent. + * @CLOSING_POWER_SWITCH_OFF: HCI VS_POWER_SWITCH_OFF command has been sent. + * @CLOSING_SHUT_DOWN: We have now shut down the chip. + */ +enum closing_state { + CLOSING_RESET, + CLOSING_POWER_SWITCH_OFF, + CLOSING_SHUT_DOWN +}; + +/** + * enum file_load_state - BOOT_FILE_LOAD-state for CG2900 chip driver. + * @FILE_LOAD_GET_PATCH: Loading patches. + * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings. + * @FILE_LOAD_NO_MORE_FILES: No more files to load. + * @FILE_LOAD_FAILED: File loading failed. + */ +enum file_load_state { + FILE_LOAD_GET_PATCH, + FILE_LOAD_GET_STATIC_SETTINGS, + FILE_LOAD_NO_MORE_FILES, + FILE_LOAD_FAILED +}; + +/** + * enum download_state - BOOT_DOWNLOAD state. + * @DOWNLOAD_PENDING: Download in progress. + * @DOWNLOAD_SUCCESS: Download successfully finished. + * @DOWNLOAD_FAILED: Downloading failed. + */ +enum download_state { + DOWNLOAD_PENDING, + DOWNLOAD_SUCCESS, + DOWNLOAD_FAILED +}; + +/** + * enum fm_radio_mode - FM Radio mode. + * It's needed because some FM do-commands generate interrupts only when + * the FM driver is in specific mode and we need to know if we should expect + * the interrupt. + * @FM_RADIO_MODE_IDLE: Radio mode is Idle (default). + * @FM_RADIO_MODE_FMT: Radio mode is set to FMT (transmitter). + * @FM_RADIO_MODE_FMR: Radio mode is set to FMR (receiver). + */ +enum fm_radio_mode { + FM_RADIO_MODE_IDLE = 0, + FM_RADIO_MODE_FMT = 1, + FM_RADIO_MODE_FMR = 2 +}; + + +/** + * struct cg2900_channel_item - List object for channel. + * @list: list_head struct. + * @user: User for this channel. + */ +struct cg2900_channel_item { + struct list_head list; + struct cg2900_user_data *user; +}; + +/** + * struct cg2900_delayed_work_struct - Work structure for CG2900 chip. + * @delayed_work: Work structure. + * @data: Pointer to private data. + */ +struct cg2900_delayed_work_struct { + struct delayed_work work; + void *data; +}; + +/** + * struct cg2900_skb_data - Structure for storing private data in an sk_buffer. + * @dev: CG2900 device for this sk_buffer. + */ +struct cg2900_skb_data { + struct cg2900_user_data *user; +}; +#define cg2900_skb_data(__skb) ((struct cg2900_skb_data *)((__skb)->cb)) + +/** + * struct cg2900_chip_info - Main info structure for CG2900 chip driver. + * @dev: Current device. Same as @chip_dev->dev. + * @patch_file_name: Stores patch file name. + * @settings_file_name: Stores settings file name. + * @file_info: Firmware file info (patch or settings). + * @boot_state: Current BOOT-state of CG2900 chip driver. + * @closing_state: Current CLOSING-state of CG2900 chip driver. + * @file_load_state: Current BOOT_FILE_LOAD-state of CG2900 chip + * driver. + * @download_state: Current BOOT_DOWNLOAD-state of CG2900 chip + * driver. + * @wq: CG2900 chip driver workqueue. + * @chip_dev: Chip handler info. + * @tx_bt_lock: Spinlock used to protect some global structures + * related to internal BT command flow control. + * @tx_fm_lock: Spinlock used to protect some global structures + * related to internal FM command flow control. + * @tx_fm_audio_awaiting_irpt: Indicates if an FM interrupt event related to + * audio driver command is expected. + * @fm_radio_mode: Current FM radio mode. + * @tx_nr_pkts_allowed_bt: Number of packets allowed to send on BT HCI CMD + * H4 channel. + * @audio_bt_cmd_op: Stores the OpCode of the last sent audio driver + * HCI BT CMD. + * @audio_fm_cmd_id: Stores the command id of the last sent + * HCI FM RADIO command by the fm audio user. + * @hci_fm_cmd_func: Stores the command function of the last sent + * HCI FM RADIO command by the fm radio user. + * @tx_queue_bt: TX queue for HCI BT commands when nr of commands + * allowed is 0 (CG2900 internal flow control). + * @tx_queue_fm: TX queue for HCI FM commands when nr of commands + * allowed is 0 (CG2900 internal flow control). + * @user_in_charge: User currently operating. Normally used at + * channel open and close. + * @last_user: Last user of this chip. To avoid complications + * this will never be set for bt_audio and + * fm_audio. + * @logger: Logger user of this chip. + * @selftest_work: Delayed work for reading selftest results. + * @nbr_of_polls: Number of times we should poll for selftest + * results. + */ +struct cg2900_chip_info { + struct device *dev; + char *patch_file_name; + char *settings_file_name; + struct cg2900_file_info file_info; + enum main_state main_state; + enum boot_state boot_state; + enum closing_state closing_state; + enum file_load_state file_load_state; + enum download_state download_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + spinlock_t tx_bt_lock; + spinlock_t tx_fm_lock; + spinlock_t rw_lock; + bool tx_fm_audio_awaiting_irpt; + enum fm_radio_mode fm_radio_mode; + int tx_nr_pkts_allowed_bt; + u16 audio_bt_cmd_op; + u16 audio_fm_cmd_id; + u16 hci_fm_cmd_func; + struct sk_buff_head tx_queue_bt; + struct sk_buff_head tx_queue_fm; + struct list_head open_channels; + struct cg2900_user_data *user_in_charge; + struct cg2900_user_data *last_user; + struct cg2900_user_data *logger; + struct cg2900_user_data *bt_audio; + struct cg2900_user_data *fm_audio; + struct cg2900_delayed_work_struct selftest_work; + int nbr_of_polls; +}; + +/** + * struct main_info - Main info structure for CG2900 chip driver. + * @dev: Device structure. + * @cell_base_id: Base ID for MFD cells. + * @man_mutex: Management mutex. + */ +struct main_info { + struct device *dev; + int cell_base_id; + struct mutex man_mutex; +}; + +static struct main_info *main_info; + +/* + * main_wait_queue - Main Wait Queue in CG2900 driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue); + +static void chip_startup_finished(struct cg2900_chip_info *info, int err); +static void chip_shutdown(struct cg2900_user_data *user); + +/** + * bt_is_open() - Checks if any BT user is in open state. + * @info: CG2900 info. + * + * Returns: + * true if a BT channel is open. + * false if no BT channel is open. + */ +static bool bt_is_open(struct cg2900_chip_info *info) +{ + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == CHANNEL_BT_CMD) + return true; + } + return false; +} + +/** + * fm_is_open() - Checks if any FM user is in open state. + * @info: CG2900 info. + * + * Returns: + * true if a FM channel is open. + * false if no FM channel is open. + */ +static bool fm_is_open(struct cg2900_chip_info *info) +{ + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == CHANNEL_FM_RADIO) + return true; + } + return false; +} + +/** + * fm_irpt_expected() - check if this FM command will generate an interrupt. + * @cmd_id: command identifier. + * + * Returns: + * true if the command will generate an interrupt. + * false if it won't. + */ +static bool fm_irpt_expected(struct cg2900_chip_info *info, u16 cmd_id) +{ + bool retval = false; + + switch (cmd_id) { + case CG2900_FM_DO_AIP_FADE_START: + if (info->fm_radio_mode == FM_RADIO_MODE_FMT) + retval = true; + break; + + case CG2900_FM_DO_AUP_BT_FADE_START: + case CG2900_FM_DO_AUP_EXT_FADE_START: + case CG2900_FM_DO_AUP_FADE_START: + if (info->fm_radio_mode == FM_RADIO_MODE_FMR) + retval = true; + break; + + case CG2900_FM_DO_FMR_SETANTENNA: + case CG2900_FM_DO_FMR_SP_AFSWITCH_START: + case CG2900_FM_DO_FMR_SP_AFUPDATE_START: + case CG2900_FM_DO_FMR_SP_BLOCKSCAN_START: + case CG2900_FM_DO_FMR_SP_PRESETPI_START: + case CG2900_FM_DO_FMR_SP_SCAN_START: + case CG2900_FM_DO_FMR_SP_SEARCH_START: + case CG2900_FM_DO_FMR_SP_SEARCHPI_START: + case CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL: + case CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL: + case CG2900_FM_DO_FMT_PA_SETCTRL: + case CG2900_FM_DO_FMT_PA_SETMODE: + case CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL: + case CG2900_FM_DO_GEN_ANTENNACHECK_START: + case CG2900_FM_DO_GEN_GOTOMODE: + case CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE: + case CG2900_FM_DO_GEN_SELECTREFERENCECLOCK: + case CG2900_FM_DO_GEN_SETPROCESSINGCLOCK: + case CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL: + case CG2900_FM_DO_TST_TX_RAMP_START: + retval = true; + break; + + default: + break; + } + + if (retval) + dev_dbg(info->dev, "Following interrupt event expected for this" + " Cmd complete evt: cmd_id = 0x%X\n", + cmd_id); + + return retval; +} + +/** + * fm_is_do_cmd_irpt() - Check if irpt_val is one of the FM DO command related interrupts. + * @irpt_val: interrupt value. + * + * Returns: + * true if it's do-command related interrupt value. + * false if it's not. + */ +static bool fm_is_do_cmd_irpt(u16 irpt_val) +{ + if ((irpt_val & CG2900_FM_IRPT_OPERATION_SUCCEEDED) || + (irpt_val & CG2900_FM_IRPT_OPERATION_FAILED)) { + dev_dbg(MAIN_DEV, "Irpt evt for FM do-command found, " + "irpt_val = 0x%X\n", irpt_val); + return true; + } + + return false; +} + +/** + * fm_reset_flow_ctrl - Clears up internal FM flow control. + * + * Resets outstanding commands and clear FM TX list and set CG2900 FM mode to + * idle. + */ +static void fm_reset_flow_ctrl(struct cg2900_chip_info *info) +{ + dev_dbg(info->dev, "fm_reset_flow_ctrl\n"); + + skb_queue_purge(&info->tx_queue_fm); + + /* Reset the fm_cmd_id. */ + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + + info->fm_radio_mode = FM_RADIO_MODE_IDLE; +} + + +/** + * fm_parse_cmd - Parses a FM command packet. + * @data: FM command packet. + * @cmd_func: Out: FM legacy command function. + * @cmd_id: Out: FM legacy command ID. + */ +static void fm_parse_cmd(u8 *data, u8 *cmd_func, u16 *cmd_id) +{ + /* Move past H4-header to start of actual package */ + struct fm_leg_cmd *pkt = (struct fm_leg_cmd *)(data + HCI_H4_SIZE); + + *cmd_func = CG2900_FM_CMD_PARAM_NONE; + *cmd_id = CG2900_FM_CMD_NONE; + + if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) { + dev_err(MAIN_DEV, "fm_parse_cmd: Not an FM legacy command " + "0x%02X\n", pkt->opcode); + return; + } + + *cmd_func = pkt->fm_function; + if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) + *cmd_id = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->fm_cmd.head)); +} + + +/** + * fm_parse_event - Parses a FM event packet + * @data: FM event packet. + * @event: Out: FM event. + * @cmd_func: Out: FM legacy command function. + * @cmd_id: Out: FM legacy command ID. + * @intr_val: Out: FM interrupt value. + */ +static void fm_parse_event(u8 *data, u8 *event, u8 *cmd_func, u16 *cmd_id, + u16 *intr_val) +{ + /* Move past H4-header to start of actual package */ + union fm_leg_evt_or_irq *pkt = (union fm_leg_evt_or_irq *)data; + + *cmd_func = CG2900_FM_CMD_PARAM_NONE; + *cmd_id = CG2900_FM_CMD_NONE; + *intr_val = 0; + *event = CG2900_FM_EVENT_UNKNOWN; + + if (pkt->evt.opcode == CG2900_FM_GEN_ID_LEGACY && + pkt->evt.read_write == CG2900_FM_CMD_LEG_PARAM_WRITE) { + /* Command complete */ + *event = CG2900_FM_EVENT_CMD_COMPLETE; + *cmd_func = pkt->evt.fm_function; + if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) + *cmd_id = cg2900_get_fm_cmd_id( + le16_to_cpu(pkt->evt.response_head)); + } else if (pkt->irq_v2.opcode == CG2900_FM_GEN_ID_LEGACY && + pkt->irq_v2.event_type == CG2900_FM_CMD_LEG_PARAM_IRQ) { + /* Interrupt, PG2 style */ + *event = CG2900_FM_EVENT_INTERRUPT; + *intr_val = le16_to_cpu(pkt->irq_v2.irq); + } else if (pkt->irq_v1.opcode == CG2900_FM_GEN_ID_LEGACY) { + /* Interrupt, PG1 style */ + *event = CG2900_FM_EVENT_INTERRUPT; + *intr_val = le16_to_cpu(pkt->irq_v1.irq); + } else + dev_err(MAIN_DEV, "fm_parse_event: Not an FM legacy command " + "0x%X %X %X %X\n", data[0], data[1], data[2], data[3]); +} + +/** + * fm_update_mode - Updates the FM mode state machine. + * @data: FM command packet. + * + * Parses a FM command packet and updates the FM mode state machine. + */ +static void fm_update_mode(struct cg2900_chip_info *info, u8 *data) +{ + u8 cmd_func; + u16 cmd_id; + + fm_parse_cmd(data, &cmd_func, &cmd_id); + + if (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND && + cmd_id == CG2900_FM_DO_GEN_GOTOMODE) { + /* Move past H4-header to start of actual package */ + struct fm_leg_cmd *pkt = + (struct fm_leg_cmd *)(data + HCI_H4_SIZE); + + info->fm_radio_mode = le16_to_cpu(pkt->fm_cmd.data[0]); + dev_dbg(info->dev, "FM Radio mode changed to %d\n", + info->fm_radio_mode); + } +} + + +/** + * transmit_skb_from_tx_queue_bt() - Check flow control info and transmit skb. + * + * The transmit_skb_from_tx_queue_bt() function checks if there are tickets + * available and commands waiting in the TX queue and if so transmits them + * to the controller. + * It shall always be called within spinlock_bh. + */ +static void transmit_skb_from_tx_queue_bt(struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user; + struct cg2900_chip_info *info = dev->c_data; + struct sk_buff *skb; + + dev_dbg(dev->dev, "transmit_skb_from_tx_queue_bt\n"); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_bt); + while (skb) { + if (info->tx_nr_pkts_allowed_bt <= 0) { + /* + * If no more packets allowed just return, we'll get + * back here after next Command Complete/Status event. + * Put skb back at head of queue. + */ + skb_queue_head(&info->tx_queue_bt, skb); + return; + } + + (info->tx_nr_pkts_allowed_bt)--; + dev_dbg(dev->dev, "tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + user = cg2900_skb_data(skb)->user; /* user is never NULL */ + + /* + * If it's a command from audio application, store the OpCode, + * it'll be used later to decide where to dispatch + * the Command Complete event. + */ + if (info->bt_audio == user) { + struct hci_command_hdr *hdr = (struct hci_command_hdr *) + (skb->data + HCI_H4_SIZE); + + info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode); + dev_dbg(user->dev, + "Sending cmd from audio driver, saving " + "OpCode = 0x%04X\n", info->audio_bt_cmd_op); + } + + cg2900_tx_to_chip(user, info->logger, skb); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_bt); + } +} + +/** + * transmit_skb_from_tx_queue_fm() - Check flow control info and transmit skb. + * + * The transmit_skb_from_tx_queue_fm() function checks if it possible to + * transmit and commands waiting in the TX queue and if so transmits them + * to the controller. + * It shall always be called within spinlock_bh. + */ +static void transmit_skb_from_tx_queue_fm(struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user; + struct cg2900_chip_info *info = dev->c_data; + struct sk_buff *skb; + + dev_dbg(dev->dev, "transmit_skb_from_tx_queue_fm\n"); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_fm); + while (skb) { + u16 cmd_id; + u8 cmd_func; + + if (info->audio_fm_cmd_id != CG2900_FM_CMD_NONE || + info->hci_fm_cmd_func != CG2900_FM_CMD_PARAM_NONE) { + /* + * There are currently outstanding FM commands. + * Wait for them to finish. We will get back here later. + * Queue back the skb at head of list. + */ + skb_queue_head(&info->tx_queue_bt, skb); + return; + } + + user = cg2900_skb_data(skb)->user; /* user is never NULL */ + + if (!user->opened) { + /* + * Channel is not open. That means that the user that + * originally sent it has deregistered. + * Just throw it away and check the next skb in the + * queue. + */ + kfree_skb(skb); + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_fm); + continue; + } + + fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id); + + /* + * Store the FM command function , it'll be used later to decide + * where to dispatch the Command Complete event. + */ + if (info->fm_audio == user) { + info->audio_fm_cmd_id = cmd_id; + dev_dbg(user->dev, "Sending FM audio cmd 0x%04X\n", + info->audio_fm_cmd_id); + } else { + /* FM radio command */ + info->hci_fm_cmd_func = cmd_func; + fm_update_mode(info, &skb->data[0]); + dev_dbg(user->dev, "Sending FM radio cmd 0x%04X\n", + info->hci_fm_cmd_func); + } + + /* + * We have only one ticket on FM. Just return after + * sending the skb. + */ + cg2900_tx_to_chip(user, info->logger, skb); + return; + } +} + +/** + * update_flow_ctrl_bt() - Update number of outstanding commands for BT CMD. + * @dev: Current chip device. + * @skb: skb with received packet. + * + * The update_flow_ctrl_bt() checks if incoming data packet is + * BT Command Complete/Command Status Event and if so updates number of tickets + * and number of outstanding commands. It also calls function to send queued + * commands (if the list of queued commands is not empty). + */ +static void update_flow_ctrl_bt(struct cg2900_chip_dev *dev, + const struct sk_buff * const skb) +{ + u8 *data = skb->data; + struct hci_event_hdr *event; + struct cg2900_chip_info *info = dev->c_data; + + event = (struct hci_event_hdr *)data; + data += sizeof(*event); + + if (HCI_EV_CMD_COMPLETE == event->evt) { + struct hci_ev_cmd_complete *complete; + complete = (struct hci_ev_cmd_complete *)data; + + /* + * If it's HCI Command Complete Event then we might get some + * HCI tickets back. Also we can decrease the number outstanding + * HCI commands (if it's not NOP command or one of the commands + * that generate both Command Status Event and Command Complete + * Event). + * Check if we have any HCI commands waiting in the TX list and + * send them if there are tickets available. + */ + spin_lock_bh(&info->tx_bt_lock); + info->tx_nr_pkts_allowed_bt = complete->ncmd; + dev_dbg(dev->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + if (!skb_queue_empty(&info->tx_queue_bt)) + transmit_skb_from_tx_queue_bt(dev); + spin_unlock_bh(&info->tx_bt_lock); + } else if (HCI_EV_CMD_STATUS == event->evt) { + struct hci_ev_cmd_status *status; + status = (struct hci_ev_cmd_status *)data; + + /* + * If it's HCI Command Status Event then we might get some + * HCI tickets back. Also we can decrease the number outstanding + * HCI commands (if it's not NOP command). + * Check if we have any HCI commands waiting in the TX queue and + * send them if there are tickets available. + */ + spin_lock_bh(&info->tx_bt_lock); + info->tx_nr_pkts_allowed_bt = status->ncmd; + dev_dbg(dev->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + if (!skb_queue_empty(&info->tx_queue_bt)) + transmit_skb_from_tx_queue_bt(dev); + spin_unlock_bh(&info->tx_bt_lock); + } +} + +/** + * update_flow_ctrl_fm() - Update packets allowed for FM channel. + * @dev: Current chip device. + * @skb: skb with received packet. + * + * The update_flow_ctrl_fm() checks if incoming data packet is FM packet + * indicating that the previous command has been handled and if so update + * packets. It also calls function to send queued commands (if the list of + * queued commands is not empty). + */ +static void update_flow_ctrl_fm(struct cg2900_chip_dev *dev, + const struct sk_buff * const skb) +{ + u8 cmd_func = CG2900_FM_CMD_PARAM_NONE; + u16 cmd_id = CG2900_FM_CMD_NONE; + u16 irpt_val = 0; + u8 event = CG2900_FM_EVENT_UNKNOWN; + struct cg2900_chip_info *info = dev->c_data; + + fm_parse_event(&(skb->data[0]), &event, &cmd_func, &cmd_id, &irpt_val); + + if (event == CG2900_FM_EVENT_CMD_COMPLETE) { + /* FM legacy command complete event */ + spin_lock_bh(&info->tx_fm_lock); + /* + * Check if it's not an write command complete event, because + * then it cannot be a DO command. + * If it's a write command complete event check that is not a + * DO command complete event before setting the outstanding + * FM packets to none. + */ + if (cmd_func != CG2900_FM_CMD_PARAM_WRITECOMMAND || + !fm_irpt_expected(info, cmd_id)) { + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + dev_dbg(dev->dev, + "FM_Write: Outstanding FM commands:\n" + "\tRadio: 0x%04X\n" + "\tAudio: 0x%04X\n", + info->hci_fm_cmd_func, + info->audio_fm_cmd_id); + transmit_skb_from_tx_queue_fm(dev); + + /* + * If there was a write do command complete event check if it is + * DO command previously sent by the FM audio user. If that's + * the case we need remember that in order to be able to + * dispatch the interrupt to the correct user. + */ + } else if (cmd_id == info->audio_fm_cmd_id) { + info->tx_fm_audio_awaiting_irpt = true; + dev_dbg(dev->dev, + "FM Audio waiting for interrupt = true\n"); + } + spin_unlock_bh(&info->tx_fm_lock); + } else if (event == CG2900_FM_EVENT_INTERRUPT) { + /* FM legacy interrupt */ + if (fm_is_do_cmd_irpt(irpt_val)) { + /* + * If it is an interrupt related to a DO command update + * the outstanding flow control and transmit blocked + * FM commands. + */ + spin_lock_bh(&info->tx_fm_lock); + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + dev_dbg(dev->dev, + "FM_INT: Outstanding FM commands:\n" + "\tRadio: 0x%04X\n" + "\tAudio: 0x%04X\n", + info->hci_fm_cmd_func, + info->audio_fm_cmd_id); + info->tx_fm_audio_awaiting_irpt = false; + dev_dbg(dev->dev, + "FM Audio waiting for interrupt = false\n"); + transmit_skb_from_tx_queue_fm(dev); + spin_unlock_bh(&info->tx_fm_lock); + } + } +} + +/** + * send_bt_enable() - Send HCI VS BT Enable command to the chip. + * @info: Chip info structure. + * @bt_enable: Value for BT Enable parameter (e.g. CG2900_BT_DISABLE). + */ +static void send_bt_enable(struct cg2900_chip_info *info, u8 bt_enable) +{ + struct bt_vs_bt_enable_cmd cmd; + + cmd.op_code = cpu_to_le16(CG2900_BT_OP_VS_BT_ENABLE); + cmd.plen = BT_PARAM_LEN(sizeof(cmd)); + cmd.enable = bt_enable; + cg2900_send_bt_cmd(info->user_in_charge, info->logger, + &cmd, sizeof(cmd)); +} + +/** + * send_bd_address() - Send HCI VS command with BD address to the chip. + */ +static void send_bd_address(struct cg2900_chip_info *info) +{ + struct bt_vs_store_in_fs_cmd *cmd; + u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE; + + cmd = kmalloc(plen, GFP_KERNEL); + if (!cmd) { + dev_err(info->dev, "send_bd_address could not allocate cmd\n"); + return; + } + + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_STORE_IN_FS); + cmd->plen = BT_PARAM_LEN(plen); + cmd->user_id = CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR; + cmd->len = BT_BDADDR_SIZE; + /* Now copy the BD address received from user space control app. */ + memcpy(cmd->data, bd_address, BT_BDADDR_SIZE); + + dev_dbg(BOOT_DEV, "New boot_state: BOOT_SEND_BD_ADDRESS\n"); + info->boot_state = BOOT_SEND_BD_ADDRESS; + + cg2900_send_bt_cmd(info->user_in_charge, info->logger, cmd, plen); + + kfree(cmd); +} + +/** + * send_settings_file() - Transmit settings file. + * + * The send_settings_file() function transmit settings file. + * The file is read in parts to fit in HCI packets. When finished, + * close the settings file and send HCI reset to activate settings and patches. + */ +static void send_settings_file(struct cg2900_chip_info *info) +{ + int bytes_sent; + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_settings_file: Error %d occurred\n", + bytes_sent); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, bytes_sent); + return; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + /* Settings file finished. Release used resources */ + dev_dbg(BOOT_DEV, "Settings file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_NO_MORE_FILES\n"); + info->file_load_state = FILE_LOAD_NO_MORE_FILES; + + /* Create and send HCI VS Store In FS command with bd address. */ + send_bd_address(info); +} + +/** + * send_patch_file - Transmit patch file. + * + * The send_patch_file() function transmit patch file. + * The file is read in parts to fit in HCI packets. When the complete file is + * transmitted, the file is closed. + * When finished, continue with settings file. + */ +static void send_patch_file(struct cg2900_chip_dev *dev) +{ + int err; + int bytes_sent; + struct cg2900_chip_info *info = dev->c_data; + int file_name_size = strlen("CG2900_XXXX_XXXX_settings.fw"); + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_patch_file: Error %d occurred\n", + bytes_sent); + err = bytes_sent; + goto error_handling; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + dev_dbg(BOOT_DEV, "Patch file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + /* + * Create the settings file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->settings_file_name, file_name_size + 1, + "CG2900_%04X_%04X_settings.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading settings file %s\n", + info->settings_file_name); + } else { + dev_err(BOOT_DEV, "Settings file name failed! err=%d\n", err); + goto error_handling; + } + + /* Retrieve the settings file */ + err = request_firmware(&info->file_info.fw_file, + info->settings_file_name, + info->dev); + if (err) { + dev_err(BOOT_DEV, "Couldn't get settings file (%d)\n", err); + goto error_handling; + } + /* Now send the settings file */ + dev_dbg(BOOT_DEV, + "New file_load_state: FILE_LOAD_GET_STATIC_SETTINGS\n"); + info->file_load_state = FILE_LOAD_GET_STATIC_SETTINGS; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + send_settings_file(info); + return; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, err); +} + +/** + * work_power_off_chip() - Work item to power off the chip. + * @work: Reference to work data. + * + * The work_power_off_chip() function handles transmission of the HCI command + * vs_power_switch_off and then informs the CG2900 Core that this chip driver is + * finished and the Core driver can now shut off the chip. + */ +static void work_power_off_chip(struct work_struct *work) +{ + struct sk_buff *skb = NULL; + u8 *h4_header; + struct cg2900_platform_data *pf_data; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_power_off_chip: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* + * Get the VS Power Switch Off command to use based on connected + * connectivity controller + */ + pf_data = dev_get_platdata(dev->dev); + if (pf_data->get_power_switch_off_cmd) + skb = pf_data->get_power_switch_off_cmd(dev, NULL); + + /* + * Transmit the received command. + * If no command found for the device, just continue + */ + if (!skb) { + dev_err(dev->dev, + "Could not retrieve PowerSwitchOff command\n"); + goto shut_down_chip; + } + + dev_dbg(dev->dev, + "Got power_switch_off command. Add H4 header and transmit\n"); + + /* + * Move the data pointer to the H:4 header position and store + * the H4 header + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = CHANNEL_BT_CMD; + + dev_dbg(dev->dev, "New closing_state: CLOSING_POWER_SWITCH_OFF\n"); + info->closing_state = CLOSING_POWER_SWITCH_OFF; + + if (info->user_in_charge) + cg2900_tx_to_chip(info->user_in_charge, info->logger, skb); + else + cg2900_tx_no_user(dev, skb); + + /* + * Mandatory to wait 500ms after the power_switch_off command has been + * transmitted, in order to make sure that the controller is ready. + */ + schedule_timeout_killable(msecs_to_jiffies(POWER_SW_OFF_WAIT)); + +shut_down_chip: + dev_dbg(dev->dev, "New closing_state: CLOSING_SHUT_DOWN\n"); + info->closing_state = CLOSING_SHUT_DOWN; + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + /* Chip shut-down finished, set correct state and wake up the chip. */ + dev_dbg(dev->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + wake_up_all(&main_wait_queue); + + kfree(my_work); +} + +/** + * work_chip_shutdown() - Shut down the chip. + * @work: Reference to work data. + */ +static void work_chip_shutdown(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_user_data *user; + + if (!work) { + dev_err(MAIN_DEV, "work_chip_shutdown: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + user = my_work->user_data; + + chip_shutdown(user); + + kfree(my_work); +} + +/** + * work_reset_after_error() - Handle reset. + * @work: Reference to work data. + * + * Handle a reset after received Command Complete event. + */ +static void work_reset_after_error(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_reset_after_error: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + chip_startup_finished(info, -EIO); + + kfree(my_work); +} + +/** + * work_load_patch_and_settings() - Start loading patches and settings. + * @work: Reference to work data. + */ +static void work_load_patch_and_settings(struct work_struct *work) +{ + int err = 0; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int file_name_size = strlen("CG2900_XXXX_XXXX_patch.fw"); + + if (!work) { + dev_err(MAIN_DEV, + "work_load_patch_and_settings: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Check that we are in the right state */ + if (info->boot_state != BOOT_GET_FILES_TO_LOAD) + goto finished; + + /* + * Create the patch file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->patch_file_name, file_name_size + 1, + "CG2900_%04X_%04X_patch.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading patch file %s\n", + info->patch_file_name); + } else { + dev_err(BOOT_DEV, "Patch file name failed! err=%d\n", err); + goto error_handling; + } + + /* We now all info needed */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DOWNLOAD_PATCH\n"); + info->boot_state = BOOT_DOWNLOAD_PATCH; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_GET_PATCH\n"); + info->file_load_state = FILE_LOAD_GET_PATCH; + info->file_info.chunk_id = 0; + info->file_info.file_offset = 0; + info->file_info.fw_file = NULL; + + /* OK. Now it is time to download the patches */ + err = request_firmware(&(info->file_info.fw_file), + info->patch_file_name, + dev->dev); + if (err < 0) { + dev_err(BOOT_DEV, "Couldn't get patch file (%d)\n", err); + goto error_handling; + } + send_patch_file(dev); + + goto finished; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); +finished: + kfree(my_work); +} + +/** + * work_cont_file_download() - A file block has been written. + * @work: Reference to work data. + * + * Handle a received HCI VS Write File Block Complete event. + * Normally this means continue to send files to the controller. + */ +static void work_cont_file_download(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_cont_file_download: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Continue to send patches or settings to the controller */ + if (info->file_load_state == FILE_LOAD_GET_PATCH) + send_patch_file(dev); + else if (info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS) + send_settings_file(info); + else + dev_dbg(BOOT_DEV, "No more files to load\n"); + + kfree(my_work); +} + +/** + * work_send_read_selftest_cmd() - HCI VS Read_SelfTests_Result command shall be sent. + * @work: Reference to work data. + */ +static void work_send_read_selftest_cmd(struct work_struct *work) +{ + struct delayed_work *del_work; + struct cg2900_delayed_work_struct *current_work; + struct cg2900_chip_info *info; + struct hci_command_hdr cmd; + + if (!work) { + dev_err(MAIN_DEV, + "work_send_read_selftest_cmd: work == NULL\n"); + return; + } + + del_work = to_delayed_work(work); + current_work = container_of(del_work, + struct cg2900_delayed_work_struct, work); + info = current_work->data; + + if (info->boot_state != BOOT_READ_SELFTEST_RESULT) + return; + + cmd.opcode = cpu_to_le16(CG2900_BT_OP_VS_READ_SELTESTS_RESULT); + cmd.plen = 0; /* No parameters for Read Selftests Result */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); +} + +/** + * handle_reset_cmd_complete() - Handles HCI Reset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_reset_cmd_complete(struct cg2900_chip_dev *dev, u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, "Received Reset complete event with status 0x%X\n", + status); + + if (CG2900_CLOSING != info->main_state && + CLOSING_RESET != info->closing_state) + return false; + + if (HCI_BT_ERROR_NO_ERROR != status) { + /* + * Continue in case of error, the chip is going to be shut down + * anyway. + */ + dev_err(BOOT_DEV, "Command complete for HciReset received with " + "error 0x%X\n", status); + } + + cg2900_create_work_item(info->wq, work_power_off_chip, dev); + + return true; +} + +/** + * handle_vs_store_in_fs_cmd_complete() - Handles HCI VS StoreInFS Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_store_in_fs_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, + "Received Store_in_FS complete event with status 0x%X\n", + status); + + if (info->boot_state != BOOT_SEND_BD_ADDRESS) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) { + struct hci_command_hdr cmd; + + /* Send HCI SystemReset command to activate patches */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_ACTIVATE_PATCHES_AND_SETTINGS\n"); + info->boot_state = BOOT_ACTIVATE_PATCHES_AND_SETTINGS; + + cmd.opcode = cpu_to_le16(CG2900_BT_OP_VS_SYSTEM_RESET); + cmd.plen = 0; /* No parameters for System Reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); + } else { + dev_err(BOOT_DEV, + "Command complete for StoreInFS received with error " + "0x%X\n", status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_complete() - Handles HCI VS WriteFileBlock Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) + cg2900_create_work_item(info->wq, work_cont_file_download, dev); + else { + dev_err(BOOT_DEV, + "Command complete for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_status() - Handles HCI VS WriteFileBlock Command Status event. + * @status: Returned status of WriteFileBlock command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Command status for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_power_switch_off_cmd_complete() - Handles HCI VS PowerSwitchOff Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_power_switch_off_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (CLOSING_POWER_SWITCH_OFF != info->closing_state) + return false; + + dev_dbg(BOOT_DEV, + "handle_vs_power_switch_off_cmd_complete status %d\n", status); + + /* + * We were waiting for this but we don't need to do anything upon + * reception except warn for error status + */ + if (HCI_BT_ERROR_NO_ERROR != status) + dev_err(BOOT_DEV, + "Command Complete for PowerSwitchOff received with " + "error 0x%X", status); + + return true; +} + +/** + * handle_vs_system_reset_cmd_complete() - Handle HCI VS SystemReset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_system_reset_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_system_reset_cmd_complete status %d\n", + status); + + if (HCI_BT_ERROR_NO_ERROR == status) { + if (dev->chip.hci_revision == CG2900_PG2_REV) { + /* + * We must now wait for the selftest results. They will + * take a certain amount of time to finish so start a + * delayed work that will then send the command. + */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_READ_SELFTEST_RESULT\n"); + info->boot_state = BOOT_READ_SELFTEST_RESULT; + queue_delayed_work(info->wq, &info->selftest_work.work, + msecs_to_jiffies(SELFTEST_INITIAL)); + info->nbr_of_polls = 0; + } else { + /* + * We are now almost finished. Shut off BT Core. It will + * be re-enabled by the Bluetooth driver when needed. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DISABLE_BT\n"); + info->boot_state = BOOT_DISABLE_BT; + send_bt_enable(info, CG2900_BT_DISABLE); + } + } else { + dev_err(BOOT_DEV, + "Received Reset complete event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_vs_read_selftests_cmd_complete() - Handle HCI VS ReadSelfTestsResult Command Complete event. + * @dev: Current chip. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_read_selftests_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + struct bt_vs_read_selftests_result_evt *evt = + (struct bt_vs_read_selftests_result_evt *)data; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_READ_SELFTEST_RESULT) + return false; + + dev_dbg(BOOT_DEV, + "handle_vs_read_selftests_cmd_complete status %d result %d\n", + evt->status, evt->result); + + if (HCI_BT_ERROR_NO_ERROR != evt->status) + goto err_handling; + + if (CG2900_BT_SELFTEST_SUCCESSFUL == evt->result || + CG2900_BT_SELFTEST_FAILED == evt->result) { + if (CG2900_BT_SELFTEST_FAILED == evt->result) + dev_err(BOOT_DEV, "CG2900 self test failed\n"); + + /* + * We are now almost finished. Shut off BT Core. It will + * be re-enabled by the Bluetooth driver when needed. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DISABLE_BT\n"); + info->boot_state = BOOT_DISABLE_BT; + send_bt_enable(info, CG2900_BT_DISABLE); + return true; + } else if (CG2900_BT_SELFTEST_NOT_COMPLETED == evt->result) { + /* + * Self tests are not yet finished. Wait some more time + * before resending the command + */ + if (info->nbr_of_polls > MAX_NBR_OF_POLLS) { + dev_err(BOOT_DEV, "Selftest results reached max" + " number of polls\n"); + goto err_handling; + } + queue_delayed_work(info->wq, &info->selftest_work.work, + msecs_to_jiffies(SELFTEST_POLLING)); + info->nbr_of_polls++; + return true; + } + +err_handling: + dev_err(BOOT_DEV, + "Received Read SelfTests Result complete event with " + "status 0x%X and result 0x%X\n", + evt->status, evt->result); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + return true; +} + +/** + * handle_vs_bt_enable_cmd_status() - Handles HCI VS BtEnable Command Status event. + * @status: Returned status of BtEnable command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_bt_enable_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DISABLE_BT) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_bt_enable_cmd_status status %d\n", status); + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Received BtEnable status event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_vs_bt_enable_cmd_complete() - Handle HCI VS BtEnable Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_bt_enable_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DISABLE_BT) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_bt_enable_cmd_complete status %d\n", + status); + + if (HCI_BT_ERROR_NO_ERROR == status) { + /* + * The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + chip_startup_finished(info, 0); + } else { + dev_err(BOOT_DEV, + "Received BtEnable complete event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if received data should be handled in CG2900 chip driver. + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in CG2900 chip driver. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + /* skb cannot be NULL here so it is safe to de-reference */ + u8 *data = skb->data; + struct hci_event_hdr *evt; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + data += sizeof(*evt); + + /* First check the event code. */ + if (HCI_EV_CMD_COMPLETE == evt->evt) { + struct hci_ev_cmd_complete *cmd_complete; + + cmd_complete = (struct hci_ev_cmd_complete *)data; + op_code = le16_to_cpu(cmd_complete->opcode); + dev_dbg(dev->dev, + "Received Command Complete: op_code = 0x%04X\n", + op_code); + /* Move to first byte after OCF */ + data += sizeof(*cmd_complete); + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete(dev, data); + else if (op_code == CG2900_BT_OP_VS_STORE_IN_FS) + pkt_handled = handle_vs_store_in_fs_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = + handle_vs_write_file_block_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_POWER_SWITCH_OFF) + pkt_handled = + handle_vs_power_switch_off_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_SYSTEM_RESET) + pkt_handled = handle_vs_system_reset_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_BT_ENABLE) + pkt_handled = handle_vs_bt_enable_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_READ_SELTESTS_RESULT) + pkt_handled = handle_vs_read_selftests_cmd_complete(dev, + data); + } else if (HCI_EV_CMD_STATUS == evt->evt) { + struct hci_ev_cmd_status *cmd_status; + + cmd_status = (struct hci_ev_cmd_status *)data; + + op_code = le16_to_cpu(cmd_status->opcode); + + dev_dbg(dev->dev, "Received Command Status: op_code = 0x%04X\n", + op_code); + + if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = handle_vs_write_file_block_cmd_status + (dev, cmd_status->status); + else if (op_code == CG2900_BT_OP_VS_BT_ENABLE) + pkt_handled = handle_vs_bt_enable_cmd_status + (dev, cmd_status->status); + } else if (HCI_EV_HW_ERROR == evt->evt) { + struct hci_ev_hw_error *hw_error; + + hw_error = (struct hci_ev_hw_error *)data; + /* + * Only do a printout. There might be a receiving stack that can + * handle this event + */ + dev_err(dev->dev, "HW Error event received with error 0x%02X\n", + hw_error->hw_code); + return false; + } else + return false; + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * transmit_skb_with_flow_ctrl_bt() - Send the BT skb to the controller if it is allowed or queue it. + * @user: Current user. + * @skb: Data packet. + * + * The transmit_skb_with_flow_ctrl_bt() function checks if there are + * tickets available and if so transmits buffer to controller. Otherwise the skb + * and user name is stored in a list for later sending. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void transmit_skb_with_flow_ctrl_bt(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + /* + * Because there are more users of some H4 channels (currently audio + * application for BT command and FM channel) we need to have an + * internal HCI command flow control in CG2900 driver. + * So check here how many tickets we have and store skb in a queue if + * there are no tickets left. The skb will be sent later when we get + * more ticket(s). + */ + spin_lock_bh(&info->tx_bt_lock); + + if (info->tx_nr_pkts_allowed_bt > 0) { + info->tx_nr_pkts_allowed_bt--; + dev_dbg(user->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + /* + * If it's command from audio app store the OpCode, + * it'll be used later to decide where to dispatch Command + * Complete event. + */ + if (info->bt_audio == user) { + struct hci_command_hdr *hdr = (struct hci_command_hdr *) + (skb->data + HCI_H4_SIZE); + + info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode); + dev_dbg(user->dev, + "Sending cmd from audio driver, saving " + "OpCode = 0x%X\n", + info->audio_bt_cmd_op); + } + + cg2900_tx_to_chip(user, info->logger, skb); + } else { + dev_dbg(user->dev, "Not allowed to send cmd to controller, " + "storing in TX queue\n"); + + cg2900_skb_data(skb)->user = user; + skb_queue_tail(&info->tx_queue_bt, skb); + } + spin_unlock_bh(&info->tx_bt_lock); +} + +/** + * transmit_skb_with_flow_ctrl_fm() - Send the FM skb to the controller if it is allowed or queue it. + * @user: Current user. + * @skb: Data packet. + * + * The transmit_skb_with_flow_ctrl_fm() function checks if chip is available and + * if so transmits buffer to controller. Otherwise the skb and user name is + * stored in a list for later sending. + * Also it updates the FM radio mode if it's FM GOTOMODE command, this is needed + * to know how to handle some FM DO commands complete events. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void transmit_skb_with_flow_ctrl_fm(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + u8 cmd_func = CG2900_FM_CMD_PARAM_NONE; + u16 cmd_id = CG2900_FM_CMD_NONE; + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id); + + /* + * If this is an FM IP disable or reset send command and also reset + * the flow control and audio user. + */ + if (cmd_func == CG2900_FM_CMD_PARAM_DISABLE || + cmd_func == CG2900_FM_CMD_PARAM_RESET) { + spin_lock_bh(&info->tx_fm_lock); + fm_reset_flow_ctrl(info); + spin_unlock_bh(&info->tx_fm_lock); + cg2900_tx_to_chip(user, info->logger, skb); + return; + } + + /* + * If this is a FM user and no FM audio user command pending just send + * FM command. It is up to the user of the FM channel to handle its own + * flow control. + */ + spin_lock_bh(&info->tx_fm_lock); + if (info->fm_audio != user && + info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) { + info->hci_fm_cmd_func = cmd_func; + dev_dbg(user->dev, "Sending FM radio command 0x%04X\n", + info->hci_fm_cmd_func); + /* If a GotoMode command update FM mode */ + fm_update_mode(info, &skb->data[0]); + cg2900_tx_to_chip(user, info->logger, skb); + } else if (info->fm_audio == user && + info->hci_fm_cmd_func == CG2900_FM_CMD_PARAM_NONE && + info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) { + /* + * If it's command from fm audio user store the command id. + * It'll be used later to decide where to dispatch + * command complete event. + */ + info->audio_fm_cmd_id = cmd_id; + dev_dbg(user->dev, "Sending FM audio command 0x%04X\n", + info->audio_fm_cmd_id); + cg2900_tx_to_chip(user, info->logger, skb); + } else { + dev_dbg(user->dev, + "Not allowed to send FM cmd to controller, storing in " + "TX queue\n"); + + cg2900_skb_data(skb)->user = user; + skb_queue_tail(&info->tx_queue_fm, skb); + } + spin_unlock_bh(&info->tx_fm_lock); +} + +/** + * is_bt_audio_user() - Checks if this packet is for the BT audio user. + * @info: CG2900 info. + * @h4_channel: H:4 channel for this packet. + * @skb: Packet to check. + * + * Returns: + * true if packet is for BT audio user. + * false otherwise. + */ +static bool is_bt_audio_user(struct cg2900_chip_info *info, int h4_channel, + const struct sk_buff * const skb) +{ + struct hci_event_hdr *hdr; + u8 *payload; + u16 opcode; + + if (h4_channel != CHANNEL_BT_EVT) + return false; + + hdr = (struct hci_event_hdr *)skb->data; + payload = (u8 *)(hdr + 1); /* follows header */ + + if (HCI_EV_CMD_COMPLETE == hdr->evt) + opcode = le16_to_cpu( + ((struct hci_ev_cmd_complete *)payload)->opcode); + else if (HCI_EV_CMD_STATUS == hdr->evt) + opcode = le16_to_cpu( + ((struct hci_ev_cmd_status *)payload)->opcode); + else + return false; + + if (opcode != info->audio_bt_cmd_op) + return false; + + dev_dbg(info->bt_audio->dev, "Audio BT OpCode match = 0x%04X\n", + opcode); + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + return true; +} + +/** + * is_fm_audio_user() - Checks if this packet is for the FM audio user. + * @info: CG2900 info. + * @h4_channel: H:4 channel for this packet. + * @skb: Packet to check. + * + * Returns: + * true if packet is for BT audio user. + * false otherwise. + */ +static bool is_fm_audio_user(struct cg2900_chip_info *info, int h4_channel, + const struct sk_buff * const skb) +{ + u8 cmd_func; + u16 cmd_id; + u16 irpt_val; + u8 event; + + if (h4_channel != CHANNEL_FM_RADIO) + return false; + + cmd_func = CG2900_FM_CMD_PARAM_NONE; + cmd_id = CG2900_FM_CMD_NONE; + irpt_val = 0; + event = CG2900_FM_EVENT_UNKNOWN; + + fm_parse_event(&skb->data[0], &event, &cmd_func, &cmd_id, + &irpt_val); + /* Check if command complete event FM legacy interface. */ + if ((event == CG2900_FM_EVENT_CMD_COMPLETE) && + (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) && + (cmd_id == info->audio_fm_cmd_id)) { + dev_dbg(info->fm_audio->dev, + "FM Audio Function Code match = 0x%04X\n", + cmd_id); + return true; + } + + /* Check if Interrupt legacy interface. */ + if ((event == CG2900_FM_EVENT_INTERRUPT) && + (fm_is_do_cmd_irpt(irpt_val)) && + (info->tx_fm_audio_awaiting_irpt)) + return true; + + return false; +} + +/** + * data_from_chip() - Called when data is received from the chip. + * @dev: Chip info. + * @cg2900_dev: CG2900 user for this packet. + * @skb: Packet received. + * + * The data_from_chip() function updates flow control and checks + * if packet is a response for a packet it itself has transmitted. If not it + * finds the correct user and sends the packet* to the user. + */ +static void data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + int h4_channel; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + struct cg2900_chip_info *info = dev->c_data; + struct cg2900_user_data *user = NULL; + + spin_lock_bh(&info->rw_lock); + /* Copy RX Data into logger.*/ + if (info->logger) + cg2900_send_to_hci_logger(info->logger, skb, + LOGGER_DIRECTION_RX); + + h4_channel = skb->data[0]; + skb_pull(skb, HCI_H4_SIZE); + + /* First check if it is a BT or FM audio event */ + if (is_bt_audio_user(info, h4_channel, skb)) + user = info->bt_audio; + else if (is_fm_audio_user(info, h4_channel, skb)) + user = info->fm_audio; + spin_unlock_bh(&info->rw_lock); + + /* Now check if we should update flow control */ + if (h4_channel == CHANNEL_BT_EVT) + update_flow_ctrl_bt(dev, skb); + else if (h4_channel == CHANNEL_FM_RADIO) + update_flow_ctrl_fm(dev, skb); + + /* Then check if this is a response to data we have sent */ + if (h4_channel == CHANNEL_BT_EVT && handle_rx_data_bt_evt(dev, skb)) + return; + + spin_lock_bh(&info->rw_lock); + + if (user) + goto user_found; + + /* Let's see if it is the last user */ + if (info->last_user && info->last_user->h4_channel == h4_channel) { + user = info->last_user; + goto user_found; + } + + /* Search through the list of all open channels to find the user */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == h4_channel) { + user = tmp->user; + goto user_found; + } + } + +user_found: + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + + spin_unlock_bh(&info->rw_lock); + + if (user) + user->read_cb(user, skb); + else { + dev_err(dev->dev, + "Could not find corresponding user to h4_channel %d\n", + h4_channel); + kfree_skb(skb); + } +} + +/** + * chip_removed() - Called when transport has been removed. + * @dev: Chip device. + * + * Removes registered MFD devices and frees internal resources. + */ +static void chip_removed(struct cg2900_chip_dev *dev) +{ + struct cg2900_chip_info *info = dev->c_data; + + cancel_delayed_work(&info->selftest_work.work); + mfd_remove_devices(dev->dev); + kfree(info->settings_file_name); + kfree(info->patch_file_name); + destroy_workqueue(info->wq); + kfree(info); + dev->c_data = NULL; + dev->c_cb.chip_removed = NULL; + dev->c_cb.data_from_chip = NULL; +} + +/** + * last_bt_user_removed() - Called when last BT user is removed. + * @info: Chip handler info. + * + * Clears out TX queue for BT. + */ +static void last_bt_user_removed(struct cg2900_chip_info *info) +{ + spin_lock_bh(&info->tx_bt_lock); + skb_queue_purge(&info->tx_queue_bt); + + /* + * Reset number of packets allowed and number of outstanding + * BT commands. + */ + info->tx_nr_pkts_allowed_bt = 1; + /* Reset the audio_bt_cmd_op. */ + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + spin_unlock_bh(&info->tx_bt_lock); +} + +/** + * last_fm_user_removed() - Called when last FM user is removed. + * @info: Chip handler info. + * + * Clears out TX queue for BT. + */ +static void last_fm_user_removed(struct cg2900_chip_info *info) +{ + spin_lock_bh(&info->tx_fm_lock); + fm_reset_flow_ctrl(info); + spin_unlock_bh(&info->tx_fm_lock); +} + +/** + * chip_shutdown() - Reset and power the chip off. + * @user: MFD device. + */ +static void chip_shutdown(struct cg2900_user_data *user) +{ + struct hci_command_hdr cmd; + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(user->dev, "chip_shutdown\n"); + + /* First do a quick power switch of the chip to assure a good state */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + + /* + * Wait 50ms before continuing to be sure that the chip detects + * chip power off. + */ + schedule_timeout_killable( + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_killable(msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + info->user_in_charge = user; + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport and to put BT part in reset. + */ + dev_dbg(user->dev, "New closing_state: CLOSING_RESET\n"); + info->closing_state = CLOSING_RESET; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); +} + +/** + * chip_startup_finished() - Called when chip startup has finished. + * @info: Chip handler info. + * @err: Result of chip startup, 0 for no error. + * + * Shuts down the chip upon error, sets state to active, wakes waiting threads, + * and informs transport that startup has finished. + */ +static void chip_startup_finished(struct cg2900_chip_info *info, int err) +{ + dev_dbg(BOOT_DEV, "chip_startup_finished (%d)\n", err); + + if (err) + /* Shutdown the chip */ + cg2900_create_work_item(info->wq, work_chip_shutdown, + info->user_in_charge); + else { + dev_dbg(BOOT_DEV, "New main_state: CG2900_ACTIVE\n"); + info->main_state = CG2900_ACTIVE; + } + + wake_up_all(&main_wait_queue); + + if (err) + return; + + if (!info->chip_dev->t_cb.chip_startup_finished) + dev_dbg(BOOT_DEV, "chip_startup_finished callback not found\n"); + else + info->chip_dev->t_cb.chip_startup_finished(info->chip_dev); +} + +/** + * cg2900_open() - Called when user wants to open an H4 channel. + * @user: MFD device to open. + * + * Checks that H4 channel is not already opened. If chip is not started, starts + * up the chip. Sets channel as opened and adds user to active users. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL or read_cb is NULL. + * -EBUSY if chip is in transit state (being started or shutdown). + * -EACCES if H4 channel is already opened. + * -ENOMEM if allocation fails. + * -EIO if chip startup fails. + * Error codes generated by t_cb.open. + */ +static int cg2900_open(struct cg2900_user_data *user) +{ + int err; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!user->read_cb) { + dev_err(user->dev, "cg2900_open: read_cb missing\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* + * Add a minor wait in order to avoid CPU blocking, looping openings. + * Note there will of course be no wait if we are already in the right + * state. + */ + err = wait_event_timeout(main_wait_queue, + (CG2900_IDLE == info->main_state || + CG2900_ACTIVE == info->main_state), + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + if (err <= 0) { + if (CG2900_INIT == info->main_state) + dev_err(user->dev, "Transport not opened\n"); + else + dev_err(user->dev, "cg2900_open currently busy (0x%X). " + "Try again\n", info->main_state); + err = -EBUSY; + goto err_free_mutex; + } + + err = 0; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == user->h4_channel && + tmp->user->is_audio == user->is_audio) { + dev_err(user->dev, "Channel %d is already opened\n", + user->h4_channel); + err = -EACCES; + goto err_free_mutex; + } + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + dev_err(user->dev, "Could not allocate tmp\n"); + err = -ENOMEM; + goto err_free_mutex; + } + tmp->user = user; + + if (CG2900_ACTIVE != info->main_state && + !user->chip_independent) { + /* Open transport and start-up the chip */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait to be sure that the chip is ready */ + schedule_timeout_killable( + msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (dev->t_cb.open) { + err = dev->t_cb.open(dev); + if (err) { + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + goto err_free_list_item; + } + } + + /* Start the boot sequence */ + info->user_in_charge = user; + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + dev_dbg(user->dev, "New boot_state: BOOT_GET_FILES_TO_LOAD\n"); + info->boot_state = BOOT_GET_FILES_TO_LOAD; + dev_dbg(user->dev, "New main_state: CG2900_BOOTING\n"); + info->main_state = CG2900_BOOTING; + cg2900_create_work_item(info->wq, work_load_patch_and_settings, + dev); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to start\n"); + wait_event_timeout(main_wait_queue, + (CG2900_ACTIVE == info->main_state || + CG2900_IDLE == info->main_state), + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + if (CG2900_ACTIVE != info->main_state) { + dev_err(user->dev, "CG2900 driver failed to start\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + err = -EIO; + goto err_free_list_item; + } + } + + list_add_tail(&tmp->list, &info->open_channels); + + user->opened = true; + + dev_dbg(user->dev, "H:4 channel opened\n"); + + mutex_unlock(&main_info->man_mutex); + return 0; +err_free_list_item: + kfree(tmp); +err_free_mutex: + mutex_unlock(&main_info->man_mutex); + return err; +} + +/** + * cg2900_hci_log_open() - Called when user wants to open HCI logger channel. + * @user: MFD device to open. + * + * Registers user as hci_logger and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EACCES if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_hci_log_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_log_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_hci_log_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->logger) { + dev_err(user->dev, "HCI Logger already stored\n"); + return -EACCES; + } + + info->logger = user; + err = cg2900_open(user); + if (err) + info->logger = NULL; + return err; +} + +/** + * cg2900_bt_audio_open() - Called when user wants to open BT audio channel. + * @user: MFD device to open. + * + * Registers user as bt_audio and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EACCES if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_bt_audio_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_bt_audio_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_bt_audio_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->bt_audio) { + dev_err(user->dev, "BT Audio already stored\n"); + return -EACCES; + } + + info->bt_audio = user; + err = cg2900_open(user); + if (err) + info->bt_audio = NULL; + return err; +} + +/** + * cg2900_fm_audio_open() - Called when user wants to open FM audio channel. + * @user: MFD device to open. + * + * Registers user as fm_audio and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EACCES if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_fm_audio_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_fm_audio_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_fm_audio_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->fm_audio) { + dev_err(user->dev, "FM Audio already stored\n"); + return -EACCES; + } + + info->fm_audio = user; + err = cg2900_open(user); + if (err) + info->fm_audio = NULL; + return err; +} + +/** + * cg2900_close() - Called when user wants to close an H4 channel. + * @user: MFD device to close. + * + * Clears up internal resources, sets channel as closed, and shuts down chip if + * this was the last user. + */ +static void cg2900_close(struct cg2900_user_data *user) +{ + bool keep_powered = false; + struct list_head *cursor, *next; + struct cg2900_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* + * Go through each open channel. Remove our channel and check if there + * is any other channel that want to keep the chip running + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user == user) { + list_del(cursor); + kfree(tmp); + } else if (!tmp->user->chip_independent) + keep_powered = true; + } + + if (user->h4_channel == CHANNEL_BT_CMD && !bt_is_open(info)) + last_bt_user_removed(info); + else if (user->h4_channel == CHANNEL_FM_RADIO && !fm_is_open(info)) + last_fm_user_removed(info); + + if (keep_powered) + /* This was not the last user, we're done. */ + goto finished; + + if (CG2900_IDLE == info->main_state) + /* Chip has already been shut down. */ + goto finished; + + dev_dbg(user->dev, "New main_state: CG2900_CLOSING\n"); + info->main_state = CG2900_CLOSING; + chip_shutdown(user); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to shut-down\n"); + wait_event_timeout(main_wait_queue, + (CG2900_IDLE == info->main_state), + msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT)); + + /* Force shutdown if we timed out */ + if (CG2900_IDLE != info->main_state) { + dev_err(user->dev, + "ST-Ericsson CG2900 Core Driver was shut-down with " + "problems\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + } + +finished: + mutex_unlock(&main_info->man_mutex); + user->opened = false; + dev_dbg(user->dev, "H:4 channel closed\n"); +} + +/** + * cg2900_hci_log_close() - Called when user wants to close HCI logger channel. + * @user: MFD device to close. + * + * Clears hci_logger user and calls @cg2900_close to close the channel. + */ +static void cg2900_hci_log_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_log_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_hci_log_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->logger) { + dev_err(user->dev, "cg2900_hci_log_close: Trying to remove " + "another user\n"); + return; + } + + info->logger = NULL; + cg2900_close(user); +} + +/** + * cg2900_bt_audio_close() - Called when user wants to close BT audio channel. + * @user: MFD device to close. + * + * Clears bt_audio user and calls @cg2900_close to close the channel. + */ +static void cg2900_bt_audio_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_bt_audio_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_bt_audio_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->bt_audio) { + dev_err(user->dev, "cg2900_bt_audio_close: Trying to remove " + "another user\n"); + return; + } + + info->bt_audio = NULL; + cg2900_close(user); +} + +/** + * cg2900_fm_audio_close() - Called when user wants to close FM audio channel. + * @user: MFD device to close. + * + * Clears fm_audio user and calls @cg2900_close to close the channel. + */ +static void cg2900_fm_audio_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_fm_audio_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_fm_audio_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->fm_audio) { + dev_err(user->dev, "cg2900_fm_audio_close: Trying to remove " + "another user\n"); + return; + } + + info->fm_audio = NULL; + cg2900_close(user); +} + +/** + * cg2900_reset() - Called when user wants to reset the chip. + * @user: MFD device to reset. + * + * Closes down the chip and calls reset_cb for all open users. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + */ +static int cg2900_reset(struct cg2900_user_data *user) +{ + struct list_head *cursor, *next; + struct cg2900_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!user) { + dev_err(MAIN_DEV, "cg2900_reset: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_info(user->dev, "cg2900_reset\n"); + + BUG_ON(!main_info); + + mutex_lock(&main_info->man_mutex); + + dev_dbg(user->dev, "New main_state: CG2900_RESETING\n"); + info->main_state = CG2900_RESETING; + + chip_shutdown(user); + + /* + * Inform all opened channels about the reset and free the user devices + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + list_del(cursor); + tmp->user->opened = false; + tmp->user->reset_cb(tmp->user); + kfree(tmp); + } + + /* Reset finished. We are now idle until first channel is opened */ + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + + mutex_unlock(&main_info->man_mutex); + + /* + * Send wake-up since this might have been called from a failed boot. + * No harm done if it is a CG2900 chip user who called. + */ + wake_up_all(&main_wait_queue); + + return 0; +} + +/** + * cg2900_alloc_skb() - Allocates socket buffer. + * @size: Sk_buffer size in bytes. + * @priority: GFP priorit for allocation. + * + * Allocates a sk_buffer and reserves space for H4 header. + * + * Returns: + * sk_buffer if success. + * NULL if allocation fails. + */ +static struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + dev_dbg(MAIN_DEV, "cg2900_alloc_skb size %d bytes\n", size); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CG2900_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, CG2900_SKB_RESERVE); + + return skb; +} + +/** + * cg2900_write() - Called when user wants to write to the chip. + * @user: MFD device representing H4 channel to write to. + * @skb: Sk_buffer to transmit. + * + * Transmits the sk_buffer to the chip. If it is a BT cmd or FM audio packet it + * is checked that it is allowed to transmit the chip. + * Note that if error is returned it is up to the user to free the skb. + * + * Returns: + * 0 if success. + * -EINVAL if user or skb is NULL. + * -EACCES if channel is closed. + */ +static int cg2900_write(struct cg2900_user_data *user, struct sk_buff *skb) +{ + u8 *h4_header; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_write: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!skb) { + dev_err(user->dev, "cg2900_write with no sk_buffer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_dbg(user->dev, "cg2900_write length %d bytes\n", skb->len); + + if (!user->opened) { + dev_err(user->dev, + "Trying to transmit data on a closed channel\n"); + return -EACCES; + } + + /* + * Move the data pointer to the H:4 header position and + * store the H4 header. + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = (u8)user->h4_channel; + + if (user->h4_channel == CHANNEL_BT_CMD) + transmit_skb_with_flow_ctrl_bt(user, skb); + else if (user->h4_channel == CHANNEL_FM_RADIO) + transmit_skb_with_flow_ctrl_fm(user, skb); + else + cg2900_tx_to_chip(user, info->logger, skb); + + return 0; +} + +/** + * cg2900_no_write() - Used for channels where it is not allowed to write. + * @user: MFD device representing H4 channel to write to. + * @skb: Sk_buffer to transmit. + * + * Returns: + * -EPERM. + */ +static int cg2900_no_write(struct cg2900_user_data *user, + __attribute__((unused)) struct sk_buff *skb) +{ + dev_err(user->dev, "Not allowed to send on this channel\n"); + return -EPERM; +} + +/** + * cg2900_get_local_revision() - Called to retrieve revision data for the chip. + * @user: MFD device to check. + * @rev_data: Revision data to fill in. + * + * Returns: + * true if success. + * false upon failure. + */ +static bool cg2900_get_local_revision(struct cg2900_user_data *user, + struct cg2900_rev_data *rev_data) +{ + struct cg2900_chip_dev *dev; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_get_local_revision: Calling with " + "NULL pointer\n"); + return false; + } + + if (!rev_data) { + dev_err(user->dev, "Calling with rev_data NULL\n"); + return false; + } + + dev = cg2900_get_prv(user); + + rev_data->revision = dev->chip.hci_revision; + rev_data->sub_version = dev->chip.hci_sub_version; + + return true; +} + +static struct cg2900_user_data btcmd_data = { + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data btacl_data = { + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data btevt_data = { + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data fm_data = { + .h4_channel = CHANNEL_FM_RADIO, +}; +static struct cg2900_user_data gnss_data = { + .h4_channel = CHANNEL_GNSS, +}; +static struct cg2900_user_data debug_data = { + .h4_channel = CHANNEL_DEBUG, +}; +static struct cg2900_user_data ste_tools_data = { + .h4_channel = CHANNEL_STE_TOOLS, +}; +static struct cg2900_user_data hci_logger_data = { + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = cg2900_no_write, + .open = cg2900_hci_log_open, + .close = cg2900_hci_log_close, +}; +static struct cg2900_user_data core_data = { + .h4_channel = CHANNEL_CORE, + .write = cg2900_no_write, +}; +static struct cg2900_user_data audio_bt_data = { + .h4_channel = CHANNEL_BT_CMD, + .is_audio = true, + .open = cg2900_bt_audio_open, + .close = cg2900_bt_audio_close, +}; +static struct cg2900_user_data audio_fm_data = { + .h4_channel = CHANNEL_FM_RADIO, + .is_audio = true, + .open = cg2900_fm_audio_open, + .close = cg2900_fm_audio_close, +}; + +static struct mfd_cell cg2900_devs[] = { + { + .name = "cg2900-btcmd", + .mfd_data = &btcmd_data, + }, + { + .name = "cg2900-btacl", + .mfd_data = &btacl_data, + }, + { + .name = "cg2900-btevt", + .mfd_data = &btevt_data, + }, + { + .name = "cg2900-fm", + .mfd_data = &fm_data, + }, + { + .name = "cg2900-gnss", + .mfd_data = &gnss_data, + }, + { + .name = "cg2900-debug", + .mfd_data = &debug_data, + }, + { + .name = "cg2900-stetools", + .mfd_data = &ste_tools_data, + }, + { + .name = "cg2900-hcilogger", + .mfd_data = &hci_logger_data, + }, + { + .name = "cg2900-core", + .mfd_data = &core_data, + }, + { + .name = "cg2900-audiobt", + .mfd_data = &audio_bt_data, + }, + { + .name = "cg2900-audiofm", + .mfd_data = &audio_fm_data, + }, +}; + +static struct cg2900_user_data char_btcmd_data = { + .channel_data = { + .char_dev_name = CG2900_BT_CMD, + }, + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data char_btacl_data = { + .channel_data = { + .char_dev_name = CG2900_BT_ACL, + }, + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data char_btevt_data = { + .channel_data = { + .char_dev_name = CG2900_BT_EVT, + }, + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data char_fm_data = { + .channel_data = { + .char_dev_name = CG2900_FM_RADIO, + }, + .h4_channel = CHANNEL_FM_RADIO, +}; +static struct cg2900_user_data char_gnss_data = { + .channel_data = { + .char_dev_name = CG2900_GNSS, + }, + .h4_channel = CHANNEL_GNSS, +}; +static struct cg2900_user_data char_debug_data = { + .channel_data = { + .char_dev_name = CG2900_DEBUG, + }, + .h4_channel = CHANNEL_DEBUG, +}; +static struct cg2900_user_data char_ste_tools_data = { + .channel_data = { + .char_dev_name = CG2900_STE_TOOLS, + }, + .h4_channel = CHANNEL_STE_TOOLS, +}; +static struct cg2900_user_data char_hci_logger_data = { + .channel_data = { + .char_dev_name = CG2900_HCI_LOGGER, + }, + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = cg2900_no_write, + .open = cg2900_hci_log_open, + .close = cg2900_hci_log_close, +}; +static struct cg2900_user_data char_core_data = { + .channel_data = { + .char_dev_name = CG2900_CORE, + }, + .h4_channel = CHANNEL_CORE, + .write = cg2900_no_write, +}; +static struct cg2900_user_data char_audio_bt_data = { + .channel_data = { + .char_dev_name = CG2900_BT_AUDIO, + }, + .h4_channel = CHANNEL_BT_CMD, + .is_audio = true, +}; +static struct cg2900_user_data char_audio_fm_data = { + .channel_data = { + .char_dev_name = CG2900_FM_AUDIO, + }, + .h4_channel = CHANNEL_FM_RADIO, + .is_audio = true, +}; + +static struct mfd_cell cg2900_char_devs[] = { + { + .name = "cg2900-chardev", + .id = 0, + .mfd_data = &char_btcmd_data, + }, + { + .name = "cg2900-chardev", + .id = 1, + .mfd_data = &char_btacl_data, + }, + { + .name = "cg2900-chardev", + .id = 2, + .mfd_data = &char_btevt_data, + }, + { + .name = "cg2900-chardev", + .id = 3, + .mfd_data = &char_fm_data, + }, + { + .name = "cg2900-chardev", + .id = 4, + .mfd_data = &char_gnss_data, + }, + { + .name = "cg2900-chardev", + .id = 5, + .mfd_data = &char_debug_data, + }, + { + .name = "cg2900-chardev", + .id = 6, + .mfd_data = &char_ste_tools_data, + }, + { + .name = "cg2900-chardev", + .id = 7, + .mfd_data = &char_hci_logger_data, + }, + { + .name = "cg2900-chardev", + .id = 8, + .mfd_data = &char_core_data, + }, + { + .name = "cg2900-chardev", + .id = 9, + .mfd_data = &char_audio_bt_data, + }, + { + .name = "cg2900-chardev", + .id = 10, + .mfd_data = &char_audio_fm_data, + }, +}; + +/** + * set_plat_data() - Initializes data for an MFD cell. + * @cell: MFD cell. + * @dev: Current chip. + * + * Sets each callback to default function unless already set. + */ +static void set_plat_data(struct mfd_cell *cell, struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *pf_data = cell->mfd_data; + + if (!pf_data->open) + pf_data->open = cg2900_open; + if (!pf_data->close) + pf_data->close = cg2900_close; + if (!pf_data->reset) + pf_data->reset = cg2900_reset; + if (!pf_data->alloc_skb) + pf_data->alloc_skb = cg2900_alloc_skb; + if (!pf_data->write) + pf_data->write = cg2900_write; + if (!pf_data->get_local_revision) + pf_data->get_local_revision = cg2900_get_local_revision; + + cg2900_set_prv(pf_data, dev); +} + +/** + * check_chip_support() - Checks if connected chip is handled by this driver. + * @dev: Chip info structure. + * + * First check if chip is supported by this driver. If that is the case fill in + * the callbacks in @dev and initiate internal variables. Finally create MFD + * devices for all supported H4 channels. When finished power off the chip. + * + * Returns: + * true if chip is handled by this driver. + * false otherwise. + */ +static bool check_chip_support(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct cg2900_chip_info *info; + int i; + int err; + + dev_dbg(dev->dev, "check_chip_support\n"); + + /* + * Check if this is a CG2900 revision. + * We do not care about the sub-version at the moment. Change this if + * necessary. + */ + if ((dev->chip.manufacturer != CG2900_SUPP_MANUFACTURER) || + (dev->chip.hci_revision != CG2900_PG1_SPECIAL_REV && + (dev->chip.hci_revision < CG2900_SUPP_REVISION_MIN || + dev->chip.hci_revision > CG2900_SUPP_REVISION_MAX))) { + dev_dbg(dev->dev, "Chip not supported by CG2900 driver\n" + "\tMan: 0x%02X\n" + "\tRev: 0x%04X\n" + "\tSub: 0x%04X\n", + dev->chip.manufacturer, dev->chip.hci_revision, + dev->chip.hci_sub_version); + return false; + } + + /* Store needed data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info struct\n"); + return false; + } + + /* Initialize all variables */ + skb_queue_head_init(&info->tx_queue_bt); + skb_queue_head_init(&info->tx_queue_fm); + + INIT_LIST_HEAD(&info->open_channels); + + spin_lock_init(&info->tx_bt_lock); + spin_lock_init(&info->tx_fm_lock); + spin_lock_init(&info->rw_lock); + + info->tx_nr_pkts_allowed_bt = 1; + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->fm_radio_mode = FM_RADIO_MODE_IDLE; + info->chip_dev = dev; + info->dev = dev->dev; + + info->wq = create_singlethread_workqueue(WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + goto err_handling_free_info; + } + + info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->patch_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffer for patch file\n"); + goto err_handling_destroy_wq; + } + + info->settings_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->settings_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffers settings file\n"); + goto err_handling_free_patch_name; + } + + info->selftest_work.data = info; + INIT_DELAYED_WORK(&info->selftest_work.work, + work_send_read_selftest_cmd); + + dev->c_data = info; + /* Set the callbacks */ + dev->c_cb.data_from_chip = data_from_chip; + dev->c_cb.chip_removed = chip_removed; + + mutex_lock(&main_info->man_mutex); + + pf_data = dev_get_platdata(dev->dev); + btcmd_data.channel_data.bt_bus = pf_data->bus; + btacl_data.channel_data.bt_bus = pf_data->bus; + btevt_data.channel_data.bt_bus = pf_data->bus; + + for (i = 0; i < ARRAY_SIZE(cg2900_devs); i++) + set_plat_data(&cg2900_devs[i], dev); + for (i = 0; i < ARRAY_SIZE(cg2900_char_devs); i++) + set_plat_data(&cg2900_char_devs[i], dev); + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, cg2900_devs, + ARRAY_SIZE(cg2900_devs), NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2900_devs (%d)\n", err); + goto err_handling_free_settings_name; + } + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + cg2900_char_devs, ARRAY_SIZE(cg2900_char_devs), + NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2900_char_devs (%d)\n", err); + goto err_handling_remove_devs; + } + + main_info->cell_base_id += 30; + mutex_unlock(&main_info->man_mutex); + + dev_info(dev->dev, "Chip supported by the CG2900 chip driver\n"); + + /* Finish by turning off the chip */ + cg2900_create_work_item(info->wq, work_power_off_chip, dev); + + return true; + +err_handling_remove_devs: + mfd_remove_devices(dev->dev); +err_handling_free_settings_name: + kfree(info->settings_file_name); + mutex_unlock(&main_info->man_mutex); +err_handling_free_patch_name: + kfree(info->patch_file_name); +err_handling_destroy_wq: + destroy_workqueue(info->wq); +err_handling_free_info: + kfree(info); + return false; +} + +static struct cg2900_id_callbacks chip_support_callbacks = { + .check_chip_support = check_chip_support, +}; + +/** + * cg2900_chip_probe() - Initialize CG2900 chip handler resources. + * @pdev: Platform device. + * + * This function initializes the CG2900 driver, then registers to + * the CG2900 Core. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by cg2900_register_chip_driver. + */ +static int __devinit cg2900_chip_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "cg2900_chip_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_ATOMIC); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + + err = cg2900_register_chip_driver(&chip_support_callbacks); + if (err) { + dev_err(&pdev->dev, + "Couldn't register chip driver (%d)\n", err); + goto error_handling; + } + + dev_info(&pdev->dev, "CG2900 chip driver started\n"); + + return 0; + +error_handling: + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return err; +} + +/** + * cg2900_chip_remove() - Release CG2900 chip handler resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit cg2900_chip_remove(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "CG2900 chip driver removed\n"); + + cg2900_deregister_chip_driver(&chip_support_callbacks); + + if (!main_info) + return 0; + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return 0; +} + +static struct platform_driver cg2900_chip_driver = { + .driver = { + .name = "cg2900-chip", + .owner = THIS_MODULE, + }, + .probe = cg2900_chip_probe, + .remove = __devexit_p(cg2900_chip_remove), +}; + +/** + * cg2900_chip_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_chip_init(void) +{ + pr_debug("cg2900_chip_init"); + return platform_driver_register(&cg2900_chip_driver); +} + +/** + * cg2900_chip_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_chip_exit(void) +{ + pr_debug("cg2900_chip_exit"); + platform_driver_unregister(&cg2900_chip_driver); +} + +module_init(cg2900_chip_init); +module_exit(cg2900_chip_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_chip.h b/drivers/staging/cg2900/mfd/cg2900_chip.h new file mode 100644 index 00000000000..886015976d3 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_chip.h @@ -0,0 +1,613 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_CHIP_H_ +#define _CG2900_CHIP_H_ + +/* + * Utility + */ + +static inline void set_low_nibble(__u8 *var, __u8 value) +{ + *var = (*var & 0xf0) | (value & 0x0f); +} + +static inline void set_high_nibble(__u8 *var, __u8 value) +{ + *var = (*var & 0x0f) | (value << 4); +} + +static inline void store_bit(__u8 *var, size_t bit, __u8 value) +{ + *var = (*var & ~(1u << bit)) | (value << bit); +} + +/* + * General chip defines + */ + +/* Supported chips */ +#define CG2900_SUPP_MANUFACTURER 0x30 +#define CG2900_SUPP_REVISION_MIN 0x0100 +#define CG2900_SUPP_REVISION_MAX 0x0200 + +/* Specific chip version data */ +#define CG2900_PG1_REV 0x0101 +#define CG2900_PG2_REV 0x0200 +#define CG2900_PG1_SPECIAL_REV 0x0700 + +/* + * Bluetooth + */ + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +struct bt_cmd_cmpl_event { + __u8 eventcode; + __u8 plen; + __u8 n_commands; + __le16 opcode; + /* + * According to BT-specification what follows is "parameters" + * and unique to every command, but all commands start the + * parameters with the status field so include it here for + * convenience + */ + __u8 status; + __u8 data[]; +} __packed; + +/* BT VS Store In FS command */ +#define CG2900_BT_OP_VS_STORE_IN_FS 0xFC22 +struct bt_vs_store_in_fs_cmd { + __le16 opcode; + __u8 plen; + __u8 user_id; + __u8 len; + __u8 data[]; +} __packed; + +#define CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE + +/* BT VS Write File Block command */ +#define CG2900_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E +struct bt_vs_write_file_block_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 data[]; +} __packed; + +#define CG2900_BT_DISABLE 0x00 +#define CG2900_BT_ENABLE 0x01 + +/* BT VS BT Enable command */ +#define CG2900_BT_OP_VS_BT_ENABLE 0xFF10 +struct bt_vs_bt_enable_cmd { + __le16 op_code; + u8 plen; + u8 enable; +} __packed; + +/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */ +#define CG2900_BAUD_RATE_57600 0x03 +#define CG2900_BAUD_RATE_115200 0x02 +#define CG2900_BAUD_RATE_230400 0x01 +#define CG2900_BAUD_RATE_460800 0x00 +#define CG2900_BAUD_RATE_921600 0x20 +#define CG2900_BAUD_RATE_2000000 0x25 +#define CG2900_BAUD_RATE_3000000 0x27 +#define CG2900_BAUD_RATE_4000000 0x2B + +/* BT VS SetBaudRate command */ +#define CG2900_BT_OP_VS_SET_BAUD_RATE 0xFC09 +struct bt_vs_set_baud_rate_cmd { + __le16 opcode; + __u8 plen; + __u8 baud_rate; +} __packed; + +#define CG2900_BT_SELFTEST_SUCCESSFUL 0x00 +#define CG2900_BT_SELFTEST_FAILED 0x01 +#define CG2900_BT_SELFTEST_NOT_COMPLETED 0x02 + +/* BT VS ReadSelfTestsResult command & event */ +#define CG2900_BT_OP_VS_READ_SELTESTS_RESULT 0xFC10 +struct bt_vs_read_selftests_result_evt { + __u8 status; + __u8 result; +} __packed; + +/* Bluetooth Vendor Specific Opcodes */ +#define CG2900_BT_OP_VS_POWER_SWITCH_OFF 0xFD40 +#define CG2900_BT_OP_VS_SYSTEM_RESET 0xFF12 + +#define CG2900_BT_OPCODE_NONE 0xFFFF + +/* + * Common multimedia + */ + +#define CG2900_CODEC_TYPE_NONE 0x00 +#define CG2900_CODEC_TYPE_SBC 0x01 + +#define CG2900_PCM_MODE_SLAVE 0x00 +#define CG2900_PCM_MODE_MASTER 0x01 + +#define CG2900_I2S_MODE_MASTER 0x00 +#define CG2900_I2S_MODE_SLAVE 0x01 + +/* + * CG2900 PG1 multimedia API + */ + +#define CG2900_BT_VP_TYPE_PCM 0x00 +#define CG2900_BT_VP_TYPE_I2S 0x01 +#define CG2900_BT_VP_TYPE_SLIMBUS 0x02 +#define CG2900_BT_VP_TYPE_FM 0x03 +#define CG2900_BT_VP_TYPE_BT_SCO 0x04 +#define CG2900_BT_VP_TYPE_BT_A2DP 0x05 +#define CG2900_BT_VP_TYPE_ANALOG 0x07 + +#define CG2900_BT_VS_SET_HARDWARE_CONFIG 0xFD54 +/* These don't have the same length, so a union won't work */ +struct bt_vs_set_hw_cfg_cmd_pcm { + __le16 opcode; + __u8 plen; + __u8 vp_type; + __u8 port_id; + __u8 mode_dir; /* NB: mode is in bit 1 (not 0) */ + __u8 bit_clock; + __le16 frame_len; +} __packed; +#define HWCONFIG_PCM_SET_MODE(pcfg, mode) \ + set_low_nibble(&(pcfg)->mode_dir, (mode) << 1) +#define HWCONFIG_PCM_SET_DIR(pcfg, idx, dir) \ + store_bit(&(pcfg)->mode_dir, (idx) + 4, (dir)) + +struct bt_vs_set_hw_cfg_cmd_i2s { + __le16 opcode; + __u8 plen; + __u8 vp_type; + __u8 port_id; + __u8 half_period; + __u8 master_slave; +} __packed; + +/* Max length for allocating */ +#define CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG \ + (sizeof(struct bt_vs_set_hw_cfg_cmd_pcm)) + +#define CG2900_BT_VS_SET_SESSION_CONFIG 0xFD55 +struct session_config_vport { + __u8 type; + union { + struct { + __le16 acl_handle; + __u8 reserved[10]; + } sco; + struct { + __u8 reserved[12]; + } fm; + struct { + __u8 index; + __u8 slots_used; + __u8 slot_start[4]; + __u8 reserved[6]; + } pcm; + struct { + __u8 index; + __u8 channel; + __u8 reserved[10]; + } i2s; + }; +} __packed; +#define SESSIONCFG_PCM_SET_USED(port, idx, use) \ + store_bit(&(port).pcm.slots_used, (idx), (use)) + +struct session_config_stream { + __u8 media_type; + __u8 csel_srate; + __u8 codec_type; + __u8 codec_mode; + __u8 codec_params[3]; + struct session_config_vport inport; + struct session_config_vport outport; +} __packed; +#define SESSIONCFG_SET_CHANNELS(pcfg, chnl) \ + set_low_nibble(&(pcfg)->csel_srate, (chnl)) +#define SESSIONCFG_I2S_SET_SRATE(pcfg, rate) \ + set_high_nibble(&(pcfg)->csel_srate, (rate)) + +struct bt_vs_session_config_cmd { + __le16 opcode; + __u8 plen; + __u8 n_streams; /* we only support one here */ + struct session_config_stream stream; +} __packed; + +#define CG2900_BT_SESSION_MEDIA_TYPE_AUDIO 0x00 + +#define CG2900_BT_SESSION_RATE_8K 0x01 +#define CG2900_BT_SESSION_RATE_16K 0x02 +#define CG2900_BT_SESSION_RATE_44_1K 0x04 +#define CG2900_BT_SESSION_RATE_48K 0x05 + +#define CG2900_BT_MEDIA_CONFIG_MONO 0x00 +#define CG2900_BT_MEDIA_CONFIG_STEREO 0x01 +#define CG2900_BT_MEDIA_CONFIG_JOINT_STEREO 0x02 +#define CG2900_BT_MEDIA_CONFIG_DUAL_CHANNEL 0x03 + +#define CG2900_BT_SESSION_I2S_INDEX_I2S 0x00 +#define CG2900_BT_SESSION_PCM_INDEX_PCM_I2S 0x00 + + +#define CG2900_BT_VS_SESSION_CTRL 0xFD57 +struct bt_vs_session_ctrl_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 control; +} __packed; + +#define CG2900_BT_SESSION_START 0x00 +#define CG2900_BT_SESSION_STOP 0x01 +#define CG2900_BT_SESSION_PAUSE 0x02 +#define CG2900_BT_SESSION_RESUME 0x03 + +#define CG2900_BT_VS_RESET_SESSION_CONFIG 0xFD56 +struct bt_vs_reset_session_cfg_cmd { + __le16 opcode; + __u8 plen; + __u8 id; +} __packed; + +/* + * CG2900 PG2 multimedia API + */ + +#define CG2900_MC_PORT_PCM_I2S 0x00 +#define CG2900_MC_PORT_I2S 0x01 +#define CG2900_MC_PORT_BT_SCO 0x04 +#define CG2900_MC_PORT_FM_RX_0 0x07 +#define CG2900_MC_PORT_FM_RX_1 0x08 +#define CG2900_MC_PORT_FM_TX 0x09 + +#define CG2900_MC_VS_PORT_CONFIG 0xFD64 +struct mc_vs_port_cfg_cmd { + __le16 opcode; + __u8 plen; + __u8 type; + /* + * one of the following configuration structs should follow, but they + * have different lengths so a union will not work + */ +} __packed; + +struct mc_vs_port_cfg_pcm_i2s { + __u8 role_dir; + __u8 sco_a2dp_slots_used; + __u8 fm_slots_used; + __u8 ring_slots_used; + __u8 slot_start[4]; + __u8 ratio_mode; + __u8 frame_len; + __u8 bitclk_srate; +} __packed; +#define PORTCFG_PCM_SET_ROLE(cfg, role) \ + set_low_nibble(&(cfg).role_dir, (role)) +#define PORTCFG_PCM_SET_DIR(cfg, idx, dir) \ + store_bit(&(cfg).role_dir, (idx) + 4, (dir)) +static inline void portcfg_pcm_set_sco_used(struct mc_vs_port_cfg_pcm_i2s *cfg, + size_t index, __u8 use) +{ + if (use) { + /* clear corresponding slot in all cases */ + cfg->sco_a2dp_slots_used &= ~(0x11 << index); + cfg->fm_slots_used &= ~(0x11 << index); + cfg->ring_slots_used &= ~(0x11 << index); + /* set for sco */ + cfg->sco_a2dp_slots_used |= (1u << index); + } else { + /* only clear for sco */ + cfg->sco_a2dp_slots_used &= ~(1u << index); + } +} +#define PORTCFG_PCM_SET_SCO_USED(cfg, idx, use) \ + portcfg_pcm_set_sco_used(&cfg, idx, use) +#define PORTCFG_PCM_SET_RATIO(cfg, r) \ + set_low_nibble(&(cfg).ratio_mode, (r)) +#define PORTCFG_PCM_SET_MODE(cfg, mode) \ + set_high_nibble(&(cfg).ratio_mode, (mode)) +#define PORTCFG_PCM_SET_BITCLK(cfg, clk) \ + set_low_nibble(&(cfg).bitclk_srate, (clk)) +#define PORTCFG_PCM_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).bitclk_srate, (rate)) + +#define CG2900_MC_PCM_SAMPLE_RATE_8 1 +#define CG2900_MC_PCM_SAMPLE_RATE_16 2 +#define CG2900_MC_PCM_SAMPLE_RATE_44_1 4 +#define CG2900_MC_PCM_SAMPLE_RATE_48 6 + +struct mc_vs_port_cfg_i2s { + __u8 role_hper; + __u8 csel_srate; + __u8 wordlen; +}; +#define PORTCFG_I2S_SET_ROLE(cfg, role) \ + set_low_nibble(&(cfg).role_hper, (role)) +#define PORTCFG_I2S_SET_HALFPERIOD(cfg, hper) \ + set_high_nibble(&(cfg).role_hper, (hper)) +#define PORTCFG_I2S_SET_CHANNELS(cfg, chnl) \ + set_low_nibble(&(cfg).csel_srate, (chnl)) +#define PORTCFG_I2S_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).csel_srate, (rate)) +#define PORTCFG_I2S_SET_WORDLEN(cfg, len) \ + set_low_nibble(&(cfg).wordlen, len) + +#define CG2900_MC_I2S_RIGHT_CHANNEL 1 +#define CG2900_MC_I2S_LEFT_CHANNEL 2 +#define CG2900_MC_I2S_BOTH_CHANNELS 3 + +#define CG2900_MC_I2S_SAMPLE_RATE_8 0 +#define CG2900_MC_I2S_SAMPLE_RATE_16 1 +#define CG2900_MC_I2S_SAMPLE_RATE_44_1 2 +#define CG2900_MC_I2S_SAMPLE_RATE_48 4 + +#define CG2900_MC_I2S_WORD_16 1 +#define CG2900_MC_I2S_WORD_32 3 + +struct mc_vs_port_cfg_fm { + __u8 srate; /* NB: value goes in _upper_ nibble! */ +}; +#define PORTCFG_FM_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).srate, (rate)) + +struct mc_vs_port_cfg_sco { + __le16 acl_id; + __u8 wbs_codec; + __u8 sbc_params[3]; /* replace when we actually enable WBS... */ +} __packed; +#define PORTCFG_SCO_SET_WBS(cfg, wbs) \ + set_low_nibble(&(cfg).wbs_codec, (wbs)) +#define PORTCFG_SCO_SET_CODEC(cfg, codec) \ + set_high_nibble(&(cfg).wbs_codec, (codec)) + +#define CG2900_MC_VS_CREATE_STREAM 0xFD66 +struct mc_vs_create_stream_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 inport; + __u8 outport; + __u8 order; /* NB: not used by chip */ +} __packed; + +#define CG2900_MC_VS_DELETE_STREAM 0xFD67 +struct mc_vs_delete_stream_cmd { + __le16 opcode; + __u8 plen; + __u8 stream; +} __packed; + +#define CG2900_MC_VS_STREAM_CONTROL 0xFD68 +struct mc_vs_stream_ctrl_cmd { + __le16 opcode; + __u8 plen; + __u8 command; + __u8 n_streams; + __u8 stream[]; +} __packed; + +#define CG2900_MC_STREAM_START 0x00 +#define CG2900_MC_STREAM_STOP 0x01 +#define CG2900_MC_STREAM_STOP_FLUSH 0x02 + +#define CG2900_MC_VS_SET_FM_START_MODE 0xFD69 + +/* + * FM + */ + +/* FM legacy command packet */ +struct fm_leg_cmd { + __u8 length; + __u8 opcode; + __u8 read_write; + __u8 fm_function; + union { /* Payload varies with function */ + __le16 irqmask; + struct fm_leg_fm_cmd { + __le16 head; + __le16 data[]; + } fm_cmd; + }; +} __packed; + +/* FM legacy command complete packet */ +struct fm_leg_cmd_cmpl { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 read_write; + __u8 cmd_status; + __u8 fm_function; + __le16 response_head; + __le16 data[]; +} __packed; + +/* FM legacy interrupt packet, PG2 style */ +struct fm_leg_irq_v2 { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 event_type; + __u8 event_id; + __le16 irq; +} __packed; + +/* FM legacy interrupt packet, PG1 style */ +struct fm_leg_irq_v1 { + __u8 param_length; + __u8 opcode; + __u8 event_id; + __le16 irq; +} __packed; + +union fm_leg_evt_or_irq { + __u8 param_length; + struct fm_leg_cmd_cmpl evt; + struct fm_leg_irq_v2 irq_v2; + struct fm_leg_irq_v1 irq_v1; +} __packed; + +/* FM Opcode generic*/ +#define CG2900_FM_GEN_ID_LEGACY 0xFE + +/* FM event*/ +#define CG2900_FM_EVENT_UNKNOWN 0 +#define CG2900_FM_EVENT_CMD_COMPLETE 1 +#define CG2900_FM_EVENT_INTERRUPT 2 + +/* FM do-command identifiers. */ +#define CG2900_FM_DO_AIP_FADE_START 0x0046 +#define CG2900_FM_DO_AUP_BT_FADE_START 0x01C2 +#define CG2900_FM_DO_AUP_EXT_FADE_START 0x0102 +#define CG2900_FM_DO_AUP_FADE_START 0x00A2 +#define CG2900_FM_DO_FMR_SETANTENNA 0x0663 +#define CG2900_FM_DO_FMR_SP_AFSWITCH_START 0x04A3 +#define CG2900_FM_DO_FMR_SP_AFUPDATE_START 0x0463 +#define CG2900_FM_DO_FMR_SP_BLOCKSCAN_START 0x0683 +#define CG2900_FM_DO_FMR_SP_PRESETPI_START 0x0443 +#define CG2900_FM_DO_FMR_SP_SCAN_START 0x0403 +#define CG2900_FM_DO_FMR_SP_SEARCH_START 0x03E3 +#define CG2900_FM_DO_FMR_SP_SEARCHPI_START 0x0703 +#define CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL 0x03C3 +#define CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL 0x04C3 +#define CG2900_FM_DO_FMT_PA_SETCTRL 0x01A4 +#define CG2900_FM_DO_FMT_PA_SETMODE 0x01E4 +#define CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL 0x0064 +#define CG2900_FM_DO_GEN_ANTENNACHECK_START 0x02A1 +#define CG2900_FM_DO_GEN_GOTOMODE 0x0041 +#define CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE 0x0221 +#define CG2900_FM_DO_GEN_SELECTREFERENCECLOCK 0x0201 +#define CG2900_FM_DO_GEN_SETPROCESSINGCLOCK 0x0241 +#define CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL 0x01A1 +#define CG2900_FM_DO_TST_TX_RAMP_START 0x0147 +#define CG2900_FM_CMD_NONE 0xFFFF +#define CG2900_FM_CMD_ID_GEN_GOTO_POWER_DOWN 0x0081 +#define CG2900_FM_CMD_ID_GEN_GOTO_STANDBY 0x0061 + +/* FM Command IDs */ +#define CG2900_FM_CMD_ID_AUP_EXT_SET_MODE 0x0162 +#define CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL 0x0182 +#define CG2900_FM_CMD_ID_AIP_SET_MODE 0x01C6 +#define CG2900_FM_CMD_ID_AIP_BT_SET_CTRL 0x01A6 +#define CG2900_FM_CMD_ID_AIP_BT_SET_MODE 0x01E6 + +/* FM Command Parameters. */ +#define CG2900_FM_CMD_PARAM_ENABLE 0x00 +#define CG2900_FM_CMD_PARAM_DISABLE 0x01 +#define CG2900_FM_CMD_PARAM_RESET 0x02 +#define CG2900_FM_CMD_PARAM_WRITECOMMAND 0x10 +#define CG2900_FM_CMD_PARAM_SET_INT_MASK_ALL 0x20 +#define CG2900_FM_CMD_PARAM_GET_INT_MASK_ALL 0x21 +#define CG2900_FM_CMD_PARAM_SET_INT_MASK 0x22 +#define CG2900_FM_CMD_PARAM_GET_INT_MASK 0x23 +#define CG2900_FM_CMD_PARAM_FM_FW_DOWNLOAD 0x30 +#define CG2900_FM_CMD_PARAM_NONE 0xFF + +/* FM Legacy Command Parameters */ +#define CG2900_FM_CMD_LEG_PARAM_WRITE 0x00 +#define CG2900_FM_CMD_LEG_PARAM_IRQ 0x01 + +/* FM Command Status. */ +#define CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED 0x00 +#define CG2900_FM_CMD_STATUS_HW_FAILURE 0x03 +#define CG2900_FM_CMD_STATUS_INVALID_PARAMS 0x12 +#define CG2900_FM_CMD_STATUS_UNINITILIZED 0x15 +#define CG2900_FM_CMD_STATUS_UNSPECIFIED_ERROR 0x1F +#define CG2900_FM_CMD_STATUS_COMMAND_DISALLOWED 0x0C +#define CG2900_FM_CMD_STATUS_FW_WRONG_SEQUENCE_NR 0xF1 +#define CG2900_FM_CMD_STATUS_FW_UNKNOWN_FILE 0xF2 +#define CG2900_FM_CMD_STATUS_FW_FILE_VER_MISMATCH 0xF3 + +/* FM Interrupts. */ +#define CG2900_FM_IRPT_FIQ 0x0000 +#define CG2900_FM_IRPT_OPERATION_SUCCEEDED 0x0001 +#define CG2900_FM_IRPT_OPERATION_FAILED 0x0002 +#define CG2900_FM_IRPT_BUFFER_FULL 0x0008 +#define CG2900_FM_IRPT_BUFFER_EMPTY 0x0008 +#define CG2900_FM_IRPT_SIGNAL_QUALITY_LOW 0x0010 +#define CG2900_FM_IRPT_MUTE_STATUS_CHANGED 0x0010 +#define CG2900_FM_IRPT_MONO_STEREO_TRANSITION 0x0020 +#define CG2900_FM_IRPT_OVER_MODULATION 0x0020 +#define CG2900_FM_IRPT_RDS_SYNC_FOUND 0x0040 +#define CG2900_FM_IRPT_INPUT_OVERDRIVE 0x0040 +#define CG2900_FM_IRPT_RDS_SYNC_LOST 0x0080 +#define CG2900_FM_IRPT_PI_CODE_CHANGED 0x0100 +#define CG2900_FM_IRPT_REQUEST_BLOCK_AVALIBLE 0x0200 +#define CG2900_FM_IRPT_BUFFER_CLEARED 0x2000 +#define CG2900_FM_IRPT_WARM_BOOT_READY 0x4000 +#define CG2900_FM_IRPT_COLD_BOOT_READY 0x8000 + +/* FM Legacy Function Command Parameters */ + +/* AUP_EXT_SetMode Output enum */ +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_DISABLED 0x0000 +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_I2S 0x0001 +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL 0x0002 + +/* SetControl Conversion enum */ +#define CG2900_FM_CMD_SET_CTRL_CONV_UP 0x0000 +#define CG2900_FM_CMD_SET_CTRL_CONV_DOWN 0x0001 + +/* AIP_SetMode Input enum */ +#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_ANA 0x0000 +#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG 0x0001 + +/* AIP_BT_SetMode Input enum */ +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_RESERVED 0x0000 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_I2S 0x0001 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR 0x0002 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_FIFO 0x0003 + +/* FM Parameter Lengths = FM command length - length field (1 byte) */ +#define CG2900_FM_CMD_PARAM_LEN(len) (len - 1) + +/* + * FM Command ID mapped per byte and shifted 3 bits left + * Also adds number of parameters at first 3 bits of LSB. + */ +static inline __u16 cg2900_get_fm_cmd_id(__u16 opcode) +{ + return opcode >> 3; +} + +static inline __u16 cg2900_make_fm_cmd_id(__u16 id, __u8 num_params) +{ + return (id << 3) | num_params; +} + +/* + * GNSS + */ + +struct gnss_hci_hdr { + __u8 op_code; + __le16 plen; +} __packed; + +#endif /* _CG2900_CHIP_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_core.c b/drivers/staging/cg2900/mfd/cg2900_core.c new file mode 100644 index 00000000000..66a452f817a --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_core.c @@ -0,0 +1,713 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_core" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" +#include "cg2900_core.h" + +/* Device names */ +#define CG2900_CDEV_NAME "cg2900_core_test" +#define CG2900_CLASS_NAME "cg2900_class" +#define CG2900_DEVICE_NAME "cg2900_driver" +#define CORE_WQ_NAME "cg2900_core_wq" + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/* + * Timeout values + */ +#define CHIP_READY_TIMEOUT (100) /* ms */ +#define REVISION_READOUT_TIMEOUT (500) /* ms */ +#define SLEEP_TIMEOUT_MS (10000) /* ms */ + +/** + * enum boot_state - BOOT-state for CG2900 Core. + * @BOOT_RESET: HCI Reset has been sent. + * @BOOT_READ_LOCAL_VERSION_INFORMATION: ReadLocalVersionInformation + * command has been sent. + * @BOOT_READY: CG2900 Core boot is ready. + * @BOOT_FAILED: CG2900 Core boot failed. + */ +enum boot_state { + BOOT_RESET, + BOOT_READ_LOCAL_VERSION_INFORMATION, + BOOT_READY, + BOOT_FAILED +}; + +/** + * struct chip_handler_item - Structure to store chip handler cb. + * @list: list_head struct. + * @cb: Chip handler callback struct. + */ +struct chip_handler_item { + struct list_head list; + struct cg2900_id_callbacks cb; +}; + +/** + * struct core_info - Main info structure for CG2900 Core. + * @boot_state: Current BOOT-state of CG2900 Core. + * @wq: CG2900 Core workqueue. + * @chip_dev: Device structure for chip driver. + * @work: Work structure. + */ +struct core_info { + enum boot_state boot_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + struct work_struct work; +}; + +/** + * struct main_info - Main info structure for CG2900 Core. + * @dev: Device structure for STE Connectivity driver. + * @man_mutex: Management mutex. + * @chip_handlers: List of the register handlers for different chips. + * @wq: Wait queue. + */ +struct main_info { + struct device *dev; + struct mutex man_mutex; + struct list_head chip_handlers; + wait_queue_head_t wq; +}; + +/* core_info - Main information object for CG2900 Core. */ +static struct main_info *main_info; + +/* Module parameters */ +u8 bd_address[] = {0x00, 0xBE, 0xAD, 0xDE, 0x80, 0x00}; +EXPORT_SYMBOL_GPL(bd_address); +int bd_addr_count = BT_BDADDR_SIZE; + +static int sleep_timeout_ms = SLEEP_TIMEOUT_MS; + +/** + * send_bt_cmd() - Copy and send sk_buffer with no assigned user. + * @dev: Current chip to transmit to. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The send_bt_cmd() function allocate sk_buffer, copy supplied + * data to it, and send the sk_buffer to controller. + */ +void send_bt_cmd(struct cg2900_chip_dev *dev, void *data, int length) +{ + struct sk_buff *skb; + int err; + + skb = alloc_skb(length + HCI_H4_SIZE, GFP_ATOMIC); + if (!skb) { + dev_err(dev->dev, "send_bt_cmd: Couldn't alloc sk_buff with " + "length %d\n", length); + return; + } + + skb_reserve(skb, HCI_H4_SIZE); + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + err = dev->t_cb.write(dev, skb); + if (err) { + dev_err(dev->dev, "send_bt_cmd: Transport write failed (%d)\n", + err); + kfree_skb(skb); + } +} + +/** + * handle_reset_cmd_complete_evt() - Handle a received HCI Command Complete event for a Reset command. + * @dev: Current device. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_reset_cmd_complete_evt(struct cg2900_chip_dev *dev, u8 *data) +{ + bool pkt_handled = false; + u8 status = data[0]; + struct hci_command_hdr cmd; + struct core_info *info = dev->prv_data; + + dev_dbg(dev->dev, "Received Reset complete event with status 0x%X\n", + status); + + if (info->boot_state == BOOT_RESET) { + /* Transmit HCI Read Local Version Information command */ + dev_dbg(dev->dev, "New boot_state: " + "BOOT_READ_LOCAL_VERSION_INFORMATION\n"); + info->boot_state = BOOT_READ_LOCAL_VERSION_INFORMATION; + cmd.opcode = cpu_to_le16(HCI_OP_READ_LOCAL_VERSION); + cmd.plen = 0; /* No parameters for HCI reset */ + send_bt_cmd(dev, &cmd, sizeof(cmd)); + + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * handle_read_local_version_info_cmd_complete_evt() - Handle a received HCI Command Complete event for a ReadLocalVersionInformation command. + * @dev: Current device. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool +handle_read_local_version_info_cmd_complete_evt(struct cg2900_chip_dev *dev, + u8 *data) +{ + struct hci_rp_read_local_version *evt; + struct core_info *info = dev->prv_data; + + /* Check we're in the right state */ + if (info->boot_state != BOOT_READ_LOCAL_VERSION_INFORMATION) + return false; + + /* We got an answer for our HCI command. Extract data */ + evt = (struct hci_rp_read_local_version *)data; + + /* We will handle the packet */ + if (HCI_BT_ERROR_NO_ERROR != evt->status) { + dev_err(dev->dev, "Received Read Local Version Information " + "with status 0x%X\n", evt->status); + dev_dbg(dev->dev, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + wake_up_all(&main_info->wq); + return true; + } + + /* The command worked. Store the data */ + dev->chip.hci_version = evt->hci_ver; + dev->chip.hci_revision = le16_to_cpu(evt->hci_rev); + dev->chip.lmp_pal_version = evt->lmp_ver; + dev->chip.manufacturer = le16_to_cpu(evt->manufacturer); + dev->chip.hci_sub_version = le16_to_cpu(evt->lmp_subver); + dev_info(dev->dev, "Received Read Local Version Information with:\n" + "\thci_version: 0x%02X\n" + "\thci_revision: 0x%04X\n" + "\tlmp_pal_version: 0x%02X\n" + "\tmanufacturer: 0x%04X\n" + "\thci_sub_version: 0x%04X\n", + dev->chip.hci_version, dev->chip.hci_revision, + dev->chip.lmp_pal_version, dev->chip.manufacturer, + dev->chip.hci_sub_version); + + dev_dbg(dev->dev, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + wake_up_all(&main_info->wq); + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if data should be handled in CG2900 Core. + * @dev: Current chip + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in CG2900 Core. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + u8 *data = &skb->data[CG2900_SKB_RESERVE]; + struct hci_event_hdr *evt; + struct hci_ev_cmd_complete *cmd_complete; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + + /* First check the event code */ + if (HCI_EV_CMD_COMPLETE != evt->evt) + return false; + + data += sizeof(*evt); + cmd_complete = (struct hci_ev_cmd_complete *)data; + + op_code = le16_to_cpu(cmd_complete->opcode); + + dev_dbg(dev->dev, "Received Command Complete: op_code = 0x%04X\n", + op_code); + data += sizeof(*cmd_complete); /* Move to first byte after OCF */ + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete_evt(dev, data); + else if (op_code == HCI_OP_READ_LOCAL_VERSION) + pkt_handled = handle_read_local_version_info_cmd_complete_evt + (dev, data); + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +static void cg2900_data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + u8 h4_channel; + + dev_dbg(dev->dev, "cg2900_data_from_chip\n"); + + if (!skb) { + dev_err(dev->dev, "No data supplied\n"); + return; + } + + h4_channel = skb->data[0]; + + /* + * First check if this is the response for something + * we have sent internally. + */ + if (HCI_BT_EVT_H4_CHANNEL == h4_channel && + handle_rx_data_bt_evt(dev, skb)) { + dev_dbg(dev->dev, "Received packet handled internally\n"); + } else { + dev_err(dev->dev, + "cg2900_data_from_chip: Received unexpected packet\n"); + kfree_skb(skb); + } +} + +/** + * work_hw_registered() - Called when the interface to HW has been established. + * @work: Reference to work data. + * + * Since there now is a transport identify the connected chip and decide which + * chip handler to use. + */ +static void work_hw_registered(struct work_struct *work) +{ + struct hci_command_hdr cmd; + struct cg2900_chip_dev *dev; + struct core_info *info; + bool chip_handled = false; + struct list_head *cursor; + struct chip_handler_item *tmp; + + dev_dbg(main_info->dev, "work_hw_registered\n"); + + if (!work) { + dev_err(main_info->dev, "work_hw_registered: work == NULL\n"); + return; + } + + info = container_of(work, struct core_info, work); + dev = info->chip_dev; + + /* + * This might look strange, but we need to read out + * the revision info in order to be able to shutdown the chip properly. + */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_killable(msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + /* Set our function to receive data from chip */ + dev->c_cb.data_from_chip = cg2900_data_from_chip; + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport + */ + dev_dbg(dev->dev, "New boot_state: BOOT_RESET\n"); + info->boot_state = BOOT_RESET; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + send_bt_cmd(dev, &cmd, sizeof(cmd)); + + dev_dbg(dev->dev, + "Wait up to 500 milliseconds for revision to be read\n"); + wait_event_timeout(main_info->wq, + (BOOT_READY == info->boot_state || + BOOT_FAILED == info->boot_state), + msecs_to_jiffies(REVISION_READOUT_TIMEOUT)); + + if (BOOT_READY != info->boot_state) { + dev_err(dev->dev, + "Could not read out revision from the chip\n"); + return; + } + + dev->c_cb.data_from_chip = NULL; + + mutex_lock(&main_info->man_mutex); + list_for_each(cursor, &main_info->chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + chip_handled = tmp->cb.check_chip_support(dev); + if (chip_handled) { + dev_info(dev->dev, "Chip handler found\n"); + break; + } + } + mutex_unlock(&main_info->man_mutex); + + if (!chip_handled) + dev_info(dev->dev, "No chip handler found\n"); +} + +/** + * cg2900_register_chip_driver() - Register a chip handler. + * @cb: Callbacks to call when chip is connected. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb) +{ + struct chip_handler_item *item; + + dev_dbg(main_info->dev, "cg2900_register_chip_driver\n"); + + if (!cb) { + dev_err(main_info->dev, "NULL supplied as cb\n"); + return -EINVAL; + } + + item = kzalloc(sizeof(*item), GFP_KERNEL); + if (!item) { + dev_err(main_info->dev, + "cg2900_register_chip_driver: " + "Failed to alloc memory\n"); + return -ENOMEM; + } + + memcpy(&item->cb, cb, sizeof(cb)); + mutex_lock(&main_info->man_mutex); + list_add_tail(&item->list, &main_info->chip_handlers); + mutex_unlock(&main_info->man_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_register_chip_driver); + +/** + * cg2900_deregister_chip_driver() - Deregister a chip handler. + * @cb: Callbacks to call when chip is connected. + */ +void cg2900_deregister_chip_driver(struct cg2900_id_callbacks *cb) +{ + struct chip_handler_item *tmp; + struct list_head *cursor, *next; + + dev_dbg(main_info->dev, "cg2900_deregister_chip_driver\n"); + + if (!cb) { + dev_err(main_info->dev, "NULL supplied as cb\n"); + return; + } + mutex_lock(&main_info->man_mutex); + list_for_each_safe(cursor, next, &main_info->chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + if (tmp->cb.check_chip_support == cb->check_chip_support) { + list_del(cursor); + kfree(tmp); + break; + } + } + mutex_unlock(&main_info->man_mutex); +} +EXPORT_SYMBOL_GPL(cg2900_deregister_chip_driver); + +/** + * cg2900_register_trans_driver() - Register a transport driver. + * @dev: Transport device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + * -EACCES if work can't be queued. + */ +int cg2900_register_trans_driver(struct cg2900_chip_dev *dev) +{ + int err; + struct cg2900_platform_data *pf_data; + struct core_info *info; + + BUG_ON(!main_info); + + if (!dev || !dev->dev) { + dev_err(main_info->dev, "cg2900_register_trans_driver: " + "Received NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(dev->dev, "cg2900_register_trans_driver\n"); + + if (!dev->t_cb.write) { + dev_err(dev->dev, "cg2900_register_trans_driver: Write function" + " missing\n"); + return -EINVAL; + } + + pf_data = dev_get_platdata(dev->dev); + if (!pf_data) { + dev_err(dev->dev, "cg2900_register_trans_driver: Missing " + "platform data\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info\n"); + return -ENOMEM; + } + + if (pf_data->init) { + err = pf_data->init(dev); + if (err) { + dev_err(dev->dev, "Platform init failed (%d)\n", err); + goto error_handling; + } + } + + info->chip_dev = dev; + dev->prv_data = info; + + info->wq = create_singlethread_workqueue(CORE_WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + err = -ENOMEM; + goto error_handling_exit; + } + + dev_info(dev->dev, "Transport connected\n"); + + INIT_WORK(&info->work, work_hw_registered); + if (!queue_work(info->wq, &info->work)) { + dev_err(dev->dev, "Failed to queue work_hw_registered because " + "it's already in the queue\n"); + err = -EACCES; + goto error_handling_wq; + } + + return 0; + +error_handling_wq: + destroy_workqueue(info->wq); +error_handling_exit: + if (pf_data->exit) + pf_data->exit(dev); +error_handling: + kfree(info); + return err; +} +EXPORT_SYMBOL_GPL(cg2900_register_trans_driver); + +/** + * cg2900_deregister_trans_driver() - Deregister a transport driver. + * @dev: Transport device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +int cg2900_deregister_trans_driver(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct core_info *info = dev->prv_data; + + BUG_ON(!main_info); + + dev_dbg(dev->dev, "cg2900_deregister_trans_driver\n"); + + if (dev->c_cb.chip_removed) + dev->c_cb.chip_removed(dev); + + destroy_workqueue(info->wq); + + dev->prv_data = NULL; + kfree(info); + + dev_info(dev->dev, "Transport disconnected\n"); + + pf_data = dev_get_platdata(dev->dev); + if (!pf_data) { + dev_err(dev->dev, "Missing platform data\n"); + return -EINVAL; + } + + if (pf_data->exit) + pf_data->exit(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_deregister_trans_driver); + +/** + * cg2900_get_sleep_timeout() - Return sleep timeout in jiffies. + * + * Returns: + * Sleep timeout in jiffies. 0 means that sleep timeout shall not be used. + */ +unsigned long cg2900_get_sleep_timeout(void) +{ + if (!sleep_timeout_ms) + return 0; + + return msecs_to_jiffies(sleep_timeout_ms); +} +EXPORT_SYMBOL_GPL(cg2900_get_sleep_timeout); + +/** + * cg2900_probe() - Initialize module. + * + * @pdev: Platform device. + * + * This function initialize the transport and CG2900 Core, then + * register to the transport framework. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + */ +static int __devinit cg2900_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "cg2900_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_KERNEL); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + INIT_LIST_HEAD(&main_info->chip_handlers); + init_waitqueue_head(&main_info->wq); + + dev_info(&pdev->dev, "CG2900 Core driver started\n"); + + return 0; +} + +/** + * cg2900_remove() - Remove module. + * + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if core_info does not exist. + * -EINVAL if platform data does not exist in the device. + */ +static int __devexit cg2900_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "cg2900_remove\n"); + + kfree(main_info); + main_info = NULL; + + dev_info(&pdev->dev, "CG2900 Core driver removed\n"); + + return 0; +} + +static struct platform_driver cg2900_driver = { + .driver = { + .name = "cg2900", + .owner = THIS_MODULE, + }, + .probe = cg2900_probe, + .remove = __devexit_p(cg2900_remove), +}; + +/** + * cg2900_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_init(void) +{ + pr_debug("cg2900_init"); + return platform_driver_register(&cg2900_driver); +} + +/** + * cg2900_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_exit(void) +{ + pr_debug("cg2900_exit"); + platform_driver_unregister(&cg2900_driver); +} + +module_init(cg2900_init); +module_exit(cg2900_exit); + +module_param(sleep_timeout_ms, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(sleep_timeout_ms, + "Sleep timeout for data transmissions:\n" + "\tDefault 10000 ms\n" + "\t0 = disable\n" + "\t>0 = sleep timeout in milliseconds"); + +module_param_array(bd_address, byte, &bd_addr_count, + S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(bd_address, + "Bluetooth Device address. " + "Default 0x00 0x80 0xDE 0xAD 0xBE 0xEF. " + "Enter as comma separated value."); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 CG2900 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_core.h b/drivers/staging/cg2900/mfd/cg2900_core.h new file mode 100644 index 00000000000..bdd951a501d --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_core.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_CORE_H_ +#define _CG2900_CORE_H_ + +#include +#include + +/* Reserve 1 byte for the HCI H:4 header */ +#define HCI_H4_SIZE 1 +#define CG2900_SKB_RESERVE HCI_H4_SIZE + +/* Number of bytes to reserve at start of sk_buffer when receiving packet */ +#define RX_SKB_RESERVE 8 + +#define BT_BDADDR_SIZE 6 + +/* Standardized Bluetooth H:4 channels */ +#define HCI_BT_CMD_H4_CHANNEL 0x01 +#define HCI_BT_ACL_H4_CHANNEL 0x02 +#define HCI_BT_SCO_H4_CHANNEL 0x03 +#define HCI_BT_EVT_H4_CHANNEL 0x04 + +/* Default H4 channels which may change depending on connected controller */ +#define HCI_FM_RADIO_H4_CHANNEL 0x08 +#define HCI_GNSS_H4_CHANNEL 0x09 + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* Bluetooth lengths */ +#define HCI_BT_SEND_FILE_MAX_CHUNK_SIZE 254 + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/* module_param declared in cg2900_core.c */ +extern u8 bd_address[BT_BDADDR_SIZE]; + +#endif /* _CG2900_CORE_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_lib.c b/drivers/staging/cg2900/mfd/cg2900_lib.c new file mode 100644 index 00000000000..cb8ad46a486 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_lib.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_lib" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include + +#include "cg2900.h" +#include "cg2900_chip.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" + +/* + * Max length in bytes for line buffer used to parse settings and patch file. + * Must be max length of name plus characters used to define chip version. + */ +#define LINE_BUFFER_LENGTH (NAME_MAX + 30) +#define LOGGER_HEADER_SIZE 1 +/** + * cg2900_tx_to_chip() - Transmit buffer to the transport. + * @user: User data for BT command channel. + * @logger: User data for logger channel. + * @skb: Data packet. + * + * The transmit_skb_to_chip() function transmit buffer to the transport. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +void cg2900_tx_to_chip(struct cg2900_user_data *user, + struct cg2900_user_data *logger, struct sk_buff *skb) +{ + int err; + struct cg2900_chip_dev *chip_dev; + + dev_dbg(user->dev, "cg2900_tx_to_chip %d bytes.\n", skb->len); + + if (logger) + cg2900_send_to_hci_logger(logger, skb, LOGGER_DIRECTION_TX); + + chip_dev = cg2900_get_prv(user); + err = chip_dev->t_cb.write(chip_dev, skb); + if (err) { + dev_err(user->dev, "cg2900_tx_to_chip: Transport write failed " + "(%d)\n", err); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(cg2900_tx_to_chip); + +/** + * cg2900_tx_no_user() - Transmit buffer to the transport. + * @dev: Current chip to transmit to. + * @skb: Data packet. + * + * This function transmits buffer to the transport when no user exist (system + * startup for example). + */ +void cg2900_tx_no_user(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + int err; + + dev_dbg(dev->dev, "cg2900_tx_no_user %d bytes.\n", skb->len); + + err = dev->t_cb.write(dev, skb); + if (err) { + dev_err(dev->dev, "cg2900_tx_no_user: Transport write failed " + "(%d)\n", err); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(cg2900_tx_no_user); + +/** + * create_and_send_bt_cmd() - Copy and send sk_buffer. + * @user: User data for current channel. + * @logger: User data for logger channel. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data + * to it, and send the sk_buffer to controller. + */ +void cg2900_send_bt_cmd(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + void *data, int length) +{ + struct sk_buff *skb; + + skb = user->alloc_skb(length, GFP_ATOMIC); + if (!skb) { + dev_err(user->dev, "cg2900_send_bt_cmd: Couldn't alloc " + "sk_buff with length %d\n", length); + return; + } + + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_to_chip(user, logger, skb); +} +EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd); + +/** + * cg2900_send_bt_cmd_no_user() - Copy and send sk_buffer with no assigned user. + * @dev: Current chip to transmit to. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The cg2900_send_bt_cmd_no_user() function allocate sk_buffer, copy supplied + * data to it, and send the sk_buffer to controller. + */ +void cg2900_send_bt_cmd_no_user(struct cg2900_chip_dev *dev, void *data, + int length) +{ + struct sk_buff *skb; + + skb = alloc_skb(length + HCI_H4_SIZE, GFP_KERNEL); + if (!skb) { + dev_err(dev->dev, "cg2900_send_bt_cmd_no_user: Couldn't alloc " + "sk_buff with length %d\n", length); + return; + } + + skb_reserve(skb, HCI_H4_SIZE); + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_no_user(dev, skb); +} +EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd_no_user); + +/** + * create_work_item() - Create work item and add it to the work queue. + * @wq: Work queue. + * @work_func: Work function. + * @user_data: Arbitrary data set by user. + * + * The create_work_item() function creates work item and add it to + * the work queue. + * Note that work is allocated by kmalloc and work must be freed when work + * function is started. + */ +void cg2900_create_work_item(struct workqueue_struct *wq, work_func_t work_func, + void *user_data) +{ + struct cg2900_work *new_work; + int err; + + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC); + if (!new_work) { + pr_err("Failed to alloc memory for new_work"); + return; + } + + INIT_WORK(&new_work->work, work_func); + new_work->user_data = user_data; + + err = queue_work(wq, &new_work->work); + if (!err) { + pr_err("Failed to queue work_struct because it's already " + "in the queue"); + kfree(new_work); + } +} +EXPORT_SYMBOL_GPL(cg2900_create_work_item); + +/** + * read_and_send_file_part() - Transmit a part of the supplied file. + * @user: User data for current channel. + * @logger: User data for logger channel. + * @info: File information. + * + * The cg2900_read_and_send_file_part() function transmit a part of the supplied + * file to the controller. + * + * Returns: + * 0 if there is no more data in the file. + * >0 for number of bytes sent. + * -ENOMEM if skb allocation failed. + */ +int cg2900_read_and_send_file_part(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct cg2900_file_info *info) +{ + int bytes_to_copy; + struct sk_buff *skb; + struct bt_vs_write_file_block_cmd *cmd; + int plen; + + /* + * Calculate number of bytes to copy; + * either max bytes for HCI packet or number of bytes left in file + */ + bytes_to_copy = min((int)HCI_BT_SEND_FILE_MAX_CHUNK_SIZE, + (int)(info->fw_file->size - info->file_offset)); + + if (bytes_to_copy <= 0) { + /* Nothing more to read in file. */ + dev_dbg(user->dev, "File download finished\n"); + info->chunk_id = 0; + info->file_offset = 0; + return 0; + } + + /* There is more data to send */ + plen = sizeof(*cmd) + bytes_to_copy; + skb = user->alloc_skb(plen, GFP_KERNEL); + if (!skb) { + dev_err(user->dev, "Couldn't allocate sk_buffer\n"); + return -ENOMEM; + } + + skb_put(skb, plen); + + cmd = (struct bt_vs_write_file_block_cmd *)skb->data; + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_WRITE_FILE_BLOCK); + cmd->plen = BT_PARAM_LEN(plen); + cmd->id = info->chunk_id; + info->chunk_id++; + + /* Copy the data from offset position */ + memcpy(cmd->data, + &(info->fw_file->data[info->file_offset]), + bytes_to_copy); + + /* Increase offset with number of bytes copied */ + info->file_offset += bytes_to_copy; + + skb_push(skb, CG2900_SKB_RESERVE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_to_chip(user, logger, skb); + + return bytes_to_copy; +} +EXPORT_SYMBOL_GPL(cg2900_read_and_send_file_part); + +void cg2900_send_to_hci_logger(struct cg2900_user_data *logger, + struct sk_buff *skb, + u8 direction) +{ + struct sk_buff *skb_log; + u8 *p; + + /* + * Alloc a new sk_buff and copy the data into it. Then send it to + * the HCI logger. + */ + skb_log = alloc_skb(skb->len + LOGGER_HEADER_SIZE, GFP_NOWAIT); + if (!skb_log) { + pr_err("cg2900_send_to_hci_logger:\ + Couldn't allocate skb_log\n"); + return; + } + /* Reserve 1 byte for direction.*/ + skb_reserve(skb_log, LOGGER_HEADER_SIZE); + + memcpy(skb_put(skb_log, skb->len), skb->data, skb->len); + p = skb_push(skb_log, LOGGER_HEADER_SIZE); + *p = (u8) direction; + + if (logger->read_cb) + logger->read_cb(logger, skb_log); + + return; +} +EXPORT_SYMBOL_GPL(cg2900_send_to_hci_logger); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Library functions"); diff --git a/drivers/staging/cg2900/mfd/cg2900_lib.h b/drivers/staging/cg2900/mfd/cg2900_lib.h new file mode 100644 index 00000000000..99d5ce6cfdb --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_lib.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_LIB_H_ +#define _CG2900_LIB_H_ + +#include +#include +#include + +#include "cg2900.h" + +/** + * struct cg2900_work - Generic work structure. + * @work: Work structure. + * @user_data: Arbitrary data set by user. + */ +struct cg2900_work { + struct work_struct work; + void *user_data; +}; + +/** + * struct cg2900_file_info - Info structure for file to download. + * @fw_file: Stores firmware file. + * @file_offset: Current read offset in firmware file. + * @chunk_id: Stores current chunk ID of write file + * operations. + */ +struct cg2900_file_info { + const struct firmware *fw_file; + int file_offset; + u8 chunk_id; +}; + +extern void cg2900_tx_to_chip(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct sk_buff *skb); +extern void cg2900_tx_no_user(struct cg2900_chip_dev *dev, struct sk_buff *skb); +extern void cg2900_send_bt_cmd(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + void *data, int length); +extern void cg2900_send_bt_cmd_no_user(struct cg2900_chip_dev *dev, void *data, + int length); +extern void cg2900_create_work_item(struct workqueue_struct *wq, + work_func_t work_func, + void *user_data); +extern int cg2900_read_and_send_file_part(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct cg2900_file_info *info); +extern void cg2900_send_to_hci_logger(struct cg2900_user_data *logger, + struct sk_buff *skb, + u8 direction); + +#endif /* _CG2900_LIB_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_test.c b/drivers/staging/cg2900/mfd/cg2900_test.c new file mode 100644 index 00000000000..58ac6166af6 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_test.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Driver for ST-Ericsson CG2900 test character device. + */ +#define NAME "cg2900_test" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" +#include "cg2900_core.h" + +#define MISC_DEV (info->misc_dev.this_device) + +/* Device names */ +#define CG2900_CDEV_NAME "cg2900_core_test" + +/** + * struct test_info - Main info structure for CG2900 test char device. + * @misc_dev: Registered Misc Device. + * @rx_queue: RX data queue. + * @dev: Device structure for STE Connectivity driver. + * @pdev: Platform device structure for STE Connectivity driver. + */ +struct test_info { + struct miscdevice misc_dev; + struct sk_buff_head rx_queue; + struct device *dev; + struct platform_device *pdev; +}; + +static struct test_info *test_info; + +/* + * main_wait_queue - Char device Wait Queue in CG2900 Core. + */ +static DECLARE_WAIT_QUEUE_HEAD(char_wait_queue); + +/** + * tx_to_char_dev() - Handle data received from CG2900 Core. + * @dev: Current chip device information. + * @skb: Buffer with data coming form device. + */ +static int tx_to_char_dev(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + struct test_info *info = dev->t_data; + skb_queue_tail(&info->rx_queue, skb); + wake_up_interruptible_all(&char_wait_queue); + return 0; +} + +/** + * cg2900_test_open() - User space char device has been opened. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -EACCES if transport already exists. + * -ENOMEM if allocation fails. + * Errors from create_work_item. + */ +static int cg2900_test_open(struct inode *inode, struct file *filp) +{ + struct test_info *info = test_info; + struct cg2900_chip_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(MISC_DEV, "Cannot allocate test_dev\n"); + return -ENOMEM; + } + dev->dev = info->dev; + dev->pdev = info->pdev; + dev->t_data = info; + dev->t_cb.write = tx_to_char_dev; + filp->private_data = dev; + + dev_info(MISC_DEV, "CG2900 test char dev opened\n"); + return cg2900_register_trans_driver(dev); +} + +/** + * cg2900_test_release() - User space char device has been closed. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + */ +static int cg2900_test_release(struct inode *inode, struct file *filp) +{ + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + + dev_info(MISC_DEV, "CG2900 test char dev closed\n"); + skb_queue_purge(&info->rx_queue); + cg2900_deregister_trans_driver(dev); + kfree(dev); + + return 0; +} + +/** + * cg2900_test_read() - Queue and copy buffer to user space char device. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Count of received data in bytes. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes read. + * -EFAULT if copy_to_user fails. + */ +static ssize_t cg2900_test_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + int bytes_to_copy; + int err; + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + struct sk_buff_head *rx_queue = &info->rx_queue; + + dev_dbg(MISC_DEV, "cg2900_test_read count %d\n", count); + + if (skb_queue_empty(rx_queue)) + wait_event_interruptible(char_wait_queue, + !(skb_queue_empty(rx_queue))); + + skb = skb_dequeue(rx_queue); + if (!skb) { + dev_dbg(MISC_DEV, + "skb queue is empty - return with zero bytes\n"); + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, skb->len); + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + skb_queue_head(rx_queue, skb); + return -EFAULT; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(rx_queue, skb); + else + kfree_skb(skb); + +finished: + return bytes_to_copy; +} + +/** + * cg2900_test_write() - Copy buffer from user and write to CG2900 Core. + * @filp: Pointer to the file struct. + * @buf: Read buffer. + * @count: Size of the buffer write. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes written. + * -EFAULT if copy_from_user fails. + */ +static ssize_t cg2900_test_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + + dev_dbg(MISC_DEV, "cg2900_test_write count %d\n", count); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(count + RX_SKB_RESERVE, GFP_KERNEL); + if (!skb) { + dev_err(MISC_DEV, "cg2900_test_write: Failed to alloc skb\n"); + return -ENOMEM; + } + skb_reserve(skb, RX_SKB_RESERVE); + + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + + dev->c_cb.data_from_chip(dev, skb); + + return count; +} + +/** + * cg2900_test_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * Returns: + * Mask of current set POLL values (0 or (POLLIN | POLLRDNORM)) + */ +static unsigned int cg2900_test_poll(struct file *filp, poll_table *wait) +{ + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + unsigned int mask = 0; + + poll_wait(filp, &char_wait_queue, wait); + + if (!(skb_queue_empty(&info->rx_queue))) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static const struct file_operations test_char_dev_fops = { + .open = cg2900_test_open, + .release = cg2900_test_release, + .read = cg2900_test_read, + .write = cg2900_test_write, + .poll = cg2900_test_poll +}; + +/** + * test_char_dev_create() - Create a char device for testing. + * @info: Test device info. + * + * Creates a separate char device that will interact directly with userspace + * test application. + * + * Returns: + * 0 if there is no error. + * Error codes from misc_register. + */ +static int test_char_dev_create(struct test_info *info) +{ + int err; + + /* Initialize the RX queue */ + skb_queue_head_init(&info->rx_queue); + + /* Prepare miscdevice struct before registering the device */ + info->misc_dev.minor = MISC_DYNAMIC_MINOR; + info->misc_dev.name = CG2900_CDEV_NAME; + info->misc_dev.fops = &test_char_dev_fops; + info->misc_dev.parent = info->dev; + info->misc_dev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&info->misc_dev); + if (err) { + dev_err(info->dev, "Error %d registering misc dev", err); + return err; + } + + return 0; +} + +/** + * test_char_dev_destroy() - Clean up after test_char_dev_create(). + * @info: Test device info. + */ +static void test_char_dev_destroy(struct test_info *info) +{ + int err; + + err = misc_deregister(&info->misc_dev); + if (err) + dev_err(info->dev, "Error %d deregistering misc dev\n", err); + + /* Clean the message queue */ + skb_queue_purge(&info->rx_queue); +} + +/** + * cg2900_test_probe() - Initialize module. + * + * @pdev: Platform device. + * + * This function initializes and registers the test misc char device. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * -EEXIST if device already exists. + * Error codes generated by test_char_dev_create. + */ +static int __devinit cg2900_test_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "cg2900_test_probe\n"); + + if (test_info) { + dev_err(&pdev->dev, "test_info exists\n"); + return -EEXIST; + } + + test_info = kzalloc(sizeof(*test_info), GFP_KERNEL); + if (!test_info) { + dev_err(&pdev->dev, "Couldn't allocate test_info\n"); + return -ENOMEM; + } + + test_info->dev = &pdev->dev; + test_info->pdev = pdev; + + /* Create and add test char device. */ + err = test_char_dev_create(test_info); + if (err) { + kfree(test_info); + test_info = NULL; + return err; + } + + dev_set_drvdata(&pdev->dev, test_info); + + dev_info(&pdev->dev, "CG2900 test char device driver started\n"); + + return 0; +} + +/** + * cg2900_test_remove() - Remove module. + * + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if core_info does not exist. + * -EINVAL if platform data does not exist in the device. + */ +static int __devexit cg2900_test_remove(struct platform_device *pdev) +{ + struct test_info *test_info; + + dev_dbg(&pdev->dev, "cg2900_test_remove\n"); + test_info = dev_get_drvdata(&pdev->dev); + test_char_dev_destroy(test_info); + dev_set_drvdata(&pdev->dev, NULL); + kfree(test_info); + test_info = NULL; + dev_info(&pdev->dev, "CG2900 Test char device driver removed\n"); + return 0; +} + +static struct platform_driver cg2900_test_driver = { + .driver = { + .name = "cg2900-test", + .owner = THIS_MODULE, + }, + .probe = cg2900_test_probe, + .remove = __devexit_p(cg2900_test_remove), +}; + +/** + * cg2900_test_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_test_init(void) +{ + pr_debug("cg2900_test_init"); + return platform_driver_register(&cg2900_test_driver); +} + +/** + * cg2900_test_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_test_exit(void) +{ + pr_debug("cg2900_test_exit"); + platform_driver_unregister(&cg2900_test_driver); +} + +module_init(cg2900_test_init); +module_exit(cg2900_test_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Test Char Device Driver"); diff --git a/drivers/staging/cg2900/mfd/stlc2690_chip.c b/drivers/staging/cg2900/mfd/stlc2690_chip.c new file mode 100644 index 00000000000..47d0cf1c0e5 --- /dev/null +++ b/drivers/staging/cg2900/mfd/stlc2690_chip.c @@ -0,0 +1,1643 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller. + */ +#define NAME "stlc2690_chip" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" +#include "stlc2690_chip.h" + +#define MAIN_DEV (main_info->dev) +#define BOOT_DEV (info->user_in_charge->dev) + +#define WQ_NAME "stlc2690_chip_wq" + +#define LINE_TOGGLE_DETECT_TIMEOUT 50 /* ms */ +#define CHIP_READY_TIMEOUT 100 /* ms */ +#define CHIP_STARTUP_TIMEOUT 15000 /* ms */ +#define CHIP_SHUTDOWN_TIMEOUT 15000 /* ms */ + +/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel + * for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_CMD 0x01 + +/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel + * for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_ACL 0x02 + +/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel + * for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_EVT 0x04 + +/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel + * for logging all transmitted H4 packets (on all channels). + */ +#define CHANNEL_HCI_LOGGER 0xFA + +/** CHANNEL_CORE - Bluetooth HCI H:4 channel + * for user space control of the ST-Ericsson connectivity controller. + */ +#define CHANNEL_CORE 0xFD + +/* + * For the char dev names we keep the same names in order to be able to reuse + * the users and to keep a consistent interface. + */ + +/** STLC2690_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands. + */ +#define STLC2690_BT_CMD "cg2900_bt_cmd" + +/** STLC2690_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data. + */ +#define STLC2690_BT_ACL "cg2900_bt_acl" + +/** STLC2690_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events. + */ +#define STLC2690_BT_EVT "cg2900_bt_evt" + +/** STLC2690_HCI_LOGGER - BT channel for logging all transmitted H4 packets. + * Data read is copy of all data transferred on the other channels. + * Only write allowed is configuration of the HCI Logger. + */ +#define STLC2690_HCI_LOGGER "cg2900_hci_logger" + +/** STLC2690_CORE- Channel for keeping ST-Ericsson STLC2690 enabled. + * Opening this channel forces the chip to stay powered. + * No data can be written to or read from this channel. + */ +#define STLC2690_CORE "cg2900_core" + +/** + * enum main_state - Main-state for STLC2690 driver. + * @STLC2690_INIT: STLC2690 initializing. + * @STLC2690_IDLE: No user registered to STLC2690 driver. + * @STLC2690_BOOTING: STLC2690 booting after first user is registered. + * @STLC2690_CLOSING: STLC2690 closing after last user has deregistered. + * @STLC2690_RESETING: STLC2690 reset requested. + * @STLC2690_ACTIVE: STLC2690 up and running with at least one user. + */ +enum main_state { + STLC2690_INIT, + STLC2690_IDLE, + STLC2690_BOOTING, + STLC2690_CLOSING, + STLC2690_RESETING, + STLC2690_ACTIVE +}; + +/** + * enum boot_state - BOOT-state for STLC2690 chip driver. + * @BOOT_RESET: HCI Reset has been sent. + * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address + * has been sent. + * @BOOT_GET_FILES_TO_LOAD: STLC2690 chip driver is retrieving file + * to load. + * @BOOT_DOWNLOAD_PATCH: STLC2690 chip driver is downloading + * patches. + * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: STLC2690 chip driver is activating + * patches and settings. + * @BOOT_READY: STLC2690 chip driver boot is ready. + * @BOOT_FAILED: STLC2690 chip driver boot failed. + */ +enum boot_state { + BOOT_RESET, + BOOT_SEND_BD_ADDRESS, + BOOT_GET_FILES_TO_LOAD, + BOOT_DOWNLOAD_PATCH, + BOOT_ACTIVATE_PATCHES_AND_SETTINGS, + BOOT_READY, + BOOT_FAILED +}; + +/** + * enum file_load_state - BOOT_FILE_LOAD-state for STLC2690 chip driver. + * @FILE_LOAD_GET_PATCH: Loading patches. + * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings. + * @FILE_LOAD_NO_MORE_FILES: No more files to load. + * @FILE_LOAD_FAILED: File loading failed. + */ +enum file_load_state { + FILE_LOAD_GET_PATCH, + FILE_LOAD_GET_STATIC_SETTINGS, + FILE_LOAD_NO_MORE_FILES, + FILE_LOAD_FAILED +}; + +/** + * enum download_state - BOOT_DOWNLOAD state. + * @DOWNLOAD_PENDING: Download in progress. + * @DOWNLOAD_SUCCESS: Download successfully finished. + * @DOWNLOAD_FAILED: Downloading failed. + */ +enum download_state { + DOWNLOAD_PENDING, + DOWNLOAD_SUCCESS, + DOWNLOAD_FAILED +}; + + +/** + * struct stlc2690_channel_item - List object for channel. + * @list: list_head struct. + * @user: User for this channel. + */ +struct stlc2690_channel_item { + struct list_head list; + struct cg2900_user_data *user; +}; + +/** + * struct stlc2690_skb_data - Structure for storing private data in an sk_buffer. + * @dev: STLC2690 device for this sk_buffer. + */ +struct stlc2690_skb_data { + struct cg2900_user_data *user; +}; +#define stlc2690_skb_data(__skb) ((struct stlc2690_skb_data *)((__skb)->cb)) + +/** + * struct stlc2690_chip_info - Main info structure for STLC2690 chip driver. + * @patch_file_name: Stores patch file name. + * @settings_file_name: Stores settings file name. + * @file_info: Firmware file info (patch or settings). + * @main_state: Current MAIN-state of STLC2690 chip driver. + * @boot_state: Current BOOT-state of STLC2690 chip driver. + * @file_load_state: Current BOOT_FILE_LOAD-state of STLC2690 chip + * driver. + * @download_state: Current BOOT_DOWNLOAD-state of STLC2690 chip + * driver. + * @wq: STLC2690 chip driver workqueue. + * @chip_dev: Chip handler info. + * @user_in_charge: User currently operating. Normally used at + * channel open and close. + * @last_user: Last user of this chip. + * @logger: Logger user of this chip. + */ +struct stlc2690_chip_info { + char *patch_file_name; + char *settings_file_name; + struct cg2900_file_info file_info; + enum main_state main_state; + enum boot_state boot_state; + enum file_load_state file_load_state; + enum download_state download_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + spinlock_t rw_lock; + struct list_head open_channels; + struct cg2900_user_data *user_in_charge; + struct cg2900_user_data *last_user; + struct cg2900_user_data *logger; +}; + +/** + * struct main_info - Main info structure for STLC2690 chip driver. + * @dev: Device structure. + * @cell_base_id: Base ID for MFD cells. + * @man_mutex: Management mutex. + */ +struct main_info { + struct device *dev; + int cell_base_id; + struct mutex man_mutex; +}; + +static struct main_info *main_info; + +/* + * main_wait_queue - Main Wait Queue in STLC2690 driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue); + +static void chip_startup_finished(struct stlc2690_chip_info *info, int err); + +/** + * send_bd_address() - Send HCI VS command with BD address to the chip. + */ +static void send_bd_address(struct stlc2690_chip_info *info) +{ + struct bt_vs_store_in_fs_cmd *cmd; + u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE; + + cmd = kmalloc(plen, GFP_KERNEL); + if (!cmd) + return; + + cmd->opcode = cpu_to_le16(STLC2690_BT_OP_VS_STORE_IN_FS); + cmd->plen = BT_PARAM_LEN(plen); + cmd->user_id = STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR; + cmd->len = BT_BDADDR_SIZE; + /* Now copy the BD address received from user space control app. */ + memcpy(cmd->data, bd_address, BT_BDADDR_SIZE); + + dev_dbg(BOOT_DEV, "New boot_state: BOOT_SEND_BD_ADDRESS\n"); + info->boot_state = BOOT_SEND_BD_ADDRESS; + + cg2900_send_bt_cmd(info->user_in_charge, info->logger, cmd, plen); + + kfree(cmd); +} + +/** + * send_settings_file() - Transmit settings file. + * + * The send_settings_file() function transmit settings file. + * The file is read in parts to fit in HCI packets. When finished, + * close the settings file and send HCI reset to activate settings and patches. + */ +static void send_settings_file(struct stlc2690_chip_info *info) +{ + int bytes_sent; + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_settings_file: Error %d occurred\n", + bytes_sent); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, bytes_sent); + return; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + /* Settings file finished. Release used resources */ + dev_dbg(BOOT_DEV, "Settings file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_NO_MORE_FILES\n"); + info->file_load_state = FILE_LOAD_NO_MORE_FILES; + + /* Create and send HCI VS Store In FS command with bd address. */ + send_bd_address(info); +} + +/** + * send_patch_file - Transmit patch file. + * + * The send_patch_file() function transmit patch file. + * The file is read in parts to fit in HCI packets. When the complete file is + * transmitted, the file is closed. + * When finished, continue with settings file. + */ +static void send_patch_file(struct cg2900_chip_dev *dev) +{ + int err; + int bytes_sent; + struct stlc2690_chip_info *info = dev->c_data; + int file_name_size = strlen("STLC2690_XXXX_XXXX_settings.fw"); + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_patch_file: Error %d occurred\n", + bytes_sent); + err = bytes_sent; + goto error_handling; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + dev_dbg(BOOT_DEV, "Patch file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + /* + * Create the settings file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->settings_file_name, file_name_size + 1, + "STLC2690_%04X_%04X_settings.fw", + dev->chip.hci_revision, dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading settings file %s\n", + info->settings_file_name); + } else { + dev_err(BOOT_DEV, "Settings file name failed! err=%d\n", err); + goto error_handling; + } + + /* Retrieve the settings file */ + err = request_firmware(&info->file_info.fw_file, + info->settings_file_name, + info->chip_dev->dev); + if (err) { + dev_err(BOOT_DEV, "Couldn't get settings file (%d)\n", err); + goto error_handling; + } + /* Now send the settings file */ + dev_dbg(BOOT_DEV, + "New file_load_state: FILE_LOAD_GET_STATIC_SETTINGS\n"); + info->file_load_state = FILE_LOAD_GET_STATIC_SETTINGS; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + send_settings_file(info); + return; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, err); +} + +/** + * work_reset_after_error() - Handle reset. + * @work: Reference to work data. + * + * Handle a reset after received Command Complete event. + */ +static void work_reset_after_error(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_reset_after_error: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + chip_startup_finished(info, -EIO); + + kfree(my_work); +} + +/** + * work_load_patch_and_settings() - Start loading patches and settings. + * @work: Reference to work data. + */ +static void work_load_patch_and_settings(struct work_struct *work) +{ + int err = 0; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + int file_name_size = strlen("STLC2690_XXXX_XXXX_patch.fw"); + + if (!work) { + dev_err(MAIN_DEV, + "work_load_patch_and_settings: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Check that we are in the right state */ + if (info->boot_state != BOOT_GET_FILES_TO_LOAD) + goto finished; + + /* + * Create the patch file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->patch_file_name, file_name_size + 1, + "STLC2690_%04X_%04X_patch.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading patch file %s\n", + info->patch_file_name); + } else { + dev_err(BOOT_DEV, "Patch file name failed! err=%d\n", err); + goto error_handling; + } + + /* We now all info needed */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DOWNLOAD_PATCH\n"); + info->boot_state = BOOT_DOWNLOAD_PATCH; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_GET_PATCH\n"); + info->file_load_state = FILE_LOAD_GET_PATCH; + info->file_info.chunk_id = 0; + info->file_info.file_offset = 0; + info->file_info.fw_file = NULL; + + /* OK. Now it is time to download the patches */ + err = request_firmware(&(info->file_info.fw_file), + info->patch_file_name, + dev->dev); + if (err < 0) { + dev_err(BOOT_DEV, "Couldn't get patch file (%d)\n", err); + goto error_handling; + } + send_patch_file(dev); + + goto finished; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); +finished: + kfree(my_work); +} + +/** + * work_cont_file_download() - A file block has been written. + * @work: Reference to work data. + * + * Handle a received HCI VS Write File Block Complete event. + * Normally this means continue to send files to the controller. + */ +static void work_cont_file_download(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_cont_file_download: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Continue to send patches or settings to the controller */ + if (info->file_load_state == FILE_LOAD_GET_PATCH) + send_patch_file(dev); + else if (info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS) + send_settings_file(info); + else + dev_dbg(BOOT_DEV, "No more files to load\n"); + + kfree(my_work); +} + +/** + * handle_reset_cmd_complete() - Handles HCI Reset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_reset_cmd_complete(struct cg2900_chip_dev *dev, u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, "Received Reset complete event with status 0x%X\n", + status); + + if (BOOT_RESET != info->boot_state && + BOOT_ACTIVATE_PATCHES_AND_SETTINGS != info->boot_state) + return false; + + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, "Command complete for HciReset received with " + "error 0x%X\n", status); + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + return true; + } + + if (BOOT_RESET == info->boot_state) { + info->boot_state = BOOT_GET_FILES_TO_LOAD; + cg2900_create_work_item(info->wq, work_load_patch_and_settings, + dev); + } else { + /* + * The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + chip_startup_finished(info, 0); + } + + return true; +} + + +/** + * handle_vs_store_in_fs_cmd_complete() - Handles HCI VS StoreInFS Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_store_in_fs_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, + "Received Store_in_FS complete event with status 0x%X\n", + status); + + if (info->boot_state != BOOT_SEND_BD_ADDRESS) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) { + struct hci_command_hdr cmd; + + /* Send HCI Reset command to activate patches */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_ACTIVATE_PATCHES_AND_SETTINGS\n"); + info->boot_state = BOOT_ACTIVATE_PATCHES_AND_SETTINGS; + + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for Reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); + } else { + dev_err(BOOT_DEV, + "Command complete for StoreInFS received with error " + "0x%X\n", status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_complete() - Handles HCI VS WriteFileBlock Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) + cg2900_create_work_item(info->wq, work_cont_file_download, dev); + else { + dev_err(BOOT_DEV, + "Command complete for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_status() - Handles HCI VS WriteFileBlock Command Status event. + * @status: Returned status of WriteFileBlock command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct stlc2690_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Command status for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if received data should be handled in STLC2690 chip driver. + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in STLC2690 chip driver. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + /* skb cannot be NULL here so it is safe to de-reference */ + u8 *data = skb->data; + struct hci_event_hdr *evt; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + data += sizeof(*evt); + + /* First check the event code. */ + if (HCI_EV_CMD_COMPLETE == evt->evt) { + struct hci_ev_cmd_complete *cmd_complete; + + cmd_complete = (struct hci_ev_cmd_complete *)data; + op_code = le16_to_cpu(cmd_complete->opcode); + dev_dbg(dev->dev, + "Received Command Complete: op_code = 0x%04X\n", + op_code); + /* Move to first byte after OCF */ + data += sizeof(*cmd_complete); + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete(dev, data); + else if (op_code == STLC2690_BT_OP_VS_STORE_IN_FS) + pkt_handled = handle_vs_store_in_fs_cmd_complete(dev, + data); + else if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = + handle_vs_write_file_block_cmd_complete(dev, + data); + } else if (HCI_EV_CMD_STATUS == evt->evt) { + struct hci_ev_cmd_status *cmd_status; + + cmd_status = (struct hci_ev_cmd_status *)data; + + op_code = le16_to_cpu(cmd_status->opcode); + + dev_dbg(dev->dev, "Received Command Status: op_code = 0x%04X\n", + op_code); + + if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = handle_vs_write_file_block_cmd_status + (dev, cmd_status->status); + } else if (HCI_EV_HW_ERROR == evt->evt) { + struct hci_ev_hw_error *hw_error; + + hw_error = (struct hci_ev_hw_error *)data; + /* + * Only do a printout. There might be a receiving stack that can + * handle this event + */ + dev_err(dev->dev, "HW Error event received with error 0x%02X\n", + hw_error->hw_code); + return false; + } else + return false; + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * data_from_chip() - Called when data is received from the chip. + * @dev: Chip info. + * @skb: Packet received. + * + * The data_from_chip() function checks if packet is a response for a packet it + * itself has transmitted. If not it finds the correct user and sends the packet + * to the user. + */ +static void data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + int h4_channel; + struct list_head *cursor; + struct stlc2690_channel_item *tmp; + struct stlc2690_chip_info *info = dev->c_data; + struct cg2900_user_data *user = NULL; + + h4_channel = skb->data[0]; + skb_pull(skb, HCI_H4_SIZE); + + /* Then check if this is a response to data we have sent */ + if (h4_channel == CHANNEL_BT_EVT && handle_rx_data_bt_evt(dev, skb)) + return; + + spin_lock_bh(&info->rw_lock); + + /* Let's see if this packet has the same user as the last one */ + if (info->last_user && info->last_user->h4_channel == h4_channel) { + user = info->last_user; + goto user_found; + } + + /* Search through the list of all open channels to find the user */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user->h4_channel == h4_channel) { + user = tmp->user; + goto user_found; + } + } + +user_found: + info->last_user = user; + spin_unlock_bh(&info->rw_lock); + + if (user) + user->read_cb(user, skb); + else { + dev_err(dev->dev, + "Could not find corresponding user to h4_channel %d\n", + h4_channel); + kfree_skb(skb); + } +} + +static void chip_removed(struct cg2900_chip_dev *dev) +{ + struct stlc2690_chip_info *info = dev->c_data; + + mfd_remove_devices(dev->dev); + kfree(info->settings_file_name); + kfree(info->patch_file_name); + destroy_workqueue(info->wq); + kfree(info); + dev->c_data = NULL; + dev->c_cb.chip_removed = NULL; + dev->c_cb.data_from_chip = NULL; +} + +/** + * chip_shutdown() - Reset and power the chip off. + */ +static void chip_shutdown(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(user->dev, "chip_shutdown\n"); + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + /* Chip shut-down finished, set correct state and wake up the chip. */ + dev_dbg(dev->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + wake_up_all(&main_wait_queue); +} + +static void chip_startup_finished(struct stlc2690_chip_info *info, int err) +{ + dev_dbg(BOOT_DEV, "chip_startup_finished (%d)\n", err); + + if (err) + /* Shutdown the chip */ + chip_shutdown(info->user_in_charge); + else { + dev_dbg(BOOT_DEV, "New main_state: CORE_ACTIVE\n"); + info->main_state = STLC2690_ACTIVE; + } + + wake_up_all(&main_wait_queue); + + if (err) + return; + + if (!info->chip_dev->t_cb.chip_startup_finished) + dev_err(BOOT_DEV, "chip_startup_finished callback not found\n"); + else + info->chip_dev->t_cb.chip_startup_finished(info->chip_dev); +} + +static int stlc2690_open(struct cg2900_user_data *user) +{ + int err; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + struct list_head *cursor; + struct stlc2690_channel_item *tmp; + struct hci_command_hdr cmd; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "stlc2690_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "stlc2690_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* Add a minor wait in order to avoid CPU blocking, looping openings */ + err = wait_event_timeout(main_wait_queue, + (STLC2690_IDLE == info->main_state || + STLC2690_ACTIVE == info->main_state), + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + if (err <= 0) { + if (STLC2690_INIT == info->main_state) + dev_err(user->dev, "Transport not opened\n"); + else + dev_err(user->dev, "stlc2690_open currently busy " + "(0x%X). Try again\n", info->main_state); + err = -EBUSY; + goto err_free_mutex; + } + + err = 0; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user->h4_channel == user->h4_channel) { + dev_err(user->dev, "Channel %d is already opened\n", + user->h4_channel); + err = -EACCES; + goto err_free_mutex; + } + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + dev_err(user->dev, "Could not allocate tmp\n"); + err = -ENOMEM; + goto err_free_mutex; + } + tmp->user = user; + + if (STLC2690_ACTIVE != info->main_state && + !user->chip_independent) { + /* Open transport and start-up the chip */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait to be sure that the chip is ready */ + schedule_timeout_killable( + msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (dev->t_cb.open) + err = dev->t_cb.open(dev); + if (err) { + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + goto err_free_list_item; + } + + /* Start the boot sequence */ + info->user_in_charge = user; + info->last_user = user; + dev_dbg(user->dev, "New boot_state: BOOT_RESET\n"); + info->boot_state = BOOT_RESET; + dev_dbg(user->dev, "New main_state: STLC2690_BOOTING\n"); + info->main_state = STLC2690_BOOTING; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + cg2900_send_bt_cmd(user, info->logger, &cmd, sizeof(cmd)); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to start\n"); + wait_event_timeout(main_wait_queue, + (STLC2690_ACTIVE == info->main_state || + STLC2690_IDLE == info->main_state), + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + if (STLC2690_ACTIVE != info->main_state) { + dev_err(user->dev, "STLC2690 driver failed to start\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CORE_IDLE\n"); + info->main_state = STLC2690_IDLE; + err = -EIO; + goto err_free_list_item; + } + } + + list_add_tail(&tmp->list, &info->open_channels); + + user->opened = true; + + dev_dbg(user->dev, "H:4 channel opened\n"); + + mutex_unlock(&main_info->man_mutex); + return 0; +err_free_list_item: + kfree(tmp); +err_free_mutex: + mutex_unlock(&main_info->man_mutex); + return err; +} + +static int stlc2690_hci_log_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_hci_log_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "stlc2690_hci_log_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->logger) { + dev_err(user->dev, "HCI Logger already stored\n"); + return -EACCES; + } + + info->logger = user; + err = stlc2690_open(user); + if (err) + info->logger = NULL; + return err; +} + +static void stlc2690_close(struct cg2900_user_data *user) +{ + bool keep_powered = false; + struct list_head *cursor, *next; + struct stlc2690_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "stlc2690_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* + * Go through each open channel. Remove our channel and check if there + * is any other channel that want to keep the chip running + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user == user) { + list_del(cursor); + kfree(tmp); + } else if (!tmp->user->chip_independent) + keep_powered = true; + } + + if (keep_powered) + /* This was not the last user, we're done. */ + goto finished; + + if (STLC2690_IDLE == info->main_state) + /* Chip has already been shut down. */ + goto finished; + + dev_dbg(user->dev, "New main_state: CORE_CLOSING\n"); + info->main_state = STLC2690_CLOSING; + chip_shutdown(user); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to shut-down\n"); + wait_event_timeout(main_wait_queue, + STLC2690_IDLE == info->main_state, + msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT)); + + /* Force shutdown if we timed out */ + if (STLC2690_IDLE != info->main_state) { + dev_err(user->dev, + "ST-Ericsson STLC2690 Core Driver was shut-down with " + "problems\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CORE_IDLE\n"); + info->main_state = STLC2690_IDLE; + } + +finished: + mutex_unlock(&main_info->man_mutex); + user->opened = false; + dev_dbg(user->dev, "H:4 channel closed\n"); +} + +static void stlc2690_hci_log_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_hci_log_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "stlc2690_hci_log_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + info->logger = NULL; + stlc2690_close(user); +} + +static int stlc2690_reset(struct cg2900_user_data *user) +{ + struct list_head *cursor, *next; + struct stlc2690_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_reset: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_info(user->dev, "stlc2690_reset\n"); + + BUG_ON(!main_info); + + mutex_lock(&main_info->man_mutex); + + dev_dbg(user->dev, "New main_state: CORE_RESETING\n"); + info->main_state = STLC2690_RESETING; + + chip_shutdown(user); + + /* + * Inform all opened channels about the reset and free the user devices + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + list_del(cursor); + tmp->user->opened = false; + tmp->user->reset_cb(tmp->user); + kfree(tmp); + } + + /* Reset finished. We are now idle until first channel is opened */ + dev_dbg(user->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + + mutex_unlock(&main_info->man_mutex); + + /* + * Send wake-up since this might have been called from a failed boot. + * No harm done if it is a STLC2690 chip user who called. + */ + wake_up_all(&main_wait_queue); + + return 0; +} + +static struct sk_buff *stlc2690_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + dev_dbg(MAIN_DEV, "stlc2690_alloc_skb size %d bytes\n", size); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CG2900_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, CG2900_SKB_RESERVE); + + return skb; +} + +static int stlc2690_write(struct cg2900_user_data *user, struct sk_buff *skb) +{ + int err = 0; + u8 *h4_header; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_write: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!skb) { + dev_err(user->dev, "stlc2690_write with no sk_buffer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_dbg(user->dev, "stlc2690_write length %d bytes\n", skb->len); + + if (!user->opened) { + dev_err(user->dev, + "Trying to transmit data on a closed channel\n"); + return -EACCES; + } + + /* + * Move the data pointer to the H:4 header position and + * store the H4 header. + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = (u8)user->h4_channel; + cg2900_tx_to_chip(user, info->logger, skb); + + return err; +} + +static int stlc2690_no_write(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + dev_err(user->dev, "Not allowed to send on this channel\n"); + return -EPERM; +} + +static bool stlc2690_get_local_revision(struct cg2900_user_data *user, + struct cg2900_rev_data *rev_data) +{ + struct cg2900_chip_dev *dev; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "stlc2690_get_local_revision: Calling with " + "NULL pointer\n"); + return false; + } + + if (!rev_data) { + dev_err(user->dev, "Calling with rev_data NULL\n"); + return false; + } + + dev = cg2900_get_prv(user); + + rev_data->revision = dev->chip.hci_revision; + rev_data->sub_version = dev->chip.hci_sub_version; + + return true; +} + +static struct cg2900_user_data btcmd_data = { + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data btacl_data = { + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data btevt_data = { + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data hci_logger_data = { + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = stlc2690_no_write, + .open = stlc2690_hci_log_open, + .close = stlc2690_hci_log_close, +}; +static struct cg2900_user_data core_data = { + .h4_channel = CHANNEL_CORE, + .write = stlc2690_no_write, +}; + +static struct mfd_cell stlc2690_devs[] = { + { + .name = "cg2900-btcmd", + .mfd_data = &btcmd_data, + }, + { + .name = "cg2900-btacl", + .mfd_data = &btacl_data, + }, + { + .name = "cg2900-btevt", + .mfd_data = &btevt_data, + }, + { + .name = "cg2900-hcilogger", + .mfd_data = &hci_logger_data, + }, + { + .name = "cg2900-core", + .mfd_data = &core_data, + }, +}; + +static struct cg2900_user_data char_btcmd_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_CMD, + }, + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data char_btacl_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_ACL, + }, + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data char_btevt_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_EVT, + }, + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data char_hci_logger_data = { + .channel_data = { + .char_dev_name = STLC2690_HCI_LOGGER, + }, + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = stlc2690_no_write, + .open = stlc2690_hci_log_open, + .close = stlc2690_hci_log_close, +}; +static struct cg2900_user_data char_core_data = { + .channel_data = { + .char_dev_name = STLC2690_CORE, + }, + .h4_channel = CHANNEL_CORE, + .write = stlc2690_no_write, +}; + +static struct mfd_cell stlc2690_char_devs[] = { + { + .name = "cg2900-chardev", + .id = 0, + .mfd_data = &char_btcmd_data, + }, + { + .name = "cg2900-chardev", + .id = 1, + .mfd_data = &char_btacl_data, + }, + { + .name = "cg2900-chardev", + .id = 2, + .mfd_data = &char_btevt_data, + }, + { + .name = "cg2900-chardev", + .id = 7, + .mfd_data = &char_hci_logger_data, + }, + { + .name = "cg2900-chardev", + .id = 8, + .mfd_data = &char_core_data, + }, +}; + +/** + * set_plat_data() - Initializes data for an MFD cell. + * @cell: MFD cell. + * @dev: Current chip. + * + * Sets each callback to default function unless already set. + */ +static void set_plat_data(struct mfd_cell *cell, struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user = cell->mfd_data; + + if (!user->open) + user->open = stlc2690_open; + if (!user->close) + user->close = stlc2690_close; + if (!user->reset) + user->reset = stlc2690_reset; + if (!user->alloc_skb) + user->alloc_skb = stlc2690_alloc_skb; + if (!user->write) + user->write = stlc2690_write; + if (!user->get_local_revision) + user->get_local_revision = stlc2690_get_local_revision; + + cg2900_set_prv(user, dev); +} + +/** + * check_chip_support() - Checks if connected chip is handled by this driver. + * @dev: Chip info structure. + * + * If supported return true and fill in @callbacks. + * + * Returns: + * true if chip is handled by this driver. + * false otherwise. + */ +static bool check_chip_support(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct stlc2690_chip_info *info; + int i; + int err; + + dev_dbg(dev->dev, "check_chip_support\n"); + + /* + * Check if this is a STLC2690 revision. + * We do not care about the sub-version at the moment. Change this if + * necessary. + */ + if (dev->chip.manufacturer != STLC2690_SUPP_MANUFACTURER || + dev->chip.hci_revision < STLC2690_SUPP_REVISION_MIN || + dev->chip.hci_revision > STLC2690_SUPP_REVISION_MAX) { + dev_dbg(dev->dev, "Chip not supported by STLC2690 driver\n" + "\tMan: 0x%02X\n" + "\tRev: 0x%04X\n" + "\tSub: 0x%04X\n", + dev->chip.manufacturer, dev->chip.hci_revision, + dev->chip.hci_sub_version); + return false; + } + + /* Store needed data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info struct\n"); + return false; + } + + /* Initialize all variables */ + INIT_LIST_HEAD(&info->open_channels); + spin_lock_init(&info->rw_lock); + info->chip_dev = dev; + + info->wq = create_singlethread_workqueue(WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + goto err_handling_free_info; + } + + info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->patch_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffer for patch file\n"); + goto err_handling_destroy_wq; + } + + info->settings_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->settings_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffers settings file\n"); + goto err_handling_free_patch_name; + } + + dev->c_data = info; + /* Set the callbacks */ + dev->c_cb.data_from_chip = data_from_chip; + dev->c_cb.chip_removed = chip_removed, + info->chip_dev = dev; + + mutex_lock(&main_info->man_mutex); + + pf_data = dev_get_platdata(dev->dev); + btcmd_data.channel_data.bt_bus = pf_data->bus; + btacl_data.channel_data.bt_bus = pf_data->bus; + btevt_data.channel_data.bt_bus = pf_data->bus; + + for (i = 0; i < ARRAY_SIZE(stlc2690_devs); i++) + set_plat_data(&stlc2690_devs[i], dev); + for (i = 0; i < ARRAY_SIZE(stlc2690_char_devs); i++) + set_plat_data(&stlc2690_char_devs[i], dev); + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, stlc2690_devs, + ARRAY_SIZE(stlc2690_devs), NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add stlc2690_devs (%d)\n", err); + goto err_handling_free_settings_name; + } + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + stlc2690_char_devs, + ARRAY_SIZE(stlc2690_char_devs), NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add stlc2690_char_devs (%d)\n", + err); + goto err_handling_remove_devs; + } + + main_info->cell_base_id += 30; + mutex_unlock(&main_info->man_mutex); + + dev_info(dev->dev, "Chip supported by the STLC2690 chip driver\n"); + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(dev->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + + return true; + +err_handling_remove_devs: + mfd_remove_devices(dev->dev); +err_handling_free_settings_name: + kfree(info->settings_file_name); +err_handling_free_patch_name: + kfree(info->patch_file_name); +err_handling_destroy_wq: + destroy_workqueue(info->wq); +err_handling_free_info: + kfree(info); + return false; +} + +static struct cg2900_id_callbacks chip_support_callbacks = { + .check_chip_support = check_chip_support, +}; + +/** + * stlc2690_chip_probe() - Initialize STLC2690 chip handler resources. + * @pdev: Platform device. + * + * This function initializes the STLC2690 driver, then registers to + * the CG2900 Core. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by cg2900_register_chip_driver. + */ +static int __devinit stlc2690_chip_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "stlc2690_chip_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_ATOMIC); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + + err = cg2900_register_chip_driver(&chip_support_callbacks); + if (err) { + dev_err(&pdev->dev, + "Couldn't register chip driver (%d)\n", err); + goto error_handling; + } + + dev_info(&pdev->dev, "STLC2690 chip driver started\n"); + + return 0; + +error_handling: + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return err; +} + +/** + * stlc2690_chip_remove() - Release STLC2690 chip handler resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit stlc2690_chip_remove(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "STLC2690 chip driver removed\n"); + + cg2900_deregister_chip_driver(&chip_support_callbacks); + + if (!main_info) + return 0; + + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return 0; +} + +static struct platform_driver stlc2690_chip_driver = { + .driver = { + .name = "stlc2690-chip", + .owner = THIS_MODULE, + }, + .probe = stlc2690_chip_probe, + .remove = __devexit_p(stlc2690_chip_remove), +}; + +/** + * stlc2690_chip_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init stlc2690_chip_init(void) +{ + pr_debug("stlc2690_chip_init"); + return platform_driver_register(&stlc2690_chip_driver); +} + +/** + * stlc2690_chip_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit stlc2690_chip_exit(void) +{ + pr_debug("stlc2690_chip_exit"); + platform_driver_unregister(&stlc2690_chip_driver); +} + +module_init(stlc2690_chip_init); +module_exit(stlc2690_chip_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux STLC2690 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/stlc2690_chip.h b/drivers/staging/cg2900/mfd/stlc2690_chip.h new file mode 100644 index 00000000000..d14e7737636 --- /dev/null +++ b/drivers/staging/cg2900/mfd/stlc2690_chip.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller. + */ + +#ifndef _STLC2690_CHIP_H_ +#define _STLC2690_CHIP_H_ + +/* Supported chips */ +#define STLC2690_SUPP_MANUFACTURER 0x30 +#define STLC2690_SUPP_REVISION_MIN 0x0500 +#define STLC2690_SUPP_REVISION_MAX 0x06FF + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +/* BT VS Store In FS command */ +#define STLC2690_BT_OP_VS_STORE_IN_FS 0xFC22 +struct bt_vs_store_in_fs_cmd { + __le16 opcode; + __u8 plen; + __u8 user_id; + __u8 len; + __u8 data[]; +} __packed; + +/* BT VS Write File Block command */ +#define STLC2690_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E +struct bt_vs_write_file_block_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 data[]; +} __packed; + +/* User ID for storing BD address in chip using Store_In_FS command */ +#define STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE + +#endif /* _STLC2690_CHIP_H_ */ -- cgit v1.2.3 From e96e0a0f9f4881c6b98221a582c59bb6f254276e Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 20 Apr 2011 08:46:01 +0200 Subject: cg2900: Merge 35 changes to V4L headers Add changes to V4L needed by LTP test. Signed-off-by: Robert Marklund --- include/linux/videodev2.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h index 8a4c309d234..6904507d860 100644 --- a/include/linux/videodev2.h +++ b/include/linux/videodev2.h @@ -1359,6 +1359,32 @@ enum v4l2_mpeg_cx2341x_video_median_filter_type { #define V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP (V4L2_CID_MPEG_CX2341X_BASE+10) #define V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS (V4L2_CID_MPEG_CX2341X_BASE+11) +/* Private Base control IDs specific to the CG2900 FM driver as defined by V4L2 */ +#define V4L2_CID_CG2900_RADIO_PRIVATE_BASE (V4L2_CID_PRIVATE_BASE | 0x1000) +#define V4L2_CID_CG2900_RADIO_BANDSCAN (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+1) +enum v4l2_cg2900_radio_bandscan { + V4L2_CG2900_RADIO_BANDSCAN_START = 0, + V4L2_CG2900_RADIO_BANDSCAN_STOP = 1, +}; +#define V4L2_CID_CG2900_RADIO_BANDSCAN_GET_RESULTS (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+2) +#define V4L2_CID_CG2900_RADIO_BLOCKSCAN_START (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+3) +#define V4L2_CID_CG2900_RADIO_BLOCKSCAN_GET_RESULTS (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+4) +#define V4L2_CID_CG2900_RADIO_CHIP_STATE (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+5) +enum v4l2_cg2900_radio_chip_state { + V4L2_CG2900_RADIO_STANDBY = 0, + V4L2_CG2900_RADIO_POWERUP = 1, +}; +#define V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+6) +#define V4L2_CID_CG2900_RADIO_SELECT_ANTENNA (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+7) +enum v4l2_cg2900_radio_select_antenna { + V4L2_CG2900_RADIO_EMBEDDED_ANTENNA = 0, + V4L2_CG2900_RADIO_WIRED_ANTENNA = 1, +}; +#define V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+8) +#define V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_GET_RESULT (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+9) +#define V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+10) +#define V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+11) + /* Camera class control IDs */ #define V4L2_CID_CAMERA_CLASS_BASE (V4L2_CTRL_CLASS_CAMERA | 0x900) #define V4L2_CID_CAMERA_CLASS (V4L2_CTRL_CLASS_CAMERA | 1) -- cgit v1.2.3 From b63e9d34b862618107cc9ad13326cc7f421656fe Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Tue, 29 Mar 2011 16:29:14 -0600 Subject: Adding configuration options for cg2900. Signed-off-by: Mathieu Poirier --- arch/arm/configs/u8500_defconfig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 97d31a4663d..66a2481d62b 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -97,6 +97,13 @@ CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y +CONFIG_CG2900=y +CONFIG_CG2900_CHIP=y +CONFIG_STLC2690_CHIP=y +CONFIG_CG2900_UART=y +CONFIG_CG2900_AUDIO=y +CONFIG_CG2900_TEST=y +CONFIG_BT_CG2900=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y @@ -109,6 +116,8 @@ CONFIG_CONFIGFS_FS=m # CONFIG_MISC_FILESYSTEMS is not set CONFIG_NFS_FS=y CONFIG_ROOT_NFS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_BLKDEV_PARTITION=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y CONFIG_MAGIC_SYSRQ=y -- cgit v1.2.3 From 4a8566c62502e2cfc6eb2ffb1e900a74c5b7a1d5 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Thu, 6 May 2010 09:10:53 +0200 Subject: fix cache coherence issues with bounce buffers [Rabin VINCENT] This is for I$-D$ coherence issues when bounce buffers are used for the MMC driver and code is executed from a file system on eMMC. IMO a better fix is to either change flush_kernel_dcache_page() to flush the D$ even on VIPT non-aliasing caches or to replace the flush_kernel_dcache_page() in lib/scatterlist.c with flush_dcache_page(). Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Lee Jones --- fs/mpage.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/mpage.c b/fs/mpage.c index fdfae9fa98c..cc782f9e68d 100644 --- a/fs/mpage.c +++ b/fs/mpage.c @@ -53,6 +53,8 @@ static void mpage_end_io(struct bio *bio, int err) prefetchw(&bvec->bv_page->flags); if (bio_data_dir(bio) == READ) { if (uptodate) { + /* FIXME: fix to solve cache coherence issues. */ + flush_dcache_page(page); SetPageUptodate(page); } else { ClearPageUptodate(page); -- cgit v1.2.3 From 275a1f0d45bbd9ad7630577b91a5ebcd01e2650d Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 24 Mar 2011 09:56:19 +0000 Subject: FS: FAT: Fix build warning Ported from Jonas Aaberg's 2.6.35 GLK commit for the 2.6.38 GLK Signed-off-by: Jonas Aaberg Change-Id: I9f86641ad50f050271b30ae2f03906f831ee614d Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17148 Reviewed-by: Sebastian RASMUSSEN Signed-off-by: Lee Jones --- fs/fat/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fat/dir.c b/fs/fat/dir.c index 4ad64732cbc..d08e558c8eb 100644 --- a/fs/fat/dir.c +++ b/fs/fat/dir.c @@ -1231,7 +1231,7 @@ int fat_add_entries(struct inode *dir, void *slots, int nr_slots, struct super_block *sb = dir->i_sb; struct msdos_sb_info *sbi = MSDOS_SB(sb); struct buffer_head *bh, *prev, *bhs[3]; /* 32*slots (672bytes) */ - struct msdos_dir_entry *de; + struct msdos_dir_entry *de = NULL; int err, free_slots, i, nr_bhs; loff_t pos, i_pos; -- cgit v1.2.3 From c03fa3ead24a37919f84213db893e7b80e2aefdb Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Fri, 25 Mar 2011 15:51:47 -0600 Subject: Porting architecture portion of av8100 driver from .35 to .38 Signed-off-by: Mathieu Poirier --- arch/arm/mach-ux500/Makefile | 6 +- arch/arm/mach-ux500/board-mop500-mcde.c | 671 ++++++++++++++++++++++++++ arch/arm/mach-ux500/mcde.c | 102 ++++ arch/arm/mach-ux500/pins.h | 43 ++ include/video/av8100.h | 561 +++++++++++++++++++++ include/video/hdmi.h | 176 +++++++ include/video/mcde.h | 434 +++++++++++++++++ include/video/mcde_display-ab8500.h | 23 + include/video/mcde_display-av8100.h | 38 ++ include/video/mcde_display-generic_dsi.h | 35 ++ include/video/mcde_display-sony_sy35560_dsi.h | 45 ++ include/video/mcde_display-vuib500-dpi.h | 31 ++ include/video/mcde_display.h | 139 ++++++ include/video/mcde_dss.h | 79 +++ include/video/mcde_fb.h | 65 +++ 15 files changed, 2445 insertions(+), 3 deletions(-) create mode 100644 arch/arm/mach-ux500/board-mop500-mcde.c create mode 100644 arch/arm/mach-ux500/mcde.c create mode 100644 arch/arm/mach-ux500/pins.h create mode 100644 include/video/av8100.h create mode 100644 include/video/hdmi.h create mode 100644 include/video/mcde.h create mode 100644 include/video/mcde_display-ab8500.h create mode 100644 include/video/mcde_display-av8100.h create mode 100644 include/video/mcde_display-generic_dsi.h create mode 100644 include/video/mcde_display-sony_sy35560_dsi.h create mode 100644 include/video/mcde_display-vuib500-dpi.h create mode 100644 include/video/mcde_display.h create mode 100644 include/video/mcde_dss.h create mode 100644 include/video/mcde_fb.h diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 03525cc7be9..703e947a3c9 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -16,9 +16,9 @@ obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o \ obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \ board-mop500-regulators.o \ board-mop500-uib.o board-mop500-stuib.o \ - board-mop500-u8500uib.o \ - board-mop500-pins.o \ - board-mop500-msp.o board-mop500-bm.o + board-mop500-u8500uib.o board-mop500-pins.o \ + board-mop500-mcde.o + board-mop500-msp.o board-mop500-bm.o \ obj-$(CONFIG_MACH_U5500) += board-u5500.o board-u5500-sdi.o obj-$(CONFIG_SMP) += platsmp.o headsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o diff --git a/arch/arm/mach-ux500/board-mop500-mcde.c b/arch/arm/mach-ux500/board-mop500-mcde.c new file mode 100644 index 00000000000..59bc22c01d1 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-mcde.c @@ -0,0 +1,671 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * Author: Marcus Lorentzon + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ +#include +#include +#include +#include +#include +#include