diff --git a/drivers/Kconfig b/drivers/Kconfig index f4076d9..77428e4 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -84,6 +84,8 @@ source "drivers/edac/Kconfig" source "drivers/rtc/Kconfig" +source "drivers/regulator/Kconfig" + source "drivers/dma/Kconfig" source "drivers/dca/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 8cb37e3..b52a889 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -84,6 +84,7 @@ obj-y += firmware/ obj-$(CONFIG_CRYPTO) += crypto/ obj-$(CONFIG_SUPERH) += sh/ obj-$(CONFIG_GENERIC_TIME) += clocksource/ +bj-$(CONFIG_REGULATOR) += regulator/ obj-$(CONFIG_DMA_ENGINE) += dma/ obj-$(CONFIG_DCA) += dca/ obj-$(CONFIG_HID) += hid/ diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ec568fa..e397df4 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -38,6 +38,12 @@ config LEDS_SPITZ help This option enables support for the LEDs on Sharp Zaurus SL-Cxx00 series (C1000, C3000, C3100). + +config LEDS_WM8350 + tristate "LED Support for the WM8350" + depends on LEDS_CLASS && REGULATOR_WM8350 + help + This option enables support for the LEDs driven by the WM8350. config LEDS_IXP4XX tristate "LED Support for GPIO connected LEDs on IXP4XX processors" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index a60de1b..8d97961 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o obj-$(CONFIG_LEDS_CORGI) += leds-corgi.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_SPITZ) += leds-spitz.o +obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_IXP4XX) += leds-ixp4xx-gpio.o obj-$(CONFIG_LEDS_TOSA) += leds-tosa.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c new file mode 100644 index 0000000..dd43a57 --- /dev/null +++ b/drivers/leds/leds-wm8350.c @@ -0,0 +1,279 @@ +/* + * LED driver for WM8350 driven LEDS. + * + * Copyright(C) 2007 Wolfson Microelectronics PLC. + * + * 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 + +#define WM8350_LED_VERSION "0.2" + +struct wm8350_led { + struct work_struct work; + struct mutex mutex; + struct regulator *isink; + struct regulator *dcdc; + struct notifier_block notifier; + enum led_brightness value; + struct led_classdev cdev; + int retries; + int half_value; + int full_value; + int current_value; +}; +#define to_wm8350_led(led_cdev) \ + container_of(led_cdev, struct wm8350_led, cdev) + +static void led_work(struct work_struct *work) +{ + struct wm8350_led *led = + container_of(work, struct wm8350_led, work); + + mutex_lock(&led->mutex); + switch (led->value) { + case LED_OFF: + led->current_value = 0; + break; + case LED_HALF: + led->retries = 0; + led->current_value = led->half_value; + break; + case LED_FULL: + led->retries = 0; + led->current_value = led->full_value; + break; + } + + regulator_set_current(led->isink, led->current_value); + mutex_unlock(&led->mutex); +} + +static void wm8350_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct wm8350_led *led = to_wm8350_led(led_cdev); + + mutex_lock(&led->mutex); + led->value = value; + mutex_unlock(&led->mutex); + schedule_work(&led->work); +} + +static int wm8350_led_notifier(struct notifier_block *self, + unsigned long event, void *data) +{ + struct wm8350_led *led = + container_of(self, struct wm8350_led, notifier); + + if (event & REGULATOR_EVENT_UNDER_VOLTAGE) + printk(KERN_ERR "wm8350: LED DCDC undervoltage\n"); + if (event & REGULATOR_EVENT_REGULATION_OUT) + printk(KERN_ERR "wm8350: LED ISINK out of regulation\n"); + + mutex_lock(&led->mutex); + if (led->retries) { + led->retries--; + regulator_set_current(led->isink, led->value); + regulator_enable(led->isink); + } else { + printk(KERN_ERR "wm8350: LED regulation retry failure - disabled\n"); + led->current_value = 0; + regulator_disable(led->isink); + } + mutex_unlock(&led->mutex); + return 0; +} + +#ifdef CONFIG_PM +static int wm8350_led_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct wm8350_led *led = + (struct wm8350_led *)platform_get_drvdata(pdev); + + led_classdev_suspend(&led->cdev); + return 0; +} + +static int wm8350_led_resume(struct platform_device *pdev) +{ + struct wm8350_led *led = + (struct wm8350_led *)platform_get_drvdata(pdev); + + led_classdev_resume(&led->cdev); + return 0; +} +#endif + +static void wm8350_led_shutdown(struct platform_device *pdev) +{ + struct wm8350_led *led = + (struct wm8350_led *)platform_get_drvdata(pdev); + + mutex_lock(&led->mutex); + led->value = LED_OFF; + regulator_disable(led->isink); + mutex_unlock(&led->mutex); +} + +static int wm8350_led_probe(struct platform_device *pdev) +{ + struct regulator *isink, *dcdc; + struct wm8350_led *led; + struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; + struct wm8350_pmic *pmic; + int ret; + + printk(KERN_INFO "wm8350: LED driver %s\n", WM8350_LED_VERSION); + + if (pdata == NULL) { + printk(KERN_ERR "%s: no platform data\n", __func__); + return -ENODEV; + } + if (pdata->isink != WM8350_ISINK_A && pdata->isink != WM8350_ISINK_B) { + printk(KERN_ERR "%s: invalid ISINK\n", __func__); + return -EINVAL; + } + if (pdata->dcdc != WM8350_DCDC_2 && pdata->dcdc != WM8350_DCDC_5) { + printk(KERN_ERR "%s: invalid DCDC\n", __func__); + return -EINVAL; + } + + printk(KERN_INFO "wm8350: LED using %s and %s\n", + pdata->isink == WM8350_ISINK_A ? "ISINKA" : "ISINKB", + pdata->dcdc == WM8350_DCDC_2 ? "DCDC2" : "DCDC5"); + + isink = regulator_get(&pdev->dev, + pdata->isink == WM8350_ISINK_A ? "ISINKA" : "ISINKB"); + if (IS_ERR(isink) || isink == NULL) { + printk(KERN_ERR "%s: cant get ISINK\n", __func__); + return PTR_ERR(isink); + } + + dcdc = regulator_get(&pdev->dev, + pdata->dcdc == WM8350_DCDC_2 ? "DCDC2" : "DCDC5"); + if (IS_ERR(dcdc) || dcdc == NULL) { + printk(KERN_ERR "%s: cant get DCDC\n", __func__); + regulator_put(isink); + return PTR_ERR(dcdc); + } + + led = kzalloc(sizeof(*led), GFP_KERNEL); + if (led == NULL) { + regulator_put(isink); + regulator_put(dcdc); + return -ENOMEM; + } + + led->retries = pdata->retries; + led->half_value = pdata->half_value; + led->full_value = pdata->full_value; + led->cdev.brightness_set = wm8350_led_set; + led->cdev.default_trigger = (char*) pdata->default_trigger; + led->cdev.name = pdata->name; + led->isink = isink; + led->dcdc = dcdc; + pmic = regulator_get_drvdata(led->isink); + + mutex_init(&led->mutex); + INIT_WORK(&led->work, led_work); + led->value = LED_OFF; + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) { + regulator_put(isink); + regulator_put(dcdc); + kfree(led); + return ret; + } + + led->notifier.notifier_call = wm8350_led_notifier; + regulator_register_client(isink, &led->notifier); + regulator_register_client(dcdc, &led->notifier); + + /* WM8350 ISINK & DCDC setup */ + if (pdata->isink == WM8350_ISINK_A) + pmic->isink_A_dcdc = pdata->dcdc; + else + pmic->isink_B_dcdc = pdata->dcdc; + + wm8350_dcdc_set_slot(pmic, pdata->dcdc, 1, 1, + pdata->dcdc == WM8350_DCDC_2 ? + WM8350_DC2_ERRACT_SHUTDOWN_CONV : + WM8350_DC5_ERRACT_SHUTDOWN_CONV); + + wm8350_isink_set_flash(pmic, pdata->isink, + WM8350_ISINK_FLASH_DISABLE, + WM8350_ISINK_FLASH_TRIG_BIT, + WM8350_ISINK_FLASH_DUR_32MS, + WM8350_ISINK_FLASH_ON_INSTANT, + WM8350_ISINK_FLASH_OFF_INSTANT, + WM8350_ISINK_FLASH_MODE_EN); + + wm8350_dcdc25_set_mode(pmic, pdata->dcdc, + WM8350_ISINK_MODE_BOOST, WM8350_ISINK_ILIM_NORMAL, + pdata->voltage_ramp, pdata->isink == WM8350_ISINK_A ? + WM8350_DC5_FBSRC_ISINKA : WM8350_DC5_FBSRC_ISINKB); + + regulator_set_current(isink, 0); + regulator_enable(led->isink); + return 0; +} + +static int wm8350_led_remove(struct platform_device *pdev) +{ + struct wm8350_led *led = + (struct wm8350_led *)platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + schedule_work(&led->work); + flush_scheduled_work(); + regulator_set_current(led->isink, 0); + regulator_disable(led->isink); + regulator_unregister_client(led->dcdc, &led->notifier); + regulator_unregister_client(led->isink, &led->notifier); + regulator_put(led->dcdc); + regulator_put(led->isink); + kfree(led); + return 0; +} + +struct platform_driver wm8350_led_driver = { + .driver = { + .name = "wm8350-led", + .owner = THIS_MODULE, + }, + .probe = wm8350_led_probe, + .remove = wm8350_led_remove, + .shutdown = wm8350_led_shutdown, + .suspend = wm8350_led_suspend, + .resume = wm8350_led_resume, +}; + +static int __devinit wm8350_led_init(void) +{ + return platform_driver_register(&wm8350_led_driver); +} + +static void wm8350_led_exit(void) +{ + platform_driver_unregister(&wm8350_led_driver); +} + +module_init(wm8350_led_init); +module_exit(wm8350_led_exit); + +MODULE_AUTHOR("Liam Girdwood "); +MODULE_DESCRIPTION("WM8350 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 58c806e..1371c52 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -22,6 +22,13 @@ config PDA_POWER one or two external power supplies (AC/USB) connected to main and backup batteries, and optional builtin charger. +config BATTERY_WM8350 + tristate "WM8350 Battery" + depends on REGULATOR_WM8350 + help + Say Y here to enable support for battery status on mx31ads with + wm8350. + config APM_POWER tristate "APM emulation for class batteries" depends on APM_EMULATION diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 6413ded..313cda9 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_POWER_SUPPLY) += power_supply.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_BATTERY_WM8350) += wm8350_power.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c new file mode 100644 index 0000000..0071394 --- /dev/null +++ b/drivers/power/wm8350_power.c @@ -0,0 +1,546 @@ +/* + * Battery driver for wm8350 PMIC + * + * Copyright © 2007 Wolfson Microelectronics PLC. + * + * Based on OLCP Battery Driver + * + * Copyright © 2006 David Woodhouse + * + * 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 + +#define WM8350_POWER_VERSION "0.4" + +#define WM8350_BATT_SUPPLY 1 +#define WM8350_USB_SUPPLY 2 +#define WM8350_LINE_SUPPLY 4 + +static int wm8350_charger_config (struct wm8350 *wm8350, + struct wm8350_charger_policy *policy) +{ + u16 reg; + + /* make sure the end of charge (eoc) current is not greater or equal + * to the actual charge current. */ + if (policy->eoc_mA >= policy->trickle_charge_mA) { + printk(KERN_ERR "%s: eoc greater than charge\n", __func__); + return -EINVAL; + } + + wm8350_reg_unlock(wm8350); + reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1) + & WM8350_CHG_ENA_R168; + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + reg | policy->eoc_mA | policy->trickle_start_mV); + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, + policy->charge_mV | policy->trickle_charge_mA | + policy->fast_limit_mA | policy->charge_timeout); + wm8350_reg_lock(wm8350); + return 0; +} + +int wm8350_charger_enable(struct wm8350_power *power, int enable) +{ + struct wm8350 *wm8350 = to_wm8350_from_power(power); + struct wm8350_charger_policy *policy = &power->policy; + + mutex_lock(&policy->mutex); + if (enable) { + /* load policy */ + wm8350_charger_config(wm8350, policy); + /* enable */ + policy->enable = 1; + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA); + wm8350_reg_lock(wm8350); + } else { + /* disable */ + policy->enable = 0; + wm8350_reg_unlock(wm8350); + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA); + wm8350_reg_lock(wm8350); + } + mutex_unlock(&policy->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_charger_enable); + +int wm8350_fast_charger_enable(struct wm8350_power *power, int enable) +{ + struct wm8350_charger_policy *policy = &power->policy; + + mutex_lock(&policy->mutex); + if (enable) + /* enable */ + policy->fast_enable = 1; + else + /* disable */ + policy->fast_enable = 0; + mutex_unlock(&policy->mutex); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_fast_charger_enable); + +static int wm8350_get_supplies(struct wm8350 *wm8350) +{ + u16 sm, ov, co, chrg; + int supplies=0; + + sm = wm8350_reg_read(wm8350, 233); + ov = wm8350_reg_read(wm8350, 227); + co = wm8350_reg_read(wm8350, 231); + chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + + /* USB_SM */ + sm >>= 8; + sm &= 0x7; + + /* USB_LIMIT_OVRDE */ + ov >>= 10; + ov &= 0x01; + + /* WALL_FB_OVRDE */ + co >>= 14; + co &= 0x01; + + /* CHG_ISEL */ + chrg &= WM8350_CHG_ISEL_MASK; + + if (((sm == 0x01) || (sm == 0x05) || (sm == 0x07)) && !ov) + supplies = WM8350_USB_SUPPLY; + else if (((sm == 0x01) || (sm == 0x05) || (sm == 0x07)) && ov && + (chrg == 0x00)) + supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY; + else if (co) + supplies = WM8350_LINE_SUPPLY; + else + supplies = WM8350_BATT_SUPPLY; + + return supplies; +} + +static int wm8350_batt_status(struct wm8350 *wm8350) +{ + u16 chrg; + + chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + chrg &= WM8350_CHG_STS_MASK; + chrg >>= 12; + + switch (chrg) { + case 0x00: + return POWER_SUPPLY_STATUS_DISCHARGING; + case 0x01: + case 0x02: + return POWER_SUPPLY_STATUS_CHARGING; + } + return POWER_SUPPLY_STATUS_UNKNOWN; +} + +static ssize_t supply_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350_power *power = to_wm8350_power_device(dev); + struct wm8350 *wm8350 = to_wm8350_from_power(power); + char *supply; + int state; + + state = wm8350_get_supplies(wm8350); + switch (state) { + case WM8350_BATT_SUPPLY: + supply = "Battery"; + break; + case WM8350_USB_SUPPLY: + supply = "USB"; + break; + case WM8350_LINE_SUPPLY: + supply = "Line"; + break; + case WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY: + supply ="Battery and USB"; + break; + default: + return 0; + } + + return sprintf(buf, "%s\n", supply); +} +static DEVICE_ATTR(supply_state, 0444, supply_state_show, NULL); + +static ssize_t battery_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350_power *power = to_wm8350_power_device(dev); + struct wm8350 *wm8350 = to_wm8350_from_power(power); + char *batt; + int state; + + state = wm8350_batt_status(wm8350); + switch (state) { + case POWER_SUPPLY_STATUS_DISCHARGING: + batt = "Discharging"; + break; + case POWER_SUPPLY_STATUS_CHARGING: + batt = "Charging"; + break; + case POWER_SUPPLY_STATUS_UNKNOWN: + batt = "Unknown"; + break; + default: + return 0; + } + + return sprintf(buf, "%s\n", batt); +} +static DEVICE_ATTR(battery_state, 0444, battery_state_show, NULL); + +static ssize_t charge_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350_power *power = to_wm8350_power_device(dev); + struct wm8350 *wm8350 = to_wm8350_from_power(power); + char *charge; + int state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & + WM8350_CHG_STS_MASK; + switch (state) { + case WM8350_CHG_STS_OFF: + charge = "Charger Off"; + break; + case WM8350_CHG_STS_TRICKLE: + charge = "Trickle Charging"; + break; + case WM8350_CHG_STS_FAST: + charge = "Fast Charging"; + break; + default: + return 0; + } + + return sprintf(buf, "%s\n", charge); +} +static DEVICE_ATTR(charge_state, 0444, charge_state_show, NULL); + +static void wm8350_charger_handler(struct wm8350 *wm8350, int irq, void *data) +{ + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = &power->policy; + + switch (irq) { + case WM8350_IRQ_CHG_BAT_HOT: + printk(KERN_ERR "wm8350-power: battery too hot\n"); + break; + case WM8350_IRQ_CHG_BAT_COLD: + printk(KERN_ERR "wm8350-power: battery too cold\n"); + break; + case WM8350_IRQ_CHG_BAT_FAIL: + printk(KERN_ERR "wm8350-power: battery failed\n"); + break; + case WM8350_IRQ_CHG_TO: + printk(KERN_INFO "wm8350-power: charger timeout\n"); + break; + case WM8350_IRQ_CHG_END: + printk(KERN_INFO "wm8350-power: charger stopped\n"); + break; + case WM8350_IRQ_CHG_START: + printk(KERN_INFO "wm8350-power: charger started\n"); + break; + case WM8350_IRQ_CHG_FAST_RDY: + mutex_lock(&policy->mutex); + if (policy->fast_enable) { + printk(KERN_INFO "wm8350-power: fast charger ready\n"); + wm8350_charger_config(wm8350, policy); + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + WM8350_CHG_FAST); + wm8350_reg_lock(wm8350); + } else + printk(KERN_INFO "wm8350-power: fast charger ready but disabled\n"); + mutex_unlock(&policy->mutex); + break; + case WM8350_IRQ_CHG_VBATT_LT_3P9: + printk(KERN_WARNING "wm8350-power: battery < 3.9V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_3P1: + printk(KERN_WARNING "wm8350-power: battery < 3.1V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_2P85: + printk(KERN_WARNING "wm8350-power: battery < 2.85V\n"); + break; + default: + printk(KERN_ERR "wm8350-power: irq %d don't care\n", irq); + } +} + +/********************************************************************* + * AC Power + *********************************************************************/ +static int wm8350_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350_power *wm8350_power = + to_wm8350_power_device(psy->dev->parent); + struct wm8350 *wm8350 = to_wm8350_from_power(wm8350_power); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_LINE_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_line_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * USB Power + *********************************************************************/ +static int wm8350_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350_power *wm8350_power = + to_wm8350_power_device(psy->dev->parent); + struct wm8350 *wm8350 = to_wm8350_from_power(wm8350_power); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_USB_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_usb_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +static int wm8350_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350_power *wm8350_power = + to_wm8350_power_device(psy->dev->parent); + struct wm8350 *wm8350 = to_wm8350_from_power(wm8350_power); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = wm8350_batt_status(wm8350); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_BATT_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_battery_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm8350_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static void free_charger_irq(struct wm8350 *wm8350) +{ + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_TO); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_END); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_START); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); +} + +static int wm8350_power_probe(struct device *dev) +{ + struct wm8350_power *power = to_wm8350_power_device(dev); + struct wm8350 *wm8350 = to_wm8350_from_power(power); + struct power_supply *usb = &power->usb; + struct power_supply *battery = &power->battery; + struct power_supply *ac = &power->ac; + int ret; + + printk(KERN_INFO "wm8350: power driver %s\n", WM8350_POWER_VERSION); + + mutex_init(&power->policy.mutex); + + ac->name = "wm8350-ac"; + ac->type = POWER_SUPPLY_TYPE_MAINS, + ac->properties = wm8350_ac_props, + ac->num_properties = ARRAY_SIZE(wm8350_ac_props), + ac->get_property = wm8350_ac_get_prop, + ret = power_supply_register(dev, ac); + if (ret) + return ret; + + battery->name = "wm8350-battery", + battery->properties = wm8350_bat_props, + battery->num_properties = ARRAY_SIZE(wm8350_bat_props), + battery->get_property = wm8350_bat_get_property, + battery->use_for_apm = 1, + ret = power_supply_register(dev, battery); + if (ret) + goto battery_failed; + + usb->name = "wm8350-usb", + usb->type = POWER_SUPPLY_TYPE_USB, + usb->properties = wm8350_usb_props, + usb->num_properties = ARRAY_SIZE(wm8350_usb_props), + usb->get_property = wm8350_usb_get_prop, + ret = power_supply_register(dev, usb); + if (ret) + goto usb_failed; + + /* register our interest in charger events */ + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_TO); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_END); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_START); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); + + ret = device_create_file(dev, &dev_attr_supply_state); + if (ret < 0) + printk(KERN_WARNING "wm8350: failed to add supply sysfs\n"); + ret = device_create_file(dev, &dev_attr_battery_state); + if (ret < 0) + printk(KERN_WARNING "wm8350: failed to add battery sysfs\n"); + ret = device_create_file(dev, &dev_attr_charge_state); + if (ret < 0) + printk(KERN_WARNING "wm8350: failed to add charge sysfs\n"); + ret = 0; + goto success; + +usb_failed: + power_supply_unregister(battery); +battery_failed: + power_supply_unregister(ac); +success: + return ret; +} + +static int __exit wm8350_power_remove(struct device *dev) +{ + struct wm8350_power *power = to_wm8350_power_device(dev); + struct wm8350 *wm8350 = to_wm8350_from_power(power); + + free_charger_irq(wm8350); + device_remove_file(dev, &dev_attr_supply_state); + device_remove_file(dev, &dev_attr_battery_state); + device_remove_file(dev, &dev_attr_charge_state); + power_supply_unregister(&power->battery); + power_supply_unregister(&power->ac); + power_supply_unregister(&power->usb); + return 0; +} + +struct device_driver wm8350_power_driver = { + .name = "wm8350-power", + .bus = &wm8350_bus_type, + .owner = THIS_MODULE, + .probe = wm8350_power_probe, + .remove = wm8350_power_remove, +}; + +static int __init wm8350_bat_init(void) +{ + return driver_register(&wm8350_power_driver); +} + +static void __exit wm8350_bat_exit(void) +{ + driver_unregister(&wm8350_power_driver); +} + +module_init(wm8350_bat_init); +module_exit(wm8350_bat_exit); + +MODULE_AUTHOR("Graeme Gregory + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define REGULATOR_VERSION "0.2" + +/* We need to undef the current macro (from include/asm/current.h) otherwise + * our "current" sysfs entry becomes "(get_current())". + */ +#undef current + +static DEFINE_MUTEX(list_mutex); +static LIST_HEAD(regulator_cdevs); + +/** + * struct regulator_cdev + * + * Voltage / Current regulator class device. One for each regulator. + */ +struct regulator_cdev { + struct regulator_desc *desc; + int use_count; + + struct list_head list; + struct list_head consumer_list; + struct blocking_notifier_head notifier; + struct mutex mutex; + struct module *owner; + struct class_device cdev; + struct regulation_constraints *constraints; + struct regulator_cdev *parent; /* for tree */ + + void *reg_data; /* regulator_cdev data */ + void *vendor; /* regulator_cdev vendor extensions */ +}; +#define to_rcdev(cd) \ + container_of(cd, struct regulator_cdev, cdev) + +/* + * struct regulator + * + * One for each consumer device. + */ +struct regulator { + struct device *dev; + struct list_head list; + int uA_load; + int uV_required; + int enabled; + struct device_attribute dev_attr; + struct regulator_cdev *rcdev; +}; + +static int _regulator_is_enabled(struct regulator_cdev *rcdev); +static int _regulator_disable(struct regulator_cdev *rcdev); +static int _regulator_get_voltage(struct regulator_cdev *rcdev); +static int _regulator_get_current(struct regulator_cdev *rcdev); +static unsigned int _regulator_get_mode(struct regulator_cdev *rcdev); + +static struct regulator *get_regulator_load(struct device *dev) +{ + struct regulator *regulator = NULL; + struct regulator_cdev *rcdev; + + list_for_each_entry(rcdev, ®ulator_cdevs, list) { + list_for_each_entry(regulator, &rcdev->consumer_list, list) { + if (regulator->dev == dev) + return regulator; + } + } + return NULL; +} + +static int regulator_check_voltage(struct regulator_cdev *rcdev, int uV) +{ + if (!rcdev->constraints) { + printk(KERN_ERR "%s: no constraints for %s\n", __func__, + rcdev->desc->name); + return -ENODEV; + } + if (!rcdev->constraints->valid_ops_mask & REGULATOR_CHANGE_VOLTAGE) { + printk(KERN_ERR "%s: operation not allowed for %s\n", + __func__, rcdev->desc->name); + return -EPERM; + } + if (uV > rcdev->constraints->max_uV || + uV < rcdev->constraints->min_uV) { + printk(KERN_ERR "%s: invalid voltage %duV for %s\n", + __func__, uV, rcdev->desc->name); + return -EINVAL; + } + return 0; +} + +static int regulator_check_current(struct regulator_cdev *rcdev, int uA) +{ + if (!rcdev->constraints) { + printk(KERN_ERR "%s: no constraints for %s\n", __func__, + rcdev->desc->name); + return -ENODEV; + } + if (!rcdev->constraints->valid_ops_mask & REGULATOR_CHANGE_CURRENT) { + printk(KERN_ERR "%s: operation not allowed for %s\n", + __func__, rcdev->desc->name); + return -EPERM; + } + if (uA > rcdev->constraints->max_uA || + uA < rcdev->constraints->min_uA) { + printk(KERN_ERR "%s: invalid current %duA for %s\n", + __func__, uA, rcdev->desc->name); + return -EINVAL; + } + return 0; +} + +static int regulator_check_mode(struct regulator_cdev *rcdev, int mode) +{ + if (!rcdev->constraints) { + printk(KERN_ERR "%s: no constraints for %s\n", __func__, + rcdev->desc->name); + return -ENODEV; + } + if (!rcdev->constraints->valid_ops_mask & REGULATOR_CHANGE_MODE) { + printk(KERN_ERR "%s: operation not allowed for %s\n", + __func__, rcdev->desc->name); + return -EPERM; + } + if (!rcdev->constraints->valid_modes_mask & mode) { + printk(KERN_ERR "%s: invalid mode %x for %s\n", + __func__, mode, rcdev->desc->name); + return -EINVAL; + } + return 0; +} + +static int regulator_check_drms(struct regulator_cdev *rcdev) +{ + if (!rcdev->constraints) { + printk(KERN_ERR "%s: no constraints for %s\n", __func__, + rcdev->desc->name); + return -ENODEV; + } + if (!rcdev->constraints->valid_ops_mask & REGULATOR_CHANGE_DRMS) { + printk(KERN_ERR "%s: operation not allowed for %s\n", + __func__, rcdev->desc->name); + return -EPERM; + } + return 0; +} + +static ssize_t dev_load_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator *regulator; + + regulator = get_regulator_load(dev); + if (regulator == NULL) + return 0; + + return sprintf(buf, "%d\n", regulator->uA_load); +} + +static ssize_t regulator_uV_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + + return sprintf(buf, "%d\n", _regulator_get_voltage(rcdev)); +} + +static ssize_t regulator_uA_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + + return sprintf(buf, "%d\n", _regulator_get_current(rcdev)); +} + +static ssize_t regulator_mode_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + int mode = _regulator_get_mode(rcdev); + + switch (mode) { + case REGULATOR_MODE_FAST: + return sprintf(buf, "fast\n"); + case REGULATOR_MODE_NORMAL: + return sprintf(buf, "normal\n"); + case REGULATOR_MODE_IDLE: + return sprintf(buf, "idle\n"); + case REGULATOR_MODE_STANDBY: + return sprintf(buf, "standby\n"); + } + return sprintf(buf, "unknown\n"); +} + +static ssize_t regulator_state_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + int state = _regulator_is_enabled(rcdev); + + if (state > 0) + return sprintf(buf, "enabled\n"); + else if (state == 0) + return sprintf(buf, "disabled\n"); + else + return sprintf(buf, "unknown\n"); +} + +static ssize_t regulator_constraint_uA_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + + if (!rcdev->constraints) + return sprintf(buf, "constraints not defined\n"); + + return sprintf (buf, "%d %d\n", rcdev->constraints->min_uA, + rcdev->constraints->max_uA); +} + +static ssize_t regulator_constraint_uV_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + + if (!rcdev->constraints) + return sprintf(buf, "constraints not defined\n"); + + return sprintf (buf, "%d %d\n", rcdev->constraints->min_uV, + rcdev->constraints->max_uV); +} + +static ssize_t regulator_constraint_modes_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + int count = 0; + + if (!rcdev->constraints) + return sprintf(buf, "constraints not defined\n"); + + if (rcdev->constraints->valid_modes_mask & REGULATOR_MODE_FAST) + count = sprintf (buf, "fast "); + if (rcdev->constraints->valid_modes_mask & REGULATOR_MODE_NORMAL) + count += sprintf (buf + count, "normal "); + if (rcdev->constraints->valid_modes_mask & REGULATOR_MODE_IDLE) + count += sprintf (buf + count, "idle "); + if (rcdev->constraints->valid_modes_mask & REGULATOR_MODE_STANDBY) + count += sprintf (buf + count, "standby"); + count += sprintf(buf + count, "\n"); + return count; +} + +static ssize_t regulator_total_dev_load(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + struct regulator *regulator; + int uA = 0; + + list_for_each_entry(regulator, &rcdev->consumer_list, list) + uA =+ regulator->uA_load; + return sprintf(buf, "%d\n", uA); +} + +static ssize_t regulator_enabled_use_count(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + return sprintf(buf, "%d\n", rcdev->use_count); +} + +static ssize_t regulator_type_show(struct class_device *cdev, char *buf) +{ + struct regulator_cdev *rcdev = to_rcdev(cdev); + + switch (rcdev->desc->type) { + case REGULATOR_VOLTAGE: + return sprintf(buf, "voltage\n"); + case REGULATOR_CURRENT: + return sprintf(buf, "current\n"); + } + return sprintf(buf, "unknown\n"); +} + +static struct class_device_attribute regulator_dev_attrs[] = { + __ATTR(voltage, 0444, regulator_uV_show, NULL), + __ATTR(current, 0444, regulator_uA_show, NULL), + __ATTR(opmode, 0444, regulator_mode_show, NULL), + __ATTR(enabled, 0444, regulator_state_show, NULL), + __ATTR(voltage_limits, 0444, regulator_constraint_uA_show, NULL), + __ATTR(current_limits, 0444, regulator_constraint_uV_show, NULL), + __ATTR(valid_opmodes, 0444, regulator_constraint_modes_show, NULL), + __ATTR(total_load, 0444, regulator_total_dev_load, NULL), + __ATTR(enabled_count, 0444, regulator_enabled_use_count, NULL), + __ATTR(type, 0444, regulator_type_show, NULL), + __ATTR_NULL, +}; + +static void regulator_dev_release(struct class_device *class_dev) {} + +struct class regulator_class = { + .name = "regulator", + .release = regulator_dev_release, + .class_dev_attrs = regulator_dev_attrs, +}; + +/* find the lowest stable voltage that all enabled clients can operate at */ +static int get_lowest_stable_voltage(struct regulator_cdev *rcdev) +{ + struct regulator *regulator; + int highest_uV = 0; + + list_for_each_entry(regulator, &rcdev->consumer_list, list) { + if (regulator->enabled && regulator->uV_required > highest_uV) + highest_uV = regulator->uV_required; + } + return highest_uV; +} + +/* set the regulator voltage to the lowest possible value that can safely + * support all the client devices */ +static int regulator_set_stable_voltage(struct regulator_cdev *rcdev) +{ + int ret = 0, uV; + + if (rcdev->desc->type == REGULATOR_VOLTAGE) { + uV = get_lowest_stable_voltage(rcdev); + if (uV && rcdev->desc->ops->set_voltage) + ret = rcdev->desc->ops->set_voltage(rcdev, uV); + } + return ret; +} + +static void regulator_load_change (struct regulator_cdev *rcdev) +{ + struct regulator *sibling; + int current_uA = 0, output_uV, input_uV, err; + unsigned int mode; + + err = regulator_check_drms(rcdev); + if (err < 0 || !rcdev->desc->ops->get_optimum_mode || + !rcdev->desc->ops->get_voltage || !rcdev->desc->ops->set_mode); + return; + + /* get output voltage */ + output_uV = rcdev->desc->ops->get_voltage(rcdev); + + /* get input voltage */ + if (rcdev->parent && rcdev->parent->desc->ops->get_voltage) + input_uV = rcdev->parent->desc->ops->get_voltage(rcdev->parent); + else + input_uV = rcdev->constraints->input_uV; + + /* calc total requested load */ + list_for_each_entry(sibling, &rcdev->consumer_list, list) + current_uA += sibling->uA_load; + + /* now get the optimum mode for our new total regulator load */ + mode = rcdev->desc->ops->get_optimum_mode(rcdev, input_uV, + output_uV, current_uA); + + /* check the new mode is allowed */ + err = regulator_check_mode(rcdev, mode); + if (err == 0) + rcdev->desc->ops->set_mode(rcdev, mode); +} + +static void print_constraints(struct regulator_cdev *rcdev) +{ + struct regulation_constraints *constraints = rcdev->constraints; + char buf[80]; + int count; + + if (rcdev->desc->type == REGULATOR_VOLTAGE) { + if (constraints->min_uV == constraints->max_uV) + count = sprintf(buf, "%d mV ", + uV_to_mV(constraints->min_uV)); + else + count = sprintf(buf, "%d <--> %d mV ", + uV_to_mV(constraints->min_uV), + uV_to_mV(constraints->max_uV)); + } else { + if (constraints->min_uA == constraints->max_uA) + count = sprintf(buf, "%d mA ", + uA_to_mA(constraints->min_uA)); + else + count = sprintf(buf, "%d <--> %d mA ", + uA_to_mA(constraints->min_uA), + uA_to_mA(constraints->max_uA)); + } + if (constraints->valid_modes_mask & REGULATOR_MODE_FAST) + count += sprintf (buf + count, "fast "); + if (constraints->valid_modes_mask & REGULATOR_MODE_NORMAL) + count += sprintf (buf + count, "normal "); + if (constraints->valid_modes_mask & REGULATOR_MODE_IDLE) + count += sprintf (buf + count, "idle "); + if (constraints->valid_modes_mask & REGULATOR_MODE_STANDBY) + count += sprintf (buf + count, "standby"); + + printk(KERN_INFO "regulator: %s: %s\n", rcdev->desc->name, buf); +} + +static struct regulator *create_regulator(struct regulator_cdev *rcdev, + struct device *dev) +{ + struct regulator *regulator; + char buf[32]; + int err; + + regulator = kzalloc(sizeof(*regulator), GFP_KERNEL); + if (regulator == NULL) + return NULL; + + regulator->rcdev = rcdev; + sprintf(buf, "current_requested-%s", regulator->rcdev->desc->name); + list_add(®ulator->list, &rcdev->consumer_list); + + if (dev == NULL) + goto out; + + regulator->dev = dev; + regulator->dev_attr.attr.name = kstrdup(buf, GFP_KERNEL); + if (regulator->dev_attr.attr.name == NULL) + goto err_out; + regulator->dev_attr.attr.owner = THIS_MODULE; + regulator->dev_attr.attr.mode = 0444; + regulator->dev_attr.show = dev_load_show; + err = device_create_file(dev, ®ulator->dev_attr); + if (err < 0) { + printk(KERN_WARNING "%s: could not add regulator_cdev load" + " sysfs\n", __func__); + goto err_out; + } + err = sysfs_create_link(&rcdev->cdev.kobj, &dev->kobj, + dev->kobj.name); + if (err) { + printk(KERN_WARNING "%s : could not add device link %s err %d\n", + __func__, dev->kobj.name, err); + goto err_out; + } +out: + return regulator; +err_out: + kfree(regulator->dev_attr.attr.name); + list_del(®ulator->list); + kfree(regulator); + return NULL; +} + +struct regulator *regulator_get(struct device *dev, const char *id) +{ + struct regulator_cdev *rcdev; + struct regulator *regulator = ERR_PTR(-ENODEV); + + if (id == NULL) + return regulator; + + mutex_lock(&list_mutex); + list_for_each_entry(rcdev, ®ulator_cdevs, list) { + if (strcmp(id, rcdev->desc->name) == 0 && + try_module_get(rcdev->owner)) { + goto found; + } + } + printk(KERN_ERR "regulator: Unable to get requested regulator: %s\n", id); + mutex_unlock(&list_mutex); + return regulator; + +found: + regulator = create_regulator(rcdev, dev); + if (regulator == NULL) { + regulator = ERR_PTR(-ENOMEM); + module_put(rcdev->owner); + } + + mutex_unlock(&list_mutex); + return regulator; +} +EXPORT_SYMBOL_GPL(regulator_get); + +void regulator_put(struct regulator *regulator) +{ + struct regulator_cdev *rcdev = regulator->rcdev; + + if (regulator == NULL || IS_ERR(regulator)) + return; + + /* remove any sysfs entries */ + if (regulator->dev) { + sysfs_remove_link(&rcdev->cdev.kobj, regulator->dev->kobj.name); + device_remove_file(regulator->dev, ®ulator->dev_attr); + } + list_del(®ulator->list); + kfree(regulator->dev_attr.attr.name); + kfree(regulator); + + module_put(rcdev->owner); + mutex_unlock(&list_mutex); +} +EXPORT_SYMBOL_GPL(regulator_put); + +static int _regulator_enable(struct regulator_cdev *rcdev) +{ + int ret = 0; + + /* if the regulator is currently disabled make sure we check + * it's parent, mode and required voltage */ + if (rcdev->use_count == 0) { + if (rcdev->parent) { + ret = _regulator_enable(rcdev->parent); + if (ret < 0) { + printk(KERN_ERR "%s: failed to enable %s\n", + __func__, rcdev->desc->name); + goto out; + } + } + if (rcdev->desc->ops->enable) { + ret = regulator_set_stable_voltage(rcdev); + if (ret < 0) { + printk(KERN_ERR "%s: invalid voltage for %s\n", + __func__, rcdev->desc->name); + goto out; + } + regulator_load_change(rcdev); + ret = rcdev->desc->ops->enable(rcdev); + if (ret < 0) { + printk(KERN_ERR "%s: failed to enable %s\n", + __func__, rcdev->desc->name); + goto out; + } + } + } else { + ret = regulator_set_stable_voltage(rcdev); + if (ret < 0) { + printk(KERN_ERR "%s: invalid voltage for %s\n", + __func__, rcdev->desc->name); + goto out; + } + regulator_load_change(rcdev); + } + rcdev->use_count++; +out: + return ret; +} + +int regulator_enable(struct regulator *regulator) +{ + int ret; + + mutex_lock(®ulator->rcdev->mutex); + regulator->enabled = 1; + ret = _regulator_enable(regulator->rcdev); + if (ret < 0) + regulator->enabled = 0; + mutex_unlock(®ulator->rcdev->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_enable); + +static int _regulator_disable(struct regulator_cdev *rcdev) +{ + int ret = 0; + + /* make sure we are the last user before disabling this regulator */ + if (rcdev->use_count == 1) { + if (rcdev->desc->ops->disable) { + ret = rcdev->desc->ops->disable(rcdev); + if (ret < 0) { + printk(KERN_ERR "%s: failed to disable %s\n", + __func__, rcdev->desc->name); + goto out; + } + } + /* decrease parent ref count and disable if required */ + if (rcdev->parent) + _regulator_disable(rcdev->parent); + } else { + /* set to next best requested voltage and mode */ + ret = regulator_set_stable_voltage(rcdev); + regulator_load_change(rcdev); + } + rcdev->use_count--; +out: + if (rcdev->use_count < 0) { + printk(KERN_ERR "%s: tried to disable too many times\n", + __func__); + rcdev->use_count = 0; + } + return ret; +} + +int regulator_disable(struct regulator *regulator) +{ + int ret; + + mutex_lock(®ulator->rcdev->mutex); + regulator->enabled = 0; + regulator->uA_load = 0; + ret = _regulator_disable(regulator->rcdev); + if (ret < 0) + regulator->enabled = 1; + mutex_unlock(®ulator->rcdev->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_disable); + +static int _regulator_is_enabled(struct regulator_cdev *rcdev) +{ + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->is_enabled) { + ret = -EINVAL; + goto out; + } + + ret = rcdev->desc->ops->is_enabled(rcdev); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} + +int regulator_is_enabled(struct regulator *regulator) +{ + return _regulator_is_enabled(regulator->rcdev); +} +EXPORT_SYMBOL_GPL(regulator_is_enabled); + +int regulator_set_voltage(struct regulator *regulator, int uV) +{ + struct regulator_cdev *rcdev = regulator->rcdev; + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->set_voltage) { + ret = -EINVAL; + goto out; + } + + /* constraints check */ + ret = regulator_check_voltage(rcdev, uV); + if (ret < 0) + goto out; + regulator->uV_required = uV; + + /* only set the new voltage if it's all clients can use it */ + ret = regulator_set_stable_voltage(rcdev); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_voltage); + +static int _regulator_get_voltage(struct regulator_cdev *rcdev) +{ + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->get_voltage) { + ret = -EINVAL; + goto out; + } + + ret = rcdev->desc->ops->get_voltage(rcdev); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} + +int regulator_get_voltage(struct regulator *regulator) +{ + return _regulator_get_voltage(regulator->rcdev); +} +EXPORT_SYMBOL_GPL(regulator_get_voltage); + +int regulator_set_current(struct regulator *regulator, int uA) +{ + struct regulator_cdev *rcdev = regulator->rcdev; + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->set_current) { + ret = -EINVAL; + goto out; + } + + /* constraints check */ + ret = regulator_check_current(rcdev, uA); + if (ret < 0) + goto out; + + ret = rcdev->desc->ops->set_current(rcdev, uA); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_current); + +static int _regulator_get_current(struct regulator_cdev *rcdev) +{ + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->get_current) { + ret = -EINVAL; + goto out; + } + + ret = rcdev->desc->ops->get_current(rcdev); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} + +int regulator_get_current(struct regulator *regulator) +{ + return _regulator_get_current(regulator->rcdev); +} +EXPORT_SYMBOL_GPL(regulator_get_current); + +int regulator_set_mode(struct regulator *regulator, unsigned int mode) +{ + struct regulator_cdev *rcdev = regulator->rcdev; + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->set_mode) { + ret = -EINVAL; + goto out; + } + + /* constraints check */ + ret = regulator_check_mode(rcdev, mode); + if (ret < 0) + goto out; + + ret = rcdev->desc->ops->set_mode(rcdev, mode); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(regulator_set_mode); + +static unsigned int _regulator_get_mode(struct regulator_cdev *rcdev) +{ + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->get_mode) { + ret = -EINVAL; + goto out; + } + + ret = rcdev->desc->ops->get_mode(rcdev); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} + +unsigned int regulator_get_mode(struct regulator *regulator) +{ + return _regulator_get_mode(regulator->rcdev); +} +EXPORT_SYMBOL_GPL(regulator_get_mode); + +static unsigned int _regulator_get_optimum_mode(struct regulator_cdev *rcdev, + int input_uV, int output_uV, int load_uA) +{ + int ret; + + mutex_lock(&rcdev->mutex); + + /* sanity check */ + if (!rcdev->desc->ops->get_optimum_mode) { + ret = -EINVAL; + goto out; + } + + ret = rcdev->desc->ops->get_optimum_mode(rcdev, + input_uV, output_uV, load_uA); +out: + mutex_unlock(&rcdev->mutex); + return ret; +} + +unsigned int regulator_get_optimum_mode(struct regulator *regulator, + int input_uV, int output_uV, int load_uA) +{ + return _regulator_get_optimum_mode(regulator->rcdev, input_uV, + output_uV, load_uA); +} +EXPORT_SYMBOL_GPL(regulator_get_optimum_mode); + +int regulator_register_client(struct regulator *regulator, + struct notifier_block *nb) +{ + return blocking_notifier_chain_register(®ulator->rcdev->notifier, nb); +} +EXPORT_SYMBOL_GPL(regulator_register_client); + +int regulator_unregister_client(struct regulator *regulator, + struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(®ulator->rcdev->notifier, nb); +} +EXPORT_SYMBOL_GPL(regulator_unregister_client); + +int regulator_notifier_call_chain(struct regulator_cdev *rcdev, + unsigned long event, void *data) +{ + return blocking_notifier_call_chain(&rcdev->notifier, event, data); +} +EXPORT_SYMBOL_GPL(regulator_notifier_call_chain); + +struct regulator_cdev *regulator_register( + struct regulator_desc *regulator_desc, void *reg_data) +{ + static atomic_t regulator_no = ATOMIC_INIT(0); + struct regulator_cdev *rcdev; + int ret; + + if (regulator_desc == NULL) + return ERR_PTR(-EINVAL); + + if (regulator_desc->name == NULL || regulator_desc->ops == NULL) + return ERR_PTR(-EINVAL); + + if (!regulator_desc->type == REGULATOR_VOLTAGE && + !regulator_desc->type == REGULATOR_CURRENT) + return ERR_PTR(-EINVAL); + + rcdev = kzalloc(sizeof(struct regulator_cdev), GFP_KERNEL); + if (rcdev == NULL) + return ERR_PTR(-ENOMEM); + + mutex_lock(&list_mutex); + + mutex_init(&rcdev->mutex); + rcdev->reg_data = reg_data; + rcdev->owner = regulator_desc->owner; + rcdev->desc = regulator_desc; + INIT_LIST_HEAD(&rcdev->consumer_list); + INIT_LIST_HEAD(&rcdev->list); + BLOCKING_INIT_NOTIFIER_HEAD(&rcdev->notifier); + + rcdev->cdev.class = ®ulator_class; + class_device_initialize(&rcdev->cdev); + snprintf(rcdev->cdev.class_id, sizeof(rcdev->cdev.class_id), + "regulator-%ld-%s", + (unsigned long) atomic_inc_return(®ulator_no) - 1, + regulator_desc->name); + + ret = class_device_add(&rcdev->cdev); + if (ret == 0) + list_add(&rcdev->list, ®ulator_cdevs); + else { + kfree(rcdev); + rcdev = ERR_PTR(ret); + } + mutex_unlock(&list_mutex); + return rcdev; +} +EXPORT_SYMBOL_GPL(regulator_register); + +void regulator_unregister(struct regulator_cdev *rcdev) +{ + if (rcdev == NULL) + return; + + mutex_lock(&list_mutex); + list_del(&rcdev->list); + if (rcdev->parent) + sysfs_remove_link(&rcdev->cdev.kobj, "source"); + class_device_unregister(&rcdev->cdev); + kfree(rcdev); + mutex_unlock(&list_mutex); +} +EXPORT_SYMBOL_GPL(regulator_unregister); + +int regulator_set_platform_source(const char *regulator_source, + const char *regulator_parent) +{ + struct regulator_cdev *source_rcdev, *parent_rcdev; + int err; + + if (regulator_source == NULL || regulator_parent == NULL) + return -EINVAL; + + mutex_lock(&list_mutex); + + list_for_each_entry(source_rcdev, ®ulator_cdevs, list) { + if (!strcmp(source_rcdev->desc->name, regulator_source)) + goto found_source; + } + mutex_unlock(&list_mutex); + return -ENODEV; + +found_source: + list_for_each_entry(parent_rcdev, ®ulator_cdevs, list) { + if (!strcmp(parent_rcdev->desc->name, regulator_parent)) + goto found_parent; + } + mutex_unlock(&list_mutex); + return -ENODEV; + +found_parent: + source_rcdev->parent = parent_rcdev; + err = sysfs_create_link(&source_rcdev->cdev.kobj, &parent_rcdev->cdev.kobj, + "source"); + if (err) + printk(KERN_WARNING "%s : could not add device link %s err %d\n", + __func__, parent_rcdev->cdev.kobj.name, err); + mutex_unlock(&list_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(regulator_set_platform_source); + +const char *regulator_get_platform_source(const char *regulator_name) +{ + struct regulator_cdev *rcdev; + + if (regulator_name == NULL) + return NULL; + + mutex_lock(&list_mutex); + list_for_each_entry(rcdev, ®ulator_cdevs, list) { + if (!strcmp(rcdev->desc->name, regulator_name)) + goto found; + } + mutex_unlock(&list_mutex); + return NULL; + +found: + mutex_unlock(&list_mutex); + if (rcdev->parent) + return rcdev->parent->desc->name; + else + return NULL; +} +EXPORT_SYMBOL_GPL(regulator_get_platform_source); + +int regulator_set_platform_constraints(const char *regulator_name, + struct regulation_constraints *constraints) +{ + struct regulator_cdev *rcdev; + + if (regulator_name == NULL) + return -EINVAL; + + mutex_lock(&list_mutex); + list_for_each_entry(rcdev, ®ulator_cdevs, list) { + if (!strcmp(regulator_name, rcdev->desc->name)) { + mutex_lock(&rcdev->mutex); + rcdev->constraints = constraints; + print_constraints(rcdev); + mutex_unlock(&rcdev->mutex); + mutex_unlock(&list_mutex); + return 0; + } + } + mutex_unlock(&list_mutex); + return -ENODEV; +} +EXPORT_SYMBOL_GPL(regulator_set_platform_constraints); + +void regulator_drms_notify_load(struct regulator *regulator, int uA) +{ + struct regulator_cdev *rcdev = regulator->rcdev; + + mutex_lock(&rcdev->mutex); + regulator->uA_load = uA; + regulator_load_change (rcdev); + mutex_unlock(&rcdev->mutex); +} +EXPORT_SYMBOL_GPL(regulator_drms_notify_load); + +void *regulator_get_drvdata(struct regulator *regulator) +{ + return regulator->rcdev->reg_data; +} +EXPORT_SYMBOL_GPL(regulator_get_drvdata); + +void regulator_set_drvdata(struct regulator *regulator, void *data) +{ + regulator->rcdev->reg_data = data; +} +EXPORT_SYMBOL_GPL(regulator_set_drvdata); + +int rcdev_get_id (struct regulator_cdev *rcdev) +{ + return rcdev->desc->id; +} +EXPORT_SYMBOL_GPL(rcdev_get_id); + +void *rcdev_get_drvdata (struct regulator_cdev *rcdev) +{ + return rcdev->reg_data; +} +EXPORT_SYMBOL_GPL(rcdev_get_drvdata); + +static int __init regulator_init(void) +{ + printk(KERN_INFO "regulator: core version %s\n", REGULATOR_VERSION); + return class_register(®ulator_class); +} + +/* init early to allow our consumers to complete system booting */ +core_initcall(regulator_init); diff --git a/drivers/regulator/wm8350/Kconfig b/drivers/regulator/wm8350/Kconfig new file mode 100644 index 0000000..9a98f88 --- /dev/null +++ b/drivers/regulator/wm8350/Kconfig @@ -0,0 +1,31 @@ +menu "Wolfson Microelectronics WM8350 PMIC" + +config REGULATOR_WM8350 + bool + +config PMIC_WM8350_MODE_0 + bool + +config PMIC_WM8350_MODE_1 + bool + +config PMIC_WM8350_MODE_2 + bool + +config PMIC_WM8350_MODE_3 + bool + +config IMX32ADS_WM8350_PMU + bool "WOLFSON WM8350 IMX32ADS board PMU support" + depends on REGULATOR + select REGULATOR_WM8350 + select I2C_MXC_SELECT2 + select PMIC_WM8350_MODE_0 + help + If you say yes here you get support for the + Wolfon WM8350 PMIC chip on the Freescale IMX32ADS. + + This driver can also be built as a module. If so, the module + will be called imx32ads-wm8350. + +endmenu diff --git a/drivers/regulator/wm8350/Makefile b/drivers/regulator/wm8350/Makefile new file mode 100644 index 0000000..bdcb357 --- /dev/null +++ b/drivers/regulator/wm8350/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for WM8350 drivers. +# + +obj-$(CONFIG_REGULATOR_WM8350) += reg-wm8350.o reg-wm8350-bus.o +obj-$(CONFIG_IMX32ADS_WM8350_PMU) += imx32ads-wm8350.o \ No newline at end of file diff --git a/drivers/regulator/wm8350/imx32ads-wm8350.c b/drivers/regulator/wm8350/imx32ads-wm8350.c new file mode 100644 index 0000000..bfa904a --- /dev/null +++ b/drivers/regulator/wm8350/imx32ads-wm8350.c @@ -0,0 +1,394 @@ +/* + * imx32ads-wm8350.c -- i.MX32ADS Board Driver for Wolfson WM8350 PMIC + * + * This driver defines and configures the WM8350 for the Freescale i.MX32ADS. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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. + * + * Revision history + * 23rd Jan 2007 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WM8350_IMX32ADS_VERSION "0.5" + +/* + * Set to 1 when testing battery that is connected otherwise spuriuos debug + */ +#define BATTERY 0 + +static int wm8350_pmic_i2c_detect (struct i2c_adapter *adap, int addr, int kind); +static struct platform_device *imx32ads_snd_device; + +#if BATTERY +static int wm8350_init_battery(struct wm8350* wm8350) +{ + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = &power->policy; + + policy->eoc_mA = WM8350_CHG_EOC_mA(10); + policy->charge_mV = WM8350_CHG_4_05V; + policy->fast_limit_mA = WM8350_CHG_FAST_LIMIT_mA(400); + policy->charge_timeout = WM8350_CHG_TIME_MIN(wm8350->rev, 60); + policy->trickle_start_mV = WM8350_CHG_TRICKLE_3_1V; + policy->trickle_charge_mA = WM8350_CHG_TRICKLE_50mA; + + wm8350_charger_enable(power, 1); + wm8350_fast_charger_enable(power, 1); + return 0; +} +#endif + +/* this could be hooked up to ON/OFF switches */ +static void imx32ads_switch_handler(struct wm8350 *wm8350, int irq, void *data) +{ + printk("switch pressed %d\n", irq); +} + +static int config_board_gpios(struct wm8350* wm8350) +{ + /* power on */ + wm8350_gpio_config(wm8350, 0, WM8350_GPIO_DIR_IN, + WM8350_GPIO0_PWR_ON_IN, WM8350_GPIO_ACTIVE_LOW, + WM8350_GPIO_PULL_UP, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_ON); + + /* Sw3 --> PWR_OFF_GPIO3 */ + /* lg - TODO: GPIO1_0 to be pulled down */ + wm8350_gpio_config(wm8350, 3, WM8350_GPIO_DIR_IN, + WM8350_GPIO3_PWR_OFF_IN, WM8350_GPIO_ACTIVE_HIGH, + WM8350_GPIO_PULL_DOWN, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_ON); + + /* MR or MEMRST ????? */ + wm8350_gpio_config(wm8350, 4, WM8350_GPIO_DIR_IN, + WM8350_GPIO4_MR_IN, WM8350_GPIO_ACTIVE_HIGH, + WM8350_GPIO_PULL_DOWN, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_OFF); + + /* Hibernate -- GPIO 7 */ + wm8350_gpio_config(wm8350, 7, WM8350_GPIO_DIR_IN, + WM8350_GPIO7_HIBERNATE_IN, WM8350_GPIO_ACTIVE_HIGH, + WM8350_GPIO_PULL_DOWN, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_OFF); + + /* SDOUT */ + wm8350_gpio_config(wm8350, 6, WM8350_GPIO_DIR_OUT, + WM8350_GPIO6_SDOUT_OUT, WM8350_GPIO_ACTIVE_HIGH, + WM8350_GPIO_PULL_NONE, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_OFF); + + /* GPIO switch SW2 */ + wm8350_gpio_config(wm8350, 7, WM8350_GPIO_DIR_IN, WM8350_GPIO7_GPIO_IN, + WM8350_GPIO_ACTIVE_HIGH, WM8350_GPIO_PULL_DOWN, + WM8350_GPIO_INVERT_OFF, WM8350_GPIO_DEBOUNCE_ON); + wm8350_register_irq(wm8350, WM8350_IRQ_GPIO(7), + imx32ads_switch_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_GPIO(7)); + + /* PWR_FAIL */ + wm8350_gpio_config(wm8350, 8, WM8350_GPIO_DIR_OUT, + WM8350_GPIO8_VCC_FAULT_OUT, WM8350_GPIO_ACTIVE_LOW, + WM8350_GPIO_PULL_NONE, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_OFF); + + /* BATT Fault */ + wm8350_gpio_config(wm8350, 9, WM8350_GPIO_DIR_OUT, + WM8350_GPIO9_BATT_FAULT_OUT, WM8350_GPIO_ACTIVE_LOW, + WM8350_GPIO_PULL_NONE, WM8350_GPIO_INVERT_OFF, + WM8350_GPIO_DEBOUNCE_OFF); + + return 0; +} + +static int config_board_hibernate(struct wm8350* wm8350) +{ + struct wm8350_pmic *pmic = &wm8350->pmic; + + /* dont assert RTS when hibernating */ + wm8350_set_bits(wm8350, WM8350_SYSTEM_HIBERNATE, WM8350_RST_HIB_MODE); + + /* set up hibernate voltages -- needs refining for each board */ + wm8350_dcdc_set_image_voltage(pmic, WM8350_DCDC_1, 1400, + WM8350_DCDC_HIB_MODE_IMAGE, WM8350_DCDC_HIB_SIG_REG); + wm8350_dcdc_set_image_voltage(pmic, WM8350_DCDC_3, 2800, + WM8350_DCDC_HIB_MODE_IMAGE, WM8350_DCDC_HIB_SIG_REG); + wm8350_dcdc_set_image_voltage(pmic, WM8350_DCDC_4, 1800, + WM8350_DCDC_HIB_MODE_IMAGE, WM8350_DCDC_HIB_SIG_REG); + wm8350_dcdc_set_image_voltage(pmic, WM8350_DCDC_6, 1800, + WM8350_DCDC_HIB_MODE_IMAGE, WM8350_DCDC_HIB_SIG_REG); + wm8350_ldo_set_image_voltage(pmic, WM8350_LDO_1, 2800, + WM8350_LDO_HIB_MODE_IMAGE, WM8350_LDO_HIB_SIG_REG); + wm8350_ldo_set_image_voltage(pmic, WM8350_LDO_2, 3300, + WM8350_LDO_HIB_MODE_IMAGE, WM8350_LDO_HIB_SIG_REG); + wm8350_ldo_set_image_voltage(pmic, WM8350_LDO_3, 1500, + WM8350_LDO_HIB_MODE_IMAGE, WM8350_LDO_HIB_SIG_REG); + wm8350_ldo_set_image_voltage(pmic, WM8350_LDO_4, 2500, + WM8350_LDO_HIB_MODE_IMAGE, WM8350_LDO_HIB_MODE_DIS); + return 0; +} + +struct regulation_constraints led = { + .min_uA = 0, + .max_uA = 27898, + .valid_ops_mask = REGULATOR_CHANGE_CURRENT, + .valid_modes_mask = REGULATOR_MODE_NORMAL, +}; + +struct regulation_constraints cpufreq = { + .min_uV = mV_to_uV(1300), + .max_uV = mV_to_uV(1600), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | REGULATOR_MODE_FAST, +}; + +struct regulation_constraints avdd = { + .min_uV = mV_to_uV(3300), + .max_uV = mV_to_uV(3300), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .valid_modes_mask = REGULATOR_MODE_NORMAL, +}; + +static void set_regulator_constraints(struct wm8350 *wm8350) +{ + regulator_set_platform_constraints("DCDC1", &cpufreq); + regulator_set_platform_constraints("ISINKA", &led); + regulator_set_platform_constraints("LDO2", &avdd); +} + +/* + * WM8350 2 wire address + */ +#define WM8350_I2C_ADDR (0x34 >> 1) +static unsigned short normal_i2c[] = { WM8350_I2C_ADDR, I2C_CLIENT_END }; + +/* Magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver wm8350_i2c_driver; +static struct i2c_client client_template; + +static void wm8350_irq_work(struct work_struct *work) +{ + wm8350_irq_worker(work); +} + +static irqreturn_t wm8350_irq_handler(int irq, void *data) +{ + struct wm8350 *wm8350 = (struct wm8350*)data; + + schedule_work(&wm8350->work); + return IRQ_HANDLED; +} + +static int wm8350_i2c_detach(struct i2c_client *client) +{ + struct wm8350 *wm8350 = i2c_get_clientdata(client); + +#if BATTERY + struct wm8350_power *power = &wm8350->power; +#endif + wm8350_mask_irq(wm8350, WM8350_IRQ_GPIO(7)); + wm8350_free_irq(wm8350, WM8350_IRQ_GPIO(7)); + wm8350_mask_irq(wm8350, WM8350_IRQ_WKUP_ONKEY); + wm8350_free_irq(wm8350, WM8350_IRQ_WKUP_ONKEY); + +#if BATTERY + wm8350_charger_enable(power, 0); + wm8350_fast_charger_enable(power, 0); +#endif + if (wm8350->nirq) + free_irq(wm8350->nirq, wm8350); + + flush_scheduled_work(); + + /* unregister any client devices */ + if (wm8350->rtc.dev.is_registered) + device_unregister(&wm8350->rtc.dev); + if (wm8350->wdg.dev.is_registered) + device_unregister(&wm8350->wdg.dev); + if (wm8350->power.dev.is_registered) + device_unregister(&wm8350->power.dev); + platform_device_unregister(imx32ads_snd_device); + + wm8350_pmu_exit(wm8350); + + i2c_detach_client(client); + kfree(client); + return 0; +} + +static int wm8350_i2c_attach(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, wm8350_pmic_i2c_detect); +} + +#define I2C_DRIVERID_WM8350 0xfefe /* TODO: liam need proper id */ + +static struct i2c_driver wm8350_i2c_driver = { + .driver = { + .name = "WM8350", + .owner = THIS_MODULE, + }, + .id = I2C_DRIVERID_WM8350, + .attach_adapter = wm8350_i2c_attach, + .detach_client = wm8350_i2c_detach, + .command = NULL, +}; + +static struct i2c_client client_template = { + .name = "WM8350", + .driver = &wm8350_i2c_driver, +}; + +static int wm8350_init(struct wm8350* wm8350) +{ + int ret; + + printk("wm8350: i.MX32ADS client driver version %s\n", + WM8350_IMX32ADS_VERSION); + + /* register regulator and set constraints */ + ret = wm8350_pmu_init(wm8350); + if (ret < 0) { + printk(KERN_ERR "%s: failed to initialise device\n", __func__); + goto err; + } + + set_regulator_constraints(wm8350); + + /* now register other clients */ + wm8350_device_register_rtc(wm8350); + wm8350_device_register_wdg(wm8350); + wm8350_device_register_power(wm8350); + + /* register sound */ + imx32ads_snd_device = platform_device_alloc("imx31ads-audio", -1); + if (!imx32ads_snd_device) { + ret = -ENOMEM; + goto err; + } + platform_set_drvdata(imx32ads_snd_device, wm8350); + ret = platform_device_add(imx32ads_snd_device); + if (ret) + goto snd_err; + + /* set up PMIC IRQ (active high) to i.MX32ADS */ + INIT_WORK(&wm8350->work, wm8350_irq_work); + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_SYSTEM_CONTROL_1, WM8350_IRQ_POL); + wm8350_reg_lock(wm8350); + set_irq_type(IOMUX_TO_IRQ(MX31_PIN_GPIO1_3), IRQT_RISING); + ret = request_irq(IOMUX_TO_IRQ(MX31_PIN_GPIO1_3), wm8350_irq_handler, + IRQF_DISABLED, "wm8350-pmic", wm8350); + if (ret != 0) { + printk(KERN_ERR "wm8350: cant request irq %d\n", + INT_EXT_POWER); + goto err; + } + wm8350->nirq = IOMUX_TO_IRQ(MX31_PIN_GPIO1_3); + + config_board_gpios(wm8350); + config_board_hibernate(wm8350); + + /* Sw1 --> PWR_ON */ + wm8350_register_irq(wm8350, WM8350_IRQ_WKUP_ONKEY, + imx32ads_switch_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_WKUP_ONKEY); + + /* unmask all & clear sticky */ + wm8350_reg_write(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK, 0x0); + schedule_work(&wm8350->work); + +#if BATTERY + /* not much use without a battery atm */ + wm8350_init_battery(wm8350); +#endif + + return ret; +snd_err: + platform_device_put(imx32ads_snd_device); + wm8350_pmu_exit(wm8350); +err: + return ret; +} + +static int wm8350_pmic_i2c_detect (struct i2c_adapter *adap, int addr, int kind) +{ + struct wm8350* wm8350; + struct i2c_client *i2c; + int ret = 0; + + if (addr != WM8350_I2C_ADDR) + return -ENODEV; + + client_template.adapter = adap; + client_template.addr = addr; + + i2c = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (i2c == NULL) + return -ENOMEM; + + wm8350 = kzalloc(sizeof(struct wm8350), GFP_KERNEL); + if (wm8350 == NULL) { + kfree(i2c); + return -ENOMEM; + } + + memcpy(i2c, &client_template, sizeof(struct i2c_client)); + mutex_init(&wm8350->work_mutex); + i2c_set_clientdata(i2c, wm8350); + wm8350->i2c_client = i2c; + wm8350_set_io(wm8350, WM8350_IO_I2C, NULL, NULL); + + ret = i2c_attach_client(i2c); + if (ret < 0) { + printk(KERN_ERR "wm8350: failed to attach device at addr 0x%x\n", addr); + goto err; + } + ret = wm8350_init(wm8350); + if (ret == 0) + return ret; + +err: + wm8350_i2c_detach(i2c); + return ret; +} + +static int __init imx32ads_wm8350_pmic_init(void) +{ + return i2c_add_driver(&wm8350_i2c_driver); +} + +/* init early so consumer devices can complete system boot */ +subsys_initcall(imx32ads_wm8350_pmic_init); diff --git a/drivers/regulator/wm8350/reg-wm8350-bus.c b/drivers/regulator/wm8350/reg-wm8350-bus.c new file mode 100644 index 0000000..6e2bcf4 --- /dev/null +++ b/drivers/regulator/wm8350/reg-wm8350-bus.c @@ -0,0 +1,1442 @@ +/* + * wm8350_bus.c -- Power Management Driver for Wolfson WM8350 PMIC + * + * Copyright 2007 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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. + * + * Revision history + * 23rd Jan 2007 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WM8350_UNLOCK_KEY 0x0013 +#define WM8350_LOCK_KEY 0x0000 + +#define WM8350_CLOCK_CONTROL_1 0x28 +#define WM8350_AIF_TEST 0x74 + +/* debug */ +#define WM8350_BUS_DEBUG 0 +#if WM8350_BUS_DEBUG +#define dbg(format, arg...) printk(format, ## arg) +#define dump(regs, src) do { \ + int i; \ + u16 *src_ = src; \ + for (i = 0; i < regs; i++) \ + dbg(" 0x%4.4x", *src_++); \ + dbg("\n"); \ +} while (0); +#else +#define dbg(format, arg...) +#define dump(bytes, src) +#endif + +#define WM8350_LOCK_DEBUG 0 +#if WM8350_LOCK_DEBUG +#define ldbg(format, arg...) printk(format, ## arg) +#else +#define ldbg(format, arg...) +#endif + +#define BYTE_SWAP16(x) (x >> 8 | x << 8) // lg replace with generic + +/* + * WM8350 can run in 1 of 4 configuration modes. + * Each mode has different default register values. + */ +#if defined(CONFIG_PMIC_WM8350_MODE_0) +static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_0; +#elif defined(CONFIG_PMIC_WM8350_MODE_1) +static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_1; +#elif defined(CONFIG_PMIC_WM8350_MODE_2) +static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_2; +#elif defined(CONFIG_PMIC_WM8350_MODE_3) +static const u16 wm8350_reg_map[] = WM8350_REGISTER_DEFAULTS_3; +#else +#error Invalid WM8350 configuration +#endif + +/* + * WM8350 Register IO access map + */ +static const struct wm8350_reg_access wm8350_reg_io_map[] = WM8350_ACCESS; + +/* + * WM8350 Device IO + */ +static DEFINE_MUTEX(io_mutex); +static DEFINE_MUTEX(reg_lock_mutex); +static DEFINE_MUTEX(auxadc_mutex); + +#ifdef CONFIG_I2C +static int wm8350_read_i2c_device(struct wm8350 *wm8350, char reg, + int bytes, char *dest) +{ + int ret; + + ret = i2c_master_send(wm8350->i2c_client, ®, 1); + if (ret < 0) + return ret; + return i2c_master_recv(wm8350->i2c_client, dest, bytes); +} + +static int wm8350_write_i2c_device(struct wm8350 *wm8350, char reg, + int bytes, char *src) +{ + /* we add 1 byte for device register */ + u8 msg[(WM8350_MAX_REGISTER << 1) + 1]; + + if (bytes > ((WM8350_MAX_REGISTER << 1) + 1)) + return -EINVAL; + + msg[0] = reg; + memcpy(&msg[1], src, bytes); + return i2c_master_send(wm8350->i2c_client, msg, bytes + 1); +} +#endif + +#ifdef CONFIG_SPI +static int wm8350_read_spi_device(struct wm8350 *wm8350, char reg, + int bytes, char *dest) +{ + int ret; + u8 tx_msg[4], rx_msg[4]; + + /* don't support incremental write with SPI */ + if (bytes != 2) + return -EIO; + + tx_msg[0] = 0x80; + tx_msg[1] = reg; + tx_msg[2] = 0; + tx_msg[3] = 0; + + ret = spi_write_then_read(wm8350->spi_device, tx_msg, 4, rx_msg, 4); + if (ret < 0) { + printk(KERN_ERR "%s: io failure %d\n", __func__, ret); + return 0; + } + + *dest++ = rx_msg[2]; + *dest = rx_msg[3]; + return 0; +} + +static int wm8350_write_spi_device(struct wm8350 *wm8350, char reg, + int bytes, char *src) +{ + u8 msg[4]; + + /* don't support incremental write with SPI */ + if (bytes != 2) + return -EIO; + + msg[0] = 0; + msg[1] = reg; + msg[2] = *src++; + msg[3] = *src; + return spi_write(wm8350->spi_device, msg, 4); +} +#endif + +/* mask in WM8350 read bits */ +static inline void wm8350_mask_read(u8 reg, int bytes, u16 *buf) +{ + int i; + + for (i = reg; i < reg + (bytes >> 1); i++) + *buf++ &= wm8350_reg_io_map[i].readable; +} + +/* mask in WM8350 write bits */ +static inline void wm8350_mask_write(u8 reg, int bytes, u16 *buf) +{ + int i; + + for (i = reg; i < reg + (bytes >> 1); i++) + *buf++ &= wm8350_reg_io_map[i].writable; +} + +/* WM8350 is big endian, swap if necessary */ +static inline void wm8350_endian_swap(u8 reg, int bytes, u16 *buf) +{ +#ifdef __LITTLE_ENDIAN + int i; + u16 tmp; + + for (i = reg; i < reg + (bytes >> 1); i++) { + tmp = BYTE_SWAP16(*buf); + *buf++ = tmp; + } +#endif +} + +static inline void wm8350_cache_mask(struct wm8350 *wm8350, u8 reg, + int bytes, u16 *dest) +{ + int i; + u16 mask; + + for (i = reg; i < reg + (bytes >> 1); i++) { + *dest &= wm8350_reg_io_map[i].vol; + mask = wm8350->reg_cache[reg] & ~wm8350_reg_io_map[i].vol; + *dest |= mask; + dest++; + } +} + +static int wm8350_read(struct wm8350 *wm8350, u8 reg, int num_regs, u16 *dest) +{ + int i, end = reg + num_regs, ret = 0, bytes = num_regs << 1; + + if (wm8350->read_dev == NULL) + return -ENODEV; + + if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) { + printk(KERN_ERR "wm8350: invalid reg %x\n", reg + num_regs - 1); + return -EINVAL; + } + + dbg("%s R%d(0x%2.2x) %d regs ", __FUNCTION__, reg, reg, num_regs); + +#if WM8350_BUS_DEBUG + /* we can _safely_ read any register, but warn if read not supported */ + for (i = reg; i < end; i++) { + if (!wm8350_reg_io_map[i].readable) + printk(KERN_WARNING "wm8350: reg R%d is not readable\n", i); + } +#endif + /* if any volatile registers are required, then read back all */ + for (i = reg; i < end; i++) { + if (wm8350_reg_io_map[i].vol) { + dbg("volatile read "); + ret = wm8350->read_dev(wm8350, reg, + bytes, (char*)dest); + wm8350_endian_swap(reg, bytes, dest); + wm8350_cache_mask(wm8350, reg, bytes, dest); + wm8350_mask_read(reg, bytes, dest); + dump(num_regs, dest); + return ret; + } + } + + /* no volatiles, then cache is good */ + dbg("cache read "); + memcpy(dest, &wm8350->reg_cache[reg], bytes); + dump(num_regs, dest); + return ret; +} + +static inline int is_reg_locked(struct wm8350 *wm8350, u8 reg) +{ + if (reg == WM8350_SECURITY || + wm8350->reg_cache[WM8350_SECURITY] == WM8350_UNLOCK_KEY) + return 0; + + if ((reg == WM8350_GPIO_CONFIGURATION_I_O) || + (reg >= WM8350_GPIO_FUNCTION_SELECT_1 && + reg <= WM8350_GPIO_FUNCTION_SELECT_4) || + (reg >= WM8350_BATTERY_CHARGER_CONTROL_1 && + reg <= WM8350_BATTERY_CHARGER_CONTROL_3)) + return 1; + return 0; +} + +static int wm8350_write(struct wm8350 *wm8350, u8 reg, int num_regs, u16 *src) +{ + int ret, i, end = reg + num_regs, bytes = num_regs << 1; + + if (wm8350->write_dev == NULL) + return -ENODEV; + + if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) { + printk(KERN_ERR "wm8350: invalid reg %x\n", reg + num_regs - 1); + return -EINVAL; + } + + wm8350_mask_write(reg, bytes, src); + memcpy(&wm8350->reg_cache[reg], src, bytes); + dbg("%s R%d(0x%2.2x) %d regs ", __FUNCTION__, reg, reg, num_regs); + dump(num_regs, src); + + wm8350_endian_swap(reg, bytes, src); + + /* it's generally not a good idea to write to RO or locked registers */ + for (i = reg; i < end; i++) { + if (!wm8350_reg_io_map[i].writable) { + printk(KERN_ERR "wm8350: attempted write to read only reg R%d\n", i); + return -EINVAL; + } + + if (is_reg_locked(wm8350, i)) { + printk(KERN_ERR "wm8350: attempted write to locked reg R%d\n", i); + return -EINVAL; + } + } + + /* write registers and update cache if successful */ + ret = wm8350->write_dev(wm8350, reg, bytes, (char*)src); + return ret; +} + +/* + * Safe read, modify, write methods + */ +int wm8350_clear_bits(struct wm8350 *wm8350, u16 reg, u16 mask) +{ + u16 data; + int err; + + mutex_lock(&io_mutex); + err = wm8350_read(wm8350, reg, 1, &data); + if (err) { + printk(KERN_ERR "wm8350: read from reg R%d failed\n", reg); + goto out; + } + + data &= ~mask; + err = wm8350_write(wm8350, reg, 1, &data); + if (err) + printk(KERN_ERR "wm8350: write to reg R%d failed\n", reg); +out: + mutex_unlock(&io_mutex); + return err; +} +EXPORT_SYMBOL_GPL(wm8350_clear_bits); + +int wm8350_set_bits(struct wm8350 *wm8350, u16 reg, u16 mask) +{ + u16 data; + int err; + + mutex_lock(&io_mutex); + err = wm8350_read(wm8350, reg, 1, &data); + if (err) { + printk(KERN_ERR "wm8350: read from reg R%d failed\n", reg); + goto out; + } + + data |= mask; + err = wm8350_write(wm8350, reg, 1, &data); + if (err) + printk(KERN_ERR "wm8350: write to reg R%d failed\n", reg); +out: + mutex_unlock(&io_mutex); + return err; +} +EXPORT_SYMBOL_GPL(wm8350_set_bits); + +u16 wm8350_reg_read(struct wm8350 *wm8350, int reg) +{ + u16 data; + int err; + + mutex_lock(&io_mutex); + err = wm8350_read(wm8350, reg, 1, &data); + if (err) + printk(KERN_ERR "wm8350: read from reg R%d failed\n", reg); + + mutex_unlock(&io_mutex); + return data; +} +EXPORT_SYMBOL_GPL(wm8350_reg_read); + +int wm8350_reg_write(struct wm8350 *wm8350, int reg, u16 val) +{ + int ret; + u16 data = val; + + mutex_lock(&io_mutex); + ret = wm8350_write(wm8350, reg, 1, &data); + if (ret) + printk(KERN_ERR "wm8350: write to reg R%d failed\n", reg); + mutex_unlock(&io_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_write); + +int wm8350_block_read(struct wm8350 *wm8350, int start_reg, int regs, + u16 *dest) +{ + int err = 0; + + mutex_lock(&io_mutex); + err = wm8350_read(wm8350, start_reg, regs, dest); + if (err) + printk(KERN_ERR "wm8350: block read starting from R%d failed\n", + start_reg); + mutex_unlock(&io_mutex); + return err; +} +EXPORT_SYMBOL_GPL(wm8350_block_read); + +int wm8350_block_write(struct wm8350 *wm8350, int start_reg, int regs, + u16 *src) +{ + int ret = 0; + + mutex_lock(&io_mutex); + ret = wm8350_write(wm8350, start_reg, regs, src); + if (ret) + printk(KERN_ERR "wm8350: block write starting at R%d failed\n", + start_reg); + mutex_unlock(&io_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_block_write); + +int wm8350_reg_lock(struct wm8350 *wm8350) +{ + u16 key = WM8350_LOCK_KEY; + int ret; + + ldbg ("%s\n", __FUNCTION__); + mutex_lock(&io_mutex); + ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key); + if (ret) + printk(KERN_ERR "wm8350: lock failed\n"); + mutex_unlock(&io_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_lock); + +int wm8350_reg_unlock(struct wm8350 *wm8350) +{ + u16 key = WM8350_UNLOCK_KEY; + int ret; + + ldbg ("%s\n", __FUNCTION__); + mutex_lock(&io_mutex); + ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key); + if (ret) + printk(KERN_ERR "wm8350: unlock failed\n"); + mutex_unlock(&io_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_reg_unlock); + +/* + * For Switching between SPI and I2C IO + */ +int wm8350_set_io(struct wm8350 *wm8350, int io, wm8350_hw_read_t read_dev, + wm8350_hw_write_t write_dev) +{ + mutex_lock(&io_mutex); + switch (io) { + case WM8350_IO_I2C: +#ifdef CONFIG_I2C + wm8350->read_dev = wm8350_read_i2c_device; + wm8350->write_dev = wm8350_write_i2c_device; + break; +#else + printk(KERN_ERR "wm8350: I2C not selected.\n"); + wm8350->read_dev = NULL; + wm8350->write_dev = NULL; + mutex_unlock(&io_mutex); + return -EINVAL; +#endif + case WM8350_IO_SPI: +#ifdef CONFIG_SPI + wm8350->read_dev = wm8350_read_spi_device; + wm8350->write_dev = wm8350_write_spi_device; + break; +#else + printk(KERN_ERR "wm8350: SPI not selected.\n"); + wm8350->read_dev = NULL; + wm8350->write_dev = NULL; + mutex_unlock(&io_mutex); + return -EINVAL; +#endif + default: + printk(KERN_ERR "wm8350: invalid IO mechanism\n"); + wm8350->read_dev = NULL; + wm8350->write_dev = NULL; + mutex_unlock(&io_mutex); + return -EINVAL; + } + mutex_unlock(&io_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_set_io); + +/* + * Cache is always host endian. + */ +int wm8350_create_cache(struct wm8350 *wm8350) +{ + int i, ret = 0; + u16 value; + + if (wm8350->read_dev == NULL) + return -ENODEV; + + wm8350->reg_cache = + kzalloc(sizeof(u16) * (WM8350_MAX_REGISTER + 1), GFP_KERNEL); + if (wm8350->reg_cache == NULL) + return -ENOMEM; + + /* TODO: check if we are virgin state so we don't have to do this */ + /* refresh cache with chip regs as some registers can survive reboot */ + for (i = 0; i < WM8350_MAX_REGISTER; i++) { + /* audio register range */ + if (wm8350_reg_io_map[i].readable && + (i < WM8350_CLOCK_CONTROL_1 || i > WM8350_AIF_TEST)) { + ret = wm8350->read_dev(wm8350, i, 2, (char*)&value); + if (ret < 0) { + printk(KERN_ERR + "wm8350: failed to create cache\n"); + goto out; + } + wm8350_endian_swap(i, 2, &value); + wm8350_mask_read(i, 2, &value); + wm8350->reg_cache[i] = value; + } else + wm8350->reg_cache[i] = wm8350_reg_map[i]; + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_create_cache); + + +static void wm8350_irq_call_worker(struct wm8350 *wm8350, int irq) +{ + mutex_lock(&wm8350->work_mutex); + + if (wm8350->irq[irq].handler) + wm8350->irq[irq].handler(wm8350, irq, wm8350->irq[irq].data); + else { + mutex_unlock(&wm8350->work_mutex); + printk(KERN_ERR "wm8350: irq %d nobody cared. now masked.\n", + irq); + wm8350_mask_irq(wm8350, irq); + return; + } + mutex_unlock(&wm8350->work_mutex); +} + +void wm8350_irq_worker(struct work_struct *work) +{ + u16 level_one, status1, status2, comp, oc, gpio, uv; + struct wm8350 *wm8350 = + container_of(work, struct wm8350, work); + + /* read this in 1 block read */ + /* read 1st level irq sources and then read required 2nd sources */ + level_one = wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS) + & ~wm8350_reg_read(wm8350, WM8350_SYSTEM_INTERRUPTS_MASK); + uv = wm8350_reg_read(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS) + & ~wm8350_reg_read(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK); + oc = wm8350_reg_read(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS) + & ~wm8350_reg_read(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS_MASK); + status1 = wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_1) + & ~wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_1_MASK); + status2 = wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_2) + & ~wm8350_reg_read(wm8350, WM8350_INTERRUPT_STATUS_2_MASK); + comp = wm8350_reg_read(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS) + & ~wm8350_reg_read(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK); + gpio = wm8350_reg_read(wm8350, WM8350_GPIO_INTERRUPT_STATUS) + & ~wm8350_reg_read(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK); + + /* over current */ + if (level_one & WM8350_OC_INT) { + if (oc & WM8350_OC_LS_EINT) /* limit switch */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_OC_LS); + } + + /* under voltage */ + if (level_one & WM8350_UV_INT) { + if (uv & WM8350_UV_DC1_EINT) /* DCDC1 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC1); + if (uv & WM8350_UV_DC2_EINT) /* DCDC2 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC2); + if (uv & WM8350_UV_DC3_EINT) /* DCDC3 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC3); + if (uv & WM8350_UV_DC4_EINT) /* DCDC4 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC4); + if (uv & WM8350_UV_DC5_EINT) /* DCDC5 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC5); + if (uv & WM8350_UV_DC6_EINT) /* DCDC6 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_DC6); + if (uv & WM8350_UV_LDO1_EINT) /* LDO1 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO1); + if (uv & WM8350_UV_LDO2_EINT) /* LDO2 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO2); + if (uv & WM8350_UV_LDO3_EINT) /* LDO3 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO3); + if (uv & WM8350_UV_LDO4_EINT) /* LDO4 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_UV_LDO4); + } + + /* charger, RTC */ + if (status1) { + if (status1 & WM8350_CHG_BAT_HOT_EINT) /* battery too hot */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_BAT_HOT); + if (status1 & WM8350_CHG_BAT_COLD_EINT) /* battery too cold */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_BAT_COLD); + if (status1 & WM8350_CHG_BAT_FAIL_EINT) /* battery fail */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_BAT_FAIL); + if (status1 & WM8350_CHG_TO_EINT) /* charger timeout */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_TO); + if (status1 & WM8350_CHG_END_EINT) /* fast charge current */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_END); + if (status1 & WM8350_CHG_START_EINT) /* charging started */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_START); + if (status1 & WM8350_CHG_FAST_RDY_EINT) /* fast charge ready */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_FAST_RDY); + if (status1 & WM8350_CHG_VBATT_LT_3P9_EINT) /* battery voltage < 3.9 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); + if (status1 & WM8350_CHG_VBATT_LT_3P1_EINT) /* battery voltage < 3.1 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); + if (status1 & WM8350_CHG_VBATT_LT_2P85_EINT) /* battery voltage < 2.85 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); + + if (status1 & WM8350_RTC_ALM_EINT) /* alarm */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_RTC_ALM); + if (status1 & WM8350_RTC_SEC_EINT) /* second rollover */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_RTC_SEC); + if (status1 & WM8350_RTC_PER_EINT) /* periodic */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_RTC_PER); + } + + /* current sink, system, aux adc */ + if (status2) { + if (status2 & WM8350_CS1_EINT) /* current sink 1 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CS1); + if (status2 & WM8350_CS2_EINT) /* current sink 2 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CS2); + + if (status2 & WM8350_SYS_HYST_COMP_FAIL_EINT) /* comp fail */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_HYST_COMP_FAIL); + if (status2 & WM8350_SYS_CHIP_GT115_EINT) /* chip > 115 C */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_CHIP_GT115); + if (status2 & WM8350_SYS_CHIP_GT140_EINT) /* chip > 140 C */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_CHIP_GT140); + if (status2 & WM8350_SYS_WDOG_TO_EINT) /* heartbeat missed */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_SYS_WDOG_TO); + + if (status2 & WM8350_AUXADC_DATARDY_EINT) /* data ready */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DATARDY); + if (status2 & WM8350_AUXADC_DCOMP4_EINT) /* exceeds comp 4 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP4); + if (status2 & WM8350_AUXADC_DCOMP3_EINT) /* exceeds comp 3 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP3); + if (status2 & WM8350_AUXADC_DCOMP2_EINT) /* exceeds comp 2 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP2); + if (status2 & WM8350_AUXADC_DCOMP1_EINT) /* exceeds comp 1 */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_AUXADC_DCOMP1); + + if (status2 & WM8350_USB_LIMIT_EINT) /* usb limit */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_USB_LIMIT); + } + + /* wake, codec, ext */ + if (comp) { + if (comp & WM8350_WKUP_OFF_STATE_EINT) /* wake from off */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_OFF_STATE); + if (comp & WM8350_WKUP_HIB_STATE_EINT) /* wake from hibernate */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_HIB_STATE); + if (comp & WM8350_WKUP_CONV_FAULT_EINT) /* wake from fault */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_CONV_FAULT); + if (comp & WM8350_WKUP_WDOG_RST_EINT) /* wake from reset */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_WDOG_RST); + if (comp & WM8350_WKUP_GP_PWR_ON_EINT) /* power on changed */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_GP_PWR_ON); + if (comp & WM8350_WKUP_ONKEY_EINT) /* on key > specified time */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_ONKEY); + if (comp & WM8350_WKUP_GP_WAKEUP_EINT) /* wake from GPIO */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_WKUP_GP_WAKEUP); + + if (comp & WM8350_CODEC_JCK_DET_L_EINT) /* left chn Jack detect */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_JCK_DET_L); + if (comp & WM8350_CODEC_JCK_DET_R_EINT) /* right chn Jack detect */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_JCK_DET_R); + if (comp & WM8350_CODEC_MICSCD_EINT) /* mic detect */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_MICSCD); + if (comp & WM8350_CODEC_MICD_EINT) /* mic short circuit */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_CODEC_MICD); + + if (comp & WM8350_EXT_USB_FB_EINT) /* usb connect */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_EXT_USB_FB); + if (comp & WM8350_EXT_WALL_FB_EINT) /* wall adaptor connect */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_EXT_WALL_FB); + if (comp & WM8350_EXT_BAT_FB_EINT) /* battery insertion */ + wm8350_irq_call_worker(wm8350, WM8350_IRQ_EXT_BAT_FB); + } + + if (level_one & WM8350_GP_INT) { /* gpio */ + int i; + + for (i = 0; i < 12; i++) { + if (gpio & (1 << i)) + wm8350_irq_call_worker(wm8350, WM8350_IRQ_GPIO(i)); + } + } +} +EXPORT_SYMBOL_GPL(wm8350_irq_worker); + +int wm8350_register_irq(struct wm8350 *wm8350, int irq, + void (*handler)(struct wm8350 *, int, void*), void *data) +{ + if (irq < 0 || irq > WM8350_NUM_IRQ || !handler) + return -EINVAL; + + if (wm8350->irq[irq].handler) + return -EBUSY; + + mutex_lock(&wm8350->work_mutex); + wm8350->irq[irq].handler = handler; + wm8350->irq[irq].data = data; + mutex_unlock(&wm8350->work_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_register_irq); + +int wm8350_free_irq(struct wm8350 *wm8350, int irq) +{ + if (irq < 0 || irq > WM8350_NUM_IRQ) + return -EINVAL; + + mutex_lock(&wm8350->work_mutex); + wm8350->irq[irq].handler = NULL; + mutex_unlock(&wm8350->work_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_free_irq); + +int wm8350_mask_irq(struct wm8350 *wm8350, int irq) +{ + switch (irq) { + case WM8350_IRQ_CHG_BAT_HOT: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_BAT_HOT_EINT); + case WM8350_IRQ_CHG_BAT_COLD: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_BAT_COLD_EINT); + case WM8350_IRQ_CHG_BAT_FAIL: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_BAT_FAIL_EINT); + case WM8350_IRQ_CHG_TO: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_TO_EINT); + case WM8350_IRQ_CHG_END: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_END_EINT); + case WM8350_IRQ_CHG_START: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_START_EINT); + case WM8350_IRQ_CHG_FAST_RDY: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_FAST_RDY_EINT); + case WM8350_IRQ_RTC_PER: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_RTC_PER_EINT); + case WM8350_IRQ_RTC_SEC: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_RTC_SEC_EINT); + case WM8350_IRQ_RTC_ALM: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_RTC_ALM_EINT); + case WM8350_IRQ_CHG_VBATT_LT_3P9: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_VBATT_LT_3P9_EINT); + case WM8350_IRQ_CHG_VBATT_LT_3P1: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_VBATT_LT_3P1_EINT); + case WM8350_IRQ_CHG_VBATT_LT_2P85: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_VBATT_LT_2P85_EINT); + case WM8350_IRQ_CS1: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_CS1_EINT); + case WM8350_IRQ_CS2: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_CS2_EINT); + case WM8350_IRQ_USB_LIMIT: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_USB_LIMIT_EINT); + case WM8350_IRQ_AUXADC_DATARDY: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DATARDY_EINT); + case WM8350_IRQ_AUXADC_DCOMP4: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP4_EINT); + case WM8350_IRQ_AUXADC_DCOMP3: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP3_EINT); + case WM8350_IRQ_AUXADC_DCOMP2: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP2_EINT); + case WM8350_IRQ_AUXADC_DCOMP1: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP1_EINT); + case WM8350_IRQ_SYS_HYST_COMP_FAIL: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_HYST_COMP_FAIL_EINT); + case WM8350_IRQ_SYS_CHIP_GT115: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_CHIP_GT115_EINT); + case WM8350_IRQ_SYS_CHIP_GT140: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_CHIP_GT140_EINT); + case WM8350_IRQ_SYS_WDOG_TO: + return wm8350_set_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_WDOG_TO_EINT); + case WM8350_IRQ_UV_LDO4: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO4_EINT); + case WM8350_IRQ_UV_LDO3: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO3_EINT); + case WM8350_IRQ_UV_LDO2: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO2_EINT); + case WM8350_IRQ_UV_LDO1: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO1_EINT); + case WM8350_IRQ_UV_DC6: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC6_EINT); + case WM8350_IRQ_UV_DC5: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC5_EINT); + case WM8350_IRQ_UV_DC4: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC4_EINT); + case WM8350_IRQ_UV_DC3: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC3_EINT); + case WM8350_IRQ_UV_DC2: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC2_EINT); + case WM8350_IRQ_UV_DC1: + return wm8350_set_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC1_EINT); + case WM8350_IRQ_OC_LS: + return wm8350_set_bits(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS_MASK, + WM8350_IM_OC_LS_EINT); + case WM8350_IRQ_EXT_USB_FB: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_EXT_USB_FB_EINT); + case WM8350_IRQ_EXT_WALL_FB: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_EXT_WALL_FB_EINT); + case WM8350_IRQ_EXT_BAT_FB: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_EXT_BAT_FB_EINT); + case WM8350_IRQ_CODEC_JCK_DET_L: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_JCK_DET_L_EINT); + case WM8350_IRQ_CODEC_JCK_DET_R: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_JCK_DET_R_EINT); + case WM8350_IRQ_CODEC_MICSCD: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_MICSCD_EINT); + case WM8350_IRQ_CODEC_MICD: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_MICD_EINT); + case WM8350_IRQ_WKUP_OFF_STATE: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_OFF_STATE_EINT); + case WM8350_IRQ_WKUP_HIB_STATE: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_HIB_STATE_EINT); + case WM8350_IRQ_WKUP_CONV_FAULT: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_CONV_FAULT_EINT); + case WM8350_IRQ_WKUP_WDOG_RST: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_OFF_STATE_EINT); + case WM8350_IRQ_WKUP_GP_PWR_ON: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_GP_PWR_ON_EINT); + case WM8350_IRQ_WKUP_ONKEY: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_ONKEY_EINT); + case WM8350_IRQ_WKUP_GP_WAKEUP: + return wm8350_set_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_GP_WAKEUP_EINT); + case WM8350_IRQ_GPIO(0): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP0_EINT); + case WM8350_IRQ_GPIO(1): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP1_EINT); + case WM8350_IRQ_GPIO(2): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP2_EINT); + case WM8350_IRQ_GPIO(3): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP3_EINT); + case WM8350_IRQ_GPIO(4): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP4_EINT); + case WM8350_IRQ_GPIO(5): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP5_EINT); + case WM8350_IRQ_GPIO(6): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP6_EINT); + case WM8350_IRQ_GPIO(7): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP7_EINT); + case WM8350_IRQ_GPIO(8): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP8_EINT); + case WM8350_IRQ_GPIO(9): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP9_EINT); + case WM8350_IRQ_GPIO(10): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP10_EINT); + case WM8350_IRQ_GPIO(11): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP11_EINT); + case WM8350_IRQ_GPIO(12): + return wm8350_set_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP12_EINT); + default: + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_mask_irq); + +int wm8350_unmask_irq(struct wm8350 *wm8350, int irq) +{ + switch (irq) { + case WM8350_IRQ_CHG_BAT_HOT: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_BAT_HOT_EINT); + case WM8350_IRQ_CHG_BAT_COLD: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_BAT_COLD_EINT); + case WM8350_IRQ_CHG_BAT_FAIL: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_BAT_FAIL_EINT); + case WM8350_IRQ_CHG_TO: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_TO_EINT); + case WM8350_IRQ_CHG_END: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_END_EINT); + case WM8350_IRQ_CHG_START: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_START_EINT); + case WM8350_IRQ_CHG_FAST_RDY: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_FAST_RDY_EINT); + case WM8350_IRQ_RTC_PER: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_RTC_PER_EINT); + case WM8350_IRQ_RTC_SEC: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_RTC_SEC_EINT); + case WM8350_IRQ_RTC_ALM: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_RTC_ALM_EINT); + case WM8350_IRQ_CHG_VBATT_LT_3P9: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_VBATT_LT_3P9_EINT); + case WM8350_IRQ_CHG_VBATT_LT_3P1: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_VBATT_LT_3P1_EINT); + case WM8350_IRQ_CHG_VBATT_LT_2P85: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_1_MASK, + WM8350_IM_CHG_VBATT_LT_2P85_EINT); + case WM8350_IRQ_CS1: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_CS1_EINT); + case WM8350_IRQ_CS2: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_CS2_EINT); + case WM8350_IRQ_USB_LIMIT: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_USB_LIMIT_EINT); + case WM8350_IRQ_AUXADC_DATARDY: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DATARDY_EINT); + case WM8350_IRQ_AUXADC_DCOMP4: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP4_EINT); + case WM8350_IRQ_AUXADC_DCOMP3: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP3_EINT); + case WM8350_IRQ_AUXADC_DCOMP2: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP2_EINT); + case WM8350_IRQ_AUXADC_DCOMP1: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_AUXADC_DCOMP1_EINT); + case WM8350_IRQ_SYS_HYST_COMP_FAIL: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_HYST_COMP_FAIL_EINT); + case WM8350_IRQ_SYS_CHIP_GT115: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_CHIP_GT115_EINT); + case WM8350_IRQ_SYS_CHIP_GT140: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_CHIP_GT140_EINT); + case WM8350_IRQ_SYS_WDOG_TO: + return wm8350_clear_bits(wm8350, WM8350_INTERRUPT_STATUS_2_MASK, + WM8350_IM_SYS_WDOG_TO_EINT); + case WM8350_IRQ_UV_LDO4: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO4_EINT); + case WM8350_IRQ_UV_LDO3: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO3_EINT); + case WM8350_IRQ_UV_LDO2: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO2_EINT); + case WM8350_IRQ_UV_LDO1: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_LDO1_EINT); + case WM8350_IRQ_UV_DC6: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC6_EINT); + case WM8350_IRQ_UV_DC5: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC5_EINT); + case WM8350_IRQ_UV_DC4: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC4_EINT); + case WM8350_IRQ_UV_DC3: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC3_EINT); + case WM8350_IRQ_UV_DC2: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC2_EINT); + case WM8350_IRQ_UV_DC1: + return wm8350_clear_bits(wm8350, WM8350_UNDER_VOLTAGE_INTERRUPT_STATUS_MASK, + WM8350_IM_UV_DC1_EINT); + case WM8350_IRQ_OC_LS: + return wm8350_clear_bits(wm8350, WM8350_OVER_CURRENT_INTERRUPT_STATUS_MASK, + WM8350_IM_OC_LS_EINT); + case WM8350_IRQ_EXT_USB_FB: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_EXT_USB_FB_EINT); + case WM8350_IRQ_EXT_WALL_FB: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_EXT_WALL_FB_EINT); + case WM8350_IRQ_EXT_BAT_FB: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_EXT_BAT_FB_EINT); + case WM8350_IRQ_CODEC_JCK_DET_L: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_JCK_DET_L_EINT); + case WM8350_IRQ_CODEC_JCK_DET_R: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_JCK_DET_R_EINT); + case WM8350_IRQ_CODEC_MICSCD: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_MICSCD_EINT); + case WM8350_IRQ_CODEC_MICD: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_CODEC_MICD_EINT); + case WM8350_IRQ_WKUP_OFF_STATE: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_OFF_STATE_EINT); + case WM8350_IRQ_WKUP_HIB_STATE: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_HIB_STATE_EINT); + case WM8350_IRQ_WKUP_CONV_FAULT: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_CONV_FAULT_EINT); + case WM8350_IRQ_WKUP_WDOG_RST: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_OFF_STATE_EINT); + case WM8350_IRQ_WKUP_GP_PWR_ON: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_GP_PWR_ON_EINT); + case WM8350_IRQ_WKUP_ONKEY: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_ONKEY_EINT); + case WM8350_IRQ_WKUP_GP_WAKEUP: + return wm8350_clear_bits(wm8350, WM8350_COMPARATOR_INTERRUPT_STATUS_MASK, + WM8350_IM_WKUP_GP_WAKEUP_EINT); + case WM8350_IRQ_GPIO(0): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP0_EINT); + case WM8350_IRQ_GPIO(1): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP1_EINT); + case WM8350_IRQ_GPIO(2): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP2_EINT); + case WM8350_IRQ_GPIO(3): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP3_EINT); + case WM8350_IRQ_GPIO(4): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP4_EINT); + case WM8350_IRQ_GPIO(5): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP5_EINT); + case WM8350_IRQ_GPIO(6): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP6_EINT); + case WM8350_IRQ_GPIO(7): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP7_EINT); + case WM8350_IRQ_GPIO(8): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP8_EINT); + case WM8350_IRQ_GPIO(9): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP9_EINT); + case WM8350_IRQ_GPIO(10): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP10_EINT); + case WM8350_IRQ_GPIO(11): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP11_EINT); + case WM8350_IRQ_GPIO(12): + return wm8350_clear_bits(wm8350, WM8350_GPIO_INTERRUPT_STATUS_MASK, + WM8350_IM_GP12_EINT); + default: + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_unmask_irq); + +static int gpio_set_dir(struct wm8350 *wm8350, int gpio, int dir) +{ + int ret; + + wm8350_reg_unlock(wm8350); + if (dir == WM8350_GPIO_DIR_OUT) + ret = wm8350_clear_bits(wm8350, + WM8350_GPIO_CONFIGURATION_I_O, 1 << gpio); + else + ret = wm8350_set_bits(wm8350, + WM8350_GPIO_CONFIGURATION_I_O, 1 << gpio); + wm8350_reg_lock(wm8350); + return ret; +} + +static int gpio_set_debounce(struct wm8350 *wm8350, int gpio, int db) +{ + if (db == WM8350_GPIO_DEBOUNCE_ON) + return wm8350_set_bits(wm8350, + WM8350_GPIO_DEBOUNCE, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_DEBOUNCE, 1 << gpio); +} + +static int gpio_set_func(struct wm8350 *wm8350, int gpio, int func) +{ + u16 reg; + + wm8350_reg_unlock(wm8350); + switch (gpio) { + case 0: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP0_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 0)); + break; + case 1: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP1_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 4)); + break; + case 2: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP2_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 8)); + break; + case 3: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_1) + & ~WM8350_GP3_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_1, + reg | ((func & 0xf) << 12)); + break; + case 4: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP4_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 0)); + break; + case 5: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP5_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 4)); + break; + case 6: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP6_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 8)); + break; + case 7: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_2) + & ~WM8350_GP7_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_2, + reg | ((func & 0xf) << 12)); + break; + case 8: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP8_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 0)); + break; + case 9: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP9_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 4)); + break; + case 10: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP10_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 8)); + break; + case 11: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_3) + & ~WM8350_GP11_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_3, + reg | ((func & 0xf) << 12)); + break; + case 12: + reg = wm8350_reg_read(wm8350, WM8350_GPIO_FUNCTION_SELECT_4) + & ~WM8350_GP12_FN_MASK; + wm8350_reg_write(wm8350, WM8350_GPIO_FUNCTION_SELECT_4, + reg | ((func & 0xf) << 0)); + break; + default: + wm8350_reg_lock(wm8350); + return -EINVAL; + } + + wm8350_reg_lock(wm8350); + return 0; +} + +int wm8350_gpio_set_status(struct wm8350 *wm8350, int gpio, int status) +{ + if (status) + return wm8350_set_bits(wm8350, + WM8350_GPIO_PIN_STATUS, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_PIN_STATUS, 1 << gpio); +} +EXPORT_SYMBOL_GPL(wm8350_gpio_set_status); + +int wm8350_gpio_get_status(struct wm8350 *wm8350, int gpio) +{ + return (wm8350_reg_read(wm8350, WM8350_GPIO_PIN_STATUS) & + (1 << gpio)) ? 1: 0; +} +EXPORT_SYMBOL_GPL(wm8350_gpio_get_status); + +static int gpio_set_pull_up(struct wm8350 *wm8350, int gpio, int up) +{ + if (up) + return wm8350_set_bits(wm8350, + WM8350_GPIO_PIN_PULL_UP_CONTROL, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_PIN_PULL_UP_CONTROL, 1 << gpio); +} + +static int gpio_set_pull_down(struct wm8350 *wm8350, int gpio, int down) +{ + if (down) + return wm8350_set_bits(wm8350, + WM8350_GPIO_PULL_DOWN_CONTROL, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_PULL_DOWN_CONTROL, 1 << gpio); +} + +static int gpio_set_polarity(struct wm8350 *wm8350, int gpio, int pol) +{ + if (pol == WM8350_GPIO_ACTIVE_HIGH) + return wm8350_set_bits(wm8350, + WM8350_GPIO_PIN_POLARITY_TYPE, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_PIN_POLARITY_TYPE, 1 << gpio); +} + +static int gpio_set_invert(struct wm8350 *wm8350, int gpio, int invert) +{ + if (invert == WM8350_GPIO_INVERT_ON) + return wm8350_set_bits(wm8350, + WM8350_GPIO_INTERRUPT_MODE, 1 << gpio); + else + return wm8350_clear_bits(wm8350, + WM8350_GPIO_INTERRUPT_MODE, 1 << gpio); +} + +int wm8350_gpio_config(struct wm8350 *wm8350, int gpio, int dir, int func, + int pol, int pull, int invert, int debounce) +{ + /* make sure we never pull up and down at the same time */ + if (pull == WM8350_GPIO_PULL_NONE) { + if (gpio_set_pull_up(wm8350, gpio, 0)) + goto err; + if (gpio_set_pull_down(wm8350, gpio, 0)) + goto err; + } else if (pull == WM8350_GPIO_PULL_UP) { + if (gpio_set_pull_down(wm8350, gpio, 0)) + goto err; + if (gpio_set_pull_up(wm8350, gpio, 1)) + goto err; + } else if (pull == WM8350_GPIO_PULL_DOWN) { + if (gpio_set_pull_up(wm8350, gpio, 0)) + goto err; + if (gpio_set_pull_down(wm8350, gpio, 1)) + goto err; + } + + if (gpio_set_invert(wm8350, gpio, invert)) + goto err; + if (gpio_set_polarity(wm8350, gpio, pol)) + goto err; + if (gpio_set_debounce(wm8350, gpio, debounce)) + goto err; + if (gpio_set_dir(wm8350, gpio, dir)) + goto err; + return gpio_set_func(wm8350, gpio, func); + +err: + return -EIO; +} +EXPORT_SYMBOL_GPL(wm8350_gpio_config); + +int wm8350_read_auxadc(struct wm8350 *wm8350, int channel, int scale, int vref) +{ + u16 reg, result = 0; + int tries = 5; + + if (channel < WM8350_AUXADC_AUX1 || channel > WM8350_AUXADC_TEMP) + return -EINVAL; + if (channel >= WM8350_AUXADC_USB && channel <= WM8350_AUXADC_TEMP + && (scale != 0 || vref != 0)) + return -EINVAL; + + mutex_lock(&auxadc_mutex); + + /* Turn on the ADC */ + reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5, reg | WM8350_AUXADC_ENA); + + if (scale || vref) { + reg = scale << 13; + reg |= vref << 12; + wm8350_reg_write(wm8350, WM8350_AUX1_READBACK + channel, reg); + } + + reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1); + reg |= 1 << channel | WM8350_AUXADC_POLL; + wm8350_reg_write(wm8350, WM8350_DIGITISER_CONTROL_1, reg); + + do { + schedule_timeout_interruptible(1); + reg = wm8350_reg_read(wm8350, WM8350_DIGITISER_CONTROL_1); + } while (tries-- && (reg & WM8350_AUXADC_POLL)); + + if (!tries) + printk (KERN_ERR "wm8350: adc chn %d read timeout\n", channel); + else + result = wm8350_reg_read(wm8350, + WM8350_AUX1_READBACK + channel); + + /* Turn off the ADC */ + reg=wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5); + wm8350_reg_write(wm8350, WM8350_POWER_MGMT_5, reg & ~WM8350_AUXADC_ENA); + + mutex_unlock(&auxadc_mutex); + return result & WM8350_AUXADC_DATA1_MASK; +} +EXPORT_SYMBOL_GPL(wm8350_read_auxadc); + +static void wm8350_rtc_dev_release(struct device *dev){} + +int wm8350_device_register_rtc(struct wm8350 *wm8350) +{ + int ret; + + strcpy(wm8350->rtc.dev.bus_id, "wm8350-rtc"); + wm8350->rtc.dev.bus = &wm8350_bus_type; + wm8350->rtc.dev.parent = &wm8350->i2c_client->dev; + wm8350->rtc.dev.release = wm8350_rtc_dev_release; + + ret = device_register(&wm8350->rtc.dev); + if (ret < 0) + printk(KERN_ERR "failed to register WM8350 RTC device\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_device_register_rtc); + +static void wm8350_wdg_dev_release(struct device *dev){} + +int wm8350_device_register_wdg(struct wm8350 *wm8350) +{ + int ret; + + strcpy(wm8350->wdg.dev.bus_id, "wm8350-wdt"); + wm8350->wdg.dev.bus = &wm8350_bus_type; + wm8350->wdg.dev.parent = &wm8350->i2c_client->dev; + wm8350->wdg.dev.release = wm8350_wdg_dev_release; + + ret = device_register(&wm8350->wdg.dev); + if (ret < 0) + printk(KERN_ERR "failed to register WM8350 Watchdog device\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_device_register_wdg); + +static void wm8350_power_dev_release(struct device *dev){} + +int wm8350_device_register_power(struct wm8350 *wm8350) +{ + int ret; + + strcpy(wm8350->power.dev.bus_id, "wm8350-power"); + wm8350->power.dev.bus = &wm8350_bus_type; + wm8350->power.dev.parent = &wm8350->i2c_client->dev; + wm8350->power.dev.release = wm8350_power_dev_release; + + ret = device_register(&wm8350->power.dev); + if (ret < 0) + printk(KERN_ERR "failed to register WM8350 Power device\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(wm8350_device_register_power); diff --git a/drivers/regulator/wm8350/reg-wm8350.c b/drivers/regulator/wm8350/reg-wm8350.c new file mode 100644 index 0000000..bb25cea --- /dev/null +++ b/drivers/regulator/wm8350/reg-wm8350.c @@ -0,0 +1,1140 @@ +/* + * wm8350_pmu.c -- Power Management Driver for Wolfson WM8350 PMIC + * + * Copyright 2007 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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. + * + * Revision history + * 23rd Jan 2007 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WM8350_CORE_VERSION "0.7" + +/* debug */ +#define WM8350_DEBUG 0 +#if WM8350_DEBUG +#define dbg(format, arg...) printk(format, ## arg) +#else +#define dbg(format, arg...) +#endif + +/* hundredths of uA, 405 = 4.05 uA */ +static const int isink_cur[] = { + 405, 482, 573, 681, 810, 963, 1146, 1362, 1620, 1927, 2291, 2725, + 3240, 3853, 4582, 5449, 6480, 7706, 9164, 10898, 12960, 15412, 18328, + 21796, 25920, 30824, 36656, 43592, 51840, 61648, 73313, 87184, + 103680, 123297, 146626, 174368, 207360, 246594, 293251, 348737, + 414720, 493188, 586503, 697473, 829440, 986376, 1173005, 1394946, + 1658880, 1972752, 2346011, 2789892, 3317760, 3945504, 4692021, + 5579785, 6635520, 7891008, 9384042, 11159570, 13271040, 15782015, + 18768085, 22319140 +}; + +static int get_isink_val(int uA) +{ + int i, huA = uA * 100; + + for (i = ARRAY_SIZE(isink_cur) - 1; i >= 0 ; i--) { + if (huA > isink_cur[i]) + return i; + } + return 0; +} + +static int wm8350_isink_set_current(struct regulator_cdev *rcdev, int uA) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int isink = rcdev_get_id(rcdev); + u16 val; + + switch (isink) { + case WM8350_ISINK_A: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) & + ~WM8350_CS1_ISEL_MASK; + wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_A, val | + get_isink_val(uA)); + break; + case WM8350_ISINK_B: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) & + ~WM8350_CS1_ISEL_MASK; + wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_B, val | + get_isink_val(uA)); + break; + default: + return -EINVAL; + } + return 0; +} + +static int wm8350_isink_get_current(struct regulator_cdev *rcdev) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int isink = rcdev_get_id(rcdev); + u16 val; + + switch (isink) { + case WM8350_ISINK_A: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) & + WM8350_CS1_ISEL_MASK; + break; + case WM8350_ISINK_B: + val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) & + WM8350_CS1_ISEL_MASK; + break; + default: + return 0; + } + + return val * 1000; +} + +/* turn on ISINK followed by DCDC */ +static int wm8350_isink_enable(struct regulator_cdev *rcdev) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int isink = rcdev_get_id(rcdev); + + /* TODO: add ISINK's C,D,E */ + switch (isink) { + case WM8350_ISINK_A: + switch (pmic->isink_A_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS1_ENA); + wm8350_set_bits(wm8350, WM8350_CSA_FLASH_CONTROL, + WM8350_CS1_DRIVE); + wm8350_set_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (pmic->isink_A_dcdc - WM8350_DCDC_1)); + break; + default: + return -EINVAL; + } + break; + case WM8350_ISINK_B: + switch (pmic->isink_B_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS2_ENA); + wm8350_set_bits(wm8350, WM8350_CSB_FLASH_CONTROL, + WM8350_CS2_DRIVE); + wm8350_set_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (pmic->isink_B_dcdc - WM8350_DCDC_1)); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int wm8350_isink_disable(struct regulator_cdev *rcdev) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int isink = rcdev_get_id(rcdev); + + /* TODO: add ISINK's C,D,E */ + switch (isink) { + case WM8350_ISINK_A: + switch (pmic->isink_A_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_clear_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (pmic->isink_A_dcdc - WM8350_DCDC_1)); + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS1_ENA); + break; + default: + return -EINVAL; + } + break; + case WM8350_ISINK_B: + switch (pmic->isink_B_dcdc) { + case WM8350_DCDC_2: + case WM8350_DCDC_5: + wm8350_clear_bits(wm8350, WM8350_DCDC_LDO_REQUESTED, + 1 << (pmic->isink_B_dcdc - WM8350_DCDC_1)); + wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_7, + WM8350_CS2_ENA); + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int wm8350_isink_is_enabled(struct regulator_cdev *rcdev) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int isink = rcdev_get_id(rcdev); + + switch (isink) { + case WM8350_ISINK_A: + return wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) & + 0x8000; + case WM8350_ISINK_B: + return wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) & + 0x8000; + } + return -EINVAL; +} + +int wm8350_isink_set_flash(struct wm8350_pmic *pmic, int isink, u16 mode, + u16 trigger, u16 duration, u16 on_ramp, u16 off_ramp, u16 drive) +{ + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + + switch (isink) { + case WM8350_ISINK_A: + wm8350_reg_write(wm8350, WM8350_CSA_FLASH_CONTROL, + (mode ? WM8350_CS1_FLASH_MODE : 0) | + (trigger ? WM8350_CS1_TRIGSRC : 0) | + duration | on_ramp | off_ramp | drive); + break; + case WM8350_ISINK_B: + wm8350_reg_write(wm8350, WM8350_CSB_FLASH_CONTROL, + (mode ? WM8350_CS2_FLASH_MODE : 0) | + (trigger ? WM8350_CS2_TRIGSRC : 0) | + duration | on_ramp | off_ramp | drive); + break; + default: + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_isink_set_flash); + +static int wm8350_dcdc_set_voltage(struct regulator_cdev *rcdev, int uV) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int volt_reg, dcdc = rcdev_get_id(rcdev), mV = uV / 1000; + u16 val; + + dbg("%s %d mV %d\n", __FUNCTION__, dcdc, mV); + + if (mV < 850 || mV > 4025) { + printk(KERN_ERR "wm8350: dcdc %d voltage %d mV out of range\n", + dcdc, mV); + return -EINVAL; + } + + switch (dcdc) { + case WM8350_DCDC_1: + volt_reg = WM8350_DCDC1_CONTROL; + break; + case WM8350_DCDC_3: + volt_reg = WM8350_DCDC3_CONTROL; + break; + case WM8350_DCDC_4: + volt_reg = WM8350_DCDC4_CONTROL; + break; + case WM8350_DCDC_6: + volt_reg = WM8350_DCDC6_CONTROL; + break; + case WM8350_DCDC_2: + case WM8350_DCDC_5: + default: + return -EINVAL; + } + + /* all DCDC's have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_DC1_VSEL_MASK; + wm8350_reg_write(wm8350, volt_reg, val | WM8350_DC1_VSEL(mV)); + return 0; +} + +static int wm8350_dcdc_get_voltage(struct regulator_cdev *rcdev) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int volt_reg, dcdc = rcdev_get_id(rcdev); + u16 val; + + switch (dcdc) { + case WM8350_DCDC_1: + volt_reg = WM8350_DCDC1_CONTROL; + break; + case WM8350_DCDC_3: + volt_reg = WM8350_DCDC3_CONTROL; + break; + case WM8350_DCDC_4: + volt_reg = WM8350_DCDC4_CONTROL; + break; + case WM8350_DCDC_6: + volt_reg = WM8350_DCDC6_CONTROL; + break; + case WM8350_DCDC_2: + case WM8350_DCDC_5: + default: + return -EINVAL; + } + + /* all DCDC's have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & WM8350_DC1_VSEL_MASK; + return WM8350_DC6_VSEL_V(val) * 1000; +} + +int wm8350_dcdc_set_image_voltage(struct wm8350_pmic *pmic, int dcdc, int mV, + int mode, int signal) +{ + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int volt_reg; + + dbg("%s %d mV %d\n", __FUNCTION__, dcdc, mV); + + if (mV && (mV < 850 || mV > 4025)) { + printk(KERN_ERR "wm8350: dcdc %d image voltage %d mV out of range\n", + dcdc, mV); + return -EINVAL; + } + if (mV == 0) + mV = 850; + + switch (dcdc) { + case WM8350_DCDC_1: + volt_reg = WM8350_DCDC1_LOW_POWER; + break; + case WM8350_DCDC_3: + volt_reg = WM8350_DCDC3_LOW_POWER; + break; + case WM8350_DCDC_4: + volt_reg = WM8350_DCDC4_LOW_POWER; + break; + case WM8350_DCDC_6: + volt_reg = WM8350_DCDC6_LOW_POWER; + break; + case WM8350_DCDC_2: + case WM8350_DCDC_5: + default: + return -EINVAL; + } + + /* all DCDC's have same mV bits */ + wm8350_reg_write(wm8350, volt_reg, WM8350_DC1_VSEL(mV) | mode | signal); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_dcdc_set_image_voltage); + +static int wm8350_ldo_set_voltage(struct regulator_cdev *rcdev, int uV) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int volt_reg, ldo = rcdev_get_id(rcdev), mV = uV / 1000; + u16 val; + + dbg("%s %d mV %d\n", __FUNCTION__, ldo, mV); + + if (mV < 900 || mV > 3300) { + printk(KERN_ERR "wm8350: ldo %d voltage %d mV out of range\n", + ldo, mV); + return -EINVAL; + } + + switch (ldo) { + case WM8350_LDO_1: + volt_reg = WM8350_LDO1_CONTROL; + break; + case WM8350_LDO_2: + volt_reg = WM8350_LDO2_CONTROL; + break; + case WM8350_LDO_3: + volt_reg = WM8350_LDO3_CONTROL; + break; + case WM8350_LDO_4: + volt_reg = WM8350_LDO4_CONTROL; + break; + default: + return -EINVAL; + } + + /* all LDO's have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & ~WM8350_LDO1_VSEL_MASK; + wm8350_reg_write(wm8350, volt_reg, val | WM8350_LDO1_VSEL(mV)); + return 0; +} + +static int wm8350_ldo_get_voltage(struct regulator_cdev *rcdev) +{ + struct wm8350_pmic *pmic = rcdev_get_drvdata(rcdev); + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int volt_reg, ldo = rcdev_get_id(rcdev); + u16 val; + + switch (ldo) { + case WM8350_LDO_1: + volt_reg = WM8350_LDO1_CONTROL; + break; + case WM8350_LDO_2: + volt_reg = WM8350_LDO2_CONTROL; + break; + case WM8350_LDO_3: + volt_reg = WM8350_LDO3_CONTROL; + break; + case WM8350_LDO_4: + volt_reg = WM8350_LDO4_CONTROL; + break; + default: + return -EINVAL; + } + + /* all LDO's have same mV bits */ + val = wm8350_reg_read(wm8350, volt_reg) & WM8350_LDO1_VSEL_MASK; + return WM8350_LDO1_VSEL_V(val) * 1000; +} + +int wm8350_ldo_set_image_voltage(struct wm8350_pmic *pmic, int ldo, int mV, + int mode, int signal) +{ + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int volt_reg; + + dbg("%s %d mV %d\n", __FUNCTION__, ldo, mV); + + if (mV < 900 || mV > 3300) { + printk(KERN_ERR "wm8350: ldo %d voltage %d mV out of range\n", + ldo, mV); + return -EINVAL; + } + + switch (ldo) { + case WM8350_LDO_1: + volt_reg = WM8350_LDO1_LOW_POWER; + break; + case WM8350_LDO_2: + volt_reg = WM8350_LDO2_LOW_POWER; + break; + case WM8350_LDO_3: + volt_reg = WM8350_LDO3_LOW_POWER; + break; + case WM8350_LDO_4: + volt_reg = WM8350_LDO4_LOW_POWER; + break; + default: + return -EINVAL; + } + + /* all LDO's have same mV bits */ + wm8350_reg_write(wm8350, volt_reg, WM8350_LDO1_VSEL(mV) | mode | signal); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_ldo_set_image_voltage); + +int wm8350_dcdc_set_slot(struct wm8350_pmic *pmic, int dcdc, u16 start, + u16 stop, u16 fault) +{ + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int slot_reg; + u16 val; + + dbg("%s %d start %d stop %d\n", __FUNCTION__, dcdc, start, stop); + + /* slot valid ? */ + if (start > 15 || stop > 15) + return -EINVAL; + + switch (dcdc) { + case WM8350_DCDC_1: + slot_reg = WM8350_DCDC1_TIMEOUTS; + break; + case WM8350_DCDC_2: + slot_reg = WM8350_DCDC2_TIMEOUTS; + break; + case WM8350_DCDC_3: + slot_reg = WM8350_DCDC3_TIMEOUTS; + break; + case WM8350_DCDC_4: + slot_reg = WM8350_DCDC4_TIMEOUTS; + break; + case WM8350_DCDC_5: + slot_reg = WM8350_DCDC5_TIMEOUTS; + break; + case WM8350_DCDC_6: + slot_reg = WM8350_DCDC6_TIMEOUTS; + break; + default: + return -EINVAL; + } + + val = wm8350_reg_read(wm8350, slot_reg) & + ~(WM8350_DC1_ENSLOT_MASK | WM8350_DC1_SDSLOT_MASK | + WM8350_DC1_ERRACT_MASK); + wm8350_reg_write(wm8350, slot_reg, + val | (start << WM8350_DC1_ENSLOT_SHIFT) | + (stop << WM8350_DC1_SDSLOT_SHIFT) | + (fault << WM8350_DC1_ERRACT_SHIFT)); + + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_dcdc_set_slot); + +int wm8350_ldo_set_slot(struct wm8350_pmic *pmic, int ldo, u16 start, + u16 stop) +{ + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + int slot_reg; + u16 val; + + dbg("%s %d start %d stop %d\n", __FUNCTION__, ldo, start, stop); + + /* slot valid ? */ + if (start > 15 || stop > 15) + return -EINVAL; + + switch (ldo) { + case WM8350_LDO_1: + slot_reg = WM8350_LDO1_TIMEOUTS; + break; + case WM8350_LDO_2: + slot_reg = WM8350_LDO2_TIMEOUTS; + break; + case WM8350_LDO_3: + slot_reg = WM8350_LDO3_TIMEOUTS; + break; + case WM8350_LDO_4: + slot_reg = WM8350_LDO4_TIMEOUTS; + break; + default: + return -EINVAL; + } + + val = wm8350_reg_read(wm8350, slot_reg) & ~WM8350_LDO1_SDSLOT_MASK; + wm8350_reg_write(wm8350, slot_reg, val | ((start << 10) | (stop << 6))); + return 0; +} +EXPORT_SYMBOL_GPL(wm8350_ldo_set_slot); + +int wm8350_dcdc25_set_mode(struct wm8350_pmic *pmic, int dcdc, u16 mode, + u16 ilim, u16 ramp, u16 feedback) +{ + struct wm8350 *wm8350 = to_wm8350_from_pmic(pmic); + u16 val; + + dbg("%s